본문으로 건너뛰기

소스 변경 전체 보기

PHP 8 · GODO26
변경 파일 수: 209개
변경 파일 목록
총 209개
엑셀 다운로드 시 다운로드 기록 수집
ExcelRequest.phpComponent/Excel/
개인정보 수집 및 이용 동의 템플릿 개정
excel_member_up.phpAdmin/member/member_detail_search.phpAdmin/member/member_joinitem.phpAdmin/member/member_modify.phpAdmin/member/member_register.phpAdmin/member/_member_view_app_device.phpAdmin/member/_member_view_business.phpAdmin/member/_member_view_history.phpAdmin/member/_member_view_other.phpAdmin/member/_member_view.phpAdmin/member/base_agreement_with_private.phpAdmin/policy/member_crm_detail.phpAdmin/share/BuyerInformCode.phpComponent/Agreement/BuyerInform.phpComponent/Agreement/BuyerInformVo.phpComponent/Agreement/DBTableField.phpComponent/Database/MemberTableField.phpComponent/Database/ExcelMember.phpComponent/Excel/History.phpComponent/Member/MemberAdmin.phpComponent/Member/MemberDAO.phpComponent/Member/Member.phpComponent/Member/MemberVO.phpComponent/Member/MyPage.phpComponent/Member/MemberUtil.phpComponent/Member/Util/MemberModifyController.phpController/Admin/Member/MemberPsController.phpController/Admin/Member/MemberRegisterController.phpController/Admin/Member/BaseAgreementWithPrivateController.phpController/Admin/Policy/BaseAgreementWithPrivatePsController.phpController/Admin/Policy/MemberCrmDetailController.phpController/Admin/Share/JoinAgreementController.phpController/Front/Member/LoginPsController.phpController/Front/Member/MyPageController.phpController/Front/Mypage/AgreementController.phpController/Front/Service/join_agreement.htmldata/skin/front/moment/member/_join_view.htmldata/skin/front/moment/member/_join_view_other.htmldata/skin/front/moment/member/my_page.htmldata/skin/front/moment/mypage/join_agreement.htmldata/skin/front/mygreek/member/_join_view.htmldata/skin/front/mygreek/member/_join_view_other.htmldata/skin/front/mygreek/member/my_page.htmldata/skin/front/mygreek/mypage/join_agreement.htmldata/skin/front/story_g/member/_join_view.htmldata/skin/front/story_g/member/_join_view_other.htmldata/skin/front/story_g/member/my_page.htmldata/skin/front/story_g/mypage/join_agreement.htmldata/skin/mobile/moment/member/_join_view.htmldata/skin/mobile/moment/member/_join_view_other.htmldata/skin/mobile/moment/member/my_page.htmldata/skin/mobile/moment/mypage/join_agreement.htmldata/skin/mobile/mygreek/member/_join_view.htmldata/skin/mobile/mygreek/member/_join_view_other.htmldata/skin/mobile/mygreek/member/my_page.htmldata/skin/mobile/mygreek/mypage/join_agreement.htmldata/skin/mobile/story_g/member/_join_view.htmldata/skin/mobile/story_g/member/_join_view_other.htmldata/skin/mobile/story_g/member/my_page.htmldata/skin/mobile/story_g/mypage/
지역별 추가배송비 구간별 반복 부과 옵션 추가
delivery_regist.phpAdmin/policy/delivery_regist.phpAdmin/provider/policy/Cart.phpComponent/Cart/DBTableField.phpComponent/Database/DeliveryCart.phpComponent/Delivery/Delivery.phpComponent/Delivery/DeliveryRegistController.phpController/Admin/Policy/layer.cssdata/skin/front/moment/css/common/layer/goods_view.htmldata/skin/front/moment/goods/layer_option.htmldata/skin/front/moment/goods/layer.cssdata/skin/front/mygreek/css/common/layer/goods_view.htmldata/skin/front/mygreek/goods/layer_option.htmldata/skin/front/mygreek/goods/icon_warning_green.svgdata/skin/front/mygreek/img/icon/etc/gd_share.cssdata/skin/front/story_g/css/goods_view.htmldata/skin/front/story_g/goods/layer_option.htmldata/skin/front/story_g/goods/goods_view.htmldata/skin/mobile/moment/goods/goods_view.htmldata/skin/mobile/mygreek/goods/goods_view.htmldata/skin/mobile/story_g/goods/
디자인 에디터 사용 이력 수집 기능 추가
layout_design_editor.phpAdmin/
- [게시판] 비회원 글쓰기 보안 강화
Board.phpComponent/Board/BoardPsController.phpController/Mobile/Board/
LMS 메시지 발송 시 제목 입력 기능
auto_send_config_recipient_section.phpAdmin/crm/auto_send_config/auto_send_config.phpAdmin/crm/crm_recipe_send.phpAdmin/crm/layer_preview_auto_send.phpAdmin/crm/layer_template_contents_sms.phpAdmin/crm/message_template_register.phpAdmin/crm/template_contents_sms.phpAdmin/crm/message_template_register/layer_member_sms_history_result.phpAdmin/crm/mobile_message_history/layer_sms_contents.phpAdmin/crm/mobile_message_history/layer_sms_history_result.phpAdmin/crm/mobile_message_history/layer_kakao_alimtalk_content_setting.phpAdmin/crm/mobile_send/layer_kakao_friendtalk_content_setting.phpAdmin/crm/mobile_send/layer_mobile_send_save_sms_template.phpAdmin/crm/mobile_send/layer_mobile_send_sms_template.phpAdmin/crm/mobile_send/layer_mobile_send_sms_template_result.phpAdmin/crm/mobile_send/layer_myapp_push_content_setting.phpAdmin/crm/mobile_send/layer_send_method_setting.phpAdmin/crm/mobile_send/layer_sms_content_setting.phpAdmin/crm/mobile_send/mobile_send.phpAdmin/crm/layer_preview_mobile_send_alternative.phpAdmin/crm/preview_mobile_send/layer_preview_mobile_send_sms.phpAdmin/crm/preview_mobile_send/sms_auto.phpAdmin/member/sms_log.phpAdmin/member/sms_send.phpAdmin/member/cart_remind_regist.phpAdmin/promotion/comeback_coupon_regist.phpAdmin/promotion/layer_cart_remind_message.phpAdmin/promotion/layer_comeback_coupon_sms.phpAdmin/share/layer_standby_sms.phpAdmin/share/member_crm_sms.phpAdmin/share/CartRemind.phpComponent/CartRemind/CouponAdmin.phpComponent/Coupon/DBTableField.phpComponent/Database/SmsAdmin.phpComponent/Sms/SmsAuto.phpComponent/Sms/SmsSender.phpComponent/Sms/LayerPreviewMobileSendController.phpController/Admin/Crm/LayerKakaoFriendtalkContentSettingController.phpController/Admin/Crm/MobileSend/LayerMobileSendSaveSmsTemplateController.phpController/Admin/Crm/MobileSend/LayerMyappPushContentSettingController.phpController/Admin/Crm/MobileSend/LayerSmsContentSettingController.phpController/Admin/Crm/MobileSend/SmsTemplatePsController.phpController/Admin/Crm/Sms/SmsAutoController.phpController/Admin/Member/SmsLogPsController.phpController/Admin/Member/SmsSendController.phpController/Admin/Member/CartRemindRegistController.phpController/Admin/Promotion/ComebackCouponRegistController.phpController/Admin/Promotion/LayerCartRemindMessageController.phpController/Admin/Promotion/LayerStandbySmsController.phpController/Admin/Share/
무료보안 관리자 화면 안정성 개선
SecureSocketLayer.phpComponent/SiteLink/
본인인증(아이핀) 페이지 보안 강화
IpinMainController.phpController/Front/Member/Ipin/
자동메일 발송설정 게시판 선택 기능 개선.
AutoSendConfigController.phpController/Admin/Crm/AutoSendController.phpController/Admin/Crm/CrmRecipeSendController.phpController/Admin/Crm/IndexController.phpController/Admin/Crm/MailGateBridgeController.phpController/Admin/Crm/MailGateController.phpController/Admin/Crm/MailSendController.phpController/Admin/Crm/MailSendReceiveRefuseCountController.phpController/Admin/Crm/MessageConfigController.phpController/Admin/Crm/MessageTemplateController.phpController/Admin/Crm/MessageTemplateRegisterController.phpController/Admin/Crm/MobileHistoryController.phpController/Admin/Crm/MobileHistoryListController.phpController/Admin/Crm/MobileSendController.phpController/Admin/Crm/PopupChargeMessagePointsController.phpController/Admin/Crm/PopupManageCallNumberController.phpController/Admin/Crm/PopupSelectMembersController.phpController/Admin/Crm/
상품 검색 보안 강화.
Goods.phpComponent/Goods/
관리자-유저 세션 격리 강화
MemberSnsDAO.phpComponent/Member/
변경 파일 코드
추가삭제
엑셀 다운로드 시 다운로드 기록 수집
Component/Excel/ExcelRequest.php+116
변경 전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\Enum\RegularDelivery\RegularGoods\DeliveryCycle;
use Request;
use Session;
$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;
} else {
 
return false;
}
 
 
return ['getData' => $setData, 'totalCount' => '$totalNum'];
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
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\Enum\RegularDelivery\RegularGoods\DeliveryCycle;
use Request;
use Session;
$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;
} else {
 
return false;
}
 
 
return ['getData' => $setData, 'totalCount' => '$totalNum'];
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
변경 후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;
use Origin\Enum\RegularDelivery\RegularGoods\DeliveryCycle;
use Request;
use Session;
$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;
}
 
 
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
{
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
]
);
}
}
}
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\RegularDelivery\RegularGoods\DeliveryCycle;
use Request;
use Session;
$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($arrData, true, ShopDataExtractLogDTO::EXTRACT_TYPE_EXCEL);
return $arrData['requestSno'];
}
 
unset($arrBind);
 
$this->publishExtractLog($arrData, true, ShopDataExtractLogDTO::EXTRACT_TYPE_EXCEL);
return true;
} else {
$this->publishExtractLog($arrData, false, ShopDataExtractLogDTO::EXTRACT_TYPE_EXCEL);
return false;
}
 
 
return ['getData' => $setData, 'totalCount' => '$totalNum'];
}
 
/**
* 상점 데이터 추출 완료 결과를 카프카 토픽으로 발행한다.
*
* @param array $arrData 추출 요청 데이터 (menu 포함)
* @param bool $isSuccess 추출 성공 여부
* @param string $extractType 추출 유형 (ShopDataExtractLogDTO::EXTRACT_TYPE_* 상수)
* @return void
*/
protected function publishExtractLog(array $arrData, bool $isSuccess, string $extractType): void
{
try {
$godosno = Globals::get('gLicense.godosno');
if (empty($godosno)) {
throw new MissingRequiredFieldException('상점 데이터 추출 로그 발행 실패: 라이선스 상점번호(godosno)가 없습니다.');
}
 
$extractTarget = $arrData['menu'] ? strtoupper($arrData['menu']) : null;
$producer = new ProducerUtils();
$shopDataExtractLogDto = new ShopDataExtractLogDTO(
[(int)$godosno],
$extractType,
$extractTarget,
$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,
'extractTarget' => $arrData['menu'] ?? null
]
);
}
}
}
 
애널리틱스 세션 분석 기능 고도화
Component/Cart/Cart.php+12
변경 전Cart.php
use Framework\Utility\ArrayUtils;
use Framework\Utility\NumberUtils;
use Framework\Utility\ProducerUtils;
 
use Framework\Utility\SkinUtils;
use Framework\Utility\StringUtils;
use Globals;
$result = $kafka->send($kafka::TOPIC_CART_ADD_STATISTICS, $kafka->makeData([$cartStatistics], 'cas'), $kafka::MODE_RESULT_CALLLBACK, true);
\Logger::channel('kafka')->info('process sendMQ - return :', $result);
}
 
 
 
 
 
 
 
 
 
 
 
// 장바구니 변경 갯수 상품 업데이트
$goods = \App::load(\Component\Goods\Goods::class);
$goods->setCartGoodsCount($arrData['goodsNo']);
 
변경 후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;
use Globals;
$result = $kafka->send($kafka::TOPIC_CART_ADD_STATISTICS, $kafka->makeData([$cartStatistics], 'cas'), $kafka::MODE_RESULT_CALLLBACK, true);
\Logger::channel('kafka')->info('process sendMQ - return :', $result);
}
 
// 장바구니 추가 통계 수집 (수기주문 제외)
try {
$cartAddHistoryAnalyticsCollectorService = \App::getInstance(CartAddHistoryAnalyticsCollectorService::class);
if ($cartAddHistoryAnalyticsCollectorService->isCollectableCart($this->isWrite)) {
$cartAddHistoryAnalyticsCollectorService->collectAnalytics($arrData);
}
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analytics 수집 실패', ['exception' => $e, 'errorMessage' => $e->getMessage()]);
}
 
// 장바구니 변경 갯수 상품 업데이트
$goods = \App::load(\Component\Goods\Goods::class);
$goods->setCartGoodsCount($arrData['goodsNo']);
 
Component/Cart/RegularOrderCart.php+9
변경 전RegularOrderCart.php
use Framework\Utility\Globals;
use Repository\Goods\AddGoodsRepository;
use Framework\Log\Logger;
 
use Component\Policy\Policy;
use Framework\Http\Request;
use Component\Goods\Goods;
// 장바구니 저장
$cartSno = $this->regularOrderCartRepository->saveCart($arrData);
 
 
 
 
 
 
 
 
 
// 장바구니 변경 갯수 상품 업데이트
$goods = \App::load(\Component\Goods\Goods::class);
$goods->setCartGoodsCount($arrData['goodsNo']);
 
변경 후RegularOrderCart.php
use Framework\Utility\Globals;
use Repository\Goods\AddGoodsRepository;
use Framework\Log\Logger;
use Origin\Service\Statistics\Analytics\Cart\CartAddHistoryAnalyticsCollectorService;
use Component\Policy\Policy;
use Framework\Http\Request;
use Component\Goods\Goods;
// 장바구니 저장
$cartSno = $this->regularOrderCartRepository->saveCart($arrData);
 
// 장바구니 추가 통계 수집
try {
$cartAddHistoryAnalyticsCollectorService = \App::getInstance(CartAddHistoryAnalyticsCollectorService::class);
$cartAddHistoryAnalyticsCollectorService->collectAnalytics($arrData);
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analytics 수집 실패', ['exception' => $e, 'errorMessage' => $e->getMessage()]);
}
 
// 장바구니 변경 갯수 상품 업데이트
$goods = \App::load(\Component\Goods\Goods::class);
$goods->setCartGoodsCount($arrData['goodsNo']);
 
Component/Database/DBTableField.php+14
변경 전DBTableField.php
* @author artherot
* @return array order 테이블 필드 정보
*/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
public static function tableOrder()
{
// @formatter:off
 
변경 후DBTableField.php
* @author artherot
* @return array order 테이블 필드 정보
*/
public static function tableAnalyticsOrder()
{
// @formatter:off
$arrField = [
['val' => 'orderNo', 'typ' => 's', 'def' => null], // 주문번호
['val' => 'clientId', 'typ' => 's', 'def' => null], // 애널리틱스 클라이언트 ID
['val' => 'sessionId', 'typ' => 's', 'def' => null], // 애널리틱스 세션 ID
['val' => 'regDt', 'typ' => 's', 'def' => null], // 등록일
];
// @formatter:on
 
return $arrField;
}
 
public static function tableOrder()
{
// @formatter:off
 
Component/Order/ExternalOrder.php+12
변경 전ExternalOrder.php
use Session;
use Framework\Utility\ArrayUtils;
use Framework\Utility\StringUtils;
 
 
/**
* 외부채널 주문 관련 class
}
 
unset($arrBind);
 
 
 
 
 
 
 
 
 
 
 
}
 
/*
 
변경 후ExternalOrder.php
use Session;
use Framework\Utility\ArrayUtils;
use Framework\Utility\StringUtils;
use Origin\Service\Statistics\Analytics\Order\AnalyticsOrderService;
 
/**
* 외부채널 주문 관련 class
}
 
unset($arrBind);
 
// 애널리틱스 주문 추적값 저장
try {
if (empty($insertDataOrder['orderNo'])) {
throw new \InvalidArgumentException('orderNo가 비어있습니다');
}
$analyticsOrderService = \App::getInstance(AnalyticsOrderService::class);
$analyticsOrderService->saveAnalyticsOrder($insertDataOrder['orderNo']);
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analyticsOrder 저장 실패', ['exception' => $e, 'errorMessage' => $e->getMessage(), 'orderNo' => $insertDataOrder['orderNo']]);
}
}
 
/*
 
Component/Order/OrderAdminNew.php+33-20
변경 전OrderAdminNew.php
use Framework\Utility\StringUtils;
use Globals;
use LogHandler;
use Origin\Enum\Order\OrderSettleKind;
use Origin\Enum\Order\OrderHandleMode;
use Origin\Enum\Order\OrderStatusCode;
use Origin\Enum\Order\OrderStatusGroup;
 
 
use Origin\Service\Statistics\Analytics\Order\OrderCompleteAnalyticsCollectorService;
use Origin\Service\WebHook\OrderWebHookService;
use Repository\Order\OrderDeliveryOriginalRepository;
break;
}
 
// 주문 완료 통계 수집
try {
$isOrderAnalyticsCollectAvailable = false;
$analyticsCheckChangeStatus = substr($changeStatus, 0, 1);
if (in_array($analyticsCheckChangeStatus, ['p', 'o'])) { // 결제완료, 입금대기일때
if ($analyticsCheckChangeStatus === 'o' && in_array($orderData['settleKind'], OrderSettleKind::getVirtualAccountKinds())) { // 입금 대기중인 가상계좌는 수집
$isOrderAnalyticsCollectAvailable = true;
}
 
if ($analyticsCheckChangeStatus === 'p' && !in_array($orderData['settleKind'], OrderSettleKind::getBankAndVirtualAccountKinds())) { // 결제 완료된 무통장/가상계좌는 수집 하지 않음
$isOrderAnalyticsCollectAvailable = true;
}
}
 
if ($isOrderAnalyticsCollectAvailable) {
$orderCompleteStatisticsCollectorService = \App::getInstance(OrderCompleteAnalyticsCollectorService::class);
$orderAnalyticsData = [
'orderNo' => $orderNo,
'order' => $orderData,
];
$orderCompleteStatisticsCollectorService->collectAnalytics($orderAnalyticsData);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
} catch (\Throwable $e) {
\Logger::channel('global')->error(__METHOD__ . ' analytics 수집 실패: ' . $e->getMessage(), [$e->getTraceAsString()]);
}
 
// 고도몰 주문 웹훅 발송
 
변경 후OrderAdminNew.php
use Framework\Utility\StringUtils;
use Globals;
use LogHandler;
 
use Origin\Enum\Order\OrderHandleMode;
use Origin\Enum\Order\OrderStatusCode;
use Origin\Enum\Order\OrderStatusGroup;
use Origin\Enum\Statistics\Analytics\Order\ClaimType;
use Origin\Service\Statistics\Analytics\Order\ClaimCompleteAnalyticsCollectorService;
use Origin\Service\Statistics\Analytics\Order\OrderCompleteAnalyticsCollectorService;
use Origin\Service\WebHook\OrderWebHookService;
use Repository\Order\OrderDeliveryOriginalRepository;
break;
}
 
 
try {
$orderCompleteStatisticsCollectorService = \App::getInstance(OrderCompleteAnalyticsCollectorService::class);
$claimCompleteStatisticsCollectorService = \App::getInstance(ClaimCompleteAnalyticsCollectorService::class);
 
// 결제완료 묶음에 교환추가(z)가 섞이면 통째로 claim, 아니면 일반 결제완료(order_complete)
$exchangeExtraPaymentAmount = $claimCompleteStatisticsCollectorService->getCollectibleExchangeExtraPaymentAmount($changeStatus, $orderNo, $bundleData['sno'] ?? []);
if (!empty($exchangeExtraPaymentAmount)) {
// 교환추가가 섞인 결제완료 → claim_complete 수집 (일반상품 결제금액 + 교환 차액)
$claimCompleteStatisticsCollectorService->collectAnalytics([
'orderNo' => $orderNo,
'claimType' => ClaimType::EXCHANGE->value,
'claimAmount' => $exchangeExtraPaymentAmount,
]);
} elseif ($orderCompleteStatisticsCollectorService->isOrderCompleteCollectible($changeStatus)) {
$orderCompleteStatisticsCollectorService->collectAnalytics([
 
'orderNo' => $orderNo,
'order' => $orderData,
]);
}
// 결제완료 → 입금대기 되돌림 시점 통계 수집
// 되돌림은 주문 묶음 전체 단위로만 일어나므로(부분 되돌림 없음) 대표로 첫 상품 상태만 확인
$prevStatus = array_values($goodsData)[0]['orderStatus'] ?? '';
$claimAmount = $orderData['settlePrice'] ?? 0;
$mileageClaimAmount = $orderData['useMileage'] ?? 0;
$depositClaimAmount = $orderData['useDeposit'] ?? 0;
if ($claimCompleteStatisticsCollectorService->isReversalToWaitDepositCollectible($changeStatus, $prevStatus, (float)$claimAmount, (float)$mileageClaimAmount, (float)$depositClaimAmount)) {
$claimCompleteStatisticsCollectorService->collectAnalytics([
'orderNo' => $orderNo,
'claimType' => ClaimType::REVERSAL->value,
'claimAmount' => $claimAmount,
'mileageClaimAmount' => $mileageClaimAmount,
'depositClaimAmount' => $depositClaimAmount,
]);
}
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analytics 수집 실패', ['exception' => $e, 'errorMessage' => $e->getMessage()]);
}
 
// 고도몰 주문 웹훅 발송
 
Component/Order/OrderAdmin.php+33-20
변경 전OrderAdmin.php
use Framework\Utility\StringUtils;
use Globals;
use LogHandler;
use Origin\Enum\Order\OrderSettleKind;
use Origin\Enum\Order\OrderHandleMode;
use Origin\Enum\Order\OrderStatusCode;
use Origin\Enum\Order\OrderStatusGroup;
 
 
use Origin\Service\Statistics\Analytics\Order\OrderCompleteAnalyticsCollectorService;
use Origin\Service\WebHook\OrderWebHookService;
use Repository\Order\OrderDeliveryOriginalRepository;
break;
}
 
// 주문 완료 통계 수집
try {
$isOrderAnalyticsCollectAvailable = false;
$analyticsCheckChangeStatus = substr($changeStatus, 0, 1);
if (in_array($analyticsCheckChangeStatus, ['p', 'o'])) { // 결제완료, 입금대기일때
if ($analyticsCheckChangeStatus === 'o' && in_array($orderData['settleKind'], OrderSettleKind::getVirtualAccountKinds())) { // 입금 대기중인 가상계좌는 수집
$isOrderAnalyticsCollectAvailable = true;
}
 
if ($analyticsCheckChangeStatus === 'p' && !in_array($orderData['settleKind'], OrderSettleKind::getBankAndVirtualAccountKinds())) { // 결제 완료된 무통장/가상계좌는 수집 하지 않음
$isOrderAnalyticsCollectAvailable = true;
}
}
 
if ($isOrderAnalyticsCollectAvailable) {
$orderCompleteStatisticsCollectorService = \App::getInstance(OrderCompleteAnalyticsCollectorService::class);
$orderAnalyticsData = [
'orderNo' => $orderNo,
'order' => $orderData,
];
$orderCompleteStatisticsCollectorService->collectAnalytics($orderAnalyticsData);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
} catch (\Throwable $e) {
\Logger::channel('global')->error(__METHOD__ . ' analytics 수집 실패: ' . $e->getMessage(), [$e->getTraceAsString()]);
}
 
// 고도몰 주문 웹훅 발송
 
변경 후OrderAdmin.php
use Framework\Utility\StringUtils;
use Globals;
use LogHandler;
 
use Origin\Enum\Order\OrderHandleMode;
use Origin\Enum\Order\OrderStatusCode;
use Origin\Enum\Order\OrderStatusGroup;
use Origin\Enum\Statistics\Analytics\Order\ClaimType;
use Origin\Service\Statistics\Analytics\Order\ClaimCompleteAnalyticsCollectorService;
use Origin\Service\Statistics\Analytics\Order\OrderCompleteAnalyticsCollectorService;
use Origin\Service\WebHook\OrderWebHookService;
use Repository\Order\OrderDeliveryOriginalRepository;
break;
}
 
 
try {
$orderCompleteStatisticsCollectorService = \App::getInstance(OrderCompleteAnalyticsCollectorService::class);
$claimCompleteStatisticsCollectorService = \App::getInstance(ClaimCompleteAnalyticsCollectorService::class);
 
// 결제완료 묶음에 교환추가(z)가 섞이면 통째로 claim, 아니면 일반 결제완료(order_complete)
$exchangeExtraPaymentAmount = $claimCompleteStatisticsCollectorService->getCollectibleExchangeExtraPaymentAmount($changeStatus, $orderNo, $bundleData['sno'] ?? []);
if (!empty($exchangeExtraPaymentAmount)) {
// 교환추가가 섞인 결제완료 → claim_complete 수집 (일반상품 결제금액 + 교환 차액)
$claimCompleteStatisticsCollectorService->collectAnalytics([
'orderNo' => $orderNo,
'claimType' => ClaimType::EXCHANGE->value,
'claimAmount' => $exchangeExtraPaymentAmount,
]);
} elseif ($orderCompleteStatisticsCollectorService->isOrderCompleteCollectible($changeStatus)) {
$orderCompleteStatisticsCollectorService->collectAnalytics([
 
'orderNo' => $orderNo,
'order' => $orderData,
]);
}
// 결제완료 → 입금대기 되돌림 시점 통계 수집
// 되돌림은 주문 묶음 전체 단위로만 일어나므로(부분 되돌림 없음) 대표로 첫 상품 상태만 확인
$prevStatus = array_values($goodsData)[0]['orderStatus'] ?? '';
$claimAmount = $orderData['settlePrice'] ?? 0;
$mileageClaimAmount = $orderData['useMileage'] ?? 0;
$depositClaimAmount = $orderData['useDeposit'] ?? 0;
if ($claimCompleteStatisticsCollectorService->isReversalToWaitDepositCollectible($changeStatus, $prevStatus, (float)$claimAmount, (float)$mileageClaimAmount, (float)$depositClaimAmount)) {
$claimCompleteStatisticsCollectorService->collectAnalytics([
'orderNo' => $orderNo,
'claimType' => ClaimType::REVERSAL->value,
'claimAmount' => $claimAmount,
'mileageClaimAmount' => $mileageClaimAmount,
'depositClaimAmount' => $depositClaimAmount,
]);
}
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analytics 수집 실패', ['exception' => $e, 'errorMessage' => $e->getMessage()]);
}
 
// 고도몰 주문 웹훅 발송
 
Component/Order/OrderNew.php+15-3
변경 전OrderNew.php
use LogHandler;
use Origin\Constants\WebHookEventType;
use Origin\Service\Order\OrderInfoService;
 
use Origin\Service\Statistics\Analytics\Order\OrderCompleteAnalyticsCollectorService;
use Origin\Service\WebHook\OrderWebHookService;
use Request;
$this->db->set_insert_db(DB_ORDER, $arrBind['param'], $arrBind['bind'], 'y', false);
unset($arrBind);
 
 
 
 
 
 
 
 
 
 
 
 
$this->setPayHistory($history);
 
// 주문 사은품 정보 저장
}
}
 
// 주문 완료 통계 수집
try {
if ($orderInfo['settleKind'] == 'gb' || $orderInfo['settleKind'] == self::SETTLE_KIND_ZERO) {
$orderCompleteStatisticsCollectorService = \App::getInstance(OrderCompleteAnalyticsCollectorService::class);
$orderAnalyticsData = [
'orderNo' => $this->orderNo,
$orderCompleteStatisticsCollectorService->collectAnalytics($orderAnalyticsData);
}
} catch (\Throwable $e) {
\Logger::channel('global')->error(__METHOD__ . ' analytics 수집 실패: ' . $e->getMessage(), [$e->getTraceAsString()]);
}
 
// 주문서 저장 이후, channelType 세션 데이터 제거
 
변경 후OrderNew.php
use LogHandler;
use Origin\Constants\WebHookEventType;
use Origin\Service\Order\OrderInfoService;
use Origin\Service\Statistics\Analytics\Order\AnalyticsOrderService;
use Origin\Service\Statistics\Analytics\Order\OrderCompleteAnalyticsCollectorService;
use Origin\Service\WebHook\OrderWebHookService;
use Request;
$this->db->set_insert_db(DB_ORDER, $arrBind['param'], $arrBind['bind'], 'y', false);
unset($arrBind);
 
// 애널리틱스 주문 추적값 저장
try {
if (empty($this->orderNo)) {
throw new \InvalidArgumentException('orderNo가 비어있습니다');
}
$analyticsOrderService = \App::getInstance(AnalyticsOrderService::class);
$analyticsOrderService->saveAnalyticsOrder($this->orderNo);
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analyticsOrder 저장 실패', ['exception' => $e, 'errorMessage' => $e->getMessage(), 'orderNo' => $this->orderNo]);
}
 
$this->setPayHistory($history);
 
// 주문 사은품 정보 저장
}
}
 
// 결제완료 시점 통계 수집
try {
if ($orderInfo['settleKind'] == self::SETTLE_KIND_ZERO) {
$orderCompleteStatisticsCollectorService = \App::getInstance(OrderCompleteAnalyticsCollectorService::class);
$orderAnalyticsData = [
'orderNo' => $this->orderNo,
$orderCompleteStatisticsCollectorService->collectAnalytics($orderAnalyticsData);
}
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analytics 수집 실패', ['exception' => $e, 'errorMessage' => $e->getMessage()]);
}
 
// 주문서 저장 이후, channelType 세션 데이터 제거
 
Component/Order/Order.php+15-3
변경 전Order.php
use LogHandler;
use Origin\Constants\WebHookEventType;
use Origin\Service\Order\OrderInfoService;
 
use Origin\Service\Statistics\Analytics\Order\OrderCompleteAnalyticsCollectorService;
use Origin\Service\WebHook\OrderWebHookService;
use Origin\Enum\RegularDelivery\RegularOrder\RegularOrderStatus;
$this->db->set_insert_db(DB_ORDER, $arrBind['param'], $arrBind['bind'], 'y', false);
unset($arrBind);
 
 
 
 
 
 
 
 
 
 
 
 
// 최초결제 결제히스토리 저장
$history = [
'orderNo' => $this->orderNo,// 주문번호
}
}
 
// 주문 완료 통계 수집
try {
if ($orderInfo['settleKind'] == 'gb' || $orderInfo['settleKind'] == self::SETTLE_KIND_ZERO) {
$orderCompleteStatisticsCollectorService = \App::getInstance(OrderCompleteAnalyticsCollectorService::class);
$orderAnalyticsData = [
'orderNo' => $this->orderNo,
$orderCompleteStatisticsCollectorService->collectAnalytics($orderAnalyticsData);
}
} catch (\Throwable $e) {
\Logger::channel('global')->error(__METHOD__ . ' analytics 수집 실패: ' . $e->getMessage(), [$e->getTraceAsString()]);
}
 
// 페이스북 FBE v2 pixelCookie 값 주문서 저장
 
변경 후Order.php
use LogHandler;
use Origin\Constants\WebHookEventType;
use Origin\Service\Order\OrderInfoService;
use Origin\Service\Statistics\Analytics\Order\AnalyticsOrderService;
use Origin\Service\Statistics\Analytics\Order\OrderCompleteAnalyticsCollectorService;
use Origin\Service\WebHook\OrderWebHookService;
use Origin\Enum\RegularDelivery\RegularOrder\RegularOrderStatus;
$this->db->set_insert_db(DB_ORDER, $arrBind['param'], $arrBind['bind'], 'y', false);
unset($arrBind);
 
// 애널리틱스 주문 추적값 저장
try {
if (empty($this->orderNo)) {
throw new \InvalidArgumentException('orderNo가 비어있습니다');
}
$analyticsOrderService = \App::getInstance(AnalyticsOrderService::class);
$analyticsOrderService->saveAnalyticsOrder($this->orderNo);
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analyticsOrder 저장 실패', ['exception' => $e, 'errorMessage' => $e->getMessage(), 'orderNo' => $this->orderNo]);
}
 
// 최초결제 결제히스토리 저장
$history = [
'orderNo' => $this->orderNo,// 주문번호
}
}
 
// 결제완료 시점 통계 수집
try {
if ($orderInfo['settleKind'] == self::SETTLE_KIND_ZERO) {
$orderCompleteStatisticsCollectorService = \App::getInstance(OrderCompleteAnalyticsCollectorService::class);
$orderAnalyticsData = [
'orderNo' => $this->orderNo,
$orderCompleteStatisticsCollectorService->collectAnalytics($orderAnalyticsData);
}
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analytics 수집 실패', ['exception' => $e, 'errorMessage' => $e->getMessage()]);
}
 
// 페이스북 FBE v2 pixelCookie 값 주문서 저장
 
Component/Order/ReOrderCalculation.php+54
변경 전ReOrderCalculation.php
use Framework\Utility\NumberUtils;
use Globals;
use Origin\Service\WebHook\OrderWebHookService;
 
 
use Session;
use Origin\Traits\Logger\CommonLogStorageTrait;
 
// 바로 위의 updateStatusPreprocess 는 내부에 transaction 이 한번 더 있기 때문에 이 exception 롤백과 무관하게 commit 됨.
throw new Exception(__($returnError));
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
public function setRefundCompleteOrderGoodsNew($getData, $autoProcess = false, $userHandleFl = false)
$presentConfirm = \App::getInstance(\Component\Present\Confirm\PresentConfirm::class);
$presentConfirm->clearConfirmToken($getData['orderNo']);
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
/**
$this->orderWebHookService->sendOrderGoodsWebHook($webhookOrderGoodsSnos, $claimStatus, $changeStatus);
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
return true;
}
 
 
변경 후ReOrderCalculation.php
use Framework\Utility\NumberUtils;
use Globals;
use Origin\Service\WebHook\OrderWebHookService;
use Origin\Enum\Statistics\Analytics\Order\ClaimType;
use Origin\Service\Statistics\Analytics\Order\ClaimCompleteAnalyticsCollectorService;
use Session;
use Origin\Traits\Logger\CommonLogStorageTrait;
 
// 바로 위의 updateStatusPreprocess 는 내부에 transaction 이 한번 더 있기 때문에 이 exception 롤백과 무관하게 commit 됨.
throw new Exception(__($returnError));
}
 
// 환불완료 시점 통계 수집
try {
$claimCompleteStatisticsCollectorService = \App::getInstance(ClaimCompleteAnalyticsCollectorService::class);
$claimAmount = $getData['check']['totalRefundPrice'] ?? 0;
$mileageClaimAmount = gd_isset($getData['info']['refundGoodsUseMileage'], 0) + gd_isset($getData['info']['refundDeliveryUseMileage'], 0) - gd_isset($getData['info']['refundUseMileageCommission'], 0);
$depositClaimAmount = gd_isset($getData['info']['refundGoodsUseDeposit'], 0) + gd_isset($getData['info']['refundDeliveryUseDeposit'], 0) - gd_isset($getData['info']['refundUseDepositCommission'], 0);
if ($claimCompleteStatisticsCollectorService->isRefundAmountCollectible((float)$claimAmount, (float)$mileageClaimAmount, (float)$depositClaimAmount)) {
$claimCompleteStatisticsCollectorService->collectAnalytics([
'orderNo' => $getData['orderNo'],
'claimType' => ClaimType::REFUND->value,
'claimAmount' => $claimAmount,
'mileageClaimAmount' => $mileageClaimAmount,
'depositClaimAmount' => $depositClaimAmount,
]);
}
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analytics 수집 실패', ['exception' => $e, 'errorMessage' => $e->getMessage()]);
}
}
 
public function setRefundCompleteOrderGoodsNew($getData, $autoProcess = false, $userHandleFl = false)
$presentConfirm = \App::getInstance(\Component\Present\Confirm\PresentConfirm::class);
$presentConfirm->clearConfirmToken($getData['orderNo']);
}
 
// 환불완료 시점 통계 수집
try {
$claimCompleteStatisticsCollectorService = \App::getInstance(ClaimCompleteAnalyticsCollectorService::class);
$claimAmount = $getData['check']['totalRefundPrice'] ?? 0;
$mileageClaimAmount = gd_isset($getData['info']['refundGoodsUseMileage'], 0) + gd_isset($getData['info']['refundDeliveryUseMileage'], 0) - gd_isset($getData['info']['refundUseMileageCommission'], 0);
$depositClaimAmount = gd_isset($getData['info']['refundGoodsUseDeposit'], 0) + gd_isset($getData['info']['refundDeliveryUseDeposit'], 0) - gd_isset($getData['info']['refundUseDepositCommission'], 0);
if ($claimCompleteStatisticsCollectorService->isRefundAmountCollectible((float)$claimAmount, (float)$mileageClaimAmount, (float)$depositClaimAmount)) {
$claimCompleteStatisticsCollectorService->collectAnalytics([
'orderNo' => $getData['orderNo'],
'claimType' => ClaimType::REFUND->value,
'claimAmount' => $claimAmount,
'mileageClaimAmount' => $mileageClaimAmount,
'depositClaimAmount' => $depositClaimAmount,
]);
}
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analytics 수집 실패', ['exception' => $e, 'errorMessage' => $e->getMessage()]);
}
}
 
/**
$this->orderWebHookService->sendOrderGoodsWebHook($webhookOrderGoodsSnos, $claimStatus, $changeStatus);
}
 
// 교환접수 시점 통계 수집
try {
$claimCompleteStatisticsCollectorService = \App::getInstance(ClaimCompleteAnalyticsCollectorService::class);
if ($postData['totalChangePrice'] > 0) {
$claimCompleteStatisticsCollectorService->collectAnalytics([
'orderNo' => $postData['orderNo'],
'claimType' => ClaimType::EXCHANGE->value,
'claimAmount' => $postData['totalChangePrice'],
]);
}
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analytics 수집 실패', ['exception' => $e, 'errorMessage' => $e->getMessage()]);
}
 
return true;
}
 
 
Component/Wish/Wish.php+17
변경 전Wish.php
use Repository\RegularDelivery\RegularGoods\RegularGoodsRepository;
use Origin\Enum\RegularDelivery\RegularGoods\RegularGoodsAttribute;
use Origin\Service\WebHook\CartWebHookService;
 
 
class Wish
{
const ERROR_VIEW = 'ERROR_VIEW';
$result = $kafka->send($kafka::TOPIC_CART_ADD_STATISTICS, $kafka->makeData([$cartStatistics], 'cas'), $kafka::MODE_RESULT_CALLLBACK, true);
\Logger::channel('kafka')->info('process sendMQ - return :', $result);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$cartInfo = gd_policy('order.cart');
if($cartInfo['moveWishPageFl'] =='n') {
//장바구니 저장 상품 삭제
 
변경 후Wish.php
use Repository\RegularDelivery\RegularGoods\RegularGoodsRepository;
use Origin\Enum\RegularDelivery\RegularGoods\RegularGoodsAttribute;
use Origin\Service\WebHook\CartWebHookService;
use Origin\Service\Statistics\Analytics\Cart\CartAddHistoryAnalyticsCollectorService;
use Origin\Repository\Wish\WishRepository;
class Wish
{
const ERROR_VIEW = 'ERROR_VIEW';
$result = $kafka->send($kafka::TOPIC_CART_ADD_STATISTICS, $kafka->makeData([$cartStatistics], 'cas'), $kafka::MODE_RESULT_CALLLBACK, true);
\Logger::channel('kafka')->info('process sendMQ - return :', $result);
 
// 장바구니 추가 통계 수집
try {
// 다중 insert 시 insert_id는 첫 sno만 반환 → insert된 cart 특정 불가, 원본 위시(es_wish) 조회
$wishRepository = \App::getInstance(WishRepository::class);
$wishGoodsList = $wishRepository->findGoodsByWishSnosAndMemNo($sno, (int)Session::get('member.memNo'));
if (!empty($wishGoodsList)) {
$cartAddHistoryAnalyticsCollectorService = \App::getInstance(CartAddHistoryAnalyticsCollectorService::class);
foreach ($wishGoodsList as $wishGoods) {
$cartAddHistoryAnalyticsCollectorService->collectAnalytics($wishGoods);
}
}
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analytics 수집 실패', ['exception' => $e, 'errorMessage' => $e->getMessage()]);
}
 
$cartInfo = gd_policy('order.cart');
if($cartInfo['moveWishPageFl'] =='n') {
//장바구니 저장 상품 삭제
 
Controller/Front/Member/MemberPsController.php+2-2
변경 전MemberPsController.php
$memberStatisticsCollectorService = \App::getInstance(MemberJoinCompleteAnalyticsCollectorService::class);
$memberStatisticsCollectorService->collectAnalytics($memberVO->toArray());
} catch (\Throwable $e) {
\Logger::channel('global')->error(__METHOD__ . ' analytics 수집 실패: ' . $e->getMessage(), [$e->getTraceAsString()]);
}
 
// 평생회원 이벤트
$memberStatisticsCollectorService = \App::getInstance(MemberJoinCompleteAnalyticsCollectorService::class);
$memberStatisticsCollectorService->collectAnalytics($memberVO->toArray());
} catch (\Throwable $e) {
\Logger::channel('global')->error(__METHOD__ . ' analytics 수집 실패: ' . $e->getMessage(), [$e->getTraceAsString()]);
}
 
$memNo = $memberVO->getMemNo();
 
변경 후MemberPsController.php
$memberStatisticsCollectorService = \App::getInstance(MemberJoinCompleteAnalyticsCollectorService::class);
$memberStatisticsCollectorService->collectAnalytics($memberVO->toArray());
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analytics 수집 실패', ['exception' => $e, 'errorMessage' => $e->getMessage()]);
}
 
// 평생회원 이벤트
$memberStatisticsCollectorService = \App::getInstance(MemberJoinCompleteAnalyticsCollectorService::class);
$memberStatisticsCollectorService->collectAnalytics($memberVO->toArray());
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analytics 수집 실패', ['exception' => $e, 'errorMessage' => $e->getMessage()]);
}
 
$memNo = $memberVO->getMemNo();
 
Controller/Mobile/Member/MemberPsController.php+2-2
변경 전MemberPsController.php
$memberStatisticsCollectorService = \App::getInstance(MemberJoinCompleteAnalyticsCollectorService::class);
$memberStatisticsCollectorService->collectAnalytics($memberVO->toArray());
} catch (\Throwable $e) {
\Logger::channel('global')->error(__METHOD__ . ' analytics 수집 실패: ' . $e->getMessage(), [$e->getTraceAsString()]);
}
 
// request가 ajax일 때 (카카오 싱크)
$memberStatisticsCollectorService = \App::getInstance(MemberJoinCompleteAnalyticsCollectorService::class);
$memberStatisticsCollectorService->collectAnalytics($memberVO->toArray());
} catch (\Throwable $e) {
\Logger::channel('global')->error(__METHOD__ . ' analytics 수집 실패: ' . $e->getMessage(), [$e->getTraceAsString()]);
}
 
$memNo = $memberVO->getMemNo();
 
변경 후MemberPsController.php
$memberStatisticsCollectorService = \App::getInstance(MemberJoinCompleteAnalyticsCollectorService::class);
$memberStatisticsCollectorService->collectAnalytics($memberVO->toArray());
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analytics 수집 실패', ['exception' => $e, 'errorMessage' => $e->getMessage()]);
}
 
// request가 ajax일 때 (카카오 싱크)
$memberStatisticsCollectorService = \App::getInstance(MemberJoinCompleteAnalyticsCollectorService::class);
$memberStatisticsCollectorService->collectAnalytics($memberVO->toArray());
} catch (\Throwable $e) {
\Logger::channel('analytics')->warning('analytics 수집 실패', ['exception' => $e, 'errorMessage' => $e->getMessage()]);
}
 
$memNo = $memberVO->getMemNo();
 
개인정보 수집 및 이용 동의 템플릿 개정
Admin/member/excel_member_up.php+3
변경 전excel_member_up.php
<div class="notice-info">
엑셀업로드 전 회원가입 시 발송 가능한 항목(회원가입, 마일리지 지급, 회원가입 축하 쿠폰)의 발송설정을 확인해주시기 바랍니다. <a href="/crm/auto_send.php" target="_blank" class="btn-link">SMS설정&gt;</a>&nbsp;&nbsp;<a href="/crm/mail_config_auto.php" target="_blank" class="btn-link">자동 메일 설정&gt;</a>
</div>
 
 
 
</td>
</tr>
</table>
 
변경 후excel_member_up.php
<div class="notice-info">
엑셀업로드 전 회원가입 시 발송 가능한 항목(회원가입, 마일리지 지급, 회원가입 축하 쿠폰)의 발송설정을 확인해주시기 바랍니다. <a href="/crm/auto_send.php" target="_blank" class="btn-link">SMS설정&gt;</a>&nbsp;&nbsp;<a href="/crm/mail_config_auto.php" target="_blank" class="btn-link">자동 메일 설정&gt;</a>
</div>
<div class="notice-info">
마케팅 목적 개인정보 수집 및 이용 항목 업로드 시 반드시 고객으로부터 받은 별도의 개인정보 수집·이용 동의서를 보유하고 계셔야 하며, NHN COMMERCE는 수탁사로서 업로드되는 데이터에 대한 책임을 지지 않습니다.
</div>
</td>
</tr>
</table>
 
Admin/member/member_detail_search.php+21-1
변경 전member_detail_search.php
</td>
</tr>
<tr>
<th>SMS수신동의</th>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<td>
<label class="radio-inline">
<input type="radio" name="smsFl"
 
변경 후member_detail_search.php
</td>
</tr>
<tr>
<th>마케팅 목적 개인정보 수집 및 이용 동의</th>
<td colspan="3">
<label class="radio-inline">
<input type="radio" name="privateMarketingConsentOptionFl"
value="" <?= gd_isset($checked['privateMarketingConsentOptionFl']['']); ?>/>
전체
</label>
<label class="radio-inline">
<input type="radio" name="privateMarketingConsentOptionFl"
value="y" <?= gd_isset($checked['privateMarketingConsentOptionFl']['y']); ?>/>
동의
</label>
<label class="radio-inline">
<input type="radio" name="privateMarketingConsentOptionFl"
value="n" <?= gd_isset($checked['privateMarketingConsentOptionFl']['n']); ?>/>
미동의
</label>
</td>
</tr>
<tr>
<th>모바일 메시지 수신동의</th>
<td>
<label class="radio-inline">
<input type="radio" name="smsFl"
 
Admin/member/member_joinitem.php+2-34
변경 전member_joinitem.php
</tr>
<tr>
<th>이메일</th>
<td>
<?php if ($mall['mallSno'] == 1) { ?>
<label class="checkbox-inline" title="사용여부를 선택해주세요!">
<input type="checkbox" name="email[use]" value="y"/>
<input type="hidden" name="email[require]" value="y"/>
<?php } ?>
</td>
<td>
<label class="checkbox-inline" for="maillingFl">
<input type="checkbox" id="maillingFl" name="maillingFl[use]" value="y">
정보/이벤트 메일 수신 동의 사용
</label>
</td>
</tr>
<tr>
<th>휴대폰번호</th>
<td <?= $mall['mallSno'] == 1 ? '' : 'colspan=2' ?>>
<label class="checkbox-inline" title="사용여부를 선택해주세요!">
<input type="checkbox" name="cellPhone[use]" value="y"/>
사용
필수
</label>
</td>
<?php if ($mall['mallSno'] == 1) { ?>
<td>
<label class="checkbox-inline" for="smsFl">
<input type="checkbox" id="smsFl" name="smsFl[use]" value="y">
정보/이벤트 SMS 수신 동의 사용
</label>
</td>
<?php } ?>
</tr>
<?php if ($mall['mallSno'] == 1) { ?>
<tr>
}
});
 
$(':checkbox[name="maillingFl[use]"], :checkbox[name="smsFl[use]"]', '#frmSetup').on('change', function (e) {
var $target = $(e.target);
if ($target.prop('checked') === false) {
BootstrapDialog.confirm({
title: "정보/이벤트 메일(SMS)수신 동의 사용 체크 해제",
message: "정보통신망법에 따라 수신동의한 회원에게만 정보/이벤트 소식을 전송할 수 있습니다. 수신동의 사용을 해제하시겠습니까?",
btnCancelLabel: "취소",
btnOkLabel: "해제",
callback: function (result) {
if (result) {
$target.prop('checked', false);
} else {
$target.prop('checked', true);
}
}
});
}
});
$('.expiration_detail').on('click', function () {
var msg = "휴면회원 정책에서 휴면회원 ‘사용함’으로 설정할 경우, 일정 기간 동안 쇼핑몰을 이용하지 않는 회원은 휴면회원으로 전환되어 쇼핑몰의 이용이 제한되고, 쇼핑몰 운영자는 회원에게 정보/이벤트성 안내를 할 수 없습니다."+'<br>'+"-&nbsp;&nbsp;&nbsp;회원가입 항목에서 개인 정보 유효기간 미사용 시 1년간 쇼핑몰을 이용하지 않는 회원들을 "+'<br>'+"&nbsp;&nbsp;&nbsp;&nbsp;휴면회원으로 일괄 전환합니다."+'<br>'+"-&nbsp;&nbsp;&nbsp;개인 정보 유효기간 사용 시 회원에게 ‘휴면회원 방지기간’ 정보를 수집할 수 있으며, 각 회원이 "+'<br>'+"&nbsp;&nbsp;&nbsp;&nbsp;“1년(기본값)/3년/5년/탈퇴 시” 중 선택한 기간 동안 쇼핑몰을 이용하지 않는 경우 해당 회원의"+'<br>'+"&nbsp;&nbsp;&nbsp;&nbsp;정보를 휴면회원으로 전환합니다."+'<br><div><img src="../admin/gd_share/img/expirationFl_img.png" style="width:80%; height:100%; margin-top:20px; margin-left:50px;"></div>' +
'<br>'+"휴면회원 정책에서 휴면회원 ‘사용 안함’으로 설정할 경우, 각 회원별로 개인정보 유효기간이 만료되더라도 휴면회원으로 전환되지 않으며 계속 쇼핑몰을 이용할 수 있습니다."+'<br>';
 
변경 후member_joinitem.php
</tr>
<tr>
<th>이메일</th>
<td colspan="2">
<?php if ($mall['mallSno'] == 1) { ?>
<label class="checkbox-inline" title="사용여부를 선택해주세요!">
<input type="checkbox" name="email[use]" value="y"/>
<input type="hidden" name="email[require]" value="y"/>
<?php } ?>
</td>
 
 
 
 
 
 
</tr>
<tr>
<th>휴대폰번호</th>
<td colspan="2">
<label class="checkbox-inline" title="사용여부를 선택해주세요!">
<input type="checkbox" name="cellPhone[use]" value="y"/>
사용
필수
</label>
</td>
 
 
 
 
 
 
 
 
</tr>
<?php if ($mall['mallSno'] == 1) { ?>
<tr>
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$('.expiration_detail').on('click', function () {
var msg = "휴면회원 정책에서 휴면회원 ‘사용함’으로 설정할 경우, 일정 기간 동안 쇼핑몰을 이용하지 않는 회원은 휴면회원으로 전환되어 쇼핑몰의 이용이 제한되고, 쇼핑몰 운영자는 회원에게 정보/이벤트성 안내를 할 수 없습니다."+'<br>'+"-&nbsp;&nbsp;&nbsp;회원가입 항목에서 개인 정보 유효기간 미사용 시 1년간 쇼핑몰을 이용하지 않는 회원들을 "+'<br>'+"&nbsp;&nbsp;&nbsp;&nbsp;휴면회원으로 일괄 전환합니다."+'<br>'+"-&nbsp;&nbsp;&nbsp;개인 정보 유효기간 사용 시 회원에게 ‘휴면회원 방지기간’ 정보를 수집할 수 있으며, 각 회원이 "+'<br>'+"&nbsp;&nbsp;&nbsp;&nbsp;“1년(기본값)/3년/5년/탈퇴 시” 중 선택한 기간 동안 쇼핑몰을 이용하지 않는 경우 해당 회원의"+'<br>'+"&nbsp;&nbsp;&nbsp;&nbsp;정보를 휴면회원으로 전환합니다."+'<br><div><img src="../admin/gd_share/img/expirationFl_img.png" style="width:80%; height:100%; margin-top:20px; margin-left:50px;"></div>' +
'<br>'+"휴면회원 정책에서 휴면회원 ‘사용 안함’으로 설정할 경우, 각 회원별로 개인정보 유효기간이 만료되더라도 휴면회원으로 전환되지 않으며 계속 쇼핑몰을 이용할 수 있습니다."+'<br>';
 
Admin/member/member_modify.php+16-2
변경 전member_modify.php
<form id="frm" action="../member/member_ps.php" method="post" enctype="multipart/form-data">
 
<div class="page-header js-affix">
<h3>회원 수정 </h3>
<input type="button" value="저장" class="btn btn-red btn-register">
</form>
<?php
// 모바일앱 정보
if ($hasDeviceInfo) {
include('_member_view_app_device.php');
}
?>
 
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/member.js"></script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/member2.js"></script>
 
<script type="text/javascript">
member2.init($('#frm'));
member2.set_my_page(true);
var certificationMaxFileSize = <?= $certificationMaxFileSize ?>;
var join_field = <?= json_encode($joinField) ?>;
</script>
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후member_modify.php
<link rel="stylesheet" href="<?= PATH_ADMIN_GD_SHARE ?>ncds/css/member/member-view.css">
<form id="frm" action="../member/member_ps.php" method="post" enctype="multipart/form-data" class="ncua-content">
<div class="page-header js-affix">
<h3>회원 수정 </h3>
<input type="button" value="저장" class="btn btn-red btn-register">
</form>
<?php
// 모바일앱 정보
if (!empty($hasDeviceInfo)) {
include('_member_view_app_device.php');
}
?>
 
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/member.js"></script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/member2.js"></script>
<script defer src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/charcount.js')?>"></script>
<script type="text/javascript">
member2.init($('#frm'));
member2.set_my_page(true);
var certificationMaxFileSize = <?= $certificationMaxFileSize ?>;
var join_field = <?= json_encode($joinField) ?>;
</script>
<script type="text/javascript">
$(document).ready(function() {
const memberPwManager = createCharCountManager({
targetClasses: ['js-member-pw'],
});
memberPwManager.init();
const reMemberPwManager = createCharCountManager({
targetClasses: ['js-re-member-pw'],
});
reMemberPwManager.init();
});
</script>
 
Admin/member/member_register.php+28-3
변경 전member_register.php
<form id="frm" action="../member/member_ps.php" method="post" enctype="multipart/form-data">
 
<div class="page-header js-affix">
<h3>회원 등록 </h3>
<div class="btn-group">
<input type="button" value="목록" class="btn btn-white btn-icon-list" onclick="goList('./member_list.php');"/>
<input type="button" value="저장" class="btn btn-red btn-register">
</div>
</div>
</form>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/member.js"></script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/member2.js"></script>
 
 
<script type="text/javascript">
member2.init($('#frm'));
 
var certificationMaxFileSize = <?= $certificationMaxFileSize ?>;
var join_field = <?= json_encode($joinField) ?>;
</script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후member_register.php
<link rel="stylesheet" href="<?= PATH_ADMIN_GD_SHARE ?>ncds/css/member/member-view.css">
<form id="frm" class="ncua-content" action="../member/member_ps.php" method="post" enctype="multipart/form-data">
<div class="page-header js-affix">
<h3 class="ncua-help-manual">회원 등록</h3>
<div class="btn-group">
<input type="button" value="목록" class="btn btn-white" onclick="goList('./member_list.php');"/>
<input type="button" value="저장" class="btn btn-red btn-register">
</div>
</div>
</form>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/member.js"></script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/member2.js"></script>
<script defer src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/charcount.js')?>"></script>
 
<script type="text/javascript">
member2.init($('#frm'));
 
var certificationMaxFileSize = <?= $certificationMaxFileSize ?>;
var join_field = <?= json_encode($joinField) ?>;
</script>
 
<script type="text/javascript">
$(document).ready(function() {
const memberPwManager = createCharCountManager({
targetClasses: ['js-member-pw'],
});
memberPwManager.init();
const reMemberPwManager = createCharCountManager({
targetClasses: ['js-re-member-pw'],
});
reMemberPwManager.init();
});
</script>
<script type="text/javascript">
const code = '260713001';
if (window.GodoCosGuide) {
window.GodoCosGuide.init({
apiUrl: <?= json_encode($cosGuideApiUrl) ?>,
guideCode: code,
});
}
</script>
 
Admin/member/_member_view_app_device.php+91-85
변경 전_member_view_app_device.php
<div class="table-title">
모바일앱 정보
</div>
<div class="form-inline">
<table class="table table-cols">
<colgroup>
<col class="width-sm"/>
<col/>
</colgroup>
<tr>
<th>혜택 정보</th>
<td>
<table class="table table-cols">
<colgroup>
<col class="width-md"/>
<col class="width-md"/>
<col class="width-md"/>
<col/>
</colgroup>
<tr>
<th>앱 설치 혜택</th>
<td><?=$installBenefitReceived;?></td>
<th>최초 로그인 일시</th>
<td>
<?=$firstLoginDateTime;?><br>
<p class="notice-danger notice-info">앱을 접속한 최초 일시입니다.</p>
</td>
</tr>
<tr>
<th>앱 알림 혜택</th>
<td><?=$pushBenefitReceived;?></td>
<th>혜택 지급 일시</th>
<td>
<?=$pushBenefitReceivedDateTime?><br>
<p class="notice-danger notice-info">앱에서 알림 혜택이 지급된 일시입니다.</p>
</td>
</tr>
<tr>
<th>앱 주문 혜택</th>
<td><?=$firstOrderBenefitReceived;?></td>
<th>최초 주문 일시</th>
<td>
<?=$firstOrderDateTime;?><br>
<p class="notice-danger notice-info">앱에서 결제완료한 최초 일시입니다.</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<th>접속 정보</th>
<td>
<table class="table table-rows">
<colgroup>
<col class="width-xs"/>
<col class="width-md"/>
<col class="width-sm"/>
<col class="width-sm"/>
<col class="width-md"/>
<col/>
</colgroup>
<thead>
<tr>
<th>운영체제</th>
<th>모바일 기종</th>
<th>최근 로그인 일시</th>
<th>정보성 알림 수신 동의</th>
<th>광고성 알림 수신 동의</th>
</tr>
</thead>
<tbody>
<tr>
<td class="center"><?=$deviceInfos['platform'];?></td>
<td class="center"><?=$deviceInfos['deviceModel'];?></td>
<td class="center"><?=$deviceInfos['latestLoginDateTime'];?></td>
<td class="center"><?=$deviceInfos['isNotificationAgreed'];?></td>
<td class="center"><?=$deviceInfos['isAdNotificationAgreed'];?></td>
</tr>
</tbody>
</table>
<p class="notice-danger notice-info">최근 90일내 접속한 디바이스가 표시됩니다.</p>
</td>
</tr>
</table>
</div>
 
 
 
 
 
 
 
변경 후_member_view_app_device.php
<section class="ncua-block-container ncua-content ncua-member-mobile-device">
<header class="ncua-block-header">
<div class="ncua-block-header__row">
<span class="ncua-block-header__title">모바일앱 정보</span>
</div>
</header>
<div class="ncua-block-container__body">
<div class="ncua-table ncua-table--vertical">
<table>
<colgroup>
<col style="width: 120px;"/>
<col/>
</colgroup>
<tr>
<th><div>혜택 정보</div></th>
<td class="ncua-member-mobile-benefit-info">
<table>
<colgroup>
<col style="width: 120px;"/>
<col />
<col style="width: 120px;"/>
<col/>
</colgroup>
<tr>
<th><div>앱 설치 혜택</div></th>
<td><div><?=$installBenefitReceived;?></div></td>
<th><div>최초 로그인 일시</div></th>
<td>
<div class="ncua-flex-column ncua-align-items-start">
<?=$firstLoginDateTime;?><br>
<p class="notice-danger notice-info">앱을 접속한 최초 일시입니다.</p>
</div>
</td>
</tr>
<tr>
<th><div>앱 알림 혜택</div></th>
<td><div><?=$pushBenefitReceived;?></div></td>
<th><div>혜택 지급 일시</div></th>
<td>
<div class="ncua-flex-column ncua-align-items-start">
<?=$pushBenefitReceivedDateTime?><br>
<p class="notice-danger notice-info">앱에서 알림 혜택이 지급된 일시입니다.</p></div>
</td>
</tr>
<tr>
<th><div>앱 주문 혜택</div></th>
<td><div><?=$firstOrderBenefitReceived;?></div></td>
<th><div>최초 주문 일시</div></th>
<td>
<div class="ncua-flex-column ncua-align-items-start">
<?=$firstOrderDateTime;?><br>
<p class="notice-danger notice-info">앱에서 결제완료한 최초 일시입니다.</p></div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<th><div>접속 정보</div></th>
<td class="ncua-member-access-info">
<div>
<div class="ncua-table ncua-table--horizontal">
<table>
<thead>
<tr>
<th><div class="ncua-align-center">운영체제</div></th>
<th><div class="ncua-align-center">모바일 기종</div></th>
<th><div class="ncua-align-center">최근 로그인 일시</div></th>
<th><div class="ncua-align-center">정보성 알림 수신 동의</div></th>
<th><div class="ncua-align-center">광고성 알림 수신 동의</div></th>
</tr>
</thead>
<tbody>
<tr>
<td><div class="ncua-align-center"><?=$deviceInfos['platform'];?></div></td>
<td><div class="ncua-align-center"><?=$deviceInfos['deviceModel'];?></div></td>
<td><div class="ncua-align-center"><?=$deviceInfos['latestLoginDateTime'];?></div></td>
<td><div class="ncua-align-center"><?=$deviceInfos['isNotificationAgreed'];?></div></td>
<td><div class="ncua-align-center"><?=$deviceInfos['isAdNotificationAgreed'];?></div></td>
</tr>
</tbody>
</table>
</div>
</div>
</td>
</tr>
</table>
</div>
<p class="ncua-notice-info">최근 90일내 접속한 디바이스가 표시됩니다.</p>
</div>
</section>
 
Admin/member/_member_view_business.php+318-215
변경 전_member_view_business.php
} ?>">(<?php echo $data['comZipcode']; ?>)</span>
<input type="button"
onclick="postcode_search('comZonecode', 'comAddress', 'comZipcode');"
value="우편번호" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray"/>
<!-- addr2 -->
<div class="ncua-input ncua-input--xs is-disabled ncua-member-addr2">
<div class="ncua-input__content">
<div class="table-title div-business gd-help-manual">
사업자정보
</div>
<div class="input_wrap form-inline div-business">
<table class="table table-cols">
<colgroup>
<col class="width-sm"/>
<col class="<?=(!empty($widthClass)) ? $widthClass : 'width-3xl'?>"/>
<col class="width-sm"/>
<col class="<?=(!empty($widthClass)) ? $widthClass : ''?>"/>
</colgroup>
<tr>
<th>상호</th>
<td>
<span title="회사명를 입력해주세요!">
<input type="text" name="company" class="form-control" value="<?= $data['company']; ?>" maxlength="50" data-pattern="gdMemberNmGlobal"/>
</span>
<span id="companyMsg" class="input_error_msg"></span>
</td>
<th>사업자번호</th>
<td>
<span title="사업자번호를 입력해주세요!">
<input type="text" name="busiNo[]" size="3" maxlength="3" class="form-control js-number"
value="<?= $data['busiNo'][0]; ?>"/>
-
<input type="text" name="busiNo[]" size="2" maxlength="2" class="form-control js-number"
value="<?= $data['busiNo'][1]; ?>"/>
-
<input type="text" name="busiNo[]" size="5" maxlength="5" class="form-control js-number"
value="<?= $data['busiNo'][2]; ?>"/>
<input type="hidden" id="busiNo" name="fullBusiNo" class="error" value="<?= gd_implode('', $data['busiNo']); ?>" data-overlap-businofl="<?= $joinField['busiNo']['overlapBusiNoFl']; ?>" data-charlen="<?= $joinField['busiNo']['charlen']; ?>" data-oldbusino="<?= gd_implode('', $data['busiNo']); ?>"/>
<?php if ($joinField['busiNo']['overlapBusiNoFl'] == 'y') { ?>
<button type="button" id="overlap_busiNo" class="btn btn-gray btn-sm">중복확인</button>
<?php } ?>
<button type="button" id="find_busiNo" class="btn btn-gray btn-sm">사업자번호 조회</button>
</span>
</td>
</tr>
<tr>
<th>대표자명</th>
<td class="input_area" colspan="3">
<span title="대표자를 입력해주세요!">
<input type="text" name="ceo" class="form-control" value="<?= $data['ceo']; ?>" data-pattern="gdEngKor" maxlength="20"/>
</span>
</td>
</tr>
<tr>
<th>업태</th>
<td>
<span title="업태를 입력해주세요!">
<input type="text" name="service" class="form-control" value="<?= $data['service']; ?>" data-pattern="gdEngKor" maxlength="30"/>
</span>
</td>
<th>종목</th>
<td>
<span title="종목을 입력해주세요!">
<input type="text" name="item" class="form-control" value="<?= $data['item']; ?>" data-pattern="gdEngKor" maxlength="30"/>
</span>
</td>
</tr>
<tr>
<th>사업장 주소</th>
<td class="input_area" colspan="3">
<div class="form-inline mgb5">
<span title="우편번호를 입력해주세요!">
<input type="text" size="7" maxlength="5" name="comZonecode" class="form-control"
value="<?= $data['comZonecode'] ?>"/>
<input type="hidden" name="comZipcode" value="<?= $data['comZipcode'] ?>"/>
<span id="comZipcodeText" class="number <?php if (strlen($data['comZipcode']) != 7) {
echo 'display-none';
} ?>">(<?php echo $data['comZipcode']; ?>)
</span>
</span>
<input type="button"
onclick="postcode_search('comZonecode', 'comAddress', 'comZipcode');"
value="우편번호찾기" class="btn btn-sm btn-gray"/>
</div>
<div>
<span title="주소를 입력해주세요!">
<input type="text" name="comAddress" class="form-control width-2xl"
value="<?= $data['comAddress']; ?>"/>
</span>
<span title="상세주소를 입력해주세요!">
<input type="text" name="comAddressSub"
class="form-control width-2xl"
value="<?= $data['comAddressSub']; ?>"/>
</span>
</div>
</td>
</tr>
<tr>
<th>사업자등록증</th>
<td class="input_area" colspan="3">
<?php if (!empty($certification)): ?>
<a href="#" class="js-certification-download" data-url="../member/company_certification.php?mode=download&sno=<?= $certification['sno'] ?>">
<?= $certification['imageFileNm'] ?>
</a>
<br>
<?php endif; ?>
<div class="form-inline">
<input type="file" id="companyCertification" name="companyCertification" class="form-control js-cert-file" accept="image/*, application/pdf" data-max-size="<?= $certificationMaxFileSize ?>"/>
<?php if ($mode === 'register'): // 등록 시에만 첨부 취소 버튼 노출 (수정 시 미노출) ?>
<button type="button" class="btn btn-white btn-sm js-cert-file-clear" style="display:none;">- 삭제</button>
<?php endif; ?>
</div>
<div class="notice-info mgt10">
파일 업로드 최대 사이즈는 <?= $certificationMaxFileSize ?>MB 입니다.<br>
</div>
<?php if (!empty($certification)): ?>
<span>
<input type="checkbox" name="certificationDeleteSno" class="js-certification-checkbox" value="<?= $certification['sno'] ?>" />
업로드된 파일 삭제 .. <?= $certification['imageFileNm'] ?>
<br>
</span>
<?php endif; ?>
</td>
</tr>
<?php
for ($i = 0; $i < $certificationAdditionalFileCount ; $i++) {
$additional = $additionalCertification["comAddiCert$i"] ?? null;
if($additional && $additional['use'] === 'y') { ?>
<tr>
<th><?= $additional['name'] ?></th>
<td class="input_area" colspan="3">
<?php if (!empty($additional['file'])): ?>
<a href="#" class="js-certification-download" data-url="../member/company_certification.php?mode=download&sno=<?= $additional['file']['sno'] ?>">
<?= $additional['file']['imageFileNm'] ?>
</a>
<br>
<?php endif; ?>
<div class="form-inline">
<input type="file" id="comAddiCert[<?= $i ?>][file]" name="comAddiCert[<?= $i ?>][file]" class="form-control js-cert-file" accept="image/*, application/pdf" data-max-size="<?= $additional['certificationFileSize'] ?>"/>
<?php if ($mode === 'register'): // 등록 시에만 첨부 취소 버튼 노출 (수정 시 미노출) ?>
<button type="button" class="btn btn-white btn-sm js-cert-file-clear" style="display:none;">- 삭제</button>
<?php endif; ?>
</div>
<div class="notice-info mgt10">
파일 업로드 최대 사이즈는 <?= $additional['certificationFileSize'] ?>MB 입니다.<br>
</div>
<?php if (!empty($additional['file'])): ?>
<span>
<input type="checkbox" name="comAddiCert[<?= $i ?>][DeleteSno]" class="js-certification-checkbox" value="<?= $additional['file']['sno'] ?>" />
업로드된 파일 삭제 .. <?= $additional['file']['imageFileNm'] ?>
<br>
</span>
<?php endif; ?>
</td>
</tr>
<?php
}
} ?>
</table>
</div>
<iframe id="downloadFrame" style="display:none;"></iframe>
<script>
$(document).on('click', '.js-certification-download', function(e) {
e.preventDefault();
var url = $(this).data('url');
$('#downloadFrame').attr('src', url);
});
$(document).on('change', '.js-certification-checkbox', function(e) {
var checkbox = this;
if (checkbox.checked) {
// 추가서류(comAddiCert[N][DeleteSno])와 사업자등록증(certificationDeleteSno) 문구 구분
var message = checkbox.name.indexOf('comAddiCert') === 0
? '등록된 첨부파일을 삭제하시겠습니까? 회원정보 최종 저장 시 삭제되며, 복구가 불가능 합니다.'
: '등록된 사업자등록증을 삭제하시겠습니까? 회원정보 최종 저장 시 삭제되며, 복구가 불가합니다.';
dialog_confirm(message, function(result) {
if (!result) {
checkbox.checked = false;
}
}, '경고');
}
});
// 선택 파일 비우기: bootstrap-filestyle 위젯이 적용된 경우 표시 텍스트박스까지 함께 비운다
function clearCertFile($input) {
if ($input.data('filestyle')) {
$input.filestyle('clear');
} else {
$input.val('');
}
}
// 찾아보기로 파일 선택 시 확장자·용량 검사 후 '- 삭제' 버튼 노출, 클릭 시 선택 취소(첨부 제거)
$(document).on('change', '.js-cert-file', function () {
const file = this.files[0];
if (file) {
const allowedExt = ['pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
const ext = file.name.split('.').pop().toLowerCase();
if (!allowedExt.includes(ext)) {
clearCertFile($(this));
dialog_alert('등록이 불가한 파일 형식입니다. 이미지(jpg, jpeg, png, gif, bmp, webp, svg) 및 pdf 파일만 업로드 가능합니다.');
} else {
const fileMaxSize = parseFloat($(this).data('max-size')) || 2; // MB
const fileSizeMB = file.size / (1024 * 1024);
if (fileSizeMB > fileMaxSize) {
clearCertFile($(this));
dialog_alert('파일은 최대 ' + fileMaxSize.toString() + 'MB 이하로만 등록 가능합니다.');
}
}
}
$(this).closest('.form-inline').find('.js-cert-file-clear').toggle(this.files.length > 0);
});
$(document).on('click', '.js-cert-file-clear', function () {
clearCertFile($(this).closest('.form-inline').find('.js-cert-file'));
$(this).hide();
});
</script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후_member_view_business.php
} ?>">(<?php echo $data['comZipcode']; ?>)</span>
<input type="button"
onclick="postcode_search('comZonecode', 'comAddress', 'comZipcode');"
value="주소찾기" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray"/>
<!-- addr2 -->
<div class="ncua-input ncua-input--xs is-disabled ncua-member-addr2">
<div class="ncua-input__content">
<section class="ncua-block-container div-business ncua-member-business">
<header class="ncua-block-header">
<div class="ncua-block-header__row">
<div class="ncua-block-header__title-area">
<span class="ncua-block-header__title">사업자정보</span>
</div>
</div>
</header>
<div class="ncua-block-container__body">
<div class="ncua-table ncua-table--vertical">
<table>
<colgroup>
<col style="width: 240px;"/>
<col />
</colgroup>
<!-- 상호 -->
<tr>
<th><div>상호</div></th>
<td><div class="ncua-gap-4">
<div class="ncua-input ncua-input--xs ncua-input-width-320">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="company" value="<?= $data['company']; ?>" maxlength="50" data-pattern="gdMemberNmGlobal" title="회사명를 입력해주세요!"/>
</div>
</div>
</div>
<span id="companyMsg" class="input_error_msg"></span>
</div></td>
</tr>
<!-- 사업자번호 -->
<tr>
<th><div>사업자번호</div></th>
<td><div class="ncua-flex ncua-align-items-center ncua-gap-4">
<div class="ncua-input ncua-input--xs ncua-input-width-80">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="busiNo[]" maxlength="3" class="js-number" value="<?= $data['busiNo'][0]; ?>"/>
</div>
</div>
</div>
-
<div class="ncua-input ncua-input--xs ncua-input-width-80">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="busiNo[]" maxlength="2" class="js-number" value="<?= $data['busiNo'][1]; ?>"/>
</div>
</div>
</div>
-
<div class="ncua-input ncua-input--xs ncua-input-width-100">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="busiNo[]" maxlength="5" class="js-number" value="<?= $data['busiNo'][2]; ?>"/>
</div>
</div>
</div>
<input type="hidden" id="busiNo" name="fullBusiNo" class="error" value="<?= gd_implode('', $data['busiNo']); ?>" data-overlap-businofl="<?= $joinField['busiNo']['overlapBusiNoFl']; ?>" data-charlen="<?= $joinField['busiNo']['charlen']; ?>" data-oldbusino="<?= gd_implode('', $data['busiNo']); ?>"/>
<?php if ($joinField['busiNo']['overlapBusiNoFl'] == 'y') { ?>
<button type="button" id="overlap_busiNo" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray">중복확인</button>
<?php } ?>
<button type="button" id="find_busiNo" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray">사업자번호 조회</button>
</div></td>
</tr>
<!-- 대표자명 -->
<tr>
<th><div>대표자명</div></th>
<td><div>
<div class="ncua-input ncua-input--xs ncua-input-width-320">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="ceo" value="<?= $data['ceo']; ?>" data-pattern="gdEngKor" maxlength="20" title="대표자를 입력해주세요!"/>
</div>
</div>
</div>
</div></td>
</tr>
<!-- 업태 -->
<tr>
<th><div>업태</div></th>
<td><div>
<div class="ncua-input ncua-input--xs ncua-input-width-320">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="service" value="<?= $data['service']; ?>" data-pattern="gdEngKor" maxlength="30" title="업태를 입력해주세요!"/>
</div>
</div>
</div>
</div></td>
</tr>
<!-- 종목 -->
<tr>
<th><div>종목</div></th>
<td><div>
<div class="ncua-input ncua-input--xs ncua-input-width-320">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="item" value="<?= $data['item']; ?>" data-pattern="gdEngKor" maxlength="30" title="종목을 입력해주세요!"/>
</div>
</div>
</div>
</div></td>
</tr>
<!-- 사업장 주소 -->
<tr>
<th><div>사업장 주소</div></th>
<td><div>
<div class="ncua-member-addr">
<!-- addr1 -->
<div class="ncua-input ncua-input--xs is-disabled ncua-member-addr1">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" maxlength="5" name="comZonecode" value="<?= $data['comZonecode'] ?>" title="우편번호를 입력해주세요!"/>
</div>
</div>
</div>
<input type="hidden" name="comZipcode" value="<?= $data['comZipcode'] ?>"/>
<span id="comZipcodeText" class="number <?php if (strlen($data['comZipcode']) != 7) {
echo 'display-none';
} ?>">(<?php echo $data['comZipcode']; ?>)</span>
<input type="button"
onclick="postcode_search('comZonecode', 'comAddress', 'comZipcode');"
value="우편번호" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray"/>
<!-- addr2 -->
<div class="ncua-input ncua-input--xs is-disabled ncua-member-addr2">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="comAddress" value="<?= $data['comAddress']; ?>" title="주소를 입력해주세요!"/>
</div>
</div>
</div>
<!-- addr3 -->
<div class="ncua-input ncua-input--xs ncua-member-addr2">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="comAddressSub" value="<?= $data['comAddressSub']; ?>" title="상세주소를 입력해주세요!"/>
</div>
</div>
</div>
</div>
 
</div></td>
</tr>
<!-- 사업자등록증 -->
<tr>
<th><div>사업자등록증</div></th>
<td><div>
<?php if (!empty($certification)): ?>
<a href="#" class="js-certification-download" data-url="../member/company_certification.php?mode=download&sno=<?= $certification['sno'] ?>">
<?= $certification['imageFileNm'] ?>
</a>
<?php endif; ?>
<div class="member-cert-upload">
<div id="companyCertificationFileInput"></div>
<input type="file" id="companyCertification" name="companyCertification" accept="image/*, application/pdf" class="no-filestyle" style="display:none"/>
<div id="companyCertificationTags" class="member-cert-files"></div>
</div>
<?php if (!empty($certification)): ?>
<label class="ncua-checkbox-field ncua-checkbox-field--xs has-text">
<span class="ncua-checkbox-input ncua-checkbox-input--xs ncua-checkbox-field__input">
<input type="checkbox" name="certificationDeleteSno" class="js-certification-checkbox" value="<?= $certification['sno'] ?>" />
</span>
<span><span class="ncua-checkbox-field__text">Delete Uploaded File .. <?= $certification['imageFileNm'] ?></span></span>
</label>
<?php endif; ?>
</div></td>
</tr>
<?php
// 추가서류: 사업자등록증과 동일하게 ncua.FileInput 위젯 사용 (행마다 별도 인스턴스)
$addiCertInits = [];
for ($i = 0; $i < $certificationAdditionalFileCount ; $i++) {
$additional = $additionalCertification["comAddiCert$i"] ?? null;
if($additional && $additional['use'] === 'y') {
$addiCertInits[] = ['i' => $i, 'maxSize' => (float)($additional['certificationFileSize'] ?? 2)];
?>
<tr>
<th><div><?= $additional['name'] ?></div></th>
<td><div>
<?php if (!empty($additional['file'])): ?>
<a href="#" class="js-certification-download" data-url="../member/company_certification.php?mode=download&sno=<?= $additional['file']['sno'] ?>">
<?= $additional['file']['imageFileNm'] ?>
</a>
<?php endif; ?>
<div class="member-cert-upload">
<div id="comAddiCertFileInput_<?= $i ?>"></div>
<input type="file" id="comAddiCertCarrier_<?= $i ?>" name="comAddiCert[<?= $i ?>][file]" accept="image/*, application/pdf" class="no-filestyle" style="display:none"/>
<div id="comAddiCertTags_<?= $i ?>" class="member-cert-files"></div>
</div>
<?php if (!empty($additional['file'])): ?>
<label class="ncua-checkbox-field ncua-checkbox-field--xs has-text">
<span class="ncua-checkbox-input ncua-checkbox-input--xs ncua-checkbox-field__input">
<input type="checkbox" name="comAddiCert[<?= $i ?>][DeleteSno]" class="js-certification-checkbox" value="<?= $additional['file']['sno'] ?>" />
</span>
<span><span class="ncua-checkbox-field__text">업로드된 파일 삭제 .. <?= $additional['file']['imageFileNm'] ?></span></span>
</label>
<?php endif; ?>
</div></td>
</tr>
<?php
}
} ?>
</table>
</div>
</div>
</section>
<iframe id="downloadFrame" style="display:none;"></iframe>
<script>
$(document).on('click', '.js-certification-download', function(e) {
e.preventDefault();
var url = $(this).data('url');
$('#downloadFrame').attr('src', url);
});
 
$(document).on('change', '.js-certification-checkbox', function(e) {
var checkbox = this;
if (checkbox.checked) {
// 추가서류(comAddiCert[N][DeleteSno])와 사업자등록증(certificationDeleteSno) 문구 구분
var message = checkbox.name.indexOf('comAddiCert') === 0
? '등록된 첨부파일을 삭제하시겠습니까? 회원정보 최종 저장 시 삭제되며, 복구가 불가능 합니다.'
: '등록된 사업자등록증을 삭제하시겠습니까? 회원정보 최종 저장 시 삭제되며, 복구가 불가합니다.';
dialog_confirm(message, function(result) {
if (!result) {
checkbox.checked = false;
}
}, '경고');
}
});
</script>
<script>
// 사업자등록증/추가서류 공통: ncua.FileInput 위젯 초기화 (파일을 폼 제출용 carrier input에 주입)
function initCertFileInput(opts) {
const tagContainer = document.getElementById(opts.tagsId);
const carrier = document.getElementById(opts.carrierId);
if (!tagContainer || !carrier) {
return;
}
let currentTag = null;
 
// 선택 파일을 폼 제출용 file input에 주입 (multipart 전송)
const setCarrierFile = function (file) {
const dt = new DataTransfer();
if (file) {
dt.items.add(file);
}
carrier.files = dt.files;
};
 
new ncua.FileInput({
container: opts.containerId,
buttonLabel: '파일 찾기',
multiple: false,
size: 'xs',
accept: '.pdf,.jpg,.jpeg,.png,.gif,.bmp,.webp,.svg',
maxFileSize: opts.maxSizeMB * 1024 * 1024,
hintItems: ['파일 업로드 최대 사이즈는 ' + opts.maxSizeMB + 'MB 입니다.'],
// 파일 선택 시: (1) carrier input에 주입해 제출 (2) 회색 영역에 파일명 태그 + 썸네일 표시 (단일 파일)
onChange: function (newFiles) {
tagContainer.innerHTML = '';
currentTag = null;
const file = (newFiles || [])[0];
setCarrierFile(file);
if (!file) {
return;
}
// 파일명 태그 + 삭제 버튼
currentTag = new ncua.Tag({
text: file.name,
size: 'sm',
close: true,
onButtonClick: function () {
tagContainer.innerHTML = '';
currentTag = null;
setCarrierFile(null);
},
});
tagContainer.appendChild(currentTag.getElement());
// 이미지 파일이면 썸네일 미리보기
if (file.type && file.type.indexOf('image/') === 0) {
const reader = new FileReader();
reader.onload = function (ev) {
const img = document.createElement('img');
img.className = 'member-cert-files__thumb';
img.src = ev.target.result;
img.alt = file.name;
tagContainer.appendChild(img);
};
reader.readAsDataURL(file);
}
},
onFail: function () {
dialog_alert('이미지(jpg, jpeg, png, gif, bmp, webp, svg) 및 pdf 파일만, 최대 ' + opts.maxSizeMB + 'MB 이하로 등록 가능합니다.');
},
});
}
 
document.addEventListener('DOMContentLoaded', function () {
const certMaxSizeMB = (typeof certificationMaxFileSize !== 'undefined' ? certificationMaxFileSize : 2);
 
// 사업자등록증
initCertFileInput({
containerId: 'companyCertificationFileInput',
carrierId: 'companyCertification',
tagsId: 'companyCertificationTags',
maxSizeMB: certMaxSizeMB,
});
 
// 추가서류 (행마다 별도 인스턴스)
var addiCertInits = <?= json_encode($addiCertInits) ?>;
addiCertInits.forEach(function (cfg) {
initCertFileInput({
containerId: 'comAddiCertFileInput_' + cfg.i,
carrierId: 'comAddiCertCarrier_' + cfg.i,
tagsId: 'comAddiCertTags_' + cfg.i,
maxSizeMB: cfg.maxSize,
});
});
});
</script>
 
Admin/member/_member_view_history.php+65-48
변경 전_member_view_history.php
<form id="frmHistory" method="get">
<input type="hidden" name="pageNum"/>
 
<div class="table-title">
회원정보 수정이력
</div>
<div class="table-header">
<div class="pull-right">
<div class="form-inline">
<?= gd_select_box_by_page_view_count(Request::get()->get('pageNum', 10), null, null, ''); ?>
</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
</div>
</div>
<table class="table table-rows">
<colgroup>
<col class="width-xs"/>
<col class="width-md"/>
<col class="width-sm"/>
<col class="width-sm"/>
<col class="width-md"/>
<col/>
</colgroup>
<thead>
<tr>
<th>번호</th>
<th>변경일자</th>
<th>처리자</th>
<th>IP주소</th>
<th>변경항목</th>
<th>변경내용</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="center" id="div-pagination"></div>
</form>
<script type="text/template" id="history-row-template2">
<% _.each(data, function(item){ %>
<% if(item.updateColumn !== '최종구매일') { %>
<tr class="center">
<td class="font-num"><%- item.idx %></td>
<td class="font-date"><%- item.regDt %></td>
<td><%- item.processor %><%- item.deleteText %></td>
<td><%- item.processorIp %></td>
<td><%- item.updateColumn %></td>
 
 
 
 
 
<% if(item.updateColumn === '비밀번호') { %>
<td><%- item.afterValue %></td>
<% } else { %>
<td><%- item.beforeValue %>&nbsp;&gt;&nbsp;<%- item.afterValue %><% if (typeof(item.displayOtherValue) === 'string') { %><%- item.displayOtherValue %><% } %></br>
<% if(item.otherValue != 'null' && typeof(item.otherValue) === 'string' && item.processor == 'member') { %><%- item.otherValue %><% } %>
<% if(item.memberLifeEventValue != 'null' && typeof(item.memberLifeEventValue) === 'string' && item.updateColumn == '개인정보유효기간' && item.beforeValue !== '999' && item.afterValue === '999') { %><%- item.memberLifeEventValue %><% } %>
</td>
<% } %>
</tr>
<% } %>
<%});%>
</script>
<script id="history-pagination-template2" type="text/template">
<nav>
<ul class="pagination pagination-sm">
<% for(var i = page.start; i < page.end; i++) { %> <% if(i === (page.now *1)){ %>
<li class="active">
<span><%- i %></span>
</li>
 
변경 후_member_view_history.php
<section class="ncua-content ncua-block-container ncua-member-view-history">
<header class="ncua-block-header">
<div class="ncua-block-header__row">
<span class="ncua-block-header__title">회원정보 수정이력</span>
</div>
</header>
<div class="ncua-block-container__body">
<div class="ncua-search-result">
<form id="frmHistory" method="get">
<input type="hidden" name="pageNum"/>
<div class="ncua-search-result__content">
<div class="ncua-search-result__actions">
<div class="ncua-search-result__actions-select">
<span class="ncua-select ncua-select--xs">
<span class="ncua-select__content">
<?= gd_select_box_by_page_view_count(Request::get()->get('pageNum', 10), null, null, 'ncua-select__tag'); ?>
</span>
</span>
</div>
</div>
<div class="ncua-table ncua-table--horizontal">
<table>
<colgroup>
<col width="80px"/>
<col width="200px"/>
<col width="100px"/>
<col width="100px"/>
<col width="278px"/>
<col width="*"/>
</colgroup>
<thead>
<tr>
<th><div>번호</div></th>
<th><div>변경일자</div></th>
<th><div>처리자</div></th>
<th><div>IP주소</div></th>
<th><div>변경항목</div></th>
<th><div>변경내용</div></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="ncua-pagination" id="div-pagination"></div>
</form>
</div>
</div>
</section>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<script type="text/template" id="history-row-template2">
<% var rows = _.filter(data || [], function(item){ return item.updateColumn !== '최종구매일'; }); %>
<% if(rows.length === 0) { %>
<tr>
<td colspan="6" height="50" class="no-data"><div>등록된 게시물이 없습니다.</div></td>
</tr>
<% } %>
<% _.each(rows, function(item){ %>
<tr>
<td><div><%- item.idx %></div></td>
<td><div><%- item.regDt %></div></td>
<td><div><%- item.processor %><%- item.deleteText %></div></td>
<td><div><%- item.processorIp %></div></td>
<td><div><%- item.updateColumn %></div></td>
<% if(item.updateColumn === '비밀번호') { %>
<td><div><%- item.afterValue %></div></td>
<% } else { %>
<td><div><%- item.beforeValue %>&nbsp;&gt;&nbsp;<%- item.afterValue %><% if (typeof(item.displayOtherValue) === 'string') { %><%- item.displayOtherValue %><% } %><br/>
<% if(item.otherValue != 'null' && typeof(item.otherValue) === 'string' && item.processor == 'member') { %><%- item.otherValue %><% } %>
<% if(item.memberLifeEventValue != 'null' && typeof(item.memberLifeEventValue) === 'string' && item.updateColumn == '개인정보유효기간' && item.beforeValue !== '999' && item.afterValue === '999') { %><%- item.memberLifeEventValue %><% } %>
</div></td>
<% } %>
</tr>
 
<%});%>
</script>
<script id="history-pagination-template2" type="text/template">
<nav>
<ul class="pagination pagination-sm">
<% for(var i = page.start; i <= page.end; i++) { %> <% if(i === (page.now *1)){ %>
<li class="active">
<span><%- i %></span>
</li>
 
Admin/member/_member_view_other.php+306-211
변경 전_member_view_other.php
<div class="table-title gd-help-manual">부가정보</div>
<div class="input_wrap form-inline">
<table class="table table-cols">
<colgroup>
<col class="width-sm"/>
<col class="<?= (!empty($widthClass)) ? $widthClass : 'width-3xl' ?>"/>
<col class="width-sm"/>
<col class="<?= (!empty($widthClass)) ? $widthClass : '' ?>"/>
</colgroup>
<tr>
<th>팩스번호</th>
<td>
<span title="팩스번호를 입력해주세요!">
<input type="text" name="fax" value="<?= $data['fax'] ?>" maxlength="12" class="form-control js-number-only width-md"/>
</span>
</td>
<th>직업</th>
<td class="input_area" colspan="3">
<?= gd_select_box('job', 'job', $joinField['job']['data'], null, $data['job'], '선택'); ?>
</td>
</tr>
<tr>
<th>성별</th>
<td>
<label class="radio-inline">
<input type="radio" name="sexFl" value="m" <?= $checked['sexFl']['m']; ?>>
남자
</label>
<label class="radio-inline">
<input type="radio" name="sexFl" value="w" <?= $checked['sexFl']['w']; ?>>
여자
</label>
</td>
<th>생일</th>
<td>
<?= gd_select_box(
'calendarFl', 'calendarFl', [
's' => '양력',
'l' => '음력',
], null, $data['calendarFl'], '선택'
); ?>
<span title="생일을 입력해주세요!">
<input type="text" class="js-datepicker" name="birthDt"
value="<?= $data['birthDt']; ?>"/>
</span>
</td>
</tr>
<tr>
<th>결혼여부</th>
<td>
<label class="radio-inline">
<input type="radio" name="marriFl" value="n"
data-target="input[name=marriDate]" <?= $checked['marriFl']['n']; ?>>
미혼
</label>
<label class="radio-inline">
<input type="radio" name="marriFl" value="y"
data-target="input[name=marriDate]" <?= $checked['marriFl']['y']; ?>>
기혼
</label>
</td>
<th>결혼기념일</th>
<td>
<span title="결혼기념일을 입력해주세요!">
<input type="text" class="js-datepicker" name="marriDate"
value="<?= $data['marriDate']; ?>"/>
</span>
</td>
</tr>
<tr>
<th>추천인아이디</th>
<td>
<span id="recomm" title="추천인 아이디를 입력해주세요!">
<?php if (empty(gd_isset($data['recommId'], ''))) { ?>
<input type="text" name="recommId" id="recommId" class="form-control error"
value="<?= gd_isset($data['recommId'], ''); ?>"/>
<input type="hidden" name="chkRecommendId" value=""/>
<input type="button" id="btnRecommendCheck" value="확인" class="btn btn-gray btn-sm"/>
<?php } else {
echo gd_isset($data['recommId'], '');
echo '<input type="hidden" name="recommId" value="' . gd_isset($data['recommId']) . '"/>';
?>
<?php } ?>
</span>
<span id="recommIdMsg" class="input_error_msg"></span>
</td>
<th>추천받은횟수</th>
<td><?= gd_isset($data['recommendCnt'], '0'); ?>회</td>
</tr>
<tr>
<th>관심분야</th>
<td class="input_area" colspan="3">
<?= gd_check_box('interest[]', $joinField['interest']['data'], $data['interest'], $checkBoxRow) ?>
</td>
</tr>
<tr>
<th>개인정보유효기간</th>
<td class="input_area" colspan="3">
<label class="radio-inline">
<input type="radio" name="expirationFl" value="1" <?= $checked['expirationFl']['1']; ?>>
1년
</label>
<label class="radio-inline">
<input type="radio" name="expirationFl" value="3" <?= $checked['expirationFl']['3']; ?>>
3년
</label>
<label class="radio-inline">
<input type="radio" name="expirationFl" value="5" <?= $checked['expirationFl']['5']; ?>>
5년
</label>
<label class="radio-inline">
<input type="radio" name="expirationFl" value="999" <?= $checked['expirationFl']['999']; ?>>
탈퇴 시 <?= $data['lifeMemberConversionDt']; ?>
</label>
</td>
</tr>
<tr>
<th>남기는 말씀</th>
<td class="input_area" colspan="3">
<span title="메모를 작성해 주세요!">
<textarea name="memo" rows="5" cols=""
class="form-control width90p js-maxlength"><?= $data['memo']; ?></textarea>
</span>
</td>
</tr>
</table>
</div>
<?php if (gd_count($joinField['ex']) > 0) { ?>
<div class="table-title gd-help-manual">
추가정보
</div>
<div class="input_wrap form-inline">
<table class="table table-cols">
<colgroup>
<col class="width-sm"/>
<col class="<?= (!empty($widthClass)) ? $widthClass : 'width-3xl' ?>"/>
<col class="width-sm"/>
<col class="<?= (!empty($widthClass)) ? $widthClass : '' ?>"/>
</colgroup>
<?= $htmlExtra; ?>
</table>
</div>
<?php } ?>
<div class="table-title gd-help-manual">
개인정보 수집&middot;이용 선택동의 내역
</div>
<div class="form-inline">
<table class="table table-cols">
<colgroup>
<col class="width-sm"/>
<col/>
</colgroup>
<tr>
<th>개인정보<br/>수집&middot;이용</th>
<td>
<?php
if (isset($data['privateApprovalOptionFl']) && is_array($data['privateApprovalOptionFl']) && isset($privateApprovalOption)) {
foreach ($privateApprovalOption as $index2 => $item2) {
if($data['privateApprovalOptionFl'][$item2['sno']] == 'y'){
echo '<div>- ' . $item2['informNm'] . ' : 동의함</div>';
} elseif ($data['privateApprovalOptionFl'][$item2['sno']] == 'n') {
echo '<div>- ' . $item2['informNm'] . ' : 동의안함</div>';
}
}
} else {
echo '사용안함';
}
?>
</td>
</tr>
<tr>
<th>개인정보 취급위탁</th>
<td>
<?php
if (isset($data['privateConsignFl']) && is_array($data['privateConsignFl']) && isset($privateConsign)) {
foreach ($privateConsign as $index2 => $item2) {
if($data['privateConsignFl'][$item2['sno']] == 'y'){
echo '<div>- ' . $item2['informNm'] . ' : 동의함</div>';
} elseif ($data['privateConsignFl'][$item2['sno']] == 'n') {
echo '<div>- ' . $item2['informNm'] . ' : 동의안함</div>';
}
}
} else {
echo '사용안함';
}
?>
</td>
</tr>
<tr>
<th>개인정보<br/>제3자 제공</th>
<td>
<?php
if (isset($data['privateOfferFl']) && is_array($data['privateOfferFl']) && isset($privateOffer)) {
foreach ($privateOffer as $index2 => $item2) {
if($data['privateOfferFl'][$item2['sno']] == 'y'){
echo '<div>- ' . $item2['informNm'] . ' : 동의함</div>';
} elseif ($data['privateOfferFl'][$item2['sno']] == 'n') {
echo '<div>- ' . $item2['informNm'] . ' : 동의안함</div>';
}
}
} else {
echo '사용안함';
}
?>
</td>
</tr>
</table>
</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후_member_view_other.php
<section class="ncua-block-container">
<header class="ncua-block-header">
<div class="ncua-block-header__row">
<div class="ncua-block-header__title-area">
<span class="ncua-block-header__title">부가정보</span>
</div>
</div>
</header>
<div class="ncua-block-container__body">
<div class="ncua-table ncua-table--vertical">
<table>
<colgroup>
<col style="width: 240px;"/>
<col />
</colgroup>
<!-- 팩스 번호 -->
<tr>
<th><div data-tooltip-seq="025">팩스번호</div></th>
<td><div>
<div class="ncua-input ncua-input--xs ncua-input-width-320">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="fax" value="<?= $data['fax'] ?>" maxlength="12" class="js-number-only" title="팩스번호를 입력해주세요!" placeholder="'-' 없이 입력하세요."/>
</div>
</div>
</div>
</div></td>
</tr>
<!-- 직업 -->
<tr>
<th><div data-tooltip-seq="026">직업</div></th>
<td><div>
<span class="ncua-select ncua-select--xs">
<span class="ncua-select__content">
<?= gd_select_box('job', 'job', $joinField['job']['data'], null, $data['job'], '선택', null, 'ncua-select__tag'); ?>
</span>
</span>
</div></td>
</tr>
<!-- 성별 -->
<tr>
<th><div data-tooltip-seq="027">성별</div></th>
<td><div class="ncua-flex-gap">
<!-- TODO(개발자 확인): '선택안함'(sexFl value="") 신규 추가. checked 하드코딩이라 저장값과 무관하게 항상 선택됨 → $checked['sexFl'] 기반 조건 처리 필요. BE에서 빈 값('') 저장/표시 처리 확인 필요 -->
<label class="ncua-radio-field ncua-radio-field--xs has-text">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="sexFl" value="" checked />
</span>
<span class="ncua-radio-field__text">선택안함</span>
</label>
<label class="ncua-radio-field ncua-radio-field--xs has-text">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="sexFl" value="m" <?= $checked['sexFl']['m']; ?>/>
</span>
<span class="ncua-radio-field__text">남자</span>
</label>
<label class="ncua-radio-field ncua-radio-field--xs has-text">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="sexFl" value="w" <?= $checked['sexFl']['w']; ?>/>
</span>
<span class="ncua-radio-field__text">여자</span>
</label>
</div></td>
</tr>
<!-- 생일 -->
<tr>
<th><div data-tooltip-seq="028">생일</div></th>
<td><div class="ncua-flex ncua-align-items-center ncua-gap-8">
<span class="ncua-select ncua-select--xs">
<span class="ncua-select__content">
<?= gd_select_box('calendarFl', 'calendarFl', ['s' => '양력', 'l' => '음력'], null, $data['calendarFl'], '선택', null, 'ncua-select__tag'); ?>
</span>
</span>
<div id="datepicker-birthday-container"></div>
</div></td>
</tr>
<!-- 결혼여부 -->
<tr>
<th><div data-tooltip-seq="029">결혼여부</div></th>
<td><div class="ncua-flex-gap">
<!-- TODO(개발자 확인): '선택안함'(marriFl value="n") 신규 추가. 미혼과 value="n"이 동일해 BE에서 구분 불가 + checked 하드코딩. 선택안함 전용 값/처리 방식 확인 필요 -->
<label class="ncua-radio-field ncua-radio-field--xs has-text">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="marriFl" value="n" data-target="input[name=marriDate]" checked />
</span>
<span class="ncua-radio-field__text">선택안함</span>
</label>
<label class="ncua-radio-field ncua-radio-field--xs has-text">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="marriFl" value="n" data-target="input[name=marriDate]" <?= $checked['marriFl']['n']; ?>/>
</span>
<span class="ncua-radio-field__text">미혼</span>
</label>
<label class="ncua-radio-field ncua-radio-field--xs has-text">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="marriFl" value="y" data-target="input[name=marriDate]" <?= $checked['marriFl']['y']; ?>/>
</span>
<span class="ncua-radio-field__text">기혼</span>
</label>
</div></td>
</tr>
<tr>
<th><div data-tooltip-seq="030">결혼기념일</div></th>
<td><div>
<div id="datepicker-marriage-container"></div>
</div></td>
</tr>
<tr>
<th><div data-tooltip-seq="031">추천인아이디</div></th>
<td><div class="ncua-gap-4">
<span id="recomm">
<?php if (empty(gd_isset($data['recommId'], ''))) { ?>
<div class="ncua-input ncua-input--xs ncua-input-width-320">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="recommId" id="recommId" class="error" value="<?= gd_isset($data['recommId'], ''); ?>" title="추천인 아이디를 입력해주세요!" placeholder="추천인 아이디를 입력하세요."/>
</div>
</div>
</div>
<input type="hidden" name="chkRecommendId" value=""/>
<button type="button" id="btnRecommendCheck" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray">확인</button>
<?php } else {
echo gd_isset($data['recommId'], '');
echo '<input type="hidden" name="recommId" value="' . gd_isset($data['recommId']) . '"/>';
} ?>
</span>
<span id="recommIdMsg" class="input_error_msg"></span>
</div></td>
</tr>
<tr>
<th><div data-tooltip-seq="032">추천받은횟수</div></th>
<td><div><?= gd_isset($data['recommendCnt'], '0'); ?>회</div></td>
</tr>
<tr>
<th><div data-tooltip-seq="033">관심분야</div></th>
<td><div class="ncua-flex-gap">
<?= gd_check_box_ncua('interest[]', $joinField['interest']['data'] ?? [], $data['interest'], $checkBoxRow ?? 0) ?>
</div></td>
</tr>
<tr>
<th><div data-tooltip-seq="034">개인정보유효기간</div></th>
<td><div class="ncua-flex-gap">
<label class="ncua-radio-field ncua-radio-field--xs has-text">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="expirationFl" value="1" <?= $checked['expirationFl']['1']; ?>/>
</span>
<span class="ncua-radio-field__text">1년</span>
</label>
<label class="ncua-radio-field ncua-radio-field--xs has-text">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="expirationFl" value="3" <?= $checked['expirationFl']['3']; ?>/>
</span>
<span class="ncua-radio-field__text">3년</span>
</label>
<label class="ncua-radio-field ncua-radio-field--xs has-text">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="expirationFl" value="5" <?= $checked['expirationFl']['5']; ?>/>
</span>
<span class="ncua-radio-field__text">5년</span>
</label>
<label class="ncua-radio-field ncua-radio-field--xs has-text">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="expirationFl" value="999" <?= $checked['expirationFl']['999']; ?>/>
</span>
<span class="ncua-radio-field__text">탈퇴 시 <?= $data['lifeMemberConversionDt']; ?></span>
</label>
</div></td>
</tr>
<tr>
<th><div data-tooltip-seq="035">남기는 말씀</div></th>
<td><div>
<div class="ncua-input ncua-input--textarea ncua-input--textarea--xs">
<textarea class="ncua-input__textarea" name="memo" rows="5" title="메모를 작성해 주세요!" placeholder="내용을 입력하세요."><?= $data['memo']; ?></textarea>
</div>
</div></td>
</tr>
</table>
</div>
</div>
</section>
 
<?php if (gd_count($joinField['ex']) > 0) { ?>
<section class="ncua-block-container">
<header class="ncua-block-header">
<div class="ncua-block-header__row">
<div class="ncua-block-header__title-area">
<span class="ncua-block-header__title">추가정보</span>
</div>
</div>
</header>
<div class="ncua-block-container__body">
<!-- tr/th/td 행 전체는 makeExtraField(BE) 생성물. 사용 중인 추가정보 항목 1개당 tr 1행(th + td) 출력 -->
<div class="ncua-table ncua-table--vertical">
<table>
<colgroup>
<col style="width: 240px;"/>
<col />
</colgroup>
<?= $htmlExtra; ?>
</table>
</div>
</div>
</section>
<?php } ?>
 
<section class="ncua-block-container">
<header class="ncua-block-header">
<div class="ncua-block-header__row">
<div class="ncua-block-header__title-area">
<span class="ncua-block-header__title">개인정보 수집&middot;이용 선택동의 내역</span>
</div>
</div>
</header>
<div class="ncua-block-container__body">
<div class="ncua-table ncua-table--vertical">
<table>
<colgroup>
<col style="width: 240px;"/>
<col />
</colgroup>
<?php foreach (($privateAgreementRows ?? []) as $row): ?>
<tr>
<th>
<div<?= !empty($row['tooltipSeq']) ? ' data-tooltip-seq="' . $row['tooltipSeq'] . '"' : '' ?>><?= $row['title'] ?></div>
</th>
<td>
<?php foreach ($row['lines'] as $line): ?>
<div><?= $line ?></div>
<?php endforeach; ?>
</td>
</tr>
<?php endforeach; ?>
<tr>
<th><div data-tooltip-seq="040">광고성 수신 동의</div></th>
<td>
<?php foreach (($advertisingReceiveLines ?? []) as $line): ?>
<div><?= $line ?></div>
<?php endforeach; ?>
</td>
</tr>
</table>
</div>
</div>
</section>
 
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
const birthDatePicker = new ncua.DatePicker(document.querySelector('#datepicker-birthday-container'), {
size: 'xs',
datePickerOptions: [{
element: 'start-date',
attrName: 'birthDt',
options: {
mode: 'single',
static: true,
dateFormat: 'Y-m-d',
clickOpens: true,
allowInvalidPreload: true,
allowInput: true,
locale: 'ko',
},
}],
});
<?php if (!empty($data['birthDt'])) { ?>
birthDatePicker.setDate(["<?= $data['birthDt']; ?>"]);
<?php } ?>
 
const marriageContainer = document.querySelector('#datepicker-marriage-container');
const marriageDatePicker = new ncua.DatePicker(marriageContainer, {
size: 'xs',
datePickerOptions: [{
element: 'start-date',
attrName: 'marriDate',
options: {
mode: 'single',
static: true,
dateFormat: 'Y-m-d',
clickOpens: true,
allowInvalidPreload: true,
allowInput: true,
locale: 'ko',
},
}],
});
<?php if (!empty($data['marriDate'])) { ?>
marriageDatePicker.setDate(["<?= $data['marriDate']; ?>"]);
<?php } ?>
 
// 결혼여부(marriFl)에 따라 결혼기념일 datepicker 활성/비활성 (기혼=y일 때만 활성)
const syncMarriageDisabled = function () {
const married = document.querySelector('input[name="marriFl"]:checked');
const isEnabled = !!(married && married.value === 'y');
const ncuaDatePicker = marriageContainer.querySelector('.ncua-date-picker');
document.querySelectorAll('[name="marriDate"]').forEach(function (el) {
el.disabled = !isEnabled;
});
if (ncuaDatePicker) {
ncuaDatePicker.classList.toggle('ncua-date-picker--disabled', !isEnabled);
}
};
syncMarriageDisabled();
document.querySelectorAll('input[name="marriFl"]').forEach(function (el) {
el.addEventListener('change', syncMarriageDisabled);
});
});
</script>
 
Admin/member/_member_view.php+427-282
변경 전_member_view.php
<div class="table-title gd-help-manual">
기본정보
</div>
<div class="form-inline">
<table class="table table-cols">
<colgroup>
<col class="width-sm"/>
<col class="<?= (!empty($widthClass)) ? $widthClass : 'width-3xl' ?>"/>
<col class="width-sm"/>
<col class="<?= (!empty($widthClass)) ? $widthClass : '' ?>"/>
</colgroup>
<tr>
<th>회원구분</th>
<td <?= $data['mallSno'] > 0 ? '' : 'colspan="3"' ?>>
<label class="radio-inline">
<input type="radio" name="memberFl" value="personal"
data-target=".div-business" <?= $checked['memberFl']['personal']; ?>/>
개인회원
</label>
<?php if ($data['mallSno'] == $gGlobal['defaultMallSno'] || $mode == 'register') { ?>
<label class="radio-inline">
<input type="radio" name="memberFl" value="business"
data-target=".div-business" <?= $checked['memberFl']['business']; ?>/>
사업자회원
</label>
<?php } ?>
</td>
<?php if ($data['mallSno'] > 0) { ?>
<th>상점구분</th>
<td>
<span class="flag flag-16 flag-<?= gd_isset($gGlobal['mallList'][$data['mallSno']]['domainFl'], 'kr'); ?>"></span><?= gd_isset($gGlobal['mallList'][$data['mallSno']]['mallName'], '기준몰'); ?>
</td>
<?php } ?>
</tr>
<tr>
<th>등급</th>
<td>
<?= gd_select_box_by_group_list($data['groupSno'], '등급선택'); ?>
<span id="groupSnoMsg" class="input_error_msg"></span>
<?php if ($mode != 'register') { ?>
<div>수정일:
<span class="font-num"><?= $data['groupModDt']; ?></span>
</div>
<?php } ?>
</td>
<th>승인</th>
<td>
<div class="radio">
<label class="radio-inline">
<input type="radio" name="appFl" value="y" <?= $checked['appFl']['y']; ?>/>
승인
</label>
<label class="radio-inline">
<input type="radio" name="appFl" value="n" <?= $checked['appFl']['n']; ?>/>
미승인
</label>
</div>
</td>
</tr>
<tr>
<th class="require">아이디</th>
<td>
<?php if ($mode == 'register') { ?>
<span title="이용하실 아이디를 입력해주세요!">
<input type="text" name="memId" id="memId" value="" class="form-control error" data-pattern="gdMemberId"/>
<button type="button" id="overlap_memId" class="btn btn-gray btn-sm">중복확인</button>
</span>
<?php } else { ?>
<input type="hidden" name="memId" id="memId" class="" value="<?= $data['memId'] ?>"/>
<strong><?= $data['memId'] ?></strong>
<?= gd_get_third_party_icon_web_path($data['snsTypeFl']); ?>
<?php if($data['simpleJoinFl'] == 'order') { ?><span class="text-blue">(주문 간단 가입)</span><?php } ?>
<?php if($data['simpleJoinFl'] == 'push') { ?><span class="text-blue">(가입 유도 푸시)</span><?php } ?>
<?php } ?>
</td>
<th>닉네임</th>
<td>
<span title="별명을 입력해주세요!">
<input type="text" name="nickNm" id="nickNm" value="<?= $data['nickNm'] ?>"
class="form-control width-sm error">
</span>
<button type="button" id="overlap_nickNm" class="btn btn-gray btn-sm">중복확인</button>
</td>
</tr>
<tr>
<th class="require">이름</th>
<td colspan="<?= $hidePasswordBlock ? 3 : 1 ?>">
<span title="이름을 입력해주세요!">
<input type="text" name="memNm" id="memNm" value="<?= $data['memNm'] ?>"
class="form-control width-sm" data-pattern="<?= (($data['mallSno'] > $gGlobal['defaultMallSno'])) ? 'gdEngKor' : 'gdMemberNmGlobal' ?>" maxlength="20">
</span>
<?php if ($joinField['pronounceName']['use'] == 'y') { ?>
<input type="text" name="pronounceName" id="pronounceName" value="<?= $data['pronounceName'] ?>"
class="form-control width-sm" maxlength="20" placeholder="발음 표시" data-pattern="<?= (($data['mallSno'] > 1)) ? 'gdEngKor' : 'gdMemberNmGlobal' ?>">
<?php } ?>
</td>
<?php if (!$hidePasswordBlock) : ?>
<th class="require">비밀번호</th>
<td>
<span title="비밀번호를 입력해주세요!">
<input type="password" name="memPw" value="" class="form-control width-sm js-maxlength"
placeholder="비밀번호입력" maxlength="16">
&nbsp;&nbsp;
<input type="password" name="memPwRe" value="" class="form-control width-sm js-maxlength"
placeholder="비밀번호확인" maxlength="16" style="margin-left: 40px">
</span>
<?php
if ($data['mallSno'] == $gGlobal['defaultMallSno'] || $mode == 'register') {
echo '<p class="notice-danger notice-info">*영문대문자/영문소문자/숫자/특수문자 중 2개 이상 포함, 10~16자리 이하로 설정할 수 있습니다.</p>';
} else {
if ($joinField['passwordCombineFl'] == 'engNum') {
echo '<p class="notice-danger notice-info">*영문대문자/영문소문자/숫자 중 2개 이상 포함,';
} elseif ($joinField['passwordCombineFl'] == 'engNumEtc') {
echo '<p class="notice-danger notice-info">*영문대문자/영문소문자/숫자/특수문자 중 3개 이상 포함,';
} else {
echo '<p class="notice-danger notice-info">*영문대문자/영문소문자 or 숫자,';
}
echo ' ' . $joinField['memPw']['minlen'] . '~' . $joinField['memPw']['maxlen'] . '자리 이하로 설정할 수 있습니다.</p>';
} ?>
</td>
<?php endif; ?>
</tr>
<?php if ($mode != 'register') { ?>
<tr>
<th>주문금액</th>
<td colspan="3"><?= gd_currency_display($data['saleAmt']); ?></td>
</tr>
<tr>
<th>마일리지</th>
<td><?= gd_money_format($data['mileage']) . gd_display_mileage_unit() ?></td>
<th>예치금</th>
<td><?= gd_money_format($data['deposit']) . gd_display_deposit('unit') ?></td>
</tr>
<tr>
<th>회원가입일</th>
<td><?= $data['entryDt'] ?></td>
<th>가입경로</th>
<td><?= $data['entryPath'] ?></td>
</tr>
<tr>
<th>최종로그인일</th>
<td><?= $data['lastLoginDt'] ?></td>
<th>방문횟수</th>
<td><?= $data['loginCnt'] ?></td>
</tr>
<tr>
<th>휴면해제일</th>
<td colspan="3"><?= $data['sleepWakeDt']; ?></td>
</tr>
<?php } ?>
<tr>
<th>이메일</th>
<td>
<div class="form-inline mgb5">
<input type="hidden" name="chkEmail" value=""/>
<span title="이메일을 입력해주세요!">
<input type="text" id="emailAddress" name="email[]" value="<?= $data['email'][0] ?>"
class="form-control width-sm">
@
</span>
<span title="이메일을 입력해주세요!">
<input type="text" id="emailDomain" name="email[]" value="<?= $data['email'][1] ?>"
class="form-control width-sm error">
</span>
<?= gd_select_box_by_mail_domain('email_site', 'email_site', null, $data['email'][1], '직접입력'); ?>
<button type="button" id="overlap_email" class="btn btn-gray btn-sm">중복확인</button>
</div>
<div class="form-inline">
<label class="checkbox-inline">
<input type="checkbox" name="maillingFl" value="y" data-default="" <?= $checked['maillingFl']['y']; ?>/>
정보/이벤트 MAIL 수신에 동의합니다.
</label>
</div>
<p class="notice-danger notice-info">*수신동의설정 안내메일의 자동발송여부에 따라 회원정보의 수신동의설정 변경 시 해당 회원에게 안내메일이 자동 발송됩니다.</p>
</td>
<th>이메일<br/>수신동의/거부일</th>
<td>
<?php
$mailLastReceiveAgreementDt = gd_isset($data['lastReceiveAgreementDt']['mail'], $data['entryDt']);
$mailAgreementView = '동의 : ' . $mailLastReceiveAgreementDt;
if (gd_isset($data['lastNotificationDt']['mail'], '-') != '-') {
$mailAgreementView .= '<br/>(수신동의여부 확인메일 발송<br/> : ' . $data['lastNotificationDt']['mail'] . ')';
}
?>
<?= $data['maillingFl'] == 'y' ? $mailAgreementView : '거부 : ' . gd_date_format('Y-m-d', $mailLastReceiveAgreementDt); ?>
</td>
</tr>
<tr>
<th>휴대폰번호</th>
<td>
<div class="form-inline mgb5">
<?php
$cellPhoneCallPrefixAttributes = [
'id' => 'cellPhoneCountryCode',
'name' => 'cellPhoneCountryCode',
];
if ($mode != 'register' && $data['mallSno'] != $gGlobal['defaultMallSno']) {
echo gd_select_box_by_call_prefix($cellPhoneCallPrefixAttributes, $data['cellPhoneCountryCode']);
}
?>
<span title="휴대폰번호를 입력해주세요!">
<input type="text" name="cellPhone" value="<?= $data['cellPhone'] ?>" maxlength="12" class="form-control js-number-only width-md"/>
</span>
</div>
<?php if ($data['mallSno'] == $gGlobal['defaultMallSno'] || $mode == 'register') { ?>
<div class="form-inline">
<span id="cellPhoneMsg" class="input_error_msg"></span>
<label class="checkbox-inline">
<input type="checkbox" name="smsFl" value="y" <?= $checked['smsFl']['y']; ?>>
정보/이벤트 SMS 수신에 동의합니다.
</label>
</div>
<p class="notice-danger notice-info">*수신동의설정 안내메일의 자동발송여부에 따라 회원정보의 수신동의설정 변경 시 해당 회원에게 SMS가 자동 발송됩니다.</p>
<?php } ?>
</td>
<th>SMS<br/>수신동의/거부일</th>
<td>
<?php
$smsLastReceiveAgreementDt = gd_isset($data['lastReceiveAgreementDt']['sms'], $data['entryDt']);
$smsAgreementView = '동의 : ' . $smsLastReceiveAgreementDt;
if (gd_isset($data['lastNotificationDt']['sms'], '-') != '-') {
$smsAgreementView .= '<br/>(수신동의여부 확인SMS 발송<br/> : ' . $data['lastNotificationDt']['sms'] . ')';
}
?>
<?= $data['smsFl'] == 'y' ? $smsAgreementView : '거부 : ' . gd_date_format('Y-m-d', $smsLastReceiveAgreementDt); ?>
</td>
</tr>
<tr>
<th>주소</th>
<td colspan="3">
<?php if ($data['mallSno'] == $gGlobal['defaultMallSno'] || $mode == 'register') { ?>
<div class="form-inline mgb5">
<span title="우편번호를 입력해주세요!">
<input type="text" size="6" maxlength="5" name="zonecode" class="form-control js-number"
data-number="5"
value="<?= $data['zonecode'] ?>"/>
<input type="hidden" name="zipcode" value="<?= $data['zipcode'] ?>"/>
<span id="zipcodeText" class="number <?php if (strlen($data['zipcode']) != 7) {
echo 'display-none';
} ?>">(<?php echo $data['zipcode']; ?>)
</span>
</span>
<input type="button"
onclick="postcode_search('zonecode', 'address', 'zipcode');"
value="우편번호찾기" class="btn btn-gray btn-sm"/>
</div>
<div class="form-inline">
<span title="주소를 입력해주세요!">
<input type="text" name="address" id="address"
class="form-control width-2xl"
value="<?= $data['address']; ?>"/>
</span>
<span title="상세주소를 입력해주세요!">
<input type="text" name="addressSub" id="addressSub"
class="form-control width-2xl"
value="<?= $data['addressSub']; ?>"/>
</span>
</div>
<?php } ?>
</td>
</tr>
<tr>
<th>전화번호</th>
<td colspan="3">
<div class="form-inline">
<?php
$phoneCallPrefixAttributes = [
'id' => 'phoneCountryCode',
'name' => 'phoneCountryCode',
];
if ($mode != 'register' && $data['mallSno'] != $gGlobal['defaultMallSno']) {
gd_select_box_by_call_prefix($phoneCallPrefixAttributes, $data['phoneCountryCode']);
}
?>
<span title="전화번호를 입력해주세요!">
<input type="text" name="phone" value="<?= $data['phone'] ?>" maxlength="12" class="form-control js-number-only width-md"/>
</span>
</div>
</td>
</tr>
</table>
</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후_member_view.php
<section class="ncua-block-container">
<header class="ncua-block-header ncua-block-header--is-required">
<div class="ncua-block-header__row">
<div class="ncua-block-header__title-area">
<span class="ncua-block-header__title">기본정보</span>
</div>
<div class="ncua-block-header__action-area">
<span class="ncua-block-header__required-notice"><span class="ncua-block-header__required-notice--red">* 는 필수 입력</span><span class="ncua-block-header__required-notice--gray"> 항목입니다.</span></span>
</div>
</div>
</header>
<div class="ncua-block-container__body">
<div class="ncua-table ncua-table--vertical">
<table>
<colgroup>
<col style="width: 240px;"/>
<col />
</colgroup>
<!-- 회원구분 -->
<tr>
<th><div data-tooltip-seq="001">회원구분</div></th>
<td><div class="ncua-flex-gap">
<label class="ncua-radio-field ncua-radio-field--xs has-text">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="memberFl" value="personal"
data-target=".div-business" <?= $checked['memberFl']['personal']; ?>/>
</span>
<span class="ncua-radio-field__text">개인회원</span>
</label>
<?php if ($data['mallSno'] == $gGlobal['defaultMallSno'] || $mode == 'register') { ?>
<label class="ncua-radio-field ncua-radio-field--xs has-text">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="memberFl" value="business"
data-target=".div-business" <?= $checked['memberFl']['business']; ?>/>
</span>
<span class="ncua-radio-field__text">사업자회원</span>
</label>
<?php } ?>
</div></td>
</tr>
<!-- 상점구분 -->
<?php if ($data['mallSno'] > 0) { ?>
<tr>
<th><div>상점구분</div></th>
<td><div class="ncua-gap-8">
<span class="flag flag-16 flag-<?= gd_isset($gGlobal['mallList'][$data['mallSno']]['domainFl'], 'kr'); ?>"></span><?= gd_isset($gGlobal['mallList'][$data['mallSno']]['mallName'], '기준몰'); ?>
</div></td>
</tr>
<?php } ?>
<!-- 등급 -->
<tr>
<th><div data-tooltip-seq="002">등급</div></th>
<td><div class="ncua-gap-8">
<span class="ncua-select ncua-select--xs">
<span class="ncua-select__content">
<?= gd_select_box('groupSno', 'groupSno', \Component\Member\Group\Util::getGroupName(), null, $data['groupSno'], '등급선택', null, 'ncua-select__tag'); ?>
</span>
</span>
<span id="groupSnoMsg" class="input_error_msg"></span>
<?php if ($mode != 'register') { ?>
<div>수정일:
<span class="font-num"><?= $data['groupModDt']; ?></span>
</div>
<?php } ?>
</div></td>
</tr>
<!-- 승인 -->
<tr>
<th><div data-tooltip-seq="003">승인</div></th>
<td>
<div class="ncua-flex-gap">
<label class="ncua-radio-field ncua-radio-field--xs has-text">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="appFl" value="y" <?= $checked['appFl']['y']; ?>/>
</span>
<span class="ncua-radio-field__text">승인</span>
</label>
<label class="ncua-radio-field ncua-radio-field--xs has-text">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="appFl" value="n" <?= $checked['appFl']['n']; ?>/>
</span>
<span class="ncua-radio-field__text">미승인</span>
</label>
</div>
</td>
</tr>
<!-- 아이디 -->
<tr>
<th class="ncua-required"><div data-tooltip-seq="004">아이디</div></th>
<td><div class="ncua-gap-4">
<?php if ($mode == 'register') { ?>
<div class="ncua-input ncua-input--xs ncua-input-width-320">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="memId" id="memId" value="" class="error" data-pattern="gdMemberId" placeholder="영문, 숫자, 특수문자(-),(_),(.),(@), 4~50자"/>
</div>
</div>
</div>
<button type="button" id="overlap_memId" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray">중복확인</button>
<?php } else { ?>
<input type="hidden" name="memId" id="memId" class="" value="<?= $data['memId'] ?>"/>
<strong><?= $data['memId'] ?></strong>
<?= gd_get_third_party_icon_web_path($data['snsTypeFl']); ?>
<?php if($data['simpleJoinFl'] == 'order') { ?><span class="text-blue">(주문 간단 가입)</span><?php } ?>
<?php if($data['simpleJoinFl'] == 'push') { ?><span class="text-blue">(가입 유도 푸시)</span><?php } ?>
<?php } ?>
</div></td>
</tr>
<!-- 닉네임 -->
<tr>
<th><div data-tooltip-seq="005">닉네임</div></th>
<td><div class="ncua-gap-4">
<div class="ncua-input ncua-input--xs ncua-input-width-320">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="nickNm" id="nickNm" value="<?= $data['nickNm'] ?>" class="error" placeholder="닉네임을 입력하세요">
</div>
</div>
</div>
<button type="button" id="overlap_nickNm" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray">중복확인</button>
</div></td>
</tr>
<!-- 이름 -->
<tr>
<th class="ncua-required"><div data-tooltip-seq="006">이름</div></th>
<td><div>
<div class="ncua-input ncua-input--xs ncua-input-width-320">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="memNm" id="memNm" value="<?= $data['memNm'] ?>" data-pattern="<?= (($data['mallSno'] > $gGlobal['defaultMallSno'])) ? 'gdEngKor' : 'gdMemberNmGlobal' ?>" maxlength="20" title="이름을 입력해주세요!" placeholder="이름을 입력하세요.">
</div>
</div>
</div>
<?php if ($joinField['pronounceName']['use'] == 'y') { ?>
<div class="ncua-input ncua-input--xs ncua-input-width-320">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="pronounceName" id="pronounceName" value="<?= $data['pronounceName'] ?>" maxlength="20" placeholder="발음 표시" data-pattern="<?= (($data['mallSno'] > 1)) ? 'gdEngKor' : 'gdMemberNmGlobal' ?>">
</div>
</div>
</div>
<?php } ?>
</div></td>
</tr>
<!-- 비밀번호 -->
<?php if (!$hidePasswordBlock) : ?>
<tr>
<th class="ncua-required"><div data-tooltip-seq="007">비밀번호</div></th>
<td>
<div class="ncua-flex-column ncua-align-items-start ncua-gap-8">
<div class="ncua-input ncua-input--xs">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content ncua-input-width-380">
<div class="ncua-input__field ncua-input__field--xs">
<input type="password" name="memPw" maxlength="<?php echo $joinField['memPw']['maxlen']; ?>" value="" class="js-member-pw" placeholder="<?= $passwordPlaceholder ?? '' ?>" data-charcount-key="memberPwText">
</div> </div>
<div class="ncua-input__field-text-count" data-charcount-text="memberPwText">
<output class="ncua-input__field-text-count-current">0</output>
<span>/<?php echo $joinField['memPw']['maxlen']; ?></span>
</div>
</div>
</div>
</div>
</td>
</tr>
<!-- 비밀번호 확인 -->
<tr>
<th class="ncua-required"><div data-tooltip-seq="008">비밀번호 확인</div></th>
<td>
<div class="ncua-flex-column ncua-align-items-start ncua-gap-8">
<div class="ncua-input ncua-input--xs">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content ncua-input-width-380">
<div class="ncua-input__field ncua-input__field--xs">
<input type="password" name="memPwRe" value="" class="js-re-member-pw" maxlength="<?php echo $joinField['memPw']['maxlen']; ?>" placeholder="<?= $passwordPlaceholder ?? '' ?>" data-charcount-key="reMemberPwText">
</div>
</div>
<div class="ncua-input__field-text-count" data-charcount-text="reMemberPwText">
<output class="ncua-input__field-text-count-current">0</output>
<span>/<?php echo $joinField['memPw']['maxlen']; ?></span>
</div>
</div>
</div>
</div>
</td>
</tr>
<?php endif; ?>
<!-- 주문금액, 마일리지, 예치금, 회원가입일 -->
<?php if ($mode != 'register') { ?>
<tr>
<th><div data-tooltip-seq="017">주문금액</div></th>
<td><div><?= gd_currency_display($data['saleAmt']); ?></div></td>
</tr>
<tr>
<th><div data-tooltip-seq="018">마일리지</div></th>
<td><div><?= gd_money_format($data['mileage']) . gd_display_mileage_unit() ?></div></td>
</tr>
<tr>
<th><div data-tooltip-seq="019">예치금</div></th>
<td><div><?= gd_money_format($data['deposit']) . gd_display_deposit('unit') ?></div></td>
</tr>
<tr>
<th><div data-tooltip-seq="020">회원가입일</div></th>
<td><div><?= $data['entryDt'] ?></div></td>
</tr>
<tr>
<th><div data-tooltip-seq="021">가입경로</div></th>
<td><div><?= $data['entryPath'] ?></div></td>
</tr>
<tr>
<th><div data-tooltip-seq="022">최종로그인일</div></th>
<td><div><?= $data['lastLoginDt'] ?></div></td>
</tr>
<tr>
<th><div data-tooltip-seq="023">방문횟수</div></th>
<td><div><?= $data['loginCnt'] ?></div></td>
</tr>
<tr>
<th><div data-tooltip-seq="024">휴면해제일</div></th>
<td><div><?= $data['sleepWakeDt']; ?></div></td>
</tr>
<?php } ?>
<!-- 마케팅 목적 개인정보 수집 및 이용 동의 -->
<?php if ($privateMarketingMemberUse): ?>
<tr>
<th><div data-tooltip-seq="009">마케팅 목적 개인정보 수집 및 이용 동의</div></th>
<td><div>
<label class="ncua-checkbox-field ncua-checkbox-field--xs has-text">
<span class="ncua-checkbox-input ncua-checkbox-input--xs ncua-checkbox-field__input">
<input type="checkbox" name="privateMarketingConsentOptionFl" value="y" <?= ($mode === 'register') ? 'disabled' : '' ?> <?= $checked['privateMarketingConsentOptionFl']['y']; ?>/>
</span>
<span class="ncua-checkbox-field__text">마케팅 목적 개인정보 수집 및 이용 동의</span>
</label>
</div></td>
</tr>
<tr>
<th><div data-tooltip-seq="010">마케팅 목적 개인정보 수집 및 이용 동의/거부일</div></th>
<td><div>
<?= ($data['privateMarketingConsentOptionFl'] === 'y' ? '동의 : ' : '거부 : ') . ($data['privateMktAgreementDt'] ?? '-') ?>
</div></td>
</tr>
<?php endif; ?>
<!-- 이메일 -->
<tr>
<th><div data-tooltip-seq="011">이메일</div></th>
<td>
<div class="ncua-flex-column ncua-align-items-start ncua-gap-4">
<div class="ncua-flex ncua-gap-4">
<div class="ncua-flex ncua-align-items-center ncua-gap-8">
<input type="hidden" name="chkEmail" value=""/>
<div class="ncua-input ncua-input--xs ncua-input-width-150">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" id="emailAddress" name="email[]" value="<?= $data['email'][0] ?>" title="이메일을 입력해주세요!">
</div>
</div>
</div>
@
<div class="ncua-input ncua-input--xs ncua-input-width-150">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" id="emailDomain" name="email[]" value="<?= $data['email'][1] ?>" class="error" title="이메일을 입력해주세요!">
</div>
</div>
</div>
</div>
<span class="ncua-select ncua-select--xs">
<span class="ncua-select__content">
<?= gd_select_box_by_mail_domain('email_site', 'email_site', null, $data['email'][1], '직접입력', null, 'ncua-select__tag'); ?>
</span>
</span>
<button type="button" id="overlap_email" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray">중복확인</button>
</div>
<div>
<label class="ncua-checkbox-field ncua-checkbox-field--xs has-text">
<span class="ncua-checkbox-input ncua-checkbox-input--xs ncua-checkbox-field__input">
<input type="checkbox" name="maillingFl" <?= ($mode === 'register') ? 'disabled' : '' ?> value="y" data-default="" <?= $checked['maillingFl']['y']; ?>/>
</span>
<span class="ncua-checkbox-field__text">마케팅 이메일 수신 동의</span>
</label>
</div>
<p class="ncua-caution-text">수신동의설정 안내메일의 자동발송여부에 따라 회원정보의 수신동의설정 변경 시 해당 회원에게 안내메일이 자동 발송됩니다.</p>
</div>
</td>
</tr>
<!-- 이메일 수신동의/거부일 -->
<tr>
<th><div data-tooltip-seq="012">이메일 수신동의/거부일</div></th>
<td><div class="ncua-flex-column ncua-align-items-start">
<?php
$mailLastReceiveAgreementDt = gd_isset($data['lastReceiveAgreementDt']['mail'], $data['entryDt']);
$mailAgreementView = '동의 : ' . $mailLastReceiveAgreementDt;
if (gd_isset($data['lastNotificationDt']['mail'], '-') != '-') {
$mailAgreementView .= '<br/>(수신동의여부 확인메일 발송<br/> : ' . $data['lastNotificationDt']['mail'] . ')';
}
?>
<?= $data['maillingFl'] == 'y' ? $mailAgreementView : '거부 : ' . $mailLastReceiveAgreementDt; ?>
</div></td>
</tr>
<!-- 휴대폰번호 -->
<tr>
<th><div data-tooltip-seq="013">휴대폰번호</div></th>
<td>
<div class="ncua-flex-column ncua-align-items-start ncua-gap-4">
<div class="ncua-flex ncua-gap-4">
<?php if ($mode != 'register' && $data['mallSno'] != $gGlobal['defaultMallSno']): ?>
<span class="ncua-select ncua-select--xs">
<span class="ncua-select__content">
<select class="ncua-select__tag" id="cellPhoneCountryCode" name="cellPhoneCountryCode">
<option value="">국가선택</option>
<?php foreach ($callPrefixList as $code => $label): ?>
<option value="<?= gd_htmlspecialchars($code) ?>"<?= $data['cellPhoneCountryCode'] === $code ? ' selected' : '' ?>><?= $label ?></option>
<?php endforeach; ?>
</select>
</span>
</span>
<?php endif; ?>
<div class="ncua-input ncua-input--xs ncua-input-width-150">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="cellPhone" value="<?= $data['cellPhone'] ?>" maxlength="12" class="js-number-only" title="휴대폰번호를 입력해주세요!" placeholder="'-' 없이 입력하세요."/>
</div>
</div>
</div>
</div>
<?php if ($data['mallSno'] == $gGlobal['defaultMallSno'] || $mode == 'register') { ?>
<div class="form-inline">
<span id="cellPhoneMsg" class="input_error_msg"></span>
<label class="ncua-checkbox-field ncua-checkbox-field--xs has-text">
<span class="ncua-checkbox-input ncua-checkbox-input--xs ncua-checkbox-field__input">
<input type="checkbox" <?= ($mode === 'register') ? 'disabled' : '' ?> name="smsFl" value="y" <?= $checked['smsFl']['y']; ?>>
</span>
<span class="ncua-checkbox-field__text">마케팅 모바일 메시지 수신 동의</span>
</label>
</div>
<p class="ncua-caution-text">수신동의설정 안내메일의 자동발송여부에 따라 회원정보의 수신동의설정 변경 시 해당 회원에게 SMS가 자동 발송됩니다.</p>
<?php } ?>
</div>
</td>
</tr>
<!-- 휴대폰번호 수신/거부 -->
<tr>
<th><div data-tooltip-seq="014">휴대폰번호 수신동의/거부일</div></th>
<td><div>
<?php
$smsLastReceiveAgreementDt = gd_isset($data['lastReceiveAgreementDt']['sms'], $data['entryDt']);
$smsAgreementView = '동의 : ' . $smsLastReceiveAgreementDt;
if (gd_isset($data['lastNotificationDt']['sms'], '-') != '-') {
$smsAgreementView .= '<br/>(수신동의여부 확인SMS 발송<br/> : ' . $data['lastNotificationDt']['sms'] . ')';
}
?>
<?= $data['smsFl'] == 'y' ? $smsAgreementView : '거부 : ' . $smsLastReceiveAgreementDt; ?>
</div></td>
</tr>
<!-- 주소 -->
<tr>
<th><div data-tooltip-seq="015">주소</div></th>
<td>
<div class="ncua-flex-column ncua-align-items-start">
<?php if ($data['mallSno'] == $gGlobal['defaultMallSno'] || $mode == 'register') { ?>
<!-- addr 1 -->
<div class="ncua-member-addr">
<div class="ncua-input ncua-input--xs is-disabled ncua-member-addr1">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input readonly type="text" size="6" maxlength="5" name="zonecode" class="js-number" data-number="5" value="<?= $data['zonecode'] ?>" title="우편번호를 입력해주세요!"/>
</div>
</div>
</div>
<input type="hidden" name="zipcode" value="<?= $data['zipcode'] ?>"/>
<span id="zipcodeText" class="number <?php if (strlen($data['zipcode']) != 7) {
echo 'display-none';
} ?>">(<?php echo $data['zipcode']; ?>)
</span>
<input type="button"
onclick="postcode_search('zonecode', 'address', 'zipcode');"
value="주소찾기" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray"/>
<!-- addr 2 -->
<div class="ncua-input ncua-input--xs is-disabled ncua-member-addr2">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input readonly type="text" name="address" id="address" value="<?= $data['address']; ?>" title="주소를 입력해주세요!"/>
</div>
</div>
</div>
<div class="ncua-input ncua-input--xs ncua-member-addr3">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="addressSub" id="addressSub" value="<?= $data['addressSub']; ?>" placeholder="상세주소를 입력하세요."/>
</div>
</div>
</div>
</div>
<!-- <div class="ncua-flex ncua-flex-column ncua-gap-4">
</div> -->
<?php } ?>
</div>
</td>
</tr>
<!-- 전화번호 -->
<tr>
<th><div data-tooltip-seq="016">전화번호</div></th>
<td><div>
<div class="form-inline">
<?php
$phoneCallPrefixAttributes = [
'id' => 'phoneCountryCode',
'name' => 'phoneCountryCode',
];
if ($mode != 'register' && $data['mallSno'] != $gGlobal['defaultMallSno']) {
gd_select_box_by_call_prefix($phoneCallPrefixAttributes, $data['phoneCountryCode']);
}
?>
<div class="ncua-input ncua-input--xs ncua-input-width-150">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="phone" value="<?= $data['phone'] ?>" maxlength="12" class="js-number-only" placeholder="'-' 없이 입력하세요."/>
</div>
</div>
</div>
</div>
</div></td>
</tr>
</table>
</div>
</div>
</section>
 
Admin/policy/base_agreement_with_private.php+98
변경 전base_agreement_with_private.php
<?= $privateApprovalTableOption ?>
<?= $privateConsignTable ?>
<?= $privateOfferTable ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<div class="table-title gd-help-manual mgt10">
비회원 대상 동의항목 설정
</div>
$(document).ready(function () {
var $formSetup = $('#formSetup');
 
 
 
 
 
 
 
 
 
 
$.validator.addMethod("year", function (value) {
var valid = moment(value, 'YYYY', true).isValid();
var checked = $('#agreementModeFlY').prop('checked');
 
변경 후base_agreement_with_private.php
<?= $privateApprovalTableOption ?>
<?= $privateConsignTable ?>
<?= $privateOfferTable ?>
<?php if (!empty($privateMarketingMember)): ?>
<table class="table table-cols">
<caption>
[선택] 마케팅 목적 개인정보 수집 및 이용 동의
</caption>
<colgroup>
<col class="width-md"/>
<col/>
</colgroup>
<tbody>
<tr>
<th>사용여부</th>
<td>
<label class="radio-inline" for="privateMarketingMemberFlY">
<input id="privateMarketingMemberFlY" type="radio" name="privateMarketingMemberFl" value="y" <?= $privateMarketingMember['modeFl'] == 'y' ? 'checked=checked' : ''; ?> />
사용
</label>
<label class="radio-inline" for="privateMarketingMemberFlN">
<input id="privateMarketingMemberFlN" type="radio" name="privateMarketingMemberFl" value="n" <?= $privateMarketingMember['modeFl'] != 'y' ? 'checked=checked' : ''; ?> />
사용하지 않음
</label>
<p class="notice notice-info js-marketing-use-notice">광고성 메시지를 보내려면 아래에 있는 [선택] 광고성 수신 동의 약관도 반드시 함께 사용해야 합니다.</p>
</td>
</tr>
<tr>
<th>안내 팝업 노출 여부</th>
<td>
<label class="radio-inline" for="privateMarketingMemberNoticePopupFlY">
<input id="privateMarketingMemberNoticePopupFlY" type="radio" name="privateMarketingMemberNoticePopupFl" value="y" <?= $privateMarketingMember['noticePopupFl'] == 'y' ? 'checked=checked' : ''; ?> />
사용
</label>
<label class="radio-inline" for="privateMarketingMemberNoticePopupFlN">
<input id="privateMarketingMemberNoticePopupFlN" type="radio" name="privateMarketingMemberNoticePopupFl" value="n" <?= $privateMarketingMember['noticePopupFl'] != 'y' ? 'checked=checked' : ''; ?> />
사용하지 않음
</label>
<p class="notice notice-info">안내 팝업 노출 여부를 ‘사용’으로 설정 시, 마케팅 목적의 개인정보 수집 및 이용 동의 이력이 없는 회원이 로그인 시 최초 1회에 한하여 팝업 안내가 출력됩니다.</p>
</td>
</tr>
<tr>
<th>내용입력</th>
<td>
<input type="text" class="form-control" id="privateMarketingMemberWriteTitle" name="privateMarketingMemberWriteTitle" value="<?= gd_htmlspecialchars($privateMarketingMember['informNm']) ?>">
<textarea id="privateMarketingMemberWriteWrite" name="privateMarketingMemberWriteWrite" rows="12" class="form-control mgt5"><?= gd_htmlspecialchars($privateMarketingMember['content']) ?></textarea>
</td>
</tr>
</tbody>
</table>
<?php endif; ?>
<?php if (!empty($advertisingReceive)): ?>
<table class="table table-cols">
<caption>
[선택] 광고성 수신 동의
</caption>
<colgroup>
<col class="width-md"/>
<col/>
</colgroup>
<tbody>
<tr>
<th>사용여부</th>
<td>
<label class="checkbox-inline" for="advertisingReceiveMailFl">
<input id="advertisingReceiveMailFl" type="checkbox" name="advertisingReceiveMailFl" value="y" <?= $advertisingReceiveMailUse == 'y' ? 'checked="checked"' : '' ?> <?= $joinItemEmailUse == 'y' ? '' : 'disabled="disabled"' ?> />
이메일 수신 동의
</label>
<label class="checkbox-inline" for="advertisingReceiveSmsFl">
<input id="advertisingReceiveSmsFl" type="checkbox" name="advertisingReceiveSmsFl" value="y" <?= $advertisingReceiveSmsUse == 'y' ? 'checked="checked"' : '' ?> <?= $joinItemCellPhoneUse == 'y' ? '' : 'disabled="disabled"' ?> />
모바일 메시지 수신 동의
</label>
<?php if ($joinItemEmailUse != 'y' || $joinItemCellPhoneUse != 'y'): ?>
<p class="notice notice-info">광고성 수신 동의는 회원가입 항목에서 이메일/휴대폰번호를 사용 중일 때만 설정 가능합니다.</p>
<?php endif; ?>
<p class="mgt5">
이메일 : <?php if ($joinItemEmailUse == 'y'): ?>사용
<?php else: ?>사용 안 함 <a href="../member/member_joinitem.php" class="btn-link-underline" target="_blank">설정 바로가기></a><?php endif; ?><br/>
휴대폰번호 : <?php if ($joinItemCellPhoneUse == 'y'): ?>사용<?php else: ?>사용 안 함 <a href="../member/member_joinitem.php" class="btn-link-underline" target="_blank">설정 바로가기></a><?php endif; ?>
</p>
</td>
</tr>
<tr>
<th>내용입력</th>
<td>
<input type="text" class="form-control" id="advertisingReceiveWriteTitle" name="advertisingReceiveWriteTitle" value="<?= gd_htmlspecialchars($advertisingReceive['informNm']) ?>">
<textarea id="advertisingReceiveWriteWrite" name="advertisingReceiveWriteWrite" rows="12" class="form-control mgt5"><?= gd_htmlspecialchars($advertisingReceive['content']) ?></textarea>
</td>
</tr>
</tbody>
</table>
<?php endif; ?>
<div class="table-title gd-help-manual mgt10">
비회원 대상 동의항목 설정
</div>
$(document).ready(function () {
var $formSetup = $('#formSetup');
 
// 안내 팝업 노출 여부 비활성화 + 광고성 수신 동의 관련 안내 문구 숨김
const toggleMarketingNoticePopup = function () {
const isUse = $('input[name=privateMarketingMemberFl]:checked').val() === 'y';
$('input[name=privateMarketingMemberNoticePopupFl]').prop('disabled', !isUse);
$('.js-marketing-use-notice').toggle(isUse);
};
$formSetup.on('change', 'input[name=privateMarketingMemberFl]', toggleMarketingNoticePopup);
toggleMarketingNoticePopup();
 
$.validator.addMethod("year", function (value) {
var valid = moment(value, 'YYYY', true).isValid();
var checked = $('#agreementModeFlY').prop('checked');
 
Admin/share/member_crm_detail.php+15-1
변경 전member_crm_detail.php
$checkBoxRow = 6;
}
?>
<form id="frm" action="../member/member_ps.php" method="post">
 
<input type="hidden" name="mode" id="mode" value="<?= $mode ?>"/>
<input type="hidden" name="memNo" id="memNo" value="<?= $data['memNo'] ?>"/>
<?php include gd_admin_skin_path("member/_member_view.php"); ?>
<?php include "/../member/_member_view_history.php"; ?>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/member.js"></script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/member2.js"></script>
 
<script type="text/javascript">
$('#emailDomain').removeClass('error');
$('#nickNm').removeClass('error');
var certificationMaxFileSize = <?= $certificationMaxFileSize ?>;
var join_field = <?= json_encode($joinField) ?>;
</script>
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후member_crm_detail.php
$checkBoxRow = 6;
}
?>
<link rel="stylesheet" href="<?= PATH_ADMIN_GD_SHARE ?>ncds/css/member/member-view.css">
<form id="frm" class="ncua-content" action="../member/member_ps.php" method="post">
<input type="hidden" name="mode" id="mode" value="<?= $mode ?>"/>
<input type="hidden" name="memNo" id="memNo" value="<?= $data['memNo'] ?>"/>
<?php include gd_admin_skin_path("member/_member_view.php"); ?>
<?php include "/../member/_member_view_history.php"; ?>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/member.js"></script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/member2.js"></script>
<script defer src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/charcount.js')?>"></script>
<script type="text/javascript">
$('#emailDomain').removeClass('error');
$('#nickNm').removeClass('error');
var certificationMaxFileSize = <?= $certificationMaxFileSize ?>;
var join_field = <?= json_encode($joinField) ?>;
</script>
<script type="text/javascript">
$(document).ready(function() {
const memberPwManager = createCharCountManager({
targetClasses: ['js-member-pw'],
});
memberPwManager.init();
const reMemberPwManager = createCharCountManager({
targetClasses: ['js-re-member-pw'],
});
reMemberPwManager.init();
});
</script>
 
Component/Agreement/BuyerInformCode.php+37
변경 전BuyerInformCode.php
const PRIVATE_MEMBER_AUTO_APPROVAL_CONSENT = '991013';
/** 마케팅 활용을 위한 개인정보 수집 · 이용 동의 (비회원 주문 시) */
const PRIVATE_MARKETING = '001011';
 
 
 
 
 
 
 
/** 이용안내 */
const BASE_GUIDE = '006001';
/** 탈퇴안내 */
case self::PRIVATE_MARKETING:
$this->initPrivateMarketing();
break;
 
 
 
 
 
 
}
}
 
$this->_groupCd = '001';
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 멤버 변수를 배열로 반환하는 함수
*
 
변경 후BuyerInformCode.php
const PRIVATE_MEMBER_AUTO_APPROVAL_CONSENT = '991013';
/** 마케팅 활용을 위한 개인정보 수집 · 이용 동의 (비회원 주문 시) */
const PRIVATE_MARKETING = '001011';
 
/** 마케팅 목적 개인정보 수집 및 이용 동의 */
const PRIVATE_MARKETING_MEMBER = '001015';
 
/** 광고성 수신 동의 */
const ADVERTISING_RECEIVE = '001016';
 
/** 이용안내 */
const BASE_GUIDE = '006001';
/** 탈퇴안내 */
case self::PRIVATE_MARKETING:
$this->initPrivateMarketing();
break;
case self::PRIVATE_MARKETING_MEMBER:
$this->initPrivateMarketingMember();
break;
case self::ADVERTISING_RECEIVE:
$this->initAdvertisingReceive();
break;
}
}
 
$this->_groupCd = '001';
}
 
/**
* 멤버 변수에 마케팅 목적 개인정보 수집 및 이용 동의 관련 데이터베이스 조회 데이터 설정
*
* @return void
*/
public function initPrivateMarketingMember()
{
$this->_informCd = self::PRIVATE_MARKETING_MEMBER;
$this->_informNm = __('마케팅 목적 개인정보 수집 및 이용 동의');
$this->_groupCd = '001';
}
 
/**
* 멤버 변수에 광고성 수신 동의 관련 데이터베이스 조회 데이터 설정
*
* @return void
*/
public function initAdvertisingReceive(): void
{
$this->_informCd = self::ADVERTISING_RECEIVE;
$this->_informNm = __('광고성 수신 동의');
$this->_groupCd = '001';
}
 
/**
* 멤버 변수를 배열로 반환하는 함수
*
 
Component/Agreement/BuyerInform.php+104
변경 전BuyerInform.php
return $inform;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 사용자 정의 이용약관 데이터 반환 함수
*
if (empty($vo->getInformNm())) {
$excludeField .= ',informNm';
}
 
 
 
 
 
// 테이블명 반환
$tableName = $this->mall->getTableName(DB_BUYER_INFORM, $this->saveMallSno);
$this->db->set_insert_db($tableName, $arrBind['param'], $arrBind['bind'], 'y');
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 기본정책 약관, 개인정보관련 동의사항, 이용, 탈퇴 안내 저장 함수
*
$vo->setModeFl(gd_isset($postValue['privateMarketingFl'], 'n'));
}
 
 
 
 
 
 
 
 
 
 
 
 
 
$strSQL = "SELECT sno, displayShopFl FROM " . $tableName . " WHERE informCd='" . $code . "'";
if ($mallSno > DEFAULT_MALL_NUMBER) {
$strSQL .= " AND mallSno='" . $mallSno . "'";
 
변경 후BuyerInform.php
return $inform;
}
 
/**
* 광고성 수신 동의 정보 반환 - 단일 배열 (상점명 치환코드 {rc_mallNm} 적용)
*
* @param int $mallSno
*
* @return array
* @throws Exception
*/
public function getAdvertisingReceiveContentWithReplaceCode(int $mallSno = DEFAULT_MALL_NUMBER): array
{
/** @var \Bundle\Component\Design\ReplaceCode $replaceCode */
$replaceCode = App::load('\\Component\\Design\\ReplaceCode');
$replaceCode->initWithUnsetDiff(
[
'{rc_mallNm}',
]
);
$mallInfo = array_map(function ($value) {
return $value['val'];
}, $replaceCode->getDefinedCode());
 
$inform = $this->getInformData(BuyerInformCode::ADVERTISING_RECEIVE, $mallSno);
if (!empty($inform['content'])) {
$inform['content'] = str_replace(array_keys($mallInfo), array_values($mallInfo), $inform['content']);
}
 
return $inform;
}
 
/**
* 사용자 정의 이용약관 데이터 반환 함수
*
if (empty($vo->getInformNm())) {
$excludeField .= ',informNm';
}
// [선택] 마케팅 목적 개인정보 수집 및 이용 동의 / 광고성 수신 동의는 본문을 공백으로 저장하면 기존 내용 유지
if (in_array($vo->getInformCd(), [BuyerInformCode::PRIVATE_MARKETING_MEMBER, BuyerInformCode::ADVERTISING_RECEIVE], true) && empty($vo->getContent())) {
$excludeField .= ',content';
}
 
// 테이블명 반환
$tableName = $this->mall->getTableName(DB_BUYER_INFORM, $this->saveMallSno);
$this->db->set_insert_db($tableName, $arrBind['param'], $arrBind['bind'], 'y');
}
 
/**
* 광고성 수신 동의 채널(이메일/모바일) 저장값 확정.
* 가입항목(joinitem) 미사용 채널은 체크박스가 disabled 로 출력되어 POST 에 실리지 않으므로 이전 es_config 값을 보존한다.
* es_config(member.advertisingReceive) 저장과 es_buyerInform modeFl 파생이 동일 결과를 쓰도록 단일화한다.
*
* @param array $post 저장 요청 POST 배열 (advertisingReceiveMailFl / advertisingReceiveSmsFl 포함 가능)
* @param int $mallSno
*
* @return array ['email' => ['use' => 'y'|'n'], 'mobile' => ['use' => 'y'|'n']]
*/
public function resolveAdvertisingReceiveChannels(array $post, int $mallSno = DEFAULT_MALL_NUMBER): array
{
$joinItem = gd_policy('member.joinitem', $mallSno);
$prevAdvertisingReceive = gd_policy('member.advertisingReceive', $mallSno);
 
return [
'email' => ['use' => ($joinItem['email']['use'] ?? 'n') === 'y'
? gd_isset($post['advertisingReceiveMailFl'], 'n')
: ($prevAdvertisingReceive['email']['use'] ?? 'n')],
'mobile' => ['use' => ($joinItem['cellPhone']['use'] ?? 'n') === 'y'
? gd_isset($post['advertisingReceiveSmsFl'], 'n')
: ($prevAdvertisingReceive['mobile']['use'] ?? 'n')],
];
}
 
/**
* 광고성 수신 동의 채널(이메일/모바일)의 프론트 노출 여부.
* 노출 조건 = 가입항목(joinitem) 사용 AND 광고성 수신 동의(es_config) 사용. (가입항목 선검사 강제 → 호출부 누락 방지)
*
* @param int $mallSno
*
* @return array ['mail' => 'y'|'n', 'sms' => 'y'|'n']
*/
public function getAdvertisingReceiveChannelUse(int $mallSno = DEFAULT_MALL_NUMBER): array
{
$joinItem = gd_policy('member.joinitem', $mallSno);
$advertisingReceive = gd_policy('member.advertisingReceive', $mallSno);
 
return [
'mail' => (($joinItem['email']['use'] ?? 'n') === 'y' && ($advertisingReceive['email']['use'] ?? 'n') === 'y') ? 'y' : 'n',
'sms' => (($joinItem['cellPhone']['use'] ?? 'n') === 'y' && ($advertisingReceive['mobile']['use'] ?? 'n') === 'y') ? 'y' : 'n',
];
}
 
/**
* 광고성 수신 동의 사용 여부. 채널(이메일/모바일) 중 하나라도 노출 가능하면 사용(true), 둘 다 미사용이면 false.
* es_buyerInform modeFl 대신 이 값을 프론트 노출/사용 판정 기준으로 쓴다.
*
* @param int $mallSno
*
* @return bool
*/
public function isUseAdvertisingReceive(int $mallSno = DEFAULT_MALL_NUMBER): bool
{
$channelUse = $this->getAdvertisingReceiveChannelUse($mallSno);
 
return $channelUse['mail'] === 'y' || $channelUse['sms'] === 'y';
}
 
/**
* 기본정책 약관, 개인정보관련 동의사항, 이용, 탈퇴 안내 저장 함수
*
$vo->setModeFl(gd_isset($postValue['privateMarketingFl'], 'n'));
}
 
if ($code == BuyerInformCode::PRIVATE_MARKETING_MEMBER) {
$vo->setInformNm(strip_tags($postValue['privateMarketingMemberWriteTitle']));
$vo->setModeFl(gd_isset($postValue['privateMarketingMemberFl'], 'n'));
$vo->setNoticePopupFl(gd_isset($postValue['privateMarketingMemberNoticePopupFl'], 'n'));
}
 
if ($code == BuyerInformCode::ADVERTISING_RECEIVE) {
$advertisingReceiveChannels = $this->resolveAdvertisingReceiveChannels($postValue, (int)$mallSno);
$vo->setInformNm(strip_tags($postValue['advertisingReceiveWriteTitle']));
$vo->setModeFl(($advertisingReceiveChannels['email']['use'] === 'y' || $advertisingReceiveChannels['mobile']['use'] === 'y') ? 'y' : 'n');
}
 
$strSQL = "SELECT sno, displayShopFl FROM " . $tableName . " WHERE informCd='" . $code . "'";
if ($mallSno > DEFAULT_MALL_NUMBER) {
$strSQL .= " AND mallSno='" . $mallSno . "'";
 
Component/Agreement/BuyerInformVo.php+17
변경 전BuyerInformVo.php
private $regDt;
private $modDt;
private $displayShopFl;
 
function __construct($code = null)
{
$this->displayShopFl = $displayShopFl;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* object return array
*
 
변경 후BuyerInformVo.php
private $regDt;
private $modDt;
private $displayShopFl;
private string $noticePopupFl = 'n';
function __construct($code = null)
{
$this->displayShopFl = $displayShopFl;
}
/**
* @return string
*/
public function getNoticePopupFl(): string
{
return $this->noticePopupFl;
}
/**
* @param string $noticePopupFl
*/
public function setNoticePopupFl(string $noticePopupFl)
{
$this->noticePopupFl = $noticePopupFl;
}
/**
* object return array
*
 
Component/Database/DBTableField.php+1
변경 전DBTableField.php
['val' => 'scmDisplayFl', 'typ' => 's', 'def' => 'n'], // 모드 사용여부
['val' => 'content', 'typ' => 's', 'def' => null], // 내용
['val' => 'displayShopFl', 'typ' => 's', 'def' => 'n'], // 약관 노출 여부
 
];
// @formatter:on
 
 
변경 후DBTableField.php
['val' => 'scmDisplayFl', 'typ' => 's', 'def' => 'n'], // 모드 사용여부
['val' => 'content', 'typ' => 's', 'def' => null], // 내용
['val' => 'displayShopFl', 'typ' => 's', 'def' => 'n'], // 약관 노출 여부
['val' => 'noticePopupFl', 'typ' => 's', 'def' => 'n'], // 마케팅 목적 개인정보 수집 동의 안내 팝업 노출 여부
];
// @formatter:on
 
 
Component/Database/MemberTableField.php+12
변경 전MemberTableField.php
'def' => null,
],
// 개인정보동의 취급업무 위탁
 
 
 
 
 
 
 
 
 
 
 
 
[
'val' => 'foreigner',
'typ' => 's',
 
변경 후MemberTableField.php
'def' => null,
],
// 개인정보동의 취급업무 위탁
[
'val' => 'privateMarketingConsentOptionFl',
'typ' => 's',
'def' => '', // 미응답(빈값) 상태 유지 - DB DEFAULT '' 와 동일
],
// 마케팅 목적 개인정보 수집 및 이용 동의
[
'val' => 'privateMarketingConsentNoticePopupDt',
'typ' => 's',
'def' => null,
],
// 마케팅 목적 개인정보 수집 및 이용 동의 안내 팝업 노출 일시 (NULL:미노출)
[
'val' => 'foreigner',
'typ' => 's',
 
Component/Excel/ExcelMember.php+10-1
변경 전ExcelMember.php
'dbName' => 'member',
'dbKey' => 'smsFl',
'excelKey' => 'sms_fl',
'text' => __('SMS수신동의'),
'sample' => 'y',
'comment' => sprintf('y:%s<br />n:%s', __('받음'), __('거부')),
'desc' => sprintf('y:%s, n:%s, %s', __('받음'), __('거부'), __('기본은 y(받음)입니다.')),
'comment' => sprintf('n:%s<br />y:%s', __('동의안함'), __('동의함')),
'desc' => sprintf('n:%s, y:%s, %s', __('동의안함'), __('동의함'), __('기본은 n(동의안함)입니다.')),
],
 
 
 
 
 
 
 
 
 
[
'dbName' => 'member',
'dbKey' => 'foreigner',
 
변경 후ExcelMember.php
'dbName' => 'member',
'dbKey' => 'smsFl',
'excelKey' => 'sms_fl',
'text' => __('모바일메시지수신동의'),
'sample' => 'y',
'comment' => sprintf('y:%s<br />n:%s', __('받음'), __('거부')),
'desc' => sprintf('y:%s, n:%s, %s', __('받음'), __('거부'), __('기본은 y(받음)입니다.')),
'comment' => sprintf('n:%s<br />y:%s', __('동의안함'), __('동의함')),
'desc' => sprintf('n:%s, y:%s, %s', __('동의안함'), __('동의함'), __('기본은 n(동의안함)입니다.')),
],
[
'dbName' => 'member',
'dbKey' => 'privateMarketingConsentOptionFl',
'excelKey' => 'private_marketing_consent_option_fl',
'text' => __('마케팅 목적 개인정보 수집 및 이용'),
'sample' => 'n',
'comment' => sprintf('n:%s<br />y:%s', __('동의안함'), __('동의함')),
'desc' => sprintf('n:%s, y:%s, %s', __('동의안함'), __('동의함'), __('기본은 n(동의안함)입니다.')),
],
[
'dbName' => 'member',
'dbKey' => 'foreigner',
 
Component/Member/History.php+114-25
변경 전History.php
//__('마일리지')
'deposit' => '예치금',
//__('예치금')
'maillingFl' => '메일수신동의',
//__('메일수신동의')
'smsFl' => 'SMS수신동의',
//__('SMS수신동의')
'marriFl' => '결혼여부',
//__('결혼여부')
'marriDate' => '결혼기념일',
//__('개인정보 수집 및 이용 필수')
'privateApprovalOptionFl' => '개인정보 수집 및 이용 선택',
//__('개인정보 수집 및 이용 선택')
 
 
'privateOfferFl' => '개인정보동의 제3자 제공',
//__('개인정보동의 제3자 제공')
'privateConsignFl' => '개인정보동의 취급업무 위탁',
 
$different = [];
$this->getDifferent($different);
if (gd_array_key_exists('smsFl', $different)) {
$this->filters[] = 'smsFl';
}
if (gd_array_key_exists('maillingFl', $different)) {
$this->filters[] = 'maillingFl';
}
 
if (gd_array_key_exists('privateApprovalOptionFl', $different)) {
 
 
 
$this->filters[] = 'privateApprovalOptionFl';
}
if (gd_array_key_exists('privateOfferFl', $different)) {
 
 
 
$this->filters[] = 'privateOfferFl';
}
if (gd_array_key_exists('privateConsignFl', $different)) {
$this->filters[] = 'privateConsignFl';
}
if (key_exists('companyCertification', $different)) {
}
$beforeValue = $displayArray[$beforeValue];
$value = $displayArray[$value];
if (gd_array_key_exists('groupModDt', $this->after) && ($this->before['groupModDt'] != $this->after['groupModDt'])) {
$different['groupModDt'] = [
$this->before['groupModDt'],
$this->after['groupModDt'],
$beforeValue = $displayArray[$beforeValue];
$value = $displayArray[$value];
}
 
 
 
 
unset($displayArray);
$different[$key] = [
$beforeValue,
StringUtils::strIsSet($item['otherValue'], '{}');
$otherValue = json_decode($item['otherValue'], true);
StringUtils::strIsSet($otherValue['smsFl']['sms080']['regDt'], '');
if (!empty($otherValue['smsFl']['sms080']['regDt']) && $item['updateColumn'] == 'SMS수신동의') {
 
$item['displayOtherValue'] = sprintf(' (080 수신거부 : %s)', $otherValue['smsFl']['sms080']['regDt']);
}
if (!empty($otherValue['reflectApplyMemberInfo']['orderNo']) && empty($otherValue['simpleJoin'])) {
*/
public function selectLastReceiveAgreementByMember($memberNo)
{
// __('SMS수신동의')
// __('메일수신동의')
 
$this->db->strField = 'mh.memNo, mh.updateColumn, mh.afterValue, MAX(mh.regDt) AS lastUpdateDt';
$this->db->strWhere = 'mh.memNo=? AND mh.updateColumn IN (\'SMS수신동의\', \'메일수신동의\')';
 
$this->db->strGroup = 'mh.updateColumn';
$this->db->bind_param_push($bind, 'i', $memberNo);
$query = $this->db->query_complete();
*/
public function getLastReceiveAgreementByMember($memberNo)
{
// __('SMS수신동의')
// __('메일수신동의')
$history = $this->selectLastReceiveAgreementByMember($memberNo);
$result = [];
foreach ($history as $item) {
if ($item['updateColumn'] == 'SMS수신동의') {
$result['lastReceiveAgreementDt']['sms'] = DateTimeUtils::dateFormat('Y-m-d H:i:s', $item['lastUpdateDt']);
} elseif ($item['updateColumn'] == '메일수신동의') {
$result['lastReceiveAgreementDt']['mail'] = DateTimeUtils::dateFormat('Y-m-d H:i:s', $item['lastUpdateDt']);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
}
 
return $result;
$resultArray =[];
 
$this->db->strField = 'mh.memNo, mh.updateColumn, MAX(mh.regDt) AS lastUpdateDt';
$this->db->strWhere = 'mh.memNo in ('.gd_implode(',', $memberNos).') AND mh.updateColumn IN (\'SMS수신동의\', \'메일수신동의\')';
 
$this->db->strGroup = 'mh.memNo, mh.updateColumn';
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_MEMBER_HISTORY . ' AS mh' . gd_implode(' ', $query);
if (is_array($memberNos)) {
$history = $this->selectLastReceiveAgreementByMembers($memberNos);
foreach ($history as $item) {
if ($item['updateColumn'] == 'SMS수신동의') {
$result[$item['memNo']]['lastReceiveAgreementDt']['sms'] = DateTimeUtils::dateFormat('Y-m-d H:i:s', $item['lastUpdateDt']);
} elseif ($item['updateColumn'] == '메일수신동의') {
$result[$item['memNo']]['lastReceiveAgreementDt']['mail'] = DateTimeUtils::dateFormat('Y-m-d H:i:s', $item['lastUpdateDt']);
 
 
 
 
 
 
 
 
}
}
}
 
변경 후History.php
//__('마일리지')
'deposit' => '예치금',
//__('예치금')
'maillingFl' => '이메일 수신 동의',
//__('이메일 수신 동의')
'smsFl' => '모바일 메시지 수신 동의',
//__('모바일 메시지 수신 동의')
'marriFl' => '결혼여부',
//__('결혼여부')
'marriDate' => '결혼기념일',
//__('개인정보 수집 및 이용 필수')
'privateApprovalOptionFl' => '개인정보 수집 및 이용 선택',
//__('개인정보 수집 및 이용 선택')
'privateMarketingConsentOptionFl' => '마케팅 목적 개인정보 수집 및 이용 동의',
//__('마케팅 목적 개인정보 수집 및 이용 동의')
'privateOfferFl' => '개인정보동의 제3자 제공',
//__('개인정보동의 제3자 제공')
'privateConsignFl' => '개인정보동의 취급업무 위탁',
 
$different = [];
$this->getDifferent($different);
if (array_key_exists('smsFl', $different)) {
$this->filters[] = 'smsFl';
}
if (array_key_exists('maillingFl', $different)) {
$this->filters[] = 'maillingFl';
}
 
if (array_key_exists('privateApprovalFl', $different)) {
$this->filters[] = 'privateApprovalFl';
}
if (array_key_exists('privateApprovalOptionFl', $different)) {
$this->filters[] = 'privateApprovalOptionFl';
}
if (array_key_exists('privateMarketingConsentOptionFl', $different)) {
$this->filters[] = 'privateMarketingConsentOptionFl';
}
if (array_key_exists('privateOfferFl', $different)) {
$this->filters[] = 'privateOfferFl';
}
if (array_key_exists('privateConsignFl', $different)) {
$this->filters[] = 'privateConsignFl';
}
if (key_exists('companyCertification', $different)) {
}
$beforeValue = $displayArray[$beforeValue];
$value = $displayArray[$value];
if (array_key_exists('groupModDt', $this->after) && ($this->before['groupModDt'] != $this->after['groupModDt'])) {
$different['groupModDt'] = [
$this->before['groupModDt'],
$this->after['groupModDt'],
$beforeValue = $displayArray[$beforeValue];
$value = $displayArray[$value];
}
// 마케팅 목적 개인정보 수집 및 이용 동의: 미응답(빈값)은 미동의(n)로 표시 (비교는 원본이라 최초 응답도 이력에 남음)
if ('privateMarketingConsentOptionFl' == $key && '' === $beforeValue) {
$beforeValue = 'n';
}
unset($displayArray);
$different[$key] = [
$beforeValue,
StringUtils::strIsSet($item['otherValue'], '{}');
$otherValue = json_decode($item['otherValue'], true);
StringUtils::strIsSet($otherValue['smsFl']['sms080']['regDt'], '');
// 'SMS수신동의' 는 구 라벨 - 마이그레이션 미완 데이터 대비
if (!empty($otherValue['smsFl']['sms080']['regDt']) && in_array($item['updateColumn'], ['모바일 메시지 수신 동의', 'SMS수신동의'], true)) {
$item['displayOtherValue'] = sprintf(' (080 수신거부 : %s)', $otherValue['smsFl']['sms080']['regDt']);
}
if (!empty($otherValue['reflectApplyMemberInfo']['orderNo']) && empty($otherValue['simpleJoin'])) {
*/
public function selectLastReceiveAgreementByMember($memberNo)
{
// __('모바일 메시지 수신 동의')
// __('이메일 수신 동의')
// __('마케팅 목적 개인정보 수집 및 이용 동의')
$this->db->strField = 'mh.memNo, mh.updateColumn, mh.afterValue, MAX(mh.regDt) AS lastUpdateDt';
// 'SMS수신동의'/'메일수신동의' 는 구 라벨 - 마이그레이션 미완 데이터 대비
$this->db->strWhere = 'mh.memNo=? AND mh.updateColumn IN (\'모바일 메시지 수신 동의\', \'SMS수신동의\', \'이메일 수신 동의\', \'메일수신동의\', \'마케팅 목적 개인정보 수집 및 이용 동의\')';
$this->db->strGroup = 'mh.updateColumn';
$this->db->bind_param_push($bind, 'i', $memberNo);
$query = $this->db->query_complete();
*/
public function getLastReceiveAgreementByMember($memberNo)
{
// __('모바일 메시지 수신 동의')
// __('이메일 수신 동의')
$history = $this->selectLastReceiveAgreementByMember($memberNo);
$result = [];
foreach ($history as $item) {
$dt = DateTimeUtils::dateFormat('Y-m-d H:i:s', $item['lastUpdateDt']);
// 'SMS수신동의'/'메일수신동의' 는 구 라벨 - 마이그레이션 미완 데이터 대비
if (in_array($item['updateColumn'], ['모바일 메시지 수신 동의', 'SMS수신동의'], true)) {
$key = 'sms';
} elseif (in_array($item['updateColumn'], ['이메일 수신 동의', '메일수신동의'], true)) {
$key = 'mail';
} elseif ($item['updateColumn'] == '마케팅 목적 개인정보 수집 및 이용 동의') {
$key = 'privateMkt';
} else {
continue;
}
// 옛/새 라벨이 각각 그룹으로 나뉘어 올 수 있어 더 늦은 일자만 유지
if (empty($result['lastReceiveAgreementDt'][$key]) || $result['lastReceiveAgreementDt'][$key] < $dt) {
$result['lastReceiveAgreementDt'][$key] = $dt;
}
}
 
return $result;
}
 
/**
* 회원의 개인정보 동의 항목별 마지막 변경 일시 조회 (updateColumn 한글 라벨 그룹, MAX regDt)
*
* @param $memberNo
*
* @return array
*/
public function selectLastPrivateAgreementByMember($memberNo): array
{
// __('개인정보 수집 및 이용 필수')
// __('개인정보 수집 및 이용 선택')
// __('개인정보동의 제3자 제공')
// __('개인정보동의 취급업무 위탁')
// __('마케팅 목적 개인정보 수집 및 이용 동의')
$this->db->strField = 'mh.updateColumn, MAX(mh.regDt) AS lastUpdateDt';
$this->db->strWhere = 'mh.memNo=? AND mh.updateColumn IN (\'개인정보 수집 및 이용 필수\', \'개인정보 수집 및 이용 선택\', \'개인정보동의 제3자 제공\', \'개인정보동의 취급업무 위탁\', \'마케팅 목적 개인정보 수집 및 이용 동의\')';
$this->db->strGroup = 'mh.updateColumn';
$this->db->bind_param_push($bind, 'i', $memberNo);
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_MEMBER_HISTORY . ' AS mh' . implode(' ', $query);
$result = $this->db->query_fetch($strSQL, $bind);
 
return $result;
}
 
/**
* 회원의 개인정보 동의 항목별 마지막 변경 일시 (컬럼명 => 'Y-m-d H:i:s')
* 이력이 없는 항목은 키 자체가 없으므로 호출부에서 entryDt 등으로 폴백한다.
*
* @param int $memberNo
*
* @return array privateApprovalFl / privateApprovalOptionFl / privateOfferFl / privateConsignFl / privateMarketingConsentOptionFl 중 이력 있는 항목만
*/
public function getLastPrivateAgreementDateByMember(int $memberNo): array
{
$labelToColumn = [
'개인정보 수집 및 이용 필수' => 'privateApprovalFl',
'개인정보 수집 및 이용 선택' => 'privateApprovalOptionFl',
'개인정보동의 제3자 제공' => 'privateOfferFl',
'개인정보동의 취급업무 위탁' => 'privateConsignFl',
'마케팅 목적 개인정보 수집 및 이용 동의' => 'privateMarketingConsentOptionFl',
];
$history = $this->selectLastPrivateAgreementByMember($memberNo);
$result = [];
foreach ($history as $item) {
$column = $labelToColumn[$item['updateColumn']] ?? null;
if ($column === null) {
continue;
}
$result[$column] = DateTimeUtils::dateFormat('Y-m-d H:i:s', $item['lastUpdateDt']);
}
 
return $result;
$resultArray =[];
 
$this->db->strField = 'mh.memNo, mh.updateColumn, MAX(mh.regDt) AS lastUpdateDt';
// 'SMS수신동의'/'메일수신동의' 는 구 라벨 - 마이그레이션 미완 데이터 대비
$this->db->strWhere = 'mh.memNo in ('.gd_implode(',', $memberNos).') AND mh.updateColumn IN (\'모바일 메시지 수신 동의\', \'SMS수신동의\', \'이메일 수신 동의\', \'메일수신동의\')';
$this->db->strGroup = 'mh.memNo, mh.updateColumn';
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_MEMBER_HISTORY . ' AS mh' . gd_implode(' ', $query);
if (is_array($memberNos)) {
$history = $this->selectLastReceiveAgreementByMembers($memberNos);
foreach ($history as $item) {
$dt = DateTimeUtils::dateFormat('Y-m-d H:i:s', $item['lastUpdateDt']);
// 'SMS수신동의'/'메일수신동의' 는 구 라벨 - 마이그레이션 미완 데이터 대비
if (in_array($item['updateColumn'], ['모바일 메시지 수신 동의', 'SMS수신동의'], true)) {
$key = 'sms';
} elseif (in_array($item['updateColumn'], ['이메일 수신 동의', '메일수신동의'], true)) {
$key = 'mail';
} else {
continue;
}
// 옛/새 라벨이 각각 그룹으로 나뉘어 올 수 있어 더 늦은 일자만 유지
if (empty($result[$item['memNo']]['lastReceiveAgreementDt'][$key]) || $result[$item['memNo']]['lastReceiveAgreementDt'][$key] < $dt) {
$result[$item['memNo']]['lastReceiveAgreementDt'][$key] = $dt;
}
}
}
 
Component/Member/MemberAdmin.php+10-2
변경 전MemberAdmin.php
}
$this->db->strJoin = ' LEFT JOIN ' . DB_MEMBER_SNS . ' AS ms ON m.memNo = ms.memNo';
$this->db->strJoin .= ' LEFT JOIN ' . DB_MEMBER_GROUP . ' AS mg ON m.groupSno = mg.sno';
$this->db->strJoin .= ' LEFT JOIN ' . DB_MEMBER_HISTORY . ' AS mh ON (m.memNo=mh.memNo AND mh.updateColumn=\'SMS수신동의\')';
$this->db->strJoin .= ' LEFT JOIN ' . DB_MEMBER_HISTORY . ' AS mh2 ON (m.memNo=mh2.memNo AND mh2.updateColumn=\'메일수신동의\')';
$this->db->strGroup = 'm.memNo';
$arrQuery = $this->db->query_complete(true, true);
$strSQL = 'SELECT ' . array_shift($arrQuery) . ' FROM ' . DB_MEMBER . ' AS m ' . gd_implode(' ', $arrQuery);
}
}
 
 
 
 
 
 
 
$arrData = $this->validateMemberByAdminModification($arrData);
 
foreach ($preservedFileKeys as $fileKey => $fileVal) {
\App::getInstance('session')->set(SESSION_GLOBAL_MALL, ['isAdminModify' => true]);
}
\Component\Member\MemberValidation::addValidateMember($v, $require);
 
 
$v->add('memNo', 'number', true);
if (!$this->isExcelUpload) {
if (gd_isset($params['memPw'], '') !== '') {
 
변경 후MemberAdmin.php
}
$this->db->strJoin = ' LEFT JOIN ' . DB_MEMBER_SNS . ' AS ms ON m.memNo = ms.memNo';
$this->db->strJoin .= ' LEFT JOIN ' . DB_MEMBER_GROUP . ' AS mg ON m.groupSno = mg.sno';
$this->db->strJoin .= ' LEFT JOIN ' . DB_MEMBER_HISTORY . ' AS mh ON (m.memNo=mh.memNo AND mh.updateColumn=\'모바일 메시지 수신 동의\')';
$this->db->strJoin .= ' LEFT JOIN ' . DB_MEMBER_HISTORY . ' AS mh2 ON (m.memNo=mh2.memNo AND mh2.updateColumn=\'이메일 수신 동의\')';
$this->db->strGroup = 'm.memNo';
$arrQuery = $this->db->query_complete(true, true);
$strSQL = 'SELECT ' . array_shift($arrQuery) . ' FROM ' . DB_MEMBER . ' AS m ' . gd_implode(' ', $arrQuery);
}
}
 
// 마케팅 목적 개인정보 수집 및 이용 동의는 약관 사용 시에만 포함
$privateMarketingMemberData = (new \Component\Agreement\BuyerInform())->getInformDataArray(\Component\Agreement\BuyerInformCode::PRIVATE_MARKETING_MEMBER);
if (($privateMarketingMemberData[0]['modeFl'] ?? 'n') === 'y') {
StringUtils::strIsSet($arrData['privateMarketingConsentOptionFl'], 'n');
}
 
$arrData = $this->validateMemberByAdminModification($arrData);
 
foreach ($preservedFileKeys as $fileKey => $fileVal) {
\App::getInstance('session')->set(SESSION_GLOBAL_MALL, ['isAdminModify' => true]);
}
\Component\Member\MemberValidation::addValidateMember($v, $require);
// 마케팅 목적 개인정보 수집 및 이용 동의 (validator 미등록 시 act garbageDelete 로 제거되어 미저장)
$v->add('privateMarketingConsentOptionFl', 'yn', false, '{' . __('마케팅 목적 개인정보 수집 및 이용 동의') . '}');
$v->add('memNo', 'number', true);
if (!$this->isExcelUpload) {
if (gd_isset($params['memPw'], '') !== '') {
 
Component/Member/MemberDAO.php+18-4
변경 전MemberDAO.php
 
$this->db->strField = 'm.memNo, m.memId, m.memNm, m.groupSno, m.mileage, m.deposit, m.smsFl, m.maillingFl, m.cellPhone, m.email';
$this->db->strField .= ', ' . $strFieldBySmsAgreementDt . ', ' . $strFieldByMailAgreementDt;
$this->db->strJoin = ' LEFT JOIN (SELECT memNo, COUNT(*) AS cnt, MAX(regDt) AS maxRegDt FROM ' . DB_MEMBER_HISTORY . ' WHERE updateColumn=\'SMS수신동의\' GROUP BY memNo) AS mh_sms ON m.memNo = mh_sms.memNo';
$this->db->strJoin .= ' LEFT JOIN (SELECT memNo, COUNT(*) AS cnt, MAX(regDt) AS maxRegDt FROM ' . DB_MEMBER_HISTORY . ' WHERE updateColumn=\'메일수신동의\' GROUP BY memNo) AS mh_mail ON m.memNo = mh_mail.memNo';
 
 
$this->db->strJoin .= ' LEFT JOIN (SELECT DISTINCT memNo FROM ' . DB_MEMBER_NOTIFICATION_LOG . ' WHERE ReasonCode=\'' . MailMimeAuto::AGREEMENT2YPERIOD . '\' AND regDt >= \'' . $standardDt . ' 00:00:00\' AND regDt <= \'' . $nowDt . ' 23:59:59\') AS mnl ON m.memNo = mnl.memNo';
 
$this->db->strWhere = '';
$this->db->bindParameter('sexFl', $params, $arrBind, $arrWhere, 'tableMember', 'm');
$this->db->bindParameter('maillingFl', $params, $arrBind, $arrWhere, 'tableMember', 'm');
$this->db->bindParameter('smsFl', $params, $arrBind, $arrWhere, 'tableMember', 'm');
 
 
 
 
 
 
 
 
 
 
$this->db->bindParameter('calendarFl', $params, $arrBind, $arrWhere, 'tableMember', 'm');
$this->db->bindParameter('marriFl', $params, $arrBind, $arrWhere, 'tableMember', 'm');
$this->db->bindParameter('connectSns', $params, $arrBind, $arrWhere, 'tableMemberSns', 'ms', 'snsTypeFl');
 
$query = MemberModel::from('es_member as m')
->leftJoin('es_memberHistory as mh', function ($join) {
 
$join->on('mh.memNo', '=', 'm.memNo')
->where('mh.updateColumn', 'SMS수신동의');
})
->leftJoin('es_memberHistory as mh2', function ($join) {
 
$join->on('mh2.memNo', '=', 'm.memNo')
->where('mh2.updateColumn', '메일수신동의');
})
->leftJoin('es_memberGroup as mg', 'm.groupSno', '=', 'mg.sno');
 
 
변경 후MemberDAO.php
 
$this->db->strField = 'm.memNo, m.memId, m.memNm, m.groupSno, m.mileage, m.deposit, m.smsFl, m.maillingFl, m.cellPhone, m.email';
$this->db->strField .= ', ' . $strFieldBySmsAgreementDt . ', ' . $strFieldByMailAgreementDt;
// 'SMS수신동의' 는 구 라벨 - 마이그레이션 미완 데이터 대비
$this->db->strJoin = ' LEFT JOIN (SELECT memNo, COUNT(*) AS cnt, MAX(regDt) AS maxRegDt FROM ' . DB_MEMBER_HISTORY . ' WHERE updateColumn IN (\'모바일 메시지 수신 동의\', \'SMS수신동의\') GROUP BY memNo) AS mh_sms ON m.memNo = mh_sms.memNo';
// '메일수신동의' 는 구 라벨 - 마이그레이션 미완 데이터 대비
$this->db->strJoin .= ' LEFT JOIN (SELECT memNo, COUNT(*) AS cnt, MAX(regDt) AS maxRegDt FROM ' . DB_MEMBER_HISTORY . ' WHERE updateColumn IN (\'이메일 수신 동의\', \'메일수신동의\') GROUP BY memNo) AS mh_mail ON m.memNo = mh_mail.memNo';
$this->db->strJoin .= ' LEFT JOIN (SELECT DISTINCT memNo FROM ' . DB_MEMBER_NOTIFICATION_LOG . ' WHERE ReasonCode=\'' . MailMimeAuto::AGREEMENT2YPERIOD . '\' AND regDt >= \'' . $standardDt . ' 00:00:00\' AND regDt <= \'' . $nowDt . ' 23:59:59\') AS mnl ON m.memNo = mnl.memNo';
 
$this->db->strWhere = '';
$this->db->bindParameter('sexFl', $params, $arrBind, $arrWhere, 'tableMember', 'm');
$this->db->bindParameter('maillingFl', $params, $arrBind, $arrWhere, 'tableMember', 'm');
$this->db->bindParameter('smsFl', $params, $arrBind, $arrWhere, 'tableMember', 'm');
// 마케팅 목적 개인정보 수집 및 이용 동의 검색값 화이트리스트 정규화
if (!in_array($params['privateMarketingConsentOptionFl'] ?? '', ['', 'y', 'n'], true)) {
$params['privateMarketingConsentOptionFl'] = '';
}
// 마케팅 목적 개인정보 수집 및 이용 동의 (미동의는 동의여부 저장 안 한 디폴트('') 포함이라 != 'y')
if (!empty($params['privateMarketingConsentOptionFl'])) {
$arrWhere[] = $params['privateMarketingConsentOptionFl'] === 'y'
? "m.privateMarketingConsentOptionFl = 'y'"
: "m.privateMarketingConsentOptionFl != 'y'";
}
$this->db->bindParameter('calendarFl', $params, $arrBind, $arrWhere, 'tableMember', 'm');
$this->db->bindParameter('marriFl', $params, $arrBind, $arrWhere, 'tableMember', 'm');
$this->db->bindParameter('connectSns', $params, $arrBind, $arrWhere, 'tableMemberSns', 'ms', 'snsTypeFl');
 
$query = MemberModel::from('es_member as m')
->leftJoin('es_memberHistory as mh', function ($join) {
// '모바일 메시지 수신 동의'(신규) / 'SMS수신동의'(구 라벨) dual-match - 마이그레이션 미완 데이터 대비
$join->on('mh.memNo', '=', 'm.memNo')
->whereIn('mh.updateColumn', ['모바일 메시지 수신 동의', 'SMS수신동의']);
})
->leftJoin('es_memberHistory as mh2', function ($join) {
// '이메일 수신 동의'(신규) / '메일수신동의'(구 라벨) dual-match - 마이그레이션 미완 데이터 대비
$join->on('mh2.memNo', '=', 'm.memNo')
->whereIn('mh2.updateColumn', ['이메일 수신 동의', '메일수신동의']);
})
->leftJoin('es_memberGroup as mg', 'm.groupSno', '=', 'mg.sno');
 
 
Component/Member/Member.php+59
변경 전Member.php
}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 로그인 비밀번호를 재검증 하는 함수
*
$v->add('privateApprovalOptionFl', '', false, '{' . __('개인정보 수집.이용 동의 선택사항') . '}'); // 개인정보동의 이용자 동의사항
$v->add('privateOfferFl', '', false, '{' . __('개인정보동의 제3자 제공') . '}'); // 개인정보동의 제3자 제공
$v->add('privateConsignFl', '', false, '{' . __('개인정보동의 취급업무 위탁') . '}'); // 개인정보동의 취급업무 위탁
 
 
 
$v->add('foreigner', '', false, '{' . __('내외국인구분') . '}'); // 내외국인구분
$v->add('dupeinfo', '', false, '{' . __('본인확인 중복가입확인정보') . '}'); // 본인확인 중복가입확인정보
$v->add('pakey', '', false, '{' . __('본인확인 번호') . '}'); // 본인확인 번호
$vo->setPrivateApprovalOptionFl(json_encode($joinSession->get('privateApprovalOptionFl'), JSON_UNESCAPED_SLASHES));
$vo->setPrivateOfferFl(json_encode($joinSession->get('privateOfferFl'), JSON_UNESCAPED_SLASHES));
$vo->setPrivateConsignFl(json_encode($joinSession->get('privateConsignFl'), JSON_UNESCAPED_SLASHES));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$vo->setForeigner($joinSession->get('foreigner'));
$vo->setDupeinfo($joinSession->get('dupeinfo'));
$vo->setPakey($joinSession->get('pakey'));
'sexFl',
'marriFl',
'expirationFl',
 
], $memberData
);
 
 
변경 후Member.php
}
}
 
/**
* 마케팅 목적 개인정보 수집 및 이용 동의 안내(alert) 노출 여부 판정 + 노출 이력 기록
* 조건 통과 시 노출 이력(privateMarketingConsentNoticePopupDt)에 노출시각을 기록해 계정당 1회만 노출
*
* @return bool 안내 노출 대상이면 true (동시에 노출 이력 기록)
* @throws Exception
*/
public function markPrivateMarketingConsentNoticeShown(): bool
{
if (!MemberUtil::isLogin()) {
return false;
}
 
// 마케팅 목적 개인정보 수집 및 이용 동의 약관(001015) 사용중(modeFl='y') + 안내 팝업 노출 사용(noticePopupFl='y')
/** @var \Bundle\Component\Agreement\BuyerInform $buyerInform */
$buyerInform = \App::load('\\Component\\Agreement\\BuyerInform');
$inform = $buyerInform->getInformDataArray(\Component\Agreement\BuyerInformCode::PRIVATE_MARKETING_MEMBER, 'modeFl, noticePopupFl');
if (($inform[0]['modeFl'] ?? 'n') !== 'y' || ($inform[0]['noticePopupFl'] ?? 'n') !== 'y') {
return false;
}
 
$member = MemberUtil::getMemberBySession('memNo, privateMarketingConsentOptionFl, privateMarketingConsentNoticePopupDt');
$noticeDt = $member['privateMarketingConsentNoticePopupDt'] ?? '';
$alreadyNoticed = !empty($noticeDt) && $noticeDt !== '0000-00-00 00:00:00';
// 동의('y'), 안내 노출 이력이 있으면 미노출
if (($member['privateMarketingConsentOptionFl'] ?? '') === 'y' || $alreadyNoticed) {
return false;
}
 
// 노출 이력 기록 (계정당 평생 1회 — 노출했다는 사실만 저장, 동의/거부와 무관)
try {
$this->update($member['memNo'], 'memNo', ['privateMarketingConsentNoticePopupDt'], [DateTimeUtils::dateFormat('Y-m-d H:i:s', 'now')]);
} catch (\Exception $e) {
\Logger::channel('userLogin')->warning('마케팅 목적 개인정보 수집 및 이용 동의 안내 노출 이력 기록 실패', ['memNo' => $member['memNo'], 'exception' => $e]);
 
return false;
}
 
return true;
}
 
/**
* 로그인 비밀번호를 재검증 하는 함수
*
$v->add('privateApprovalOptionFl', '', false, '{' . __('개인정보 수집.이용 동의 선택사항') . '}'); // 개인정보동의 이용자 동의사항
$v->add('privateOfferFl', '', false, '{' . __('개인정보동의 제3자 제공') . '}'); // 개인정보동의 제3자 제공
$v->add('privateConsignFl', '', false, '{' . __('개인정보동의 취급업무 위탁') . '}'); // 개인정보동의 취급업무 위탁
$v->add('privateMarketingConsentOptionFl', 'yn', false, '{' . __('마케팅 목적 개인정보 수집 및 이용 동의') . '}'); // 마케팅 목적 개인정보 수집 및 이용 동의
$v->add('maillingFl', 'yn', false, '{' . __('이메일 수신 동의') . '}'); // 광고성 수신 동의 - 이메일
$v->add('smsFl', 'yn', false, '{' . __('모바일 메시지 수신 동의') . '}'); // 광고성 수신 동의 - 모바일
$v->add('foreigner', '', false, '{' . __('내외국인구분') . '}'); // 내외국인구분
$v->add('dupeinfo', '', false, '{' . __('본인확인 중복가입확인정보') . '}'); // 본인확인 중복가입확인정보
$v->add('pakey', '', false, '{' . __('본인확인 번호') . '}'); // 본인확인 번호
$vo->setPrivateApprovalOptionFl(json_encode($joinSession->get('privateApprovalOptionFl'), JSON_UNESCAPED_SLASHES));
$vo->setPrivateOfferFl(json_encode($joinSession->get('privateOfferFl'), JSON_UNESCAPED_SLASHES));
$vo->setPrivateConsignFl(json_encode($joinSession->get('privateConsignFl'), JSON_UNESCAPED_SLASHES));
// 마수동 약관 사용중(modeFl='y')일 때만 동의('y')/미동의('n') 확정. 미사용 시 ''(미응답) = 로그인 안내 팝업 대상.
$privateMarketingConsentOptionFl = $joinSession->get('privateMarketingConsentOptionFl');
$buyerInform = \App::load('\\Component\\Agreement\\BuyerInform');
$privateMarketingMember = $buyerInform->getInformDataArray(\Component\Agreement\BuyerInformCode::PRIVATE_MARKETING_MEMBER, 'modeFl');
if (($privateMarketingMember[0]['modeFl'] ?? 'n') === 'y') {
$privateMarketingConsentOptionFl = ($privateMarketingConsentOptionFl === 'y') ? 'y' : 'n';
// 약관 사용 중 가입 = 이미 선택함 → 로그인 안내 팝업 제외 (노출 이력에 가입 시각 기록)
$vo->setPrivateMarketingConsentNoticePopupDt(date('Y-m-d H:i:s'));
} else {
$privateMarketingConsentOptionFl = '';
}
$vo->setPrivateMarketingConsentOptionFl($privateMarketingConsentOptionFl);
$vo->setMaillingFl($joinSession->get('maillingFl'));
$vo->setSmsFl($joinSession->get('smsFl'));
$vo->setForeigner($joinSession->get('foreigner'));
$vo->setDupeinfo($joinSession->get('dupeinfo'));
$vo->setPakey($joinSession->get('pakey'));
'sexFl',
'marriFl',
'expirationFl',
'privateMarketingConsentOptionFl',
], $memberData
);
 
 
Component/Member/MemberVO.php+34
변경 전MemberVO.php
protected $ex1, $ex2, $ex3, $ex4, $ex5, $ex6;
protected $privateApprovalFl;
protected $privateApprovalOptionFl;
 
 
protected $privateOfferFl;
protected $privateConsignFl;
protected $foreigner;
$this->privateApprovalOptionFl = $privateApprovalOptionFl;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* @param mixed $privateOfferFl
*/
 
변경 후MemberVO.php
protected $ex1, $ex2, $ex3, $ex4, $ex5, $ex6;
protected $privateApprovalFl;
protected $privateApprovalOptionFl;
protected $privateMarketingConsentOptionFl;
protected $privateMarketingConsentNoticePopupDt;
protected $privateOfferFl;
protected $privateConsignFl;
protected $foreigner;
$this->privateApprovalOptionFl = $privateApprovalOptionFl;
}
/**
* @param mixed $privateMarketingConsentOptionFl 마케팅 목적 개인정보 수집 및 이용 동의
*/
public function setPrivateMarketingConsentOptionFl($privateMarketingConsentOptionFl)
{
$this->privateMarketingConsentOptionFl = $privateMarketingConsentOptionFl;
}
/**
* @param mixed $privateMarketingConsentNoticePopupDt 마케팅 목적 개인정보 수집 및 이용 동의 안내 팝업 노출 일시
*/
public function setPrivateMarketingConsentNoticePopupDt($privateMarketingConsentNoticePopupDt)
{
$this->privateMarketingConsentNoticePopupDt = $privateMarketingConsentNoticePopupDt;
}
/**
* @param mixed $maillingFl 이메일 수신 동의
*/
public function setMaillingFl($maillingFl)
{
$this->maillingFl = $maillingFl;
}
/**
* @param mixed $smsFl 모바일 메시지 수신 동의
*/
public function setSmsFl($smsFl)
{
$this->smsFl = $smsFl;
}
/**
* @param mixed $privateOfferFl
*/
 
Component/Member/MyPage.php+13
변경 전MyPage.php
$requestParams['privateConsignFl'] = json_encode($agreementData['privateConsignFl'], JSON_UNESCAPED_SLASHES);
$requestParams['privateOfferFl'] = json_encode($agreementData['privateOfferFl'], JSON_UNESCAPED_SLASHES);
 
 
 
 
 
 
$passValidation = isset($memberSession['memPw']) == false && $memberSession['snsJoinFl'] == 'y';
$requestParams = $this->validateMemberByModification($requestParams, $passValidation);
$addExclude = [
$v->add('privateApprovalOptionFl', '', false, '{' . __('개인정보 수집 및 이용') . '}');
$v->add('privateOfferFl', '', false, '{' . __('개인정보동의 제3자 제공') . '}');
$v->add('privateConsignFl', '', false, '{' . __('개인정보동의 취급업무 위탁') . '}');
 
if (StringUtils::strIsSet($requestParams['memPw'], '') !== '') {
\Component\Member\MemberValidation::validateMemberPassword($requestParams['memPw']);
$v->add('memPw', '', true, '{' . __('비밀번호') . '}');
} else {
$requestParams['privateOfferFl'] = $arrprivateOffer;
}
 
 
 
 
 
 
 
return $requestParams;
}
}
 
변경 후MyPage.php
$requestParams['privateConsignFl'] = json_encode($agreementData['privateConsignFl'], JSON_UNESCAPED_SLASHES);
$requestParams['privateOfferFl'] = json_encode($agreementData['privateOfferFl'], JSON_UNESCAPED_SLASHES);
 
// 마케팅 목적 개인정보 수집 및 이용 동의는 사용 시에만 setAgreementData 세팅 (미사용 시 미포함 → 컬럼값 갱신 안 함, 기존값 유지)
if (array_key_exists('privateMarketingConsentOptionFl', $agreementData)) {
$requestParams['privateMarketingConsentOptionFl'] = $agreementData['privateMarketingConsentOptionFl'];
}
 
$passValidation = isset($memberSession['memPw']) == false && $memberSession['snsJoinFl'] == 'y';
$requestParams = $this->validateMemberByModification($requestParams, $passValidation);
$addExclude = [
$v->add('privateApprovalOptionFl', '', false, '{' . __('개인정보 수집 및 이용') . '}');
$v->add('privateOfferFl', '', false, '{' . __('개인정보동의 제3자 제공') . '}');
$v->add('privateConsignFl', '', false, '{' . __('개인정보동의 취급업무 위탁') . '}');
$v->add('privateMarketingConsentOptionFl', '', false, '{' . __('마케팅 목적 개인정보 수집 및 이용 동의') . '}');
if (StringUtils::strIsSet($requestParams['memPw'], '') !== '') {
\Component\Member\MemberValidation::validateMemberPassword($requestParams['memPw']);
$v->add('memPw', '', true, '{' . __('비밀번호') . '}');
} else {
$requestParams['privateOfferFl'] = $arrprivateOffer;
}
 
// 마케팅 목적 개인정보 수집 및 이용 동의 (사용 시에만 동의 'y' / 미동의 'n' 저장. 미사용 시 빈값 유지 - 로그인 안내 팝업 조건)
$privateMarketingMemberData = $inform->getInformDataArray(BuyerInformCode::PRIVATE_MARKETING_MEMBER);
if (($privateMarketingMemberData[0]['modeFl'] ?? 'n') === 'y') {
$requestParams['privateMarketingConsentOptionFl'] = (($requestParams['privateMarketingConsentOptionFl'] ?? '') === 'y') ? 'y' : 'n';
}
 
return $requestParams;
}
}
 
Component/Member/Util/MemberUtil.php+177-14
변경 전MemberUtil.php
}
$joinField = \Component\Policy\JoinItemPolicy::getInstance()->getPolicy($mallSno);
 
$html[] = '<tr>';
for ($i = 1; $i < 7; $i++) {
$key = 'ex' . $i;
$value = $joinField[$key];
continue;
}
if ($value['name']) {
$title .= '<br /><span class="nobold">(' . $value['name'] . ')</span>';
}
if ($i != 1 && $i % 2 != 0) {
$html[] = '</tr><tr>';
}
$html[] = '<th class="input_title r_space ex">' . $title . '</th>';
$html[] = '<td>';
$tmpArray = array_map('trim', explode(',', $value['value']));
switch ($value['type']) {
default :
$html[] = '<input type="text" name="' . $key . '" class="form-control" value="' . $fieldValue[$key] . '"/>';
break;
case 'SELECT':
$html[] = gd_select_box($key, $key, gd_array_change_key_value($tmpArray), null, $fieldValue[$key], '=선택=');
 
 
 
 
break;
case 'RADIO':
$html[] = gd_radio_box($key, array_combine(gd_array_values($tmpArray), gd_array_values($tmpArray)), $fieldValue[$key]);
break;
case 'CHECKBOX':
$html[] = gd_check_box($key . '[]', array_combine(gd_array_values($tmpArray), gd_array_values($tmpArray)), $fieldValue[$key]);
 
 
 
 
 
 
 
 
 
break;
}
$html[] = '</td>';
 
}
 
return gd_implode('', $html);
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 로그인 되었는 지를 체크 (회원/비회원)
*
$data['agreementInfoFl'] = gd_isset($arrData['agreementInfoFl'], 'n');
$data['privateApprovalFl'] = gd_isset($arrData['privateApprovalFl'], 'n');
$data['under14ConsentFl'] = gd_isset($arrData['under14ConsentFl'], 'n');
 
 
 
//이용약관 체크값 설정
$myPage = \App::load('\\Component\\Member\\MyPage');
$data['privateApprovalOptionFl'] = gd_isset($agreementData['privateApprovalOptionFl']);
$data['privateOfferFl'] = gd_isset($agreementData['privateOfferFl']);
$data['privateConsignFl'] = gd_isset($agreementData['privateConsignFl']);
 
 
 
// 인증설정체크해서 데이터 추가 useDataJoinFl
$dreamInfo = gd_policy('member.auth_cellphone');
$params['sexFl'] = $params['sexFl'] ?? '';
$params['maillingFl'] = $params['maillingFl'] ?? '';
$params['smsFl'] = $params['smsFl'] ?? '';
 
$params['calendarFl'] = $params['calendarFl'] ?? '';
$params['expirationFl'] = $params['expirationFl'] ?? '';
 
$checked['sexFl'][$params['sexFl']] =
$checked['maillingFl'][$params['maillingFl']] =
$checked['smsFl'][$params['smsFl']] =
 
$checked['calendarFl'][$params['calendarFl']] =
$checked['expirationFl'][$params['expirationFl']] = 'checked="checked"';
 
 
변경 후MemberUtil.php
}
$joinField = \Component\Policy\JoinItemPolicy::getInstance()->getPolicy($mallSno);
 
$html = [];
for ($i = 1; $i < 7; $i++) {
$key = 'ex' . $i;
$value = $joinField[$key];
continue;
}
if ($value['name']) {
$title .= '<span class="nobold">(' . $value['name'] . ')</span>';
}
$html[] = '<tr>';
$html[] = '<th><div class="ncua-align-items-center ncua-flex-column ncua-align-items-start">' . $title . '</div></th>';
$html[] = '<td><div class="ncua-flex-gap">';
 
 
$tmpArray = array_map('trim', explode(',', $value['value']));
switch ($value['type']) {
 
 
 
case 'SELECT':
$html[] = '<span class="ncua-select ncua-select--xs">';
$html[] = '<span class="ncua-select__content">';
$html[] = gd_select_box($key, $key, gd_array_change_key_value($tmpArray), null, $fieldValue[$key], '=선택=', null, 'ncua-select__tag');
$html[] = '</span>';
$html[] = '</span>';
break;
case 'RADIO':
$html[] = gd_radio_box_ncua($key, array_combine(gd_array_values($tmpArray), gd_array_values($tmpArray)), $fieldValue[$key]);
break;
case 'CHECKBOX':
$html[] = gd_check_box_ncua($key . '[]', array_combine(gd_array_values($tmpArray), gd_array_values($tmpArray)), $fieldValue[$key]);
break;
default :
$html[] = '<div class="ncua-input ncua-input--xs ncua-input-width-320">';
$html[] = '<div class="ncua-input__content">';
$html[] = '<div class="ncua-input__field ncua-input__field--xs">';
$html[] = '<input type="text" name="' . $key . '" value="' . gd_htmlspecialchars($fieldValue[$key]) . '" placeholder="추가정보를 입력하세요."/>';
$html[] = '</div>';
$html[] = '</div>';
$html[] = '</div>';
break;
}
$html[] = '</div></td>';
$html[] = '</tr>';
}
 
return gd_implode('', $html);
}
 
/**
* 약관 동의 표시 항목 생성 (informNm + 동의함/동의안함 라벨)
*
* 개인정보 수집·이용/취급위탁/제3자 제공/마케팅 동의 등 약관 표시 블록을 정형화한다.
*
* @param array $informList 약관 목록 (각 항목에 sno, informNm 포함. modeFl 이 'y' 아니면 '사용안함' 표기)
* @param array $consentBySno 동의 상태 맵 (sno => 'y'/'n')
* @param bool $denyWhenMissing 미응답(y/n 아님)을 '동의안함'으로 표시할지 여부. false면 해당 항목 스킵
* @return array [['informNm' => string, 'label' => string, 'unused'? => bool], ...] (unused=true 면 미사용 약관 → 항목명 없이 '사용안함'만 표기)
*/
public static function makeConsentItems(array $informList, array $consentBySno, bool $denyWhenMissing = false, string $agreementDt = ''): array
{
$items = [];
foreach ($informList as $inform) {
// 개별 약관 미사용(modeFl != 'y')이면 동의 여부와 무관하게 '사용안함' 표기
if (($inform['modeFl'] ?? 'y') !== 'y') {
$items[] = ['informNm' => $inform['informNm'], 'label' => '사용안함', 'unused' => true];
continue;
}
$flag = $consentBySno[$inform['sno']] ?? '';
if ($flag === 'y') {
// 동의: 년월일시분초까지 표기
$label = '동의함' . ($agreementDt !== '' ? '(' . DateTimeUtils::dateFormat('Y-m-d H:i:s', $agreementDt) . ')' : '');
} elseif ($flag === 'n' || $denyWhenMissing) {
// 미동의: 년월일까지만 표기 (일시 없거나 미응답이면 날짜 생략)
$label = '동의안함' . (($agreementDt !== '' && $flag === 'n') ? '(' . DateTimeUtils::dateFormat('Y-m-d', $agreementDt) . ')' : '');
} else {
continue;
}
$items[] = ['informNm' => $inform['informNm'], 'label' => $label];
}
 
return $items;
}
 
/**
* 회원 약관 동의 표시용 행 데이터 생성. 약관 행 설정 배열을 받아 뷰에서 그대로 출력할 표시용 배열을 반환한다 (HTML 미생성 — 마크업은 뷰가 담당).
* 약관이 늘어도 호출부에서 배열 항목만 추가하면 되고 메서드 시그니처는 불변.
*
* @param array $agreements 약관 행 목록. 각 항목:
* - title string 제목 (뷰 <th> 출력용)
* - tooltipSeq string data-tooltip-seq 값(선택)
* - use bool 약관 사용/표시 여부 (false면 '사용안함')
* - informs array 약관 목록 (sno, informNm) — use=true 일 때만 참조
* - flags mixed 동의 상태. sno=>('y'/'n') 배열(개인정보 항목별) 또는 단일 'y'/'n' 문자열(마케팅)
* - denyWhenMissing bool 미응답(y/n 아님)을 '동의안함'으로 표시할지. false면 해당 항목 스킵
* - agreementDt string 동의/미동의 일시(선택). 지정 시 동의함(초까지)/동의안함(날짜까지)에 괄호로 병기. 컬럼 공통 값
* @return array 표시용 행 목록. 각 항목: [title, tooltipSeq, lines(표시 문자열 배열)]
*/
public static function buildPrivateAgreementRows(array $agreements): array
{
$rows = [];
foreach ($agreements as $agreement) {
$lines = [];
if (!empty($agreement['use'])) {
$informs = $agreement['informs'] ?? [];
$flags = $agreement['flags'] ?? [];
// 단일 플래그 문자열이면 전 항목에 동일 적용하도록 sno 맵으로 변환
if (!is_array($flags)) {
$singleFlag = (string) $flags;
$flags = [];
foreach ($informs as $inform) {
$flags[$inform['sno']] = $singleFlag;
}
}
$agreementDt = (string) ($agreement['agreementDt'] ?? '');
$unusedShown = false;
foreach (self::makeConsentItems($informs, $flags, !empty($agreement['denyWhenMissing']), $agreementDt) as $item) {
if (!empty($item['unused'])) {
// 미사용 약관(modeFl != 'y')은 항목명/동의여부 없이 '사용안함'만, 그룹당 1회만 표기
if ($unusedShown) {
continue;
}
$unusedShown = true;
$lines[] = '사용안함';
} else {
$lines[] = '- ' . $item['informNm'] . ' : ' . $item['label'];
}
}
} else {
$lines[] = '사용안함';
}
$rows[] = [
'title' => $agreement['title'] ?? '',
'tooltipSeq' => $agreement['tooltipSeq'] ?? '',
'lines' => $lines,
];
}
 
return $rows;
}
 
/**
* 광고성 수신 동의(이메일/모바일 메시지) 표시용 라인 데이터 생성. 채널 사용여부 + 수신 flag + 최종 수신동의일(es_memberHistory 파생)로
* 동의함(초까지)/동의안함(날짜까지) 라벨을 조립한다 (HTML 미생성 — 마크업은 뷰가 담당).
*
* @param array $advertisingReceive gd_policy('member.advertisingReceive') — email.use / mobile.use ('y'/'n')
* @param array $data 회원 데이터 (maillingFl, smsFl, lastReceiveAgreementDt[mail|sms], entryDt)
* @return array 표시 문자열 배열. 채널 모두 미사용이면 ['사용안함']
*/
public static function buildAdvertisingReceiveLines(array $advertisingReceive, array $data): array
{
$mailUse = (($advertisingReceive['email']['use'] ?? 'n') === 'y');
$smsUse = (($advertisingReceive['mobile']['use'] ?? 'n') === 'y');
if (!$mailUse && !$smsUse) {
return ['사용안함'];
}
 
$channels = [];
if ($mailUse) {
$channels[] = ['label' => '이메일', 'flag' => $data['maillingFl'] ?? 'n', 'dt' => $data['lastReceiveAgreementDt']['mail'] ?? ($data['entryDt'] ?? '')];
}
if ($smsUse) {
$channels[] = ['label' => '모바일 메시지', 'flag' => $data['smsFl'] ?? 'n', 'dt' => $data['lastReceiveAgreementDt']['sms'] ?? ($data['entryDt'] ?? '')];
}
 
$lines = [];
foreach ($channels as $channel) {
// 동의: 년월일시분초까지 / 미동의: 년월일까지 (일시 없으면 괄호 생략)
if ($channel['flag'] === 'y') {
$label = '동의함' . ($channel['dt'] !== '' ? '(' . DateTimeUtils::dateFormat('Y-m-d H:i:s', $channel['dt']) . ')' : '');
} else {
$label = '동의안함' . ($channel['dt'] !== '' ? '(' . DateTimeUtils::dateFormat('Y-m-d', $channel['dt']) . ')' : '');
}
$lines[] = '- ' . $channel['label'] . ' : ' . $label;
}
 
return $lines;
}
 
/**
* 비밀번호 입력 필드 placeholder 문구 생성 (회원 등록/수정 공통)
*
* @param array $joinField 회원가입 항목 정책 (passwordCombineFl, memPw.minlen/maxlen)
* @return string 조합 규칙 + 길이 안내 문구
*/
public static function buildPasswordPlaceholder(array $joinField): string
{
$combine = match ($joinField['passwordCombineFl'] ?? '') {
'engNum' => '영문대문자/영문소문자/숫자 중 2개 이상 포함',
'engNumEtc' => '영문대문자/영문소문자/숫자/특수문자 중 3개 이상 포함',
default => '영문대문자/영문소문자 or 숫자',
};
 
return $combine . ', ' . $joinField['memPw']['minlen'] . '~' . $joinField['memPw']['maxlen'] . '자';
}
 
/**
* 로그인 되었는 지를 체크 (회원/비회원)
*
$data['agreementInfoFl'] = gd_isset($arrData['agreementInfoFl'], 'n');
$data['privateApprovalFl'] = gd_isset($arrData['privateApprovalFl'], 'n');
$data['under14ConsentFl'] = gd_isset($arrData['under14ConsentFl'], 'n');
// 광고성 수신 동의 (이메일 수신 동의 / 모바일 메시지 수신 동의)
$data['maillingFl'] = gd_isset($arrData['maillingFl'], 'n');
$data['smsFl'] = gd_isset($arrData['smsFl'], 'n');
//이용약관 체크값 설정
$myPage = \App::load('\\Component\\Member\\MyPage');
$data['privateApprovalOptionFl'] = gd_isset($agreementData['privateApprovalOptionFl']);
$data['privateOfferFl'] = gd_isset($agreementData['privateOfferFl']);
$data['privateConsignFl'] = gd_isset($agreementData['privateConsignFl']);
// 마케팅 목적 개인정보 수집 및 이용 동의
$data['privateMarketingConsentOptionFl'] = gd_isset($agreementData['privateMarketingConsentOptionFl']);
 
// 인증설정체크해서 데이터 추가 useDataJoinFl
$dreamInfo = gd_policy('member.auth_cellphone');
$params['sexFl'] = $params['sexFl'] ?? '';
$params['maillingFl'] = $params['maillingFl'] ?? '';
$params['smsFl'] = $params['smsFl'] ?? '';
$params['privateMarketingConsentOptionFl'] = $params['privateMarketingConsentOptionFl'] ?? '';
$params['calendarFl'] = $params['calendarFl'] ?? '';
$params['expirationFl'] = $params['expirationFl'] ?? '';
 
$checked['sexFl'][$params['sexFl']] =
$checked['maillingFl'][$params['maillingFl']] =
$checked['smsFl'][$params['smsFl']] =
$checked['privateMarketingConsentOptionFl'][$params['privateMarketingConsentOptionFl']] =
$checked['calendarFl'][$params['calendarFl']] =
$checked['expirationFl'][$params['expirationFl']] = 'checked="checked"';
 
 
Controller/Admin/Member/MemberModifyController.php+34-4
변경 전MemberModifyController.php
 
use Framework\Debug\Exception\AlertBackException;
use Framework\Utility\DateTimeUtils;
 
use Framework\Utility\StringUtils;
 
/**
$memberData = [];
$memberService->getMemberDataWithChecked($memNo, $memberData, $checked);
$memberData = gd_array_merge($memberData, $memberService->getLastAgreementNotificationByMember($memNo));
$privateApprovalOption = $buyerInformService->getInformDataArray(\Component\Agreement\BuyerInformCode::PRIVATE_APPROVAL_OPTION, 'sno,informNm,content', true, $memberData['mallSno']);
$privateOffer = $buyerInformService->getInformDataArray(\Component\Agreement\BuyerInformCode::PRIVATE_OFFER, 'sno,informNm,content', true, $memberData['mallSno']);
$privateConsign = $buyerInformService->getInformDataArray(\Component\Agreement\BuyerInformCode::PRIVATE_CONSIGN, 'sno,informNm,content', true, $memberData['mallSno']);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$memberData = gd_array_merge($memberData, $historyService->getLastReceiveAgreementByMember($memNo));
 
 
$memberHistory = $historyService->getMemberHistory($memNo, $request->get()->get('page', 1), $request->get()->get('pageNum', 10));
$memberData['recommendCnt'] = $memberService->getRecommendCount($memberData['memId']);
if ($memberData['rncheck'] == 'realname') {
$additionalCertification[$key]['file'] = $companyCertification->getCertificationByMemNoAndType($memNo, $key);
}
 
 
 
$this->setData('mode', 'modify');
$this->setData('joinField', \Component\Member\Util\MemberUtil::getJoinField($memberData['mallSno']));
 
 
 
$this->setData('htmlExtra', \Component\Member\Util\MemberUtil::makeExtraField($memberData));
$this->setData('data', $memberData);
$this->setData('checked', $checked);
$this->setData('privateApprovalOption', $privateApprovalOption);
$this->setData('privateOffer', $privateOffer);
$this->setData('privateConsign', $privateConsign);
 
 
 
 
 
$this->setData('groupCouponConditionManual', gd_policy('member.group')['couponConditionManual']);
$this->setData('hidePasswordBlock', $hidePasswordBlock);
$this->setData('certification', $certification);
 
변경 후MemberModifyController.php
 
use Framework\Debug\Exception\AlertBackException;
use Framework\Utility\DateTimeUtils;
use Framework\Utility\SkinUtils;
use Framework\Utility\StringUtils;
 
/**
$memberData = [];
$memberService->getMemberDataWithChecked($memNo, $memberData, $checked);
$memberData = gd_array_merge($memberData, $memberService->getLastAgreementNotificationByMember($memNo));
$privateApprovalOption = $buyerInformService->getInformDataArray(\Component\Agreement\BuyerInformCode::PRIVATE_APPROVAL_OPTION, 'sno,informNm,content,modeFl', true, $memberData['mallSno']);
$privateOffer = $buyerInformService->getInformDataArray(\Component\Agreement\BuyerInformCode::PRIVATE_OFFER, 'sno,informNm,content,modeFl', true, $memberData['mallSno']);
$privateConsign = $buyerInformService->getInformDataArray(\Component\Agreement\BuyerInformCode::PRIVATE_CONSIGN, 'sno,informNm,content,modeFl', true, $memberData['mallSno']);
$privateMarketingMember = $buyerInformService->getInformDataArray(\Component\Agreement\BuyerInformCode::PRIVATE_MARKETING_MEMBER, 'sno,informNm,content,modeFl', true, $memberData['mallSno']);
$privateMarketingMemberUse = (($privateMarketingMember[0]['modeFl'] ?? 'n') === 'y');
// 개인정보 동의 항목별 마지막 변경 일시(컬럼 공통). 이력 없으면 가입일(entryDt) 폴백
$privateAgreementDt = $historyService->getLastPrivateAgreementDateByMember((int) $memNo);
$memberEntryDt = $memberData['entryDt'] ?? '';
// 약관 동의 표시 행 조립 (개인정보 항목별/마케팅). 표시용 데이터 변환은 MemberUtil::buildPrivateAgreementRows
$privateAgreements = [
['title' => '개인정보 수집&middot;이용', 'tooltipSeq' => '036', 'use' => is_array($memberData['privateApprovalOptionFl'] ?? null), 'informs' => $privateApprovalOption, 'flags' => $memberData['privateApprovalOptionFl'] ?? [], 'denyWhenMissing' => false, 'agreementDt' => $privateAgreementDt['privateApprovalOptionFl'] ?? $memberEntryDt],
['title' => '개인정보 취급위탁', 'tooltipSeq' => '037', 'use' => is_array($memberData['privateConsignFl'] ?? null), 'informs' => $privateConsign, 'flags' => $memberData['privateConsignFl'] ?? [], 'denyWhenMissing' => false, 'agreementDt' => $privateAgreementDt['privateConsignFl'] ?? $memberEntryDt],
['title' => '개인정보 제3자 제공', 'tooltipSeq' => '038', 'use' => is_array($memberData['privateOfferFl'] ?? null), 'informs' => $privateOffer, 'flags' => $memberData['privateOfferFl'] ?? [], 'denyWhenMissing' => false, 'agreementDt' => $privateAgreementDt['privateOfferFl'] ?? $memberEntryDt],
['title' => '마케팅 목적 개인정보 수집 및 이용 동의', 'tooltipSeq' => '039', 'use' => $privateMarketingMemberUse, 'informs' => $privateMarketingMember, 'flags' => $memberData['privateMarketingConsentOptionFl'] ?? '', 'denyWhenMissing' => true, 'agreementDt' => $privateAgreementDt['privateMarketingConsentOptionFl'] ?? $memberEntryDt],
];
$advertisingReceiveChannelUse = $buyerInformService->getAdvertisingReceiveChannelUse($memberData['mallSno']);
$advertisingReceive = [
'email' => ['use' => $advertisingReceiveChannelUse['mail']],
'mobile' => ['use' => $advertisingReceiveChannelUse['sms']],
];
$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') {
$additionalCertification[$key]['file'] = $companyCertification->getCertificationByMemNoAndType($memNo, $key);
}
 
// NCDS 템플릿 설정
$this->setData('adminBodySubClass', 'ncds');
$this->setData('mode', 'modify');
$this->setData('callPrefixList', SkinUtils::getCallPrefixList());
$joinField = \Component\Member\Util\MemberUtil::getJoinField($memberData['mallSno']);
$this->setData('joinField', $joinField);
$this->setData('passwordPlaceholder', \Component\Member\Util\MemberUtil::buildPasswordPlaceholder($joinField));
$this->setData('htmlExtra', \Component\Member\Util\MemberUtil::makeExtraField($memberData));
$this->setData('data', $memberData);
$this->setData('checked', $checked);
$this->setData('privateApprovalOption', $privateApprovalOption);
$this->setData('privateOffer', $privateOffer);
$this->setData('privateConsign', $privateConsign);
$this->setData('privateMarketingMember', $privateMarketingMember);
$this->setData('privateMarketingMemberUse', $privateMarketingMemberUse);
$this->setData('privateAgreementRows', \Component\Member\Util\MemberUtil::buildPrivateAgreementRows($privateAgreements));
$this->setData('advertisingReceive', $advertisingReceive);
$this->setData('advertisingReceiveLines', \Component\Member\Util\MemberUtil::buildAdvertisingReceiveLines($advertisingReceive, $memberData));
$this->setData('groupCouponConditionManual', gd_policy('member.group')['couponConditionManual']);
$this->setData('hidePasswordBlock', $hidePasswordBlock);
$this->setData('certification', $certification);
 
Controller/Admin/Member/MemberPsController.php+26
변경 전MemberPsController.php
use Framework\Debug\Exception\LayerException;
use Origin\Service\Policy\Member\JoinPolicyService;
use Origin\Service\Member\KakaoSync\KakaoSyncSettingService;
 
 
 
/**
* Class 회원 처리
 
switch ($requestAllParams['mode']) {
case 'register':
 
 
 
 
$memberService->register(new MemberVO($requestPostParams));
$this->json(__("회원등록을 성공하였습니다."));
break;
case 'modify':
 
 
 
 
$memberAdminService->modifyMemberData($requestAllParams);
$this->json(__('저장이 완료되었습니다.'));
break;
throw new Exception(__('요청을 처리할 페이지를 찾을 수 없습니다.'), 404);
break;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
} catch (Exception $e) {
if ($request->isAjax() === true) {
switch ($e->getCode()) {
 
변경 후MemberPsController.php
use Framework\Debug\Exception\LayerException;
use Origin\Service\Policy\Member\JoinPolicyService;
use Origin\Service\Member\KakaoSync\KakaoSyncSettingService;
use Respect\Validation\Exceptions\NestedValidationException;
use Respect\Validation\Validator;
 
/**
* Class 회원 처리
 
switch ($requestAllParams['mode']) {
case 'register':
// 마케팅 목적 개인정보 수집 및 이용 동의 형식 검증
Validator::arrayVal()
->key('privateMarketingConsentOptionFl', Validator::in(['y', 'n']), false)
->assert($requestAllParams);
$memberService->register(new MemberVO($requestPostParams));
$this->json(__("회원등록을 성공하였습니다."));
break;
case 'modify':
// 마케팅 목적 개인정보 수집 및 이용 동의 형식 검증
Validator::arrayVal()
->key('privateMarketingConsentOptionFl', Validator::in(['y', 'n']), false)
->assert($requestAllParams);
$memberAdminService->modifyMemberData($requestAllParams);
$this->json(__('저장이 완료되었습니다.'));
break;
throw new Exception(__('요청을 처리할 페이지를 찾을 수 없습니다.'), 404);
break;
}
} catch (NestedValidationException $e) {
\Logger::channel('member')->warning('member.validate.fail', [
'mode' => $requestAllParams['mode'] ?? '',
'errors' => $e->getMessages(),
]);
if ($request->isAjax()) {
$this->json(
data: [
'success' => false,
'message' => __('잘못된 요청입니다.'),
'data' => []
],
);
} else {
throw new LayerException(__('잘못된 요청입니다.'), $e->getCode(), $e, null, 3000);
}
} catch (Exception $e) {
if ($request->isAjax() === true) {
switch ($e->getCode()) {
 
Controller/Admin/Member/MemberRegisterController.php+33
변경 전MemberRegisterController.php
use Component\Member\Group\Util as GroupUtil;
use Component\Member\MemberVO;
use Component\Member\Util\MemberUtil;
 
 
/**
* Class 회원 등록
$companyCertification = \App::getInstance(\Component\Member\Company\CompanyCertification::class);
$certificationMaxFileSize = $companyCertification->getCompanyCertificationFileSize();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$this->setData('mode', 'register');
 
$this->setData('joinField', $joinField);
 
$this->setData('htmlExtra', MemberUtil::makeExtraField());
 
 
 
 
$this->setData('data', $member);
$this->setData('checked', $vo->toChecked());
$this->setData('certificationMaxFileSize', $certificationMaxFileSize);
 
변경 후MemberRegisterController.php
use Component\Member\Group\Util as GroupUtil;
use Component\Member\MemberVO;
use Component\Member\Util\MemberUtil;
use Framework\Utility\SkinUtils;
 
/**
* Class 회원 등록
$companyCertification = \App::getInstance(\Component\Member\Company\CompanyCertification::class);
$certificationMaxFileSize = $companyCertification->getCompanyCertificationFileSize();
 
// 신규 회원은 동의 이력이 없으므로 전부 '사용안함'
$privateAgreements = [
['title' => '개인정보 수집&middot;이용', 'use' => false],
['title' => '개인정보 취급위탁', 'use' => false],
['title' => '개인정보 제3자 제공', 'use' => false],
['title' => '마케팅 목적 개인정보 수집 및 이용 동의', 'use' => false],
];
 
$buyerInformService = \App::getInstance('BuyerInform');
if (!is_object($buyerInformService)) {
$buyerInformService = new \Component\Agreement\BuyerInform();
}
$mallSno = (int) (\Component\Mall\Mall::getSession('sno') ?: DEFAULT_MALL_NUMBER);
 
// 신규 회원은 동의 이력이 없으므로 광고성 수신 동의도 '사용안함' (몰 설정과 무관)
$advertisingReceive = [
'email' => ['use' => 'n'],
'mobile' => ['use' => 'n'],
];
 
// 마케팅 목적 개인정보 수집 및 이용 동의 약관 사용 여부 (약관 사용 시에만 노출)
$privateMarketingMember = $buyerInformService->getInformDataArray(\Component\Agreement\BuyerInformCode::PRIVATE_MARKETING_MEMBER, 'sno,informNm,content,modeFl', true, $mallSno);
$privateMarketingMemberUse = (($privateMarketingMember[0]['modeFl'] ?? 'n') === 'y');
 
// NCDS 템플릿 설정
$this->setData('adminBodySubClass', 'ncds');
$this->setData('mode', 'register');
$this->setData('callPrefixList', SkinUtils::getCallPrefixList());
$this->setData('joinField', $joinField);
$this->setData('passwordPlaceholder', MemberUtil::buildPasswordPlaceholder($joinField));
$this->setData('htmlExtra', MemberUtil::makeExtraField());
$this->setData('privateAgreementRows', MemberUtil::buildPrivateAgreementRows($privateAgreements));
$this->setData('advertisingReceive', $advertisingReceive);
$this->setData('advertisingReceiveLines', MemberUtil::buildAdvertisingReceiveLines($advertisingReceive, $member));
$this->setData('privateMarketingMemberUse', $privateMarketingMemberUse);
$this->setData('data', $member);
$this->setData('checked', $vo->toChecked());
$this->setData('certificationMaxFileSize', $certificationMaxFileSize);
 
Controller/Admin/Policy/BaseAgreementWithPrivateController.php+12
변경 전BaseAgreementWithPrivateController.php
$privateApprovalTableOption = $agreementWithPrivateUtil->getTableByPrivateApprovalOption($mallSno);
$privateConsignTable = $agreementWithPrivateUtil->getTableByPrivateConsign($mallSno);
$privateOfferTable = $agreementWithPrivateUtil->getTableByPrivateOffer($mallSno);
 
 
 
 
 
 
$privateGuestOrder = $buyerInform->getInformData(BuyerInformCode::PRIVATE_GUEST_ORDER, $mallSno);
$privateGuestBoardWrite = $buyerInform->getInformData(BuyerInformCode::PRIVATE_GUEST_BOARD_WRITE, $mallSno);
$privateGuestCommentWrite = $buyerInform->getInformData(BuyerInformCode::PRIVATE_GUEST_COMMENT_WRITE, $mallSno);
$this->setData('privateApprovalTableOption', $privateApprovalTableOption);
$this->setData('privateConsignTable', $privateConsignTable);
$this->setData('privateOfferTable', $privateOfferTable);
 
 
 
 
 
 
$this->setData('privateGuestOrderContent', $privateGuestOrder['content']);
$this->setData('privateGuestBoardWriteContent', $privateGuestBoardWrite['content']);
$this->setData('privateGuestCommentWriteContent', $privateGuestCommentWrite['content']);
 
변경 후BaseAgreementWithPrivateController.php
$privateApprovalTableOption = $agreementWithPrivateUtil->getTableByPrivateApprovalOption($mallSno);
$privateConsignTable = $agreementWithPrivateUtil->getTableByPrivateConsign($mallSno);
$privateOfferTable = $agreementWithPrivateUtil->getTableByPrivateOffer($mallSno);
$privateMarketingMember = $buyerInform->getInformData(BuyerInformCode::PRIVATE_MARKETING_MEMBER, $mallSno);
$advertisingReceive = $buyerInform->getInformData(BuyerInformCode::ADVERTISING_RECEIVE, $mallSno);
//--- 광고성 수신 동의 채널 사용여부 (es_config member.advertisingReceive) 및 회원가입 항목(이메일/휴대폰번호) 사용여부
$advertisingReceiveChannel = gd_policy('member.advertisingReceive', $mallSno);
$joinItemPolicy = \App::load('Component\\Policy\\JoinItemPolicy');
$joinItem = $joinItemPolicy->getPolicy($mallSno);
$privateGuestOrder = $buyerInform->getInformData(BuyerInformCode::PRIVATE_GUEST_ORDER, $mallSno);
$privateGuestBoardWrite = $buyerInform->getInformData(BuyerInformCode::PRIVATE_GUEST_BOARD_WRITE, $mallSno);
$privateGuestCommentWrite = $buyerInform->getInformData(BuyerInformCode::PRIVATE_GUEST_COMMENT_WRITE, $mallSno);
$this->setData('privateApprovalTableOption', $privateApprovalTableOption);
$this->setData('privateConsignTable', $privateConsignTable);
$this->setData('privateOfferTable', $privateOfferTable);
$this->setData('privateMarketingMember', $privateMarketingMember);
$this->setData('advertisingReceive', $advertisingReceive);
$this->setData('advertisingReceiveMailUse', $advertisingReceiveChannel['email']['use'] ?? 'n');
$this->setData('advertisingReceiveSmsUse', $advertisingReceiveChannel['mobile']['use'] ?? 'n');
$this->setData('joinItemEmailUse', $joinItem['email']['use'] ?? 'n');
$this->setData('joinItemCellPhoneUse', $joinItem['cellPhone']['use'] ?? 'n');
$this->setData('privateGuestOrderContent', $privateGuestOrder['content']);
$this->setData('privateGuestBoardWriteContent', $privateGuestBoardWrite['content']);
$this->setData('privateGuestCommentWriteContent', $privateGuestCommentWrite['content']);
 
Controller/Admin/Policy/BaseAgreementWithPrivatePsController.php+35-2
변경 전BaseAgreementWithPrivatePsController.php
use Framework\Debug\Exception\LayerException;
use Origin\Service\Member\KakaoSync\KakaoSyncSettingService;
use Request;
 
 
 
/**
* Class 약관/개인정보설정 처리 페이지
break;
// --- 개인정보수집 동의항목 설정
case 'privateItem':
 
 
 
 
 
 
 
 
\DB::begin_tran();
 
 
 
 
 
 
 
$removeSnoArray = json_decode(Request::post()->get('removeSno', []));
$mallSno = gd_isset(Request::post()->get('mallSno'), 1);
foreach ($removeSnoArray as $key => $val) {
$buyerInform->savePrivateConsign(Request::post()->all());
$buyerInform->savePrivateOffer(Request::post()->all());
$buyerInform->saveInformData(BuyerInformCode::PRIVATE_MARKETING, Request::post()->get('privateMarketingWriteWrite'));
 
 
 
 
 
 
// 카카오 싱크 update 토필 발행
$kakaoSyncSettingService = \App::getInstance(KakaoSyncSettingService::class);
$kakaoSyncSettingService->syncSettingWhenSaveTerms();
\DB::commit();
 
 
 
 
 
 
 
 
$this->json(
[
'message' => __('저장이 완료되었습니다.'),
'code' => 200,
]
);
throw new Exception(__('요청을 처리할 페이지를 찾을 수 없습니다.'), 404);
break;
}
 
 
 
} catch (Exception $e) {
\DB::rollback();
if (Request::isAjax()) {
 
변경 후BaseAgreementWithPrivatePsController.php
use Framework\Debug\Exception\LayerException;
use Origin\Service\Member\KakaoSync\KakaoSyncSettingService;
use Request;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Validator;
 
/**
* Class 약관/개인정보설정 처리 페이지
break;
// --- 개인정보수집 동의항목 설정
case 'privateItem':
// post 유효성 검사
Validator::arrayVal()
->key('privateMarketingMemberFl', Validator::in(['y', 'n']), false)
->key('privateMarketingMemberNoticePopupFl', Validator::in(['y', 'n']), false)
->key('advertisingReceiveMailFl', Validator::in(['y', 'n']), false)
->key('advertisingReceiveSmsFl', Validator::in(['y', 'n']), false)
->assert(Request::post()->all());
 
\DB::begin_tran();
 
// [선택] 광고성 수신 동의 채널을 사용하면서 [선택] 마케팅 목적 개인정보 수집 및 이용 동의를 사용하지 않으면 저장 차단
$advertisingUse = Request::post()->get('advertisingReceiveMailFl', 'n') === 'y' || Request::post()->get('advertisingReceiveSmsFl', 'n') === 'y';
if ($advertisingUse && Request::post()->get('privateMarketingMemberFl', 'n') !== 'y') {
throw new Exception(__("[선택] 마케팅 목적 개인정보 수집 및 이용 동의 약관을 '사용'으로 설정해주세요. [선택] 광고성 수신 동의 약관을 사용하기 위해서는 [선택] 마케팅 목적 개인정보 수집 및 이용 동의 약관을 반드시 사용해야 합니다."), 400);
}
 
$removeSnoArray = json_decode(Request::post()->get('removeSno', []));
$mallSno = gd_isset(Request::post()->get('mallSno'), 1);
foreach ($removeSnoArray as $key => $val) {
$buyerInform->savePrivateConsign(Request::post()->all());
$buyerInform->savePrivateOffer(Request::post()->all());
$buyerInform->saveInformData(BuyerInformCode::PRIVATE_MARKETING, Request::post()->get('privateMarketingWriteWrite'));
$buyerInform->saveInformData(BuyerInformCode::PRIVATE_MARKETING_MEMBER, Request::post()->get('privateMarketingMemberWriteWrite'), $mallSno);
$buyerInform->saveInformData(BuyerInformCode::ADVERTISING_RECEIVE, Request::post()->get('advertisingReceiveWriteWrite'), $mallSno);
 
$advertisingReceiveChannels = $buyerInform->resolveAdvertisingReceiveChannels(Request::post()->all(), (int)$mallSno);
$policy->setValue('member.advertisingReceive', $advertisingReceiveChannels, $mallSno);
 
// 카카오 싱크 update 토필 발행
$kakaoSyncSettingService = \App::getInstance(KakaoSyncSettingService::class);
$kakaoSyncSettingService->syncSettingWhenSaveTerms();
 
\DB::commit();
 
// [선택] 마케팅 목적 개인정보 수집 및 이용 동의를 사용하면서 [선택] 광고성 수신 동의를 사용하지 않으면 안내 문구 노출
$message = __('저장이 완료되었습니다.');
$marketingMemberUse = Request::post()->get('privateMarketingMemberFl', 'n') === 'y';
$advertisingUse = Request::post()->get('advertisingReceiveMailFl', 'n') === 'y' || Request::post()->get('advertisingReceiveSmsFl', 'n') === 'y';
if ($marketingMemberUse && !$advertisingUse) {
$message .= ' ' . __('[선택] 마케팅 목적 개인정보 수집 및 이용 동의 약관만 사용하시면 광고성 메시지를 발송할 수 없습니다. 광고성 메시지를 보내시려면 [선택] 광고성 수신 동의 약관도 함께 사용해주세요.');
}
$this->json(
[
'message' => $message,
'code' => 200,
]
);
throw new Exception(__('요청을 처리할 페이지를 찾을 수 없습니다.'), 404);
break;
}
} catch (ValidationException $e) {
\Logger::channel('agreement')->warning(__METHOD__, ['error' => $e->getMessage()]);
$this->json(['error' => ['code' => 400, 'message' => __('잘못된 요청입니다.')]]);
} catch (Exception $e) {
\DB::rollback();
if (Request::isAjax()) {
 
Controller/Admin/Share/MemberCrmDetailController.php+32-5
변경 전MemberCrmDetailController.php
 
$session->set(Member::SESSION_MODIFY_MEMBER_INFO, $memberData);
//@formatter:off
$checked = SkinUtils::setChecked(['memberFl', 'appFl', 'maillingFl', 'smsFl', 'sexFl', 'marriFl', 'expirationFl',], $memberData);
//@formatter:on
$memberData['privateApprovalOptionFl'] = json_decode(stripslashes($memberData['privateApprovalOptionFl']), true);
$memberData['privateOfferFl'] = json_decode(stripslashes($memberData['privateOfferFl']), true);
 
/** @var \Bundle\Component\Agreement\BuyerInform $buyerInform */
$buyerInform = App::load('\\Component\\Agreement\\BuyerInform');
$privateApprovalOption = $buyerInform->getInformDataArray(BuyerInformCode::PRIVATE_APPROVAL_OPTION, 'sno,informNm,content', true, $memberData['mallSno']);
$privateOffer = $buyerInform->getInformDataArray(BuyerInformCode::PRIVATE_OFFER, 'sno,informNm,content', true, $memberData['mallSno']);
$privateConsign = $buyerInform->getInformDataArray(BuyerInformCode::PRIVATE_CONSIGN, 'sno,informNm,content', true, $memberData['mallSno']);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
// 하이픈 제거
$memberData['fax'] = str_replace("-", "", $memberData['fax']);
$this->setData('memberData', $memberData);
$this->setData('mode', 'modify');
$this->setData('modeTxt', '회원수정');
$this->setData('joinField', MemberUtil::getJoinField($memberData['mallSno']));
 
 
$this->setData('htmlExtra', MemberUtil::makeExtraField($memberData));
 
$this->setData('data', $memberData);
$this->setData('checked', $checked);
$this->setData('history', $memberHistory['data']);
$this->setData('privateApprovalOption', $privateApprovalOption);
$this->setData('privateOffer', $privateOffer);
$this->setData('privateConsign', $privateConsign);
 
 
 
 
$this->setData('groupCouponConditionManual', gd_policy('member.group')['couponConditionManual']);
$this->setData('hidePasswordBlock', $hidePasswordBlock);
$this->setData('certification', $certification);
 
변경 후MemberCrmDetailController.php
 
$session->set(Member::SESSION_MODIFY_MEMBER_INFO, $memberData);
//@formatter:off
$checked = SkinUtils::setChecked(['memberFl', 'appFl', 'maillingFl', 'smsFl', 'sexFl', 'marriFl', 'expirationFl', 'privateMarketingConsentOptionFl',], $memberData);
//@formatter:on
$memberData['privateApprovalOptionFl'] = json_decode(stripslashes($memberData['privateApprovalOptionFl']), true);
$memberData['privateOfferFl'] = json_decode(stripslashes($memberData['privateOfferFl']), true);
 
/** @var \Bundle\Component\Agreement\BuyerInform $buyerInform */
$buyerInform = App::load('\\Component\\Agreement\\BuyerInform');
$privateApprovalOption = $buyerInform->getInformDataArray(BuyerInformCode::PRIVATE_APPROVAL_OPTION, 'sno,informNm,content,modeFl', true, $memberData['mallSno']);
$privateOffer = $buyerInform->getInformDataArray(BuyerInformCode::PRIVATE_OFFER, 'sno,informNm,content,modeFl', true, $memberData['mallSno']);
$privateConsign = $buyerInform->getInformDataArray(BuyerInformCode::PRIVATE_CONSIGN, 'sno,informNm,content,modeFl', true, $memberData['mallSno']);
$privateMarketingMember = $buyerInform->getInformDataArray(BuyerInformCode::PRIVATE_MARKETING_MEMBER, 'sno,informNm,content,modeFl', true, $memberData['mallSno']);
$privateMarketingMemberUse = (($privateMarketingMember[0]['modeFl'] ?? 'n') === 'y');
 
// 개인정보 동의 항목별 마지막 변경 일시(컬럼 공통). 이력 없으면 가입일(entryDt) 폴백
$privateAgreementDt = $history->getLastPrivateAgreementDateByMember((int) $memberNo);
$memberEntryDt = $memberData['entryDt'] ?? '';
// 약관 동의 표시 행 조립 (개인정보 항목별/마케팅). 표시용 데이터 변환은 MemberUtil::buildPrivateAgreementRows
$privateAgreements = [
['title' => '개인정보 수집&middot;이용', 'tooltipSeq' => '036', 'use' => is_array($memberData['privateApprovalOptionFl'] ?? null), 'informs' => $privateApprovalOption, 'flags' => $memberData['privateApprovalOptionFl'] ?? [], 'denyWhenMissing' => false, 'agreementDt' => $privateAgreementDt['privateApprovalOptionFl'] ?? $memberEntryDt],
['title' => '개인정보 취급위탁', 'tooltipSeq' => '037', 'use' => is_array($memberData['privateConsignFl'] ?? null), 'informs' => $privateConsign, 'flags' => $memberData['privateConsignFl'] ?? [], 'denyWhenMissing' => false, 'agreementDt' => $privateAgreementDt['privateConsignFl'] ?? $memberEntryDt],
['title' => '개인정보 제3자 제공', 'tooltipSeq' => '038', 'use' => is_array($memberData['privateOfferFl'] ?? null), 'informs' => $privateOffer, 'flags' => $memberData['privateOfferFl'] ?? [], 'denyWhenMissing' => false, 'agreementDt' => $privateAgreementDt['privateOfferFl'] ?? $memberEntryDt],
['title' => '마케팅 목적 개인정보 수집 및 이용 동의', 'tooltipSeq' => '039', 'use' => $privateMarketingMemberUse, 'informs' => $privateMarketingMember, 'flags' => $memberData['privateMarketingConsentOptionFl'] ?? '', 'denyWhenMissing' => true, 'agreementDt' => $privateAgreementDt['privateMarketingConsentOptionFl'] ?? $memberEntryDt],
];
$advertisingReceiveChannelUse = $buyerInform->getAdvertisingReceiveChannelUse($memberData['mallSno']);
$advertisingReceive = [
'email' => ['use' => $advertisingReceiveChannelUse['mail']],
'mobile' => ['use' => $advertisingReceiveChannelUse['sms']],
];
// 마케팅 목적 개인정보 수집 및 이용 동의/거부일 (없으면 '-')
$memberData['privateMktAgreementDt'] = $memberData['lastReceiveAgreementDt']['privateMkt'] ?? '-';
 
// 하이픈 제거
$memberData['fax'] = str_replace("-", "", $memberData['fax']);
$this->setData('memberData', $memberData);
$this->setData('mode', 'modify');
$this->setData('modeTxt', '회원수정');
$joinField = MemberUtil::getJoinField($memberData['mallSno']);
$this->setData('joinField', $joinField);
$this->setData('passwordPlaceholder', MemberUtil::buildPasswordPlaceholder($joinField));
$this->setData('htmlExtra', MemberUtil::makeExtraField($memberData));
$this->setData('privateMarketingMemberUse', $privateMarketingMemberUse);
$this->setData('data', $memberData);
$this->setData('checked', $checked);
$this->setData('history', $memberHistory['data']);
$this->setData('privateApprovalOption', $privateApprovalOption);
$this->setData('privateOffer', $privateOffer);
$this->setData('privateConsign', $privateConsign);
$this->setData('privateMarketingMember', $privateMarketingMember);
$this->setData('privateAgreementRows', MemberUtil::buildPrivateAgreementRows($privateAgreements));
$this->setData('advertisingReceive', $advertisingReceive);
$this->setData('advertisingReceiveLines', MemberUtil::buildAdvertisingReceiveLines($advertisingReceive, $memberData));
$this->setData('groupCouponConditionManual', gd_policy('member.group')['couponConditionManual']);
$this->setData('hidePasswordBlock', $hidePasswordBlock);
$this->setData('certification', $certification);
 
Controller/Front/Member/JoinAgreementController.php+9
변경 전JoinAgreementController.php
$privateApprovalOption = $inform->getInformDataArray(BuyerInformCode::PRIVATE_APPROVAL_OPTION);
$privateConsign = $inform->getInformDataArray(BuyerInformCode::PRIVATE_CONSIGN);
$privateOffer = $inform->getInformDataArray(BuyerInformCode::PRIVATE_OFFER);
 
 
 
 
$ipinConfig = gd_policy('member.ipin');
$authCellPhoneConfig = gd_get_auth_cellphone_info();
$memberJoinConfig = gd_policy('member.join');
$this->setData('privateApprovalOption', $privateApprovalOption);
$this->setData('privateConsign', $privateConsign);
$this->setData('privateOffer', $privateOffer);
 
 
 
 
 
 
 
// debug($memberJoinConfig);
 
변경 후JoinAgreementController.php
$privateApprovalOption = $inform->getInformDataArray(BuyerInformCode::PRIVATE_APPROVAL_OPTION);
$privateConsign = $inform->getInformDataArray(BuyerInformCode::PRIVATE_CONSIGN);
$privateOffer = $inform->getInformDataArray(BuyerInformCode::PRIVATE_OFFER);
// 마케팅 목적 개인정보 수집 및 이용 동의 / 광고성 수신 동의
$privateMarketingMember = $inform->getInformData(BuyerInformCode::PRIVATE_MARKETING_MEMBER);
$advertisingReceive = $inform->getAdvertisingReceiveContentWithReplaceCode();
$advertisingReceiveChannelUse = $inform->getAdvertisingReceiveChannelUse();
$ipinConfig = gd_policy('member.ipin');
$authCellPhoneConfig = gd_get_auth_cellphone_info();
$memberJoinConfig = gd_policy('member.join');
$this->setData('privateApprovalOption', $privateApprovalOption);
$this->setData('privateConsign', $privateConsign);
$this->setData('privateOffer', $privateOffer);
$this->setData('privateMarketingMember', $privateMarketingMember);
$this->setData('advertisingReceive', $advertisingReceive);
$this->setData('advertisingReceiveMailUse', $advertisingReceiveChannelUse['mail']);
$this->setData('advertisingReceiveSmsUse', $advertisingReceiveChannelUse['sms']);
$this->setData('isUseAdvertisingReceive', $inform->isUseAdvertisingReceive());
 
 
// debug($memberJoinConfig);
 
Controller/Front/Member/LoginPsController.php+7
변경 전LoginPsController.php
\DB::rollback();
$logger->info(__METHOD__ . ', ' . $e->getFile() . '[' . $e->getLine() . '], ' . $e->getMessage());
}
 
 
 
 
 
 
 
}
if($message){
throw new AlertRedirectException($message, 0, null, $returnUrl);
 
변경 후LoginPsController.php
\DB::rollback();
$logger->info(__METHOD__ . ', ' . $e->getFile() . '[' . $e->getLine() . '], ' . $e->getMessage());
}
 
// 마케팅 목적 개인정보 수집 및 이용 동의 안내 (미동의 회원 로그인 시 계정당 1회, 확인 후 마이페이지 이동)
// my_page.php는 진입 시 비밀번호 재확인을 요구하므로 게이트인 my_page_password.php로 직접 보낸다(이중 alert/리다이렉트 방지).
if (empty($message) && $member->markPrivateMarketingConsentNoticeShown()) {
$message = __('마케팅 이메일 및 모바일 메시지 수신을 위해 개인정보 수집 및 이용에 동의해 주세요.');
$returnUrl = '../mypage/my_page_password.php';
}
}
if($message){
throw new AlertRedirectException($message, 0, null, $returnUrl);
 
Controller/Front/Mypage/MyPageController.php+8
변경 전MyPageController.php
$this->setData('privateApprovalOption', $inform->getInformDataArray(BuyerInformCode::PRIVATE_APPROVAL_OPTION));
$this->setData('privateOffer', $inform->getInformDataArray(BuyerInformCode::PRIVATE_OFFER));
$this->setData('privateConsign', $inform->getInformDataArray(BuyerInformCode::PRIVATE_CONSIGN));
 
 
 
 
 
 
 
 
$this->setData('snsConnection', json_encode($snsConnection));
$this->setData('usePaycoLogin', $paycoPolicy->usePaycoLogin());
$this->setData('useNaverLogin', $naverPolicy->useNaverLogin());
 
변경 후MyPageController.php
$this->setData('privateApprovalOption', $inform->getInformDataArray(BuyerInformCode::PRIVATE_APPROVAL_OPTION));
$this->setData('privateOffer', $inform->getInformDataArray(BuyerInformCode::PRIVATE_OFFER));
$this->setData('privateConsign', $inform->getInformDataArray(BuyerInformCode::PRIVATE_CONSIGN));
$this->setData('privateMktApprovalOption', $inform->getInformDataArray(BuyerInformCode::PRIVATE_MARKETING_MEMBER));
$this->setData('advertisingReceive', $inform->getAdvertisingReceiveContentWithReplaceCode((int)$mallSno));
// 광고성 수신 동의 채널(이메일/모바일) 노출 여부
$advertisingReceiveChannelUse = $inform->getAdvertisingReceiveChannelUse((int)$mallSno);
$this->setData('advertisingReceiveMailUse', $advertisingReceiveChannelUse['mail']);
$this->setData('advertisingReceiveSmsUse', $advertisingReceiveChannelUse['sms']);
// 광고성 수신 동의 블록 노출 = 채널 하나라도 사용 (es_buyerInform modeFl 아님)
$this->setData('isUseAdvertisingReceive', $inform->isUseAdvertisingReceive((int)$mallSno));
$this->setData('snsConnection', json_encode($snsConnection));
$this->setData('usePaycoLogin', $paycoPolicy->usePaycoLogin());
$this->setData('useNaverLogin', $naverPolicy->useNaverLogin());
 
Controller/Front/Service/AgreementController.php+6
변경 전AgreementController.php
case BuyerInformCode::PRIVATE_APPROVAL:
$terms = $inform->getInformData(BuyerInformCode::PRIVATE_APPROVAL);
break;
 
 
 
 
 
 
case BuyerInformCode::BASE_PRIVATE:
$terms = $inform->getPrivateWithReplaceCode(BuyerInformCode::BASE_PRIVATE);
break;
 
변경 후AgreementController.php
case BuyerInformCode::PRIVATE_APPROVAL:
$terms = $inform->getInformData(BuyerInformCode::PRIVATE_APPROVAL);
break;
case BuyerInformCode::PRIVATE_MARKETING_MEMBER:
$terms = $inform->getInformData(BuyerInformCode::PRIVATE_MARKETING_MEMBER);
break;
case BuyerInformCode::ADVERTISING_RECEIVE:
$terms = $inform->getAdvertisingReceiveContentWithReplaceCode();
break;
case BuyerInformCode::BASE_PRIVATE:
$terms = $inform->getPrivateWithReplaceCode(BuyerInformCode::BASE_PRIVATE);
break;
 
data/skin/front/moment/member/join_agreement.html+176-1
변경 전join_agreement.html
</div>
<!-- {/} -->
<!-- //join_agreement_box -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<div class="important_check_box">
<strong class="important_check dn">{=__('이용약관과 개인정보 수집 및 이용에 대한 안내 모두 동의해주세요.')}</strong>
</div>
$('.js_terms_view :checkbox', $formTerms).change(function (e) {
$('strong.important_check').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.js_terms_view');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
if (isTargetChecked) {
if (isTermsAgreeSelect) {
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/*
* 14세 동의 항목 체크박스 이벤트
*/
}
});
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
});
</script>
{ # footer }
 
변경 후join_agreement.html
</div>
<!-- {/} -->
<!-- //join_agreement_box -->
 
<!-- {? privateMarketingMember['modeFl']=='y'} -->
<div class="join_agreement_box js_terms_view">
<div class="form_element">
<input type="checkbox" id="termsAgree6" name=""/>
<label class="check_s" for="termsAgree6"><strong class="choice">({=__('선택')})</strong> {=__('마케팅 목적 개인정보 수집 및 이용 동의')} </label>
<span><a href="../service/agreement.php?code={privateMarketingMember['informCd']}" target="_blank">{=__('전체보기')}</a></span>
</div>
<div class="agreement_choice_box">
<table summary="{=__('마케팅 목적 개인정보 수집 및 이용 동의 내용입니다.')}" border="0" cellpadding="0" cellspacing="0">
<colgroup>
<col width="10px">
<col width="300px">
</colgroup>
<thead>
<tr>
<th>{=__('선택')}</th>
<th>{=nl2br(privateMarketingMember['informNm'])}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="form_element">
<input type="checkbox" id="privateMktApprovalOption" name="privateMarketingConsentOptionFl"/>
<label class="check_s" for="privateMktApprovalOption">{=__('선택')}</label>
</div>
</td>
<td>{=nl2br(privateMarketingMember['content'])}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- {/} -->
<!-- //join_agreement_box -->
 
<!-- {? isUseAdvertisingReceive} -->
<div class="join_agreement_box js_terms_view">
<div class="form_element">
<input type="checkbox" id="termsAgree7" name=""/>
<label class="check_s" for="termsAgree7"><strong class="choice">({=__('선택')})</strong> {=__('광고성 수신 동의')} </label>
<span><a href="../service/agreement.php?code={advertisingReceive['informCd']}" target="_blank">{=__('전체보기')}</a></span>
</div>
<div class="agreement_choice_box">
<table summary="{=__('광고성 수신 동의 내용입니다.')}" border="0" cellpadding="0" cellspacing="0">
<colgroup>
<col width="10px">
<col width="300px">
</colgroup>
<thead>
<tr>
<th>{=__('선택')}</th>
<th>{=nl2br(advertisingReceive['informNm'])}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="form_element">
<input type="checkbox" id="advertisingReceiveAll" name=""/>
<label class="check_s" for="advertisingReceiveAll">{=__('선택')}</label>
</div>
</td>
<td>{=nl2br(advertisingReceive['content'])}</td>
</tr>
</tbody>
</table>
<div class="js_advertising_channel_box" style="display:flex; flex-direction:column; align-items:flex-start; margin:10px;">
<!-- {? advertisingReceiveMailUse=='y'} -->
<div class="form_element">
<input type="checkbox" id="maillingFl" name="maillingFl" class="js_advertising_channel"/>
<label class="check_s" for="maillingFl">{=__('이메일 수신 동의')}</label>
</div>
<!-- {/} -->
<!-- {? advertisingReceiveSmsUse=='y'} -->
<div class="form_element">
<input type="checkbox" id="smsFl" name="smsFl" class="js_advertising_channel"/>
<label class="check_s" for="smsFl">{=__('모바일 메시지 수신 동의')}</label>
</div>
<!-- {/} -->
</div>
</div>
</div>
<!-- {/} -->
<!-- //join_agreement_box -->
 
<div class="important_check_box">
<strong class="important_check dn">{=__('이용약관과 개인정보 수집 및 이용에 대한 안내 모두 동의해주세요.')}</strong>
</div>
$('.js_terms_view :checkbox', $formTerms).change(function (e) {
$('strong.important_check').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.js_terms_view');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
if (isTargetChecked) {
if (isTermsAgreeSelect) {
}
});
 
/*
* 광고성 수신 동의 ↔ 이메일/모바일 수신 동의 ↔ 마케팅 목적 개인정보 수집 및 이용 동의 연동
* - 광고성 수신 동의 체크 → 이메일/모바일 채널 일괄 + 마케팅 목적 동의 ON
* - 채널(1개 이상) 체크 → 광고성 수신 동의 + 마케팅 목적 동의 ON (채널은 각각 개별 토글)
*/
const checkMarketingApproval = function () {
$('#privateMktApprovalOption', $formTerms).prop('checked', true).val('y').siblings('label').addClass('on');
$('#termsAgree6', $formTerms).prop('checked', true).siblings('label').addClass('on');
};
// 마케팅 목적 개인정보 수집 및 이용 동의 체크 시 광고성 수신 동의·수신 채널 일괄 선택
const checkAdvertising = function () {
if ($('#termsAgree7', $formTerms).length === 0) {
return;
}
$('#termsAgree7', $formTerms).prop('checked', true).siblings('label').addClass('on');
$('#advertisingReceiveAll', $formTerms).prop('checked', true).siblings('label').addClass('on');
$('.js_advertising_channel', $formTerms).prop('checked', true).val('y').siblings('label').addClass('on');
};
$formTerms.on('change', '#termsAgree7', function () {
const isChecked = $(this).prop('checked') === true;
$(this).siblings('label').toggleClass('on', isChecked);
$('#advertisingReceiveAll', $formTerms).prop('checked', isChecked)
.siblings('label').toggleClass('on', isChecked);
$('.js_advertising_channel', $formTerms).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formTerms.on('change', '#advertisingReceiveAll', function () {
const isChecked = $(this).prop('checked') === true;
$('.js_advertising_channel', $formTerms).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
} else {
$('#termsAgree7', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '.js_advertising_channel', function () {
const anyChannelChecked = $('.js_advertising_channel:checked', $formTerms).length > 0;
$('#advertisingReceiveAll', $formTerms).prop('checked', anyChannelChecked)
.siblings('label').toggleClass('on', anyChannelChecked);
if (anyChannelChecked) {
checkMarketingApproval();
}
if ($(this).prop('checked') === false) {
$('#termsAgree7', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '#privateMktApprovalOption', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
} else {
$('#termsAgree6', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '#termsAgree6', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
}
});
 
/*
* 14세 동의 항목 체크박스 이벤트
*/
}
});
}
 
// 그룹 하위 항목이 모두 선택되면 상위 [선택] 헤더(termsAgree3~7)도 선택 (하나라도 해제 시 해제)
$formTerms.on('change', '.js_terms_view .agreement_choice_box :checkbox', function () {
const $termsView = $(this).closest('.js_terms_view');
const $header = $termsView.children('.form_element').children(':checkbox');
const $subs = $termsView.find('.agreement_choice_box :checkbox');
const allChecked = $subs.length > 0 && $subs.not(':checked').length === 0;
$header.prop('checked', allChecked).siblings('label').toggleClass('on', allChecked);
});
 
/*
* 전체 동의 역동기화 — 하위 체크박스가 모두 선택되면 전체 동의도 선택 (하나라도 해제되면 해제)
*/
const syncAllAgree = function () {
const $targets = $(':checkbox', $formTerms).not('#allAgree').not('#termsAgree');
const allChecked = $targets.length > 0 && $targets.not(':checked').length === 0;
$('#allAgree', $formTerms).prop('checked', allChecked)
.siblings('label').toggleClass('on', allChecked);
};
$formTerms.on('change', ':checkbox', function (e) {
if (e.target.id === 'allAgree' || e.target.id === 'termsAgree') {
return;
}
syncAllAgree();
});
});
</script>
{ # footer }
 
data/skin/front/moment/member/_join_view.html-12
변경 전_join_view.html
</select>
</div>
<div class="member_warning js_email"></div>
<!--{? joinField.maillingFl.use === 'y'}-->
<div class="form_element">
<input type="checkbox" id="maillingFl" name="maillingFl" value="y" {? data.maillingFl == 'y'}checked="checked"{/}/>
<label for="maillingFl" class="check_s {? data.maillingFl == 'y'}on{/}">{=__('정보/이벤트 메일 수신에 동의합니다.')}</label>
</div>
<!--{/}-->
</td>
</tr>
<!--{/}-->
<!--{ / }-->
<!--{ / }-->
</div>
<!--{? joinField.smsFl.use === 'y'}-->
<div class="form_element">
<input type="checkbox" id="smsFl" name="smsFl" value="y" {? data.smsFl == 'y'}checked="checked"{/}/>
<label for="smsFl" class="check_s {? data.smsFl == 'y'}on{/}">{=__('정보/이벤트 SMS 수신에 동의합니다.')}</label>
</div>
<!--{/}-->
</td>
</tr>
<!--{/}-->
 
변경 후_join_view.html
</select>
</div>
<div class="member_warning js_email"></div>
 
 
 
 
 
 
</td>
</tr>
<!--{/}-->
<!--{ / }-->
<!--{ / }-->
</div>
 
 
 
 
 
 
</td>
</tr>
<!--{/}-->
 
data/skin/front/moment/member/_join_view_other.html+98-1
변경 전_join_view_other.html
<!--{/}-->
 
<!-- N : 약관동의 시작 -->
<!--{? privateApprovalOption[0]['modeFl'] == 'y' || privateConsign[0]['modeFl']=='y' || privateOffer[0]['modeFl']=='y'}-->
<div class="join_agreement_cont">
<h3>{=__('약관동의현황')}</h3>
<!--{? privateApprovalOption[0]['modeFl'] == 'y'}-->
</div>
<!-- //join_agreement_box -->
<!--{/ // privateOffer modeFl end}-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
</div>
<!-- //join_agreement_cont -->
<!--{ / }-->
 
변경 후_join_view_other.html
<!--{/}-->
 
<!-- N : 약관동의 시작 -->
<!--{? privateApprovalOption[0]['modeFl'] == 'y' || privateConsign[0]['modeFl']=='y' || privateOffer[0]['modeFl']=='y' || privateMktApprovalOption[0]['modeFl']=='y' || isUseAdvertisingReceive}-->
<div class="join_agreement_cont">
<h3>{=__('약관동의현황')}</h3>
<!--{? privateApprovalOption[0]['modeFl'] == 'y'}-->
</div>
<!-- //join_agreement_box -->
<!--{/ // privateOffer modeFl end}-->
 
<!--{? privateMktApprovalOption[0]['modeFl'] == 'y'}-->
<div class="join_agreement_box">
<div class="form_element">
<input type="checkbox" id="termsAgree6"/>
<label for="termsAgree6" class="check_s"><strong class="choice">({=__('선택')})</strong> {=__('마케팅 목적 개인정보 수집 및 이용 동의')} </label>
<span><a href="../service/agreement.php?code={privateMktApprovalOption[0]['informCd']}" target="_blank">{=__('전체보기')}</a></span>
</div>
<!--{@ privateMktApprovalOption}-->
<div class="agreement_choice_box">
<table border="0" cellpadding="0" cellspacing="0" summary="{=__('마케팅 목적 개인정보 수집 및 이용 동의 내용입니다.')}">
<colgroup>
<col width="10px">
<col width="300px">
</colgroup>
<thead>
<tr>
<th>{=__('선택')}</th>
<th>{=nl2br(.informNm)}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="form_element">
<input type="checkbox" id="privateMarketingConsentOptionFl" name="privateMarketingConsentOptionFl" value="y" <!--{? data['privateMarketingConsentOptionFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="privateMarketingConsentOptionFl" class="check_s <!--{? data['privateMarketingConsentOptionFl'] == 'y'}-->on<!--{/}-->">{=__('선택')}</label>
</div>
</td>
<td>
<div class="agreement_scroll">
{=nl2br(.content)}
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!--{/ // privateMktApprovalOption loop end}-->
</div>
<!-- //join_agreement_box -->
<!--{/ // privateMktApprovalOption modeFl end}-->
 
<!--{? isUseAdvertisingReceive}-->
<div class="join_agreement_box js_terms_view">
<div class="form_element">
<input type="checkbox" id="termsAgree7"/>
<label for="termsAgree7" class="check_s"><strong class="choice">({=__('선택')})</strong> {=__('광고성 수신 동의')} </label>
<span><a href="../service/agreement.php?code={advertisingReceive['informCd']}" target="_blank">{=__('전체보기')}</a></span>
</div>
<div class="agreement_choice_box">
<table border="0" cellpadding="0" cellspacing="0" summary="{=__('광고성 수신 동의 내용입니다.')}">
<colgroup>
<col width="10px">
<col width="300px">
</colgroup>
<thead>
<tr>
<th>{=__('선택')}</th>
<th>{=nl2br(advertisingReceive['informNm'])}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="form_element">
<input type="checkbox" id="advertisingReceiveAll" <!--{? data['maillingFl'] == 'y' || data['smsFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="advertisingReceiveAll" class="check_s <!--{? data['maillingFl'] == 'y' || data['smsFl'] == 'y'}-->on<!--{/}-->">{=__('선택')}</label>
</div>
</td>
<td>
<div class="agreement_scroll">
{=nl2br(advertisingReceive['content'])}
</div>
</td>
</tr>
</tbody>
</table>
<div class="js_advertising_channel_box" style="display:flex; flex-direction:column; align-items:flex-start; margin:10px;">
<!--{? advertisingReceiveMailUse == 'y'}-->
<div class="form_element">
<input type="checkbox" id="advertisingReceiveMail" name="maillingFl" value="y" class="js_advertising_channel" <!--{? data['maillingFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="advertisingReceiveMail" class="check_s <!--{? data['maillingFl'] == 'y'}-->on<!--{/}-->">{=__('이메일 수신 동의')}</label>
</div>
<!--{/}-->
<!--{? advertisingReceiveSmsUse == 'y'}-->
<div class="form_element">
<input type="checkbox" id="advertisingReceiveSms" name="smsFl" value="y" class="js_advertising_channel" <!--{? data['smsFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="advertisingReceiveSms" class="check_s <!--{? data['smsFl'] == 'y'}-->on<!--{/}-->">{=__('모바일 메시지 수신 동의')}</label>
</div>
<!--{/}-->
</div>
</div>
<!-- //agreement_choice_box -->
</div>
<!-- //join_agreement_box -->
<!--{/ // isUseAdvertisingReceive end}-->
</div>
<!-- //join_agreement_cont -->
<!--{ / }-->
 
data/skin/front/moment/mypage/my_page.html+101-1
변경 전my_page.html
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.join_agreement_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
if (isTargetChecked) {
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
gd_select_email_domain('email');
<!--{? (joinField.marriFl.use == 'y' && joinField.marriDate.use == 'y') || joinField.birthDt.use == 'y' }-->
 
변경 후my_page.html
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.join_agreement_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
if (isTargetChecked) {
}
});
/*
* 광고성 수신 동의 ↔ 이메일/모바일 수신 동의 ↔ 마케팅 목적 개인정보 수집 및 이용 동의 연동
* - 광고성 수신 동의 체크 → 이메일/모바일 채널 일괄 + 마케팅 목적 동의 ON
* - 채널(1개 이상) 체크 → 광고성 수신 동의 + 마케팅 목적 동의 ON (채널은 각각 개별 토글)
*/
const checkMarketingApproval = function () {
$('#privateMarketingConsentOptionFl', $formJoin).prop('checked', true).val('y').siblings('label').addClass('on');
$('#termsAgree6', $formJoin).prop('checked', true).siblings('label').addClass('on');
};
$formJoin.on('change', '#termsAgree7', function () {
const isChecked = $(this).prop('checked') === true;
$(this).siblings('label').toggleClass('on', isChecked);
$('#advertisingReceiveAll', $formJoin).prop('checked', isChecked)
.siblings('label').toggleClass('on', isChecked);
$('.js_advertising_channel', $formJoin).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formJoin.on('change', '#advertisingReceiveAll', function () {
const isChecked = $(this).prop('checked') === true;
$('.js_advertising_channel', $formJoin).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
// 전체선택(advertisingReceiveAll) 직접 조작 시 광고성 수신 동의(termsAgree7) 헤더도 동기화
$('#termsAgree7', $formJoin).prop('checked', isChecked).siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formJoin.on('change', '.js_advertising_channel', function () {
const anyChannelChecked = $('.js_advertising_channel:checked', $formJoin).length > 0;
const allChannelChecked = $('.js_advertising_channel', $formJoin).length > 0
&& $('.js_advertising_channel:not(:checked)', $formJoin).length === 0;
$('#advertisingReceiveAll', $formJoin).prop('checked', anyChannelChecked)
.siblings('label').toggleClass('on', anyChannelChecked);
if (anyChannelChecked) {
checkMarketingApproval();
}
// 수신 채널 전부 선택 시 광고성 수신 동의(termsAgree7) 헤더 ON (일부만이면 켜지 않음)
if (allChannelChecked) {
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
}
if ($(this).prop('checked') === false) {
$('#termsAgree7', $formJoin).prop('checked', false).siblings('label').removeClass('on');
}
});
// 마케팅 목적 개인정보 수집 및 이용 동의 체크 시 광고성 수신 동의·수신 채널 일괄 선택
const checkAdvertising = function () {
if ($('#termsAgree7', $formJoin).length === 0) {
return;
}
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
$('#advertisingReceiveAll', $formJoin).prop('checked', true).siblings('label').addClass('on');
$('.js_advertising_channel', $formJoin).prop('checked', true).val('y').siblings('label').addClass('on');
};
$formJoin.on('change', '#privateMarketingConsentOptionFl', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
} else {
$('#termsAgree6', $formJoin).prop('checked', false).siblings('label').removeClass('on');
}
});
// 마케팅 목적 개인정보 수집 및 이용 동의 그룹 [선택] 헤더 체크 시 광고성 수신 동의·수신 채널 연동
$formJoin.on('change', '#termsAgree6', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
}
});
// 그룹 하위 항목이 모두 선택되면 상위 [선택] 헤더도 선택 (하나라도 해제 시 해제)
$formJoin.on('change', '.join_agreement_box .agreement_choice_box :checkbox', function () {
const $box = $(this).closest('.join_agreement_box');
const $header = $box.children('.form_element').children(':checkbox');
// 광고성 수신 동의(termsAgree7)는 채널 1개 이상 선택 의미라 전체 선택 동기화에서 제외
if ($header.attr('id') === 'termsAgree7') {
return;
}
const $subs = $box.find('.agreement_choice_box :checkbox');
const allChecked = $subs.length > 0 && $subs.not(':checked').length === 0;
$header.prop('checked', allChecked).siblings('label').toggleClass('on', allChecked);
});
// 초기 로드: 저장된 하위 선택 상태로 상위 [선택] 헤더 동기화
$('.join_agreement_box', $formJoin).each(function () {
const $box = $(this);
const $header = $box.children('.form_element').children(':checkbox');
// 광고성 수신 동의(termsAgree7)는 채널 1개 이상 선택 의미라 전체 선택 동기화에서 제외
if ($header.attr('id') === 'termsAgree7') {
return;
}
const $subs = $box.find('.agreement_choice_box :checkbox');
if ($subs.length > 0 && $subs.not(':checked').length === 0) {
$header.prop('checked', true).siblings('label').addClass('on');
}
});
// 초기 로드: 수신 채널이 전부 선택된 상태면 광고성 수신 동의(termsAgree7) 헤더 ON
if ($('.js_advertising_channel', $formJoin).length > 0
&& $('.js_advertising_channel:not(:checked)', $formJoin).length === 0) {
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
}
gd_select_email_domain('email');
<!--{? (joinField.marriFl.use == 'y' && joinField.marriDate.use == 'y') || joinField.birthDt.use == 'y' }-->
 
data/skin/front/mygreek/member/join_agreement.html+176-1
변경 전join_agreement.html
</div>
<!-- {/} -->
<!-- //join_agreement_box -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<div class="important_check_box">
<strong class="important_check dn">{=__('이용약관과 개인정보 수집 및 이용에 대한 안내 모두 동의해주세요.')}</strong>
</div>
$('.js_terms_view :checkbox', $formTerms).change(function (e) {
$('strong.important_check').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.js_terms_view');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
if (isTargetChecked) {
if (isTermsAgreeSelect) {
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/*
* 14세 동의 항목 체크박스 이벤트
*/
}
});
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
});
</script>
{ # footer }
 
변경 후join_agreement.html
</div>
<!-- {/} -->
<!-- //join_agreement_box -->
 
<!-- {? privateMarketingMember['modeFl']=='y'} -->
<div class="join_agreement_box js_terms_view">
<div class="form_element">
<input type="checkbox" id="termsAgree6" name=""/>
<label class="check_s" for="termsAgree6"><strong class="choice">({=__('선택')})</strong> {=__('마케팅 목적 개인정보 수집 및 이용 동의')} </label>
<span><a href="../service/agreement.php?code={privateMarketingMember['informCd']}" target="_blank">{=__('전체보기')}</a></span>
</div>
<div class="agreement_choice_box">
<table summary="{=__('마케팅 목적 개인정보 수집 및 이용 동의 내용입니다.')}" border="0" cellpadding="0" cellspacing="0">
<colgroup>
<col width="10px">
<col width="300px">
</colgroup>
<thead>
<tr>
<th>{=__('선택')}</th>
<th>{=nl2br(privateMarketingMember['informNm'])}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="form_element">
<input type="checkbox" id="privateMktApprovalOption" name="privateMarketingConsentOptionFl"/>
<label class="check_s" for="privateMktApprovalOption">{=__('선택')}</label>
</div>
</td>
<td>{=nl2br(privateMarketingMember['content'])}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- {/} -->
<!-- //join_agreement_box -->
 
<!-- {? isUseAdvertisingReceive} -->
<div class="join_agreement_box js_terms_view">
<div class="form_element">
<input type="checkbox" id="termsAgree7" name=""/>
<label class="check_s" for="termsAgree7"><strong class="choice">({=__('선택')})</strong> {=__('광고성 수신 동의')} </label>
<span><a href="../service/agreement.php?code={advertisingReceive['informCd']}" target="_blank">{=__('전체보기')}</a></span>
</div>
<div class="agreement_choice_box">
<table summary="{=__('광고성 수신 동의 내용입니다.')}" border="0" cellpadding="0" cellspacing="0">
<colgroup>
<col width="10px">
<col width="300px">
</colgroup>
<thead>
<tr>
<th>{=__('선택')}</th>
<th>{=nl2br(advertisingReceive['informNm'])}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="form_element">
<input type="checkbox" id="advertisingReceiveAll" name=""/>
<label class="check_s" for="advertisingReceiveAll">{=__('선택')}</label>
</div>
</td>
<td>{=nl2br(advertisingReceive['content'])}</td>
</tr>
</tbody>
</table>
<div class="js_advertising_channel_box" style="display:flex; flex-direction:column; align-items:flex-start; margin:10px;">
<!-- {? advertisingReceiveMailUse=='y'} -->
<div class="form_element">
<input type="checkbox" id="maillingFl" name="maillingFl" class="js_advertising_channel"/>
<label class="check_s" for="maillingFl">{=__('이메일 수신 동의')}</label>
</div>
<!-- {/} -->
<!-- {? advertisingReceiveSmsUse=='y'} -->
<div class="form_element">
<input type="checkbox" id="smsFl" name="smsFl" class="js_advertising_channel"/>
<label class="check_s" for="smsFl">{=__('모바일 메시지 수신 동의')}</label>
</div>
<!-- {/} -->
</div>
</div>
</div>
<!-- {/} -->
<!-- //join_agreement_box -->
 
<div class="important_check_box">
<strong class="important_check dn">{=__('이용약관과 개인정보 수집 및 이용에 대한 안내 모두 동의해주세요.')}</strong>
</div>
$('.js_terms_view :checkbox', $formTerms).change(function (e) {
$('strong.important_check').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.js_terms_view');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
if (isTargetChecked) {
if (isTermsAgreeSelect) {
}
});
 
/*
* 광고성 수신 동의 ↔ 이메일/모바일 수신 동의 ↔ 마케팅 목적 개인정보 수집 및 이용 동의 연동
* - 광고성 수신 동의 체크 → 이메일/모바일 채널 일괄 + 마케팅 목적 동의 ON
* - 채널(1개 이상) 체크 → 광고성 수신 동의 + 마케팅 목적 동의 ON (채널은 각각 개별 토글)
*/
const checkMarketingApproval = function () {
$('#privateMktApprovalOption', $formTerms).prop('checked', true).val('y').siblings('label').addClass('on');
$('#termsAgree6', $formTerms).prop('checked', true).siblings('label').addClass('on');
};
// 마케팅 목적 개인정보 수집 및 이용 동의 체크 시 광고성 수신 동의·수신 채널 일괄 선택
const checkAdvertising = function () {
if ($('#termsAgree7', $formTerms).length === 0) {
return;
}
$('#termsAgree7', $formTerms).prop('checked', true).siblings('label').addClass('on');
$('#advertisingReceiveAll', $formTerms).prop('checked', true).siblings('label').addClass('on');
$('.js_advertising_channel', $formTerms).prop('checked', true).val('y').siblings('label').addClass('on');
};
$formTerms.on('change', '#termsAgree7', function () {
const isChecked = $(this).prop('checked') === true;
$(this).siblings('label').toggleClass('on', isChecked);
$('#advertisingReceiveAll', $formTerms).prop('checked', isChecked)
.siblings('label').toggleClass('on', isChecked);
$('.js_advertising_channel', $formTerms).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formTerms.on('change', '#advertisingReceiveAll', function () {
const isChecked = $(this).prop('checked') === true;
$('.js_advertising_channel', $formTerms).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
} else {
$('#termsAgree7', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '.js_advertising_channel', function () {
const anyChannelChecked = $('.js_advertising_channel:checked', $formTerms).length > 0;
$('#advertisingReceiveAll', $formTerms).prop('checked', anyChannelChecked)
.siblings('label').toggleClass('on', anyChannelChecked);
if (anyChannelChecked) {
checkMarketingApproval();
}
if ($(this).prop('checked') === false) {
$('#termsAgree7', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '#privateMktApprovalOption', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
} else {
$('#termsAgree6', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '#termsAgree6', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
}
});
 
/*
* 14세 동의 항목 체크박스 이벤트
*/
}
});
}
 
// 그룹 하위 항목이 모두 선택되면 상위 [선택] 헤더(termsAgree3~7)도 선택 (하나라도 해제 시 해제)
$formTerms.on('change', '.js_terms_view .agreement_choice_box :checkbox', function () {
const $termsView = $(this).closest('.js_terms_view');
const $header = $termsView.children('.form_element').children(':checkbox');
const $subs = $termsView.find('.agreement_choice_box :checkbox');
const allChecked = $subs.length > 0 && $subs.not(':checked').length === 0;
$header.prop('checked', allChecked).siblings('label').toggleClass('on', allChecked);
});
 
/*
* 전체 동의 역동기화 — 하위 체크박스가 모두 선택되면 전체 동의도 선택 (하나라도 해제되면 해제)
*/
const syncAllAgree = function () {
const $targets = $(':checkbox', $formTerms).not('#allAgree').not('#termsAgree');
const allChecked = $targets.length > 0 && $targets.not(':checked').length === 0;
$('#allAgree', $formTerms).prop('checked', allChecked)
.siblings('label').toggleClass('on', allChecked);
};
$formTerms.on('change', ':checkbox', function (e) {
if (e.target.id === 'allAgree' || e.target.id === 'termsAgree') {
return;
}
syncAllAgree();
});
});
</script>
{ # footer }
 
data/skin/front/mygreek/member/_join_view.html-12
변경 전_join_view.html
</select>
</div>
<div class="member_warning js_email"></div>
<!--{? joinField.maillingFl.use === 'y'}-->
<div class="form_element">
<input type="checkbox" id="maillingFl" name="maillingFl" value="y" {? data.maillingFl == 'y'}checked="checked"{/}/>
<label for="maillingFl" class="check_s {? data.maillingFl == 'y'}on{/}">{=__('정보/이벤트 메일 수신에 동의합니다.')}</label>
</div>
<!--{/}-->
</td>
</tr>
<!--{/}-->
<!--{ / }-->
<!--{ / }-->
</div>
<!--{? joinField.smsFl.use === 'y'}-->
<div class="form_element">
<input type="checkbox" id="smsFl" name="smsFl" value="y" {? data.smsFl == 'y'}checked="checked"{/}/>
<label for="smsFl" class="check_s {? data.smsFl == 'y'}on{/}">{=__('정보/이벤트 SMS 수신에 동의합니다.')}</label>
</div>
<!--{/}-->
</td>
</tr>
<!--{/}-->
 
변경 후_join_view.html
</select>
</div>
<div class="member_warning js_email"></div>
 
 
 
 
 
 
</td>
</tr>
<!--{/}-->
<!--{ / }-->
<!--{ / }-->
</div>
 
 
 
 
 
 
</td>
</tr>
<!--{/}-->
 
data/skin/front/mygreek/member/_join_view_other.html+98-1
변경 전_join_view_other.html
<!--{/}-->
 
<!-- N : 약관동의 시작 -->
<!--{? privateApprovalOption[0]['modeFl'] == 'y' || privateConsign[0]['modeFl']=='y' || privateOffer[0]['modeFl']=='y'}-->
<div class="join_agreement_cont">
<h3>{=__('약관동의현황')}</h3>
<!--{? privateApprovalOption[0]['modeFl'] == 'y'}-->
</div>
<!-- //join_agreement_box -->
<!--{/ // privateOffer modeFl end}-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
</div>
<!-- //join_agreement_cont -->
<!--{ / }-->
 
변경 후_join_view_other.html
<!--{/}-->
 
<!-- N : 약관동의 시작 -->
<!--{? privateApprovalOption[0]['modeFl'] == 'y' || privateConsign[0]['modeFl']=='y' || privateOffer[0]['modeFl']=='y' || privateMktApprovalOption[0]['modeFl']=='y' || isUseAdvertisingReceive}-->
<div class="join_agreement_cont">
<h3>{=__('약관동의현황')}</h3>
<!--{? privateApprovalOption[0]['modeFl'] == 'y'}-->
</div>
<!-- //join_agreement_box -->
<!--{/ // privateOffer modeFl end}-->
 
<!--{? privateMktApprovalOption[0]['modeFl'] == 'y'}-->
<div class="join_agreement_box">
<div class="form_element">
<input type="checkbox" id="termsAgree6"/>
<label for="termsAgree6" class="check_s"><strong class="choice">({=__('선택')})</strong> {=__('마케팅 목적 개인정보 수집 및 이용 동의')} </label>
<span><a href="../service/agreement.php?code={privateMktApprovalOption[0]['informCd']}" target="_blank">{=__('전체보기')}</a></span>
</div>
<!--{@ privateMktApprovalOption}-->
<div class="agreement_choice_box">
<table border="0" cellpadding="0" cellspacing="0" summary="{=__('마케팅 목적 개인정보 수집 및 이용 동의 내용입니다.')}">
<colgroup>
<col width="10px">
<col width="300px">
</colgroup>
<thead>
<tr>
<th>{=__('선택')}</th>
<th>{=nl2br(.informNm)}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="form_element">
<input type="checkbox" id="privateMarketingConsentOptionFl" name="privateMarketingConsentOptionFl" value="y" <!--{? data['privateMarketingConsentOptionFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="privateMarketingConsentOptionFl" class="check_s <!--{? data['privateMarketingConsentOptionFl'] == 'y'}-->on<!--{/}-->">{=__('선택')}</label>
</div>
</td>
<td>
<div class="agreement_scroll">
{=nl2br(.content)}
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!--{/ // privateMktApprovalOption loop end}-->
</div>
<!-- //join_agreement_box -->
<!--{/ // privateMktApprovalOption modeFl end}-->
 
<!--{? isUseAdvertisingReceive}-->
<div class="join_agreement_box js_terms_view">
<div class="form_element">
<input type="checkbox" id="termsAgree7"/>
<label for="termsAgree7" class="check_s"><strong class="choice">({=__('선택')})</strong> {=__('광고성 수신 동의')} </label>
<span><a href="../service/agreement.php?code={advertisingReceive['informCd']}" target="_blank">{=__('전체보기')}</a></span>
</div>
<div class="agreement_choice_box">
<table border="0" cellpadding="0" cellspacing="0" summary="{=__('광고성 수신 동의 내용입니다.')}">
<colgroup>
<col width="10px">
<col width="300px">
</colgroup>
<thead>
<tr>
<th>{=__('선택')}</th>
<th>{=nl2br(advertisingReceive['informNm'])}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="form_element">
<input type="checkbox" id="advertisingReceiveAll" <!--{? data['maillingFl'] == 'y' || data['smsFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="advertisingReceiveAll" class="check_s <!--{? data['maillingFl'] == 'y' || data['smsFl'] == 'y'}-->on<!--{/}-->">{=__('선택')}</label>
</div>
</td>
<td>
<div class="agreement_scroll">
{=nl2br(advertisingReceive['content'])}
</div>
</td>
</tr>
</tbody>
</table>
<div class="js_advertising_channel_box" style="display:flex; flex-direction:column; align-items:flex-start; margin:10px;">
<!--{? advertisingReceiveMailUse == 'y'}-->
<div class="form_element">
<input type="checkbox" id="advertisingReceiveMail" name="maillingFl" value="y" class="js_advertising_channel" <!--{? data['maillingFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="advertisingReceiveMail" class="check_s <!--{? data['maillingFl'] == 'y'}-->on<!--{/}-->">{=__('이메일 수신 동의')}</label>
</div>
<!--{/}-->
<!--{? advertisingReceiveSmsUse == 'y'}-->
<div class="form_element">
<input type="checkbox" id="advertisingReceiveSms" name="smsFl" value="y" class="js_advertising_channel" <!--{? data['smsFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="advertisingReceiveSms" class="check_s <!--{? data['smsFl'] == 'y'}-->on<!--{/}-->">{=__('모바일 메시지 수신 동의')}</label>
</div>
<!--{/}-->
</div>
</div>
<!-- //agreement_choice_box -->
</div>
<!-- //join_agreement_box -->
<!--{/ // isUseAdvertisingReceive end}-->
</div>
<!-- //join_agreement_cont -->
<!--{ / }-->
 
data/skin/front/mygreek/mypage/my_page.html+101-1
변경 전my_page.html
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.join_agreement_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
gd_select_email_domain('email');
 
<!--{? (joinField.marriFl.use == 'y' && joinField.marriDate.use == 'y') || joinField.birthDt.use == 'y' }-->
 
변경 후my_page.html
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.join_agreement_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
}
});
 
/*
* 광고성 수신 동의 ↔ 이메일/모바일 수신 동의 ↔ 마케팅 목적 개인정보 수집 및 이용 동의 연동
* - 광고성 수신 동의 체크 → 이메일/모바일 채널 일괄 + 마케팅 목적 동의 ON
* - 채널(1개 이상) 체크 → 광고성 수신 동의 + 마케팅 목적 동의 ON (채널은 각각 개별 토글)
*/
const checkMarketingApproval = function () {
$('#privateMarketingConsentOptionFl', $formJoin).prop('checked', true).val('y').siblings('label').addClass('on');
$('#termsAgree6', $formJoin).prop('checked', true).siblings('label').addClass('on');
};
$formJoin.on('change', '#termsAgree7', function () {
const isChecked = $(this).prop('checked') === true;
$(this).siblings('label').toggleClass('on', isChecked);
$('#advertisingReceiveAll', $formJoin).prop('checked', isChecked)
.siblings('label').toggleClass('on', isChecked);
$('.js_advertising_channel', $formJoin).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formJoin.on('change', '#advertisingReceiveAll', function () {
const isChecked = $(this).prop('checked') === true;
$('.js_advertising_channel', $formJoin).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
// 전체선택(advertisingReceiveAll) 직접 조작 시 광고성 수신 동의(termsAgree7) 헤더도 동기화
$('#termsAgree7', $formJoin).prop('checked', isChecked).siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formJoin.on('change', '.js_advertising_channel', function () {
const anyChannelChecked = $('.js_advertising_channel:checked', $formJoin).length > 0;
const allChannelChecked = $('.js_advertising_channel', $formJoin).length > 0
&& $('.js_advertising_channel:not(:checked)', $formJoin).length === 0;
$('#advertisingReceiveAll', $formJoin).prop('checked', anyChannelChecked)
.siblings('label').toggleClass('on', anyChannelChecked);
if (anyChannelChecked) {
checkMarketingApproval();
}
// 수신 채널 전부 선택 시 광고성 수신 동의(termsAgree7) 헤더 ON (일부만이면 켜지 않음)
if (allChannelChecked) {
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
}
if ($(this).prop('checked') === false) {
$('#termsAgree7', $formJoin).prop('checked', false).siblings('label').removeClass('on');
}
});
// 마케팅 목적 개인정보 수집 및 이용 동의 체크 시 광고성 수신 동의·수신 채널 일괄 선택
const checkAdvertising = function () {
if ($('#termsAgree7', $formJoin).length === 0) {
return;
}
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
$('#advertisingReceiveAll', $formJoin).prop('checked', true).siblings('label').addClass('on');
$('.js_advertising_channel', $formJoin).prop('checked', true).val('y').siblings('label').addClass('on');
};
$formJoin.on('change', '#privateMarketingConsentOptionFl', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
} else {
$('#termsAgree6', $formJoin).prop('checked', false).siblings('label').removeClass('on');
}
});
// 마케팅 목적 개인정보 수집 및 이용 동의 그룹 [선택] 헤더 체크 시 광고성 수신 동의·수신 채널 연동
$formJoin.on('change', '#termsAgree6', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
}
});
// 그룹 하위 항목이 모두 선택되면 상위 [선택] 헤더도 선택 (하나라도 해제 시 해제)
$formJoin.on('change', '.join_agreement_box .agreement_choice_box :checkbox', function () {
const $box = $(this).closest('.join_agreement_box');
const $header = $box.children('.form_element').children(':checkbox');
// 광고성 수신 동의(termsAgree7)는 채널 1개 이상 선택 의미라 전체 선택 동기화에서 제외
if ($header.attr('id') === 'termsAgree7') {
return;
}
const $subs = $box.find('.agreement_choice_box :checkbox');
const allChecked = $subs.length > 0 && $subs.not(':checked').length === 0;
$header.prop('checked', allChecked).siblings('label').toggleClass('on', allChecked);
});
// 초기 로드: 저장된 하위 선택 상태로 상위 [선택] 헤더 동기화
$('.join_agreement_box', $formJoin).each(function () {
const $box = $(this);
const $header = $box.children('.form_element').children(':checkbox');
// 광고성 수신 동의(termsAgree7)는 채널 1개 이상 선택 의미라 전체 선택 동기화에서 제외
if ($header.attr('id') === 'termsAgree7') {
return;
}
const $subs = $box.find('.agreement_choice_box :checkbox');
if ($subs.length > 0 && $subs.not(':checked').length === 0) {
$header.prop('checked', true).siblings('label').addClass('on');
}
});
// 초기 로드: 수신 채널이 전부 선택된 상태면 광고성 수신 동의(termsAgree7) 헤더 ON
if ($('.js_advertising_channel', $formJoin).length > 0
&& $('.js_advertising_channel:not(:checked)', $formJoin).length === 0) {
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
}
 
gd_select_email_domain('email');
 
<!--{? (joinField.marriFl.use == 'y' && joinField.marriDate.use == 'y') || joinField.birthDt.use == 'y' }-->
 
data/skin/front/story_g/member/join_agreement.html+185-1
변경 전join_agreement.html
</div>
</div>
<!-- {/} -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<p class="msg dn">
<span>{=__('이용약관과 개인정보 수집 및 이용에 대한 안내 모두 동의해주세요.')}</span>
</p>
$('.terms-view :checkbox', $formTerms).change(function (e) {
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.terms-view');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/*
* 14세 동의 항목 체크박스 이벤트
*/
}
});
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
});
</script>
{ # footer }
 
변경 후join_agreement.html
</div>
</div>
<!-- {/} -->
<!-- {? privateMarketingMember['modeFl']=='y'} -->
<div class="terms-view">
<p>
<input type="checkbox" class="checkbox" id="termsAgree6" name="">
<label for="termsAgree6">
<span class="option"><strong>({=__('선택')})</strong></span>
{=__('마케팅 목적 개인정보 수집 및 이용 동의')}
</label>
<a href="../service/agreement.php?code={privateMarketingMember['informCd']}" target="_blank">{=__('전체보기')}</a>
</p>
<div class="consignment-box">
<table summary="{=__('마케팅 목적 개인정보 수집 및 이용 동의 내용입니다.')}" class="" cellspacing="0" border="1">
<colgroup>
<col width="79px">
<col>
</colgroup>
<thead>
<tr>
<th scope="col">{=__('선택')}</th>
<th scope="col">{=nl2br(privateMarketingMember['informNm'])}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span class="form-element">
<input type="checkbox" class="checkbox" id="privateMktApprovalOption" name="privateMarketingConsentOptionFl">
<label for="privateMktApprovalOption">{=__('선택')}</label>
</span>
</td>
<td>
<div>
{=nl2br(privateMarketingMember['content'])}
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- {/} -->
<!-- {? isUseAdvertisingReceive} -->
<div class="terms-view">
<p>
<input type="checkbox" class="checkbox" id="termsAgree7" name="">
<label for="termsAgree7">
<span class="option"><strong>({=__('선택')})</strong></span>
{=__('광고성 수신 동의')}
</label>
<a href="../service/agreement.php?code={advertisingReceive['informCd']}" target="_blank">{=__('전체보기')}</a>
</p>
<div class="consignment-box">
<table summary="{=__('광고성 수신 동의 내용입니다.')}" class="" cellspacing="0" border="1">
<colgroup>
<col width="79px">
<col>
</colgroup>
<thead>
<tr>
<th scope="col">{=__('선택')}</th>
<th scope="col">{=nl2br(advertisingReceive['informNm'])}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span class="form-element">
<input type="checkbox" class="checkbox" id="advertisingReceiveAll" name="">
<label for="advertisingReceiveAll">{=__('선택')}</label>
</span>
</td>
<td>
<div>
{=nl2br(advertisingReceive['content'])}
</div>
</td>
</tr>
</tbody>
</table>
<div class="js_advertising_channel_box" style="display:flex; flex-direction:column; align-items:flex-start; gap:8px; margin:10px;">
<!-- {? advertisingReceiveMailUse=='y'} -->
<span class="form-element">
<input type="checkbox" class="checkbox js_advertising_channel" id="maillingFl" name="maillingFl">
<label for="maillingFl">{=__('이메일 수신 동의')}</label>
</span>
<!-- {/} -->
<!-- {? advertisingReceiveSmsUse=='y'} -->
<span class="form-element">
<input type="checkbox" class="checkbox js_advertising_channel" id="smsFl" name="smsFl">
<label for="smsFl">{=__('모바일 메시지 수신 동의')}</label>
</span>
<!-- {/} -->
</div>
</div>
</div>
<!-- {/} -->
<p class="msg dn">
<span>{=__('이용약관과 개인정보 수집 및 이용에 대한 안내 모두 동의해주세요.')}</span>
</p>
$('.terms-view :checkbox', $formTerms).change(function (e) {
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.terms-view');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
}
});
 
/*
* 광고성 수신 동의 ↔ 이메일/모바일 수신 동의 ↔ 마케팅 목적 개인정보 수집 및 이용 동의 연동
* - 광고성 수신 동의 체크 → 이메일/모바일 채널 일괄 + 마케팅 목적 동의 ON
* - 채널(1개 이상) 체크 → 광고성 수신 동의 + 마케팅 목적 동의 ON (채널은 각각 개별 토글)
*/
const checkMarketingApproval = function () {
$('#privateMktApprovalOption', $formTerms).prop('checked', true).val('y').siblings('label').addClass('on');
$('#termsAgree6', $formTerms).prop('checked', true).siblings('label').addClass('on');
};
// 마케팅 목적 개인정보 수집 및 이용 동의 체크 시 광고성 수신 동의·수신 채널 일괄 선택
const checkAdvertising = function () {
if ($('#termsAgree7', $formTerms).length === 0) {
return;
}
$('#termsAgree7', $formTerms).prop('checked', true).siblings('label').addClass('on');
$('#advertisingReceiveAll', $formTerms).prop('checked', true).siblings('label').addClass('on');
$('.js_advertising_channel', $formTerms).prop('checked', true).val('y').siblings('label').addClass('on');
};
$formTerms.on('change', '#termsAgree7', function () {
const isChecked = $(this).prop('checked') === true;
$(this).siblings('label').toggleClass('on', isChecked);
$('#advertisingReceiveAll', $formTerms).prop('checked', isChecked)
.siblings('label').toggleClass('on', isChecked);
$('.js_advertising_channel', $formTerms).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formTerms.on('change', '#advertisingReceiveAll', function () {
const isChecked = $(this).prop('checked') === true;
$('.js_advertising_channel', $formTerms).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
} else {
$('#termsAgree7', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '.js_advertising_channel', function () {
const anyChannelChecked = $('.js_advertising_channel:checked', $formTerms).length > 0;
$('#advertisingReceiveAll', $formTerms).prop('checked', anyChannelChecked)
.siblings('label').toggleClass('on', anyChannelChecked);
if (anyChannelChecked) {
checkMarketingApproval();
}
if ($(this).prop('checked') === false) {
$('#termsAgree7', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '#privateMktApprovalOption', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
} else {
$('#termsAgree6', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '#termsAgree6', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
}
});
 
/*
* 14세 동의 항목 체크박스 이벤트
*/
}
});
}
 
// 그룹 하위 항목이 모두 선택되면 상위 [선택] 헤더(termsAgree3~7)도 선택 (하나라도 해제 시 해제)
$formTerms.on('change', '.terms-view .consignment-box :checkbox', function () {
const $termsView = $(this).closest('.terms-view');
const $header = $termsView.children('p').children(':checkbox');
const $subs = $termsView.find('.consignment-box :checkbox');
const allChecked = $subs.length > 0 && $subs.not(':checked').length === 0;
$header.prop('checked', allChecked).siblings('label').toggleClass('on', allChecked);
});
 
/*
* 전체 동의 역동기화 — 하위 체크박스가 모두 선택되면 전체 동의도 선택 (하나라도 해제되면 해제)
*/
const syncAllAgree = function () {
const $targets = $(':checkbox', $formTerms).not('#allAgree').not('#termsAgree');
const allChecked = $targets.length > 0 && $targets.not(':checked').length === 0;
$('#allAgree', $formTerms).prop('checked', allChecked)
.siblings('label').toggleClass('on', allChecked);
};
$formTerms.on('change', ':checkbox', function (e) {
if (e.target.id === 'allAgree' || e.target.id === 'termsAgree') {
return;
}
syncAllAgree();
});
});
</script>
{ # footer }
 
data/skin/front/story_g/member/_join_view.html-12
변경 전_join_view.html
</select>
</div>
</div>
<!--{? joinField.maillingFl.use === 'y'}-->
<div class="form-element">
<input type="checkbox" class="checkbox" id="maillingFl" name="maillingFl" value="y" {? data.maillingFl == 'y'}checked="checked"{/}>
<label for="maillingFl" class="{? data.maillingFl == 'y'}on{/}">{=__('정보/이벤트 메일 수신에 동의합니다.')}</label>
</div>
<!--{/}-->
</td>
</tr>
<!--{/}--><!--{? joinField.cellPhone.use == 'y'}-->
</button>
<!--{ / }-->
<!--{ / }-->
<!--{? joinField.smsFl.use === 'y'}-->
<div class="form-element">
<input type="checkbox" class="checkbox" id="smsFl" name="smsFl" value="y" {? data.smsFl == 'y'}checked="checked"{/}>
<label for="smsFl" class="{? data.smsFl == 'y'}on{/}">{=__('정보/이벤트 SMS 수신에 동의합니다.')}</label>
</div>
<!--{/}-->
</td>
</tr>
<!--{/}--><!--{? joinField.phone.use == 'y'}-->
 
변경 후_join_view.html
</select>
</div>
</div>
 
 
 
 
 
 
</td>
</tr>
<!--{/}--><!--{? joinField.cellPhone.use == 'y'}-->
</button>
<!--{ / }-->
<!--{ / }-->
 
 
 
 
 
 
</td>
</tr>
<!--{/}--><!--{? joinField.phone.use == 'y'}-->
 
data/skin/front/story_g/member/_join_view_other.html+102
변경 전_join_view_other.html
</div>
</div>
<!--{/ // privateOffer modeFl end}-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<!--{/ // isMyPage end}-->
 
변경 후_join_view_other.html
</div>
</div>
<!--{/ // privateOffer modeFl end}-->
<!--{? privateMktApprovalOption[0]['modeFl'] == 'y'}-->
<div class="terms-view">
<p>
<input type="checkbox" class="checkbox" id="termsAgree6" name="">
<label for="termsAgree6">
<span class="option">
<strong>({=__('선택')})</strong>
</span>
{=__('마케팅 목적 개인정보 수집 및 이용 동의')}
</label>
<a href="../service/agreement.php?code={privateMktApprovalOption[0]['informCd']}" target="_blank">{=__('전체보기')}</a>
</p>
<div class="consignment-box">
<!--{@ privateMktApprovalOption}-->
<table summary="{=__('마케팅 목적 개인정보 수집 및 이용 동의 내용입니다.')}" class="" cellspacing="0" border="1">
<colgroup>
<col width="79px">
<col>
</colgroup>
<thead>
<tr>
<th scope="col">{=__('선택')}</th>
<th scope="col">{=nl2br(.informNm)}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span class="form-element">
<input type="checkbox" class="checkbox" id="privateMarketingConsentOptionFl" name="privateMarketingConsentOptionFl" value="y" <!--{? data['privateMarketingConsentOptionFl'] == 'y'}-->checked="checked"<!--{/}-->>
<label class="<!--{? data['privateMarketingConsentOptionFl'] == 'y'}-->on<!--{/}-->" for="privateMarketingConsentOptionFl">{=__('선택')}</label>
</span>
</td>
<td>
<div>
{=nl2br(.content)}
</div>
</td>
</tr>
</tbody>
</table>
<!--{/ // privateMktApprovalOption loop end}-->
</div>
</div>
<!--{/ // privateMktApprovalOption modeFl end}-->
<!--{? isUseAdvertisingReceive}-->
<div class="terms-view js_terms_view">
<p>
<input type="checkbox" class="checkbox" id="termsAgree7" name="">
<label for="termsAgree7">
<span class="option">
<strong>({=__('선택')})</strong>
</span>
{=__('광고성 수신 동의')}
</label>
<a href="../service/agreement.php?code={advertisingReceive['informCd']}" target="_blank">{=__('전체보기')}</a>
</p>
<div class="consignment-box">
<table summary="{=__('광고성 수신 동의 내용입니다.')}" class="" cellspacing="0" border="1">
<colgroup>
<col width="79px">
<col>
</colgroup>
<thead>
<tr>
<th scope="col">{=__('선택')}</th>
<th scope="col">{=nl2br(advertisingReceive['informNm'])}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span class="form-element">
<input type="checkbox" class="checkbox" id="advertisingReceiveAll" <!--{? data['maillingFl'] == 'y' || data['smsFl'] == 'y'}-->checked="checked"<!--{/}-->>
<label class="<!--{? data['maillingFl'] == 'y' || data['smsFl'] == 'y'}-->on<!--{/}-->" for="advertisingReceiveAll">{=__('선택')}</label>
</span>
</td>
<td>
<div>
{=nl2br(advertisingReceive['content'])}
</div>
</td>
</tr>
</tbody>
</table>
<div class="js_advertising_channel_box" style="display:flex; flex-direction:column; align-items:flex-start; gap:8px; margin:10px;">
<!--{? advertisingReceiveMailUse == 'y'}-->
<span class="form-element">
<input type="checkbox" class="checkbox js_advertising_channel" id="advertisingReceiveMail" name="maillingFl" value="y" <!--{? data['maillingFl'] == 'y'}-->checked="checked"<!--{/}-->>
<label class="<!--{? data['maillingFl'] == 'y'}-->on<!--{/}-->" for="advertisingReceiveMail">{=__('이메일 수신 동의')}</label>
</span>
<!--{/}-->
<!--{? advertisingReceiveSmsUse == 'y'}-->
<span class="form-element">
<input type="checkbox" class="checkbox js_advertising_channel" id="advertisingReceiveSms" name="smsFl" value="y" <!--{? data['smsFl'] == 'y'}-->checked="checked"<!--{/}-->>
<label class="<!--{? data['smsFl'] == 'y'}-->on<!--{/}-->" for="advertisingReceiveSms">{=__('모바일 메시지 수신 동의')}</label>
</span>
<!--{/}-->
</div>
</div>
</div>
<!--{/ // isUseAdvertisingReceive end}-->
<!--{/ // isMyPage end}-->
 
data/skin/front/story_g/mypage/my_page.html+102-1
변경 전my_page.html
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.terms-view');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
if (isTargetChecked) {
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
select_email_domain('email');
<!--{? (joinField.marriFl.use == 'y' && joinField.marriDate.use == 'y') || joinField.birthDt.use == 'y' }-->
 
변경 후my_page.html
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.terms-view');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
if (isTargetChecked) {
}
});
/*
* 광고성 수신 동의 ↔ 이메일/모바일 수신 동의 ↔ 마케팅 목적 개인정보 수집 및 이용 동의 연동
* - 광고성 수신 동의 체크 → 이메일/모바일 채널 일괄 + 마케팅 목적 동의 ON
* - 채널(1개 이상) 체크 → 광고성 수신 동의 + 마케팅 목적 동의 ON (채널은 각각 개별 토글)
*/
const checkMarketingApproval = function () {
$('#privateMarketingConsentOptionFl', $formJoin).prop('checked', true).val('y').siblings('label').addClass('on');
$('#termsAgree6', $formJoin).prop('checked', true).siblings('label').addClass('on');
};
$formJoin.on('change', '#termsAgree7', function () {
const isChecked = $(this).prop('checked') === true;
$(this).siblings('label').toggleClass('on', isChecked);
$('#advertisingReceiveAll', $formJoin).prop('checked', isChecked)
.siblings('label').toggleClass('on', isChecked);
$('.js_advertising_channel', $formJoin).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formJoin.on('change', '#advertisingReceiveAll', function () {
const isChecked = $(this).prop('checked') === true;
$('.js_advertising_channel', $formJoin).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
// 전체선택(advertisingReceiveAll) 직접 조작 시 광고성 수신 동의(termsAgree7) 헤더도 동기화
$('#termsAgree7', $formJoin).prop('checked', isChecked).siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formJoin.on('change', '.js_advertising_channel', function () {
const anyChannelChecked = $('.js_advertising_channel:checked', $formJoin).length > 0;
const allChannelChecked = $('.js_advertising_channel', $formJoin).length > 0
&& $('.js_advertising_channel:not(:checked)', $formJoin).length === 0;
$('#advertisingReceiveAll', $formJoin).prop('checked', anyChannelChecked)
.siblings('label').toggleClass('on', anyChannelChecked);
if (anyChannelChecked) {
checkMarketingApproval();
}
// 수신 채널 전부 선택 시 광고성 수신 동의(termsAgree7) 헤더 ON (일부만이면 켜지 않음)
if (allChannelChecked) {
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
}
if ($(this).prop('checked') === false) {
$('#termsAgree7', $formJoin).prop('checked', false).siblings('label').removeClass('on');
}
});
// 마케팅 목적 개인정보 수집 및 이용 동의 체크 시 광고성 수신 동의·수신 채널 일괄 선택
const checkAdvertising = function () {
if ($('#termsAgree7', $formJoin).length === 0) {
return;
}
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
$('#advertisingReceiveAll', $formJoin).prop('checked', true).siblings('label').addClass('on');
$('.js_advertising_channel', $formJoin).prop('checked', true).val('y').siblings('label').addClass('on');
};
$formJoin.on('change', '#privateMarketingConsentOptionFl', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
} else {
$('#termsAgree6', $formJoin).prop('checked', false).siblings('label').removeClass('on');
}
});
// 마케팅 목적 개인정보 수집 및 이용 동의 그룹 [선택] 헤더 체크 시 광고성 수신 동의·수신 채널 연동
$formJoin.on('change', '#termsAgree6', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
}
});
// 그룹 하위 항목이 모두 선택되면 상위 [선택] 헤더도 선택 (하나라도 해제 시 해제)
// - story_g 는 약관 그룹 마크업이 .terms-view / .consignment-box 구조 (moment·mygreek 의 .join_agreement_box 아님)
$formJoin.on('change', '.terms-view .consignment-box :checkbox', function () {
const $box = $(this).closest('.terms-view');
const $subs = $box.find('.consignment-box :checkbox');
const $header = $box.find(':checkbox').not($subs);
// 광고성 수신 동의(termsAgree7)는 채널 1개 이상 선택 의미라 전체 선택 동기화에서 제외
if ($header.attr('id') === 'termsAgree7') {
return;
}
const allChecked = $subs.length > 0 && $subs.not(':checked').length === 0;
$header.prop('checked', allChecked).siblings('label').toggleClass('on', allChecked);
});
// 초기 로드: 저장된 하위 선택 상태로 상위 [선택] 헤더 동기화
$('.terms-view', $formJoin).each(function () {
const $box = $(this);
const $subs = $box.find('.consignment-box :checkbox');
const $header = $box.find(':checkbox').not($subs);
// 광고성 수신 동의(termsAgree7)는 채널 1개 이상 선택 의미라 전체 선택 동기화에서 제외
if ($header.attr('id') === 'termsAgree7') {
return;
}
if ($subs.length > 0 && $subs.not(':checked').length === 0) {
$header.prop('checked', true).siblings('label').addClass('on');
}
});
// 초기 로드: 수신 채널이 전부 선택된 상태면 광고성 수신 동의(termsAgree7) 헤더 ON
if ($('.js_advertising_channel', $formJoin).length > 0
&& $('.js_advertising_channel:not(:checked)', $formJoin).length === 0) {
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
}
select_email_domain('email');
<!--{? (joinField.marriFl.use == 'y' && joinField.marriDate.use == 'y') || joinField.birthDt.use == 'y' }-->
 
data/skin/mobile/moment/member/join_agreement.html+176-2
변경 전join_agreement.html
<!-- //privacy_content -->
</div>
<!--{/}-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<p class="msg dn">
<span>{=__('이용약관과 개인정보 수집 및 이용에 대한 안내 모두 동의해주세요.')}</span>
</p>
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/*
* 14세 동의 항목 체크박스 이벤트
*/
/*
* 약관 내용보기 이벤트
*/
$('.privacy_box dd button').on({
'click':function(){
var privacy_dp = $(this).parent().parent().parent().find('> .privacy_content').css('display');
if(privacy_dp == 'none'){
}
});
 
 
 
 
 
 
window.addEventListener('message', function (e) {
if (e.origin === "{=authDreamUrl}") {
$('#layerSearch').remove();
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
});
 
/**
 
변경 후join_agreement.html
<!-- //privacy_content -->
</div>
<!--{/}-->
<!--{? privateMarketingMember['modeFl']=='y'}-->
<div class="privacy_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" id="termsAgree6"/>
<label for="termsAgree6">({=__('선택')}) {=__('마케팅 목적 개인정보 수집 및 이용 동의')}</label>
</span>
</dt>
<dd><button type="button">{=__('내용보기')} ▼</button></dd>
</dl>
<div class="privacy_content">
<div class="privacy_big_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" class="sp" id="privateMktApprovalOption" name="privateMarketingConsentOptionFl"/>
<label for="privateMktApprovalOption">{=nl2br(privateMarketingMember['informNm'])}</label>
</span>
</dt>
<dd><button type="button" class="js_agreement_view" data-code="{privateMarketingMember['informCd']}">{=__('전체보기')}</button></dd>
</dl>
<div class="privacy_content">
<div>{=nl2br(privateMarketingMember['content'])}</div>
</div>
<!-- //privacy_content -->
</div>
<!-- //privacy_big_box -->
</div>
<!-- //privacy_content -->
</div>
<!--{/}-->
<!--{? isUseAdvertisingReceive}-->
<div class="privacy_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" id="termsAgree7"/>
<label for="termsAgree7">({=__('선택')}) {=__('광고성 수신 동의')}</label>
</span>
</dt>
<dd><button type="button">{=__('내용보기')} ▼</button></dd>
</dl>
<div class="privacy_content">
<div class="privacy_big_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" class="sp" id="advertisingReceiveAll"/>
<label for="advertisingReceiveAll">{=nl2br(advertisingReceive['informNm'])}</label>
</span>
</dt>
<dd><button type="button" class="js_agreement_view" data-code="{advertisingReceive['informCd']}">{=__('전체보기')}</button></dd>
</dl>
<div class="privacy_content">
<div>{=nl2br(advertisingReceive['content'])}</div>
</div>
<!-- //privacy_content -->
</div>
<!-- //privacy_big_box -->
<div class="js_advertising_channel_box" style="display:flex; flex-direction:column; align-items:flex-start; margin:10px;">
<!--{? advertisingReceiveMailUse=='y'}-->
<span class="inp_chk">
<input type="checkbox" class="sp js_advertising_channel" id="maillingFl" name="maillingFl"/>
<label for="maillingFl">{=__('이메일 수신 동의')}</label>
</span>
<!--{/}-->
<!--{? advertisingReceiveSmsUse=='y'}-->
<span class="inp_chk">
<input type="checkbox" class="sp js_advertising_channel" id="smsFl" name="smsFl"/>
<label for="smsFl">{=__('모바일 메시지 수신 동의')}</label>
</span>
<!--{/}-->
</div>
</div>
<!-- //privacy_content -->
</div>
<!--{/}-->
<p class="msg dn">
<span>{=__('이용약관과 개인정보 수집 및 이용에 대한 안내 모두 동의해주세요.')}</span>
</p>
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
}
});
 
/*
* 광고성 수신 동의 ↔ 이메일/모바일 수신 동의 ↔ 마케팅 목적 개인정보 수집 및 이용 동의 연동
* - 광고성 수신 동의 체크 → 이메일/모바일 채널 일괄 + 마케팅 목적 동의 ON
* - 채널(1개 이상) 체크 → 광고성 수신 동의 + 마케팅 목적 동의 ON (채널은 각각 개별 토글)
*/
const checkMarketingApproval = function () {
$('#privateMktApprovalOption', $formTerms).prop('checked', true).val('y').siblings('label').addClass('on');
$('#termsAgree6', $formTerms).prop('checked', true).siblings('label').addClass('on');
};
// 마케팅 목적 개인정보 수집 및 이용 동의 체크 시 광고성 수신 동의·수신 채널 일괄 선택
const checkAdvertising = function () {
if ($('#termsAgree7', $formTerms).length === 0) {
return;
}
$('#termsAgree7', $formTerms).prop('checked', true).siblings('label').addClass('on');
$('#advertisingReceiveAll', $formTerms).prop('checked', true).siblings('label').addClass('on');
$('.js_advertising_channel', $formTerms).prop('checked', true).val('y').siblings('label').addClass('on');
};
$formTerms.on('change', '#termsAgree7', function () {
const isChecked = $(this).prop('checked') === true;
$(this).siblings('label').toggleClass('on', isChecked);
$('#advertisingReceiveAll', $formTerms).prop('checked', isChecked)
.siblings('label').toggleClass('on', isChecked);
$('.js_advertising_channel', $formTerms).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formTerms.on('change', '#advertisingReceiveAll', function () {
const isChecked = $(this).prop('checked') === true;
$('.js_advertising_channel', $formTerms).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
} else {
$('#termsAgree7', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '.js_advertising_channel', function () {
const anyChannelChecked = $('.js_advertising_channel:checked', $formTerms).length > 0;
$('#advertisingReceiveAll', $formTerms).prop('checked', anyChannelChecked)
.siblings('label').toggleClass('on', anyChannelChecked);
if (anyChannelChecked) {
checkMarketingApproval();
}
if ($(this).prop('checked') === false) {
$('#termsAgree7', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '#privateMktApprovalOption', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
} else {
$('#termsAgree6', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '#termsAgree6', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
}
});
 
/*
* 14세 동의 항목 체크박스 이벤트
*/
/*
* 약관 내용보기 이벤트
*/
$('.privacy_box dd button:not(.js_agreement_view)').on({
'click':function(){
var privacy_dp = $(this).parent().parent().parent().find('> .privacy_content').css('display');
if(privacy_dp == 'none'){
}
});
 
// 약관 전문 새창 열기 (전체보기)
$('.js_agreement_view').on('click', function () {
window.open('../service/agreement.php?code=' + $(this).data('code'));
});
 
window.addEventListener('message', function (e) {
if (e.origin === "{=authDreamUrl}") {
$('#layerSearch').remove();
}
});
 
// 그룹 하위 항목이 모두 선택되면 상위 [선택] 헤더(termsAgree3~7)도 선택 (하나라도 해제 시 해제)
$formTerms.on('change', '.privacy_box :checkbox', function () {
const $box = $(this).closest('.privacy_box');
const $header = $box.children('dl').find(':checkbox');
const $subs = $box.find(':checkbox').not($header);
if ($subs.length === 0) {
return;
}
const allChecked = $subs.not(':checked').length === 0;
$header.prop('checked', allChecked).siblings('label').toggleClass('on', allChecked);
});
 
/*
* 전체 동의 역동기화 — 하위 체크박스가 모두 선택되면 전체 동의도 선택 (하나라도 해제되면 해제)
*/
const syncAllAgree = function () {
const $targets = $(':checkbox', $formTerms).not('#allAgree').not('#termsAgree');
const allChecked = $targets.length > 0 && $targets.not(':checked').length === 0;
$('#allAgree', $formTerms).prop('checked', allChecked)
.siblings('label').toggleClass('on', allChecked);
};
$formTerms.on('change', ':checkbox', function (e) {
if (e.target.id === 'allAgree' || e.target.id === 'termsAgree') {
return;
}
syncAllAgree();
});
});
 
/**
 
data/skin/mobile/moment/member/_join_view.html-22
변경 전_join_view.html
<div class="input_wrap">
<div class="input_title"></div>
<div class="input_content">
<!--{? joinField.maillingFl.use === 'y'}-->
<div class="description">
<span class="inp_chk">
<input type="checkbox" id="maillingFl" class="ignore" name="maillingFl" value="y" {? data.maillingFl=='y'}checked="checked"{/}/>
<label for="maillingFl">{=__('정보/이벤트 메일 수신에 동의합니다.')}</label>
</span>
</div>
<!--{/}-->
<div class="description">※ {=__('아이디 비밀번호 찾기에 활용 되므로 정확하게 입력해 주세요')}</div>
</div>
</div>
<!--{ / }-->
</div>
</div>
<!--{? joinField.smsFl.use === 'y'}-->
<div class="input_wrap">
<div class="input_title"></div>
<div class="input_content">
<div class="description">
<span class="inp_chk">
<input type="checkbox" class="ignore" id="smsFl" name="smsFl" value="y" {? data.smsFl=='y'}checked="checked"{/}/>
<label for="smsFl">{=__('정보/이벤트 SMS 수신에 동의합니다.')}</label>
</span>
</div>
</div>
</div>
<!--{/}-->
<!--{/}-->
<!--{? joinField.phone.use == 'y'}-->
<div class="input_wrap">
 
변경 후_join_view.html
<div class="input_wrap">
<div class="input_title"></div>
<div class="input_content">
 
 
 
 
 
 
 
 
<div class="description">※ {=__('아이디 비밀번호 찾기에 활용 되므로 정확하게 입력해 주세요')}</div>
</div>
</div>
<!--{ / }-->
</div>
</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<!--{/}-->
<!--{? joinField.phone.use == 'y'}-->
<div class="input_wrap">
 
data/skin/mobile/moment/member/_join_view_other.html+88-3
변경 전_join_view_other.html
<!--{/}-->
</div>
<!--{/}-->
<!--{? privateApprovalOption[0]['modeFl'] == 'y' || privateConsign[0]['modeFl']=='y' || privateOffer[0]['modeFl']=='y'}-->
<p class="tit">{=__('약관동의현황')}</p>
<!--{? privateApprovalOption[0]['modeFl'] == 'y'}-->
<div class="privacy_big_box">
</div>
</div>
<!--{/ // privateOffer modeFl end}-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<!--{/}-->
<script type="text/javascript">
$(document).ready(function() {
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
/*
* 약관 내용보기 이벤트
*/
$('.privacy_box dd button').on({
 
 
 
'click':function(){
var privacy_dp = $(this).parent().parent().parent().find('> .privacy_content').css('display');
if(privacy_dp == 'none'){
 
변경 후_join_view_other.html
<!--{/}-->
</div>
<!--{/}-->
<!--{? privateApprovalOption[0]['modeFl'] == 'y' || privateConsign[0]['modeFl']=='y' || privateOffer[0]['modeFl']=='y' || privateMktApprovalOption[0]['modeFl']=='y' || isUseAdvertisingReceive}-->
<p class="tit">{=__('약관동의현황')}</p>
<!--{? privateApprovalOption[0]['modeFl'] == 'y'}-->
<div class="privacy_big_box">
</div>
</div>
<!--{/ // privateOffer modeFl end}-->
<!--{? privateMktApprovalOption[0]['modeFl'] == 'y'}-->
<div class="privacy_big_box">
<div class="privacy_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" id="termsAgree6" name=""/>
<label for="termsAgree6">({=__('선택')}) {=__('마케팅 목적 개인정보 수집 및 이용 동의')}</label>
</span>
</dt>
<dd><button type="button">{=__('내용보기')} ▼</button></dd>
</dl>
<div class="privacy_content">
<!--{@ privateMktApprovalOption}-->
<div class="privacy_big_box">
<div class="privacy_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" id="privateMarketingConsentOptionFl" name="privateMarketingConsentOptionFl" value="y" <!--{? data['privateMarketingConsentOptionFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="privateMarketingConsentOptionFl">{=nl2br(.informNm)}</label>
</span>
</dt>
<dd><button type="button" class="js_agreement_view" data-code="{.informCd}">{=__('전체보기')}</button></dd>
</dl>
<div class="privacy_content">
<div>{=nl2br(.content)}</div>
</div>
</div>
</div>
<!--{/}-->
</div>
</div>
</div>
<!--{/ // privateMktApprovalOption modeFl end}-->
<!--{? isUseAdvertisingReceive}-->
<div class="privacy_big_box">
<div class="privacy_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" id="termsAgree7" name=""/>
<label for="termsAgree7">({=__('선택')}) {=__('광고성 수신 동의')}</label>
</span>
</dt>
<dd><button type="button">{=__('내용보기')} ▼</button></dd>
</dl>
<div class="privacy_content">
<div class="privacy_big_box">
<div class="privacy_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" id="advertisingReceiveAll" name="" <!--{? data['maillingFl'] == 'y' || data['smsFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="advertisingReceiveAll">{=nl2br(advertisingReceive['informNm'])}</label>
</span>
</dt>
<dd><button type="button" class="js_agreement_view" data-code="{advertisingReceive['informCd']}">{=__('전체보기')}</button></dd>
</dl>
<div class="privacy_content">
<div>{=nl2br(advertisingReceive['content'])}</div>
</div>
</div>
</div>
<div class="js_advertising_channel_box" style="display:flex; flex-direction:column; align-items:flex-start; margin:10px;">
<!--{? advertisingReceiveMailUse == 'y'}-->
<span class="inp_chk">
<input type="checkbox" id="advertisingReceiveMail" name="maillingFl" value="y" class="js_advertising_channel" <!--{? data['maillingFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="advertisingReceiveMail">{=__('이메일 수신 동의')}</label>
</span>
<!--{/}-->
<!--{? advertisingReceiveSmsUse == 'y'}-->
<span class="inp_chk">
<input type="checkbox" id="advertisingReceiveSms" name="smsFl" value="y" class="js_advertising_channel" <!--{? data['smsFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="advertisingReceiveSms">{=__('모바일 메시지 수신 동의')}</label>
</span>
<!--{/}-->
</div>
</div>
</div>
</div>
<!--{/ // isUseAdvertisingReceive end}-->
<!--{/}-->
<script type="text/javascript">
$(document).ready(function() {
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
/*
* 약관 내용보기 이벤트
*/
$('.js_agreement_view').on('click', function () {
window.open('../service/agreement.php?code=' + $(this).data('code'));
});
$('.privacy_box dd button:not(.js_agreement_view)').on({
'click':function(){
var privacy_dp = $(this).parent().parent().parent().find('> .privacy_content').css('display');
if(privacy_dp == 'none'){
 
data/skin/mobile/moment/mypage/my_page.html+95-1
변경 전my_page.html
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
if (isTargetChecked) {
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
gd_select_email_domain('email');
<!--{? (joinField.marriFl.use == 'y' && joinField.marriDate.use == 'y') || joinField.birthDt.use == 'y' }-->
 
변경 후my_page.html
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
if (isTargetChecked) {
}
});
/*
* 광고성 수신 동의 ↔ 이메일/모바일 수신 동의 ↔ 마케팅 목적 개인정보 수집 및 이용 동의 연동
* - 광고성 수신 동의 체크 → 이메일/모바일 채널 일괄 + 마케팅 목적 동의 ON
* - 채널(1개 이상) 체크 → 광고성 수신 동의 + 마케팅 목적 동의 ON (채널은 각각 개별 토글)
*/
const checkMarketingApproval = function () {
$('#privateMarketingConsentOptionFl', $formJoin).prop('checked', true).val('y').siblings('label').addClass('on');
$('#termsAgree6', $formJoin).prop('checked', true).siblings('label').addClass('on');
};
$formJoin.on('change', '#termsAgree7', function () {
const isChecked = $(this).prop('checked') === true;
$(this).siblings('label').toggleClass('on', isChecked);
$('#advertisingReceiveAll', $formJoin).prop('checked', isChecked)
.siblings('label').toggleClass('on', isChecked);
$('.js_advertising_channel', $formJoin).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formJoin.on('change', '#advertisingReceiveAll', function () {
const isChecked = $(this).prop('checked') === true;
$('.js_advertising_channel', $formJoin).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
// 전체선택(advertisingReceiveAll) 직접 조작 시 광고성 수신 동의(termsAgree7) 헤더도 동기화
$('#termsAgree7', $formJoin).prop('checked', isChecked).siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formJoin.on('change', '.js_advertising_channel', function () {
const anyChannelChecked = $('.js_advertising_channel:checked', $formJoin).length > 0;
const allChannelChecked = $('.js_advertising_channel', $formJoin).length > 0
&& $('.js_advertising_channel:not(:checked)', $formJoin).length === 0;
$('#advertisingReceiveAll', $formJoin).prop('checked', anyChannelChecked)
.siblings('label').toggleClass('on', anyChannelChecked);
if (anyChannelChecked) {
checkMarketingApproval();
}
// 수신 채널 전부 선택 시 광고성 수신 동의(termsAgree7) 헤더 ON (일부만이면 켜지 않음)
if (allChannelChecked) {
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
}
if ($(this).prop('checked') === false) {
$('#termsAgree7', $formJoin).prop('checked', false).siblings('label').removeClass('on');
}
});
// 마케팅 목적 개인정보 수집 및 이용 동의 체크 시 광고성 수신 동의·수신 채널 일괄 선택
const checkAdvertising = function () {
if ($('#termsAgree7', $formJoin).length === 0) {
return;
}
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
$('#advertisingReceiveAll', $formJoin).prop('checked', true).siblings('label').addClass('on');
$('.js_advertising_channel', $formJoin).prop('checked', true).val('y').siblings('label').addClass('on');
};
$formJoin.on('change', '#privateMarketingConsentOptionFl', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
} else {
$('#termsAgree6', $formJoin).prop('checked', false).siblings('label').removeClass('on');
}
});
// 마케팅 목적 개인정보 수집 및 이용 동의 그룹 [선택] 헤더 체크 시 광고성 수신 동의·수신 채널 연동
$formJoin.on('change', '#termsAgree6', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
}
});
// 그룹 하위 항목이 모두 선택되면 상위 [선택] 헤더도 선택 (하나라도 해제 시 해제) — 중첩 privacy_box 대응: 헤더 기준으로 그룹 판정
$formJoin.on('change', '.privacy_box :checkbox', function () {
$('#termsAgree3, #termsAgree4, #termsAgree5, #termsAgree6', $formJoin).each(function () {
const $header = $(this);
const $subs = $header.closest('.privacy_box').find(':checkbox').not($header);
if ($subs.length === 0) {
return;
}
const allChecked = $subs.not(':checked').length === 0;
$header.prop('checked', allChecked).siblings('label').toggleClass('on', allChecked);
});
});
// 초기 로드: 저장된 하위 선택 상태로 상위 [선택] 헤더 동기화
$('#termsAgree3, #termsAgree4, #termsAgree5, #termsAgree6', $formJoin).each(function () {
const $subs = $(this).closest('.privacy_box').find(':checkbox').not(this);
if ($subs.length > 0 && $subs.not(':checked').length === 0) {
$(this).prop('checked', true).siblings('label').addClass('on');
}
});
// 초기 로드: 수신 채널이 전부 선택된 상태면 광고성 수신 동의(termsAgree7) 헤더 ON
if ($('.js_advertising_channel', $formJoin).length > 0
&& $('.js_advertising_channel:not(:checked)', $formJoin).length === 0) {
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
}
gd_select_email_domain('email');
<!--{? (joinField.marriFl.use == 'y' && joinField.marriDate.use == 'y') || joinField.birthDt.use == 'y' }-->
 
data/skin/mobile/mygreek/member/join_agreement.html+176-2
변경 전join_agreement.html
<!-- //privacy_content -->
</div>
<!--{/}-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<p class="msg dn">
<span>{=__('이용약관과 개인정보 수집 및 이용에 대한 안내 모두 동의해주세요.')}</span>
</p>
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/*
* 14세 동의 항목 체크박스 이벤트
*/
/*
* 약관 내용보기 이벤트
*/
$('.privacy_box dd button').on({
'click':function(){
var privacy_dp = $(this).parent().parent().parent().find('> .privacy_content').css('display');
if(privacy_dp == 'none'){
}
});
 
 
 
 
 
 
window.addEventListener('message', function (e) {
if (e.origin === "{=authDreamUrl}") {
$('#layerSearch').remove();
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
});
 
/**
 
변경 후join_agreement.html
<!-- //privacy_content -->
</div>
<!--{/}-->
<!--{? privateMarketingMember['modeFl']=='y'}-->
<div class="privacy_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" id="termsAgree6"/>
<label for="termsAgree6">({=__('선택')}) {=__('마케팅 목적 개인정보 수집 및 이용 동의')}</label>
</span>
</dt>
<dd><button type="button">{=__('내용보기')} ▼</button></dd>
</dl>
<div class="privacy_content">
<div class="privacy_big_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" class="sp" id="privateMktApprovalOption" name="privateMarketingConsentOptionFl"/>
<label for="privateMktApprovalOption">{=nl2br(privateMarketingMember['informNm'])}</label>
</span>
</dt>
<dd><button type="button" class="js_agreement_view" data-code="{privateMarketingMember['informCd']}">{=__('전체보기')}</button></dd>
</dl>
<div class="privacy_content">
<div>{=nl2br(privateMarketingMember['content'])}</div>
</div>
<!-- //privacy_content -->
</div>
<!-- //privacy_big_box -->
</div>
<!-- //privacy_content -->
</div>
<!--{/}-->
<!--{? isUseAdvertisingReceive}-->
<div class="privacy_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" id="termsAgree7"/>
<label for="termsAgree7">({=__('선택')}) {=__('광고성 수신 동의')}</label>
</span>
</dt>
<dd><button type="button">{=__('내용보기')} ▼</button></dd>
</dl>
<div class="privacy_content">
<div class="privacy_big_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" class="sp" id="advertisingReceiveAll"/>
<label for="advertisingReceiveAll">{=nl2br(advertisingReceive['informNm'])}</label>
</span>
</dt>
<dd><button type="button" class="js_agreement_view" data-code="{advertisingReceive['informCd']}">{=__('전체보기')}</button></dd>
</dl>
<div class="privacy_content">
<div>{=nl2br(advertisingReceive['content'])}</div>
</div>
<!-- //privacy_content -->
</div>
<!-- //privacy_big_box -->
<div class="js_advertising_channel_box" style="display:flex; flex-direction:column; align-items:flex-start; margin:10px;">
<!--{? advertisingReceiveMailUse=='y'}-->
<span class="inp_chk">
<input type="checkbox" class="sp js_advertising_channel" id="maillingFl" name="maillingFl"/>
<label for="maillingFl">{=__('이메일 수신 동의')}</label>
</span>
<!--{/}-->
<!--{? advertisingReceiveSmsUse=='y'}-->
<span class="inp_chk">
<input type="checkbox" class="sp js_advertising_channel" id="smsFl" name="smsFl"/>
<label for="smsFl">{=__('모바일 메시지 수신 동의')}</label>
</span>
<!--{/}-->
</div>
</div>
<!-- //privacy_content -->
</div>
<!--{/}-->
<p class="msg dn">
<span>{=__('이용약관과 개인정보 수집 및 이용에 대한 안내 모두 동의해주세요.')}</span>
</p>
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
}
});
 
/*
* 광고성 수신 동의 ↔ 이메일/모바일 수신 동의 ↔ 마케팅 목적 개인정보 수집 및 이용 동의 연동
* - 광고성 수신 동의 체크 → 이메일/모바일 채널 일괄 + 마케팅 목적 동의 ON
* - 채널(1개 이상) 체크 → 광고성 수신 동의 + 마케팅 목적 동의 ON (채널은 각각 개별 토글)
*/
const checkMarketingApproval = function () {
$('#privateMktApprovalOption', $formTerms).prop('checked', true).val('y').siblings('label').addClass('on');
$('#termsAgree6', $formTerms).prop('checked', true).siblings('label').addClass('on');
};
// 마케팅 목적 개인정보 수집 및 이용 동의 체크 시 광고성 수신 동의·수신 채널 일괄 선택
const checkAdvertising = function () {
if ($('#termsAgree7', $formTerms).length === 0) {
return;
}
$('#termsAgree7', $formTerms).prop('checked', true).siblings('label').addClass('on');
$('#advertisingReceiveAll', $formTerms).prop('checked', true).siblings('label').addClass('on');
$('.js_advertising_channel', $formTerms).prop('checked', true).val('y').siblings('label').addClass('on');
};
$formTerms.on('change', '#termsAgree7', function () {
const isChecked = $(this).prop('checked') === true;
$(this).siblings('label').toggleClass('on', isChecked);
$('#advertisingReceiveAll', $formTerms).prop('checked', isChecked)
.siblings('label').toggleClass('on', isChecked);
$('.js_advertising_channel', $formTerms).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formTerms.on('change', '#advertisingReceiveAll', function () {
const isChecked = $(this).prop('checked') === true;
$('.js_advertising_channel', $formTerms).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
} else {
$('#termsAgree7', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '.js_advertising_channel', function () {
const anyChannelChecked = $('.js_advertising_channel:checked', $formTerms).length > 0;
$('#advertisingReceiveAll', $formTerms).prop('checked', anyChannelChecked)
.siblings('label').toggleClass('on', anyChannelChecked);
if (anyChannelChecked) {
checkMarketingApproval();
}
if ($(this).prop('checked') === false) {
$('#termsAgree7', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '#privateMktApprovalOption', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
} else {
$('#termsAgree6', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '#termsAgree6', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
}
});
 
/*
* 14세 동의 항목 체크박스 이벤트
*/
/*
* 약관 내용보기 이벤트
*/
$('.privacy_box dd button:not(.js_agreement_view)').on({
'click':function(){
var privacy_dp = $(this).parent().parent().parent().find('> .privacy_content').css('display');
if(privacy_dp == 'none'){
}
});
 
// 약관 전문 새창 열기 (전체보기)
$('.js_agreement_view').on('click', function () {
window.open('../service/agreement.php?code=' + $(this).data('code'));
});
 
window.addEventListener('message', function (e) {
if (e.origin === "{=authDreamUrl}") {
$('#layerSearch').remove();
}
});
 
// 그룹 하위 항목이 모두 선택되면 상위 [선택] 헤더(termsAgree3~7)도 선택 (하나라도 해제 시 해제)
$formTerms.on('change', '.privacy_box :checkbox', function () {
const $box = $(this).closest('.privacy_box');
const $header = $box.children('dl').find(':checkbox');
const $subs = $box.find(':checkbox').not($header);
if ($subs.length === 0) {
return;
}
const allChecked = $subs.not(':checked').length === 0;
$header.prop('checked', allChecked).siblings('label').toggleClass('on', allChecked);
});
 
/*
* 전체 동의 역동기화 — 하위 체크박스가 모두 선택되면 전체 동의도 선택 (하나라도 해제되면 해제)
*/
const syncAllAgree = function () {
const $targets = $(':checkbox', $formTerms).not('#allAgree').not('#termsAgree');
const allChecked = $targets.length > 0 && $targets.not(':checked').length === 0;
$('#allAgree', $formTerms).prop('checked', allChecked)
.siblings('label').toggleClass('on', allChecked);
};
$formTerms.on('change', ':checkbox', function (e) {
if (e.target.id === 'allAgree' || e.target.id === 'termsAgree') {
return;
}
syncAllAgree();
});
});
 
/**
 
data/skin/mobile/mygreek/member/_join_view.html-22
변경 전_join_view.html
<div class="input_wrap">
<div class="input_title"></div>
<div class="input_content">
<!--{? joinField.maillingFl.use === 'y'}-->
<div class="description">
<span class="inp_chk">
<input type="checkbox" id="maillingFl" class="ignore" name="maillingFl" value="y" {? data.maillingFl=='y'}checked="checked"{/}/>
<label for="maillingFl">{=__('정보/이벤트 메일 수신에 동의합니다.')}</label>
</span>
</div>
<!--{/}-->
<div class="description">※ {=__('아이디 비밀번호 찾기에 활용 되므로 정확하게 입력해 주세요')}</div>
</div>
</div>
<!--{ / }-->
</div>
</div>
<!--{? joinField.smsFl.use === 'y'}-->
<div class="input_wrap">
<div class="input_title"></div>
<div class="input_content">
<div class="description">
<span class="inp_chk">
<input type="checkbox" class="ignore" id="smsFl" name="smsFl" value="y" {? data.smsFl=='y'}checked="checked"{/}/>
<label for="smsFl">{=__('정보/이벤트 SMS 수신에 동의합니다.')}</label>
</span>
</div>
</div>
</div>
<!--{/}-->
<!--{/}-->
<!--{? joinField.phone.use == 'y'}-->
<div class="input_wrap">
 
변경 후_join_view.html
<div class="input_wrap">
<div class="input_title"></div>
<div class="input_content">
 
 
 
 
 
 
 
 
<div class="description">※ {=__('아이디 비밀번호 찾기에 활용 되므로 정확하게 입력해 주세요')}</div>
</div>
</div>
<!--{ / }-->
</div>
</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<!--{/}-->
<!--{? joinField.phone.use == 'y'}-->
<div class="input_wrap">
 
data/skin/mobile/mygreek/member/_join_view_other.html+88-3
변경 전_join_view_other.html
<!--{/}-->
</div>
<!--{/}-->
<!--{? privateApprovalOption[0]['modeFl'] == 'y' || privateConsign[0]['modeFl']=='y' || privateOffer[0]['modeFl']=='y'}-->
<p class="tit">{=__('약관동의현황')}</p>
<!--{? privateApprovalOption[0]['modeFl'] == 'y'}-->
<div class="privacy_big_box">
</div>
</div>
<!--{/ // privateOffer modeFl end}-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<!--{/}-->
<script type="text/javascript">
$(document).ready(function() {
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
/*
* 약관 내용보기 이벤트
*/
$('.privacy_box dd button').on({
 
 
 
'click':function(){
var privacy_dp = $(this).parent().parent().parent().find('> .privacy_content').css('display');
if(privacy_dp == 'none'){
 
변경 후_join_view_other.html
<!--{/}-->
</div>
<!--{/}-->
<!--{? privateApprovalOption[0]['modeFl'] == 'y' || privateConsign[0]['modeFl']=='y' || privateOffer[0]['modeFl']=='y' || privateMktApprovalOption[0]['modeFl']=='y' || isUseAdvertisingReceive}-->
<p class="tit">{=__('약관동의현황')}</p>
<!--{? privateApprovalOption[0]['modeFl'] == 'y'}-->
<div class="privacy_big_box">
</div>
</div>
<!--{/ // privateOffer modeFl end}-->
<!--{? privateMktApprovalOption[0]['modeFl'] == 'y'}-->
<div class="privacy_big_box">
<div class="privacy_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" id="termsAgree6" name=""/>
<label for="termsAgree6">({=__('선택')}) {=__('마케팅 목적 개인정보 수집 및 이용 동의')}</label>
</span>
</dt>
<dd><button type="button">{=__('내용보기')} ▼</button></dd>
</dl>
<div class="privacy_content">
<!--{@ privateMktApprovalOption}-->
<div class="privacy_big_box">
<div class="privacy_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" id="privateMarketingConsentOptionFl" name="privateMarketingConsentOptionFl" value="y" <!--{? data['privateMarketingConsentOptionFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="privateMarketingConsentOptionFl">{=nl2br(.informNm)}</label>
</span>
</dt>
<dd><button type="button" class="js_agreement_view" data-code="{.informCd}">{=__('전체보기')}</button></dd>
</dl>
<div class="privacy_content">
<div>{=nl2br(.content)}</div>
</div>
</div>
</div>
<!--{/}-->
</div>
</div>
</div>
<!--{/ // privateMktApprovalOption modeFl end}-->
<!--{? isUseAdvertisingReceive}-->
<div class="privacy_big_box">
<div class="privacy_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" id="termsAgree7" name=""/>
<label for="termsAgree7">({=__('선택')}) {=__('광고성 수신 동의')}</label>
</span>
</dt>
<dd><button type="button">{=__('내용보기')} ▼</button></dd>
</dl>
<div class="privacy_content">
<div class="privacy_big_box">
<div class="privacy_box">
<dl>
<dt>
<span class="inp_chk">
<input type="checkbox" id="advertisingReceiveAll" name="" <!--{? data['maillingFl'] == 'y' || data['smsFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="advertisingReceiveAll">{=nl2br(advertisingReceive['informNm'])}</label>
</span>
</dt>
<dd><button type="button" class="js_agreement_view" data-code="{advertisingReceive['informCd']}">{=__('전체보기')}</button></dd>
</dl>
<div class="privacy_content">
<div>{=nl2br(advertisingReceive['content'])}</div>
</div>
</div>
</div>
<div class="js_advertising_channel_box" style="display:flex; flex-direction:column; align-items:flex-start; margin:10px;">
<!--{? advertisingReceiveMailUse == 'y'}-->
<span class="inp_chk">
<input type="checkbox" id="advertisingReceiveMail" name="maillingFl" value="y" class="js_advertising_channel" <!--{? data['maillingFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="advertisingReceiveMail">{=__('이메일 수신 동의')}</label>
</span>
<!--{/}-->
<!--{? advertisingReceiveSmsUse == 'y'}-->
<span class="inp_chk">
<input type="checkbox" id="advertisingReceiveSms" name="smsFl" value="y" class="js_advertising_channel" <!--{? data['smsFl'] == 'y'}-->checked="checked"<!--{/}-->/>
<label for="advertisingReceiveSms">{=__('모바일 메시지 수신 동의')}</label>
</span>
<!--{/}-->
</div>
</div>
</div>
</div>
<!--{/ // isUseAdvertisingReceive end}-->
<!--{/}-->
<script type="text/javascript">
$(document).ready(function() {
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
/*
* 약관 내용보기 이벤트
*/
$('.js_agreement_view').on('click', function () {
window.open('../service/agreement.php?code=' + $(this).data('code'));
});
$('.privacy_box dd button:not(.js_agreement_view)').on({
'click':function(){
var privacy_dp = $(this).parent().parent().parent().find('> .privacy_content').css('display');
if(privacy_dp == 'none'){
 
data/skin/mobile/mygreek/mypage/my_page.html+95-1
변경 전my_page.html
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
gd_select_email_domain('email');
 
<!--{? (joinField.marriFl.use == 'y' && joinField.marriDate.use == 'y') || joinField.birthDt.use == 'y' }-->
 
변경 후my_page.html
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy_box');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
}
});
 
/*
* 광고성 수신 동의 ↔ 이메일/모바일 수신 동의 ↔ 마케팅 목적 개인정보 수집 및 이용 동의 연동
* - 광고성 수신 동의 체크 → 이메일/모바일 채널 일괄 + 마케팅 목적 동의 ON
* - 채널(1개 이상) 체크 → 광고성 수신 동의 + 마케팅 목적 동의 ON (채널은 각각 개별 토글)
*/
const checkMarketingApproval = function () {
$('#privateMarketingConsentOptionFl', $formJoin).prop('checked', true).val('y').siblings('label').addClass('on');
$('#termsAgree6', $formJoin).prop('checked', true).siblings('label').addClass('on');
};
$formJoin.on('change', '#termsAgree7', function () {
const isChecked = $(this).prop('checked') === true;
$(this).siblings('label').toggleClass('on', isChecked);
$('#advertisingReceiveAll', $formJoin).prop('checked', isChecked)
.siblings('label').toggleClass('on', isChecked);
$('.js_advertising_channel', $formJoin).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formJoin.on('change', '#advertisingReceiveAll', function () {
const isChecked = $(this).prop('checked') === true;
$('.js_advertising_channel', $formJoin).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
// 전체선택(advertisingReceiveAll) 직접 조작 시 광고성 수신 동의(termsAgree7) 헤더도 동기화
$('#termsAgree7', $formJoin).prop('checked', isChecked).siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formJoin.on('change', '.js_advertising_channel', function () {
const anyChannelChecked = $('.js_advertising_channel:checked', $formJoin).length > 0;
const allChannelChecked = $('.js_advertising_channel', $formJoin).length > 0
&& $('.js_advertising_channel:not(:checked)', $formJoin).length === 0;
$('#advertisingReceiveAll', $formJoin).prop('checked', anyChannelChecked)
.siblings('label').toggleClass('on', anyChannelChecked);
if (anyChannelChecked) {
checkMarketingApproval();
}
// 수신 채널 전부 선택 시 광고성 수신 동의(termsAgree7) 헤더 ON (일부만이면 켜지 않음)
if (allChannelChecked) {
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
}
if ($(this).prop('checked') === false) {
$('#termsAgree7', $formJoin).prop('checked', false).siblings('label').removeClass('on');
}
});
// 마케팅 목적 개인정보 수집 및 이용 동의 체크 시 광고성 수신 동의·수신 채널 일괄 선택
const checkAdvertising = function () {
if ($('#termsAgree7', $formJoin).length === 0) {
return;
}
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
$('#advertisingReceiveAll', $formJoin).prop('checked', true).siblings('label').addClass('on');
$('.js_advertising_channel', $formJoin).prop('checked', true).val('y').siblings('label').addClass('on');
};
$formJoin.on('change', '#privateMarketingConsentOptionFl', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
} else {
$('#termsAgree6', $formJoin).prop('checked', false).siblings('label').removeClass('on');
}
});
// 마케팅 목적 개인정보 수집 및 이용 동의 그룹 [선택] 헤더 체크 시 광고성 수신 동의·수신 채널 연동
$formJoin.on('change', '#termsAgree6', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
}
});
// 그룹 하위 항목이 모두 선택되면 상위 [선택] 헤더도 선택 (하나라도 해제 시 해제) — 중첩 privacy_box 대응: 헤더 기준으로 그룹 판정
$formJoin.on('change', '.privacy_box :checkbox', function () {
$('#termsAgree3, #termsAgree4, #termsAgree5, #termsAgree6', $formJoin).each(function () {
const $header = $(this);
const $subs = $header.closest('.privacy_box').find(':checkbox').not($header);
if ($subs.length === 0) {
return;
}
const allChecked = $subs.not(':checked').length === 0;
$header.prop('checked', allChecked).siblings('label').toggleClass('on', allChecked);
});
});
// 초기 로드: 저장된 하위 선택 상태로 상위 [선택] 헤더 동기화
$('#termsAgree3, #termsAgree4, #termsAgree5, #termsAgree6', $formJoin).each(function () {
const $subs = $(this).closest('.privacy_box').find(':checkbox').not(this);
if ($subs.length > 0 && $subs.not(':checked').length === 0) {
$(this).prop('checked', true).siblings('label').addClass('on');
}
});
// 초기 로드: 수신 채널이 전부 선택된 상태면 광고성 수신 동의(termsAgree7) 헤더 ON
if ($('.js_advertising_channel', $formJoin).length > 0
&& $('.js_advertising_channel:not(:checked)', $formJoin).length === 0) {
$('#termsAgree7', $formJoin).prop('checked', true).siblings('label').addClass('on');
}
 
gd_select_email_domain('email');
 
<!--{? (joinField.marriFl.use == 'y' && joinField.marriDate.use == 'y') || joinField.birthDt.use == 'y' }-->
 
data/skin/mobile/story_g/member/join_agreement.html+177-1
변경 전join_agreement.html
</div>
</div>
<!--{/}-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<p class="msg dn">
<span>{=__('이용약관과 개인정보 수집 및 이용에 대한 안내 모두 동의해주세요.')}</span>
</p>
$(document).ready(function () {
var body = $('body'), $formTerms = $('#formTerms');
 
 
 
 
 
 
$('#btnNextStep').click(function () {
var pass = true;
/*
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy-item');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
window.addEventListener('message', function (e) {
if (e.origin === "{=authDreamUrl}") {
$('#layerSearch').remove();
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
});
/**
* 아이폰+페이스북앱에서 location.href로 페이지 이동으로 처리시
 
변경 후join_agreement.html
</div>
</div>
<!--{/}-->
<!--{? privateMarketingMember['modeFl']=='y'}-->
<div class="privacy-item toggle-item toggle-open">
<div class="privacy-head">
<span class="inp_chk">
<input type="checkbox" class="sp" id="termsAgree6" name="">
<label for="termsAgree6">({=__('선택')}) {=__('마케팅 목적 개인정보 수집 및 이용 동의')}</label>
</span>
<div class="option">
<button type="button" class="btn-reset toggle-switch">{=__('내용보기')} ▼</button>
</div>
</div>
<div class="privacy-body">
<div class="privacy-item toggle-item toggle-open">
<div class="privacy-head">
<span class="inp_chk">
<input type="checkbox" class="sp" id="privateMktApprovalOption" name="privateMarketingConsentOptionFl">
<label for="privateMktApprovalOption">{=nl2br(privateMarketingMember['informNm'])}</label>
</span>
<div class="option">
<button type="button" class="btn-reset js_agreement_view" data-code="{privateMarketingMember['informCd']}">{=__('전체보기')}</button>
</div>
</div>
<div class="privacy-body">
{=nl2br(privateMarketingMember['content'])}
</div>
</div>
</div>
</div>
<!--{/}-->
<!--{? isUseAdvertisingReceive}-->
<div class="privacy-item toggle-item toggle-open">
<div class="privacy-head">
<span class="inp_chk">
<input type="checkbox" class="sp" id="termsAgree7" name="">
<label for="termsAgree7">({=__('선택')}) {=__('광고성 수신 동의')}</label>
</span>
<div class="option">
<button type="button" class="btn-reset toggle-switch">{=__('내용보기')} ▼</button>
</div>
</div>
<div class="privacy-body">
<div class="privacy-item toggle-item toggle-open">
<div class="privacy-head">
<span class="inp_chk">
<input type="checkbox" class="sp" id="advertisingReceiveAll" name="">
<label for="advertisingReceiveAll">{=nl2br(advertisingReceive['informNm'])}</label>
</span>
<div class="option">
<button type="button" class="btn-reset js_agreement_view" data-code="{advertisingReceive['informCd']}">{=__('전체보기')}</button>
</div>
</div>
<div class="privacy-body">
{=nl2br(advertisingReceive['content'])}
</div>
</div>
<div class="js_advertising_channel_box">
<!--{? advertisingReceiveMailUse=='y'}-->
<div class="privacy-item">
<div class="privacy-head">
<span class="inp_chk">
<input type="checkbox" class="sp js_advertising_channel" id="maillingFl" name="maillingFl">
<label for="maillingFl">{=__('이메일 수신 동의')}</label>
</span>
</div>
</div>
<!--{/}-->
<!--{? advertisingReceiveSmsUse=='y'}-->
<div class="privacy-item">
<div class="privacy-head">
<span class="inp_chk">
<input type="checkbox" class="sp js_advertising_channel" id="smsFl" name="smsFl">
<label for="smsFl">{=__('모바일 메시지 수신 동의')}</label>
</span>
</div>
</div>
<!--{/}-->
</div>
</div>
</div>
<!--{/}-->
<p class="msg dn">
<span>{=__('이용약관과 개인정보 수집 및 이용에 대한 안내 모두 동의해주세요.')}</span>
</p>
$(document).ready(function () {
var body = $('body'), $formTerms = $('#formTerms');
 
// 약관 전문 새창 열기 (전체보기)
body.on('click', '.js_agreement_view', function () {
window.open('../service/agreement.php?code=' + $(this).data('code'));
});
 
$('#btnNextStep').click(function () {
var pass = true;
/*
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy-item');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
}
});
 
/*
* 광고성 수신 동의 ↔ 이메일/모바일 수신 동의 ↔ 마케팅 목적 개인정보 수집 및 이용 동의 연동
* - 광고성 수신 동의 체크 → 이메일/모바일 채널 일괄 + 마케팅 목적 동의 ON
* - 채널(1개 이상) 체크 → 광고성 수신 동의 + 마케팅 목적 동의 ON (채널은 각각 개별 토글)
*/
const checkMarketingApproval = function () {
$('#privateMktApprovalOption', $formTerms).prop('checked', true).val('y').siblings('label').addClass('on');
$('#termsAgree6', $formTerms).prop('checked', true).siblings('label').addClass('on');
};
// 마케팅 목적 개인정보 수집 및 이용 동의 체크 시 광고성 수신 동의·수신 채널 일괄 선택
const checkAdvertising = function () {
if ($('#termsAgree7', $formTerms).length === 0) {
return;
}
$('#termsAgree7', $formTerms).prop('checked', true).siblings('label').addClass('on');
$('#advertisingReceiveAll', $formTerms).prop('checked', true).siblings('label').addClass('on');
$('.js_advertising_channel', $formTerms).prop('checked', true).val('y').siblings('label').addClass('on');
};
$formTerms.on('change', '#termsAgree7', function () {
const isChecked = $(this).prop('checked') === true;
$(this).siblings('label').toggleClass('on', isChecked);
$('#advertisingReceiveAll', $formTerms).prop('checked', isChecked)
.siblings('label').toggleClass('on', isChecked);
$('.js_advertising_channel', $formTerms).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$formTerms.on('change', '#advertisingReceiveAll', function () {
const isChecked = $(this).prop('checked') === true;
$('.js_advertising_channel', $formTerms).prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
} else {
$('#termsAgree7', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '.js_advertising_channel', function () {
const anyChannelChecked = $('.js_advertising_channel:checked', $formTerms).length > 0;
$('#advertisingReceiveAll', $formTerms).prop('checked', anyChannelChecked)
.siblings('label').toggleClass('on', anyChannelChecked);
if (anyChannelChecked) {
checkMarketingApproval();
}
if ($(this).prop('checked') === false) {
$('#termsAgree7', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '#privateMktApprovalOption', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
} else {
$('#termsAgree6', $formTerms).prop('checked', false).siblings('label').removeClass('on');
}
});
$formTerms.on('change', '#termsAgree6', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
}
});
 
window.addEventListener('message', function (e) {
if (e.origin === "{=authDreamUrl}") {
$('#layerSearch').remove();
}
});
 
// 그룹 하위 항목이 모두 선택되면 상위 [선택] 헤더(termsAgree3~7)도 선택 (하나라도 해제 시 해제)
$formTerms.on('change', '.privacy-item.toggle-item .privacy-body :checkbox', function () {
const $group = $(this).closest('.privacy-body').closest('.privacy-item.toggle-item');
const $header = $group.children('.privacy-head').find(':checkbox');
const $subs = $group.children('.privacy-body').find(':checkbox');
if ($subs.length === 0) {
return;
}
const allChecked = $subs.not(':checked').length === 0;
$header.prop('checked', allChecked).siblings('label').toggleClass('on', allChecked);
});
 
/*
* 전체 동의 역동기화 — 하위 체크박스가 모두 선택되면 전체 동의도 선택 (하나라도 해제되면 해제)
*/
const syncAllAgree = function () {
const $targets = $(':checkbox', $formTerms).not('#allAgree').not('#termsAgree');
const allChecked = $targets.length > 0 && $targets.not(':checked').length === 0;
$('#allAgree', $formTerms).prop('checked', allChecked)
.siblings('label').toggleClass('on', allChecked);
};
$formTerms.on('change', ':checkbox', function (e) {
if (e.target.id === 'allAgree' || e.target.id === 'termsAgree') {
return;
}
syncAllAgree();
});
});
/**
* 아이폰+페이스북앱에서 location.href로 페이지 이동으로 처리시
 
data/skin/mobile/story_g/member/_join_view.html-18
변경 전_join_view.html
<div class="input_wrap">
<div class="input_title"></div>
<div class="input_content">
<!--{? joinField.maillingFl.use === 'y'}-->
<div class="description chk">
<span class="inp_chk">
<input type="checkbox" class="checkbox sp" id="maillingFl" name="maillingFl" value="y" {? data.maillingFl==
'y'}checked="checked"{/}>
<label for="maillingFl">{=__('정보/이벤트 메일 수신에 동의합니다.')}</label>
</span>
</div>
<!--{/}-->
<div class="description_wrap">
<div class="description">※ {=__('아이디 비밀번호 찾기에 활용 되므로 정확하게 입력해 주세요')}</div>
</div>
</div>
<!--{ / }-->
<!--{ / }-->
<!--{? joinField.smsFl.use === 'y'}-->
<div class="description chk">
<span class="inp_chk">
<input type="checkbox" class="checkbox sp" id="smsFl" name="smsFl" value="y" {? data.smsFl==
'y'}checked="checked"{/}>
<label for="smsFl">{=__('정보/이벤트 SMS 수신에 동의합니다.')}</label>
</span>
</div>
<!--{/}-->
</div>
</div>
<!--{/}--><!--{? joinField.phone.use == 'y'}-->
 
변경 후_join_view.html
<div class="input_wrap">
<div class="input_title"></div>
<div class="input_content">
 
 
 
 
 
 
 
 
 
<div class="description_wrap">
<div class="description">※ {=__('아이디 비밀번호 찾기에 활용 되므로 정확하게 입력해 주세요')}</div>
</div>
</div>
<!--{ / }-->
<!--{ / }-->
 
 
 
 
 
 
 
 
 
</div>
</div>
<!--{/}--><!--{? joinField.phone.use == 'y'}-->
 
data/skin/mobile/story_g/member/_join_view_other.html+89-2
변경 전_join_view_other.html
<!--{/}-->
</div>
<!--{/}-->
<!--{? privateApprovalOption[0]['modeFl'] == 'y' || privateConsign[0]['modeFl']=='y' || privateOffer[0]['modeFl']=='y'}-->
<div class="account_title">
<b class="title">{=__('약관동의현황')}</b>
</div>
</div>
</div>
<!--{/ // privateOffer modeFl end}-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<!--{/}-->
<script type="text/javascript">
$(document).ready(function () {
e.preventDefault();
$('p.msg').addClass('hidden');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy-item');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
}
});
 
 
 
 
 
 
<!--{ ? useAppleLogin }-->
$("#apple_btn").on("click", startClicked);
function startClicked(){
 
변경 후_join_view_other.html
<!--{/}-->
</div>
<!--{/}-->
<!--{? privateApprovalOption[0]['modeFl'] == 'y' || privateConsign[0]['modeFl']=='y' || privateOffer[0]['modeFl']=='y' || privateMktApprovalOption[0]['modeFl']=='y' || isUseAdvertisingReceive}-->
<div class="account_title">
<b class="title">{=__('약관동의현황')}</b>
</div>
</div>
</div>
<!--{/ // privateOffer modeFl end}-->
<!--{? privateMktApprovalOption[0]['modeFl'] == 'y'}-->
<div class="privacy-item toggle-item toggle-open">
<div class="privacy-head">
<span class="inp_chk">
<input type="checkbox" class="sp" id="termsAgree6" name="">
<label for="termsAgree6">({=__('선택')}) {=__('마케팅 목적 개인정보 수집 및 이용 동의')}</label>
</span>
<div class="option">
<button type="button" class="btn-reset toggle-switch">{=__('내용보기')} ▼</button>
</div>
</div>
<div class="privacy-body">
<!--{@ privateMktApprovalOption}-->
<div class="privacy-item toggle-item toggle-open">
<div class="privacy-head">
<span class="inp_chk">
<input type="checkbox" class="sp" id="privateMarketingConsentOptionFl" name="privateMarketingConsentOptionFl" value="y" <!--{? data['privateMarketingConsentOptionFl'] == 'y'}-->checked="checked"<!--{/}-->>
<label class="single <!--{? data['privateMarketingConsentOptionFl'] == 'y'}-->on<!--{/}-->" for="privateMarketingConsentOptionFl">{=nl2br(.informNm)}</label>
</span>
<div class="option">
<button type="button" class="btn-reset js_agreement_view" data-code="{.informCd}">{=__('전체보기')}</button>
</div>
</div>
<div class="privacy-body">
{=nl2br(.content)}
</div>
</div>
<!--{/}-->
</div>
</div>
<!--{/ // privateMktApprovalOption modeFl end}-->
<!--{? isUseAdvertisingReceive}-->
<div class="privacy-item toggle-item toggle-open">
<div class="privacy-head">
<span class="inp_chk">
<input type="checkbox" class="sp" id="termsAgree7" name="">
<label for="termsAgree7">({=__('선택')}) {=__('광고성 수신 동의')}</label>
</span>
<div class="option">
<button type="button" class="btn-reset toggle-switch">{=__('내용보기')} ▼</button>
</div>
</div>
<div class="privacy-body">
<div class="privacy-item toggle-item toggle-open">
<div class="privacy-head">
<span class="inp_chk">
<input type="checkbox" class="sp" id="advertisingReceiveAll" <!--{? data['maillingFl'] == 'y' || data['smsFl'] == 'y'}-->checked="checked"<!--{/}-->>
<label class="single <!--{? data['maillingFl'] == 'y' || data['smsFl'] == 'y'}-->on<!--{/}-->" for="advertisingReceiveAll">{=nl2br(advertisingReceive['informNm'])}</label>
</span>
<div class="option">
<button type="button" class="btn-reset js_agreement_view" data-code="{advertisingReceive['informCd']}">{=__('전체보기')}</button>
</div>
</div>
<div class="privacy-body">
{=nl2br(advertisingReceive['content'])}
</div>
</div>
<div class="js_advertising_channel_box" style="display:flex; flex-direction:column; align-items:flex-start; margin:10px;">
<!--{? advertisingReceiveMailUse == 'y'}-->
<div class="privacy-item">
<div class="privacy-head">
<span class="inp_chk">
<input type="checkbox" class="sp js_advertising_channel" id="advertisingReceiveMail" name="maillingFl" value="y" <!--{? data['maillingFl'] == 'y'}-->checked="checked"<!--{/}-->>
<label class="single <!--{? data['maillingFl'] == 'y'}-->on<!--{/}-->" for="advertisingReceiveMail">{=__('이메일 수신 동의')}</label>
</span>
</div>
</div>
<!--{/}-->
<!--{? advertisingReceiveSmsUse == 'y'}-->
<div class="privacy-item">
<div class="privacy-head">
<span class="inp_chk">
<input type="checkbox" class="sp js_advertising_channel" id="advertisingReceiveSms" name="smsFl" value="y" <!--{? data['smsFl'] == 'y'}-->checked="checked"<!--{/}-->>
<label class="single <!--{? data['smsFl'] == 'y'}-->on<!--{/}-->" for="advertisingReceiveSms">{=__('모바일 메시지 수신 동의')}</label>
</span>
</div>
</div>
<!--{/}-->
</div>
</div>
</div>
<!--{/ // isUseAdvertisingReceive end}-->
<!--{/}-->
<script type="text/javascript">
$(document).ready(function () {
e.preventDefault();
$('p.msg').addClass('hidden');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.privacy-item');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
 
if (isTargetChecked) {
}
});
 
// 마수동/광수동 약관 전문 새창 열기 (전체보기)
$('.js_agreement_view').on('click', function () {
window.open('../service/agreement.php?code=' + $(this).data('code'));
});
 
<!--{ ? useAppleLogin }-->
$("#apple_btn").on("click", startClicked);
function startClicked(){
 
data/skin/mobile/story_g/mypage/my_page.html+97-1
변경 전my_page.html
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.terms-view');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5');
var isTargetChecked = $target.prop('checked') === true;
if (isTargetChecked) {
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
select_email_domain('email');
<!--{? (joinField.marriFl.use == 'y' && joinField.marriDate.use == 'y') || joinField.birthDt.use == 'y' }-->
 
변경 후my_page.html
e.preventDefault();
$('p.msg').addClass('dn');
var $target = $(e.target), $label = $target.siblings('label'), $termsView = $target.closest('.terms-view');
var isTermsAgreeSelect = (e.target.id === 'termsAgree3') || (e.target.id === 'termsAgree4') || (e.target.id === 'termsAgree5') || (e.target.id === 'termsAgree6') || (e.target.id === 'termsAgree7');
var isTargetChecked = $target.prop('checked') === true;
if (isTargetChecked) {
}
});
/*
* 광고성 수신 동의 ↔ 이메일/모바일 수신 동의 ↔ 마케팅 목적 개인정보 수집 및 이용 동의 연동 (마이페이지 전용)
* - 광고성 수신 동의 체크 → 이메일/모바일 채널 일괄 + 마케팅 목적 동의 ON
* - 채널(1개 이상) 체크 → 광고성 수신 동의 + 마케팅 목적 동의 ON (채널은 각각 개별 토글)
* - 이 스크립트는 마이페이지(my_page.html)에서만 로드되므로 $(document) 위임으로 바인딩해도 회원가입 화면엔 적용되지 않음
* (광고성 체크박스가 #formJoin 밖에 렌더되는 경우가 있어 $formJoin 스코프는 사용하지 않음)
*/
const checkMarketingApproval = function () {
$('#privateMarketingConsentOptionFl').prop('checked', true).val('y').siblings('label').addClass('on');
$('#termsAgree6').prop('checked', true).siblings('label').addClass('on');
};
$(document).on('change', '#termsAgree7', function () {
const isChecked = $(this).prop('checked') === true;
$(this).siblings('label').toggleClass('on', isChecked);
$('#advertisingReceiveAll').prop('checked', isChecked)
.siblings('label').toggleClass('on', isChecked);
$('.js_advertising_channel').prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$(document).on('change', '#advertisingReceiveAll', function () {
const isChecked = $(this).prop('checked') === true;
$('.js_advertising_channel').prop('checked', isChecked).val(isChecked ? 'y' : 'n')
.siblings('label').toggleClass('on', isChecked);
// 전체선택(advertisingReceiveAll) 직접 조작 시 광고성 수신 동의(termsAgree7) 헤더도 동기화
$('#termsAgree7').prop('checked', isChecked).siblings('label').toggleClass('on', isChecked);
if (isChecked) {
checkMarketingApproval();
}
});
$(document).on('change', '.js_advertising_channel', function () {
const anyChannelChecked = $('.js_advertising_channel:checked').length > 0;
const allChannelChecked = $('.js_advertising_channel').length > 0
&& $('.js_advertising_channel:not(:checked)').length === 0;
$('#advertisingReceiveAll').prop('checked', anyChannelChecked)
.siblings('label').toggleClass('on', anyChannelChecked);
if (anyChannelChecked) {
checkMarketingApproval();
}
// 수신 채널 전부 선택 시 광고성 수신 동의(termsAgree7) 헤더 ON (일부만이면 켜지 않음)
if (allChannelChecked) {
$('#termsAgree7').prop('checked', true).siblings('label').addClass('on');
}
if ($(this).prop('checked') === false) {
$('#termsAgree7').prop('checked', false).siblings('label').removeClass('on');
}
});
// 마케팅 목적 개인정보 수집 및 이용 동의 체크 시 광고성 수신 동의·수신 채널 일괄 선택
const checkAdvertising = function () {
if ($('#termsAgree7').length === 0) {
return;
}
$('#termsAgree7').prop('checked', true).siblings('label').addClass('on');
$('#advertisingReceiveAll').prop('checked', true).siblings('label').addClass('on');
$('.js_advertising_channel').prop('checked', true).val('y').siblings('label').addClass('on');
};
$(document).on('change', '#privateMarketingConsentOptionFl', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
} else {
$('#termsAgree6').prop('checked', false).siblings('label').removeClass('on');
}
});
// 마케팅 목적 개인정보 수집 및 이용 동의 그룹 [선택] 헤더 체크 시 광고성 수신 동의·수신 채널 연동
$(document).on('change', '#termsAgree6', function () {
if ($(this).prop('checked') === true) {
checkAdvertising();
}
});
// 그룹 하위 항목이 모두 선택되면 상위 [선택] 헤더도 선택 (하나라도 해제 시 해제)
$(document).on('change', '.privacy-head :checkbox', function () {
$('#termsAgree3, #termsAgree4, #termsAgree5, #termsAgree6').each(function () {
const $header = $(this);
const $subs = $header.closest('.privacy-item').find(':checkbox').not($header);
if ($subs.length === 0) {
return;
}
const allChecked = $subs.not(':checked').length === 0;
$header.prop('checked', allChecked).siblings('label').toggleClass('on', allChecked);
});
});
// 초기 로드: 저장된 하위 선택 상태로 상위 [선택] 헤더 동기화
$('#termsAgree3, #termsAgree4, #termsAgree5, #termsAgree6').each(function () {
const $subs = $(this).closest('.privacy-item').find(':checkbox').not(this);
if ($subs.length > 0 && $subs.not(':checked').length === 0) {
$(this).prop('checked', true).siblings('label').addClass('on');
}
});
// 초기 로드: 수신 채널이 전부 선택된 상태면 광고성 수신 동의(termsAgree7) 헤더 ON
if ($('.js_advertising_channel').length > 0
&& $('.js_advertising_channel:not(:checked)').length === 0) {
$('#termsAgree7').prop('checked', true).siblings('label').addClass('on');
}
select_email_domain('email');
<!--{? (joinField.marriFl.use == 'y' && joinField.marriDate.use == 'y') || joinField.birthDt.use == 'y' }-->
 
지역별 추가배송비 구간별 반복 부과 옵션 추가
Admin/policy/delivery_regist.php+84-2
변경 전delivery_regist.php
</div>
</td>
</tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
</tbody>
<tbody>
<tr>
</script>
<script type="text/html" id="templateMethodEtc">
<tr>
<th>배송비 설정(<%=strTitle%>별)</th>
<td colspan="3">
<% if (deliveryConfigType == 'all') { %>
<table id="basicDeliveryConfigFeeForm" class="table table-cols mgb0">
</script>
<script type="text/html" id="templateMethodEtcRangeRepeat">
<tr>
<th>배송비 설정(<%=strTitle%>별)</th>
<td colspan="3">
<% if (deliveryConfigType == 'all') { %>
<table id="basicDeliveryConfigFeeForm" class="table table-cols mgb0">
</td>
</tr>
</script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<script type="text/javascript">
<!--
var data = <?=json_encode($data)?>;
* @param string fixMethod 배송비 책정 방법
*/
var defaultMethod, defaultDeliveryConfigType, defaultRangeRepeat;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
function form_delivery_charge(fixMethod) {
deliveryConfigType();
 
// 배송비설정 초기화
form_delivery_charge(fixFl);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
// 금액 입력부 초기화
$('input[name*=\'[unitStart]\']').number_only();
$('input[name*=\'[unitEnd]\']').number_only();
$('#basic_fixFl').change(function () {
fixFl = this.value;
form_delivery_charge(this.value);
 
});
 
// 공급사 구분 선택 이벤트
else {
$('#areaGroupSelect').hide();
}
 
});
 
// 출고지/반품/교환지 주소 토글
 
변경 후delivery_regist.php
</div>
</td>
</tr>
<tr id="areaChargeRepeatArea"<?= (in_array($data['basic']['fixFl'], ['count', 'weight']) && $data['basic']['areaFl'] == 'y') ? '' : ' style="display:none;"' ?>>
<th>지역별 추가배송비<br>부과방법
<button type="button" class="btn btn-xs js-area-charge-repeat-help"><span class="icon-tooltip"></span></button>
</th>
<td colspan="3">
<label class="radio-inline">
<input type="radio" name="basic[areaChargeRepeatFl]" value="n" <?= $checked['areaChargeRepeatFl']['n'] ?? '' ?> /> 배송비 구간에 상관없이 1회 부과
</label>
<label class="radio-inline">
<input type="radio" name="basic[areaChargeRepeatFl]" value="y" <?= $checked['areaChargeRepeatFl']['y'] ?? '' ?> /> 배송비 구간마다 반복 부과
</label>
</td>
</tr>
</tbody>
<tbody>
<tr>
</script>
<script type="text/html" id="templateMethodEtc">
<tr>
<th>배송비 <%= (fixMethod == 'count' || fixMethod == 'weight') ? '구간 ' : '' %>설정(<%=strTitle%>별)</th>
<td colspan="3">
<% if (deliveryConfigType == 'all') { %>
<table id="basicDeliveryConfigFeeForm" class="table table-cols mgb0">
</script>
<script type="text/html" id="templateMethodEtcRangeRepeat">
<tr>
<th>배송비 <%= (fixMethod == 'count' || fixMethod == 'weight') ? '구간 ' : '' %>설정(<%=strTitle%>별)</th>
<td colspan="3">
<% if (deliveryConfigType == 'all') { %>
<table id="basicDeliveryConfigFeeForm" class="table table-cols mgb0">
</td>
</tr>
</script>
<!-- 지역별 추가배송비 부과방법 도움말 (팝오버 콘텐츠) -->
<div id="areaChargeRepeatHelp" class="display-none">
<p><strong>배송비 구간에 상관없이 1회 부과</strong><br/>
- 주문한 상품의 수량, 무게에 상관없이 지역별 추가 배송비가 1회만 부과됩니다.</p>
<p><strong>배송비 구간마다 반복 부과</strong><br/>
- 배송비 구간 설정에서 입력한 수량, 무게 범위 구간마다 지역별 추가 배송비가 추가 부과됩니다.</p>
<table class="table table-cols">
<colgroup><col style="width:60%"/><col style="width:40%"/></colgroup>
<thead><tr><th>구매수량 범위</th><th>배송비</th></tr></thead>
<tbody>
<tr><td>0개 이상 ~ 2개 미만일 때</td><td>1,000원</td></tr>
<tr><td>3개 이상</td><td>3,000원</td></tr>
</tbody>
</table>
<p>: 지역별 배송비가 500원일 때, 4개 구매 시 지역별 배송비가 2회 부과되어<br/>
총 배송비는 3,000원 + (500원 x 2) = 4,000원</p>
<p>- 범위 반복이 설정된 경우 입력한 수량, 무게 단위로 지역별 배송비가 추가 부과됩니다.</p>
<table class="table table-cols">
<colgroup><col style="width:60%"/><col style="width:40%"/></colgroup>
<thead><tr><th>구매수량 범위</th><th>배송비</th></tr></thead>
<tbody>
<tr><td>0개 이상 ~ 2개 미만일 때</td><td>1,000원</td></tr>
<tr><td>2개 이상 ~ 2개 마다 반복 부과</td><td>2,000원</td></tr>
</tbody>
</table>
<p>: 지역별 배송비가 500원일 때, 6개 구매 시 지역별 배송비가 4회 부과되어<br/>
총 배송비는 1,000원 + (2,000원 x 3) + (500원 x 4) = 9,000원</p>
</div>
<style>
button.js-area-charge-repeat-help { background-color: transparent; margin: 0; }
.area-charge-repeat-popover { max-width: 480px; }
.area-charge-repeat-popover .popover-content { padding: 15px; }
.area-charge-repeat-popover table { margin-bottom: 8px; }
.area-charge-repeat-popover table td { text-align: left; }
.area-charge-repeat-popover .popover-content { text-align: left; }
</style>
<script type="text/javascript">
<!--
var data = <?=json_encode($data)?>;
* @param string fixMethod 배송비 책정 방법
*/
var defaultMethod, defaultDeliveryConfigType, defaultRangeRepeat;
 
/**
* 지역별 추가배송비 부과방법(1회/구간반복) 노출 토글
* 노출조건: 배송비 유형이 수량별/무게별 이고, 지역별 추가배송비가 "있음"일 때만
*/
function toggleAreaChargeRepeat() {
var fixFlVal = $('#basic_fixFl').val();
var areaFlVal = $('input[name="basic[areaFl]"]:checked').val();
if ((fixFlVal == 'count' || fixFlVal == 'weight') && areaFlVal == 'y') {
$('#areaChargeRepeatArea').show();
} else {
$('#areaChargeRepeatArea').hide();
}
}
 
function form_delivery_charge(fixMethod) {
deliveryConfigType();
 
// 배송비설정 초기화
form_delivery_charge(fixFl);
 
// 지역별 추가배송비 부과방법 노출 초기화
toggleAreaChargeRepeat();
 
// 지역별 추가배송비 부과방법 도움말 팝오버
$('.js-area-charge-repeat-help').popover({
trigger: 'hover',
container: '#content',
html: true,
sanitize: false,
placement: 'right',
content: function () {
return $('#areaChargeRepeatHelp').html();
},
template: '<div class="popover area-charge-repeat-popover" role="tooltip"><div class="arrow"></div><div class="popover-content"></div></div>'
});
 
// 금액 입력부 초기화
$('input[name*=\'[unitStart]\']').number_only();
$('input[name*=\'[unitEnd]\']').number_only();
$('#basic_fixFl').change(function () {
fixFl = this.value;
form_delivery_charge(this.value);
toggleAreaChargeRepeat();
});
 
// 공급사 구분 선택 이벤트
else {
$('#areaGroupSelect').hide();
}
toggleAreaChargeRepeat();
});
 
// 출고지/반품/교환지 주소 토글
 
Admin/provider/policy/delivery_regist.php+84-2
변경 전delivery_regist.php
</div>
</td>
</tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
</tbody>
<tbody>
<tr>
</script>
<script type="text/html" id="templateMethodEtc">
<tr>
<th>배송비 설정(<%=strTitle%>별)</th>
<td colspan="3">
<% if (deliveryConfigType == 'all') { %>
<table id="basicDeliveryConfigFeeForm" class="table table-cols mgb0">
</script>
<script type="text/html" id="templateMethodEtcRangeRepeat">
<tr>
<th>배송비 설정(<%=strTitle%>별)</th>
<td colspan="3">
<% if (deliveryConfigType == 'all') { %>
<table id="basicDeliveryConfigFeeForm" class="table table-cols mgb0">
</tr>
</script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<script type="text/javascript">
<!--
var data = <?=json_encode($data)?>;
* @param string fixMethod 배송비 책정 방법
*/
var defaultMethod, defaultDeliveryConfigType, defaultRangeRepeat;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
function form_delivery_charge(fixMethod) {
deliveryConfigType();
 
// 배송비설정 초기화
form_delivery_charge(fixFl);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
// 금액 입력 초기화
$('input[name*=\'[unitStart]\']').number_only();
$('input[name*=\'[unitEnd]\']').number_only();
console.log($('#basic_fixFl option:selected').val());
fixFl = this.value;
form_delivery_charge(this.value);
 
});
 
// 삭제 버튼 클릭시 이벤트
else {
$('#areaGroupSelect').hide();
}
 
});
 
// 출고지/반품/교환지 주소 토글
 
변경 후delivery_regist.php
</div>
</td>
</tr>
<tr id="areaChargeRepeatArea"<?= (in_array($data['basic']['fixFl'], ['count', 'weight']) && $data['basic']['areaFl'] == 'y') ? '' : ' style="display:none;"' ?>>
<th>지역별 추가배송비<br>부과방법
<button type="button" class="btn btn-xs js-area-charge-repeat-help"><span class="icon-tooltip"></span></button>
</th>
<td colspan="3">
<label class="radio-inline">
<input type="radio" name="basic[areaChargeRepeatFl]" value="n" <?= $checked['areaChargeRepeatFl']['n'] ?? '' ?> /> 배송비 구간에 상관없이 1회 부과
</label>
<label class="radio-inline">
<input type="radio" name="basic[areaChargeRepeatFl]" value="y" <?= $checked['areaChargeRepeatFl']['y'] ?? '' ?> /> 배송비 구간마다 반복 부과
</label>
</td>
</tr>
</tbody>
<tbody>
<tr>
</script>
<script type="text/html" id="templateMethodEtc">
<tr>
<th>배송비 <%= (fixMethod == 'count' || fixMethod == 'weight') ? '구간 ' : '' %>설정(<%=strTitle%>별)</th>
<td colspan="3">
<% if (deliveryConfigType == 'all') { %>
<table id="basicDeliveryConfigFeeForm" class="table table-cols mgb0">
</script>
<script type="text/html" id="templateMethodEtcRangeRepeat">
<tr>
<th>배송비 <%= (fixMethod == 'count' || fixMethod == 'weight') ? '구간 ' : '' %>설정(<%=strTitle%>별)</th>
<td colspan="3">
<% if (deliveryConfigType == 'all') { %>
<table id="basicDeliveryConfigFeeForm" class="table table-cols mgb0">
</tr>
</script>
 
<!-- 지역별 추가배송비 부과방법 도움말 (팝오버 콘텐츠) -->
<div id="areaChargeRepeatHelp" class="display-none">
<p><strong>배송비 구간에 상관없이 1회 부과</strong><br/>
- 주문한 상품의 수량, 무게에 상관없이 지역별 추가 배송비가 1회만 부과됩니다.</p>
<p><strong>배송비 구간마다 반복 부과</strong><br/>
- 배송비 구간 설정에서 입력한 수량, 무게 범위 구간마다 지역별 추가 배송비가 추가 부과됩니다.</p>
<table class="table table-cols">
<colgroup><col style="width:60%"/><col style="width:40%"/></colgroup>
<thead><tr><th>구매수량 범위</th><th>배송비</th></tr></thead>
<tbody>
<tr><td>0개 이상 ~ 2개 미만일 때</td><td>1,000원</td></tr>
<tr><td>3개 이상</td><td>3,000원</td></tr>
</tbody>
</table>
<p>: 지역별 배송비가 500원일 때, 4개 구매 시 지역별 배송비가 2회 부과되어<br/>
총 배송비는 3,000원 + (500원 x 2) = 4,000원</p>
<p>- 범위 반복이 설정된 경우 입력한 수량, 무게 단위로 지역별 배송비가 추가 부과됩니다.</p>
<table class="table table-cols">
<colgroup><col style="width:60%"/><col style="width:40%"/></colgroup>
<thead><tr><th>구매수량 범위</th><th>배송비</th></tr></thead>
<tbody>
<tr><td>0개 이상 ~ 2개 미만일 때</td><td>1,000원</td></tr>
<tr><td>2개 이상 ~ 2개 마다 반복 부과</td><td>2,000원</td></tr>
</tbody>
</table>
<p>: 지역별 배송비가 500원일 때, 6개 구매 시 지역별 배송비가 4회 부과되어<br/>
총 배송비는 1,000원 + (2,000원 x 3) + (500원 x 4) = 9,000원</p>
</div>
<style>
button.js-area-charge-repeat-help { background-color: transparent; margin: 0; }
.area-charge-repeat-popover { max-width: 480px; }
.area-charge-repeat-popover .popover-content { padding: 15px; }
.area-charge-repeat-popover table { margin-bottom: 8px; }
.area-charge-repeat-popover table td { text-align: left; }
.area-charge-repeat-popover .popover-content { text-align: left; }
</style>
<script type="text/javascript">
<!--
var data = <?=json_encode($data)?>;
* @param string fixMethod 배송비 책정 방법
*/
var defaultMethod, defaultDeliveryConfigType, defaultRangeRepeat;
 
/**
* 지역별 추가배송비 부과방법(1회/구간반복) 노출 토글
* 노출조건: 배송비 유형이 수량별/무게별 이고, 지역별 추가배송비가 "있음"일 때만
*/
function toggleAreaChargeRepeat() {
var fixFlVal = $('#basic_fixFl').val();
var areaFlVal = $('input[name="basic[areaFl]"]:checked').val();
if ((fixFlVal == 'count' || fixFlVal == 'weight') && areaFlVal == 'y') {
$('#areaChargeRepeatArea').show();
} else {
$('#areaChargeRepeatArea').hide();
}
}
 
function form_delivery_charge(fixMethod) {
deliveryConfigType();
 
// 배송비설정 초기화
form_delivery_charge(fixFl);
 
// 지역별 추가배송비 부과방법 노출 초기화
toggleAreaChargeRepeat();
 
// 지역별 추가배송비 부과방법 도움말 팝오버
$('.js-area-charge-repeat-help').popover({
trigger: 'hover',
container: '#content',
html: true,
sanitize: false,
placement: 'right',
content: function () {
return $('#areaChargeRepeatHelp').html();
},
template: '<div class="popover area-charge-repeat-popover" role="tooltip"><div class="arrow"></div><div class="popover-content"></div></div>'
});
 
// 금액 입력 초기화
$('input[name*=\'[unitStart]\']').number_only();
$('input[name*=\'[unitEnd]\']').number_only();
console.log($('#basic_fixFl option:selected').val());
fixFl = this.value;
form_delivery_charge(this.value);
toggleAreaChargeRepeat();
});
 
// 삭제 버튼 클릭시 이벤트
else {
$('#areaGroupSelect').hide();
}
toggleAreaChargeRepeat();
});
 
// 출고지/반품/교환지 주소 토글
 
Component/Cart/Cart.php+31-1
변경 전Cart.php
if ($sameGoodsDeliveryFl !== 'y') {
// 배송비 계산
if ($tmp['deliveryByCalculate'] === 'y') {
 
if ($getDeliveryInfo[$deliverySno]['rangeRepeat'] === 'y') {
//범위 반복 설정이 되어있는 경우
$deliveryStandardFinal = 0;
}
 
$goodsDeliveryPrice += ($deliveryStandardFinal * $conditionRepeat['price']);
 
}
}
}
}
if ($tmp['deliveryByStandard'] >= $cVal['unitStart'] && $tmp['deliveryByStandard'] < $cVal['unitEnd']) {
$goodsDeliveryPrice = $cVal['price'];
 
continue;
}
}
}
 
 
 
 
 
 
}
 
// 방문수령 배송비 무료 처리
if (isset($setDeliveryCond[$deliverySno][$gVal['goodsNo']]['goodsDeliveryCollectFl']) === false || empty($setDeliveryCond[$deliverySno][$gVal['goodsNo']]['goodsDeliveryCollectFl']) === true) {
$setDeliveryCond[$deliverySno][$gVal['goodsNo']]['goodsDeliveryCollectFl'] = $goodsDeliveryCollectFl;
}
 
 
 
 
 
 
}
}
// 배송비조건별 계산
// SCM 번호 설정
$scmNo = $deliveryCond['scmNo'];
 
 
 
// 배송비 계산
if ($deliveryCond['deliveryByCalculate'] === 'y') {
foreach ($setDeliveryCharge as $cKey => $cVal) {
$deliveryStandardFinal += 1;
}
$rangeRepeatPrice += ($deliveryStandardFinal * $cVal['price']);
 
}
}
}
//범위반복 미사용일 시
if ($deliveryCond['deliveryByStandard'] >= $cVal['unitStart'] && $deliveryCond['deliveryByStandard'] < $cVal['unitEnd']) {
$conditionSatisfy = true;
 
}
}
 
$checkZeroPrice = true;
StringUtils::strIsSet($deliveryCond['goodsDeliveryAreaPrice'], 0);
if ($deliveryCond['goodsDeliveryAreaPrice'] > 0) {
$deliveryCond['goodsDeliveryCollectPrice'] += $deliveryCond['goodsDeliveryAreaPrice'];
 
 
 
 
 
}
}
 
}
}
 
 
 
 
 
 
 
// 상품별 지역배송비 총 배송 금액
gd_isset($this->totalGoodsDeliveryAreaPrice[$key], 0);
if ($multiShippingFl == 'y') gd_isset($this->totalGoodsMultiDeliveryAreaPrice[$orderInfoCd][$key], 0);
 
변경 후Cart.php
if ($sameGoodsDeliveryFl !== 'y') {
// 배송비 계산
if ($tmp['deliveryByCalculate'] === 'y') {
$areaChargeRepeatCount = 1; // 지역별 추가배송비 반복 구간 순번 (기본 1회)
if ($getDeliveryInfo[$deliverySno]['rangeRepeat'] === 'y') {
//범위 반복 설정이 되어있는 경우
$deliveryStandardFinal = 0;
}
 
$goodsDeliveryPrice += ($deliveryStandardFinal * $conditionRepeat['price']);
$areaChargeRepeatCount = $deliveryStandardFinal + 1; // 범위반복: 첫 구간(1) + 반복 횟수
}
}
}
}
if ($tmp['deliveryByStandard'] >= $cVal['unitStart'] && $tmp['deliveryByStandard'] < $cVal['unitEnd']) {
$goodsDeliveryPrice = $cVal['price'];
$areaChargeRepeatCount = $cKey + 1; // 매칭 구간 순번
continue;
}
}
}
 
// 지역별 추가배송비 구간 반복 부과
// 부과방법이 '구간마다 반복(Y)'이면 매칭 구간 순번(N)만큼 지역배송비를 곱한다
if ($getDeliveryInfo[$deliverySno]['areaChargeRepeatFl'] === 'y' && $goodsDeliveryAreaPrice > 0) {
$goodsDeliveryAreaPrice = $goodsDeliveryAreaPrice * $areaChargeRepeatCount;
}
}
 
// 방문수령 배송비 무료 처리
if (isset($setDeliveryCond[$deliverySno][$gVal['goodsNo']]['goodsDeliveryCollectFl']) === false || empty($setDeliveryCond[$deliverySno][$gVal['goodsNo']]['goodsDeliveryCollectFl']) === true) {
$setDeliveryCond[$deliverySno][$gVal['goodsNo']]['goodsDeliveryCollectFl'] = $goodsDeliveryCollectFl;
}
 
// 지역별 추가배송비 '구간마다 반복(y)'인 경우, 매칭 구간 순번(N) 곱셈을 위해 getDeliveryCond 로 전달
if ($getDeliveryInfo[$deliverySno]['areaChargeRepeatFl'] === 'y' && $goodsDeliveryAreaPrice > 0) {
$setDeliveryCond[$deliverySno][$gVal['goodsNo']]['goodsDeliveryAreaPrice'] = $goodsDeliveryAreaPrice;
$goodsDeliveryAreaPrice = 0;
}
}
}
// 배송비조건별 계산
// SCM 번호 설정
$scmNo = $deliveryCond['scmNo'];
 
$areaChargeRepeatCount = 1; // 지역별 추가배송비 반복 구간 순번 (기본 1회)
 
// 배송비 계산
if ($deliveryCond['deliveryByCalculate'] === 'y') {
foreach ($setDeliveryCharge as $cKey => $cVal) {
$deliveryStandardFinal += 1;
}
$rangeRepeatPrice += ($deliveryStandardFinal * $cVal['price']);
$areaChargeRepeatCount = $deliveryStandardFinal + 1; // 범위반복: 첫 구간(1) + 반복 횟수
}
}
}
//범위반복 미사용일 시
if ($deliveryCond['deliveryByStandard'] >= $cVal['unitStart'] && $deliveryCond['deliveryByStandard'] < $cVal['unitEnd']) {
$conditionSatisfy = true;
$areaChargeRepeatCount = $cKey + 1; // 매칭 구간 순번
}
}
 
$checkZeroPrice = true;
StringUtils::strIsSet($deliveryCond['goodsDeliveryAreaPrice'], 0);
if ($deliveryCond['goodsDeliveryAreaPrice'] > 0) {
// 지역별 추가배송비 구간 반복 부과 (착불도 매칭 구간 순번 N만큼 합산)
$repeatedAreaPrice = $deliveryCond['goodsDeliveryAreaPrice'];
if ($deliveryInfo['areaChargeRepeatFl'] === 'y') {
$repeatedAreaPrice = $repeatedAreaPrice * $areaChargeRepeatCount;
}
$deliveryCond['goodsDeliveryCollectPrice'] += $repeatedAreaPrice;
}
}
 
}
}
 
// 지역별 추가배송비 구간 반복 부과
// 부과방법이 '구간마다 반복(Y)'이면 매칭 구간 순번(N)만큼 지역배송비를 곱한다
if ($deliveryInfo['areaChargeRepeatFl'] === 'y' && $deliveryCond['deliveryByCalculate'] === 'y' && !empty($deliveryCond['goodsDeliveryAreaPrice'])) {
$deliveryCond['goodsDeliveryAreaPrice'] = $deliveryCond['goodsDeliveryAreaPrice'] * $areaChargeRepeatCount;
}
 
// 상품별 지역배송비 총 배송 금액
gd_isset($this->totalGoodsDeliveryAreaPrice[$key], 0);
if ($multiShippingFl == 'y') gd_isset($this->totalGoodsMultiDeliveryAreaPrice[$orderInfoCd][$key], 0);
 
Component/Database/DBTableField.php+1
변경 전DBTableField.php
['val' => 'dmVisitAddressUseFl', 'typ' => 's', 'def' => 'n'], // 방문수령지 상품상세노출여부
['val' => 'rangeRepeat', 'typ' => 's', 'def' => 'n'], // 범위반복구간여부
['val' => 'addGoodsCountInclude', 'typ' => 's', 'def' => 'n'], // 수량별배송비 추가옵션수량 포함여부
 
];
// @formatter:on
 
 
변경 후DBTableField.php
['val' => 'dmVisitAddressUseFl', 'typ' => 's', 'def' => 'n'], // 방문수령지 상품상세노출여부
['val' => 'rangeRepeat', 'typ' => 's', 'def' => 'n'], // 범위반복구간여부
['val' => 'addGoodsCountInclude', 'typ' => 's', 'def' => 'n'], // 수량별배송비 추가옵션수량 포함여부
['val' => 'areaChargeRepeatFl', 'typ' => 's', 'def' => 'n'], // 지역별 추가배송비 부과방법 (y:구간마다 반복 / n:1회)
];
// @formatter:on
 
 
Component/Delivery/DeliveryCart.php+1
변경 전DeliveryCart.php
'rangeLimitWeight',
'rangeRepeat',
'addGoodsCountInclude',
 
'deliveryMethodFl',
'deliveryVisitPayFl',
'deliveryConfigType',
 
변경 후DeliveryCart.php
'rangeLimitWeight',
'rangeRepeat',
'addGoodsCountInclude',
'areaChargeRepeatFl',
'deliveryMethodFl',
'deliveryVisitPayFl',
'deliveryConfigType',
 
Component/Delivery/Delivery.php+1
변경 전Delivery.php
$data['basic']['dmVisitTypeDisplayFl'] = gd_isset($arrData['basic']['dmVisitTypeDisplayFl'], 'n');
$data['basic']['rangeRepeat'] = gd_isset($arrData['basic']['rangeRepeat'], 'n');
$data['basic']['addGoodsCountInclude'] = gd_isset($arrData['basic']['addGoodsCountInclude'], 'n');
 
$data['basic']['deliveryVisitPayFl'] = gd_isset($arrData['basic']['deliveryVisitPayFl'], 'y');
$data['basic']['dmVisitAddressUseFl'] = gd_isset($arrData['basic']['dmVisitAddressUseFl'], 'n');
$data['basic']['sameGoodsDeliveryFl'] = gd_isset($arrData['basic']['sameGoodsDeliveryFl'], 'n');
 
변경 후Delivery.php
$data['basic']['dmVisitTypeDisplayFl'] = gd_isset($arrData['basic']['dmVisitTypeDisplayFl'], 'n');
$data['basic']['rangeRepeat'] = gd_isset($arrData['basic']['rangeRepeat'], 'n');
$data['basic']['addGoodsCountInclude'] = gd_isset($arrData['basic']['addGoodsCountInclude'], 'n');
$data['basic']['areaChargeRepeatFl'] = $arrData['basic']['areaChargeRepeatFl'] ?? 'n';
$data['basic']['deliveryVisitPayFl'] = gd_isset($arrData['basic']['deliveryVisitPayFl'], 'y');
$data['basic']['dmVisitAddressUseFl'] = gd_isset($arrData['basic']['dmVisitAddressUseFl'], 'n');
$data['basic']['sameGoodsDeliveryFl'] = gd_isset($arrData['basic']['sameGoodsDeliveryFl'], 'n');
 
Controller/Admin/Policy/DeliveryRegistController.php+2
변경 전DeliveryRegistController.php
gd_isset($data['basic']['dmVisitTypeFl'], 'same');
gd_isset($data['basic']['dmVisitTypeDisplayFl'], 'n');
gd_isset($data['basic']['rangeRepeat'], 'n');
 
gd_isset($data['manage']['scmNo'], (Manager::isProvider() && !Request::get()->has('sno') ? Session::get('manager.scmNo') : $data['manage']['scmNo']));
gd_isset($data['basic']['deliveryConfigType'], 'all');
 
$checked['dmVisitAddressUseFl'][$data['basic']['dmVisitAddressUseFl']] =
$checked['rangeRepeat'][$data['basic']['rangeRepeat']] =
$checked['addGoodsCountInclude'][$data['basic']['addGoodsCountInclude']] =
 
$checked['deliveryConfigType'][$data['basic']['deliveryConfigType']] = 'checked="checked"';
 
//배송 방식
 
변경 후DeliveryRegistController.php
gd_isset($data['basic']['dmVisitTypeFl'], 'same');
gd_isset($data['basic']['dmVisitTypeDisplayFl'], 'n');
gd_isset($data['basic']['rangeRepeat'], 'n');
$data['basic']['areaChargeRepeatFl'] = $data['basic']['areaChargeRepeatFl'] ?? 'n';
gd_isset($data['manage']['scmNo'], (Manager::isProvider() && !Request::get()->has('sno') ? Session::get('manager.scmNo') : $data['manage']['scmNo']));
gd_isset($data['basic']['deliveryConfigType'], 'all');
 
$checked['dmVisitAddressUseFl'][$data['basic']['dmVisitAddressUseFl']] =
$checked['rangeRepeat'][$data['basic']['rangeRepeat']] =
$checked['addGoodsCountInclude'][$data['basic']['addGoodsCountInclude']] =
$checked['areaChargeRepeatFl'][$data['basic']['areaChargeRepeatFl']] =
$checked['deliveryConfigType'][$data['basic']['deliveryConfigType']] = 'checked="checked"';
 
//배송 방식
 
data/skin/front/moment/css/common/layer/layer.css+1-1
변경 전layer.css
#wrap .ly_wrap.delivery_layer{width:305px; top:0px; left:-219px;}
/* 지역별추가배송비 레이어 */
#wrap .ly_wrap.delivery_zone_layer{width:305px; top:0px; left:-94px;}
.delivery_zone_layer .delivery_list{overflow-y:scroll; max-height:200px;}
/* 배송비 레잉어 공통 영역 */
.ly_wrap .delivery_list ul{border-bottom:1px solid #e8e8e8;}
.ly_wrap .delivery_list ul li{padding:10px 5px 10px 5px; font-weight:bold; border-top:1px solid #e8e8e8;}
 
변경 후layer.css
#wrap .ly_wrap.delivery_layer{width:305px; top:0px; left:-219px;}
/* 지역별추가배송비 레이어 */
#wrap .ly_wrap.delivery_zone_layer{width:305px; top:0px; left:-94px;}
.delivery_zone_layer .delivery_list{overflow-y:auto; max-height:200px;}
/* 배송비 레잉어 공통 영역 */
.ly_wrap .delivery_list ul{border-bottom:1px solid #e8e8e8;}
.ly_wrap .delivery_list ul li{padding:10px 5px 10px 5px; font-weight:bold; border-top:1px solid #e8e8e8;}
 
data/skin/front/moment/goods/goods_view.html+7
변경 전goods_view.html
<!--{ / }-->
</ul>
</div>
 
 
 
 
 
 
 
</div>
<!-- //ly_cont -->
<a href="#close" class="ly_close"><img src="../img/common/layer/btn_layer_close.png" alt="{=__('닫기')}"></a>
 
변경 후goods_view.html
<!--{ / }-->
</ul>
</div>
<!--{ ? goodsView['delivery']['basic']['areaChargeRepeatFl'] == 'y' }-->
<!--{ ? goodsView['delivery']['basic']['fixFl'] == 'count' }-->
<p class="chk_none">{=__('주문 수량이 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ : goodsView['delivery']['basic']['fixFl'] == 'weight' }-->
<p class="chk_none">{=__('주문 무게가 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ / }-->
<!--{ / }-->
</div>
<!-- //ly_cont -->
<a href="#close" class="ly_close"><img src="../img/common/layer/btn_layer_close.png" alt="{=__('닫기')}"></a>
 
data/skin/front/moment/goods/layer_option.html+7
변경 전layer_option.html
<!--{ / }-->
</ul>
</div>
 
 
 
 
 
 
 
</div>
<!-- //ly_cont -->
<a href="#close" class="ly_close"><img src="../img/common/layer/btn_layer_close.png" alt="{=__('닫기')}"></a>
 
변경 후layer_option.html
<!--{ / }-->
</ul>
</div>
<!--{ ? goodsView['delivery']['basic']['areaChargeRepeatFl'] == 'y' }-->
<!--{ ? goodsView['delivery']['basic']['fixFl'] == 'count' }-->
<p class="chk_none">{=__('주문 수량이 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ : goodsView['delivery']['basic']['fixFl'] == 'weight' }-->
<p class="chk_none">{=__('주문 무게가 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ / }-->
<!--{ / }-->
</div>
<!-- //ly_cont -->
<a href="#close" class="ly_close"><img src="../img/common/layer/btn_layer_close.png" alt="{=__('닫기')}"></a>
 
data/skin/front/mygreek/css/common/layer/layer.css+6-1
변경 전layer.css
}
.delivery_zone_layer .delivery_list {
overflow-y: scroll;
max-height: 200px;
}
 
 
 
 
 
/* 배송비 레잉어 공통 영역 */
.ly_wrap .delivery_list ul {
border-bottom: 1px solid #e8e8e8;
 
변경 후layer.css
}
.delivery_zone_layer .delivery_list {
overflow-y: auto;
max-height: 200px;
}
.delivery_zone_layer .chk_none {
padding: 5px 0 5px 19px;
background-image: url('../../../img/icon/etc/icon_warning_green.svg');
}
/* 배송비 레잉어 공통 영역 */
.ly_wrap .delivery_list ul {
border-bottom: 1px solid #e8e8e8;
 
data/skin/front/mygreek/goods/goods_view.html+7
변경 전goods_view.html
<!--{ / }-->
</ul>
</div>
 
 
 
 
 
 
 
</div>
<!-- //ly_cont -->
<a href="#close" class="ly_close"><img src="../img/common/layer/btn_layer_close.png" alt="{=__('닫기')}"></a>
 
변경 후goods_view.html
<!--{ / }-->
</ul>
</div>
<!--{ ? goodsView['delivery']['basic']['areaChargeRepeatFl'] == 'y' }-->
<!--{ ? goodsView['delivery']['basic']['fixFl'] == 'count' }-->
<p class="chk_none">{=__('주문 수량이 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ : goodsView['delivery']['basic']['fixFl'] == 'weight' }-->
<p class="chk_none">{=__('주문 무게가 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ / }-->
<!--{ / }-->
</div>
<!-- //ly_cont -->
<a href="#close" class="ly_close"><img src="../img/common/layer/btn_layer_close.png" alt="{=__('닫기')}"></a>
 
data/skin/front/mygreek/goods/layer_option.html+7
변경 전layer_option.html
<!--{ / }-->
</ul>
</div>
 
 
 
 
 
 
 
</div>
<!-- //ly_cont -->
<a href="#close" class="ly_close"><img src="../img/common/layer/btn_layer_close.png" alt="{=__('닫기')}"></a>
 
변경 후layer_option.html
<!--{ / }-->
</ul>
</div>
<!--{ ? goodsView['delivery']['basic']['areaChargeRepeatFl'] == 'y' }-->
<!--{ ? goodsView['delivery']['basic']['fixFl'] == 'count' }-->
<p class="chk_none">{=__('주문 수량이 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ : goodsView['delivery']['basic']['fixFl'] == 'weight' }-->
<p class="chk_none">{=__('주문 무게가 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ / }-->
<!--{ / }-->
</div>
<!-- //ly_cont -->
<a href="#close" class="ly_close"><img src="../img/common/layer/btn_layer_close.png" alt="{=__('닫기')}"></a>
 
data/skin/front/mygreek/img/icon/etc/icon_warning_green.svg+5
변경 전icon_warning_green.svg
 
 
 
 
 
 
변경 후icon_warning_green.svg
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="13" height="13" rx="6.5" stroke="#0cc472"/>
<path d="M4.4541 7.51363V6.36328H9.54501V7.51363H4.4541Z" fill="white"/>
<path d="M7.91618 3L7.76051 8.53342H6.22775L6.07208 3H7.91618ZM6.00023 10.0832C5.98826 9.58117 6.44329 9.1719 7.0061 9.17735C7.53898 9.1719 8 9.58117 8 10.0832C8 10.5907 7.53898 11 7.0061 11C6.44329 11 5.98826 10.5907 6.00023 10.0832Z" fill="#0cc472"/>
</svg>
 
data/skin/front/story_g/css/gd_share.css+22
변경 전gd_share.css
border: 1px solid #555;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.goods-view .goods .info .item ul li.delivery .delivery-layer .wrap > strong,
.option-layer .view .content-section .list .delivery-layer .wrap > strong {
display: block;
 
변경 후gd_share.css
border: 1px solid #555;
}
 
.goods-view .goods .info .item ul li.delivery .delivery-layer.delivery-area,
.option-layer .view .content-section .list .delivery-layer.delivery-area {
height: auto;
}
 
.goods-view .goods .info .item ul li.delivery .delivery-layer.delivery-area .wrap,
.option-layer .view .content-section .list .delivery-layer.delivery-area .wrap {
height: auto;
padding-bottom: 9px;
}
 
.goods-view .goods .info .item ul li.delivery .delivery-layer.delivery-area .wrap div,
.option-layer .view .content-section .list .delivery-layer.delivery-area .wrap div {
height: auto;
max-height: 219px;
}
 
.goods-view .goods .info .item ul li.delivery .delivery-layer.delivery-area .wrap .caution-msg1,
.option-layer .view .content-section .list .delivery-layer.delivery-area .wrap .caution-msg1 {
margin: 12px 9px 3px 19px;
}
 
.goods-view .goods .info .item ul li.delivery .delivery-layer .wrap > strong,
.option-layer .view .content-section .list .delivery-layer .wrap > strong {
display: block;
 
data/skin/front/story_g/goods/goods_view.html+7
변경 전goods_view.html
<!--{ / }-->
</table>
</div>
 
 
 
 
 
 
 
<button type="button" class="close target-delivery-area" title="{=__('닫기')}">{=__('닫기')}</button>
</div>
</div>
 
변경 후goods_view.html
<!--{ / }-->
</table>
</div>
<!--{ ? goodsView['delivery']['basic']['areaChargeRepeatFl'] == 'y' }-->
<!--{ ? goodsView['delivery']['basic']['fixFl'] == 'count' }-->
<p class="caution-msg1">{=__('주문 수량이 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ : goodsView['delivery']['basic']['fixFl'] == 'weight' }-->
<p class="caution-msg1">{=__('주문 무게가 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ / }-->
<!--{ / }-->
<button type="button" class="close target-delivery-area" title="{=__('닫기')}">{=__('닫기')}</button>
</div>
</div>
 
data/skin/front/story_g/goods/layer_option.html+7
변경 전layer_option.html
<!--{ / }-->
</table>
</div>
 
 
 
 
 
 
 
<button type="button" class="target-delivery-area" title="{=__('닫기')}">{=__('닫기')}</button>
</div>
</div>
 
변경 후layer_option.html
<!--{ / }-->
</table>
</div>
<!--{ ? goodsView['delivery']['basic']['areaChargeRepeatFl'] == 'y' }-->
<!--{ ? goodsView['delivery']['basic']['fixFl'] == 'count' }-->
<p class="caution-msg1">{=__('주문 수량이 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ : goodsView['delivery']['basic']['fixFl'] == 'weight' }-->
<p class="caution-msg1">{=__('주문 무게가 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ / }-->
<!--{ / }-->
<button type="button" class="target-delivery-area" title="{=__('닫기')}">{=__('닫기')}</button>
</div>
</div>
 
data/skin/mobile/moment/goods/goods_view.html+7
변경 전goods_view.html
<!--{ / }-->
</tbody>
</table>
 
 
 
 
 
 
 
</div>
</div>
</div>
 
변경 후goods_view.html
<!--{ / }-->
</tbody>
</table>
<!--{ ? goodsView['delivery']['basic']['areaChargeRepeatFl'] == 'y' }-->
<!--{ ? goodsView['delivery']['basic']['fixFl'] == 'count' }-->
<p style="margin:8px 0 0;padding:5px 10px;color:#ab3e55;font-size:12px;line-height:1.4;">※ {=__('주문 수량이 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ : goodsView['delivery']['basic']['fixFl'] == 'weight' }-->
<p style="margin:8px 0 0;padding:5px 10px;color:#ab3e55;font-size:12px;line-height:1.4;">※ {=__('주문 무게가 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ / }-->
<!--{ / }-->
</div>
</div>
</div>
 
data/skin/mobile/mygreek/goods/goods_view.html+7
변경 전goods_view.html
<!--{ / }-->
</tbody>
</table>
 
 
 
 
 
 
 
</div>
</div>
</div>
 
변경 후goods_view.html
<!--{ / }-->
</tbody>
</table>
<!--{ ? goodsView['delivery']['basic']['areaChargeRepeatFl'] == 'y' }-->
<!--{ ? goodsView['delivery']['basic']['fixFl'] == 'count' }-->
<p style="margin:8px 0 0;padding:5px 10px;color:#ab3e55;font-size:12px;line-height:1.4;">※ {=__('주문 수량이 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ : goodsView['delivery']['basic']['fixFl'] == 'weight' }-->
<p style="margin:8px 0 0;padding:5px 10px;color:#ab3e55;font-size:12px;line-height:1.4;">※ {=__('주문 무게가 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ / }-->
<!--{ / }-->
</div>
</div>
</div>
 
data/skin/mobile/story_g/goods/goods_view.html+7
변경 전goods_view.html
<!--{ / }-->
</tbody>
</table>
 
 
 
 
 
 
 
</div>
</div>
</div>
 
변경 후goods_view.html
<!--{ / }-->
</tbody>
</table>
<!--{ ? goodsView['delivery']['basic']['areaChargeRepeatFl'] == 'y' }-->
<!--{ ? goodsView['delivery']['basic']['fixFl'] == 'count' }-->
<p style="margin:8px 0 0;padding:5px 10px;color:#ab3e55;font-size:12px;line-height:1.4;">※ {=__('주문 수량이 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ : goodsView['delivery']['basic']['fixFl'] == 'weight' }-->
<p style="margin:8px 0 0;padding:5px 10px;color:#ab3e55;font-size:12px;line-height:1.4;">※ {=__('주문 무게가 속한 배송비 구간 수만큼, 지역별추가배송비가 반복 부과됩니다.')}</p>
<!--{ / }-->
<!--{ / }-->
</div>
</div>
</div>
 
디자인 에디터 사용 이력 수집 기능 추가
Admin/layout_design_editor.php+3-1
변경 전layout_design_editor.php
skinCountry: '<?=$skinCountry?>',
skinLiveFl: <?=$skinLiveFl ? 'true' : 'false'?>,
rootSkinCode: '<?=$rootSkinCode?>',
requestUrl: baseDir + '/design_editor_ps.php'
 
 
});
});
</script>
 
변경 후layout_design_editor.php
skinCountry: '<?=$skinCountry?>',
skinLiveFl: <?=$skinLiveFl ? 'true' : 'false'?>,
rootSkinCode: '<?=$rootSkinCode?>',
requestUrl: baseDir + '/design_editor_ps.php',
shopNo: <?=$clickLogShopNo ?? 0?>,
adminIdHash: '<?=$clickLogAdminIdHash ?? ''?>'
});
});
</script>
 
- [게시판] 비회원 글쓰기 보안 강화
Component/Board/Board.php+17
변경 전Board.php
const TEXT_INSUFFICIENT_INPUTDATA = '입력 정보가 부족합니다';
const TEXT_NOTMATCH_PASSWORD = '비밀번호가 일치하지 않습니다';
const TEXT_NOTHAVE_AUTHORITY = '%s권한이 없습니다.';
 
const TEXT_UPLOAD_IMPOSSIBLE = '업로드가 불가능합니다';
const EXCEPTION_CODE_AUTH = 700;
const UPLOAD_DEFAULT_MAX_SIZE = 5; //업로드 최대용량 디플트 값(mb)
if($result['cnt']>0) {
throw new \Exception(__("중복된 게시물을 연속으로 등록할 수 없습니다. \n중복 게시물이 아닌 경우, 잠시 후 다시 등록하시기 바랍니다."));
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
return true;
}
 
변경 후Board.php
const TEXT_INSUFFICIENT_INPUTDATA = '입력 정보가 부족합니다';
const TEXT_NOTMATCH_PASSWORD = '비밀번호가 일치하지 않습니다';
const TEXT_NOTHAVE_AUTHORITY = '%s권한이 없습니다.';
const TEXT_GUEST_WRITE_RATE_LIMIT = '잠시 후 다시 등록해 주세요. (1분 이내 연속 등록 제한)';
const TEXT_UPLOAD_IMPOSSIBLE = '업로드가 불가능합니다';
const EXCEPTION_CODE_AUTH = 700;
const UPLOAD_DEFAULT_MAX_SIZE = 5; //업로드 최대용량 디플트 값(mb)
if($result['cnt']>0) {
throw new \Exception(__("중복된 게시물을 연속으로 등록할 수 없습니다. \n중복 게시물이 아닌 경우, 잠시 후 다시 등록하시기 바랍니다."));
}
 
}
 
// 비회원(memNo=0) 게시글/답변 연속 등록 제한: 동일 IP 60초 이내 재등록 차단 (제목 무관)
if (in_array($this->req['mode'], ['write', 'reply'], true) && (int) $this->member['memNo'] === 0) {
$remoteAddr = Request::getRemoteAddress();
$guestBind = [];
$this->db->bind_param_push($guestBind, 's', $remoteAddr);
$guestResult = $this->db->query_fetch("SELECT count(*) as cnt FROM " . DB_BD_ . $this->cfg['bdId'] . " WHERE memNo = 0 AND writerIp = ? AND regDt >= (now() - INTERVAL 60 SECOND) ", $guestBind, false);
if ($guestResult['cnt'] > 0) {
try {
\Logger::channel('board')->warning('[Board] 비회원 연속 등록 제한', ['bdId' => $this->cfg['bdId'], 'writerIp' => $remoteAddr]);
} catch (\Throwable $e) {
}
throw new \Exception(__(self::TEXT_GUEST_WRITE_RATE_LIMIT));
}
}
return true;
}
 
Controller/Mobile/Board/BoardPsController.php+6
변경 전BoardPsController.php
use Component\Member\MemberReport;
use Component\Validator\Validator;
use Framework\Debug\Exception\AlertBackException;
 
use Framework\ObjectStorage\Service\ImageUploadService;
use GuzzleHttp\Psr7\Query;
use View\Template;
exit;
 
} catch (\Exception $e) {
 
 
 
 
 
throw new AlertBackException($e->getMessage());
}
break;
 
변경 후BoardPsController.php
use Component\Member\MemberReport;
use Component\Validator\Validator;
use Framework\Debug\Exception\AlertBackException;
use Framework\Debug\Exception\AlertRedirectException;
use Framework\ObjectStorage\Service\ImageUploadService;
use GuzzleHttp\Psr7\Query;
use View\Template;
exit;
 
} catch (\Exception $e) {
// 비밀글은 글 상세가 POST로 열리므로 답글 실패 후 뒤로가기 시 양식 재전송 확인 페이지가 노출됨
$isListReturn = gd_isset($req['gboard']) != 'y' && gd_isset($req['gboard']) != 'r';
if ($req['mode'] == 'reply' && $isListReturn && isset($boardAct) && $boardAct->cfg['bdSecretFl'] != '2') {
throw new AlertRedirectException($e->getMessage(), 0, null, '../board/list.php?' . $req['returnUrl']);
}
throw new AlertBackException($e->getMessage());
}
break;
 
LMS 메시지 발송 시 제목 입력 기능
Admin/crm/auto_send_config/auto_send_config_recipient_section.php+16-1
변경 전auto_send_config_recipient_section.php
 
<div class="ncua-card__body-block-section-wrap">
<section class="ncua-card__body-block-section">
<div class="ncua-card__body-title--xs">내용 입력</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<div id="sms-message-input-container-<?= $recipient ?>"
data-initial-value="<?= htmlspecialchars($autoSendConfig['SMS']['templateContents'] ?? '', ENT_QUOTES) ?>">
</div>
 
변경 후auto_send_config_recipient_section.php
 
<div class="ncua-card__body-block-section-wrap">
<section class="ncua-card__body-block-section">
<div class="ncua-card__body-title--xs tooltip-align" data-tooltip-seq="009">내용 입력</div>
<?php /* 제목은 본문이 LMS(90bytes 초과)일 때만 활성 — 토글·카운트는 JS(initMessageModules)가 담당 */ ?>
<div class="message-title-field">
<div class="ncua-input ncua-input--xs is-disabled">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input name="recipients[<?= $recipient ?>][SMS][templateTitle]" type="text"
value="<?= htmlspecialchars($autoSendConfig['SMS']['templateTitle'] ?? '', ENT_QUOTES) ?>"
placeholder="제목을 입력해 주세요." data-sms-title-input disabled />
</div>
</div>
</div>
<span class="message-title-count">
<span data-sms-title-current>0</span>/40
</span>
</div>
<div id="sms-message-input-container-<?= $recipient ?>"
data-initial-value="<?= htmlspecialchars($autoSendConfig['SMS']['templateContents'] ?? '', ENT_QUOTES) ?>">
</div>
 
Admin/crm/auto_send_config.php+45-1
변경 전auto_send_config.php
if (tabInfo.channel === 'SMS') {
const input = messageInputInstances[recipient]?.SMS;
const smsContent = input ? normalizeNewlines(input.getValue()) : '';
this.setPreview(section, { content: smsContent || '메시지 내용을 입력하세요.' });
 
 
} else if (tabInfo.channel === 'KAKAO_ALRIM_TALK') {
const tabContent = section.querySelector('[data-tab="KAKAO_ALRIM_TALK"]');
if (!tabContent) return;
// SMS/LMS 포인트 힌트 업데이트
const byteLength = smsInput.calculateLength(normalized);
smsInput.setHint(byteLength > 90 ? 'LMS 건당 3포인트 차감' : 'SMS 건당 1포인트 차감');
 
 
// 빈 내용일 때 안내문구 숨김
if (normalized === '') {
const caution = document.querySelector(`#sms-chip-selector-container-${recipient} [data-caution-template]`);
});
messageInputInstances[recipient].SMS = smsInput;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
// 초기 힌트 상태 설정
const hintEl = smsContainer.querySelector('[data-message-hint]');
if (hintEl) hintEl.classList.add('destructive');
 
변경 후auto_send_config.php
if (tabInfo.channel === 'SMS') {
const input = messageInputInstances[recipient]?.SMS;
const smsContent = input ? normalizeNewlines(input.getValue()) : '';
const smsTitleInput = section.querySelector('input[data-sms-title-input]');
const smsTitle = smsTitleInput && !smsTitleInput.disabled ? smsTitleInput.value.replace(/\{[^}]*\}/g, '') : '';
this.setPreview(section, { title: smsTitle, content: smsContent || '메시지 내용을 입력하세요.' });
} else if (tabInfo.channel === 'KAKAO_ALRIM_TALK') {
const tabContent = section.querySelector('[data-tab="KAKAO_ALRIM_TALK"]');
if (!tabContent) return;
// SMS/LMS 포인트 힌트 업데이트
const byteLength = smsInput.calculateLength(normalized);
smsInput.setHint(byteLength > 90 ? 'LMS 건당 3포인트 차감' : 'SMS 건당 1포인트 차감');
// 본문 기준 SMS/LMS 판단 — LMS 일 때만 제목 입력 활성화
setSmsTitleEnabled(byteLength > 90);
// 빈 내용일 때 안내문구 숨김
if (normalized === '') {
const caution = document.querySelector(`#sms-chip-selector-container-${recipient} [data-caution-template]`);
});
messageInputInstances[recipient].SMS = smsInput;
 
// ===== SMS/LMS 제목 — 본문이 LMS(90bytes 초과)일 때만 활성 (모바일 발송과 동일 규칙) =====
const SMS_TITLE_MAX_BYTES = 40;
const sectionEl = smsContainer.closest('[data-target-type]');
const smsTitleInput = sectionEl.querySelector('input[data-sms-title-input]');
const smsTitleCounter = sectionEl.querySelector('[data-sms-title-current]');
 
// 미리보기 — LMS 상태에서 값이 있을 때만 노출, 제목은 변수 미지원이라 치환자는 공란으로 표기
const updateSmsTitlePreview = () => {
if (!smsTitleInput) return;
const value = !smsTitleInput.disabled ? smsTitleInput.value.replace(/\{[^}]*\}/g, '') : '';
PreviewFacade.setPreview(sectionEl, { title: value });
};
 
// 제목 입력 — 이모지 제거, 40bytes 초과분 자동 삭제 (byte 기준은 본문 MessageInput 과 동일)
const handleSmsTitleInput = () => {
if (!smsTitleInput) return;
let value = smsInput.removeEmoji(smsTitleInput.value).value;
value = smsInput.truncateBytes(value, SMS_TITLE_MAX_BYTES);
if (smsTitleInput.value !== value) {
smsTitleInput.value = value;
}
if (smsTitleCounter) smsTitleCounter.textContent = smsInput.getByteLength(value);
updateSmsTitlePreview();
};
 
// SMS 로 돌아오면 값은 유지한 채 비활성
const setSmsTitleEnabled = (enabled) => {
if (!smsTitleInput) return;
smsTitleInput.disabled = !enabled;
smsTitleInput.closest('.ncua-input').classList.toggle('is-disabled', !enabled);
updateSmsTitlePreview();
};
 
if (smsTitleInput) {
smsTitleInput.addEventListener('input', handleSmsTitleInput);
// 초기 상태: 저장된 본문 byte 기준 활성 여부 + 저장된 제목 카운트 반영
setSmsTitleEnabled(smsInput.calculateLength(smsInitialValue) > 90);
handleSmsTitleInput();
}
 
// 초기 힌트 상태 설정
const hintEl = smsContainer.querySelector('[data-message-hint]');
if (hintEl) hintEl.classList.add('destructive');
 
Admin/crm/crm_recipe_send.php+8
변경 전crm_recipe_send.php
el.querySelector('.ncua-input__destructive-icon-wrap')?.remove();
});
 
 
 
 
 
 
const fail = (message, subMessage) => {
NCDSAlert({ message, subMessage, iconType: 'error' });
$button.prop('disabled', false);
allKeys.includes(MobileMessageReplaceCode.normalizeReplaceKey(key)) ? `{${key}}` : match);
window.alternativeMessageInput.insertText(alternativeContents);
window.alternativePreview?.setContent(alternativeContents);
 
 
 
}
 
sendClickLog('send');
 
변경 후crm_recipe_send.php
el.querySelector('.ncua-input__destructive-icon-wrap')?.remove();
});
 
// 제목 40byte 초과(C4-2)는 미해소 에러라 재표기
if (typeof refreshOverSmsTitleState === 'function') {
refreshOverSmsTitleState();
}
 
const fail = (message, subMessage) => {
NCDSAlert({ message, subMessage, iconType: 'error' });
$button.prop('disabled', false);
allKeys.includes(MobileMessageReplaceCode.normalizeReplaceKey(key)) ? `{${key}}` : match);
window.alternativeMessageInput.insertText(alternativeContents);
window.alternativePreview?.setContent(alternativeContents);
if (typeof copyMainTitleToAlternativeSmsTitle === 'function') {
copyMainTitleToAlternativeSmsTitle();
}
}
 
sendClickLog('send');
 
Admin/crm/layer_preview_auto_send.php+6-1
변경 전layer_preview_auto_send.php
<?php if ($channelKey === 'SMS'): ?>
<!-- SMS 미리보기 -->
<section data-component="sms-message-preview" class="mobile-preview mobile-preview--sms">
<div class="ncua-prev"><?= nl2br(htmlspecialchars($templateContents)) ?></div>
 
 
 
 
 
</section>
<?php elseif ($channelKey === 'KAKAO_ALRIM_TALK'):
 
변경 후layer_preview_auto_send.php
<?php if ($channelKey === 'SMS'): ?>
<!-- SMS 미리보기 -->
<section data-component="sms-message-preview" class="mobile-preview mobile-preview--sms">
<div class="ncua-prev">
<?php if (($channelData['templateTitle'] ?? '') !== ''): ?>
<div class="sms-title-side-preview"><?= htmlspecialchars($channelData['templateTitle']) ?></div>
<?php endif; ?>
<?= nl2br(htmlspecialchars($templateContents)) ?>
</div>
</section>
<?php elseif ($channelKey === 'KAKAO_ALRIM_TALK'):
 
Admin/crm/layer_template_contents_sms.php+6-1
변경 전layer_template_contents_sms.php
<!-- SMS/LMS -->
<section class="mobile-preview mobile-preview--sms layer-preview-contents--sms">
<div class="ncua-prev"><?= nl2br(htmlspecialchars($smsData?->getContents() ?? '')) ?></div>
 
 
 
 
 
</section>
 
변경 후layer_template_contents_sms.php
<!-- SMS/LMS -->
<section class="mobile-preview mobile-preview--sms layer-preview-contents--sms">
<div class="ncua-prev">
<?php if (($smsData?->getMessageTitle() ?? '') !== ''): ?>
<div class="sms-title-side-preview"><?= htmlspecialchars($smsData->getMessageTitle()) ?></div>
<?php endif; ?>
<?= nl2br(htmlspecialchars($smsData?->getContents() ?? '')) ?>
</div>
</section>
 
Admin/crm/message_template_register.php+56-2
변경 전message_template_register.php
element.querySelectorAll('input, textarea, select').forEach(field => {
// 체크박스로 제어되는 textarea는 제외
if (field.id === 'channelAddTextarea' || field.id === 'extraInfoTextarea') return;
 
 
// 수정/복제 모드에서 발송수단, 제공사는 disabled 유지
if (field.name === 'sendMethod' || field.name === 'provider') return;
field.disabled = !isVisible;
});
};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* SMS 바이트 수에 따라 SMS/LMS 포인트 차감 힌트 텍스트 변경
*/
} else {
smsMessageInput.setHint('');
}
 
 
};
 
/**
}
});
 
// 수정/복제 모드: initialValue 기준으로 SMS/LMS 힌트 초기화
updateSmsMessageHint(initialData.smsContent || '');
 
 
 
 
 
const smsVariableSelector = GodoUIModule.render({
type: 'ChipSelector',
target: '#sms-variable-selector-container',
document.querySelectorAll('#sms-message-input-container .ncua-input, #kakao-message-input-container .ncua-input, #myapp-message-input-container .ncua-input')
.forEach(el => el.setAttribute('data-show-hint-text', 'true'));
 
let templatePreview = null;
window.addEventListener('crmPreviewLoaded', function() {
const currentSendMethod = document.querySelector('input[name="sendMethod"]:checked')?.value || 'sms';
const sendTypeMap = { 'sms': 'SMS', 'kakao': 'ALIMTALK', 'myapp': 'MYAPP' };
}
templatePreview.setContent(displayValue);
}
 
 
 
};
 
// 변수 변환 체크박스 이벤트 리스너 (CrmPreview 체크박스 사용)
formData.append('templateCategory', document.querySelector('input[name="templateCategory"]:checked')?.value || '');
formData.append('templateTitle', document.querySelector('input[name="templateTitle"]')?.value || '');
formData.append('templateMessageContent', escapeLineBreaks(document.querySelector('textarea[name="smsMessageInput"]')?.value || ''));
 
 
 
submitForm();
} else if (sendMethod === 'kakao') {
 
변경 후message_template_register.php
element.querySelectorAll('input, textarea, select').forEach(field => {
// 체크박스로 제어되는 textarea는 제외
if (field.id === 'channelAddTextarea' || field.id === 'extraInfoTextarea') return;
// SMS 제목은 본문 LMS 여부로 활성 상태를 관리하므로 제외
if (field.name === 'templateMessageTitle') return;
// 수정/복제 모드에서 발송수단, 제공사는 disabled 유지
if (field.name === 'sendMethod' || field.name === 'provider') return;
field.disabled = !isVisible;
});
};
 
// crmPreviewLoaded 이후 생성 — 선언보다 먼저 실행되는 초기화 코드(제목 미리보기 등)에서 참조하므로 여기서 선언
let templatePreview = null;
 
/**
* SMS/LMS 메시지 제목 — 본문이 LMS(90bytes 초과)일 때만 활성 (모바일 발송과 동일 정책)
*/
const SMS_TITLE_MAX_BYTES = 40;
const getSmsTitleInput = () => document.querySelector('input[name="templateMessageTitle"]');
 
const isSmsTitleEnabled = () => {
const input = getSmsTitleInput();
return !!input && !input.disabled;
};
 
// 미리보기 — LMS 상태에서 값이 있을 때만 노출, 제목은 변수 미지원이라 치환자는 공란으로 표기
const updateSmsTitlePreview = () => {
if (!templatePreview) return;
const value = isSmsTitleEnabled() ? getSmsTitleInput().value.replace(/\{[^}]*\}/g, '') : '';
templatePreview.setTitle(value);
};
 
// SMS 로 돌아오면 값은 유지한 채 비활성
const setSmsTitleEnabled = (enabled) => {
const input = getSmsTitleInput();
if (!input) return;
input.disabled = !enabled;
input.closest('.ncua-input').classList.toggle('is-disabled', !enabled);
updateSmsTitlePreview();
};
 
// 제목 입력 — 이모지 제거, 40bytes 초과분 자동 삭제 (byte 기준은 본문 MessageInput 과 동일)
const handleSmsTitleInput = () => {
const input = getSmsTitleInput();
let value = smsMessageInput.removeEmoji(input.value).value;
value = smsMessageInput.truncateBytes(value, SMS_TITLE_MAX_BYTES);
if (input.value !== value) {
input.value = value;
}
document.querySelector('[data-sms-title-current]').textContent = smsMessageInput.getByteLength(value);
updateSmsTitlePreview();
};
 
/**
* SMS 바이트 수에 따라 SMS/LMS 포인트 차감 힌트 텍스트 변경
*/
} else {
smsMessageInput.setHint('');
}
// 본문 기준 SMS/LMS 판단 — LMS 일 때만 제목 입력 활성화
setSmsTitleEnabled(currentBytes > 90);
};
 
/**
}
});
 
// 수정/복제 모드: initialValue 기준으로 SMS/LMS 힌트 초기화 (제목 활성 상태 포함)
updateSmsMessageHint(initialData.smsContent || '');
 
// 제목 입력 이벤트 + 초기 카운터 반영 (수정/복제 모드: 저장된 제목)
getSmsTitleInput().addEventListener('input', handleSmsTitleInput);
handleSmsTitleInput();
 
const smsVariableSelector = GodoUIModule.render({
type: 'ChipSelector',
target: '#sms-variable-selector-container',
document.querySelectorAll('#sms-message-input-container .ncua-input, #kakao-message-input-container .ncua-input, #myapp-message-input-container .ncua-input')
.forEach(el => el.setAttribute('data-show-hint-text', 'true'));
 
 
window.addEventListener('crmPreviewLoaded', function() {
const currentSendMethod = document.querySelector('input[name="sendMethod"]:checked')?.value || 'sms';
const sendTypeMap = { 'sms': 'SMS', 'kakao': 'ALIMTALK', 'myapp': 'MYAPP' };
}
templatePreview.setContent(displayValue);
}
if (sendMethod === 'sms') {
updateSmsTitlePreview();
}
};
 
// 변수 변환 체크박스 이벤트 리스너 (CrmPreview 체크박스 사용)
formData.append('templateCategory', document.querySelector('input[name="templateCategory"]:checked')?.value || '');
formData.append('templateTitle', document.querySelector('input[name="templateTitle"]')?.value || '');
formData.append('templateMessageContent', escapeLineBreaks(document.querySelector('textarea[name="smsMessageInput"]')?.value || ''));
// 제목은 SMS/LMS 여부와 무관하게 입력값이 있으면 함께 저장 (모바일 발송 템플릿 저장과 동일)
formData.append('templateMessageTitle', getSmsTitleInput()?.value || '');
 
submitForm();
} else if (sendMethod === 'kakao') {
 
Admin/crm/message_template_register/template_contents_sms.php+15-1
변경 전template_contents_sms.php
<!-- 본문 입력: SMS 바이트형 -->
<div class="message-content sms-byte-mode" data-component="message-content">
<div class="ncua-card__body-title-wrap ncua-required">
<p class="ncua-card__body-title--xs">본문 입력</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
</div>
<div id="sms-message-input-container" class="message-input-container"></div>
</div>
<div class="ncua-accordion__content has-dot">
<ul>
<li>URL 삽입 시 [단축 URL] 앱을 이용하시면 짧고 간단하게 URL을 사용하실 수 있습니다. <a href="https://apps.godo.co.kr/apps/1519" target="_blank" class="ncua-link-underline">바로가기</a></li>
 
</ul>
</div>
</details>
 
변경 후template_contents_sms.php
<!-- 본문 입력: SMS 바이트형 -->
<div class="message-content sms-byte-mode" data-component="message-content">
<div class="ncua-card__body-title-wrap ncua-required">
<p class="ncua-card__body-title--xs tooltip-align" data-tooltip-seq="008">본문 입력</p>
</div>
<?php /* 제목은 본문이 LMS(90bytes 초과)일 때만 활성 — 토글은 JS(setSmsTitleEnabled)가 담당 */ ?>
<div class="message-title-field">
<div class="ncua-input ncua-input--xs is-disabled">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input name="templateMessageTitle" type="text" value="<?= htmlspecialchars($response->getSms()?->getMessageTitle() ?? '') ?>" placeholder="제목을 입력해 주세요." disabled />
</div>
</div>
</div>
<span class="message-title-count">
<span data-sms-title-current>0</span>/40
</span>
</div>
<div id="sms-message-input-container" class="message-input-container"></div>
</div>
<div class="ncua-accordion__content has-dot">
<ul>
<li>URL 삽입 시 [단축 URL] 앱을 이용하시면 짧고 간단하게 URL을 사용하실 수 있습니다. <a href="https://apps.godo.co.kr/apps/1519" target="_blank" class="ncua-link-underline">바로가기</a></li>
<li>제목은 LMS 발송 시에만 적용되는 선택 입력 항목으로, 미입력 시에는 ‘제목 없음’으로 발송되며 SMS 발송 시에는 포함되지 않습니다.</li>
</ul>
</div>
</details>
 
Admin/crm/mobile_message_history/layer_member_sms_history_result.php+1-1
변경 전layer_member_sms_history_result.php
</div>
</td>
<td>
<div class="ncua-align-items-start"><button class="ncua-left-align mobile-history-list-message-content-btn js-show-message-contents" data-send-key="<?= $smsHistory->getSendKey() ?>" type="button"><?= $smsHistory->getContent() ?></button></div>
</td>
<td>
<div class="ncua-flex-column"><?= $smsHistory->getSenderName() ?> <span><?= $smsHistory->getFrom() ?></span></div>
 
변경 후layer_member_sms_history_result.php
</div>
</td>
<td>
<div class="ncua-align-items-start"><button class="ncua-left-align mobile-history-list-message-content-btn js-show-message-contents" data-send-key="<?= $smsHistory->getSendKey() ?>" type="button"><?php if (($smsHistory->getTitle() ?? '') !== ''): ?><span style="display: block;"><?= gd_htmlspecialchars($smsHistory->getTitle()) ?></span><?php endif; ?><?= $smsHistory->getContent() ?></button></div>
</td>
<td>
<div class="ncua-flex-column"><?= $smsHistory->getSenderName() ?> <span><?= $smsHistory->getFrom() ?></span></div>
 
Admin/crm/mobile_message_history/layer_sms_contents.php+6-1
변경 전layer_sms_contents.php
 
<div class="mobile-preview-wrap">
<section class="mobile-preview mobile-preview--sms">
<div class="ncua-prev"><?= nl2br(gd_htmlspecialchars_stripslashes($content)) ?></div>
 
 
 
 
 
</section>
</div>
 
변경 후layer_sms_contents.php
 
<div class="mobile-preview-wrap">
<section class="mobile-preview mobile-preview--sms">
<div class="ncua-prev">
<?php if (($title ?? '') !== ''): ?>
<div class="sms-title-side-preview"><?= gd_htmlspecialchars_stripslashes($title) ?></div>
<?php endif; ?>
<?= nl2br(gd_htmlspecialchars_stripslashes($content)) ?>
</div>
</section>
</div>
 
Admin/crm/mobile_message_history/layer_sms_history_result.php+1-1
변경 전layer_sms_history_result.php
</div>
</td>
<td>
<div class="ncua-align-items-start"><button class="ncua-left-align mobile-history-list-message-content-btn js-show-message-contents" data-send-key="<?= $smsHistory->getSendKey() ?>" type="button"><?= $smsHistory->getContent() ?></button></div>
</td>
<td>
<div class="ncua-flex-column"><?= $smsHistory->getSenderName() ?> <span><?= $smsHistory->getFrom() ?></span></div>
 
변경 후layer_sms_history_result.php
</div>
</td>
<td>
<div class="ncua-align-items-start"><button class="ncua-left-align mobile-history-list-message-content-btn js-show-message-contents" data-send-key="<?= $smsHistory->getSendKey() ?>" type="button"><?php if (($smsHistory->getTitle() ?? '') !== ''): ?><span style="display: block;"><?= gd_htmlspecialchars($smsHistory->getTitle()) ?></span><?php endif; ?><?= $smsHistory->getContent() ?></button></div>
</td>
<td>
<div class="ncua-flex-column"><?= $smsHistory->getSenderName() ?> <span><?= $smsHistory->getFrom() ?></span></div>
 
Admin/crm/mobile_send/layer_kakao_alimtalk_content_setting.php+75-2
변경 전layer_kakao_alimtalk_content_setting.php
return !!input && !input.disabled;
}
 
// 발송 요청용 제목 — LMS 활성 상태에서 값이 있을 때만. 미입력 시 null('제목없음' 처리는 발송 시스템 담당)
function getAlternativeSmsTitleForSend() {
return resolveTitleForSend(getAlternativeSmsTitleInput(), isAlternativeSmsTitleEnabled());
}
 
<div class="message-content send-method-component" data-target="ALTERNATIVE">
<div class="ncua-card__body-title-wrap">
<p class="ncua-card__body-title--xs">내용 입력</p>
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--text-gray has-underline refresh-btn load-main-contents">
메시지 최신 상태 반영
</button>
</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<div id="messageContentAlternativeContainer"></div>
</div>
<div id="replaceCodeAlternativeContainer" class="send-method-component" data-target="ALTERNATIVE"></div>
<ul>
<li>SMS 작성 시 90byte를 초과하면 LMS로 자동 전환되어 발송됩니다.</li>
<li>변수에 실제 데이터가 적용된 최종 메시지가 90byte를 초과하는 경우에도 LMS로 전환될 수 있습니다.</li>
 
</ul>
</div>
</details>
alternativeMessageInput.setValue('');
alternativeMessageInput.insertText(convertedMessage);
alternativePreview.setContent(resolveReplaceCode(convertedMessage, false));
 
NCDSToast({message: '메시지가 최신 내용으로 업데이트 됐습니다.', color: 'success'});
});
 
} else {
window.alternativeMessageInput.setHint(window.alternativeMessageInput.calculateLength(input) > 90 ? 'LMS 건당 3포인트 차감' : 'SMS 건당 1포인트 차감');
}
 
 
}
});
 
setDefaultPreview(['ALIMTALK_TEXT', 'ALTERNATIVE_TEXT']);
}
</script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후layer_kakao_alimtalk_content_setting.php
return !!input && !input.disabled;
}
 
// 발송 요청용 제목 — LMS(제목 활성)일 때만 전송, 미입력·치환자만 입력 시 '[제목없음]' 으로 발송
function getAlternativeSmsTitleForSend() {
return resolveTitleForSend(getAlternativeSmsTitleInput(), isAlternativeSmsTitleEnabled());
}
 
<div class="message-content send-method-component" data-target="ALTERNATIVE">
<div class="ncua-card__body-title-wrap">
<p class="ncua-card__body-title--xs tooltip-align" data-tooltip-seq="010">내용 입력</p>
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--text-gray has-underline refresh-btn load-main-contents">
메시지 최신 상태 반영
</button>
</div>
<div class="message-title-field">
<div class="ncua-input ncua-input--xs ncua-input--full-width-with-count is-disabled">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input name="alternativeSmsTitle" type="text" value="" placeholder="제목을 입력해 주세요." disabled />
</div>
</div>
<div class="ncua-input__field-text-count">
<output class="ncua-input__field-text-count-current" data-alternative-sms-title-current>0</output><span>/40</span>
</div>
</div>
</div>
</div>
<div id="messageContentAlternativeContainer"></div>
</div>
<div id="replaceCodeAlternativeContainer" class="send-method-component" data-target="ALTERNATIVE"></div>
<ul>
<li>SMS 작성 시 90byte를 초과하면 LMS로 자동 전환되어 발송됩니다.</li>
<li>변수에 실제 데이터가 적용된 최종 메시지가 90byte를 초과하는 경우에도 LMS로 전환될 수 있습니다.</li>
<li>본문 내용이 LMS인 경우에만 제목 입력이 가능하며, 제목 영역에는 직접 (광고) 문구를 입력하지 않도록 하고 광고 삽입이 필요한 경우 광고성 문구 추가 기능을 사용해야 합니다.</li>
</ul>
</div>
</details>
alternativeMessageInput.setValue('');
alternativeMessageInput.insertText(convertedMessage);
alternativePreview.setContent(resolveReplaceCode(convertedMessage, false));
copyMainTitleToAlternativeSmsTitle();
NCDSToast({message: '메시지가 최신 내용으로 업데이트 됐습니다.', color: 'success'});
});
 
} else {
window.alternativeMessageInput.setHint(window.alternativeMessageInput.calculateLength(input) > 90 ? 'LMS 건당 3포인트 차감' : 'SMS 건당 1포인트 차감');
}
// 대체 본문 기준 SMS/LMS 판단 — LMS 일 때만 제목 입력 활성화
setAlternativeSmsTitleEnabled(window.alternativeMessageInput.calculateLength(input) > 90);
}
});
 
setDefaultPreview(['ALIMTALK_TEXT', 'ALTERNATIVE_TEXT']);
}
</script>
 
<script type="text/javascript">
// ===== SMS/LMS 대체 메시지 제목 (규칙·디자인은 SMS/친구톡 대체와 동일) =====
function getAlternativeSmsTitleInput() {
return document.querySelector('input[name="alternativeSmsTitle"]');
}
 
// 제목 입력 — 이모지 제거 후 40bytes 규칙 적용 (applySmsTitleByteRule)
function handleAlternativeSmsTitleInput() {
const input = getAlternativeSmsTitleInput();
const value = applySmsTitleByteRule(input, window.alternativeMessageInput);
document.querySelector('[data-alternative-sms-title-current]').textContent = window.alternativeMessageInput.getByteLength(value);
updateAlternativeSmsTitlePreview();
}
 
// 대체 본문이 LMS(90bytes 초과)일 때만 제목 활성 — SMS 로 돌아오면 값은 유지한 채 비활성
function setAlternativeSmsTitleEnabled(enabled) {
const input = getAlternativeSmsTitleInput();
if (!input) return;
// 대체 미지원(캐러셀·와이드아이템 등)이면 본문 길이와 무관하게 비활성 — payload 에도 실리지 않는다
if (typeof enableAlternative === 'function' && !enableAlternative(getActivatedSendMethod())) {
enabled = false;
}
input.disabled = !enabled;
input.closest('.ncua-input').classList.toggle('is-disabled', !enabled);
updateAlternativeSmsTitlePreview();
}
 
function isAlternativeSmsTitleEnabled() {
const input = getAlternativeSmsTitleInput();
return !!input && !input.disabled;
}
 
// 발송 요청용 제목 — LMS 활성 상태에서 값이 있을 때만. 미입력 시 null('제목없음' 처리는 발송 시스템 담당)
function getAlternativeSmsTitleForSend() {
return resolveTitleForSend(getAlternativeSmsTitleInput(), isAlternativeSmsTitleEnabled());
}
 
// 미리보기 — LMS 상태에서 값이 있을 때만 노출, 치환자는 공란으로 표기
function updateAlternativeSmsTitlePreview() {
window.alternativePreview.setTitle(isAlternativeSmsTitleEnabled() ? stripTitleReplaceCodes(getAlternativeSmsTitleInput().value) : '');
}
 
// 메인 제목 → 대체 제목 매핑 — 알림톡은 강조표기형(templateTitle)만 매핑 대상
function copyMainTitleToAlternativeSmsTitle() {
const input = getAlternativeSmsTitleInput();
if (!input || getActivatedSendMethod() !== 'ALIMTALK') return;
input.value = getSelectedKakaoAlimtalkTemplate()?.templateTitle ?? '';
// 본문 카피 직후엔 입력 이벤트가 없어 활성 상태가 갱신되지 않는다 — 여기서 다시 잡아야 payload 에 실린다
setAlternativeSmsTitleEnabled(window.alternativeMessageInput.calculateLength(window.alternativeMessageInput.getValue() ?? '') > 90);
handleAlternativeSmsTitleInput();
}
 
getAlternativeSmsTitleInput().addEventListener('input', handleAlternativeSmsTitleInput);
</script>
 
Admin/crm/mobile_send/layer_kakao_friendtalk_content_setting.php+73-2
변경 전layer_kakao_friendtalk_content_setting.php
return !!input && !input.disabled;
}
 
// 발송 요청용 제목 — LMS 활성 상태에서 값이 있을 때만. 미입력 시 null('제목없음' 처리는 발송 시스템 담당)
function getAlternativeSmsTitleForSend() {
return resolveTitleForSend(getAlternativeSmsTitleInput(), isAlternativeSmsTitleEnabled());
}
 
<div class="message-content send-method-component" data-target="ALTERNATIVE">
<div class="ncua-card__body-title-wrap">
<p class="ncua-card__body-title--xs">내용 입력</p>
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--text-gray has-underline refresh-btn load-main-contents">
메시지 최신 상태 반영
</button>
</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<div id="messageContentAlternativeContainer"></div>
</div>
<div id="replaceCodeAlternativeContainer" class="js-replace-code send-method-component" data-target="ALTERNATIVE"></div>
<ul>
<li>SMS 작성 시 90byte를 초과하면 LMS로 자동 전환되어 발송됩니다.</li>
<li>변수에 실제 데이터가 적용된 최종 메시지가 90byte를 초과하는 경우에도 LMS로 전환될 수 있습니다.</li>
 
</ul>
</div>
</details>
updateCampaignTypeComponent(radio.value);
updateDefaultPreview(radio.value);
applyFriendtalkRecipeDefaults(radio.value);
 
 
 
 
});
});
 
}
});
 
 
 
kakaoFriendtalkTabSection.querySelector('input[name="friendtalkCampaignHeader"]').addEventListener('input', function(e) {
window.preview.setTitle(resolveReplaceCode(e.target.value));
if (e.target.value === '') {
 
const initialTextContent = <?= json_encode($initialTextContent ?? '', JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) ?>;
const initialAlternativeMessage = <?= json_encode($initialAlternativeMessage ?? '', JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) ?>;
 
 
if (isRecipeContext) {
const wideItemPointHint = kakaoFriendtalkTabSection.querySelector('.js-wide-item-point-hint');
} else {
window.alternativeMessageInput.setHint(window.alternativeMessageInput.calculateLength(input) > 90 ? 'LMS 건당 3포인트 차감' : 'SMS 건당 1포인트 차감');
}
 
 
}
});
 
MobileMessageReplaceCode.updateExpireCodeGuide('#replaceCodeAlternativeContainer', initialAlternativeMessage);
}
 
 
 
 
 
 
 
 
applyFriendtalkRecipeDefaults(getCampaignMessageType());
}
 
updateItemListPreview();
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
// 친구톡 아이템 이미지 업로드 초기화
function initItemImageFileInput(containerId, itemKey, isFirst = false) {
const container = document.getElementById(containerId);
 
변경 후layer_kakao_friendtalk_content_setting.php
return !!input && !input.disabled;
}
 
// 발송 요청용 제목 — LMS(제목 활성)일 때만 전송, 미입력·치환자만 입력 시 '[제목없음]' 으로 발송
function getAlternativeSmsTitleForSend() {
return resolveTitleForSend(getAlternativeSmsTitleInput(), isAlternativeSmsTitleEnabled());
}
 
<div class="message-content send-method-component" data-target="ALTERNATIVE">
<div class="ncua-card__body-title-wrap">
<p class="ncua-card__body-title--xs tooltip-align" data-tooltip-seq="010">내용 입력</p>
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--text-gray has-underline refresh-btn load-main-contents">
메시지 최신 상태 반영
</button>
</div>
<div class="message-title-field">
<div class="ncua-input ncua-input--xs ncua-input--full-width-with-count is-disabled">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input name="alternativeSmsTitle" type="text" value="" placeholder="제목을 입력해 주세요." disabled />
</div>
</div>
<div class="ncua-input__field-text-count">
<output class="ncua-input__field-text-count-current" data-alternative-sms-title-current>0</output><span>/40</span>
</div>
</div>
</div>
</div>
<div id="messageContentAlternativeContainer"></div>
</div>
<div id="replaceCodeAlternativeContainer" class="js-replace-code send-method-component" data-target="ALTERNATIVE"></div>
<ul>
<li>SMS 작성 시 90byte를 초과하면 LMS로 자동 전환되어 발송됩니다.</li>
<li>변수에 실제 데이터가 적용된 최종 메시지가 90byte를 초과하는 경우에도 LMS로 전환될 수 있습니다.</li>
<li>본문 내용이 LMS인 경우에만 제목 입력이 가능하며, 제목 영역에는 직접 (광고) 문구를 입력하지 않도록 하고 광고 삽입이 필요한 경우 광고성 문구 추가 기능을 사용해야 합니다.</li>
</ul>
</div>
</details>
updateCampaignTypeComponent(radio.value);
updateDefaultPreview(radio.value);
applyFriendtalkRecipeDefaults(radio.value);
// 유형에 따라 대체 지원 여부가 바뀌므로 제목 활성 상태를 다시 평가 (초기 change 는 대체 입력 생성 전이라 건너뜀)
if (window.alternativeMessageInput) {
setAlternativeSmsTitleEnabled(window.alternativeMessageInput.calculateLength(window.alternativeMessageInput.getValue() ?? '') > 90);
}
});
});
 
}
});
 
getAlternativeSmsTitleInput().addEventListener('input', handleAlternativeSmsTitleInput);
 
kakaoFriendtalkTabSection.querySelector('input[name="friendtalkCampaignHeader"]').addEventListener('input', function(e) {
window.preview.setTitle(resolveReplaceCode(e.target.value));
if (e.target.value === '') {
 
const initialTextContent = <?= json_encode($initialTextContent ?? '', JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) ?>;
const initialAlternativeMessage = <?= json_encode($initialAlternativeMessage ?? '', JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) ?>;
const initialAlternativeTitle = <?= json_encode($initialAlternativeTitle ?? '', JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) ?>;
 
if (isRecipeContext) {
const wideItemPointHint = kakaoFriendtalkTabSection.querySelector('.js-wide-item-point-hint');
} else {
window.alternativeMessageInput.setHint(window.alternativeMessageInput.calculateLength(input) > 90 ? 'LMS 건당 3포인트 차감' : 'SMS 건당 1포인트 차감');
}
// 대체 본문 기준 SMS/LMS 판단 — LMS 일 때만 제목 입력 활성화
setAlternativeSmsTitleEnabled(window.alternativeMessageInput.calculateLength(input) > 90);
}
});
 
MobileMessageReplaceCode.updateExpireCodeGuide('#replaceCodeAlternativeContainer', initialAlternativeMessage);
}
 
// 레시피 기본 대체 제목 prefill — 대체 본문이 LMS면 활성 노출, SMS면 값만 유지한 채 비활성 (메인 SMS 레이어와 동일)
if (initialAlternativeTitle !== '') {
getAlternativeSmsTitleInput().value = initialAlternativeTitle;
setAlternativeSmsTitleEnabled(window.alternativeMessageInput.calculateLength(initialAlternativeMessage) > 90);
handleAlternativeSmsTitleInput();
}
 
applyFriendtalkRecipeDefaults(getCampaignMessageType());
}
 
updateItemListPreview();
}
 
// ===== SMS/LMS 대체 메시지 제목 (규칙은 SMS 레이어의 제목과 동일) =====
function getAlternativeSmsTitleInput() {
return kakaoFriendtalkTabSection.querySelector('input[name="alternativeSmsTitle"]');
}
 
// 제목 입력 — 이모지 제거 후 40bytes 규칙 적용 (applySmsTitleByteRule)
function handleAlternativeSmsTitleInput() {
const input = getAlternativeSmsTitleInput();
const value = applySmsTitleByteRule(input, window.alternativeMessageInput);
kakaoFriendtalkTabSection.querySelector('[data-alternative-sms-title-current]').textContent = window.alternativeMessageInput.getByteLength(value);
updateAlternativeSmsTitlePreview();
}
 
// 대체 본문이 LMS(90bytes 초과)일 때만 제목 활성 — SMS 로 돌아오면 값은 유지한 채 비활성
function setAlternativeSmsTitleEnabled(enabled) {
const input = getAlternativeSmsTitleInput();
// 대체 미지원(캐러셀·와이드아이템 등)이면 본문 길이와 무관하게 비활성 — payload 에도 실리지 않는다
if (typeof enableAlternative === 'function' && !enableAlternative(getActivatedSendMethod())) {
enabled = false;
}
input.disabled = !enabled;
input.closest('.ncua-input').classList.toggle('is-disabled', !enabled);
updateAlternativeSmsTitlePreview();
}
 
function isAlternativeSmsTitleEnabled() {
const input = getAlternativeSmsTitleInput();
return !!input && !input.disabled;
}
 
// 발송 요청용 제목 — LMS 활성 상태에서 값이 있을 때만. 미입력 시 null('제목없음' 처리는 발송 시스템 담당)
function getAlternativeSmsTitleForSend() {
return resolveTitleForSend(getAlternativeSmsTitleInput(), isAlternativeSmsTitleEnabled());
}
 
// 미리보기 — LMS 상태에서 값이 있을 때만 노출, 치환자는 공란으로 표기
function updateAlternativeSmsTitlePreview() {
window.alternativePreview.setTitle(isAlternativeSmsTitleEnabled() ? stripTitleReplaceCodes(getAlternativeSmsTitleInput().value) : '');
}
 
// 친구톡 아이템 이미지 업로드 초기화
function initItemImageFileInput(containerId, itemKey, isFirst = false) {
const container = document.getElementById(containerId);
 
Admin/crm/mobile_send/layer_mobile_send_save_sms_template.php+3-1
변경 전layer_mobile_send_save_sms_template.php
 
<div class="modal-dialog__content layer-save-as-template">
<input type="hidden" id="templateContents" value="<?= htmlspecialchars($contents, ENT_QUOTES) ?>" />
 
<div class="ncua-table ncua-table--vertical">
<table>
<tbody>
mode: 'save',
category: category.value,
subject: subject.value.trim(),
contents: contents.value
 
}, function(response) {
if (response.success) {
NCDSToast({ message: '저장되었습니다.', color: 'success' });
 
변경 후layer_mobile_send_save_sms_template.php
 
<div class="modal-dialog__content layer-save-as-template">
<input type="hidden" id="templateContents" value="<?= htmlspecialchars($contents, ENT_QUOTES) ?>" />
<input type="hidden" id="templateMessageTitle" value="<?= htmlspecialchars($messageTitle ?? '', ENT_QUOTES) ?>" />
<div class="ncua-table ncua-table--vertical">
<table>
<tbody>
mode: 'save',
category: category.value,
subject: subject.value.trim(),
contents: contents.value,
messageTitle: document.querySelector('#templateMessageTitle').value
}, function(response) {
if (response.success) {
NCDSToast({ message: '저장되었습니다.', color: 'success' });
 
Admin/crm/mobile_send/layer_mobile_send_sms_template.php+3-3
변경 전layer_mobile_send_sms_template.php
}
}
 
function applyTemplateSmsContents(contents) {
const convertedContents = contents
.replace(/\\r\\n/g, '\n')
.replace(/\\n/g, '\n');
 
setTemplate(convertedContents);
}
 
document.getElementById('btnSearch').addEventListener('click', function() {
return;
}
 
applyTemplateSmsContents(selected.dataset.contents);
layer_close();
});
 
 
변경 후layer_mobile_send_sms_template.php
}
}
 
function applyTemplateSmsContents(contents, messageTitle = '') {
const convertedContents = contents
.replace(/\\r\\n/g, '\n')
.replace(/\\n/g, '\n');
 
setTemplate(convertedContents, messageTitle);
}
 
document.getElementById('btnSearch').addEventListener('click', function() {
return;
}
 
applyTemplateSmsContents(selected.dataset.contents, selected.dataset.messageTitle ?? '');
layer_close();
});
 
 
Admin/crm/mobile_send/layer_mobile_send_sms_template_result.php+7-2
변경 전layer_mobile_send_sms_template_result.php
<div class="ncua-align-center">
<label class="ncua-radio-field ncua-radio-field--xs">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="template" value="<?= $template['sno'] ?>" data-contents="<?= htmlspecialchars($template['contents'], ENT_QUOTES) ?>"/>
</span>
</label>
</div>
</td>
<td><div><?= nl2br(htmlspecialchars(str_replace(['\r\n', '\n'], "\n", $template['subject'] ?? ''))) ?></div></td>
<td><div><?= nl2br(htmlspecialchars(str_replace(['\r\n', '\n'], "\n", $template['contents'] ?? ''))) ?></div></td>
 
 
 
 
 
</tr>
<?php endforeach; ?>
<?php endif; ?>
 
변경 후layer_mobile_send_sms_template_result.php
<div class="ncua-align-center">
<label class="ncua-radio-field ncua-radio-field--xs">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="template" value="<?= $template['sno'] ?>" data-contents="<?= htmlspecialchars($template['contents'], ENT_QUOTES) ?>" data-message-title="<?= htmlspecialchars($template['messageTitle'] ?? '', ENT_QUOTES) ?>"/>
</span>
</label>
</div>
</td>
<td><div><?= nl2br(htmlspecialchars(str_replace(['\r\n', '\n'], "\n", $template['subject'] ?? ''))) ?></div></td>
<td>
<?php /* td > div 가 flex 컨테이너라 제목·내용을 하나의 블록으로 감싸야 분리되지 않음 */ ?>
<div>
<div><?php if (($template['messageTitle'] ?? '') !== ''): ?><strong style="display: block;"><?= htmlspecialchars($template['messageTitle']) ?></strong><?php endif; ?><?= nl2br(htmlspecialchars(str_replace(['\r\n', '\n'], "\n", $template['contents'] ?? ''))) ?></div>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
 
Admin/crm/mobile_send/layer_myapp_push_content_setting.php+86-2
변경 전layer_myapp_push_content_setting.php
return !!input && !input.disabled;
}
 
// 발송 요청용 제목 — LMS 활성 상태에서 값이 있을 때만. 미입력 시 null('제목없음' 처리는 발송 시스템 담당)
function getAlternativeSmsTitleForSend() {
return resolveTitleForSend(getAlternativeSmsTitleInput(), isAlternativeSmsTitleEnabled());
}
 
<div class="message-content send-method-component" data-target="ALTERNATIVE">
<div class="ncua-card__body-title-wrap">
<p class="ncua-card__body-title--xs">내용 입력</p>
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--text-gray has-underline refresh-btn load-main-contents">
메시지 최신 상태 반영
</button>
</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<div id="messageContentAlternativeContainer"></div>
</div>
<div id="replaceCodeAlternativeContainer" class="send-method-component" data-target="ALTERNATIVE"></div>
<ul>
<li>SMS 작성 시 90byte를 초과하면 LMS로 자동 전환되어 발송됩니다.</li>
<li>변수에 실제 데이터가 적용된 최종 메시지가 90byte를 초과하는 경우에도 LMS로 전환될 수 있습니다.</li>
 
</ul>
</div>
</details>
alternativeMessageInput.setValue('');
alternativeMessageInput.insertText(convertedMessage);
alternativePreview.setContent(resolveReplaceCode(convertedMessage, false));
 
 
 
NCDSToast({message: '메시지가 최신 내용으로 업데이트 됐습니다.', color: 'success'});
});
 
} else {
window.alternativeMessageInput.setHint(window.alternativeMessageInput.calculateLength(input) > 90 ? 'LMS 건당 3포인트 차감' : 'SMS 건당 1포인트 차감');
}
 
 
}
});
 
window.alternativePreview.setContent(resolveReplaceCode(initialAlternativeMessage, false));
MobileMessageReplaceCode.updateExpireCodeGuide('#replaceCodeAlternativeContainer', initialAlternativeMessage);
}
 
syncMyappTitlePreview(initialMyappTitle);
syncMyappMessagePreview(initialMyappMessage);
}
});
});
</script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후layer_myapp_push_content_setting.php
return !!input && !input.disabled;
}
 
// 발송 요청용 제목 — LMS(제목 활성)일 때만 전송, 미입력·치환자만 입력 시 '[제목없음]' 으로 발송
function getAlternativeSmsTitleForSend() {
return resolveTitleForSend(getAlternativeSmsTitleInput(), isAlternativeSmsTitleEnabled());
}
 
<div class="message-content send-method-component" data-target="ALTERNATIVE">
<div class="ncua-card__body-title-wrap">
<p class="ncua-card__body-title--xs tooltip-align" data-tooltip-seq="010">내용 입력</p>
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--text-gray has-underline refresh-btn load-main-contents">
메시지 최신 상태 반영
</button>
</div>
<div class="message-title-field">
<div class="ncua-input ncua-input--xs ncua-input--full-width-with-count is-disabled">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input name="alternativeSmsTitle" type="text" value="" placeholder="제목을 입력해 주세요." disabled />
</div>
</div>
<div class="ncua-input__field-text-count">
<output class="ncua-input__field-text-count-current" data-alternative-sms-title-current>0</output><span>/40</span>
</div>
</div>
</div>
</div>
<div id="messageContentAlternativeContainer"></div>
</div>
<div id="replaceCodeAlternativeContainer" class="send-method-component" data-target="ALTERNATIVE"></div>
<ul>
<li>SMS 작성 시 90byte를 초과하면 LMS로 자동 전환되어 발송됩니다.</li>
<li>변수에 실제 데이터가 적용된 최종 메시지가 90byte를 초과하는 경우에도 LMS로 전환될 수 있습니다.</li>
<li>본문 내용이 LMS인 경우에만 제목 입력이 가능하며, 제목 영역에는 직접 (광고) 문구를 입력하지 않도록 하고 광고 삽입이 필요한 경우 광고성 문구 추가 기능을 사용해야 합니다.</li>
</ul>
</div>
</details>
alternativeMessageInput.setValue('');
alternativeMessageInput.insertText(convertedMessage);
alternativePreview.setContent(resolveReplaceCode(convertedMessage, false));
 
copyMainTitleToAlternativeSmsTitle();
 
NCDSToast({message: '메시지가 최신 내용으로 업데이트 됐습니다.', color: 'success'});
});
 
} else {
window.alternativeMessageInput.setHint(window.alternativeMessageInput.calculateLength(input) > 90 ? 'LMS 건당 3포인트 차감' : 'SMS 건당 1포인트 차감');
}
// 대체 본문 기준 SMS/LMS 판단 — LMS 일 때만 제목 입력 활성화
setAlternativeSmsTitleEnabled(window.alternativeMessageInput.calculateLength(input) > 90);
}
});
 
window.alternativePreview.setContent(resolveReplaceCode(initialAlternativeMessage, false));
MobileMessageReplaceCode.updateExpireCodeGuide('#replaceCodeAlternativeContainer', initialAlternativeMessage);
}
 
syncMyappTitlePreview(initialMyappTitle);
syncMyappMessagePreview(initialMyappMessage);
}
});
});
</script>
 
<script type="text/javascript">
// ===== SMS/LMS 대체 메시지 제목 (규칙·디자인은 SMS/친구톡 대체와 동일) =====
function getAlternativeSmsTitleInput() {
return document.querySelector('input[name="alternativeSmsTitle"]');
}
 
// 제목 입력 — 이모지 제거 후 40bytes 규칙 적용 (applySmsTitleByteRule)
function handleAlternativeSmsTitleInput() {
const input = getAlternativeSmsTitleInput();
const value = applySmsTitleByteRule(input, window.alternativeMessageInput);
document.querySelector('[data-alternative-sms-title-current]').textContent = window.alternativeMessageInput.getByteLength(value);
updateAlternativeSmsTitlePreview();
}
 
// 대체 본문이 LMS(90bytes 초과)일 때만 제목 활성 — SMS 로 돌아오면 값은 유지한 채 비활성
function setAlternativeSmsTitleEnabled(enabled) {
const input = getAlternativeSmsTitleInput();
if (!input) return;
// 대체 미지원(캐러셀·와이드아이템 등)이면 본문 길이와 무관하게 비활성 — payload 에도 실리지 않는다
if (typeof enableAlternative === 'function' && !enableAlternative(getActivatedSendMethod())) {
enabled = false;
}
input.disabled = !enabled;
input.closest('.ncua-input').classList.toggle('is-disabled', !enabled);
updateAlternativeSmsTitlePreview();
}
 
function isAlternativeSmsTitleEnabled() {
const input = getAlternativeSmsTitleInput();
return !!input && !input.disabled;
}
 
// 발송 요청용 제목 — LMS 활성 상태에서 값이 있을 때만. 미입력 시 null('제목없음' 처리는 발송 시스템 담당)
function getAlternativeSmsTitleForSend() {
return resolveTitleForSend(getAlternativeSmsTitleInput(), isAlternativeSmsTitleEnabled());
}
 
// 미리보기 — LMS 상태에서 값이 있을 때만 노출, 치환자는 공란으로 표기
function updateAlternativeSmsTitlePreview() {
window.alternativePreview.setTitle(isAlternativeSmsTitleEnabled() ? stripTitleReplaceCodes(getAlternativeSmsTitleInput().value) : '');
}
 
function copyMainTitleToAlternativeSmsTitle() {
const input = getAlternativeSmsTitleInput();
if (!input || getActivatedSendMethod() !== 'MYAPP') return;
input.value = document.querySelector('input[name="myappTitle"]')?.value ?? '';
handleAlternativeSmsTitleInput();
}
 
getAlternativeSmsTitleInput().addEventListener('input', handleAlternativeSmsTitleInput);
 
// 레시피 기본 대체 제목 prefill — 대체 본문이 LMS면 활성 노출, SMS면 값만 유지한 채 비활성.
// 제목 접근자가 이 블록에 선언돼 있어(앞 블록의 초기화 시점엔 미정의) 여기서 수행한다.
(function () {
const initialAlternativeTitle = <?= json_encode($initialAlternativeTitle ?? '', JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) ?>;
if (initialAlternativeTitle === '') return;
 
getAlternativeSmsTitleInput().value = initialAlternativeTitle;
setAlternativeSmsTitleEnabled(window.alternativeMessageInput.calculateLength(window.alternativeMessageInput.getValue() ?? '') > 90);
handleAlternativeSmsTitleInput();
})();
</script>
 
Admin/crm/mobile_send/layer_send_method_setting.php+130-2
변경 전layer_send_method_setting.php
previewContainer.setContent(resolveReplaceCode(content));
dispatchMessageContentInput(messageContainer);
 
// 본문 갱신으로 SMS/LMS 판정이 끝난 뒤, LMS(제목 활성)면 제목에도 (광고) 삽입 — 제목 40bytes 에 카운트됨
const titleInput = resolveAdTitleInput(messageContainer);
if (titleInput && !titleInput.disabled && !titleInput.value.startsWith('(광고)')) {
titleInput.value = '(광고)' + titleInput.value;
titleInput.dataset.titleAdInsert = '1'; // 시스템 삽입 표시 — 절삭 대신 에러 처리 (C4-2)
titleInput.dispatchEvent(new Event('input'));
return /^https?:\/\//.test(string);
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
function addAdText(messageContainer, previewContainer) {
 
const content = '(광고)\n' + messageContainer.getValue() + '\n' + smsRejectText;
messageContainer.setValue(content);
previewContainer.setContent(resolveReplaceCode(content));
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
function removeAdText(messageContainer, previewContainer) {
 
 
 
 
 
 
let content = messageContainer.getValue();
content = content.replace(/^\(광고\)\n?/, '');
const escapedRejectText = smsRejectText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
messageContainer.setValue(content);
previewContainer.setContent(resolveReplaceCode(content));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
/**
 
변경 후layer_send_method_setting.php
previewContainer.setContent(resolveReplaceCode(content));
dispatchMessageContentInput(messageContainer);
 
// 제목에도 (광고) 삽입 — 제목 영역이 비활성(SMS)이어도 삽입한다(레거시 동작과 동일). 제목 40bytes 에 카운트됨
const titleInput = resolveAdTitleInput(messageContainer);
if (titleInput && !titleInput.value.startsWith('(광고)')) {
titleInput.value = '(광고)' + titleInput.value;
titleInput.dataset.titleAdInsert = '1'; // 시스템 삽입 표시 — 절삭 대신 에러 처리 (C4-2)
titleInput.dispatchEvent(new Event('input'));
return /^https?:\/\//.test(string);
}
 
// LMS 제목 최대 byte — 직접 입력 초과는 절삭(C1-1), (광고) 삽입 초과는 에러 유지(C4-2)
const SMS_TITLE_MAX_BYTES = 40;
 
// 제목 인풋과 같은 탭의 '광고성 문구 추가' 체크박스
function getAdCheckboxForTitle(titleInput) {
for (let node = titleInput.parentElement; node; node = node.parentElement) {
const checkbox = node.querySelector('input[name="use080Reject"]');
if (checkbox) return checkbox;
}
return null;
}
 
// 초과 에러 표기 — destructive 로 테두리·byte 카운터가 함께 빨간색이 된다
function setSmsTitleOverState(titleInput, isOver) {
titleInput.classList.toggle('js-title-over', isOver);
 
if (isOver) {
NCDSValidator.highlight(titleInput);
} else {
NCDSValidator.unhighlight(titleInput);
}
}
 
// 제목 byte 규칙 적용(이모지 제거 포함) 후 최종 값 반환
function applySmsTitleByteRule(titleInput, messageInputModule) {
let value = messageInputModule.removeEmoji(titleInput.value).value;
const isOver = messageInputModule.getByteLength(value) > SMS_TITLE_MAX_BYTES;
// addAdText 가 (광고) 삽입 시에만 세우는 1회성 플래그 (사용자 타이핑과 구분)
const adInserted = titleInput.dataset.titleAdInsert === '1';
const base = titleInput.dataset.titleOverBase;
let caret = null;
 
delete titleInput.dataset.titleAdInsert;
 
if (isOver && (adInserted || base !== undefined)) {
// C4-2 — 절삭하지 않는다. 초과 상태에서는 늘리는 입력만 되돌리고 삭제는 허용
if (!adInserted && base !== undefined && messageInputModule.getByteLength(value) > messageInputModule.getByteLength(base)) {
// 되돌린 만큼 커서를 입력 직전 위치로 복원
caret = Math.max(0, (titleInput.selectionStart ?? value.length) - (value.length - base.length));
value = base;
}
titleInput.dataset.titleOverBase = value;
setSmsTitleOverState(titleInput, true);
} else {
// C1-1 — 직접 입력 초과는 (광고) 체크 여부와 무관하게 자동 절삭
value = messageInputModule.truncateBytes(value, SMS_TITLE_MAX_BYTES);
delete titleInput.dataset.titleOverBase;
setSmsTitleOverState(titleInput, false);
}
 
if (titleInput.value !== value) {
titleInput.value = value;
if (caret !== null) {
titleInput.setSelectionRange(caret, caret);
}
}
return value;
}
 
// 발송 버튼의 .destructive 일괄 초기화(mobile_send.php) 후 미해소 에러 재표기
function refreshOverSmsTitleState() {
document.querySelectorAll('input[name="smsTitle"], input[name="alternativeSmsTitle"]')
.forEach(input => {
if (input.classList.contains('js-title-over')) {
setSmsTitleOverState(input, true);
}
});
}
 
// 초과 제목이 숨은 탭에 있으면 그 탭으로 전환해 에러 표시를 보이게 한다
function revealSmsTitleInput(input) {
const target = input.closest('.send-method-component')?.dataset.target;
const tabButton = target
? document.querySelector(`.ncua-tab-button[data-send-tab-type="${target}"]`)
: null;
 
if (tabButton && !tabButton.classList.contains('is-active')) {
tabButton.click();
}
 
NCDSValidator.highlight(input);
input.focus();
input.scrollIntoView({ block: 'center' });
}
 
// 발송 차단 검사용 — payload 에 실리는 기준(disabled)과 동일하게 판정한다.
// 가시성으로 거르면 탭을 옮겨 숨겨진 제목이 검사를 건너뛴 채 전송된다.
function findOverSmsTitleInput() {
const titleInputs = document.querySelectorAll('input[name="smsTitle"], input[name="alternativeSmsTitle"]');
return Array.from(titleInputs)
.find(input => !input.disabled && input.classList.contains('js-title-over')) ?? null;
}
 
function addAdText(messageContainer, previewContainer) {
// 본문은 SMS/LMS 공통으로 (광고) 삽입
const content = '(광고)\n' + messageContainer.getValue() + '\n' + smsRejectText;
messageContainer.setValue(content);
previewContainer.setContent(resolveReplaceCode(content));
dispatchMessageContentInput(messageContainer);
 
// 본문 갱신으로 SMS/LMS 판정이 끝난 뒤, LMS(제목 활성)면 제목에도 (광고) 삽입 — 제목 40bytes 에 카운트됨
const titleInput = resolveAdTitleInput(messageContainer);
if (titleInput && !titleInput.disabled && !titleInput.value.startsWith('(광고)')) {
titleInput.value = '(광고)' + titleInput.value;
titleInput.dataset.titleAdInsert = '1'; // 시스템 삽입 표시 — 절삭 대신 에러 처리 (C4-2)
titleInput.dispatchEvent(new Event('input'));
// 초과했다면 해당 영역으로 포커싱
if (titleInput.classList.contains('js-title-over')) {
titleInput.focus();
}
}
}
 
function removeAdText(messageContainer, previewContainer) {
const titleInput = resolveAdTitleInput(messageContainer);
if (titleInput && titleInput.value.startsWith('(광고)')) {
titleInput.value = titleInput.value.replace(/^\(광고\)/, '');
titleInput.dispatchEvent(new Event('input'));
}
 
let content = messageContainer.getValue();
content = content.replace(/^\(광고\)\n?/, '');
const escapedRejectText = smsRejectText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
messageContainer.setValue(content);
previewContainer.setContent(resolveReplaceCode(content));
dispatchMessageContentInput(messageContainer);
}
 
// 광고 문구가 들어갈 제목 인풋 — 대체 메시지 컨테이너면 대체 제목, 아니면 메인 제목
function resolveAdTitleInput(messageContainer) {
return messageContainer === window.alternativeMessageInput
? document.querySelector('input[name="alternativeSmsTitle"]')
: document.querySelector('input[name="smsTitle"]');
}
 
// setValue 는 MessageInput 의 onInput 콜백을 부르지 않으므로, 본문 변경 후
// SMS/LMS 판단(제목 활성 토글·힌트 갱신)이 다시 돌도록 input 이벤트를 수동 발행
function dispatchMessageContentInput(messageContainer) {
const name = messageContainer === window.alternativeMessageInput ? 'messageAlternativeContent' : 'messageContent';
document.querySelector(`textarea[name="${name}"]`)?.dispatchEvent(new Event('input'));
}
 
/**
 
Admin/crm/mobile_send/layer_sms_content_setting.php+98-6
변경 전layer_sms_content_setting.php
return !!input && !input.disabled;
}
 
// 발송 요청용 제목 — LMS 활성 상태에서 값이 있을 때만. 미입력 시 null('제목없음' 처리는 발송 시스템 담당)
function getSmsTitleForSend() {
return resolveTitleForSend(getSmsTitleInput(), isSmsTitleEnabled());
}
<div class="tab-content-wrap">
<div class="message-content">
<div class="ncua-card__body-title-wrap">
<p class="ncua-card__body-title--xs">내용 입력</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
</div>
<div id="messageContentContainer"></div>
</div>
<ul>
<li>SMS 작성 시 90byte를 초과하면 LMS로 자동 전환되어 발송됩니다.</li>
<li>변수에 실제 데이터가 적용된 최종 메시지가 90byte를 초과하는 경우에도 LMS로 전환될 수 있습니다.</li>
 
</ul>
</div>
</details>
 
$(document).ready(function () {
initializeSmsContentSettingSectionEvents();
initializeSmsContentSettingSectionElement();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
});
 
function initializeSmsContentSettingSectionEvents() {
});
return;
}
const smsData = [{ name: "contents", value: message }];
 
 
 
 
$.post('./mobile_send/layer_mobile_send_save_sms_template.php', smsData, function (data) {
ncds_layer_popup({message: data, title: '템플릿으로 저장<div class="modal-title-description">작성한 내용을 저장할 카테고리와 제목을 선택하여 주시기 바랍니다.</div>', size: 'wide-sm'});
});
removeAdText(window.messageInput, window.preview);
}
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
// 칩 버튼 생성 함수
 
syncSmsPreviewAndHint(initialMessage);
 
 
 
 
 
 
 
 
const recipientType = document.querySelector('input[name="recipientType"]:checked')?.value;
if (['EXCEL_UPLOAD', 'DIRECT'].includes(recipientType)) {
smsSettingSection.querySelector('.js-replace-code').classList.add('display-none');
} else {
window.messageInput.setHint(window.messageInput.calculateLength(input) > 90 ? 'LMS 건당 3포인트 차감' : 'SMS 건당 1포인트 차감');
}
 
 
}
 
function setTemplate(message) {
smsSettingSection.querySelector('input[name="use080Reject"]').checked = false;
 
// 메시지 적용
window.messageInput.setValue('');
window.messageInput.insertText(message);
 
 
 
 
 
 
// 템플릿 불러오기 안내문구
smsSettingSection.querySelector('#useTemplateCaution').hidden = false;
}
shortLinkInput.value = '';
smsSettingSection.querySelector('[data-role="chip-container"]').innerHTML = '';
window.messageInput.setValue('');
 
 
 
 
 
window.preview.reset();
 
const isImpossibleAddShortLink = document.querySelectorAll(`.chip-button[data-send-tab-type="${getActivatedSendTabType()}"]`).length > 4;
 
변경 후layer_sms_content_setting.php
return !!input && !input.disabled;
}
 
// 발송 요청용 제목 — LMS(제목 활성)일 때만 전송, 미입력·치환자만 입력 시 '[제목없음]' 으로 발송
function getSmsTitleForSend() {
return resolveTitleForSend(getSmsTitleInput(), isSmsTitleEnabled());
}
<div class="tab-content-wrap">
<div class="message-content">
<div class="ncua-card__body-title-wrap">
<p class="ncua-card__body-title--xs tooltip-align" data-tooltip-seq="010">내용 입력</p>
</div>
<div class="message-title-field">
<div class="ncua-input ncua-input--xs ncua-input--full-width-with-count is-disabled">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input name="smsTitle" type="text" value="" placeholder="제목을 입력해 주세요." disabled />
</div>
</div>
<div class="ncua-input__field-text-count">
<output class="ncua-input__field-text-count-current" data-sms-title-current>0</output><span>/40</span>
</div>
</div>
</div>
</div>
<div id="messageContentContainer"></div>
</div>
<ul>
<li>SMS 작성 시 90byte를 초과하면 LMS로 자동 전환되어 발송됩니다.</li>
<li>변수에 실제 데이터가 적용된 최종 메시지가 90byte를 초과하는 경우에도 LMS로 전환될 수 있습니다.</li>
<li>본문 내용이 LMS인 경우에만 제목 입력이 가능하며, 제목 영역에는 직접 (광고) 문구를 입력하지 않도록 하고 광고 삽입이 필요한 경우 광고성 문구 추가 기능을 사용해야 합니다.</li>
</ul>
</div>
</details>
 
$(document).ready(function () {
initializeSmsContentSettingSectionEvents();
 
// CrmPreview(crm-preview.js)는 loader 가 비동기 로드하므로, 준비 전에 element 를 초기화하면
// MessagePreview 렌더가 null 을 반환해 window.preview 가 null 이 되고, 이후 제목 기본값 prefill·
// LMS 제목 활성화까지 중단된다(레시피 iframe 로드 레이스). → CrmPreview 준비 후 element 초기화.
let isElementInitialized = false;
const runElementInit = function () {
if (isElementInitialized) {
return;
}
isElementInitialized = true;
initializeSmsContentSettingSectionElement();
};
// crmPreviewLoaded 가 ready 이전에 이미 발생했을 수 있으므로(캐시 히트 등) 리스너를 먼저 등록하고,
// 준비 완료 상태면 즉시 실행한다(중복 실행은 isElementInitialized 가드로 방지).
window.addEventListener('crmPreviewLoaded', runElementInit);
if (typeof CrmPreview !== 'undefined' && typeof GodoUIModule !== 'undefined') {
runElementInit();
}
});
 
function initializeSmsContentSettingSectionEvents() {
});
return;
}
// 제목은 SMS/LMS 여부와 무관하게 입력값이 있으면 함께 저장
const smsData = [
{ name: "contents", value: message },
{ name: "messageTitle", value: getSmsTitleInput().value }
];
$.post('./mobile_send/layer_mobile_send_save_sms_template.php', smsData, function (data) {
ncds_layer_popup({message: data, title: '템플릿으로 저장<div class="modal-title-description">작성한 내용을 저장할 카테고리와 제목을 선택하여 주시기 바랍니다.</div>', size: 'wide-sm'});
});
removeAdText(window.messageInput, window.preview);
}
});
 
getSmsTitleInput().addEventListener('input', handleSmsTitleInput);
}
 
function getSmsTitleInput() {
return smsSettingSection.querySelector('input[name="smsTitle"]');
}
 
// 제목 입력 — 이모지 제거 후 40bytes 규칙 적용 (applySmsTitleByteRule)
function handleSmsTitleInput() {
const input = getSmsTitleInput();
const value = applySmsTitleByteRule(input, window.messageInput);
smsSettingSection.querySelector('[data-sms-title-current]').textContent = window.messageInput.getByteLength(value);
updateSmsTitlePreview();
}
 
// 본문이 LMS(90bytes 초과)일 때만 제목 활성 — SMS 로 돌아오면 값은 유지한 채 비활성
function setSmsTitleEnabled(enabled) {
const input = getSmsTitleInput();
input.disabled = !enabled;
input.closest('.ncua-input').classList.toggle('is-disabled', !enabled);
updateSmsTitlePreview();
}
 
function isSmsTitleEnabled() {
const input = getSmsTitleInput();
return !!input && !input.disabled;
}
 
// 발송 요청용 제목 — LMS 활성 상태에서 값이 있을 때만. 미입력 시 null('제목없음' 처리는 발송 시스템 담당)
function getSmsTitleForSend() {
return resolveTitleForSend(getSmsTitleInput(), isSmsTitleEnabled());
}
 
// 미리보기 — LMS 상태에서 값이 있을 때만 노출, 치환자는 공란으로 표기
function updateSmsTitlePreview() {
window.preview.setTitle(isSmsTitleEnabled() ? stripTitleReplaceCodes(getSmsTitleInput().value) : '');
}
 
// 칩 버튼 생성 함수
 
syncSmsPreviewAndHint(initialMessage);
 
// 레시피 기본 제목 prefill — 본문이 LMS면 활성 노출, SMS면 값만 유지한 채 비활성 (setTemplate 패턴)
const initialTitle = <?= json_encode($initialTitle ?? '', JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) ?>;
if (initialTitle !== '') {
getSmsTitleInput().value = initialTitle;
handleSmsTitleInput();
}
 
const recipientType = document.querySelector('input[name="recipientType"]:checked')?.value;
if (['EXCEL_UPLOAD', 'DIRECT'].includes(recipientType)) {
smsSettingSection.querySelector('.js-replace-code').classList.add('display-none');
} else {
window.messageInput.setHint(window.messageInput.calculateLength(input) > 90 ? 'LMS 건당 3포인트 차감' : 'SMS 건당 1포인트 차감');
}
// 본문 기준 SMS/LMS 판단 — LMS 일 때만 제목 입력 활성화 (#15092)
setSmsTitleEnabled(window.messageInput.calculateLength(input) > 90);
}
 
function setTemplate(message, messageTitle = '') {
smsSettingSection.querySelector('input[name="use080Reject"]').checked = false;
 
// 메시지 적용 (insertText 가 onInput 을 호출해 SMS/LMS 판단·제목 활성 토글이 함께 갱신됨)
window.messageInput.setValue('');
window.messageInput.insertText(message);
 
// 저장된 메시지 제목 복원 — SMS 상태면 값만 유지한 채 비활성으로 노출됨
const titleInput = getSmsTitleInput();
titleInput.value = messageTitle ?? '';
handleSmsTitleInput();
 
// 템플릿 불러오기 안내문구
smsSettingSection.querySelector('#useTemplateCaution').hidden = false;
}
shortLinkInput.value = '';
smsSettingSection.querySelector('[data-role="chip-container"]').innerHTML = '';
window.messageInput.setValue('');
const titleInput = getSmsTitleInput();
titleInput.value = '';
setSmsTitleOverState(titleInput, false); // 40byte 초과 에러 표기까지 함께 해제
setSmsTitleEnabled(false);
smsSettingSection.querySelector('[data-sms-title-current]').textContent = '0';
window.preview.reset();
 
const isImpossibleAddShortLink = document.querySelectorAll(`.chip-button[data-send-tab-type="${getActivatedSendTabType()}"]`).length > 4;
 
Admin/crm/mobile_send.php+8
변경 전mobile_send.php
el.querySelector('.ncua-input__destructive-icon-wrap')?.remove();
});
 
 
 
 
 
 
// 발신번호 검증
if (!messageConfig.hasCallNumber) {
NCDSAlert({message: '발신번호가 없어 발송할 수 없습니다.', subMessage: '설정에서 발신번호 등록 후 발송 시도를 해주세요.', iconType: 'error'});
});
window.alternativeMessageInput.insertText(alternativeContents);
window.alternativePreview.setContent(alternativeContents);
 
 
 
}
}
 
 
변경 후mobile_send.php
el.querySelector('.ncua-input__destructive-icon-wrap')?.remove();
});
 
// 제목 40byte 초과(C4-2)는 미해소 에러라 재표기
if (typeof refreshOverSmsTitleState === 'function') {
refreshOverSmsTitleState();
}
 
// 발신번호 검증
if (!messageConfig.hasCallNumber) {
NCDSAlert({message: '발신번호가 없어 발송할 수 없습니다.', subMessage: '설정에서 발신번호 등록 후 발송 시도를 해주세요.', iconType: 'error'});
});
window.alternativeMessageInput.insertText(alternativeContents);
window.alternativePreview.setContent(alternativeContents);
if (typeof copyMainTitleToAlternativeSmsTitle === 'function') {
copyMainTitleToAlternativeSmsTitle();
}
}
}
 
 
Admin/crm/preview_mobile_send/layer_preview_mobile_send_alternative.php+13-1
변경 전layer_preview_mobile_send_alternative.php
<section class="mobile-preview mobile-preview--sms">
<div class="ncua-prev alternative-contents-preview" style="white-space: pre-wrap;"><?= $alternativeContents ?></div>
 
 
 
 
 
 
</section>
<script>
const alternativeSmsPreview = document.querySelector('.alternative-contents-preview');
$(document).ready(() => {
alternativeSmsPreview.textContent = resolveReplaceCodeForPreview(alternativeSmsPreview.textContent);
 
 
 
 
 
 
})
</script>
 
변경 후layer_preview_mobile_send_alternative.php
<section class="mobile-preview mobile-preview--sms">
<?php /* pre-wrap 은 본문에만 — 말풍선에 걸면 템플릿 마크업의 공백·줄바꿈까지 렌더링됨 */ ?>
<div class="ncua-prev">
<?php if (($alternativeTitle ?? '') !== ''): ?>
<div class="sms-title-side-preview alternative-title-preview"><?= gd_htmlspecialchars($alternativeTitle) ?></div>
<?php endif; ?>
<div class="alternative-contents-preview" style="white-space: pre-wrap;"><?= $alternativeContents ?></div>
</div>
</section>
<script>
const alternativeSmsPreview = document.querySelector('.alternative-contents-preview');
$(document).ready(() => {
alternativeSmsPreview.textContent = resolveReplaceCodeForPreview(alternativeSmsPreview.textContent);
 
// 제목은 변수 치환 미지원 — 변수는 공란으로 표기
const alternativeTitlePreview = document.querySelector('.alternative-title-preview');
if (alternativeTitlePreview) {
alternativeTitlePreview.textContent = alternativeTitlePreview.textContent.replace(/[#$]?\{[^}]*\}/g, '');
}
})
</script>
 
Admin/crm/preview_mobile_send/layer_preview_mobile_send_sms.php+13-1
변경 전layer_preview_mobile_send_sms.php
<section class="mobile-preview mobile-preview--sms">
<div class="ncua-prev sms-contents-preview" style="white-space: pre-wrap;"><?= $mainContents ?></div>
 
 
 
 
 
 
</section>
<script>
const smsPreview = document.querySelector('.sms-contents-preview');
$(document).ready(() => {
smsPreview.textContent = resolveReplaceCodeForPreview(smsPreview.textContent);
 
 
 
 
 
 
})
</script>
 
변경 후layer_preview_mobile_send_sms.php
<section class="mobile-preview mobile-preview--sms">
<?php /* pre-wrap 은 본문에만 — 말풍선에 걸면 템플릿 마크업의 공백·줄바꿈까지 렌더링됨 */ ?>
<div class="ncua-prev">
<?php if (($mainTitle ?? '') !== ''): ?>
<div class="sms-title-side-preview sms-title-preview"><?= gd_htmlspecialchars($mainTitle) ?></div>
<?php endif; ?>
<div class="sms-contents-preview" style="white-space: pre-wrap;"><?= $mainContents ?></div>
</div>
</section>
<script>
const smsPreview = document.querySelector('.sms-contents-preview');
$(document).ready(() => {
smsPreview.textContent = resolveReplaceCodeForPreview(smsPreview.textContent);
 
// 제목은 변수 치환 미지원 — 변수는 공란으로 표기
const smsTitlePreview = document.querySelector('.sms-title-preview');
if (smsTitlePreview) {
smsTitlePreview.textContent = smsTitlePreview.textContent.replace(/[#$]?\{[^}]*\}/g, '');
}
})
</script>
 
Admin/member/sms_auto.php+43-4
변경 전sms_auto.php
</label>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][recipientMode]" value="<?php echo $typeData['recipientMode']; ?>"/>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][recipientSno]" value="<?php echo $typeData['recipientSno']; ?>"/>
<textarea name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][recipientContents]" rows="4" class="form-control"><?php echo $typeData['recipientContents']; ?></textarea>
 
 
 
</td>
<?php } ?>
<td class="form-inline">
</label>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][memberMode]" value="<?php echo $typeData['memberMode']; ?>"/>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][memberSno]" value="<?php echo $typeData['memberSno']; ?>"/>
<textarea name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][memberContents]" rows="4" class="form-control"><?php echo $typeData['memberContents']; ?></textarea>
 
 
 
<?php } else { ?>
<div class="text-center text-blue">운영자 전용</div>
<?php } ?>
</label>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][adminMode]" value="<?php echo $typeData['adminMode']; ?>"/>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][adminSno]" value="<?php echo $typeData['adminSno']; ?>"/>
 
 
 
<textarea name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][adminContents]" rows="4"
class="form-control <?php if ($sKey == 'admin') { ?> width100p <?php } ?>"><?php echo $typeData['adminContents']; ?></textarea>
<?php } else { ?>
<div class="text-center text-orange-red">회원 전용</div>
<?php } ?>
</label>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][providerMode]" value="<?php echo $typeData['providerMode']; ?>"/>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][providerSno]" value="<?php echo $typeData['providerSno']; ?>"/>
<textarea name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][providerContents]" rows="4" class="form-control"><?php echo $typeData['providerContents']; ?></textarea>
 
 
 
<?php } else { ?>
<?php if ($sKey == 'admin') { ?>
<div class="text-center text-orange-red">본사 전용</div>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][providerSno]" value="<?php echo $typeData['providerSno']; ?>"/>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][providerMode]" value="<?php echo $typeData['providerMode']; ?>"/>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][providerContents]" value="<?php echo $typeData['providerContents']; ?>"/>
 
<?php } ?>
</tr>
<?php
<script type="text/javascript">
<!--
$(document).ready(function () {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$('.btn-change-password').click(function () {
$.get('../share/layer_sms_password.php?mode=change', function () {
BootstrapDialog.closeAll();
 
변경 후sms_auto.php
</label>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][recipientMode]" value="<?php echo $typeData['recipientMode']; ?>"/>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][recipientSno]" value="<?php echo $typeData['recipientSno']; ?>"/>
<div class="js-sms-auto-subject-wrap mgb5">
<input type="text" name="<?= $sKey ?>[<?= $typeData['code'] ?>][recipientMessageTitle]" class="form-control js-sms-auto-subject" placeholder="제목 입력" value="<?= gd_isset($typeData['recipientMessageTitle']) ?>"/>
</div>
<textarea name="<?= $sKey ?>[<?= $typeData['code'] ?>][recipientContents]" rows="4" class="form-control js-sms-auto-contents"><?= $typeData['recipientContents'] ?></textarea>
</td>
<?php } ?>
<td class="form-inline">
</label>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][memberMode]" value="<?php echo $typeData['memberMode']; ?>"/>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][memberSno]" value="<?php echo $typeData['memberSno']; ?>"/>
<div class="js-sms-auto-subject-wrap mgb5">
<input type="text" name="<?= $sKey ?>[<?= $typeData['code'] ?>][memberMessageTitle]" class="form-control js-sms-auto-subject" placeholder="제목 입력" value="<?= gd_isset($typeData['memberMessageTitle']) ?>"/>
</div>
<textarea name="<?= $sKey ?>[<?= $typeData['code'] ?>][memberContents]" rows="4" class="form-control js-sms-auto-contents"><?= $typeData['memberContents'] ?></textarea>
<?php } else { ?>
<div class="text-center text-blue">운영자 전용</div>
<?php } ?>
</label>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][adminMode]" value="<?php echo $typeData['adminMode']; ?>"/>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][adminSno]" value="<?php echo $typeData['adminSno']; ?>"/>
<div class="js-sms-auto-subject-wrap mgb5">
<input type="text" name="<?= $sKey ?>[<?= $typeData['code'] ?>][adminMessageTitle]" class="form-control js-sms-auto-subject" placeholder="제목 입력" value="<?= gd_isset($typeData['adminMessageTitle']) ?>"/>
</div>
<textarea name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][adminContents]" rows="4"
class="form-control js-sms-auto-contents <?php if ($sKey == 'admin') { ?> width100p <?php } ?>"><?= $typeData['adminContents'] ?></textarea>
<?php } else { ?>
<div class="text-center text-orange-red">회원 전용</div>
<?php } ?>
</label>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][providerMode]" value="<?php echo $typeData['providerMode']; ?>"/>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][providerSno]" value="<?php echo $typeData['providerSno']; ?>"/>
<div class="js-sms-auto-subject-wrap mgb5">
<input type="text" name="<?= $sKey ?>[<?= $typeData['code'] ?>][providerMessageTitle]" class="form-control js-sms-auto-subject" placeholder="제목 입력" value="<?= gd_isset($typeData['providerMessageTitle']) ?>"/>
</div>
<textarea name="<?= $sKey ?>[<?= $typeData['code'] ?>][providerContents]" rows="4" class="form-control js-sms-auto-contents"><?= $typeData['providerContents'] ?></textarea>
<?php } else { ?>
<?php if ($sKey == 'admin') { ?>
<div class="text-center text-orange-red">본사 전용</div>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][providerSno]" value="<?php echo $typeData['providerSno']; ?>"/>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][providerMode]" value="<?php echo $typeData['providerMode']; ?>"/>
<input type="hidden" name="<?php echo $sKey; ?>[<?php echo $typeData['code']; ?>][providerContents]" value="<?php echo $typeData['providerContents']; ?>"/>
<input type="hidden" name="<?= $sKey ?>[<?= $typeData['code'] ?>][providerMessageTitle]" value="<?= gd_isset($typeData['providerMessageTitle']) ?>"/>
<?php } ?>
</tr>
<?php
<script type="text/javascript">
<!--
$(document).ready(function () {
// 제목 바이트 계산 (한글 등 멀티바이트 2byte, ASCII 1byte, 개행 제외)
function smsAutoStringToByte(str) {
var length = 0;
for (var i = 0; i < str.length; i++) {
if (escape(str.charAt(i)).length >= 4) length += 2;
else if (escape(str.charAt(i)) != "%0D") length++;
}
return length;
}
// 이모티콘 제거 (서로게이트 페어 + 변형선택자, 기획: 제목·본문 입력 불가)
function smsAutoStripEmoji(str) {
if (!str) return str;
return str.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '').replace(/[\uFE00-\uFE0F\u200D\u20E3]/g, '');
}
// 자동발송 제목 영역은 항상 활성 노출(C3). 본문은 이모티콘만 자동 제거.
$('.js-sms-auto-contents').on('keyup change', function () {
var $ta = $(this);
var stripped = smsAutoStripEmoji($ta.val());
if (stripped !== $ta.val()) { $ta.val(stripped); }
});
$('.js-sms-auto-subject').on('keyup keydown', function () {
var input = $(this);
var text = smsAutoStripEmoji(input.val());
while (smsAutoStringToByte(text) > <?= $lmsSubjectLimit; ?>) { text = text.substring(0, text.length - 1); }
input.val(text);
});
$('.btn-change-password').click(function () {
$.get('../share/layer_sms_password.php?mode=change', function () {
BootstrapDialog.closeAll();
 
Admin/member/sms_log.php+4
변경 전sms_log.php
$listHtml[] = '<td class="text-center number">' . ($data['receiverCnt'] * $lmsPoint) . '</td>';
}
$listHtml[] = '<td style="word-break:break-all;word-wrap:break-word;overflow:hidden;" class="multirows">';
 
 
 
 
$listHtml[] = '<div class="div-contents">' . $data['contents'] . '</div>';
if ($data['sendStatus'] === 'r' && $data['smsType'] === 'user') {
$listHtml[] = '<div><button type="button" class="btn btn-xs btn-gray btn-modify">수정하기</button></div>';
 
변경 후sms_log.php
$listHtml[] = '<td class="text-center number">' . ($data['receiverCnt'] * $lmsPoint) . '</td>';
}
$listHtml[] = '<td style="word-break:break-all;word-wrap:break-word;overflow:hidden;" class="multirows">';
if ($data['sendFl'] === 'lms') {
$subjectText = ($data['subject'] !== null && $data['subject'] !== '') ? $data['subject'] : '[' . __('제목없음') . ']';
$listHtml[] = '<div class="div-subject text-darkblue"><strong>' . gd_htmlspecialchars($subjectText) . '</strong></div>';
}
$listHtml[] = '<div class="div-contents">' . $data['contents'] . '</div>';
if ($data['sendStatus'] === 'r' && $data['smsType'] === 'user') {
$listHtml[] = '<div><button type="button" class="btn btn-xs btn-gray btn-modify">수정하기</button></div>';
 
Admin/member/sms_send.php+75-2
변경 전sms_send.php
return length;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* SMS 내용 길이 체크
*/
function setContentsLength(contentsNm, countId) {
var textarea = $('textarea[name=' + contentsNm + ']');
var contentsText = textarea.val();
 
 
 
 
var textLength = stringToByte(contentsText);
if (textLength > <?php echo $smsStringLimit;?>) {
if (textLength > <?php echo $lmsStringLimit;?>) {
$('.sms-type').hide();
$('.lms-type').show();
$('input[name=sendFl]').val('lms');
 
 
} else {
$('#' + countId).css("color", "");
$('.sms-type').show();
$('.lms-type').hide();
$('input[name=sendFl]').val('sms');
 
}
$('#' + countId).val(textLength);
}
setContentsLength('smsContents', 'smsStringCount');
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
function setReceiverCount() {
var checkedType = $('[name=receiverType]:checked').val();
var receiverCount = 0, agreeCount = 0, rejectCount = 0;
dialog: false,
debug: true,
submitHandler: function (form) {
console.log('submitHan', form);
 
 
 
 
 
var divisionInt = 1;
if ($('input[name=sendFl]').val() === 'lms') {
divisionInt = <?php echo $lmsPoint;?>;
<div class="notice-danger pull-left mgl15 display-none">회원 외 치환코드는 정상 적용되지 않으니 저장된 메시지 수정 시에만 이용바랍니다.</div>
</div>
</div>
 
 
 
 
 
 
 
 
<div class="row pdt5">
<div class="col-xs-3 pdr0">
<label class="width100p">
<div class="col-xs-12">
<div class="notice-info">URL 삽입 시 [단축 URL] 앱을 이용하시면 짧고 간단하게 URL을 사용하실 수 있습니다. <a href="https://apps.godo.co.kr/apps/1519" class="btn-link" target="_blank">바로가기</a></div>
<div class="notice-info">치환코드 사용 시 LMS로 발송될 수 있으며 일부 문자가 포인트 부족으로 발송되지 않을 수 있습니다.<br/>- 포인트 충전 후 <a href="../member/sms_log.php" class="btn-link" target="_blank">SMS 발송 내역 보기</a>에서 재발송하실 수 있습니다.</div>
 
<div class="notice-danger sms-large-caution">발송내용에 이모지 또는 사용 불가능한 특수문자, 맞춤법이 바르지 않은 문자가 포함되어있으면 일부 내용은 삭제되고 발송되오니 주의하시기 바랍니다.<br/>대량 SMS(LMS) 발송 시 이모지를 사용하지 않는 것과 맞춤법에 어긋나는 문자가 입력 되지는 않았는지 확인하시는 것을 권장 드리며,<br/> 발송 내용에 사용가능한 특수문자는 매뉴얼을 참고하시기 바랍니다.</div>
</div>
</div>
<%= checkbox %> <%= subject %>
<button type="button" class="btn btn-sm btn-white pull-right js-btn-save" data-text="저장">수정</button>
<button type="button" class="btn btn-sm btn-white pull-right js-btn-insert mgr5">삽입</button>
 
<label class="width100p pdt5">
<textarea rows="13" class="form-control width100p" name="contents"><%= contents %></textarea>
</label>
 
변경 후sms_send.php
return length;
}
 
/**
* 이모티콘 제거 (제목/본문 이모티콘 입력 불가 - 자동 삭제)
*/
function stripEmoji(str) {
if (!str) {
return str;
}
return str
.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '')
.replace(/[\uFE00-\uFE0F\u200D\u20E3]/g, '');
}
 
/**
* SMS 내용 길이 체크
*/
function setContentsLength(contentsNm, countId) {
var textarea = $('textarea[name=' + contentsNm + ']');
var stripped = stripEmoji(textarea.val());
if (stripped !== textarea.val()) {
textarea.val(stripped);
}
var contentsText = stripped;
var textLength = stringToByte(contentsText);
if (textLength > <?php echo $smsStringLimit;?>) {
if (textLength > <?php echo $lmsStringLimit;?>) {
$('.sms-type').hide();
$('.lms-type').show();
$('input[name=sendFl]').val('lms');
$('#smsSubject').prop('disabled', false); // LMS: 제목 영역 활성
setSubjectLength();
} else {
$('#' + countId).css("color", "");
$('.sms-type').show();
$('.lms-type').hide();
$('input[name=sendFl]').val('sms');
$('#smsSubject').prop('disabled', true); // SMS: 제목 영역 비활성(값 유지, 미발송)
}
$('#' + countId).val(textLength);
}
setContentsLength('smsContents', 'smsStringCount');
}
 
/**
* LMS 제목 Byte 체크 (공백 포함 최대 <?= $lmsSubjectLimit; ?>Byte, 초과분 자동 삭제)
*/
function setSubjectLength() {
var input = $('#smsSubject');
if (input.length === 0) {
return;
}
var limit = <?= $lmsSubjectLimit; ?>;
var text = stripEmoji(input.val());
var adChecked = $('input[name=sms080Reject]').is(':checked');
var byte = stringToByte(text);
if (adChecked && byte > limit) {
// C4-2: (광고) 자동 삽입으로 40Byte 초과 -> 절삭하지 않고 에러 표기
input.val(text);
setSubjectOver(true);
} else {
// C1-1: 직접 입력 40Byte 초과분 자동삭제
while (stringToByte(text) > limit) {
text = text.substring(0, text.length - 1);
}
input.val(text);
byte = stringToByte(text);
setSubjectOver(false);
}
$('#smsSubjectCount').val(byte);
}
 
function setSubjectOver(isOver) {
var input = $('#smsSubject');
if (isOver) {
input.addClass('js-subject-over').css('border-color', '#e74c3c');
$('#smsSubjectCount').css('color', '#e74c3c');
} else {
input.removeClass('js-subject-over').css('border-color', '');
$('#smsSubjectCount').css('color', '');
}
}
 
function setReceiverCount() {
var checkedType = $('[name=receiverType]:checked').val();
var receiverCount = 0, agreeCount = 0, rejectCount = 0;
dialog: false,
debug: true,
submitHandler: function (form) {
// C4-2: 제목 40Byte 초과(광고 삽입 등) 시 발송 차단
if ($('input[name=sendFl]').val() === 'lms' && $('#smsSubject').length && stringToByte(stripEmoji($('#smsSubject').val())) > <?= $lmsSubjectLimit; ?>) {
BootstrapDialog.show({title: '경고', message: '제목은 최대 <?= $lmsSubjectLimit; ?>bytes까지 입력 가능합니다.', closable: true});
$('#smsSubject').focus();
return false;
}
var divisionInt = 1;
if ($('input[name=sendFl]').val() === 'lms') {
divisionInt = <?php echo $lmsPoint;?>;
<div class="notice-danger pull-left mgl15 display-none">회원 외 치환코드는 정상 적용되지 않으니 저장된 메시지 수정 시에만 이용바랍니다.</div>
</div>
</div>
<div class="row pdt5 js-subject-row">
<div class="col-xs-3 pdr0">
<input type="text" name="smsSubject" id="smsSubject" class="form-control width100p" placeholder="제목을 입력해주세요." value="<?= gd_isset($smsSubject); ?>" onkeyup="setSubjectLength();" onkeydown="setSubjectLength();" disabled="disabled"/>
<div class="mgt5">
<input type="text" id="smsSubjectCount" value="0" readonly="readonly" class="form-control width-3xs"> / <?= $lmsSubjectLimit; ?> Bytes
</div>
</div>
</div>
<div class="row pdt5">
<div class="col-xs-3 pdr0">
<label class="width100p">
<div class="col-xs-12">
<div class="notice-info">URL 삽입 시 [단축 URL] 앱을 이용하시면 짧고 간단하게 URL을 사용하실 수 있습니다. <a href="https://apps.godo.co.kr/apps/1519" class="btn-link" target="_blank">바로가기</a></div>
<div class="notice-info">치환코드 사용 시 LMS로 발송될 수 있으며 일부 문자가 포인트 부족으로 발송되지 않을 수 있습니다.<br/>- 포인트 충전 후 <a href="../member/sms_log.php" class="btn-link" target="_blank">SMS 발송 내역 보기</a>에서 재발송하실 수 있습니다.</div>
<div class="notice-info">본문 내용이 LMS인 경우에만 제목 입력이 가능하며, 제목 영역에는 직접 (광고) 문구를 입력하지 않도록 하고 광고 삽입이 필요한 경우 광고성 문구 추가 기능을 사용해야 합니다.</div>
<div class="notice-danger sms-large-caution">발송내용에 이모지 또는 사용 불가능한 특수문자, 맞춤법이 바르지 않은 문자가 포함되어있으면 일부 내용은 삭제되고 발송되오니 주의하시기 바랍니다.<br/>대량 SMS(LMS) 발송 시 이모지를 사용하지 않는 것과 맞춤법에 어긋나는 문자가 입력 되지는 않았는지 확인하시는 것을 권장 드리며,<br/> 발송 내용에 사용가능한 특수문자는 매뉴얼을 참고하시기 바랍니다.</div>
</div>
</div>
<%= checkbox %> <%= subject %>
<button type="button" class="btn btn-sm btn-white pull-right js-btn-save" data-text="저장">수정</button>
<button type="button" class="btn btn-sm btn-white pull-right js-btn-insert mgr5">삽입</button>
<input type="text" name="messageTitle" class="form-control width100p mgt5 js-sms-contents-subject" placeholder="제목을 입력해주세요." value="<%= messageTitle %>" disabled="disabled"/>
<label class="width100p pdt5">
<textarea rows="13" class="form-control width100p" name="contents"><%= contents %></textarea>
</label>
 
Admin/promotion/cart_remind_regist.php+69-14
변경 전cart_remind_regist.php
}
if (e.target.checked) {
$body.val('(광고)\n' + $body.val() + '\n무료거부 ' + $('input[name="sms080Reject"]').data('reject-number'));
 
 
 
}
changeCartRemindContentsLength();
 
// 본문 반영으로 SMS/LMS 판정이 끝난 뒤에 제목 처리 — 제목 영역이 비활성(SMS)이면 (광고) 미표기
if (e.target.checked && $subject.length && !$subject.prop('disabled')) {
$subject.val('(광고)' + $subject.val());
}
changeCartRemindSubjectLength();
});
 
}
if (e.target.checked) {
$body.val('(광고)\n' + $body.val() + '\n무료거부 ' + $('input[name="sms080Reject"]').data('reject-number'));
if ($subject.length) {
$subject.val('(광고)' + $subject.val());
}
}
changeCartRemindContentsLength();
 
 
 
 
 
changeCartRemindSubjectLength();
});
 
<span class="cart-remind-sms notice-info">SMS : 발송 건당 1포인트 차감</span>
</div>
</div>
 
 
 
 
<div class="form-inline mgt10">
<textarea name="cartRemindSendMessage" rows="8" class="smsContents form-control width-xl js-sms080-update"><?= $cartRemindData['cartRemindSendMessage']; ?></textarea>
<div class="mgt5">
<a href="https://www.nhn-commerce.com/customer/board-view.gd?type=notice&idx=1237" target="_blank" class="btn-link">자세히보기></a>
</p>
<p class="notice-info">장바구니 링크 치환코드는 LMS인 경우에만 정상적으로 발송됩니다</p>
 
</div>
</td>
</tr>
 
// 글자수 체크
$('textarea[name=cartRemindSendMessage]').keyup(changeCartRemindContentsLength);
 
displayCartRemindCouponLink();
changeCartRemindType();
changeCartRemindSendType();
changeCartRemindManualDirectPeriod();
changeCartRemindContentsLength();
 
 
// 광고성 문구 추가
$('input[name="sms080Reject"]').click(function (e) {
$('.js-sms080-update').val($('.js-sms080-update').val().replace(/(\n무료거부\s[0-9\-]*)/g, ''));
if(e.target.checked){
$('.js-sms080-update').val($('.js-sms080-update').val() + '\n무료거부 ' + $('input[name="sms080Reject"]').data('reject-number'));
};
 
 
 
 
 
 
 
 
 
 
});
 
});
* SMS 내용 길이 체크
*/
function changeCartRemindContentsLength() {
var contentsText = $('textarea[name=cartRemindSendMessage]').val();
 
 
 
 
 
var textLength = stringToByte(contentsText);
 
if ($('input:hidden[name="cartRemindSendType"]').val() == 'sms' && textLength > <?= $smsStringLimit; ?>) {
}
$('#cartRemindSendMessageCount').val(textLength);
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
// 발송유형 따른 폼 변경
function changeCartRemindType() {
if ($('input:radio[name="cartRemindType"]:checked').val() == 'manual') {
$('.cart-remind-sms').hide();
$('.cart-remind-sms select').attr('disabled', 'disabled');
$('.cart-remind-sms input').attr('disabled', 'disabled');
 
 
// sms발송
} else if ($('input:hidden[name="cartRemindSendType"]').val() == 'sms') {
$('.cart-remind-lms').hide();
$('.cart-remind-sms').show();
$('.cart-remind-sms select').removeAttr('disabled');
$('.cart-remind-sms input').removeAttr('disabled');
 
 
}
}
// 발급방식에 따른 폼 변경
}
return length;
}
 
 
 
 
 
 
 
 
 
 
 
 
</script>
 
변경 후cart_remind_regist.php
}
if (e.target.checked) {
$body.val('(광고)\n' + $body.val() + '\n무료거부 ' + $('input[name="sms080Reject"]').data('reject-number'));
if ($subject.length) {
$subject.val('(광고)' + $subject.val());
}
}
changeCartRemindContentsLength();
 
 
 
 
 
changeCartRemindSubjectLength();
});
 
}
if (e.target.checked) {
$body.val('(광고)\n' + $body.val() + '\n무료거부 ' + $('input[name="sms080Reject"]').data('reject-number'));
 
 
 
}
changeCartRemindContentsLength();
 
// 본문 반영으로 SMS/LMS 판정이 끝난 뒤에 제목 처리 — 제목 영역이 비활성(SMS)이면 (광고) 미표기
if (e.target.checked && $subject.length && !$subject.prop('disabled')) {
$subject.val('(광고)' + $subject.val());
}
changeCartRemindSubjectLength();
});
 
<span class="cart-remind-sms notice-info">SMS : 발송 건당 1포인트 차감</span>
</div>
</div>
<div class="form-inline mgt10 js-cart-subject-row">
<input type="text" name="cartRemindSendSubject" id="cartRemindSendSubject" class="form-control width-xl" placeholder="제목을 입력해주세요." value="<?= gd_isset($cartRemindData['cartRemindSendSubject']); ?>" disabled="disabled"/>
<input type="text" id="cartRemindSubjectCount" value="0" readonly="readonly" class="form-control width-3xs"> / <?= $lmsSubjectLimit; ?> Bytes
</div>
<div class="form-inline mgt10">
<textarea name="cartRemindSendMessage" rows="8" class="smsContents form-control width-xl js-sms080-update"><?= $cartRemindData['cartRemindSendMessage']; ?></textarea>
<div class="mgt5">
<a href="https://www.nhn-commerce.com/customer/board-view.gd?type=notice&idx=1237" target="_blank" class="btn-link">자세히보기></a>
</p>
<p class="notice-info">장바구니 링크 치환코드는 LMS인 경우에만 정상적으로 발송됩니다</p>
<p class="notice-info">본문 내용이 LMS인 경우에만 제목 입력이 가능하며, 제목 영역에는 직접 (광고) 문구를 입력하지 않도록 하고 광고 삽입이 필요한 경우 광고성 문구 추가 기능을 사용해야 합니다.</p>
</div>
</td>
</tr>
 
// 글자수 체크
$('textarea[name=cartRemindSendMessage]').keyup(changeCartRemindContentsLength);
$('#cartRemindSendSubject').on('keyup keydown', changeCartRemindSubjectLength);
displayCartRemindCouponLink();
changeCartRemindType();
changeCartRemindSendType();
changeCartRemindManualDirectPeriod();
changeCartRemindContentsLength();
changeCartRemindSubjectLength();
 
// 광고성 문구 추가 (본문: (광고) 접두 + 무료거부 접미 / LMS 제목: (광고) 접두)
$('input[name="sms080Reject"]').click(function (e) {
var $body = $('.js-sms080-update');
$body.val($body.val().replace(/(\(광고\)\n)|(\n무료거부\s[0-9\-]*)/g, ''));
var $subject = $('#cartRemindSendSubject');
if ($subject.length) {
$subject.val($subject.val().replace(/^\(광고\)/, ''));
}
if (e.target.checked) {
$body.val('(광고)\n' + $body.val() + '\n무료거부 ' + $('input[name="sms080Reject"]').data('reject-number'));
if ($subject.length) {
$subject.val('(광고)' + $subject.val());
}
}
changeCartRemindContentsLength();
changeCartRemindSubjectLength();
});
 
});
* SMS 내용 길이 체크
*/
function changeCartRemindContentsLength() {
var $cartTa = $('textarea[name=cartRemindSendMessage]');
var strippedBody = stripEmoji($cartTa.val());
if (strippedBody !== $cartTa.val()) {
$cartTa.val(strippedBody);
}
var contentsText = strippedBody;
var textLength = stringToByte(contentsText);
 
if ($('input:hidden[name="cartRemindSendType"]').val() == 'sms' && textLength > <?= $smsStringLimit; ?>) {
}
$('#cartRemindSendMessageCount').val(textLength);
}
 
/**
* LMS 제목 Byte 체크 (공백 포함 최대 <?= $lmsSubjectLimit; ?>Byte, 초과분 자동 삭제)
*/
function changeCartRemindSubjectLength() {
var input = $('#cartRemindSendSubject');
if (input.length === 0) {
return;
}
var limit = <?= $lmsSubjectLimit; ?>;
var text = stripEmoji(input.val());
while (stringToByte(text) > limit) {
text = text.substring(0, text.length - 1);
}
input.val(text);
$('#cartRemindSubjectCount').val(stringToByte(text));
}
// 발송유형 따른 폼 변경
function changeCartRemindType() {
if ($('input:radio[name="cartRemindType"]:checked').val() == 'manual') {
$('.cart-remind-sms').hide();
$('.cart-remind-sms select').attr('disabled', 'disabled');
$('.cart-remind-sms input').attr('disabled', 'disabled');
 
$('#cartRemindSendSubject').prop('disabled', false); // LMS: 제목 영역 활성
// sms발송
} else if ($('input:hidden[name="cartRemindSendType"]').val() == 'sms') {
$('.cart-remind-lms').hide();
$('.cart-remind-sms').show();
$('.cart-remind-sms select').removeAttr('disabled');
$('.cart-remind-sms input').removeAttr('disabled');
 
$('#cartRemindSendSubject').prop('disabled', true); // SMS: 제목 영역 비활성(값 유지, 미저장/미발송)
}
}
// 발급방식에 따른 폼 변경
}
return length;
}
 
/**
* 이모티콘 제거 (제목/본문 이모티콘 입력 불가 - 자동 삭제)
*/
function stripEmoji(str) {
if (!str) {
return str;
}
return str
.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '')
.replace(/[\uFE00-\uFE0F\u200D\u20E3]/g, '');
}
</script>
 
Admin/promotion/comeback_coupon_regist.php+68-11
변경 전comeback_coupon_regist.php
}
if (e.target.checked) {
$send_contents.val('(광고)\n' + $send_contents.val() + '\n무료거부 ' + $(selector).data('reject-number'));
 
 
 
 
}
refresh_send_contents();
 
// 본문 반영으로 SMS/LMS 판정이 끝난 뒤에 제목 처리 — 제목 영역이 비활성(SMS)이면 (광고) 미표기
if (e.target.checked && $smsSubject.length && !$smsSubject.prop('disabled')) {
$smsSubject.val('(광고)' + $smsSubject.val());
setSubjectLength();
}
});
}
</script>
}
if (e.target.checked) {
$send_contents.val('(광고)\n' + $send_contents.val() + '\n무료거부 ' + $(selector).data('reject-number'));
if ($smsSubject.length) {
$smsSubject.val('(광고)' + $smsSubject.val());
setSubjectLength();
}
}
refresh_send_contents();
 
 
 
 
 
 
});
}
</script>
<button type="button" class="btn btn-white btn-sm pull-right js-toggle-replace-code" data-target=".replace_code_area" data-text="치환코드 닫기">치환코드 보기</button>
</div>
</div>
 
 
 
 
 
 
 
 
<div class="row pdt5">
<div class="col-xs-3 pdr0">
<label class="width100p">
링크는 단축 URL로 자동 변환하였습니다.<br/>
[단축 URL] 앱을 이용하여 단축 URL을 생성하면, 조회수 및 통계 자료를 확인하실 수 있습니다. <a href="https://apps.godo.co.kr/apps/1519" class="btn-link" target="_blank">바로가기</a>
</div>
 
</div>
</div>
</td>
return length;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* SMS 내용 길이 체크
*/
function setContentsLength(contentsNm, countId) {
var textarea = $('textarea[name=' + contentsNm + ']');
var contentsText = textarea.val();
 
 
 
 
var textLength = stringToByte(contentsText);
if (textLength > <?php echo $smsStringLimit;?>) {
if (textLength > <?php echo $lmsStringLimit;?>) {
$('.sms-type').hide();
$('.lms-type').show();
$('input[name=sendFl]').val('lms');
 
 
 
 
} else {
$('#' + countId).css("color", "");
$('.sms-type').show();
$('.lms-type').hide();
$('input[name=sendFl]').val('sms');
 
}
$('#' + countId).val(textLength);
}
 
// 글자수 체크
$('textarea[name=smsContents]').keyup(setSendLength).change(setSendLength);
 
 
$("#frmCoupon").validate({
submitHandler: function (form) {
changeTargetType();
checkSms();
setSendLength();
 
set_click_replace_code_insert('.replace-code-area .js-btn-insert');
set_change_focus_contents('textarea[name="smsContents"]');
set_click_sms080_contents(':checkbox[name="sms080Reject"]');
function set_click_sms080_contents (selector) {
$(selector).click(function (e) {
$send_contents.val($send_contents.val().replace(/(\(광고\)\n)|(\n무료거부\s[0-9\-]*)/g, ''));
 
 
 
 
if (e.target.checked) {
$send_contents.val('(광고)\n' + $send_contents.val() + '\n무료거부 ' + $(selector).data('reject-number'));
 
 
 
 
}
refresh_send_contents();
});
 
변경 후comeback_coupon_regist.php
}
if (e.target.checked) {
$send_contents.val('(광고)\n' + $send_contents.val() + '\n무료거부 ' + $(selector).data('reject-number'));
if ($smsSubject.length) {
$smsSubject.val('(광고)' + $smsSubject.val());
setSubjectLength();
}
}
refresh_send_contents();
 
 
 
 
 
 
});
}
</script>
}
if (e.target.checked) {
$send_contents.val('(광고)\n' + $send_contents.val() + '\n무료거부 ' + $(selector).data('reject-number'));
 
 
 
 
}
refresh_send_contents();
 
// 본문 반영으로 SMS/LMS 판정이 끝난 뒤에 제목 처리 — 제목 영역이 비활성(SMS)이면 (광고) 미표기
if (e.target.checked && $smsSubject.length && !$smsSubject.prop('disabled')) {
$smsSubject.val('(광고)' + $smsSubject.val());
setSubjectLength();
}
});
}
</script>
<button type="button" class="btn btn-white btn-sm pull-right js-toggle-replace-code" data-target=".replace_code_area" data-text="치환코드 닫기">치환코드 보기</button>
</div>
</div>
<div class="row pdt5 js-subject-row">
<div class="col-xs-3 pdr0">
<input type="text" name="smsSubject" id="smsSubject" class="form-control width100p" placeholder="제목을 입력해주세요." value="<?= gd_isset($couponData['smsSubject']) ?>" disabled="disabled"/>
<div class="mgt5">
<input type="text" id="smsSubjectCount" value="0" readonly="readonly" class="form-control width-3xs"> / <?= $lmsSubjectLimit; ?> Bytes
</div>
</div>
</div>
<div class="row pdt5">
<div class="col-xs-3 pdr0">
<label class="width100p">
링크는 단축 URL로 자동 변환하였습니다.<br/>
[단축 URL] 앱을 이용하여 단축 URL을 생성하면, 조회수 및 통계 자료를 확인하실 수 있습니다. <a href="https://apps.godo.co.kr/apps/1519" class="btn-link" target="_blank">바로가기</a>
</div>
<div class="notice-info">본문 내용이 LMS인 경우에만 제목 입력이 가능하며, 제목 영역에는 직접 (광고) 문구를 입력하지 않도록 하고 광고 삽입이 필요한 경우 광고성 문구 추가 기능을 사용해야 합니다.</div>
</div>
</div>
</td>
return length;
}
 
/**
* 이모티콘 제거 (제목/본문 이모티콘 입력 불가 - 자동 삭제)
*/
function stripEmoji(str) {
if (!str) {
return str;
}
return str
.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '')
.replace(/[\uFE00-\uFE0F\u200D\u20E3]/g, '');
}
 
/**
* LMS 제목 Byte 체크 (공백 포함 최대 <?= $lmsSubjectLimit; ?>Byte, 초과분 자동 삭제)
*/
function setSubjectLength() {
var input = $('#smsSubject');
if (input.length === 0) {
return;
}
var limit = <?= $lmsSubjectLimit; ?>;
var text = stripEmoji(input.val());
while (stringToByte(text) > limit) {
text = text.substring(0, text.length - 1);
}
input.val(text);
$('#smsSubjectCount').val(stringToByte(text));
}
 
/**
* SMS 내용 길이 체크
*/
function setContentsLength(contentsNm, countId) {
var textarea = $('textarea[name=' + contentsNm + ']');
var stripped = stripEmoji(textarea.val());
if (stripped !== textarea.val()) {
textarea.val(stripped);
}
var contentsText = stripped;
var textLength = stringToByte(contentsText);
if (textLength > <?php echo $smsStringLimit;?>) {
if (textLength > <?php echo $lmsStringLimit;?>) {
$('.sms-type').hide();
$('.lms-type').show();
$('input[name=sendFl]').val('lms');
<?php if ($checkMode == 'canModify') { ?>
$('#smsSubject').prop('disabled', false); // LMS: 제목 영역 활성
<?php } ?>
setSubjectLength();
} else {
$('#' + countId).css("color", "");
$('.sms-type').show();
$('.lms-type').hide();
$('input[name=sendFl]').val('sms');
$('#smsSubject').prop('disabled', true); // SMS: 제목 영역 비활성(값 유지)
}
$('#' + countId).val(textLength);
}
 
// 글자수 체크
$('textarea[name=smsContents]').keyup(setSendLength).change(setSendLength);
$('#smsSubject').on('keyup keydown', setSubjectLength);
 
$("#frmCoupon").validate({
submitHandler: function (form) {
changeTargetType();
checkSms();
setSendLength();
setSubjectLength();
set_click_replace_code_insert('.replace-code-area .js-btn-insert');
set_change_focus_contents('textarea[name="smsContents"]');
set_click_sms080_contents(':checkbox[name="sms080Reject"]');
function set_click_sms080_contents (selector) {
$(selector).click(function (e) {
$send_contents.val($send_contents.val().replace(/(\(광고\)\n)|(\n무료거부\s[0-9\-]*)/g, ''));
var $smsSubject = $('#smsSubject');
if ($smsSubject.length) {
$smsSubject.val($smsSubject.val().replace(/^\(광고\)/, ''));
}
if (e.target.checked) {
$send_contents.val('(광고)\n' + $send_contents.val() + '\n무료거부 ' + $(selector).data('reject-number'));
if ($smsSubject.length) {
$smsSubject.val('(광고)' + $smsSubject.val());
setSubjectLength();
}
}
refresh_send_contents();
});
 
Admin/promotion/layer_cart_remind_message.php+46-1
변경 전layer_cart_remind_message.php
<span class="cart-remind-sms notice-info">SMS : 발송 건당 1포인트 차감</span>
</div>
</div>
 
 
 
 
<div class="form-inline mgt10">
<textarea name="cartRemindSendMessage" rows="8" class="smsContents form-control width-xl"><?= $cartRemindData['cartRemindSendMessage']; ?></textarea>
<div class="mgt5">
<a href="https://www.nhn-commerce.com/customer/board-view.gd?type=notice&idx=1237" target="_blank" class="btn-link">자세히보기></a>
</p>
<p class="notice-info">장바구니 링크 치환코드는 LMS인 경우에만 정상적으로 발송됩니다</p>
 
</div>
</td>
</tr>
 
// 글자수 체크
$('textarea[name=cartRemindSendMessage]').keyup(changeCartRemindContentsLength);
 
changeCartRemindSendType();
changeCartRemindContentsLength();
 
});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* SMS 내용 길이 체크
*/
function changeCartRemindContentsLength() {
var contentsText = $('textarea[name=cartRemindSendMessage]').val();
 
 
 
 
 
var textLength = stringToByte(contentsText);
 
if ($('input:hidden[name="cartRemindSendType"]').val() == 'sms' && textLength > <?= $smsStringLimit; ?>) {
$('.cart-remind-sms').hide();
$('.cart-remind-sms select').attr('disabled', 'disabled');
$('.cart-remind-sms input').attr('disabled', 'disabled');
 
 
// sms발송
} else if ($('input:hidden[name="cartRemindSendType"]').val() == 'sms') {
$('.cart-remind-lms').hide();
$('.cart-remind-sms').show();
$('.cart-remind-sms select').removeAttr('disabled');
$('.cart-remind-sms input').removeAttr('disabled');
 
 
}
}
/**
}
return length;
}
 
 
 
 
 
 
 
 
 
 
 
 
</script>
 
 
변경 후layer_cart_remind_message.php
<span class="cart-remind-sms notice-info">SMS : 발송 건당 1포인트 차감</span>
</div>
</div>
<div class="form-inline mgt10 js-cart-subject-row">
<input type="text" name="cartRemindSendSubject" id="cartRemindSendSubject" class="form-control width-xl" placeholder="제목을 입력해주세요." value="<?= gd_isset($cartRemindData['cartRemindSendSubject']); ?>" disabled="disabled"/>
<input type="text" id="cartRemindSubjectCount" value="0" readonly="readonly" class="form-control width-3xs"> / <?= $lmsSubjectLimit; ?> Bytes
</div>
<div class="form-inline mgt10">
<textarea name="cartRemindSendMessage" rows="8" class="smsContents form-control width-xl"><?= $cartRemindData['cartRemindSendMessage']; ?></textarea>
<div class="mgt5">
<a href="https://www.nhn-commerce.com/customer/board-view.gd?type=notice&idx=1237" target="_blank" class="btn-link">자세히보기></a>
</p>
<p class="notice-info">장바구니 링크 치환코드는 LMS인 경우에만 정상적으로 발송됩니다</p>
<p class="notice-info">본문 내용이 LMS인 경우에만 제목 입력이 가능하며, 제목 영역에는 직접 (광고) 문구를 입력하지 않도록 하고 광고 삽입이 필요한 경우 광고성 문구 추가 기능을 사용해야 합니다.</p>
</div>
</td>
</tr>
 
// 글자수 체크
$('textarea[name=cartRemindSendMessage]').keyup(changeCartRemindContentsLength);
$('#cartRemindSendSubject').on('keyup keydown', changeCartRemindSubjectLength);
changeCartRemindSendType();
changeCartRemindContentsLength();
changeCartRemindSubjectLength();
});
 
/**
* LMS 제목 Byte 체크 (공백 포함 최대 <?= $lmsSubjectLimit; ?>Byte, 초과분 자동 삭제)
*/
function changeCartRemindSubjectLength() {
var input = $('#cartRemindSendSubject');
if (input.length === 0) {
return;
}
var limit = <?= $lmsSubjectLimit; ?>;
var text = stripEmoji(input.val());
while (stringToByte(text) > limit) {
text = text.substring(0, text.length - 1);
}
input.val(text);
$('#cartRemindSubjectCount').val(stringToByte(text));
}
/**
* SMS 내용 길이 체크
*/
function changeCartRemindContentsLength() {
var $cartTa = $('textarea[name=cartRemindSendMessage]');
var strippedBody = stripEmoji($cartTa.val());
if (strippedBody !== $cartTa.val()) {
$cartTa.val(strippedBody);
}
var contentsText = strippedBody;
var textLength = stringToByte(contentsText);
 
if ($('input:hidden[name="cartRemindSendType"]').val() == 'sms' && textLength > <?= $smsStringLimit; ?>) {
$('.cart-remind-sms').hide();
$('.cart-remind-sms select').attr('disabled', 'disabled');
$('.cart-remind-sms input').attr('disabled', 'disabled');
 
$('#cartRemindSendSubject').prop('disabled', false); // LMS: 제목 영역 활성
// sms발송
} else if ($('input:hidden[name="cartRemindSendType"]').val() == 'sms') {
$('.cart-remind-lms').hide();
$('.cart-remind-sms').show();
$('.cart-remind-sms select').removeAttr('disabled');
$('.cart-remind-sms input').removeAttr('disabled');
 
$('#cartRemindSendSubject').prop('disabled', true); // SMS: 제목 영역 비활성(값 유지, 미저장/미발송)
}
}
/**
}
return length;
}
 
/**
* 이모티콘 제거 (제목/본문 이모티콘 입력 불가 - 자동 삭제)
*/
function stripEmoji(str) {
if (!str) {
return str;
}
return str
.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '')
.replace(/[\uFE00-\uFE0F\u200D\u20E3]/g, '');
}
</script>
 
 
Admin/share/layer_comeback_coupon_sms.php+6
변경 전layer_comeback_coupon_sms.php
<col style="width:80%" />
</colgroup>
<tbody>
 
 
 
 
 
 
<tr>
<th>발송 내용</th>
<td>
 
변경 후layer_comeback_coupon_sms.php
<col style="width:80%" />
</colgroup>
<tbody>
<tr>
<th>제목</th>
<td>
<input type="text" class="form-control width100p" readonly="readonly" value="<?= gd_isset($data['smsSubject']) !== '' ? gd_isset($data['smsSubject']) : '[제목없음]' ?>"/>
</td>
</tr>
<tr>
<th>발송 내용</th>
<td>
 
Admin/share/layer_standby_sms.php+48-6
변경 전layer_standby_sms.php
*/
?>
<form action="" method="post">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<div class="row">
<div class="col-xs-12">
<div class="pull-left">
<span class="sms-type text-darkblue">SMS : 발송 건당 1포인트 차감</span>
<span class="lms-type text-orange-red display-none">LMS : 발송 건당 <?php echo $lmsPoint; ?>포인트 차감</span>
</div>
<div class="pull-right">
<input type="text" id="smsStringCount" value="0" readonly="readonly" class="form-control width-3xs"> /
<span class="sms-type"><?php echo $smsStringLimit; ?></span><span class="lms-type display-none"><?php echo number_format($lmsStringLimit); ?></span> Bytes
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<textarea name="contents" id="contents" rows="8" class="form-control"><?= $data['contents']; ?></textarea>
</div>
</div>
<div class="row">
<div class="center modal-footer">
<button type="submit" id="modify" class="btn btn-red">수정</button>
$(document).ready(function () {
var $textarea = $('textarea[name=contents]');
$textarea.on('keyup change', setSendLength).trigger('change');
 
 
});
function setSendLength() {
setContentsLength('contents', 'smsStringCount');
$('.sms-type').hide();
$('.lms-type').show();
$('input[name=sendFl]').val('lms');
 
 
} else {
$('#' + countId).css("color", "");
$('.sms-type').show();
$('.lms-type').hide();
$('input[name=sendFl]').val('sms');
 
}
$('#' + countId).val(textLength);
}
}
return length;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
</script>
 
변경 후layer_standby_sms.php
*/
?>
<form action="" method="post">
<div class="row">
<div class="col-xs-12">
<div class="pull-right form-inline">
<input type="text" id="subjectStringCount" value="0" readonly="readonly" class="form-control width-3xs"> / <?= $lmsSubjectLimit; ?> Bytes
</div>
<div style="overflow:hidden; padding-right:10px;">
<input type="text" name="subject" id="subject" class="form-control" placeholder="제목을 입력해주세요." value="<?= gd_isset($data['subject']); ?>" disabled="disabled"/>
</div>
</div>
</div>
<div class="row mgt10">
<div class="col-xs-12">
<textarea name="contents" id="contents" rows="8" class="form-control"><?= $data['contents']; ?></textarea>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="pull-left">
<span class="sms-type text-darkblue">SMS : 발송 건당 1포인트 차감</span>
<span class="lms-type text-orange-red display-none">LMS : 발송 건당 <?php echo $lmsPoint; ?>포인트 차감</span>
</div>
 
<div class="pull-right">
<input type="text" id="smsStringCount" value="0" readonly="readonly" class="form-control width-3xs"> /
<span class="sms-type"><?php echo $smsStringLimit; ?></span><span class="lms-type display-none"><?php echo number_format($lmsStringLimit); ?></span> Bytes
</div>
</div>
</div>
 
 
 
 
 
<div class="row">
<div class="center modal-footer">
<button type="submit" id="modify" class="btn btn-red">수정</button>
$(document).ready(function () {
var $textarea = $('textarea[name=contents]');
$textarea.on('keyup change', setSendLength).trigger('change');
$('#subject').on('keyup keydown', setSubjectLength);
setSubjectLength();
});
function setSendLength() {
setContentsLength('contents', 'smsStringCount');
$('.sms-type').hide();
$('.lms-type').show();
$('input[name=sendFl]').val('lms');
$('#subject').prop('disabled', false); // LMS: 제목 영역 활성
setSubjectLength();
} else {
$('#' + countId).css("color", "");
$('.sms-type').show();
$('.lms-type').hide();
$('input[name=sendFl]').val('sms');
$('#subject').prop('disabled', true); // SMS: 제목 영역 비활성(값 유지, 미발송)
}
$('#' + countId).val(textLength);
}
}
return length;
}
/**
* 이모티콘 제거 (제목/본문 이모티콘 입력 불가 - 자동 삭제)
*/
function stripEmoji(str) {
if (!str) {
return str;
}
return str
.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '')
.replace(/[\uFE00-\uFE0F\u200D\u20E3]/g, '');
}
/**
* LMS 제목 Byte 체크 (공백 포함 최대 <?= $lmsSubjectLimit; ?>Byte, 초과분 자동 삭제)
*/
function setSubjectLength() {
var input = $('#subject');
if (input.length === 0) {
return;
}
var limit = <?= $lmsSubjectLimit; ?>;
var text = stripEmoji(input.val());
while (stringToByte(text) > limit) {
text = text.substring(0, text.length - 1);
}
input.val(text);
$('#subjectStringCount').val(stringToByte(text));
}
</script>
 
Admin/share/member_crm_sms.php+8
변경 전member_crm_sms.php
$listHtml[] = '<td class="text-center">' . $sSendFl . '</td>';
$listHtml[] = '<td style="word-break:break-all;word-wrap:break-word;overflow:hidden;" class="multirows">';
if ($data['sendFl'] != 'kakao') {
 
 
 
 
 
 
 
 
$listHtml[] = '<div>' . $data['contents'] . '</div>';
} else {
$listHtml[] = '<div>' . nl2br($data['templateContent']) . '</div>';
 
변경 후member_crm_sms.php
$listHtml[] = '<td class="text-center">' . $sSendFl . '</td>';
$listHtml[] = '<td style="word-break:break-all;word-wrap:break-word;overflow:hidden;" class="multirows">';
if ($data['sendFl'] != 'kakao') {
// LMS 제목 — 있으면 그대로, 없으면 '[제목없음]' 표시(표시 시점 변환). 제목은 LMS 한정.
if ($data['sendFl'] == 'lms') {
$smsSubject = trim($data['subject'] ?? '');
if ($smsSubject === '') {
$smsSubject = '[제목없음]';
}
$listHtml[] = '<div><strong>' . gd_htmlspecialchars($smsSubject) . '</strong></div>';
}
$listHtml[] = '<div>' . $data['contents'] . '</div>';
} else {
$listHtml[] = '<div>' . nl2br($data['templateContent']) . '</div>';
 
Component/CartRemind/CartRemind.php+15
변경 전CartRemind.php
$getCartRemindSend['autoSendTime'] = $cartRemindData['cartRemindAutoSendTime'];
$getCartRemindSend['smsType'] = $cartRemindData['cartRemindSendType'];
$getCartRemindSend['message'] = $cartRemindData['cartRemindSendMessage'];
 
$getCartRemindSend['cartUrl'] = $cartRemindData['cartRemindSendUrl'];
if ($cartRemindData['cartRemindCoupon'] > 0) {
$getCartRemindSend['coupon'] = $cartRemindData['cartRemindCoupon'];
$validator->add('cartRemindCoupon', null, null); // 발송쿠폰
$validator->add('cartRemindSendType', null, true); // 발송타입 (sms,lms)
$validator->add('cartRemindSendMessage', null, true); // 발송메세지
 
 
if ($validator->act($arrData, true) === false) {
throw new \Exception(gd_implode("<br/>", $validator->errors));
// 선택에 따른 값의 초기화를 위한 빈값 제거 - 주석처리 함 - 상태에 따른 빈값 저장(초기화)이 필요한 경우가 존재함.
// $arrData = ArrayUtils::removeEmpty($arrData);
 
 
 
 
 
 
switch (substr($arrData['mode'], 0, 6)) {
case 'insert' : {
// 저장
$validator->add('cartRemindNo', 'number', true); // 장바구니 알림 고유번호
$validator->add('cartRemindSendType', null, true); // 발송타입 (sms,lms)
$validator->add('cartRemindSendMessage', null, true); // 발송메세지
 
 
if ($validator->act($arrData, true) === false) {
throw new \Exception(gd_implode("<br/>", $validator->errors));
// 선택에 따른 값의 초기화를 위한 빈값 제거 - 주석처리 함 - 상태에 따른 빈값 저장(초기화)이 필요한 경우가 존재함.
// $arrData = ArrayUtils::removeEmpty($arrData);
 
 
 
 
 
 
// 수정
$arrBind = $this->db->get_binding(DBTableField::tableCartRemind(), $arrData, 'update', gd_array_keys($arrData), ['cartRemindNo']);
$this->db->bind_param_push($arrBind['bind'], 'i', $arrData['cartRemindNo']);
}
// sms, lms
$smsSendData['sendFl'] = $cartRemindSendData['smsType'];
 
 
 
// ??
$smsSendData['agreeCnt'] = '';
 
변경 후CartRemind.php
$getCartRemindSend['autoSendTime'] = $cartRemindData['cartRemindAutoSendTime'];
$getCartRemindSend['smsType'] = $cartRemindData['cartRemindSendType'];
$getCartRemindSend['message'] = $cartRemindData['cartRemindSendMessage'];
$getCartRemindSend['subject'] = $cartRemindData['cartRemindSendSubject'] ?? '';
$getCartRemindSend['cartUrl'] = $cartRemindData['cartRemindSendUrl'];
if ($cartRemindData['cartRemindCoupon'] > 0) {
$getCartRemindSend['coupon'] = $cartRemindData['cartRemindCoupon'];
$validator->add('cartRemindCoupon', null, null); // 발송쿠폰
$validator->add('cartRemindSendType', null, true); // 발송타입 (sms,lms)
$validator->add('cartRemindSendMessage', null, true); // 발송메세지
$validator->add('cartRemindSendSubject', null, null); // LMS 제목 (선택, lms 시)
 
if ($validator->act($arrData, true) === false) {
throw new \Exception(gd_implode("<br/>", $validator->errors));
// 선택에 따른 값의 초기화를 위한 빈값 제거 - 주석처리 함 - 상태에 따른 빈값 저장(초기화)이 필요한 경우가 존재함.
// $arrData = ArrayUtils::removeEmpty($arrData);
 
// LMS 제목은 LMS 상태에서만 저장(C5). 본문이 SMS면 제목 영역은 저장하지 않음(빈값).
if (gd_isset($arrData['cartRemindSendType']) === 'sms') {
$arrData['cartRemindSendSubject'] = '';
}
 
switch (substr($arrData['mode'], 0, 6)) {
case 'insert' : {
// 저장
$validator->add('cartRemindNo', 'number', true); // 장바구니 알림 고유번호
$validator->add('cartRemindSendType', null, true); // 발송타입 (sms,lms)
$validator->add('cartRemindSendMessage', null, true); // 발송메세지
$validator->add('cartRemindSendSubject', null, null); // LMS 제목 (선택, lms 시)
 
if ($validator->act($arrData, true) === false) {
throw new \Exception(gd_implode("<br/>", $validator->errors));
// 선택에 따른 값의 초기화를 위한 빈값 제거 - 주석처리 함 - 상태에 따른 빈값 저장(초기화)이 필요한 경우가 존재함.
// $arrData = ArrayUtils::removeEmpty($arrData);
 
// LMS 제목은 LMS 상태에서만 저장(C5). 본문이 SMS면 제목 영역은 저장하지 않음(빈값).
if (gd_isset($arrData['cartRemindSendType']) === 'sms') {
$arrData['cartRemindSendSubject'] = '';
}
 
// 수정
$arrBind = $this->db->get_binding(DBTableField::tableCartRemind(), $arrData, 'update', gd_array_keys($arrData), ['cartRemindNo']);
$this->db->bind_param_push($arrBind['bind'], 'i', $arrData['cartRemindNo']);
}
// sms, lms
$smsSendData['sendFl'] = $cartRemindSendData['smsType'];
// LMS 제목 (LMS 발송 시에만 SmsAdmin 에서 사용)
$smsSendData['smsSubject'] = $cartRemindSendData['subject'] ?? '';
 
// ??
$smsSendData['agreeCnt'] = '';
 
Component/Coupon/CouponAdmin.php+3
변경 전CouponAdmin.php
지금 바로 확인하세요!";
}
$validator->add('smsContents', 'required', true); // SMS 발송 내용
 
 
// 전송일 null처리
$arrData['sendDt'] = null;
$smsSendData['smsPoint'] = Sms::getPoint();
// 전송 내용
$smsSendData['smsContents'] = str_replace('rc_memNm', 'memNm', $getData['smsContents']);
 
 
// 치환 코드 그룹
$smsSendData['replaceCodeGroup'] = 'promotion';
// 전송요청 수
 
변경 후CouponAdmin.php
지금 바로 확인하세요!";
}
$validator->add('smsContents', 'required', true); // SMS 발송 내용
$validator->add('smsSubject', null, null); // LMS 제목 (검증 없음, garbageDelete 방지용 등록)
 
// 전송일 null처리
$arrData['sendDt'] = null;
$smsSendData['smsPoint'] = Sms::getPoint();
// 전송 내용
$smsSendData['smsContents'] = str_replace('rc_memNm', 'memNm', $getData['smsContents']);
// LMS 제목 (LMS 발송 시에만 SmsAdmin 에서 사용)
$smsSendData['smsSubject'] = isset($getData['smsSubject']) ? $getData['smsSubject'] : '';
// 치환 코드 그룹
$smsSendData['replaceCodeGroup'] = 'promotion';
// 전송요청 수
 
Component/Database/DBTableField.php+3
변경 전DBTableField.php
['val' => 'cartRemindAutoSendTime', 'typ' => 'i', 'def' => '10'], // 장바구니 알림 자동발송 시 발송 시간
['val' => 'cartRemindSendType', 'typ' => 's', 'def' => 'lms'], // 장바구니 알림 전송 형식 - sms, lms
['val' => 'cartRemindSendMessage', 'typ' => 's', 'def' => '(광고)[{rc_mallNm}] {rc_memNm}님의 장바구니에 상품이 담겨 있습니다. 아래 장바구니 바로가기를 통해 바로 확인하실 수 있습니다. ▶ 장바구니 바로가기: {rc_cartRemindLink}'], // 장바구니 알림 전송 내용
 
['val' => 'cartRemindSendUrl', 'typ' => 's', 'def' => ''], // 장바구니 알림 바로가기 링크
['val' => 'cartRemindInsertAdminId', 'typ' => 's', 'def' => null], // 장바구니 알림 등록자 아이디
['val' => 'managerNo', 'typ' => 'i', 'def' => 0], // 장바구니 알림 등록자 키
['val' => 'smsAutoType', 'typ' => 's', 'def' => null], // SMS 자동발송 수신 종류 (member, admin, provider)
['val' => 'subject', 'typ' => 's', 'def' => null], // SMS 템플릿 제목
['val' => 'contents', 'typ' => 's', 'def' => null], // SMS 템플릿 내용
 
['val' => 'description', 'typ' => 's', 'def' => null], // 설명
['val' => 'isUserBasic', 'typ' => 's', 'def' => 'n'], // 전송타입(user)기본제공여부
['val' => 'modDt', 'typ' => 's', 'def' => null], // 수정일
['val' => 'smsFl', 'typ' => 's', 'def' => 'n'], // sms동시 발송여부 - y:발송 / n:미발송
['val' => 'smsSpamFl', 'typ' => 's', 'def' => 'n'], // 광고성 문구 추가 - y:추가 / n:미추가
['val' => 'smsContents', 'typ' => 's', 'def' => ''], // SMS 발송 내용
 
['val' => 'managerNo', 'typ' => 'i', 'def' => ''], // 등록 관리자 고유번호
['val' => 'deleteFl', 'typ' => 's', 'def' => 'n'], // 삭제 flag - y:삭제 / n:미삭제
['val' => 'sendDt', 'typ' => 's', 'def' => null], // 전송일
 
변경 후DBTableField.php
['val' => 'cartRemindAutoSendTime', 'typ' => 'i', 'def' => '10'], // 장바구니 알림 자동발송 시 발송 시간
['val' => 'cartRemindSendType', 'typ' => 's', 'def' => 'lms'], // 장바구니 알림 전송 형식 - sms, lms
['val' => 'cartRemindSendMessage', 'typ' => 's', 'def' => '(광고)[{rc_mallNm}] {rc_memNm}님의 장바구니에 상품이 담겨 있습니다. 아래 장바구니 바로가기를 통해 바로 확인하실 수 있습니다. ▶ 장바구니 바로가기: {rc_cartRemindLink}'], // 장바구니 알림 전송 내용
['val' => 'cartRemindSendSubject', 'typ' => 's', 'def' => ''], // 장바구니 알림 LMS 제목
['val' => 'cartRemindSendUrl', 'typ' => 's', 'def' => ''], // 장바구니 알림 바로가기 링크
['val' => 'cartRemindInsertAdminId', 'typ' => 's', 'def' => null], // 장바구니 알림 등록자 아이디
['val' => 'managerNo', 'typ' => 'i', 'def' => 0], // 장바구니 알림 등록자 키
['val' => 'smsAutoType', 'typ' => 's', 'def' => null], // SMS 자동발송 수신 종류 (member, admin, provider)
['val' => 'subject', 'typ' => 's', 'def' => null], // SMS 템플릿 제목
['val' => 'contents', 'typ' => 's', 'def' => null], // SMS 템플릿 내용
['val' => 'messageTitle', 'typ' => 's', 'def' => null], // LMS 메시지 제목
['val' => 'description', 'typ' => 's', 'def' => null], // 설명
['val' => 'isUserBasic', 'typ' => 's', 'def' => 'n'], // 전송타입(user)기본제공여부
['val' => 'modDt', 'typ' => 's', 'def' => null], // 수정일
['val' => 'smsFl', 'typ' => 's', 'def' => 'n'], // sms동시 발송여부 - y:발송 / n:미발송
['val' => 'smsSpamFl', 'typ' => 's', 'def' => 'n'], // 광고성 문구 추가 - y:추가 / n:미추가
['val' => 'smsContents', 'typ' => 's', 'def' => ''], // SMS 발송 내용
['val' => 'smsSubject', 'typ' => 's', 'def' => ''], // LMS 제목
['val' => 'managerNo', 'typ' => 'i', 'def' => ''], // 등록 관리자 고유번호
['val' => 'deleteFl', 'typ' => 's', 'def' => 'n'], // 삭제 flag - y:삭제 / n:미삭제
['val' => 'sendDt', 'typ' => 's', 'def' => null], // 전송일
 
Component/Sms/SmsAdmin.php+13
변경 전SmsAdmin.php
$dbData[$autoData[$autoType . 'Mode']][$smsType][$autoCode][$autoType]['sno'] = $autoData[$autoType . 'Sno'];
$dbData[$autoData[$autoType . 'Mode']][$smsType][$autoCode][$autoType]['subject'] = $autoData['subject'];
$dbData[$autoData[$autoType . 'Mode']][$smsType][$autoCode][$autoType]['contents'] = $autoData[$autoType . 'Contents'];
 
// $dbData[$autoData[$autoType . 'Mode']][$smsType][$autoCode][$autoType]['smsType'] = $smsType;
unset($request[$smsType][$autoCode][$autoType . 'Mode']);
unset($request[$smsType][$autoCode][$autoType . 'Sno']);
unset($request[$smsType][$autoCode]['subject']);
unset($request[$smsType][$autoCode][$autoType . 'Contents']);
 
}
}
}
$getData = gd_htmlspecialchars_stripslashes($this->db->query_fetch($strSQL, null, false));
foreach ($getData as $data) {
$setData[$data['smsType']][$data['smsAutoCode']][$data['smsAutoType']]['contents'] = $data['contents'];
 
$setData[$data['smsType']][$data['smsAutoCode']][$data['smsAutoType']]['sno'] = $data['sno'];
}
unset($getData);
foreach ($arrType as $sendType) {
if (isset($setData[$keyType][$val['code']][$sendType]['contents']) === true) {
$smsAutoType[$keyType][$key][$sendType . 'Contents'] = $setData[$keyType][$val['code']][$sendType]['contents'];
 
 
 
$smsAutoType[$keyType][$key][$sendType . 'Mode'] = 'update';
$smsAutoType[$keyType][$key][$sendType . 'Sno'] = $setData[$keyType][$val['code']][$sendType]['sno'];
} else {
$smsAutoType[$keyType][$key][$sendType . 'Contents'] = $val[$sendType . 'Contents'];
 
 
$smsAutoType[$keyType][$key][$sendType . 'Mode'] = 'insert';
$smsAutoType[$keyType][$key][$sendType . 'Sno'] = '';
}
}
if ($receiverData['sendFl'] === 'lms') {
$message = new \Component\Sms\LmsMessage($receiverData['smsContents'], $receiverData['replaceCodeGroup']);
 
 
 
 
 
} else {
$message = new \Component\Sms\SmsMessage($receiverData['smsContents'], $receiverData['replaceCodeGroup']);
}
 
변경 후SmsAdmin.php
$dbData[$autoData[$autoType . 'Mode']][$smsType][$autoCode][$autoType]['sno'] = $autoData[$autoType . 'Sno'];
$dbData[$autoData[$autoType . 'Mode']][$smsType][$autoCode][$autoType]['subject'] = $autoData['subject'];
$dbData[$autoData[$autoType . 'Mode']][$smsType][$autoCode][$autoType]['contents'] = $autoData[$autoType . 'Contents'];
$dbData[$autoData[$autoType . 'Mode']][$smsType][$autoCode][$autoType]['messageTitle'] = gd_isset($autoData[$autoType . 'MessageTitle'], '');
// $dbData[$autoData[$autoType . 'Mode']][$smsType][$autoCode][$autoType]['smsType'] = $smsType;
unset($request[$smsType][$autoCode][$autoType . 'Mode']);
unset($request[$smsType][$autoCode][$autoType . 'Sno']);
unset($request[$smsType][$autoCode]['subject']);
unset($request[$smsType][$autoCode][$autoType . 'Contents']);
unset($request[$smsType][$autoCode][$autoType . 'MessageTitle']);
}
}
}
$getData = gd_htmlspecialchars_stripslashes($this->db->query_fetch($strSQL, null, false));
foreach ($getData as $data) {
$setData[$data['smsType']][$data['smsAutoCode']][$data['smsAutoType']]['contents'] = $data['contents'];
$setData[$data['smsType']][$data['smsAutoCode']][$data['smsAutoType']]['messageTitle'] = $data['messageTitle'];
$setData[$data['smsType']][$data['smsAutoCode']][$data['smsAutoType']]['sno'] = $data['sno'];
}
unset($getData);
foreach ($arrType as $sendType) {
if (isset($setData[$keyType][$val['code']][$sendType]['contents']) === true) {
$smsAutoType[$keyType][$key][$sendType . 'Contents'] = $setData[$keyType][$val['code']][$sendType]['contents'];
$messageTitle = gd_isset($setData[$keyType][$val['code']][$sendType]['messageTitle'], '');
$smsAutoType[$keyType][$key][$sendType . 'MessageTitle'] = $messageTitle;
$smsAutoType[$keyType][$key][$sendType . 'Title'] = $messageTitle; // CRM 뷰 호환(messageTitle 일원화)
$smsAutoType[$keyType][$key][$sendType . 'Mode'] = 'update';
$smsAutoType[$keyType][$key][$sendType . 'Sno'] = $setData[$keyType][$val['code']][$sendType]['sno'];
} else {
$smsAutoType[$keyType][$key][$sendType . 'Contents'] = $val[$sendType . 'Contents'];
$smsAutoType[$keyType][$key][$sendType . 'MessageTitle'] = '';
$smsAutoType[$keyType][$key][$sendType . 'Title'] = ''; // CRM 뷰 호환
$smsAutoType[$keyType][$key][$sendType . 'Mode'] = 'insert';
$smsAutoType[$keyType][$key][$sendType . 'Sno'] = '';
}
}
if ($receiverData['sendFl'] === 'lms') {
$message = new \Component\Sms\LmsMessage($receiverData['smsContents'], $receiverData['replaceCodeGroup']);
// LMS 제목 (미입력 시 빈 값)
// 튜닝 상점 대비: 구버전 SmsSender(setSubject 미보유)에서 발송 중단 방지
if (method_exists($transport, 'setSubject')) {
$transport->setSubject($receiverData['smsSubject'] ?? '');
}
} else {
$message = new \Component\Sms\SmsMessage($receiverData['smsContents'], $receiverData['replaceCodeGroup']);
}
 
Component/Sms/SmsAuto.php+6-1
변경 전SmsAuto.php
{
$logger = $this->logger;
$binds = [];
$this->db->strField = 'smsType, smsAutoType, contents, subject';
$this->db->strJoin = DB_SMS_CONTENTS;
$this->db->strWhere = 'smsType= ? AND smsAutoCode = ? AND smsAutoType IN (\'' . gd_implode('\', \'', $wheres) . '\') AND contents != \'\' AND contents IS NOT NULL';
$this->db->bind_param_push($binds, 's', $this->smsType);
}
$transport->setSmsPoint(Sms::getPoint());
$transport->setMessage($message);
 
 
 
 
 
$transport->setSmsType($item['smsType']);
$transport->setReceiver($receiverData);
$transport->setSendDate($this->smsAutoSendDate);
 
변경 후SmsAuto.php
{
$logger = $this->logger;
$binds = [];
$this->db->strField = 'smsType, smsAutoType, contents, subject, messageTitle';
$this->db->strJoin = DB_SMS_CONTENTS;
$this->db->strWhere = 'smsType= ? AND smsAutoCode = ? AND smsAutoType IN (\'' . gd_implode('\', \'', $wheres) . '\') AND contents != \'\' AND contents IS NOT NULL';
$this->db->bind_param_push($binds, 's', $this->smsType);
}
$transport->setSmsPoint(Sms::getPoint());
$transport->setMessage($message);
// LMS 제목 (설정된 자동발송 문구 제목 - LMS 발송 시에만 SmsSender 에서 사용, 미입력 시 [제목없음])
// 튜닝 상점 대비: 구버전 SmsSender(setSubject 미보유)에서 발송 중단 방지
if (method_exists($transport, 'setSubject')) {
$transport->setSubject($item['messageTitle'] ?? '');
}
$transport->setSmsType($item['smsType']);
$transport->setReceiver($receiverData);
$transport->setSendDate($this->smsAutoSendDate);
 
Component/Sms/SmsSender.php+32-1
변경 전SmsSender.php
protected $isThrowPasswordException = false;
/** @var string|null SMS 발송 트리거 타입 (예: FALLBACK) */
protected $sendTriggerType;
 
 
/** @var string SMS 대량발송 파일 경로 */
private $_smsLargeFilePath;
/** @var object SMS 대량발송 TXT 파일 생성 로거 */
];
//@formatter:on
if ($this->_isLms()) {
$this->sendSms['subject'] = '';
}
if ($this->sendTriggerType !== null) {
$this->sendSms['sendTriggerType'] = $this->sendTriggerType;
];
if ($this->_isLms()) {
$this->smsLog['sendFl'] = 'lms';
 
}
}
$this->sendTriggerType = $sendTriggerType;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* @param bool $isThrowPasswordException
*/
 
변경 후SmsSender.php
protected $isThrowPasswordException = false;
/** @var string|null SMS 발송 트리거 타입 (예: FALLBACK) */
protected $sendTriggerType;
/** @var string|null LMS 제목 */
protected $subject;
/** @var string SMS 대량발송 파일 경로 */
private $_smsLargeFilePath;
/** @var object SMS 대량발송 TXT 파일 생성 로거 */
];
//@formatter:on
if ($this->_isLms()) {
$this->sendSms['subject'] = $this->resolveSubject();
}
if ($this->sendTriggerType !== null) {
$this->sendSms['sendTriggerType'] = $this->sendTriggerType;
];
if ($this->_isLms()) {
$this->smsLog['sendFl'] = 'lms';
$this->smsLog['subject'] = trim(preg_replace('/#?\{[^}]*\}/u', '', (string) $this->subject)); // 저장은 치환자 제거한 원문(빈값 유지), [제목없음] 변환은 발송/표시 시점만
}
}
$this->sendTriggerType = $sendTriggerType;
}
/**
* LMS 제목 설정 (LMS 발송 시에만 사용)
*
* @param string|null $subject
*/
public function setSubject(?string $subject): void
{
$this->subject = $subject;
}
/**
* 실발송용 LMS 제목 계산
* - 제목 치환자({..}/#{..})는 미지원이므로 공란 처리
* - LMS 인데 제목이 없으면(또는 치환자만 있어 공란) '[제목없음]' 으로 발송
*
* @return string
*/
protected function resolveSubject(): string
{
// 치환자 제거 (제목 영역은 치환자 미지원 → 실발송 시 공란 처리)
$subject = trim(preg_replace('/#?\{[^}]*\}/u', '', (string) $this->subject));
if ($subject === '') {
return '[제목없음]';
}
return $subject;
}
/**
* @param bool $isThrowPasswordException
*/
 
Controller/Admin/Crm/LayerPreviewMobileSendController.php+14
변경 전LayerPreviewMobileSendController.php
switch ($sendMethod) {
case 'SMS':
$smsData = $payload['smsLmsRequest'] ?? [];
 
$this->setData('mainContents', $smsData['content'] ?? '');
$this->setData('showAdWarningText', 'n');
$previewContents = 'crm/preview_mobile_send/layer_preview_mobile_send_sms.php';
 
$this->setData('friendtalk', $friendTalkData);
$this->setData('alternativeContents', $friendTalkData['alternativeInfo']['content'] ?? '');
 
 
 
 
 
 
$this->setData('showAdWarningText', $showAdWarningText);
$previewContents = 'crm/preview_mobile_send/layer_preview_mobile_send_kakao_friendtalk.php';
break;
$this->setData('template', $previewMeta);
$this->setData('hasAddChannelButton', $hasAddChannelButton);
$this->setData('alternativeContents', $alimTalkData['alternativeInfo']['content'] ?? '');
 
$this->setData('showAdWarningText', 'n');
$previewContents = 'crm/preview_mobile_send/layer_preview_mobile_send_kakao_alimtalk.php';
break;
$this->setData('myappPayload', $myappPushData);
$this->setData('showAdWarningText', $showAdWarningText);
$this->setData('alternativeContents', $payload['myappRequest']['alternativeInfo']['content'] ?? '');
 
 
 
 
 
 
$previewContents = 'crm/preview_mobile_send/layer_preview_mobile_send_myapp_push.php';
break;
}
 
변경 후LayerPreviewMobileSendController.php
switch ($sendMethod) {
case 'SMS':
$smsData = $payload['smsLmsRequest'] ?? [];
$this->setData('mainTitle', $smsData['title'] ?? '');
$this->setData('mainContents', $smsData['content'] ?? '');
$this->setData('showAdWarningText', 'n');
$previewContents = 'crm/preview_mobile_send/layer_preview_mobile_send_sms.php';
 
$this->setData('friendtalk', $friendTalkData);
$this->setData('alternativeContents', $friendTalkData['alternativeInfo']['content'] ?? '');
// 대체 제목 미입력 시 메인 메시지 타이틀(친구톡 헤더)이 있으면 카피하여 표기 (미리보기 전용)
$alternativeTitle = $friendTalkData['alternativeInfo']['title'] ?? '';
if ($alternativeTitle === '' || $alternativeTitle === null) {
$alternativeTitle = $friendTalkData['header'] ?? '';
}
$this->setData('alternativeTitle', $alternativeTitle);
$this->setData('showAdWarningText', $showAdWarningText);
$previewContents = 'crm/preview_mobile_send/layer_preview_mobile_send_kakao_friendtalk.php';
break;
$this->setData('template', $previewMeta);
$this->setData('hasAddChannelButton', $hasAddChannelButton);
$this->setData('alternativeContents', $alimTalkData['alternativeInfo']['content'] ?? '');
$this->setData('alternativeTitle', $alimTalkData['alternativeInfo']['title'] ?? '');
$this->setData('showAdWarningText', 'n');
$previewContents = 'crm/preview_mobile_send/layer_preview_mobile_send_kakao_alimtalk.php';
break;
$this->setData('myappPayload', $myappPushData);
$this->setData('showAdWarningText', $showAdWarningText);
$this->setData('alternativeContents', $payload['myappRequest']['alternativeInfo']['content'] ?? '');
// 대체 제목 미입력 시 메인 메시지 타이틀(푸시 제목)이 있으면 카피하여 표기 (미리보기 전용)
$alternativeTitle = $payload['myappRequest']['alternativeInfo']['title'] ?? '';
if ($alternativeTitle === '' || $alternativeTitle === null) {
$alternativeTitle = $myappPushData['pushSubject'] ?? '';
}
$this->setData('alternativeTitle', $alternativeTitle);
$previewContents = 'crm/preview_mobile_send/layer_preview_mobile_send_myapp_push.php';
break;
}
 
Controller/Admin/Crm/MobileSend/LayerKakaoFriendtalkContentSettingController.php+2
변경 전LayerKakaoFriendtalkContentSettingController.php
$friendtalkRequest = $defaultCampaignRequest['kakaoFriendtalkRequest'] ?? [];
$initialTextContent = $friendtalkRequest['text']['content'] ?? '';
$initialAlternativeMessage = $defaultCampaignRequest['smsLmsRequest']['content'] ?? '';
 
 
$this->setData('is080RejectAvailable', $is080RejectAvailable);
$this->setData('campaignMessageTypes', CampaignMessageType::cases());
$this->setData('friendtalkRequest', $friendtalkRequest);
$this->setData('initialTextContent', $initialTextContent);
$this->setData('initialAlternativeMessage', $initialAlternativeMessage);
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerKakaoFriendtalkContentSettingController.php
$friendtalkRequest = $defaultCampaignRequest['kakaoFriendtalkRequest'] ?? [];
$initialTextContent = $friendtalkRequest['text']['content'] ?? '';
$initialAlternativeMessage = $defaultCampaignRequest['smsLmsRequest']['content'] ?? '';
$initialAlternativeTitle = $defaultCampaignRequest['smsLmsRequest']['title'] ?? '';
 
$this->setData('is080RejectAvailable', $is080RejectAvailable);
$this->setData('campaignMessageTypes', CampaignMessageType::cases());
$this->setData('friendtalkRequest', $friendtalkRequest);
$this->setData('initialTextContent', $initialTextContent);
$this->setData('initialAlternativeMessage', $initialAlternativeMessage);
$this->setData('initialAlternativeTitle', $initialAlternativeTitle);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
Controller/Admin/Crm/MobileSend/LayerMobileSendSaveSmsTemplateController.php+2
변경 전LayerMobileSendSaveSmsTemplateController.php
{
$request = Request::post()->toArray();
$contents = $request['contents'] ?? '';
 
 
$categories = SmsTemplateCategory::getFindOptions();
 
$this->setData('categories', $categories);
$this->setData('contents', $contents);
 
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
 
변경 후LayerMobileSendSaveSmsTemplateController.php
{
$request = Request::post()->toArray();
$contents = $request['contents'] ?? '';
$messageTitle = $request['messageTitle'] ?? '';
 
$categories = SmsTemplateCategory::getFindOptions();
 
$this->setData('categories', $categories);
$this->setData('contents', $contents);
$this->setData('messageTitle', $messageTitle);
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
 
Controller/Admin/Crm/MobileSend/LayerMyappPushContentSettingController.php+2
변경 전LayerMyappPushContentSettingController.php
$initialTitle = $myappRequest['title'] ?? '';
$initialMessage = $myappRequest['content'] ?? '';
$initialAlternativeMessage = $recipeContext['defaultCampaignRequest']['smsLmsRequest']['content'] ?? '';
 
 
$this->setData('is080RejectAvailable', $is080RejectAvailable);
$this->setData('myappPushSendConditions', MyappPushSendCondition::cases());
$this->setData('initialTitle', $initialTitle);
$this->setData('initialMessage', $initialMessage);
$this->setData('initialAlternativeMessage', $initialAlternativeMessage);
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMyappPushContentSettingController.php
$initialTitle = $myappRequest['title'] ?? '';
$initialMessage = $myappRequest['content'] ?? '';
$initialAlternativeMessage = $recipeContext['defaultCampaignRequest']['smsLmsRequest']['content'] ?? '';
$initialAlternativeTitle = $recipeContext['defaultCampaignRequest']['smsLmsRequest']['title'] ?? '';
 
$this->setData('is080RejectAvailable', $is080RejectAvailable);
$this->setData('myappPushSendConditions', MyappPushSendCondition::cases());
$this->setData('initialTitle', $initialTitle);
$this->setData('initialMessage', $initialMessage);
$this->setData('initialAlternativeMessage', $initialAlternativeMessage);
$this->setData('initialAlternativeTitle', $initialAlternativeTitle);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
Controller/Admin/Crm/MobileSend/LayerSmsContentSettingController.php+2
변경 전LayerSmsContentSettingController.php
$recipeContext = $this->resolveRecipeContext();
$isRecipeContext = $this->hasRecipeContext();
$initialMessage = $recipeContext['defaultCampaignRequest']['smsLmsRequest']['content'] ?? '';
 
 
$this->setData('is080RejectAvailable', $is080RejectAvailable);
$this->setData('isRecipeContext', $isRecipeContext);
$this->setData('initialMessage', $initialMessage);
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerSmsContentSettingController.php
$recipeContext = $this->resolveRecipeContext();
$isRecipeContext = $this->hasRecipeContext();
$initialMessage = $recipeContext['defaultCampaignRequest']['smsLmsRequest']['content'] ?? '';
$initialTitle = $recipeContext['defaultCampaignRequest']['smsLmsRequest']['title'] ?? '';
 
$this->setData('is080RejectAvailable', $is080RejectAvailable);
$this->setData('isRecipeContext', $isRecipeContext);
$this->setData('initialMessage', $initialMessage);
$this->setData('initialTitle', $initialTitle);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
Controller/Admin/Crm/Sms/SmsTemplatePsController.php+1
변경 전SmsTemplatePsController.php
'templateCategory' => $category->value,
'templateTitle' => $subject,
'templateMessageContent' => $contents,
 
]);
 
$sno = $service->setSmsContents($templateDto);
 
변경 후SmsTemplatePsController.php
'templateCategory' => $category->value,
'templateTitle' => $subject,
'templateMessageContent' => $contents,
'templateMessageTitle' => trim($request['messageTitle'] ?? ''),
]);
 
$sno = $service->setSmsContents($templateDto);
 
Controller/Admin/Member/SmsAutoController.php+1
변경 전SmsAutoController.php
// SMS 포인트 Sync
Sms::saveSmsPoint();
$memberJoin = ComponentUtils::getPolicy('member.join');
 
$this->setData('smsAutoList', Sms::SMS_AUTO_RECEIVE_LIST);
$this->setData('smsAutoOrderPeriod', Sms::SMS_AUTO_ORDER_PERIOD);
$this->setData('smsAutoOrderAfterPeriod', Sms::SMS_ORDER_AFTER_PERIOD);
 
변경 후SmsAutoController.php
// SMS 포인트 Sync
Sms::saveSmsPoint();
$memberJoin = ComponentUtils::getPolicy('member.join');
$this->setData('lmsSubjectLimit', defined(Sms::class . '::LMS_SUBJECT_LIMIT') ? Sms::LMS_SUBJECT_LIMIT : 40);
$this->setData('smsAutoList', Sms::SMS_AUTO_RECEIVE_LIST);
$this->setData('smsAutoOrderPeriod', Sms::SMS_AUTO_ORDER_PERIOD);
$this->setData('smsAutoOrderAfterPeriod', Sms::SMS_ORDER_AFTER_PERIOD);
 
Controller/Admin/Member/SmsLogPsController.php+25-14
변경 전SmsLogPsController.php
try {
$requestParams = $request->post()->all();
$godoSms = new GodoSmsServerApi();
$smsLog = $componentSmsLog->getSmsLog('*', $requestParams['smsLogSno']);
 
 
 
 
 
 
 
 
 
 
 
 
if ($smsLog['replaceCodeType'] == 'none') {
// SMS 결과 수신 처리
if ($godoSms->smsReserveChange($requestParams['sno'], 'modify', $requestParams['contents']) !== true) {
throw new LayerException(__('수정이 실패되었습니다.'));
} else {
$componentSmsLog->editContents($requestParams);
$this->json(__('수정되었습니다.'));
}
} else {
// SMS 결과 수신 처리
if ($godoSms->smsSendListReserveChange($requestParams['sno'], 'modify', $requestParams['contents']) !== true) {
throw new LayerException(__('수정이 실패되었습니다.'));
} else {
$componentSmsLog->editContents($requestParams);
$this->json(__('수정되었습니다.'));
}
 
 
 
 
 
}
 
} catch (LayerException $e) {
$this->json($e->getMessage());
}
 
변경 후SmsLogPsController.php
try {
$requestParams = $request->post()->all();
$godoSms = new GodoSmsServerApi();
// 발송내역 고유번호 (수정 레이어는 sno 로 전달)
$smsLogSno = gd_isset($requestParams['smsLogSno'], gd_isset($requestParams['sno']));
$smsLog = $componentSmsLog->getSmsLog('*', $smsLogSno);
if (empty($smsLog)) {
throw new LayerException(__('발송내역을 찾을 수 없습니다.'));
}
// 예약변경 API 는 DB 의 contents/subject 를 읽어 외부(고도 SMS 서버)로 전송하므로
// 외부 예약 메시지에 수정 내용이 반영되도록 DB 를 먼저 갱신한다.
$requestParams['sno'] = $smsLogSno;
$componentSmsLog->editContents($requestParams);
if ($smsLog['replaceCodeType'] == 'none') {
$result = $godoSms->smsReserveChange($smsLogSno, 'modify');
 
 
 
 
 
 
} else {
// 치환코드 발송건은 수신자별로 재구성되므로 해당 발송내역의 발송리스트 sno 목록을 전달한다.
$smsSendList = $componentSmsLog->getSmsSendList(['smsLogSno' => $smsLogSno]);
$sendListSno = [];
foreach ((array) gd_isset($smsSendList['data'], []) as $sendListRow) {
$sendListSno[] = $sendListRow['sno'];
 
}
$result = $godoSms->smsSendListReserveChange($smsLogSno, 'modify', $sendListSno);
}
if ($result !== true) {
throw new LayerException(__('수정이 실패되었습니다.'));
}
$this->json(__('수정되었습니다.'));
} catch (LayerException $e) {
$this->json($e->getMessage());
}
 
Controller/Admin/Member/SmsSendController.php+4
변경 전SmsSendController.php
'board' => '게시판',
];
$smsContents = '';
 
$receiveTotal = $rejectCount = 0;
$receiverData = $reSenderData = [];
$popupMode = false;
$componentSmsLog = \App::load('Component\\Sms\\SmsLog');
$smsLog = $componentSmsLog->getSmsLog('*', $request->post()->get('smsLogSno')); // 재전송 문자내역 조회
$smsContents = StringUtils::htmlSpecialCharsStripSlashes($smsLog['contents']); // 재전송 메시지 설정
 
$smsSendList = $componentSmsLog->getSmsSendList($request->post()->all())['data']; // 재전송 문자내역 발송내역 조회
$hasGuest = $componentSmsLog->hasGuestBySendList($smsSendList); // 발송내역 중 비회원 발송내역 확인
$isReSendAll = $request->post()->get('mode', '') == 'resend_all_member';
$this->setData('sms080Policy', ComponentUtils::getPolicy('sms.sms080'));
$this->setData('smsStringLimit', Sms::SMS_STRING_LIMIT);
$this->setData('lmsStringLimit', Sms::LMS_STRING_LIMIT);
 
$this->setData('smsForbidTime', Sms::SMS_FORBID_TIME);
$this->setData('lmsPoint', Sms::LMS_POINT);
$this->setData('smsCallNum', $smsCallNum);
$this->setData('receiverMode', $receiverMode);
$this->setData('popupMode', $popupMode);
$this->setData('smsContents', $smsContents);
 
$this->setData('reSenderData', $reSenderData);
$this->setData('receiveTotal', $receiveTotal);
$this->setData('rejectCount', $rejectCount);
 
변경 후SmsSendController.php
'board' => '게시판',
];
$smsContents = '';
$smsSubject = '';
$receiveTotal = $rejectCount = 0;
$receiverData = $reSenderData = [];
$popupMode = false;
$componentSmsLog = \App::load('Component\\Sms\\SmsLog');
$smsLog = $componentSmsLog->getSmsLog('*', $request->post()->get('smsLogSno')); // 재전송 문자내역 조회
$smsContents = StringUtils::htmlSpecialCharsStripSlashes($smsLog['contents']); // 재전송 메시지 설정
$smsSubject = StringUtils::htmlSpecialCharsStripSlashes($smsLog['subject'] ?? ''); // 재전송 LMS 제목 복원
$smsSendList = $componentSmsLog->getSmsSendList($request->post()->all())['data']; // 재전송 문자내역 발송내역 조회
$hasGuest = $componentSmsLog->hasGuestBySendList($smsSendList); // 발송내역 중 비회원 발송내역 확인
$isReSendAll = $request->post()->get('mode', '') == 'resend_all_member';
$this->setData('sms080Policy', ComponentUtils::getPolicy('sms.sms080'));
$this->setData('smsStringLimit', Sms::SMS_STRING_LIMIT);
$this->setData('lmsStringLimit', Sms::LMS_STRING_LIMIT);
$this->setData('lmsSubjectLimit', defined(Sms::class . '::LMS_SUBJECT_LIMIT') ? Sms::LMS_SUBJECT_LIMIT : 40);
$this->setData('smsForbidTime', Sms::SMS_FORBID_TIME);
$this->setData('lmsPoint', Sms::LMS_POINT);
$this->setData('smsCallNum', $smsCallNum);
$this->setData('receiverMode', $receiverMode);
$this->setData('popupMode', $popupMode);
$this->setData('smsContents', $smsContents);
$this->setData('smsSubject', $smsSubject);
$this->setData('reSenderData', $reSenderData);
$this->setData('receiveTotal', $receiveTotal);
$this->setData('rejectCount', $rejectCount);
 
Controller/Admin/Promotion/CartRemindRegistController.php+1
변경 전CartRemindRegistController.php
$this->setData('selected', $selected);
$this->setData('smsStringLimit', Sms::SMS_STRING_LIMIT);
$this->setData('lmsStringLimit', Sms::LMS_STRING_LIMIT);
 
$this->setData('lmsPoint', Sms::LMS_POINT);
$this->setData('sms080Policy', ComponentUtils::getPolicy('sms.sms080'));
$this->setData('commerceSmsRefusalIntroUrl', ConfigUrlUtils::getNhnCommerceEchostUrl('sms-refusal-intro'));
 
변경 후CartRemindRegistController.php
$this->setData('selected', $selected);
$this->setData('smsStringLimit', Sms::SMS_STRING_LIMIT);
$this->setData('lmsStringLimit', Sms::LMS_STRING_LIMIT);
$this->setData('lmsSubjectLimit', defined(Sms::class . '::LMS_SUBJECT_LIMIT') ? Sms::LMS_SUBJECT_LIMIT : 40);
$this->setData('lmsPoint', Sms::LMS_POINT);
$this->setData('sms080Policy', ComponentUtils::getPolicy('sms.sms080'));
$this->setData('commerceSmsRefusalIntroUrl', ConfigUrlUtils::getNhnCommerceEchostUrl('sms-refusal-intro'));
 
Controller/Admin/Promotion/ComebackCouponRegistController.php+1
변경 전ComebackCouponRegistController.php
]);
$this->setData('smsStringLimit', Sms::SMS_STRING_LIMIT);
$this->setData('lmsStringLimit', Sms::LMS_STRING_LIMIT);
 
$this->setData('smsForbidTime', Sms::SMS_FORBID_TIME);
$this->setData('lmsPoint', Sms::LMS_POINT);
$this->setData('checkMode', gd_isset($checkMode));
 
변경 후ComebackCouponRegistController.php
]);
$this->setData('smsStringLimit', Sms::SMS_STRING_LIMIT);
$this->setData('lmsStringLimit', Sms::LMS_STRING_LIMIT);
$this->setData('lmsSubjectLimit', defined(Sms::class . '::LMS_SUBJECT_LIMIT') ? Sms::LMS_SUBJECT_LIMIT : 40);
$this->setData('smsForbidTime', Sms::SMS_FORBID_TIME);
$this->setData('lmsPoint', Sms::LMS_POINT);
$this->setData('checkMode', gd_isset($checkMode));
 
Controller/Admin/Promotion/LayerCartRemindMessageController.php+2-1
변경 전LayerCartRemindMessageController.php
{
$cartRemindNo = Request::get()->get('cartRemindNo');
$cartRemind = new CartRemind();
$cartRemindData = $cartRemind->getCartRemindInfo($cartRemindNo,'cartRemindNo, cartRemindSendType, cartRemindSendMessage');
 
$this->setData('cartRemindData', $cartRemindData);
$this->setData('smsStringLimit', Sms::SMS_STRING_LIMIT);
$this->setData('lmsStringLimit', Sms::LMS_STRING_LIMIT);
 
$this->setData('lmsPoint', Sms::LMS_POINT);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
 
변경 후LayerCartRemindMessageController.php
{
$cartRemindNo = Request::get()->get('cartRemindNo');
$cartRemind = new CartRemind();
$cartRemindData = $cartRemind->getCartRemindInfo($cartRemindNo,'cartRemindNo, cartRemindSendType, cartRemindSendMessage, cartRemindSendSubject');
 
$this->setData('cartRemindData', $cartRemindData);
$this->setData('smsStringLimit', Sms::SMS_STRING_LIMIT);
$this->setData('lmsStringLimit', Sms::LMS_STRING_LIMIT);
$this->setData('lmsSubjectLimit', defined(Sms::class . '::LMS_SUBJECT_LIMIT') ? Sms::LMS_SUBJECT_LIMIT : 40);
$this->setData('lmsPoint', Sms::LMS_POINT);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
 
Controller/Admin/Share/LayerStandbySmsController.php+2-1
변경 전LayerStandbySmsController.php
{
/** @var \Bundle\Component\Sms\SmsLog $smsLog */
$smsLog = App::load('\\Component\\Sms\\SmsLog');
$data = $smsLog->getSmsLog('sno, sendFl, contents, contentsMask', Request::get()->get('sno'));
$this->getView()->setDefine('layout', 'layout_layer.php');
$this->setData('data', gd_htmlspecialchars_stripslashes($data));
$this->setData('smsStringLimit', Sms::SMS_STRING_LIMIT);
$this->setData('lmsStringLimit', Sms::LMS_STRING_LIMIT);
 
$this->setData('smsForbidTime', Sms::SMS_FORBID_TIME);
$this->setData('lmsPoint', Sms::LMS_POINT);
}
 
변경 후LayerStandbySmsController.php
{
/** @var \Bundle\Component\Sms\SmsLog $smsLog */
$smsLog = App::load('\\Component\\Sms\\SmsLog');
$data = $smsLog->getSmsLog('sno, sendFl, subject, contents, contentsMask', Request::get()->get('sno'));
$this->getView()->setDefine('layout', 'layout_layer.php');
$this->setData('data', gd_htmlspecialchars_stripslashes($data));
$this->setData('smsStringLimit', Sms::SMS_STRING_LIMIT);
$this->setData('lmsStringLimit', Sms::LMS_STRING_LIMIT);
$this->setData('lmsSubjectLimit', defined(Sms::class . '::LMS_SUBJECT_LIMIT') ? Sms::LMS_SUBJECT_LIMIT : 40);
$this->setData('smsForbidTime', Sms::SMS_FORBID_TIME);
$this->setData('lmsPoint', Sms::LMS_POINT);
}
 
무통장 입금은행 보안 강화.
Component/Order/OrderAdminNew.php+40-31
변경 전OrderAdminNew.php
use Framework\Debug\Exception\LayerNotReloadException;
use Framework\Debug\Exception\AlertRedirectException;
use Framework\Debug\Exception\WarningException;
 
use Framework\StaticProxy\Proxy\UserFilePath;
use Framework\Utility\ArrayUtils;
use Framework\Utility\DateTimeUtils;
 
$this->checked['useFl'][$this->search['useFl']] = 'checked="checked"';
 
// 키워드 검색
if ($this->search['keyword']) {
if ($this->search['key'] == '') {
$tmpWhere = [
'bankName',
'accountNumber',
'depositor',
];
$arrWhereAll = [];
foreach ($tmpWhere as $keyNm) {
$arrWhereAll[] = '(' . $keyNm . ' LIKE concat(\'%\',?,\'%\'))';
$this->db->bind_param_push($this->arrBind, 's', $this->search['keyword']);
}
$this->arrWhere[] = '(' . gd_implode(' OR ', $arrWhereAll) . ')';
} else {
$this->arrWhere[] = '' . $this->search['key'] . ' LIKE concat(\'%\',?,\'%\')';
$this->db->bind_param_push($this->arrBind, 's', $this->search['keyword']);
}
}
 
// 사용 여부 검색
if ($this->search['useFl']) {
$this->arrWhere[] = 'useFl = ?';
$page->setPage();
$page->setUrl(\Request::getQueryString());
 
// 현 페이지 결과
$this->db->strField = '*';
$this->db->strWhere = gd_implode(' AND ', gd_isset($this->arrWhere));
$this->db->strOrder = "defaultFl desc," . $this->search['sort'];
$this->db->strLimit = $page->recode['start'] . ',' . $request['pageNum'];
 
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_MANAGE_BANK . gd_implode(' ', $query);
$data = $this->db->query_fetch($strSQL, $this->arrBind);
 
 
 
 
// 레코드 수
unset($query['group'], $query['order'], $query['limit']);
$strCntSQL = 'SELECT COUNT(*) AS total FROM ' . DB_MANAGE_BANK . gd_implode(' ', $query);
$page->recode['total'] = $this->db->query_fetch($strCntSQL, $this->arrBind, false)['total'];
//list($page->recode['total']) = $this->db->fetch('SELECT FOUND_ROWS()', 'row');
$page->setPage();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
// 각 데이터 배열화
$getData['data'] = gd_htmlspecialchars_stripslashes(gd_isset($data));
throw new Exception(__('예금주은(는) 필수 항목 입니다.'));
}
 
 
 
 
if (empty($arrData['defaultFl']) === true) {
$arrData['defaultFl'] = "n";
}
 
변경 후OrderAdminNew.php
use Framework\Debug\Exception\LayerNotReloadException;
use Framework\Debug\Exception\AlertRedirectException;
use Framework\Debug\Exception\WarningException;
use Framework\Security\ManageBankCryptor;
use Framework\StaticProxy\Proxy\UserFilePath;
use Framework\Utility\ArrayUtils;
use Framework\Utility\DateTimeUtils;
 
$this->checked['useFl'][$this->search['useFl']] = 'checked="checked"';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
// 사용 여부 검색
if ($this->search['useFl']) {
$this->arrWhere[] = 'useFl = ?';
$page->setPage();
$page->setUrl(\Request::getQueryString());
 
 
$this->db->strField = '*';
$this->db->strWhere = gd_implode(' AND ', gd_isset($this->arrWhere));
$this->db->strOrder = "defaultFl desc," . $this->search['sort'];
 
 
$keyword = (string) $this->search['keyword'];
if ($keyword !== '') {
// 계좌번호는 암호화 저장되어 SQL 조건 검색이 불가하므로, 전체 목록을 조회·복호화 후 검색 항목(은행명·계좌번호·예금주) 기준으로 필터한다.
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_MANAGE_BANK . gd_implode(' ', $query);
$data = gd_isset($this->db->query_fetch($strSQL, $this->arrBind), []);
 
foreach ($data as $index => $bankInfo) {
$data[$index]['accountNumber'] = ManageBankCryptor::decrypt($bankInfo['accountNumber']);
}
 
// 검색 항목 기준으로 키워드 부분검색 필터
$data = $this->filterBankListByKeyword($data, $keyword, (string) $this->search['key'], false);
 
$page->recode['total'] = count($data);
$page->setPage();
$data = array_slice($data, $page->recode['start'], (int) $request['pageNum']);
} else {
$this->db->strLimit = $page->recode['start'] . ',' . $request['pageNum'];
 
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_MANAGE_BANK . gd_implode(' ', $query);
$data = $this->db->query_fetch($strSQL, $this->arrBind);
 
// 계좌번호 복호화
if (!empty($data)) {
foreach ($data as $index => $bankInfo) {
$data[$index]['accountNumber'] = ManageBankCryptor::decrypt($bankInfo['accountNumber']);
}
}
 
// 레코드 수
unset($query['group'], $query['order'], $query['limit']);
$strCntSQL = 'SELECT COUNT(*) AS total FROM ' . DB_MANAGE_BANK . gd_implode(' ', $query);
$page->recode['total'] = $this->db->query_fetch($strCntSQL, $this->arrBind, false)['total'];
$page->setPage();
}
 
// 각 데이터 배열화
$getData['data'] = gd_htmlspecialchars_stripslashes(gd_isset($data));
throw new Exception(__('예금주은(는) 필수 항목 입니다.'));
}
 
// 계좌번호 암호화 저장
$arrData['accountNumber'] = ManageBankCryptor::encrypt($arrData['accountNumber']);
 
if (empty($arrData['defaultFl']) === true) {
$arrData['defaultFl'] = "n";
}
 
Component/Order/OrderAdmin.php+65-39
변경 전OrderAdmin.php
use Framework\Debug\Exception\LayerNotReloadException;
use Framework\Debug\Exception\AlertRedirectException;
use Framework\Debug\Exception\WarningException;
 
use Framework\StaticProxy\Proxy\UserFilePath;
use Framework\Utility\ArrayUtils;
use Framework\Utility\DateTimeUtils;
 
$this->checked['useFl'][$this->search['useFl']] = 'checked="checked"';
 
// 키워드 검색
if ($this->search['keyword']) {
if ($this->search['key'] == '') {
$tmpWhere = [
'bankName',
'accountNumber',
'depositor',
];
$arrWhereAll = [];
foreach ($tmpWhere as $keyNm) {
if ($this->search['searchKind'] == 'equalSearch') {
$arrWhereAll[] = '(' . $keyNm . ' = ? )';
} else {
$arrWhereAll[] = '(' . $keyNm . ' LIKE concat(\'%\',?,\'%\'))';
}
$this->db->bind_param_push($this->arrBind, 's', $this->search['keyword']);
}
$this->arrWhere[] = '(' . gd_implode(' OR ', $arrWhereAll) . ')';
} else {
if ($this->search['searchKind'] == 'equalSearch') {
$this->arrWhere[] = '' . $this->search['key'] . ' = ? ';
} else {
$this->arrWhere[] = '' . $this->search['key'] . ' LIKE concat(\'%\',?,\'%\')';
}
$this->db->bind_param_push($this->arrBind, 's', $this->search['keyword']);
}
}
 
// 사용 여부 검색
if ($this->search['useFl']) {
$this->arrWhere[] = 'useFl = ?';
$page->setPage();
$page->setUrl(\Request::getQueryString());
 
// 현 페이지 결과
$this->db->strField = '*';
$this->db->strWhere = gd_implode(' AND ', gd_isset($this->arrWhere));
$this->db->strOrder = "defaultFl desc," . $this->search['sort'];
$this->db->strLimit = $page->recode['start'] . ',' . $request['pageNum'];
 
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_MANAGE_BANK . gd_implode(' ', $query);
$data = $this->db->query_fetch($strSQL, $this->arrBind);
 
 
 
 
// 레코드 수
unset($query['group'], $query['order'], $query['limit']);
$strCntSQL = 'SELECT COUNT(*) AS total FROM ' . DB_MANAGE_BANK . gd_implode(' ', $query);
$page->recode['total'] = $this->db->query_fetch($strCntSQL, $this->arrBind, false)['total'];
//list($page->recode['total']) = $this->db->fetch('SELECT FOUND_ROWS()', 'row');
$page->setPage();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
// 각 데이터 배열화
$getData['data'] = gd_htmlspecialchars_stripslashes(gd_isset($data));
return $getData;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 주문정책내 무통장 입금은행 등록 / 수정
*
throw new Exception(__('예금주은(는) 필수 항목 입니다.'));
}
 
 
 
 
if (empty($arrData['defaultFl']) === true) {
$arrData['defaultFl'] = "n";
}
 
변경 후OrderAdmin.php
use Framework\Debug\Exception\LayerNotReloadException;
use Framework\Debug\Exception\AlertRedirectException;
use Framework\Debug\Exception\WarningException;
use Framework\Security\ManageBankCryptor;
use Framework\StaticProxy\Proxy\UserFilePath;
use Framework\Utility\ArrayUtils;
use Framework\Utility\DateTimeUtils;
 
$this->checked['useFl'][$this->search['useFl']] = 'checked="checked"';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
// 사용 여부 검색
if ($this->search['useFl']) {
$this->arrWhere[] = 'useFl = ?';
$page->setPage();
$page->setUrl(\Request::getQueryString());
 
 
$this->db->strField = '*';
$this->db->strWhere = gd_implode(' AND ', gd_isset($this->arrWhere));
$this->db->strOrder = "defaultFl desc," . $this->search['sort'];
 
 
$keyword = (string) $this->search['keyword'];
if ($keyword !== '') {
// 계좌번호는 암호화 저장되어 SQL 조건 검색이 불가하므로, 전체 목록을 조회·복호화 후 검색 항목(은행명·계좌번호·예금주) 기준으로 필터한다.
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_MANAGE_BANK . gd_implode(' ', $query);
$data = gd_isset($this->db->query_fetch($strSQL, $this->arrBind), []);
 
foreach ($data as $index => $bankInfo) {
$data[$index]['accountNumber'] = ManageBankCryptor::decrypt($bankInfo['accountNumber']);
}
 
// 검색 항목 기준으로 키워드 필터
$data = $this->filterBankListByKeyword($data, $keyword, (string) $this->search['key'], $this->search['searchKind'] == 'equalSearch');
 
$page->recode['total'] = count($data);
$page->setPage();
$data = array_slice($data, $page->recode['start'], (int) $request['pageNum']);
} else {
$this->db->strLimit = $page->recode['start'] . ',' . $request['pageNum'];
 
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_MANAGE_BANK . gd_implode(' ', $query);
$data = $this->db->query_fetch($strSQL, $this->arrBind);
 
// 계좌번호 복호화
if (!empty($data)) {
foreach ($data as $index => $bankInfo) {
$data[$index]['accountNumber'] = ManageBankCryptor::decrypt($bankInfo['accountNumber']);
}
}
 
// 레코드 수
unset($query['group'], $query['order'], $query['limit']);
$strCntSQL = 'SELECT COUNT(*) AS total FROM ' . DB_MANAGE_BANK . gd_implode(' ', $query);
$page->recode['total'] = $this->db->query_fetch($strCntSQL, $this->arrBind, false)['total'];
$page->setPage();
}
 
// 각 데이터 배열화
$getData['data'] = gd_htmlspecialchars_stripslashes(gd_isset($data));
return $getData;
}
 
/**
* 무통장 입금은행 목록을 검색어(은행명·계좌번호·예금주) 기준으로 필터링
*
* @param array $data 복호화 완료된 은행 목록
* @param string $keyword 검색어
* @param string $key 검색 항목 (빈 값이면 통합검색: 은행명·계좌번호·예금주)
* @param bool $isEqual 완전일치 여부 (false 면 부분검색)
*
* @return array 필터링된 목록 (인덱스 재정렬)
*/
protected function filterBankListByKeyword(array $data, string $keyword, string $key, bool $isEqual): array
{
$fields = $key === '' ? ['bankName', 'accountNumber', 'depositor'] : [$key];
 
return array_values(array_filter($data, function ($row) use ($fields, $keyword, $isEqual) {
foreach ($fields as $field) {
$target = (string) $row[$field];
if ($isEqual ? $target === $keyword : mb_strpos($target, $keyword) !== false) {
return true;
}
}
return false;
}));
}
 
/**
* 주문정책내 무통장 입금은행 등록 / 수정
*
throw new Exception(__('예금주은(는) 필수 항목 입니다.'));
}
 
// 계좌번호 암호화 저장
$arrData['accountNumber'] = ManageBankCryptor::encrypt($arrData['accountNumber']);
 
if (empty($arrData['defaultFl']) === true) {
$arrData['defaultFl'] = "n";
}
 
Component/Order/OrderNew.php+19
변경 전OrderNew.php
use Framework\Helper\MallHelper;
use Framework\SimpleCache\SimpleCache;
use Framework\SimpleCache\SimpleCacheException;
 
use Framework\Utility\ArrayUtils;
use Framework\Utility\ComponentUtils;
use Framework\Utility\KafkaUtils;
if (empty($data) === true) {
return false;
} else {
 
 
 
 
 
return gd_htmlspecialchars_stripslashes($data);
}
}
$strSQL = 'SELECT sno, ' . gd_implode(', ', DBTableField::setTableField('tableManageBank')) . ' FROM ' . DB_MANAGE_BANK . ' WHERE ' . gd_implode(' AND ', $arrWhere);
$data = $this->db->query_fetch($strSQL, $arrBind, $boolFetch);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
return gd_htmlspecialchars_stripslashes($data);
}
 
 
변경 후OrderNew.php
use Framework\Helper\MallHelper;
use Framework\SimpleCache\SimpleCache;
use Framework\SimpleCache\SimpleCacheException;
use Framework\Security\ManageBankCryptor;
use Framework\Utility\ArrayUtils;
use Framework\Utility\ComponentUtils;
use Framework\Utility\KafkaUtils;
if (empty($data) === true) {
return false;
} else {
// 계좌번호 복호화
foreach ($data as $index => $bankInfo) {
$data[$index]['accountNumber'] = ManageBankCryptor::decrypt($bankInfo['accountNumber']);
}
 
return gd_htmlspecialchars_stripslashes($data);
}
}
$strSQL = 'SELECT sno, ' . gd_implode(', ', DBTableField::setTableField('tableManageBank')) . ' FROM ' . DB_MANAGE_BANK . ' WHERE ' . gd_implode(' AND ', $arrWhere);
$data = $this->db->query_fetch($strSQL, $arrBind, $boolFetch);
 
// 계좌번호 복호화
if ($boolFetch) {
if (!empty($data)) {
foreach ($data as $index => $bankInfo) {
$data[$index]['accountNumber'] = ManageBankCryptor::decrypt($bankInfo['accountNumber']);
}
}
} else {
if (!empty($data['accountNumber'])) {
$data['accountNumber'] = ManageBankCryptor::decrypt($data['accountNumber']);
}
}
 
return gd_htmlspecialchars_stripslashes($data);
}
 
 
Component/Order/Order.php+19
변경 전Order.php
use Framework\Debug\Exception\AlertRedirectException;
use Framework\Debug\Exception\DatabaseException;
use Framework\Helper\MallHelper;
 
use Framework\Utility\ArrayUtils;
use Framework\Utility\ComponentUtils;
use Framework\Utility\NumberUtils;
if (empty($data) === true) {
return false;
} else {
 
 
 
 
 
return gd_htmlspecialchars_stripslashes($data);
}
}
$strSQL = 'SELECT sno, ' . gd_implode(', ', DBTableField::setTableField('tableManageBank')) . ' FROM ' . DB_MANAGE_BANK . ' WHERE ' . gd_implode(' AND ', $arrWhere);
$data = $this->db->secondary()->query_fetch($strSQL, $arrBind, $boolFetch);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
return gd_htmlspecialchars_stripslashes($data);
}
 
 
변경 후Order.php
use Framework\Debug\Exception\AlertRedirectException;
use Framework\Debug\Exception\DatabaseException;
use Framework\Helper\MallHelper;
use Framework\Security\ManageBankCryptor;
use Framework\Utility\ArrayUtils;
use Framework\Utility\ComponentUtils;
use Framework\Utility\NumberUtils;
if (empty($data) === true) {
return false;
} else {
// 계좌번호 복호화
foreach ($data as $index => $bankInfo) {
$data[$index]['accountNumber'] = ManageBankCryptor::decrypt($bankInfo['accountNumber']);
}
 
return gd_htmlspecialchars_stripslashes($data);
}
}
$strSQL = 'SELECT sno, ' . gd_implode(', ', DBTableField::setTableField('tableManageBank')) . ' FROM ' . DB_MANAGE_BANK . ' WHERE ' . gd_implode(' AND ', $arrWhere);
$data = $this->db->secondary()->query_fetch($strSQL, $arrBind, $boolFetch);
 
// 계좌번호 복호화
if ($boolFetch) {
if (!empty($data)) {
foreach ($data as $index => $bankInfo) {
$data[$index]['accountNumber'] = ManageBankCryptor::decrypt($bankInfo['accountNumber']);
}
}
} else {
if (!empty($data['accountNumber'])) {
$data['accountNumber'] = ManageBankCryptor::decrypt($data['accountNumber']);
}
}
 
return gd_htmlspecialchars_stripslashes($data);
}
 
 
무료보안 관리자 화면 안정성 개선
Component/SiteLink/SecureSocketLayer.php+6-4
변경 전SecureSocketLayer.php
$checkData = $this->getSsl($searchArr);
if (gd_count($checkData) > 0) {
$returnData['status'] = 'usePay';
} else if ($this->getFreeSslUse()) {
$returnData['status'] = 'used';
} else {
$returnData['status'] = 'process';
$isValidPeriod = ($sDateCheck >= 0 && $eDateCheck >= 0); // 설치완료
if ($isExpired) {
$returnData['status'] = 'request';
} else if ($isRenewRange) {
if (gd_in_array($sslData['sslConfigStatus'], ['wait', 'process']) === true) {
$returnData['status'] = $sslData['sslConfigStatus'];
} else if ($sslData['sslConfigType'] == 'godo') {
$returnData['status'] = 'renewrequest';
 
 
}
} else if ($isValidPeriod) {
$returnData['status'] = 'used';
} else {
$returnData['status'] = $sslData['sslConfigStatus'];
 
변경 후SecureSocketLayer.php
$checkData = $this->getSsl($searchArr);
if (gd_count($checkData) > 0) {
$returnData['status'] = 'usePay';
} elseif ($this->getFreeSslUse()) {
$returnData['status'] = 'used';
} else {
$returnData['status'] = 'process';
$isValidPeriod = ($sDateCheck >= 0 && $eDateCheck >= 0); // 설치완료
if ($isExpired) {
$returnData['status'] = 'request';
} elseif ($isRenewRange) {
if (gd_in_array($sslData['sslConfigStatus'], ['wait', 'process']) === true) {
$returnData['status'] = $sslData['sslConfigStatus'];
} elseif ($sslData['sslConfigType'] == 'godo') {
$returnData['status'] = 'renewrequest';
} elseif ($sslData['sslConfigType'] === 'free_multi') {
$returnData['status'] = 'used';
}
} elseif ($isValidPeriod) {
$returnData['status'] = 'used';
} else {
$returnData['status'] = $sslData['sslConfigStatus'];
 
디자인 스킨 업로드 기능 개선
Admin/design/design_skin_list/owned_skin_list_section.php+2-4
변경 전owned_skin_list_section.php
<header class="ncua-card__header">
<h4 class="ncua-card__title">보유 스킨 리스트</h4>
</header>
<!-- 탭 영역 -->
<?php if ($skinTabData['responsiveSkinCount'] > 0 && $skinTabData['adaptiveSkinCount'] > 0): ?>
<div class="swiper ncua-horizontal-tab ncua-horizontal-tab--sm ncua-horizontal-tab--underline-fill">
<div class="swiper-wrapper ncua-gap-8">
<div class="swiper-slide ncua-horizontal-tab__item">
</div>
</div>
</div>
<?php endif; ?>
</div>
<section class="ncua-card__body">
 
<p class="ncua-search-result__summary-count">
전체 <strong class="js-total-count">0</strong>개
</p>
<!-- TODO: 반응형 스킨이 있는 경우 is-visible-hidden 클래스 추가 -->
<div class="ncua-file-input ncua-file-input--xs">
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray js-skin-upload <?= $skinTabData['defaultTab'] === 'responsive' ? 'is-visible-hidden' : '' ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="none" class="ncua-btn__icon">
 
변경 후owned_skin_list_section.php
<header class="ncua-card__header">
<h4 class="ncua-card__title">보유 스킨 리스트</h4>
</header>
<!-- 탭 영역 (스킨 보유 여부와 무관하게 항상 노출) -->
 
<div class="swiper ncua-horizontal-tab ncua-horizontal-tab--sm ncua-horizontal-tab--underline-fill">
<div class="swiper-wrapper ncua-gap-8">
<div class="swiper-slide ncua-horizontal-tab__item">
</div>
</div>
</div>
 
</div>
<section class="ncua-card__body">
 
<p class="ncua-search-result__summary-count">
전체 <strong class="js-total-count">0</strong>개
</p>
<!-- 스킨 업로드: 반응형 탭 미노출, PC/모바일 탭 노출 (탭 전환 시 JS에서 토글) -->
<div class="ncua-file-input ncua-file-input--xs">
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray js-skin-upload <?= $skinTabData['defaultTab'] === 'responsive' ? 'is-visible-hidden' : '' ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="none" class="ncua-btn__icon">
 
Admin/design/design_skin_list.php+12-5
변경 전design_skin_list.php
<script type="text/javascript" src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/ncua-dropdown-wrapper.v0.1.js')?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/spinner-modal.js') ?>"></script>
 
<article class="ncua-content design-skin-list">
<header class="page-header ncua-page-header js-affix">
<h3 class="ncua-help-manual"><?= end($naviMenu->location); ?>
</h3>
<?php include $activeDesignSkinSection; ?>
<?php } ?>
 
<!-- 보유 스킨 리스트 -->
<?php if ($skinTabData['responsiveSkinCount'] > 0 || $skinTabData['adaptiveSkinCount'] > 0) { ?>
<?php include $ownedSkinListSection; ?>
<?php } ?>
 
<!-- 해외몰 홈아이콘 설정 -->
<?php if ($mallCnt > 1) { ?>
// 모두 보기 버튼 표시 여부 업데이트
ownedSkinList.updateViewAllButton(filteredList.length);
 
 
 
 
 
 
 
 
 
 
filteredList.forEach((skin, idx) => {
try {
if(maxLen && maxLen <= idx) return;
 
변경 후design_skin_list.php
<script type="text/javascript" src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/ncua-dropdown-wrapper.v0.1.js')?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/spinner-modal.js') ?>"></script>
 
<article class="ncua-content design-skin-list content-wrap-initial">
<header class="page-header ncua-page-header js-affix">
<h3 class="ncua-help-manual"><?= end($naviMenu->location); ?>
</h3>
<?php include $activeDesignSkinSection; ?>
<?php } ?>
 
<!-- 보유 스킨 리스트 (스킨 보유 여부와 무관하게 항상 노출) -->
<?php include $ownedSkinListSection; ?>
 
 
 
<!-- 해외몰 홈아이콘 설정 -->
<?php if ($mallCnt > 1) { ?>
// 모두 보기 버튼 표시 여부 업데이트
ownedSkinList.updateViewAllButton(filteredList.length);
 
// 보유 스킨이 없는 경우 안내 문구 노출
if (filteredList.length === 0) {
const emptyMessage = document.createElement('div');
emptyMessage.className = 'owned-skin-list--empty';
emptyMessage.textContent = '보유한 스킨이 없습니다.';
skinList.appendChild(emptyMessage);
return;
}
 
filteredList.forEach((skin, idx) => {
try {
if(maxLen && maxLen <= idx) return;
 
Controller/Admin/Design/DesignSkinListController.php+10-14
변경 전DesignSkinListController.php
use Origin\Service\Aggregator\GodomallAggregatorService;
use Origin\Service\MyApp\MyappSettingService;
use Origin\Enum\ManagerTutorialType;
 
use Origin\Service\Admin\Member\ManagerTutorial\HelperPopupExposureAvailabilityService;
// 스킨 설정 정보, (사용 중 아닌) 스킨 목록 설정
$designSkinConfig = gd_policy('design.skin', $mallSno);
$usingSkinType = $designSkinConfig['skinType'] ?? 'adaptive';
// 모바일샵 설정 정보
$mobileConfig = gd_policy('mobile.config');
$unusedSkinList = [];
foreach ($skinList as $skin) {
$skinType = $skin['skin_type'] ?? 'adaptive';
$skinDevice = $skin['skin_device'];
$skinCode = $skin['skin_code'];
// 적응형 스킨에만 default_design_page_id 추가
if ($skinType === 'adaptive') {
$skin['default_design_page_id'] = $this->getDefaultDesignPageId($skinCode, $skinDevice);
}
$adaptiveSkinCount = 0;
foreach ($skinList as $skin) {
if (($skin['skin_type'] ?? 'adaptive') === 'responsive') {
$responsiveSkinCount++;
} else {
$adaptiveSkinCount++;
}
}
// 기본 탭 결정: 둘 다 있으면 사용 중인 타입, 아니면 보유한 타입
if ($responsiveSkinCount > 0 && $adaptiveSkinCount > 0) {
$defaultTab = $usingSkinType;
} elseif ($responsiveSkinCount > 0) {
$defaultTab = 'responsive';
} else {
$defaultTab = 'adaptive';
}
return [
'responsiveSkinCount' => $responsiveSkinCount,
'adaptiveSkinCount' => $adaptiveSkinCount,
'isResponsiveActive' => $usingSkinType === 'responsive',
'isAdaptiveActive' => $usingSkinType !== 'responsive',
'defaultTab' => $defaultTab,
];
}
 
변경 후DesignSkinListController.php
use Origin\Service\Aggregator\GodomallAggregatorService;
use Origin\Service\MyApp\MyappSettingService;
use Origin\Enum\ManagerTutorialType;
use Origin\Enum\Design\DesignEditorSkinType;
use Origin\Service\Admin\Member\ManagerTutorial\HelperPopupExposureAvailabilityService;
// 스킨 설정 정보, (사용 중 아닌) 스킨 목록 설정
$designSkinConfig = gd_policy('design.skin', $mallSno);
$usingSkinType = $designSkinConfig['skinType'] ?? DesignEditorSkinType::ADAPTIVE->value;
// 모바일샵 설정 정보
$mobileConfig = gd_policy('mobile.config');
$unusedSkinList = [];
foreach ($skinList as $skin) {
$skinType = $skin['skin_type'] ?? DesignEditorSkinType::ADAPTIVE->value;
$skinDevice = $skin['skin_device'];
$skinCode = $skin['skin_code'];
// 적응형 스킨에만 default_design_page_id 추가
if ($skinType === DesignEditorSkinType::ADAPTIVE->value) {
$skin['default_design_page_id'] = $this->getDefaultDesignPageId($skinCode, $skinDevice);
}
$adaptiveSkinCount = 0;
foreach ($skinList as $skin) {
if (($skin['skin_type'] ?? DesignEditorSkinType::ADAPTIVE->value) === DesignEditorSkinType::RESPONSIVE->value) {
$responsiveSkinCount++;
} else {
$adaptiveSkinCount++;
}
}
// 탭은 스킨 보유 여부와 무관하게 항상 노출되므로, 기본 탭은 현재 사용 중인 스킨 유형을 따른다.
$isResponsiveActive = $usingSkinType === DesignEditorSkinType::RESPONSIVE->value;
$defaultTab = $isResponsiveActive ? DesignEditorSkinType::RESPONSIVE->value : DesignEditorSkinType::ADAPTIVE->value;
 
 
 
 
 
return [
'responsiveSkinCount' => $responsiveSkinCount,
'adaptiveSkinCount' => $adaptiveSkinCount,
'isResponsiveActive' => $isResponsiveActive,
'isAdaptiveActive' => !$isResponsiveActive,
'defaultTab' => $defaultTab,
];
}
 
해외 상점 도메인 정보 표시 개선.
Admin/policy/base_seo.php+1-1
변경 전base_seo.php
<tr>
<th>모바일 쇼핑몰</th>
<td class="form-inline">
<?php echo URI_MOBILE;?><input type="text" name="errPage[mobile]" value="<?php echo $data['errPage']['mobile']; ?>" class="form-control width-lg"/>
</td>
</tr>
</table>
 
변경 후base_seo.php
<tr>
<th>모바일 쇼핑몰</th>
<td class="form-inline">
<?=$data['mallMobileDomain'] ?? URI_MOBILE?><input type="text" name="errPage[mobile]" value="<?php echo $data['errPage']['mobile']; ?>" class="form-control width-lg"/>
</td>
</tr>
</table>
 
Component/Mall/Mall.php-1
변경 전Mall.php
const GLOBAL_MALL_BASE_INFO = [
'mallNmEng',
'mallFavicon',
'mallDomain',
'mallCategory',
'businessNo',
'service',
 
변경 후Mall.php
const GLOBAL_MALL_BASE_INFO = [
'mallNmEng',
'mallFavicon',
 
'mallCategory',
'businessNo',
'service',
 
Controller/Admin/Policy/BaseInfoController.php+9
변경 전BaseInfoController.php
if (gd_in_array($key, Mall::GLOBAL_MALL_BASE_INFO) === true) $data[$key] = $value;
}
 
 
 
 
 
 
 
 
 
 
$this->setData('disabled', self::ELEMENT_DISABLED);
$this->setData('readonly', self::ELEMENT_READONLY);
}
 
변경 후BaseInfoController.php
if (gd_in_array($key, Mall::GLOBAL_MALL_BASE_INFO) === true) $data[$key] = $value;
}
 
//설정한 shopDomain 있으면 덮어쓰기
if (!empty($globalsInfo[$mallSno]['connectDomain'])) {
$globalShopDomains = json_decode($globalsInfo[$mallSno]['connectDomain'], true);
$domainIndex = $globalsInfo[$mallSno]['useShopDomain'] ?? null;
if (isset($globalShopDomains['connect'][$domainIndex])) {
$data['mallDomain'] = $globalShopDomains['connect'][$domainIndex];
}
}
 
$this->setData('disabled', self::ELEMENT_DISABLED);
$this->setData('readonly', self::ELEMENT_READONLY);
}
 
Controller/Admin/Policy/BaseSeoController.php+10
변경 전BaseSeoController.php
$socialData = $socialShare->getConfig();
gd_isset($socialData['snsRepresentTitle'],'{=gMall.mallNm}');
 
 
 
 
 
 
 
 
 
 
//RSS 설정 ,페이지 경로 설정 , 대표URL 설정 , 연관채널 설정
$seoData = gd_policy('basic.seo', $mallSno);
 
변경 후BaseSeoController.php
$socialData = $socialShare->getConfig();
gd_isset($socialData['snsRepresentTitle'],'{=gMall.mallNm}');
//설정한 shopDomain 있으면 덮어쓰기
$globalsInfo = \Globals::get('gGlobal.mallList');
if (!empty($globalsInfo[$mallSno]['connectDomain'])) {
$globalShopDomains = json_decode($globalsInfo[$mallSno]['connectDomain'], true);
$domainIndex = $globalsInfo[$mallSno]['useShopDomain'] ?? null;
if (isset($globalShopDomains['connect'][$domainIndex])) {
$data['mallDomain'] = $globalShopDomains['connect'][$domainIndex];
$data['mallMobileDomain'] = \Request::getDomainUrl(DOMAIN_USEABLE_LIST['mobile'] . '.' . $globalShopDomains['connect'][$domainIndex]) . DS;
}
}
//RSS 설정 ,페이지 경로 설정 , 대표URL 설정 , 연관채널 설정
$seoData = gd_policy('basic.seo', $mallSno);
 
본인인증(아이핀) 페이지 보안 강화
Controller/Front/Member/Ipin/IpinApplyController.php+3-1
변경 전IpinApplyController.php
// 사용자 정보를 복호화 합니다.
// 실행방법은 싱글쿼터(`) 외에도, 'exec(), system(), shell_exec()' 등등 귀사 정책에 맞게 처리하시기 바랍니다.
$sDecData = exec("$sModulePath $sType $sSiteCode $sSitePw $sEncData");
 
 
Logger::info(__METHOD__ . '$sDecData=>' . $sDecData . ', ' . "$sModulePath $sType $sSiteCode $sSitePw $sEncData");
if ($sDecData == -9) {
 
변경 후IpinApplyController.php
// 사용자 정보를 복호화 합니다.
// 실행방법은 싱글쿼터(`) 외에도, 'exec(), system(), shell_exec()' 등등 귀사 정책에 맞게 처리하시기 바랍니다.
$sDecData = exec(
implode(' ', array_map('escapeshellarg', [$sModulePath, $sType, $sSiteCode, $sSitePw, $sEncData]))
);
Logger::info(__METHOD__ . '$sDecData=>' . $sDecData . ', ' . "$sModulePath $sType $sSiteCode $sSitePw $sEncData");
if ($sDecData == -9) {
 
Controller/Front/Member/Ipin/IpinResultController.php+3-1
변경 전IpinResultController.php
if ($sEncData != "") {
// 사용자 정보를 복호화 합니다.
// 실행방법은 싱글쿼터(`) 외에도, 'exec(), system(), shell_exec()' 등등 귀사 정책에 맞게 처리하시기 바랍니다.
$sDecData = exec("$sModulePath $sType $sSiteCode $sSitePw $sEncData");
 
 
Logger::info('sDecData=>' . $sDecData . ', ' . "$sModulePath $sType $sSiteCode $sSitePw $sEncData");
 
if ($sDecData == -9) {
 
변경 후IpinResultController.php
if ($sEncData != "") {
// 사용자 정보를 복호화 합니다.
// 실행방법은 싱글쿼터(`) 외에도, 'exec(), system(), shell_exec()' 등등 귀사 정책에 맞게 처리하시기 바랍니다.
$sDecData = exec(
implode(' ', array_map('escapeshellarg', [$sModulePath, $sType, $sSiteCode, $sSitePw, $sEncData]))
);
Logger::info('sDecData=>' . $sDecData . ', ' . "$sModulePath $sType $sSiteCode $sSitePw $sEncData");
 
if ($sDecData == -9) {
 
Controller/Mobile/Member/Ipin/IpinApplyController.php+3-1
변경 전IpinApplyController.php
// 사용자 정보를 복호화 합니다.
// 실행방법은 싱글쿼터(`) 외에도, 'exec(), system(), shell_exec()' 등등 귀사 정책에 맞게 처리하시기 바랍니다.
$sDecData = exec("$sModulePath $sType $sSiteCode $sSitePw $sEncData");
 
 
Logger::info(__METHOD__ . '$sDecData=>' . $sDecData . ', ' . "$sModulePath $sType $sSiteCode $sSitePw $sEncData");
if ($sDecData == -9) {
 
변경 후IpinApplyController.php
// 사용자 정보를 복호화 합니다.
// 실행방법은 싱글쿼터(`) 외에도, 'exec(), system(), shell_exec()' 등등 귀사 정책에 맞게 처리하시기 바랍니다.
$sDecData = exec(
implode(' ', array_map('escapeshellarg', [$sModulePath, $sType, $sSiteCode, $sSitePw, $sEncData]))
);
Logger::info(__METHOD__ . '$sDecData=>' . $sDecData . ', ' . "$sModulePath $sType $sSiteCode $sSitePw $sEncData");
if ($sDecData == -9) {
 
본인인증(아이핀) 페이지 보안 강화
Controller/Front/Member/Ipin/IpinMainController.php+8-2
변경 전IpinMainController.php
// 앞서 설명드린 바와같이, CP 요청번호는 배포된 모듈을 통해 아래와 같이 생성할 수 있습니다.
// 실행방법은 싱글쿼터(`) 외에도, 'exec(), system(), shell_exec()' 등등 귀사 정책에 맞게 처리하시기 바랍니다.
$sCPRequest = exec("$sModulePath $sType $sSiteCode", $output, $error);
 
 
 
 
if ($error !== 0) {
Logger::error(__METHOD__ . ' $sCPRequest=>' . $sCPRequest . ', ' . "$sModulePath $sType $sSiteCode", $output);
}
// 리턴 결과값에 따라, 프로세스 진행여부를 파악합니다.
// 실행방법은 싱글쿼터(`) 외에도, 'exec(), system(), shell_exec()' 등등 귀사 정책에 맞게 처리하시기 바랍니다.
$sEncData = exec("$sModulePath $sType $sSiteCode $sSitePw $sCPRequest $sReturnURL");
 
 
Logger::info(__METHOD__ . ' $sEncData=>' . $sEncData . ', ' . "$sModulePath $sType $sSiteCode $sSitePw $sCPRequest $sReturnURL");
// 리턴 결과값에 따른 처리사항
 
변경 후IpinMainController.php
// 앞서 설명드린 바와같이, CP 요청번호는 배포된 모듈을 통해 아래와 같이 생성할 수 있습니다.
// 실행방법은 싱글쿼터(`) 외에도, 'exec(), system(), shell_exec()' 등등 귀사 정책에 맞게 처리하시기 바랍니다.
$sCPRequest = exec(
implode(' ', array_map('escapeshellarg', [$sModulePath, $sType, $sSiteCode])),
$output,
$error
);
if ($error !== 0) {
Logger::error(__METHOD__ . ' $sCPRequest=>' . $sCPRequest . ', ' . "$sModulePath $sType $sSiteCode", $output);
}
// 리턴 결과값에 따라, 프로세스 진행여부를 파악합니다.
// 실행방법은 싱글쿼터(`) 외에도, 'exec(), system(), shell_exec()' 등등 귀사 정책에 맞게 처리하시기 바랍니다.
$sEncData = exec(
implode(' ', array_map('escapeshellarg', [$sModulePath, $sType, $sSiteCode, $sSitePw, $sCPRequest, $sReturnURL]))
);
Logger::info(__METHOD__ . ' $sEncData=>' . $sEncData . ', ' . "$sModulePath $sType $sSiteCode $sSitePw $sCPRequest $sReturnURL");
// 리턴 결과값에 따른 처리사항
 
자동메일 발송설정 게시판 선택 기능 개선.
Controller/Admin/Crm/AutoSendConfigController.php+1
변경 전AutoSendConfigController.php
{
public function index()
{
 
$code = Request::get()->get('code');
$isBlock = false;
$blockMode = null;
 
변경 후AutoSendConfigController.php
{
public function index()
{
$this->setNcdsSubBodyClass();
$code = Request::get()->get('code');
$isBlock = false;
$blockMode = null;
 
Controller/Admin/Crm/AutoSendController.php+1
변경 전AutoSendController.php
{
public function index()
{
 
$request = Request::get()->toArray();
 
$this->callMenu('crm', 'messageSend', 'autoSend');
 
변경 후AutoSendController.php
{
public function index()
{
$this->setNcdsSubBodyClass();
$request = Request::get()->toArray();
 
$this->callMenu('crm', 'messageSend', 'autoSend');
 
Controller/Admin/Crm/CrmRecipeSendController.php+1
변경 전CrmRecipeSendController.php
 
public function index()
{
 
$this->callMenu('crm', 'crm', 'crmRecipe');
$this->setMenuCode('crm', 'crm', 'crmRecipe');
 
 
변경 후CrmRecipeSendController.php
 
public function index()
{
$this->setNcdsSubBodyClass();
$this->callMenu('crm', 'crm', 'crmRecipe');
$this->setMenuCode('crm', 'crm', 'crmRecipe');
 
 
Controller/Admin/Crm/IndexController.php+1
변경 전IndexController.php
{
public function index()
{
 
$this->redirect('./crm_group.php');
}
}
 
변경 후IndexController.php
{
public function index()
{
$this->setNcdsSubBodyClass();
$this->redirect('./crm_group.php');
}
}
 
Controller/Admin/Crm/MailGateBridgeController.php+1
변경 전MailGateBridgeController.php
{
public function index()
{
 
 
$pMail = \App::load('\\Component\\Mail\\Pmail');
$pMail->setPmail();
 
변경 후MailGateBridgeController.php
{
public function index()
{
$this->setNcdsSubBodyClass();
 
$pMail = \App::load('\\Component\\Mail\\Pmail');
$pMail->setPmail();
 
Controller/Admin/Crm/MailGateController.php+1
변경 전MailGateController.php
{
public function index()
{
 
// --- 모듈 호출
$data = null;
 
 
변경 후MailGateController.php
{
public function index()
{
$this->setNcdsSubBodyClass();
// --- 모듈 호출
$data = null;
 
 
Controller/Admin/Crm/MailSendController.php+1
변경 전MailSendController.php
*/
public function index()
{
 
/**
* 네비게이션 설정
*/
 
변경 후MailSendController.php
*/
public function index()
{
$this->setNcdsSubBodyClass();
/**
* 네비게이션 설정
*/
 
Controller/Admin/Crm/MailSendReceiveRefuseCountController.php+1
변경 전MailSendReceiveRefuseCountController.php
*/
public function index()
{
 
/**
* @var \Bundle\Component\Member\MemberAdmin $memberAdmin
*/
 
변경 후MailSendReceiveRefuseCountController.php
*/
public function index()
{
$this->setNcdsSubBodyClass();
/**
* @var \Bundle\Component\Member\MemberAdmin $memberAdmin
*/
 
Controller/Admin/Crm/MessageConfigController.php+1
변경 전MessageConfigController.php
 
public function index()
{
 
// 메뉴 설정
$this->callMenu('crm', 'messageConfig', 'messageConfig');
$this->setMenuCode('crm', 'messageConfig', 'messageConfig');
 
변경 후MessageConfigController.php
 
public function index()
{
$this->setNcdsSubBodyClass();
// 메뉴 설정
$this->callMenu('crm', 'messageConfig', 'messageConfig');
$this->setMenuCode('crm', 'messageConfig', 'messageConfig');
 
Controller/Admin/Crm/MessageTemplateController.php+1
변경 전MessageTemplateController.php
 
public function index()
{
 
$this->callMenu('crm', 'messageSend', 'messageTemplate');
$this->getView()->setPageName('crm/message_template.php');
$this->getView()->setDefine('messageTemplateSearch', 'crm/message_template/message_template_search.php');
 
변경 후MessageTemplateController.php
 
public function index()
{
$this->setNcdsSubBodyClass();
$this->callMenu('crm', 'messageSend', 'messageTemplate');
$this->getView()->setPageName('crm/message_template.php');
$this->getView()->setDefine('messageTemplateSearch', 'crm/message_template/message_template_search.php');
 
Controller/Admin/Crm/MessageTemplateRegisterController.php+1
변경 전MessageTemplateRegisterController.php
 
public function index()
{
 
$request = \App::getInstance('request');
$requestData = $request->request()->toArray();
 
 
변경 후MessageTemplateRegisterController.php
 
public function index()
{
$this->setNcdsSubBodyClass();
$request = \App::getInstance('request');
$requestData = $request->request()->toArray();
 
 
Controller/Admin/Crm/MobileHistoryController.php+1
변경 전MobileHistoryController.php
{
public function index()
{
 
$this->callMenu('crm', 'messageHistory', 'mobileHistory');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistory');
 
 
변경 후MobileHistoryController.php
{
public function index()
{
$this->setNcdsSubBodyClass();
$this->callMenu('crm', 'messageHistory', 'mobileHistory');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistory');
 
 
Controller/Admin/Crm/MobileHistoryListController.php+1
변경 전MobileHistoryListController.php
{
public function index()
{
 
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
 
변경 후MobileHistoryListController.php
{
public function index()
{
$this->setNcdsSubBodyClass();
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
 
Controller/Admin/Crm/MobileSendController.php+1
변경 전MobileSendController.php
{
public function index()
{
 
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
// 현재 SMS 포인트 싱크
 
변경 후MobileSendController.php
{
public function index()
{
$this->setNcdsSubBodyClass();
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
// 현재 SMS 포인트 싱크
 
Controller/Admin/Crm/PopupChargeMessagePointsController.php+1
변경 전PopupChargeMessagePointsController.php
{
public function index()
{
 
$request = \App::getInstance('request');
$session = \App::getInstance('session');
$globals = \App::getInstance('globals');
 
변경 후PopupChargeMessagePointsController.php
{
public function index()
{
$this->setNcdsSubBodyClass();
$request = \App::getInstance('request');
$session = \App::getInstance('session');
$globals = \App::getInstance('globals');
 
Controller/Admin/Crm/PopupManageCallNumberController.php+1
변경 전PopupManageCallNumberController.php
{
public function index()
{
 
$this->callMenu('crm', 'messageConfig', 'manageCallNumber');
$this->setMenuCode('crm', 'messageConfig', 'manageCallNumber');
 
 
변경 후PopupManageCallNumberController.php
{
public function index()
{
$this->setNcdsSubBodyClass();
$this->callMenu('crm', 'messageConfig', 'manageCallNumber');
$this->setMenuCode('crm', 'messageConfig', 'manageCallNumber');
 
 
Controller/Admin/Crm/PopupSelectMembersController.php+1
변경 전PopupSelectMembersController.php
{
public function index()
{
 
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
/** @var Request $request */
 
변경 후PopupSelectMembersController.php
{
public function index()
{
$this->setNcdsSubBodyClass();
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
/** @var Request $request */
 
정기배송 결제카드 등록 후 표시 개선.
data/skin/front/moment/order/regular_order.html+1-1
변경 전regular_order.html
}
});
}
 
 
// 동적으로 카드 라디오버튼을 추가
function showCardInfo(cardName, cardNo, encryptCardNo) {
showCardInfo(cardName, cardMaskNo, encryptCardNo);
document.getElementById("cardRegistLayer").remove();
}
<!--{ / }-->
</script>
{ # footer }
 
변경 후regular_order.html
}
});
}
<!--{ / }-->
 
// 동적으로 카드 라디오버튼을 추가
function showCardInfo(cardName, cardNo, encryptCardNo) {
showCardInfo(cardName, cardMaskNo, encryptCardNo);
document.getElementById("cardRegistLayer").remove();
}
 
</script>
{ # footer }
 
data/skin/front/mygreek/order/regular_order.html+1-1
변경 전regular_order.html
}
});
}
 
 
// 동적으로 카드 라디오버튼을 추가
function showCardInfo(cardName, cardNo, encryptCardNo) {
showCardInfo(cardName, cardMaskNo, encryptCardNo);
document.getElementById("cardRegistLayer").remove();
}
<!--{ / }-->
</script>
{ # footer }
 
변경 후regular_order.html
}
});
}
<!--{ / }-->
 
// 동적으로 카드 라디오버튼을 추가
function showCardInfo(cardName, cardNo, encryptCardNo) {
showCardInfo(cardName, cardMaskNo, encryptCardNo);
document.getElementById("cardRegistLayer").remove();
}
 
</script>
{ # footer }
 
data/skin/front/story_g/order/regular_order.html+1-1
변경 전regular_order.html
}
});
}
 
 
// 동적으로 카드 라디오버튼을 추가
function showCardInfo(cardName, cardNo, encryptCardNo) {
showCardInfo(cardName, cardMaskNo, encryptCardNo);
document.getElementById("cardRegistLayer").remove();
}
<!--{ / }-->
</script>
{ # footer }
 
변경 후regular_order.html
}
});
}
<!--{ / }-->
 
// 동적으로 카드 라디오버튼을 추가
function showCardInfo(cardName, cardNo, encryptCardNo) {
showCardInfo(cardName, cardMaskNo, encryptCardNo);
document.getElementById("cardRegistLayer").remove();
}
 
</script>
{ # footer }
 
상품 수정 시 이미지 보존 처리 개선
Admin/goods/goods_register_option_stock.php+1
변경 전goods_register_option_stock.php
 
$imageInput = '<input type="hidden" name="imageDB[sno][]" value="' . $iVal['sno'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageSize][]" value="' . $iVal['imageSize'] . '" /> ';
 
$imageInput .= '<input type="hidden" name="imageDB[imageNo][]" value="' . $iVal['imageNo'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageKind][]" value="' . $iVal['imageKind'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageName][]" value="' . $iVal['imageName'] . '" /> ';
 
변경 후goods_register_option_stock.php
 
$imageInput = '<input type="hidden" name="imageDB[sno][]" value="' . $iVal['sno'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageSize][]" value="' . $iVal['imageSize'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageHeightSize][]" value="' . $iVal['imageHeightSize'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageNo][]" value="' . $iVal['imageNo'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageKind][]" value="' . $iVal['imageKind'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageName][]" value="' . $iVal['imageName'] . '" /> ';
 
Admin/goods/goods_register.php+1
변경 전goods_register.php
 
$imageInput = '<input type="hidden" name="imageDB[sno][]" value="' . $iVal['sno'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageSize][]" value="' . $iVal['imageSize'] . '" /> ';
 
$imageInput .= '<input type="hidden" name="imageDB[imageNo][]" value="' . $iVal['imageNo'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageKind][]" value="' . $iVal['imageKind'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageName][]" value="' . $iVal['imageName'] . '" /> ';
 
변경 후goods_register.php
 
$imageInput = '<input type="hidden" name="imageDB[sno][]" value="' . $iVal['sno'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageSize][]" value="' . $iVal['imageSize'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageHeightSize][]" value="' . $iVal['imageHeightSize'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageNo][]" value="' . $iVal['imageNo'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageKind][]" value="' . $iVal['imageKind'] . '" /> ';
$imageInput .= '<input type="hidden" name="imageDB[imageName][]" value="' . $iVal['imageName'] . '" /> ';
 
상품 검색 보안 강화.
Component/Goods/Goods.php+14
변경 전Goods.php
$this->search['reSearchKey'] = gd_isset(gd_array_values($getValue['reSearchKey']));
$this->search['reSearch'] = gd_isset($getValue['reSearch']);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$this->search['cateGoods'] = ArrayUtils::last(gd_isset($getValue['cateGoods']));
$this->search['brand'] = gd_isset($getValue['brand']);
$this->search['quickBrandGoods'] = gd_isset($getValue['quickBrandGoods']);
 
변경 후Goods.php
$this->search['reSearchKey'] = gd_isset(gd_array_values($getValue['reSearchKey']));
$this->search['reSearch'] = gd_isset($getValue['reSearch']);
 
// 검색 필드키 화이트리스트 검증 (SQL 식별자 인젝션 방어 - 허용된 검색키만 컬럼명으로 결합)
$allowedSearchKey = array_merge(array_keys($this->search['combineSearch']), ['brandNm']);
if ($this->search['key'] && !in_array($this->search['key'], $allowedSearchKey, true)) {
$this->search['key'] = 'all';
}
if (is_array($this->search['reSearchKey'])) {
foreach ($this->search['reSearchKey'] as $reSearchKeyIdx => $reSearchKeyVal) {
if (!in_array($reSearchKeyVal, $allowedSearchKey, true)) {
unset($this->search['reSearchKey'][$reSearchKeyIdx]);
unset($this->search['reSearchKeyword'][$reSearchKeyIdx]);
}
}
}
 
$this->search['cateGoods'] = ArrayUtils::last(gd_isset($getValue['cateGoods']));
$this->search['brand'] = gd_isset($getValue['brand']);
$this->search['quickBrandGoods'] = gd_isset($getValue['quickBrandGoods']);
 
사업자 회원 비활성화 시 회원정보 수정 프로세스 개선
Component/Member/MyPage.php+1-1
변경 전MyPage.php
}
 
// 사업자번호 중복여부 체크
if ($requestParams['memberFl'] == 'business' && ($require['busiNo'] || !empty($requestParams['busiNo']))) {
$newBusiNo = gd_remove_special_char($requestParams['busiNo']);
$memberData = $this->myInformation();
$oldBusiNo = $memberData['busiNo'];
 
변경 후MyPage.php
}
 
// 사업자번호 중복여부 체크
if (!empty($joinItemPolicy['businessinfo']) && $requestParams['memberFl'] == 'business' && ($require['busiNo'] || !empty($requestParams['busiNo']))) {
$newBusiNo = gd_remove_special_char($requestParams['busiNo']);
$memberData = $this->myInformation();
$oldBusiNo = $memberData['busiNo'];
 
Controller/Front/Mypage/MyPagePsController.php+2-1
변경 전MyPagePsController.php
$requestParams = array_merge($request->post()->xss()->all(), $request->files()->toArray());
 
// CSRF 토큰 체크
if (!Token::check('myPageToken', $requestParams)) {
 
throw new Exception(__('잘못된 경로로 접근하셨습니다.'));
}
 
 
변경 후MyPagePsController.php
$requestParams = array_merge($request->post()->xss()->all(), $request->files()->toArray());
 
// CSRF 토큰 체크
// 숨김 iframe 으로 제출되어 검증 실패 시 폼이 다시 그려지지 않으므로, 토큰을 소거하지 않고 재제출을 허용한다.
if (!Token::check('myPageToken', $requestParams, false, null, true)) {
throw new Exception(__('잘못된 경로로 접근하셨습니다.'));
}
 
 
data/skin/front/moment/js/gd_member2.js+2
변경 전gd_member2.js
var $button = $(parent.document.getElementsByClassName('js_btn_join')[0]);
$button.html('<em>' + $button.val() + '</em>');
$button.data('action', '');
 
 
});
$this.data('action', 'click');
$this.html('<em>' + __('진행 중') + '</em>');
 
변경 후gd_member2.js
var $button = $(parent.document.getElementsByClassName('js_btn_join')[0]);
$button.html('<em>' + $button.val() + '</em>');
$button.data('action', '');
// validation_submit 에서 잠근 버튼을 함께 풀어야 검증 실패 후 재제출이 가능하다
$button.prop('disabled', false);
});
$this.data('action', 'click');
$this.html('<em>' + __('진행 중') + '</em>');
 
data/skin/front/mygreek/js/gd_member2.js+2
변경 전gd_member2.js
var $button = $(parent.document.getElementsByClassName('js_btn_join')[0]);
$button.html('<em>' + $button.val() + '</em>');
$button.data('action', '');
 
 
});
$this.data('action', 'click');
$this.html('<em>' + __('진행 중') + '</em>');
 
변경 후gd_member2.js
var $button = $(parent.document.getElementsByClassName('js_btn_join')[0]);
$button.html('<em>' + $button.val() + '</em>');
$button.data('action', '');
// validation_submit 에서 잠근 버튼을 함께 풀어야 검증 실패 후 재제출이 가능하다
$button.prop('disabled', false);
});
$this.data('action', 'click');
$this.html('<em>' + __('진행 중') + '</em>');
 
data/skin/front/story_g/js/gd_member2.js+2
변경 전gd_member2.js
var $button = $(parent.document.getElementsByClassName('btn-join')[0]);
$button.html('<em>' + $button.val() + '</em>');
$button.data('action', '');
 
 
});
$this.data('action', 'click');
$this.html('<em>' + __('진행 중') + '</em>');
 
변경 후gd_member2.js
var $button = $(parent.document.getElementsByClassName('btn-join')[0]);
$button.html('<em>' + $button.val() + '</em>');
$button.data('action', '');
// validation_submit 에서 잠근 버튼을 함께 풀어야 검증 실패 후 재제출이 가능하다
$button.prop('disabled', false);
});
$this.data('action', 'click');
$this.html('<em>' + __('진행 중') + '</em>');
 
data/skin/mobile/moment/js/gd_member2.js+2
변경 전gd_member2.js
var $button = $(parent.document.getElementsByClassName('join_ok_btn')[0]);
$button.html('<em>' + $button.val() + '</em>');
$button.data('action', '');
 
 
});
$this.data('action', 'click');
$this.html('<em>' + __('진행 중') + '</em>');
 
변경 후gd_member2.js
var $button = $(parent.document.getElementsByClassName('join_ok_btn')[0]);
$button.html('<em>' + $button.val() + '</em>');
$button.data('action', '');
// validation_submit 에서 잠근 버튼을 함께 풀어야 검증 실패 후 재제출이 가능하다
$button.prop('disabled', false);
});
$this.data('action', 'click');
$this.html('<em>' + __('진행 중') + '</em>');
 
data/skin/mobile/mygreek/js/gd_member2.js+2
변경 전gd_member2.js
var $button = $(parent.document.getElementsByClassName('join_ok_btn')[0]);
$button.html('<em>' + $button.val() + '</em>');
$button.data('action', '');
 
 
});
$this.data('action', 'click');
$this.html('<em>' + __('진행 중') + '</em>');
 
변경 후gd_member2.js
var $button = $(parent.document.getElementsByClassName('join_ok_btn')[0]);
$button.html('<em>' + $button.val() + '</em>');
$button.data('action', '');
// validation_submit 에서 잠근 버튼을 함께 풀어야 검증 실패 후 재제출이 가능하다
$button.prop('disabled', false);
});
$this.data('action', 'click');
$this.html('<em>' + __('진행 중') + '</em>');
 
data/skin/mobile/story_g/js/gd_member2.js+2
변경 전gd_member2.js
var $button = $(parent.document.getElementsByClassName('btn-join')[0]);
$button.html('<em>' + $button.val() + '</em>');
$button.data('action', '');
 
 
});
$this.data('action', 'click');
$this.html('<em>' + __('진행 중') + '</em>');
 
변경 후gd_member2.js
var $button = $(parent.document.getElementsByClassName('btn-join')[0]);
$button.html('<em>' + $button.val() + '</em>');
$button.data('action', '');
// validation_submit 에서 잠근 버튼을 함께 풀어야 검증 실패 후 재제출이 가능하다
$button.prop('disabled', false);
});
$this.data('action', 'click');
$this.html('<em>' + __('진행 중') + '</em>');
 
카카오 간편 로그인 설정 안정성 개선
Admin/member/kakao_login_config.php+2-1
변경 전kakao_login_config.php
* @var array $checked 카카오 로그인 설정 여부 'checked="checked"'
* @var array $data KakaoLoginPolicy 설정 정보
* @var bool $installedKakaoSync 카카오 싱크 앱 설치 여부
 
* @var bool $useKakaoSync 카카오 싱크 사용 여부
* @var bool $useKakaoLogin 카카오 로그인 사용 여부
* @var bool $isAvailableOnlyKakaoSync 카카오 싱크 배포일 이후 상점 true, 이전 상점 false
?>
<link type="text/css" href="<?= PATH_ADMIN_GD_SHARE ?>script/slider/slick/slick.css" rel="stylesheet"/>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/slider/slick/slick.js"></script>
<?php if ($installedKakaoSync && $isAvailableOnlyKakaoSync === false) : ?>
<div class="page-header js-affix">
<h3><?php echo end($naviMenu->location); ?></h3>
</div>
 
변경 후kakao_login_config.php
* @var array $checked 카카오 로그인 설정 여부 'checked="checked"'
* @var array $data KakaoLoginPolicy 설정 정보
* @var bool $installedKakaoSync 카카오 싱크 앱 설치 여부
* @var bool $installedKakaoLogin 카카오 로그인 설치 여부
* @var bool $useKakaoSync 카카오 싱크 사용 여부
* @var bool $useKakaoLogin 카카오 로그인 사용 여부
* @var bool $isAvailableOnlyKakaoSync 카카오 싱크 배포일 이후 상점 true, 이전 상점 false
?>
<link type="text/css" href="<?= PATH_ADMIN_GD_SHARE ?>script/slider/slick/slick.css" rel="stylesheet"/>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/slider/slick/slick.js"></script>
<?php if ($installedKakaoSync || !$installedKakaoLogin) : ?>
<div class="page-header js-affix">
<h3><?php echo end($naviMenu->location); ?></h3>
</div>
 
Controller/Admin/Member/KakaoLoginConfigController.php+4
변경 전KakaoLoginConfigController.php
$kakaoLoginPolicy = \App::getInstance(KakaoLoginPolicy::class);
$policy = $kakaoLoginPolicy->getPolicy();
 
 
 
 
gd_isset($policy['useFl'], 'n');
gd_isset($policy['simpleLoginFl'],'y');
gd_isset($policy['baseInfo'],'y');
$this->setData('checked', $checked);
$this->setData('data', $policy);
$this->setData('installedKakaoSync', $kakaoLoginPolicy->installedKakaoSync());
 
$this->setData('useKakaoSync', $kakaoLoginPolicy->useKakaoSync());
$this->setData('useKakaoLogin', $kakaoLoginPolicy->useKakaoLogin());
$this->setData('isAvailableOnlyKakaoSync', $isAvailableOnlyKakaoSync);
 
변경 후KakaoLoginConfigController.php
$kakaoLoginPolicy = \App::getInstance(KakaoLoginPolicy::class);
$policy = $kakaoLoginPolicy->getPolicy();
 
//카카오싱크 미설치 상태에서 Policy가 비어있지 않으면, 카카오 로그인 세팅된 것으로 간주
$isInstalledKakaoLogin = !$kakaoLoginPolicy->installedKakaoSync() && !empty($policy);
 
gd_isset($policy['useFl'], 'n');
gd_isset($policy['simpleLoginFl'],'y');
gd_isset($policy['baseInfo'],'y');
$this->setData('checked', $checked);
$this->setData('data', $policy);
$this->setData('installedKakaoSync', $kakaoLoginPolicy->installedKakaoSync());
$this->setData('installedKakaoLogin', $isInstalledKakaoLogin);
$this->setData('useKakaoSync', $kakaoLoginPolicy->useKakaoSync());
$this->setData('useKakaoLogin', $kakaoLoginPolicy->useKakaoLogin());
$this->setData('isAvailableOnlyKakaoSync', $isAvailableOnlyKakaoSync);
 
메타 전환 이벤트 중복집계 개선
Controller/Front/Order/OrderEndController.php+4-2
변경 전OrderEndController.php
use Bundle\Service\Order\OrderEndService;
use Component\CartRemind\CartRemind;
use Component\Mall\Mall;
 
use Message;
use Session;
use Request;
}
}
 
//facebook Dynamic Ads 외부 스크립트 적용
$facebookAd = \App::Load('\\Component\\Marketing\\FacebookAd');
$currency = gd_isset(Mall::getSession('currencyConfig')['code'], 'KRW');
$fbConfig = $facebookAd->getConfig();
$fbConfigExtension = $facebookAd->getExtensionConfig();
if(($fbConfig['fbUseFl'] == 'y' && $fbConfig['orderEndScriptFl'] == 'y') || $fbConfigExtension['fbUseFl'] == 'y') {
$goodsInfo = $order->getOrderGoodsData($orderInfo['orderNo']);
// 상품번호 추출
$goodsNo = [];
$goodsNo[] = $val2['goodsNo'];
}
}
 
$fbScript = $facebookAd->getFbOrderEndScript($goodsNo, $orderInfo['settlePrice'], $currency);
$this->setData('fbOrderEndScript', $fbScript['script']);
 
 
변경 후OrderEndController.php
use Bundle\Service\Order\OrderEndService;
use Component\CartRemind\CartRemind;
use Component\Mall\Mall;
use Origin\Enum\Order\OrderStatusGroup;
use Message;
use Session;
use Request;
}
}
 
//facebook Dynamic Ads 외부 스크립트 적용(결제가 정상적으로 이루어지지 않은경우 제외 출력)
$facebookAd = \App::Load('\\Component\\Marketing\\FacebookAd');
$currency = gd_isset(Mall::getSession('currencyConfig')['code'], 'KRW');
$fbConfig = $facebookAd->getConfig();
$fbConfigExtension = $facebookAd->getExtensionConfig();
if($orderInfo['orderStatus'] != OrderStatusGroup::FAIL->value && (($fbConfig['fbUseFl'] == 'y' && $fbConfig['orderEndScriptFl'] == 'y') || $fbConfigExtension['fbUseFl'] == 'y')) {
$goodsInfo = $order->getOrderGoodsData($orderInfo['orderNo']);
// 상품번호 추출
$goodsNo = [];
$goodsNo[] = $val2['goodsNo'];
}
}
$facebookAd->orderNo = $orderInfo['orderNo'];
$fbScript = $facebookAd->getFbOrderEndScript($goodsNo, $orderInfo['settlePrice'], $currency);
$this->setData('fbOrderEndScript', $fbScript['script']);
 
 
Controller/Mobile/Order/OrderEndController.php+4-2
변경 전OrderEndController.php
use Component\Mall\Mall;
use DateTime;
use Framework\Debug\Exception\AlertRedirectException;
 
use Message;
use Session;
use Request;
}
}
 
//facebook Dynamic Ads 외부 스크립트 적용
$facebookAd = \App::Load('\\Component\\Marketing\\FacebookAd');
$currency = gd_isset(Mall::getSession('currencyConfig')['code'], 'KRW');
$fbConfig = $facebookAd->getConfig();
$fbConfigExtension = $facebookAd->getExtensionConfig();
$goodsInfo = $order->getOrderGoodsData($orderInfo['orderNo']);
if(($fbConfig['fbUseFl'] == 'y' && $fbConfig['orderEndScriptFl'] == 'y') || $fbConfigExtension['fbUseFl'] == 'y') {
// 상품번호 추출
$goodsNo = [];
foreach ($goodsInfo as $key => $val){
$goodsNo[] = $val2['goodsNo'];
}
}
 
$fbScript = $facebookAd->getFbOrderEndScript($goodsNo, $orderInfo['settlePrice'], $currency);
$this->setData('fbOrderEndScript', $fbScript['script']);
 
 
변경 후OrderEndController.php
use Component\Mall\Mall;
use DateTime;
use Framework\Debug\Exception\AlertRedirectException;
use Origin\Enum\Order\OrderStatusGroup;
use Message;
use Session;
use Request;
}
}
 
//facebook Dynamic Ads 외부 스크립트 적용(결제가 정상적으로 이루어지지 않은경우 제외 출력)
$facebookAd = \App::Load('\\Component\\Marketing\\FacebookAd');
$currency = gd_isset(Mall::getSession('currencyConfig')['code'], 'KRW');
$fbConfig = $facebookAd->getConfig();
$fbConfigExtension = $facebookAd->getExtensionConfig();
$goodsInfo = $order->getOrderGoodsData($orderInfo['orderNo']);
if($orderInfo['orderStatus'] != OrderStatusGroup::FAIL->value && (($fbConfig['fbUseFl'] == 'y' && $fbConfig['orderEndScriptFl'] == 'y') || $fbConfigExtension['fbUseFl'] == 'y')) {
// 상품번호 추출
$goodsNo = [];
foreach ($goodsInfo as $key => $val){
$goodsNo[] = $val2['goodsNo'];
}
}
$facebookAd->orderNo = $orderInfo['orderNo'];
$fbScript = $facebookAd->getFbOrderEndScript($goodsNo, $orderInfo['settlePrice'], $currency);
$this->setData('fbOrderEndScript', $fbScript['script']);
 
 
관리자-유저 세션 격리 강화
Component/Member/MemberSnsDAO.php+1
변경 전MemberSnsDAO.php
$this->db->strField .= ', m.email, m.adultConfirmDt, m.adultFl, m.loginCnt, m.changePasswordDt, m.guidePasswordDt';
$this->db->strField .= ', m.modDt AS mModDt, m.regDt AS mRegDt, ms.snsJoinFl, ms.snsTypeFl, ms.connectFl, ms.accessToken';
$this->db->strField .= ', mg.groupNm, mg.groupSort, mh.sno AS hackOutSno';
 
$this->db->strJoin = ' LEFT JOIN ' . DB_MEMBER . ' AS m ON ms.memNo = m.memNo';
$this->db->strJoin .= ' LEFT JOIN ' . DB_MEMBER_GROUP . ' AS mg ON mg.sno = m.groupSno';
$this->db->strJoin .= ' LEFT JOIN ' . DB_MEMBER_HACKOUT . ' AS mh ON mh.memNo = m.memNo';
 
변경 후MemberSnsDAO.php
$this->db->strField .= ', m.email, m.adultConfirmDt, m.adultFl, m.loginCnt, m.changePasswordDt, m.guidePasswordDt';
$this->db->strField .= ', m.modDt AS mModDt, m.regDt AS mRegDt, ms.snsJoinFl, ms.snsTypeFl, ms.connectFl, ms.accessToken';
$this->db->strField .= ', mg.groupNm, mg.groupSort, mh.sno AS hackOutSno';
$this->db->strField .= ', m.mallSno'; // 세션 저장 시 기준몰 판정에 사용 (누락되면 세션 핸들러가 회원을 재조회하며 세션ID를 재발급한다)
$this->db->strJoin = ' LEFT JOIN ' . DB_MEMBER . ' AS m ON ms.memNo = m.memNo';
$this->db->strJoin .= ' LEFT JOIN ' . DB_MEMBER_GROUP . ' AS mg ON mg.sno = m.groupSno';
$this->db->strJoin .= ' LEFT JOIN ' . DB_MEMBER_HACKOUT . ' AS mh ON mh.memNo = m.memNo';
 
모바일 메시지 발송 엑셀 업로드 시 name 치환코드 적용
Admin/crm/layer_preview_mobile_send.php+1
변경 전layer_preview_mobile_send.php
<script>
function resolveReplaceCodeForPreview(content, isMain = true) {
return MobileMessageReplaceCode.resolvePreviewContent(content, {
 
shortLinkKeys: isMain ? window.shortLinkKeys : window.shortLinkAlternativeKeys,
});
}
 
변경 후layer_preview_mobile_send.php
<script>
function resolveReplaceCodeForPreview(content, isMain = true) {
return MobileMessageReplaceCode.resolvePreviewContent(content, {
includeExcelUpload: isExcelUploadSmsContext(),
shortLinkKeys: isMain ? window.shortLinkKeys : window.shortLinkAlternativeKeys,
});
}
 
Admin/crm/mobile_send/layer_send_method_setting.php+1
변경 전layer_send_method_setting.php
 
return MobileMessageReplaceCode.resolvePreviewContent(content, {
includeRecipe: true,
 
shortLinkKeys: isMain ? window.shortLinkKeys : window.shortLinkAlternativeKeys,
});
}
 
변경 후layer_send_method_setting.php
 
return MobileMessageReplaceCode.resolvePreviewContent(content, {
includeRecipe: true,
includeExcelUpload: isExcelUploadSmsContext(),
shortLinkKeys: isMain ? window.shortLinkKeys : window.shortLinkAlternativeKeys,
});
}
 
Component/Excel/ExcelSms.php+1-1
변경 전ExcelSms.php
'text' => '이름',
'comment' => '',
'sample' => '홍길동',
'desc' => '불필요한 경우 이름 필드 열을 삭제하여 주시기 바랍니다.<br/>(삭제하지 않고 공란으로 입력 시 해당 번호는 업로드할 수 없음)<br/>- 치환코드 {name}으로 발송 내용에 이름 입력 가능',
],
[
'dbKey' => 'cellPhone',
 
변경 후ExcelSms.php
'text' => '이름',
'comment' => '',
'sample' => '홍길동',
'desc' => '불필요한 경우 이름 필드 열을 삭제하여 주시기 바랍니다.<br/>(삭제하지 않고 공란으로 입력 시 해당 번호는 업로드할 수 없음)<br/>- 치환코드 {name}으로 발송 내용에 이름 입력 가능<br/>※단, SMS/LMS 전용이며 친구톡에서는 치환되지 않고 문자열 그대로 표시됩니다.',
],
[
'dbKey' => 'cellPhone',
 
회원 등급 평가 스케줄러 개선
Component/Coupon/CouponAdmin.php+3-2
변경 전CouponAdmin.php
}
$arrData = [];
$arrData['couponNo'] = $getValue['couponNo'];
$arrData['couponSaveAdminId'] = Session::get('manager.managerId');
$arrData['managerNo'] = Session::get('manager.sno');
$arrData['memberCouponStartDate'] = $this->getMemberCouponStartDate($getValue['couponNo']);
$arrData['memberCouponEndDate'] = $this->getMemberCouponEndDate($getValue['couponNo']);
// 저장
$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($getValue['couponNo']);
}
 
 
return 'T';
exit;
}
 
변경 후CouponAdmin.php
}
$arrData = [];
$arrData['couponNo'] = $getValue['couponNo'];
$arrData['couponSaveAdminId'] = gd_isset(Request::post()->get('couponSaveAdminId'), Session::get('manager.managerId'));
$arrData['managerNo'] = Session::get('manager.sno');
$arrData['memberCouponStartDate'] = $this->getMemberCouponStartDate($getValue['couponNo']);
$arrData['memberCouponEndDate'] = $this->getMemberCouponEndDate($getValue['couponNo']);
// 저장
$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');
 
}
// 발급 카운트 일괄 증가 (루프 내 N회 UPDATE → 1회 UPDATE)
$this->setCouponMemberSaveCountAll($getValue['couponNo'], count($memNoArr));
return 'T';
exit;
}
 
Component/Database/MemberTableField.php+5-1
변경 전MemberTableField.php
'typ' => 's',
'def' => null,
]; // 평생회원 전환일
 
 
 
 
 
return $arrField;
}
 
 
변경 후MemberTableField.php
'typ' => 's',
'def' => null,
]; // 평생회원 전환일
$arrField[] = [
'val' => 'autoGroupAppraisalDt',
'typ' => 's',
'def' => null,
]; // 회원등급평가 실행일
return $arrField;
}
 
 
Component/Member/Group/AppraisalMailSender.php+17-4
변경 전AppraisalMailSender.php
use Component\Mail\MailUtil;
 
use Component\Validator\Validator;
/**
public function send()
{
 
 
foreach ($this->receiver as $index => $item) {
$groupVo = new \Component\Member\Group\GroupDomain($item[1]);
if (!($groupVo instanceof GroupDomain)) {
\App::getInstance('logger')->error('그룹정보 객체 오류', $item);
continue;
 
 
 
 
 
 
 
 
 
}
 
MailUtil::sendMemberGroupChangeMail($item[0], $groupVo);
}
}
 
변경 후AppraisalMailSender.php
use Component\Mail\MailUtil;
use Component\Member\Group\GroupDomain;
use Component\Validator\Validator;
/**
public function send()
{
// 같은 등급 GroupDomain을 재사용해 setGroupDcData()의 회원 수만큼 반복 DB조회(N+1)를 제거한다.
$groupVoCache = [];
foreach ($this->receiver as $index => $item) {
$groupSno = isset($item[1]['sno']) ? $item[1]['sno'] : null;
if ($groupSno !== null && isset($groupVoCache[$groupSno])) {
$groupVo = $groupVoCache[$groupSno];
} else {
$groupVo = new GroupDomain($item[1]);
if (!($groupVo instanceof GroupDomain)) {
\App::getInstance('logger')->error('그룹정보 객체 오류', $item);
continue;
}
if ($groupSno !== null) {
$groupVoCache[$groupSno] = $groupVo;
}
}
MailUtil::sendMemberGroupChangeMail($item[0], $groupVo);
}
}
 
Component/Member/Group/Appraisal.php+578-114
변경 전Appraisal.php
namespace Bundle\Component\Member\Group;
use App;
 
use Component\Database\DBTableField;
use Component\Member\Member;
use Component\Sms\Code;
use Framework\Utility\ComponentUtils;
use Framework\Utility\DateTimeUtils;
use Framework\Utility\NumberUtils;
 
 
use Session;
/**
protected $search;
/** @var AppraisalMailSender 등급평가 메일발송 */
protected $mailSender;
/** @var array 등급평가 완료된 회원번호 */
protected $completeMemNo = [];
/** @var string 등급평가일시 */
protected $appraisalDateTime;
/** @var bool 현재 평가 등급 기준치 만족 여부 */
/** @var array 현재 평가대상 회원 */
protected $currentMembers;
/** @var array 등급평가 제외 설정된 등급 sno 배열 */
protected $apprExclusionOfRating;
/** @var array 그룹 sno별 등급 */
protected $groupSort;
 
 
/** @var array 그룹 관리 설정 */
public $policy;
/** @var array 기본 설정 */
public $basicInfo;
 
 
 
 
 
 
 
 
 
 
 
 
/**
* Appraisal constructor.
*
public function __construct(AppraisalSearch $search = null)
{
parent::__construct();
 
 
 
$this->setSearch($search);
$this->groupConfig();
$this->validateAppraisalGroupConfig();
$this->policy = ComponentUtils::getPolicy('member.group');
$this->basicInfo = gd_policy('basic.info');
 
ini_set('memory_limit', '-1');
}
}
if ($data['apprSystem'] == 'point' && $data['appraisalPointOrderPriceFl'] != 'y' && $data['appraisalPointOrderRepeatFl'] != 'y' && $data['appraisalPointReviewRepeatFl'] != 'y' && $data['appraisalPointLoginRepeatFl'] != 'y') {
$logger = \App::getInstance('logger');
$logger->warning('Not set appraisal performance point');
throw new Exception(__('실적 점수제 평가 설정 값이 없습니다.'));
}
$this->appraisalGroupConfig = new SimpleStorage($data);
$validation->validateOverlapStandard();
$this->groupSort[$group['sno']] = $group['groupSort']; // 그룹 key: sno, value: sort 배열 생성
}
 
 
 
 
 
 
// 회원등급 평가시 제외인 등급 그룹 배열 생성
$exclusionGroups = $dao->selectGroupExclusion();
foreach ($exclusionGroups as $key){
$this->groupConfig = $groups;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* setGroupNames
* @return void
*/
public function appraisalGroupBySearch()
{
$logger = \App::getInstance('logger');
$logger->notice('Start group appraisal.');
$total = $this->countTotalMember();
$limit = $this->search->getAppraisalLimit();
$this->countByTotalMember = $this->appraisalCountByTotalMember($total, $limit);
$logger->notice('총 회원[' . $total . '], 1회 평가당 제한 인원수[' . $limit . '], 평가 실행 횟수[' . $this->countByTotalMember . ']');
$this->completeMemNo = [];
$this->appraisalDateTime = DateTimeUtils::dateFormat('Y-m-d H:i:s', 'now');
 
 
 
for ($offset = 0; $offset < $this->countByTotalMember; $offset++) {
$this->appraisalGroupByOffset($offset);
// 평가 대상으로 검색된 회원 초기화
$this->currentMembers = null;
}
$logger->notice('End group appraisal.');
}
/**
*/
public function appraisalGroupByOffset($offset)
{
$logger = \App::getInstance('logger');
$db = \App::getInstance('DB');
$this->search->setAppraisalOffset($offset);
$logger->info(sprintf('Start appraisal group set offset %s', $offset));
 
 
 
foreach ($this->groupConfig as $index => $item) {
$db->ping();
$logger->info(sprintf('Start group name is %s, group sno is %s, group sort is %s', $item['groupNm'], $item['sno'], $item['groupSort']));
$this->group = new SimpleStorage($item);
if ($this->currentMembers === null) {
$this->currentMembers = $this->search->search($this->group);
}
if ($this->search->hasAppraisalMember($this->currentMembers)) {
$logger->info(sprintf('has appraisal member count is %d', gd_count($this->currentMembers)));
$this->updateGroupWithAddMailReceiver($this->currentMembers);
}
$logger->info(sprintf('End group name is %s, group sno is %s, group sort is %s', $item['groupNm'], $item['sno'], $item['groupSort']));
}
if ($this->policy['downwardAdjustment'] == 'y') {
$this->isDefaultGroupMember = true;
$logger->info(sprintf('Start group name is %s, group sno is %s, group sort is %s', $this->defaultGroupConfig['groupNm'], $this->defaultGroupConfig['sno'], $this->defaultGroupConfig['groupSort']));
$this->group = new SimpleStorage($this->defaultGroupConfig);
$this->search->setAppraisalDateTime($this->appraisalDateTime);
$this->currentMembers = $this->search->searchDefaultGroup($this->appraisalDateTime);
if ($this->search->hasAppraisalMember($this->currentMembers)) {
$db->ping();
$logger->info(sprintf('has appraisal member count is %d', gd_count($this->currentMembers)));
$this->updateGroupWithAddMailReceiver($this->currentMembers);
} else {
$logger->info('Not found appraisal members by default group', $this->defaultGroupConfig);
}
}
$logger->info(sprintf('End group name is %s, group sno is %s, group sort is %s', $this->defaultGroupConfig['groupNm'], $this->defaultGroupConfig['sno'], $this->defaultGroupConfig['groupSort']));
$this->isDefaultGroupMember = false;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* appraisalMailSend
* @return void
*/
public function updateGroupWithAddMailReceiver(array $members)
{
$logger = \App::getInstance('logger');
$session = \App::getInstance('session');
$db = \App::getInstance('DB');
 
 
try {
$groupSno = $this->group->get('sno');
$settleGb = $this->group->get('settleGb');
$smsSender = \App::load(\Component\Sms\SmsSender::class);
$smsSender->validPassword(\Request::post()->get('password'));
}
$count = 500;
 
 
foreach ($members as $index => $item) {
$count--;
if ($count == 0) {
$db->ping();
$count = 500;
}
$session->set(Member::SESSION_MODIFY_MEMBER_INFO, $item);
$storage = new SimpleStorage($item);
$storage->set('settleGb', $settleGb);
$memNo = $storage->get('memNo');
array_push($this->completeMemNo, $memNo);
// 현재 등급이 '등급평가시 제외' 설정이 아닐 때
if(gd_array_key_exists($item['groupSno'], $this->apprExclusionOfRating) == false) {
$this->checkGroupByAppraisalSystem($item);
$beforeGroupSort = $this->groupSort[$item['groupSno']];
$afterGroupSort = $this->groupSort[$groupSno];
 
 
 
 
 
//하향펑가를 설정하지 않았거나, 하향평가를 사용하거나, 하향평가를 사용하지 않고 변경 등급이 기존 등급보다 높거나 같을 때 만 업데이트
if (empty($this->policy['downwardAdjustment']) || $this->policy['downwardAdjustment'] == 'y' || ($this->policy['downwardAdjustment'] == 'n' && $beforeGroupSort < $afterGroupSort)) {
if ($this->isCurrentGroupMember || $this->isDefaultGroupMember) {
$logger->info('Group change target. sno[' . $this->group->get('sno', 0) . '], name[' . $this->group->get('', '-') . ']', $item);
 
$this->updateGroup($memNo);
$this->writeHistory($memNo);
$this->addMailReceiver($storage, $this->group);
$this->addSmsReceiver($storage, $this->group);
unset($members[$index]);
}
}
}
}
 
 
 
 
 
 
if ($this->isTran) {
$db->commit();
$this->currentMembers = $members;
}
 
 
 
 
 
} catch (Exception $e) {
$logger->error(__METHOD__ . ', ' . $e->getFile() . '[' . $e->getLine() . '], ' . $e->getMessage(), $e->getTrace());
$db->rollback();
 
 
 
 
 
}
}
/**
* checkGroupByAppraisalSystem
 
 
*
* @param array $item 등급평가 대상의 기준치 정보
*
}
}
if ($point > 0) {
$logger = \App::getInstance('logger');
$logger->info(
__METHOD__, [
'memNo:'.$memNo,
$point,
*/
public function checkGroupByFigure($item)
{
$logger = \App::getInstance('logger');
$this->isCurrentGroupMember = true;
$priceUnit = 10000;
*/
public function updateGroupWithInsertHistory(SimpleStorage $storage)
{
 
 
 
 
 
 
$memNo = $storage->get('memNo');
$this->updateGroup($memNo);
$this->writeHistory($memNo);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
/**
}
}
$passwordCheckFl = gd_isset(\Request::post()->get('passwordCheckFl'), 'y');
if($passwordCheckFl != 'n') {
$smsSender = \App::load(\Component\Sms\SmsSender::class);
$smsSender->validPassword(\Request::post()->get('password'));
}
$passwordCheckFl = $passwordCheckFl == 'n' ? false : true;
//쿠폰 지급 하는 코드
if($applyCoupon === true){
$applyCouponList = explode(INT_DIVISION, $this->group->get('groupCoupon'));
foreach($applyCouponList as $value){
$coupon = new \Component\Coupon\CouponAdmin;
\Request::post()->set('couponNo', $value);
\Request::post()->set('couponSaveAdminId', '회원등급 쿠폰 혜택');
\Request::post()->set('managerNo', Session::get('manager.sno'));
\Request::post()->set('memberCouponStartDate', $coupon->getMemberCouponStartDate($value));
\Request::post()->set('memberCouponEndDate', $coupon->getMemberCouponEndDate($value));
\Request::post()->set('memberCouponState', 'y');
$memberArr[] = $memNo;
$coupon->saveMemberCouponSms($memberArr, null, $passwordCheckFl);
unset($memberArr);
}
}
$db = \App::getInstance('DB');
$fields = DBTableField::getFieldTypes('tableMember');
$arrUpdate[] = 'groupSno=?';
$arrBind = [];
$db->bind_param_push($arrBind, $fields['groupSno'], $this->group->get('sno'));
$arrUpdate[] = 'groupModDt=?';
$db->bind_param_push($arrBind, $fields['groupModDt'], $this->appraisalDateTime);
$db->bind_param_push($arrBind, $fields['memNo'], $memNo);
$db->bind_param_push($arrBind, $fields['appFl'], 'y');
$db->bind_param_push($arrBind, $fields['sleepFl'], 'n');
 
 
$db->set_update_db(DB_MEMBER, $arrUpdate, 'memNo = ? AND appFl = ? AND sleepFl = ?', $arrBind);
}
$limit = $this->search->getAppraisalLimit();
// 처리 반복 횟수 계산
$countByTotalMember = $this->appraisalCountByTotalMember($total, $limit);
$logger = \App::getInstance('logger');
$logger->notice('Start apply coupon to all members.');
$logger->notice('총 회원[' . $total . '], 1회 처리당 제한 인원수[' . $limit . '], 처리 실행 횟수[' . $countByTotalMember . ']');
//그룹 설정 가져오기
$group = \App::load('Component\Member\Group\GroupDAO');
$passwordCheckFl = gd_isset(\Request::post()->get('passwordCheckFl'), 'y');
if($passwordCheckFl != 'n') {
// 쿠폰 관리 객체 미리 생성
$coupon = new \Component\Coupon\CouponAdmin;
// 그룹별 쿠폰 정보 캐시
$groupList = [];
 
 
 
 
 
 
 
// 페이징 처리를 위한 반복문
for ($offset = 0; $offset < $countByTotalMember; $offset++) {
$start = $offset * $limit;
$logger->info(sprintf('Start apply coupon with offset %s, limit %s', $start, $limit));
// 페이지별 회원 목록 조회
$memberPageList = $member->getMemberList($arrWhere, null, null, $start, $limit, $field);
if (!empty($memberPageList) && gd_count($memberPageList) > 0) {
foreach ($memberPageList as $key => $value) {
//그룹 정보 가져오기 (캐시 활용)
if (empty($groupList[$value['groupSno']])) {
$groupList[$value['groupSno']] = $group->selectGroup($value['groupSno'])['groupCoupon'];
}
//하나씩 쿠폰 발급 하기
if (!empty($groupList[$value['groupSno']]) && gd_count($groupList[$value['groupSno']]) > 0) {
$applyCouponList = explode(INT_DIVISION, $groupList[$value['groupSno']]);
}
if (!empty($applyCouponList) && gd_count($applyCouponList) > 0) {
\Request::post()->set('couponSaveAdminId', '회원등급 쿠폰 혜택');
\Request::post()->set('memberCouponState', 'y');
\Request::post()->set('managerNo', Session::get('manager.sno'));
$memberArr[] = $value['memNo'];
foreach ($applyCouponList as $applyCoupon) {
if (is_numeric($applyCoupon) && $applyCoupon > 0) {
\Request::post()->set('couponNo', $applyCoupon);
\Request::post()->set('memberCouponStartDate', $coupon->getMemberCouponStartDate($applyCoupon));
\Request::post()->set('memberCouponEndDate', $coupon->getMemberCouponEndDate($applyCoupon));
$coupon->saveMemberCouponSms($memberArr, null, $passwordCheckFl);
}
 
 
 
}
unset($memberArr, $applyCouponList);
}
 
}
}
// 메모리 정리
unset($memberPageList);
}
$logger->notice('End apply coupon to all members.');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
/**
*/
public function addSmsReceiver(SimpleStorage $storage, SimpleStorage $group)
{
$logger = \App::getInstance('logger');
if ($storage->get('cellPhone') != null && $storage->get('memNm') != null) {
if ($storage->get('beforeGroupSno') != $group->get('sno')) {
$smsAuto = \App::load(\Component\Sms\SmsAuto::class);
'memNm' => $storage->get('memNm'),
'memNo' => $storage->get('memNo'),
'groupNm' => $group->get('groupNm'),
'rc_mallNm' => $this->basicInfo['mallNm'],
'shopUrl' => $this->basicInfo['mallDomain'],
]
);
$smsAuto->attach($observer);
} else {
$logger->info(sprintf('The membership level is the same. before[%d], now[%d]', $storage->get('beforeGroupSno'), $group->get('sno')));
}
} else {
$logger->info(sprintf('Not have a cell phone number or member name. memNo[%d]', $storage->get('memNo', 0)));
}
}
{
return $this->appraisalGroupConfig->get('apprSystem') == 'point';
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
변경 후Appraisal.php
namespace Bundle\Component\Member\Group;
use App;
use Component\Coupon\CouponAdmin;
use Component\Database\DBTableField;
use Component\Member\Member;
use Component\Sms\Code;
use Framework\Utility\ComponentUtils;
use Framework\Utility\DateTimeUtils;
use Framework\Utility\NumberUtils;
use Origin\Service\Member\Group\MemberGroupCouponService;
use Origin\Service\Member\Group\MemberGroupService;
use Session;
/**
protected $search;
/** @var AppraisalMailSender 등급평가 메일발송 */
protected $mailSender;
 
 
/** @var string 등급평가일시 */
protected $appraisalDateTime;
/** @var bool 현재 평가 등급 기준치 만족 여부 */
/** @var array 현재 평가대상 회원 */
protected $currentMembers;
/** @var array 등급평가 제외 설정된 등급 sno 배열 */
protected $apprExclusionOfRating = [];
/** @var array 평가 대상 그룹 sno별 등급 */
protected $groupSort;
/** @var array 평가 대상 여부와 무관한 전체 그룹 sno별 등급 */
protected $allGroupSort = [];
/** @var array 그룹 관리 설정 */
public $policy;
/** @var array 기본 설정 */
public $basicInfo;
/** @var string 수행일자 */
public $targetDateTime;
/** @var array es_member 필드 타입 */
protected $memberFields;
/** @var object 로거 */
protected $logger;
/** @var CouponAdmin 쿠폰 어드민 (updateGroup 내 쿠폰 발급 재사용) */
protected $couponAdmin;
/** @var array [couponNo => [memNo, ...]] — updateGroup에서 누적 후 flush 로 쿠폰별 일괄 발급 */
protected $couponSmsBuffer = [];
/** @var array rollback 된 배치의 memNo — 유지회원 stamp 제외 대상 */
protected $rolledBackMemNos = [];
/**
* Appraisal constructor.
*
public function __construct(AppraisalSearch $search = null)
{
parent::__construct();
$this->memberFields = DBTableField::getFieldTypes('tableMember');
$this->logger = App::getInstance('logger');
$this->setSearch($search);
$this->groupConfig();
$this->validateAppraisalGroupConfig();
$this->policy = ComponentUtils::getPolicy('member.group');
$this->basicInfo = gd_policy('basic.info');
$this->couponAdmin = new CouponAdmin();
ini_set('memory_limit', '-1');
}
}
if ($data['apprSystem'] == 'point' && $data['appraisalPointOrderPriceFl'] != 'y' && $data['appraisalPointOrderRepeatFl'] != 'y' && $data['appraisalPointReviewRepeatFl'] != 'y' && $data['appraisalPointLoginRepeatFl'] != 'y') {
$this->logger->warning('Not set appraisal performance point');
 
throw new Exception(__('실적 점수제 평가 설정 값이 없습니다.'));
}
$this->appraisalGroupConfig = new SimpleStorage($data);
$validation->validateOverlapStandard();
$this->groupSort[$group['sno']] = $group['groupSort']; // 그룹 key: sno, value: sort 배열 생성
}
$this->allGroupSort = (array)$this->groupSort;
if ($isAppraisalFigure) {
foreach ($dao->selectAppraisalGroups() as $group) {
$this->allGroupSort[$group['sno']] = $group['groupSort'];
}
}
// 회원등급 평가시 제외인 등급 그룹 배열 생성
$exclusionGroups = $dao->selectGroupExclusion();
foreach ($exclusionGroups as $key){
$this->groupConfig = $groups;
}
/**
* 직전 평가일(autoAppraisalDateTime)은 배치 완료 후에 갱신되므로, 평가주기만큼 전진시켜 이번 회차를 찾는다.
*
* @param string $targetDate Y-m-d
*
* @return string Y-m-d
*/
protected function resolveAppraisalCycleStartDate($targetDate)
{
$calcCycleDay = (int)$this->getPolicyValue('calcCycleDay');
$base = date_create((string)$targetDate);
if ($calcCycleDay < 1 || $base === false) {
return $targetDate;
}
$base = date_create($base->format('Y-m-d'));
$cycleStart = $this->resolveCycleStartByPreviousAppraisal($base, $calcCycleDay);
if ($cycleStart === null) {
$cycleStart = $this->cycleDateInMonth($base, $calcCycleDay);
if ($cycleStart > $base) {
$cycleStart = $this->cycleDateInMonth($this->shiftMonth($base, -1), $calcCycleDay);
}
}
return $cycleStart->format('Y-m-d');
}
/**
* @param \DateTime $base
* @param int $calcCycleDay
*
* @return \DateTime|null
*/
protected function resolveCycleStartByPreviousAppraisal(\DateTime $base, $calcCycleDay)
{
$calcCycleMonth = (int)$this->getPolicyValue('calcCycleMonth');
$previous = (string)$this->getPolicyValue('autoAppraisalDateTime');
if ($calcCycleMonth < 1 || $previous === '') {
return null;
}
$cycleStart = date_create($previous);
if ($cycleStart === false) {
return null;
}
$cycleStart = date_create($cycleStart->format('Y-m-d'));
if ($cycleStart > $base) {
return null;
}
while (true) {
$next = $this->cycleDateInMonth($this->shiftMonth($cycleStart, $calcCycleMonth), $calcCycleDay);
if ($next > $base) {
return $cycleStart;
}
$cycleStart = $next;
}
}
/**
* @param \DateTime $base
* @param int $months
*
* @return \DateTime
*/
protected function shiftMonth(\DateTime $base, $months)
{
return date_create($base->format('Y-m-01'))->modify(sprintf('%+d month', $months));
}
/**
* @param \DateTime $base
* @param int $calcCycleDay
*
* @return \DateTime
*/
protected function cycleDateInMonth(\DateTime $base, $calcCycleDay)
{
$day = min($calcCycleDay, (int)$base->format('t'));
return date_create($base->format('Y-m-') . sprintf('%02d', $day));
}
/**
* @param string $key
*
* @return mixed
*/
protected function getPolicyValue($key)
{
return isset($this->policy[$key]) ? $this->policy[$key] : '';
}
/**
* @param int $groupSno
*
* @return int
*/
protected function resolveGroupSort($groupSno)
{
if (array_key_exists($groupSno, $this->allGroupSort)) {
return (int)$this->allGroupSort[$groupSno];
}
if (array_key_exists($groupSno, (array)$this->groupSort)) {
return (int)$this->groupSort[$groupSno];
}
$this->logger->warning('Not found groupSort. groupSno[' . $groupSno . ']');
return 0;
}
/**
* setGroupNames
* @return void
*/
public function appraisalGroupBySearch()
{
$this->logger->notice('Start group appraisal.');
 
$total = $this->countTotalMember();
$limit = $this->search->getAppraisalLimit();
$this->countByTotalMember = $this->appraisalCountByTotalMember($total, $limit);
$this->logger->notice('총 회원[' . $total . '], 1회 평가당 제한 인원수[' . $limit . '], 평가 실행 횟수[' . $this->countByTotalMember . ']');
 
$this->appraisalDateTime = DateTimeUtils::dateFormat('Y-m-d H:i:s', 'now');
if (method_exists($this->search, 'setRequiredData')) {
$this->search->setRequiredData($this->resolveRequiredData());
}
for ($offset = 0; $offset < $this->countByTotalMember; $offset++) {
$this->appraisalGroupByOffset($offset);
// 평가 대상으로 검색된 회원 초기화
$this->currentMembers = null;
}
$this->logger->notice('End group appraisal.');
}
/**
*/
public function appraisalGroupByOffset($offset)
{
 
$db = \App::getInstance('DB');
$this->search->setAppraisalOffset($offset);
if (method_exists($this->search, 'setTargetDateTime')) {
$this->search->setTargetDateTime($this->targetDateTime);
}
$this->logger->info(sprintf('Start appraisal group set offset %s', $offset));
foreach ($this->groupConfig as $index => $item) {
$this->logger->info(sprintf('Start group name is %s, group sno is %s, group sort is %s', $item['groupNm'], $item['sno'], $item['groupSort']));
 
$this->group = new SimpleStorage($item);
if ($this->currentMembers === null) {
$this->currentMembers = $this->search->search($this->group);
}
if ($this->search->hasAppraisalMember($this->currentMembers)) {
$this->logger->info(sprintf('has appraisal member count is %d', count($this->currentMembers)));
$this->updateGroupWithAddMailReceiver($this->currentMembers);
}
$this->logger->info(sprintf('End group name is %s, group sno is %s, group sort is %s', $item['groupNm'], $item['sno'], $item['groupSort']));
}
if ($this->policy['downwardAdjustment'] == 'y') {
$this->isDefaultGroupMember = true;
$this->logger->info(sprintf('Start group name is %s, group sno is %s, group sort is %s', $this->defaultGroupConfig['groupNm'], $this->defaultGroupConfig['sno'], $this->defaultGroupConfig['groupSort']));
$this->group = new SimpleStorage($this->defaultGroupConfig);
$this->search->setAppraisalDateTime($this->appraisalDateTime);
$this->currentMembers = $this->search->searchDefaultGroup($this->appraisalDateTime);
if ($this->search->hasAppraisalMember($this->currentMembers)) {
$this->logger->info(sprintf('has appraisal member count is %d', count($this->currentMembers)));
 
$this->updateGroupWithAddMailReceiver($this->currentMembers);
} else {
$this->logger->info('Not found appraisal members by default group', $this->defaultGroupConfig);
}
}
$this->logger->info(sprintf('End group name is %s, group sno is %s, group sort is %s', $this->defaultGroupConfig['groupNm'], $this->defaultGroupConfig['sno'], $this->defaultGroupConfig['groupSort']));
$this->isDefaultGroupMember = false;
}
/**
* 특정 회원에 대한 그룹평가
*
* @param array $memNos 타겟 memNo 배열
* appraisalGroupBySearch() 와 appraisalGroupByOffset() deprecate 및 해당 메서드로 대체 필요
*/
public function evaluateGroup(array $memNos)
{
if (empty($memNos)) {
return;
}
if (!method_exists($this->search, 'setTargetMembers')) {
throw new Exception('AppraisalSearch 구버전 : 자동 등급평가는 신규 setTargetMembers/setRequiredData/setTargetDateTime 메서드가 필요합니다. AppraisalSearch.php 를 코어와 동기화해주세요.');
}
$this->logger->notice('Start group appraisal.');
$this->appraisalDateTime = DateTimeUtils::dateFormat('Y-m-d H:i:s', 'now');
$this->search->setTargetDateTime($this->targetDateTime);
$this->search->setTargetMembers($memNos);
$this->search->setRequiredData($this->resolveRequiredData());
foreach ($this->groupConfig as $index => $item) {
$this->logger->info(sprintf('Start group name is %s, group sno is %s, group sort is %s', $item['groupNm'], $item['sno'], $item['groupSort']));
$this->group = new SimpleStorage($item);
if ($this->currentMembers === null) {
$this->currentMembers = $this->search->search($this->group);
}
if ($this->search->hasAppraisalMember($this->currentMembers)) {
$this->logger->info(sprintf('has appraisal member count is %d', count($this->currentMembers)));
$this->updateGroupWithAddMailReceiver($this->currentMembers);
}
$this->logger->info(sprintf('End group name is %s, group sno is %s, group sort is %s', $item['groupNm'], $item['sno'], $item['groupSort']));
}
if ($this->policy['downwardAdjustment'] == 'y') {
$this->isDefaultGroupMember = true;
$this->logger->info(sprintf('Start group name is %s, group sno is %s, group sort is %s', $this->defaultGroupConfig['groupNm'], $this->defaultGroupConfig['sno'], $this->defaultGroupConfig['groupSort']));
$this->group = new SimpleStorage($this->defaultGroupConfig);
$this->search->setAppraisalDateTime($this->appraisalDateTime);
$this->currentMembers = $this->search->searchDefaultGroup($this->appraisalDateTime);
if ($this->search->hasAppraisalMember($this->currentMembers)) {
$this->logger->info(sprintf('has appraisal member count is %d', count($this->currentMembers)));
$this->updateGroupWithAddMailReceiver($this->currentMembers);
} else {
$this->logger->info('Not found appraisal members by default group', $this->defaultGroupConfig);
}
$this->logger->info(sprintf('End group name is %s, group sno is %s, group sort is %s', $this->defaultGroupConfig['groupNm'], $this->defaultGroupConfig['sno'], $this->defaultGroupConfig['groupSort']));
}
$this->isDefaultGroupMember = false;
$this->logger->notice('End group appraisal.');
}
/**
* appraisalMailSend
* @return void
*/
public function updateGroupWithAddMailReceiver(array $members)
{
 
$session = \App::getInstance('session');
$db = \App::getInstance('DB');
$updateTargetMemNos = [];
$pendingReceivers = [];
try {
$groupSno = $this->group->get('sno');
$settleGb = $this->group->get('settleGb');
$smsSender = \App::load(\Component\Sms\SmsSender::class);
$smsSender->validPassword(\Request::post()->get('password'));
}
$checkGroupByPoint = $this->_isAppraisalSystemPoint();
foreach ($members as $index => $item) {
 
 
 
 
 
$session->set(Member::SESSION_MODIFY_MEMBER_INFO, $item);
$storage = new SimpleStorage($item);
$storage->set('settleGb', $settleGb);
$memNo = $storage->get('memNo');
 
// 현재 등급이 '등급평가시 제외' 설정이 아닐 때
if(array_key_exists($item['groupSno'], $this->apprExclusionOfRating) == false) {
if ($checkGroupByPoint) {
$this->checkGroupByPoint($item);
} else {
$this->checkGroupByFigure($item);
}
$beforeGroupSort = $this->resolveGroupSort($item['groupSno']);
$afterGroupSort = $this->resolveGroupSort($groupSno);
//하향펑가를 설정하지 않았거나, 하향평가를 사용하거나, 하향평가를 사용하지 않고 변경 등급이 기존 등급보다 높거나 같을 때 만 업데이트
if (empty($this->policy['downwardAdjustment']) || $this->policy['downwardAdjustment'] == 'y' || ($this->policy['downwardAdjustment'] == 'n' && $beforeGroupSort < $afterGroupSort)) {
if ($this->isCurrentGroupMember || $this->isDefaultGroupMember) {
$this->logger->info('Group change target. sno[' . $this->group->get('sno', 0) . '], name[' . $this->group->get('', '-') . ']', $item);
$updateTargetMemNos[] = $memNo;
$this->updateGroup($memNo);
$this->writeHistory($memNo);
$pendingReceivers[] = $storage;
 
unset($members[$index]);
}
}
}
}
// 쿠폰 발급은 회원 1명씩 호출하는 대신 쿠폰 번호별로 memNos 를 묶어 일괄 호출
if (!method_exists($this, 'flushCouponSmsBuffer')) {
throw new Exception('Appraisal 구버전 감지: flushCouponSmsBuffer 메서드 누락. 등급평가 쿠폰 버퍼 일괄발급 로직 미적용 시 쿠폰 미발급이 발생할 수 있어 사전 차단합니다.');
}
$this->flushCouponSmsBuffer();
if ($this->isTran) {
$db->commit();
$this->currentMembers = $members;
}
foreach ($pendingReceivers as $receiver) {
$this->addMailReceiver($receiver, $this->group);
$this->addSmsReceiver($receiver, $this->group);
}
} catch (Exception $e) {
$this->logger->error(__METHOD__ . ', ' . $e->getMessage(), ['exception' => $e]);
$db->rollback();
$this->couponSmsBuffer = [];
$this->rolledBackMemNos = array_merge($this->rolledBackMemNos, $updateTargetMemNos);
if (method_exists($this->search, 'removeEvaluatedMemberSnapshots')) {
$this->search->removeEvaluatedMemberSnapshots($updateTargetMemNos);
}
}
}
/**
* @deprecated 반복문 내 다중 호출 시 성능 저하가 발생하므로
* 반복문 밖에서 한 번만 호출하여 결과를 캐싱해 사용하세요.
* 향후 내부 로직에서 캐싱되거나 제거될 예정입니다.
*
* @param array $item 등급평가 대상의 기준치 정보
*
}
}
if ($point > 0) {
$this->logger->info(
 
__METHOD__, [
'memNo:'.$memNo,
$point,
*/
public function checkGroupByFigure($item)
{
 
$this->isCurrentGroupMember = true;
$priceUnit = 10000;
*/
public function updateGroupWithInsertHistory(SimpleStorage $storage)
{
$passwordCheckFl = gd_isset(\Request::post()->get('passwordCheckFl'), 'y');
if ($passwordCheckFl != 'n') {
$smsSender = \App::load(\Component\Sms\SmsSender::class);
$smsSender->validPassword(\Request::post()->get('password'));
}
$memNo = $storage->get('memNo');
$this->updateGroup($memNo);
$this->writeHistory($memNo);
if (!method_exists($this, 'flushCouponSmsBuffer')) {
throw new Exception('Appraisal 구버전 감지: flushCouponSmsBuffer 메서드 누락. 등급평가 쿠폰 버퍼 일괄발급 로직 미적용 시 쿠폰 미발급이 발생할 수 있어 사전 차단합니다.');
}
$this->flushCouponSmsBuffer();
}
/**
* updateGroup 루프에서 쌓인 쿠폰 발급 대상을 쿠폰 번호별로 묶어 일괄 발급.
* 호출당 saveMemberCouponSms 내부 셋업 쿼리 (checkCouponType, getCouponInfo,
* getMemberCouponStartDate/EndDate) 를 회원 수가 아닌 쿠폰 수 만큼만 수행.
*
* @return void
*/
public function flushCouponSmsBuffer()
{
if (empty($this->couponSmsBuffer)) {
return;
}
$passwordCheckFlBool = \Request::post()->get('passwordCheckFl') == 'n' ? false : true;
\Request::post()->set('couponSaveAdminId', '회원등급 쿠폰 혜택');
\Request::post()->set('managerNo', Session::get('manager.sno'));
\Request::post()->set('memberCouponState', 'y');
foreach ($this->couponSmsBuffer as $couponNo => $memNos) {
\Request::post()->set('couponNo', $couponNo);
$result = $this->couponAdmin->saveMemberCouponSms($memNos, null, $passwordCheckFlBool);
if ($result === false) {
$this->logger->warning(sprintf('Coupon issue skipped (checkCouponType failed). couponNo[%d] memNo_cnt[%d]', $couponNo, count($memNos)));
}
}
$this->couponSmsBuffer = [];
}
/**
}
}
//쿠폰 지급 대상 버퍼에 적재 (실제 발급은 flushCouponSmsBuffer 에서 쿠폰별 일괄 호출)
 
 
 
 
 
 
if($applyCoupon === true){
foreach(explode(INT_DIVISION, $this->group->get('groupCoupon')) as $value){
$couponNo = (int)$value;
if ($couponNo > 0) {
$this->couponSmsBuffer[$couponNo][] = $memNo;
}
 
 
 
 
 
 
 
 
 
}
}
$db = \App::getInstance('DB');
 
$arrUpdate[] = 'groupSno = ?';
$arrBind = [];
$db->bind_param_push($arrBind, $this->memberFields['groupSno'], $this->group->get('sno'));
$arrUpdate[] = 'groupModDt = ?';
$db->bind_param_push($arrBind, $this->memberFields['groupModDt'], $this->appraisalDateTime);
$arrUpdate[] = 'autoGroupAppraisalDt = ?';
$db->bind_param_push($arrBind, $this->memberFields['autoGroupAppraisalDt'], $this->appraisalDateTime);
$db->bind_param_push($arrBind, $this->memberFields['memNo'], $memNo);
$db->bind_param_push($arrBind, $this->memberFields['appFl'], 'y');
$db->bind_param_push($arrBind, $this->memberFields['sleepFl'], 'n');
$db->set_update_db(DB_MEMBER, $arrUpdate, 'memNo = ? AND appFl = ? AND sleepFl = ?', $arrBind);
}
$limit = $this->search->getAppraisalLimit();
// 처리 반복 횟수 계산
$countByTotalMember = $this->appraisalCountByTotalMember($total, $limit);
 
 
 
 
$this->logger->notice('Start apply coupon to all members.');
$this->logger->notice('총 회원[' . $total . '], 1회 처리당 제한 인원수[' . $limit . '], 처리 실행 횟수[' . $countByTotalMember . ']');
$passwordCheckFl = gd_isset(\Request::post()->get('passwordCheckFl'), 'y');
if($passwordCheckFl != 'n') {
// 쿠폰 관리 객체 미리 생성
$coupon = new \Component\Coupon\CouponAdmin;
// 쿠폰 정보 미리 생성
\Request::post()->set('couponSaveAdminId', '회원등급 쿠폰 혜택');
\Request::post()->set('memberCouponState', 'y');
\Request::post()->set('managerNo', Session::get('manager.sno'));
// 그룹 쿠폰 정보
/** @var MemberGroupService $memberGroupService */
$memberGroupService = App::getInstance(MemberGroupService::class);
$allGroupList = $memberGroupService->findAllGroupsKeyBySno();
// 페이징 처리를 위한 반복문
for ($offset = 0; $offset < $countByTotalMember; $offset++) {
$start = $offset * $limit;
$this->logger->info(sprintf('Start apply coupon with offset %s, limit %s', $start, $limit));
// 페이지별 회원 목록 조회
$memberPageList = $member->getMemberList($arrWhere, null, null, $start, $limit, $field);
 
 
 
 
 
 
 
if (count($memberPageList) === 0) {
continue;
}
 
foreach ($memberPageList as $memberInfo) {
$groupCoupon = $allGroupList[$memberInfo['groupSno']]['groupCoupon'] ?? '';
if (empty($groupCoupon)) {
continue;
}
$applyCouponList = explode(INT_DIVISION, $groupCoupon);
// 하나씩 쿠폰 발급 하기
if (!empty($applyCouponList) && count($applyCouponList) > 0) {
foreach ($applyCouponList as $applyCoupon) {
if (is_numeric($applyCoupon) && $applyCoupon > 0) {
\Request::post()->set('couponNo', $applyCoupon);
\Request::post()->set('memberCouponStartDate', $coupon->getMemberCouponStartDate($applyCoupon));
\Request::post()->set('memberCouponEndDate', $coupon->getMemberCouponEndDate($applyCoupon));
$coupon->saveMemberCouponSms((array)$memberInfo['memNo'], null, $passwordCheckFl);
}
 
}
unset($applyCouponList);
}
}
// 메모리 정리
unset($memberPageList);
}
$this->logger->notice('End apply coupon to all members.');
}
/**
* 특정 회원에게 쿠폰 발급
* - 이미 등급평가를 통해 쿠폰이 발급된 회원 제외
*
* applyCouponAllMember() 를 deprecate 및 해당 메서드로 대체 필요
* @param string $targetDate 대상일자
* @param array $memNos 대상 회원번호 배열
*
* @return void
*/
public function applyCouponSpecificMember(string $targetDate, array $memNos)
{
if (empty($memNos)) {
return;
}
$this->logger->notice('Start apply coupon to specific members.');
$passwordCheckFl = gd_isset(\Request::post()->get('passwordCheckFl'), 'y');
if($passwordCheckFl != 'n') {
$smsSender = \App::load(\Component\Sms\SmsSender::class);
$smsSender->validPassword(\Request::post()->get('password'));
}
$passwordCheckFl = $passwordCheckFl == 'n' ? false : true;
// 쿠폰의 경우 재수행되었을 때 구분할 수 있는 값이 필요하여 대상일자를 추가함
$couponSaveAdminId = '회원등급 쿠폰 혜택(' . str_replace('-','', $targetDate) . ')';
\Request::post()->set('couponSaveAdminId', $couponSaveAdminId);
\Request::post()->set('memberCouponState', 'y');
\Request::post()->set('managerNo', Session::get('manager.sno'));
/** @var MemberGroupCouponService $memberGroupCouponService */
$memberGroupCouponService = App::getInstance(MemberGroupCouponService::class);
$memberGroupCouponService->applyGroupCouponsToMembers(
$memNos,
$couponSaveAdminId,
$passwordCheckFl,
$targetDate,
$this->resolveAppraisalCycleStartDate($targetDate)
);
}
/**
* 변경자는 updateGroup 에서 이미 stamp 되므로, 유지자만 보완해 동일자 재평가 대상에서 제외되도록 한다.
*
* @param array $memNos
* @param string $targetDate Y-m-d
*
* @return void
*/
public function updateAppraisalCompletedForDefaultGroupMembers(array $memNos, string $targetDate)
{
if (empty($memNos)) {
return;
}
if (!method_exists($this->search, 'getEvaluatedMemberSnapshots')) {
throw new Exception('AppraisalSearch 구버전 감지: getEvaluatedMemberSnapshots 누락. 평가 스냅샷 없이 유지회원 stamp 시 미평가 회원까지 평가완료 처리되어 다음 회차에서 누락될 수 있어 사전 차단합니다.');
}
$db = \App::getInstance('DB');
$touchDateTime = $this->appraisalDateTime ?: ($targetDate . ' 00:00:00');
$from = $targetDate . ' 00:00:00';
$snapshots = $this->search->getEvaluatedMemberSnapshots();
$exclusion = $this->apprExclusionOfRating ?? [];
$appraisalGroupSnos = $this->resolveAppraisalGroupSnos();
if (empty($appraisalGroupSnos)) {
$this->logger->warning('Skip retained member stamp. Not found appraisal target group.', [
'groupSort' => $this->groupSort,
'exclusion' => $exclusion,
]);
return;
}
if (empty($snapshots)) {
// 평가 검색이 실행되지 않은 실행(평가기준 그룹 없음 등) — 종전 라이브 조회 방식으로 stamp
$memNos = array_values(array_diff($memNos, $this->rolledBackMemNos));
if (empty($memNos)) {
return;
}
$arrBind = [];
$db->bind_param_push($arrBind, 's', $touchDateTime);
$placeholders = implode(',', array_fill(0, count($memNos), '?'));
foreach ($memNos as $memNo) {
$db->bind_param_push($arrBind, 'i', $memNo);
}
$db->bind_param_push($arrBind, 's', 'y');
$db->bind_param_push($arrBind, 's', 'n');
$groupPlaceholders = implode(',', array_fill(0, count($appraisalGroupSnos), '?'));
foreach ($appraisalGroupSnos as $sno) {
$db->bind_param_push($arrBind, 'i', $sno);
}
$db->bind_param_push($arrBind, 's', $from);
$strWhere = 'memNo IN (' . $placeholders . ')'
. ' AND appFl = ?'
. ' AND sleepFl = ?'
. ' AND groupSno IN (' . $groupPlaceholders . ')'
. ' AND (autoGroupAppraisalDt IS NULL OR autoGroupAppraisalDt < ?)';
$db->set_update_db(DB_MEMBER, 'autoGroupAppraisalDt = ?', $strWhere, $arrBind);
$this->logger->info('Update autoGroupAppraisalDt for retained members (no snapshot fallback)', [
'table' => DB_MEMBER,
'memNoCount' => count($memNos),
'from' => $from,
]);
return;
}
$retainedMemNos = $this->resolveRetainedStampTargets($snapshots, $exclusion, $appraisalGroupSnos);
if (empty($retainedMemNos)) {
return;
}
$arrBind = [];
$db->bind_param_push($arrBind, 's', $touchDateTime);
$placeholders = implode(',', array_fill(0, count($retainedMemNos), '?'));
foreach ($retainedMemNos as $memNo) {
$db->bind_param_push($arrBind, 'i', $memNo);
}
$db->bind_param_push($arrBind, 's', 'y');
$db->bind_param_push($arrBind, 's', 'n');
$db->bind_param_push($arrBind, 's', $from);
$strWhere = 'memNo IN (' . $placeholders . ')'
. ' AND appFl = ?'
. ' AND sleepFl = ?'
. ' AND (autoGroupAppraisalDt IS NULL OR autoGroupAppraisalDt < ?)';
$db->set_update_db(DB_MEMBER, 'autoGroupAppraisalDt = ?', $strWhere, $arrBind);
$this->logger->info('Update autoGroupAppraisalDt for retained members', [
'table' => DB_MEMBER,
'memNoCount' => count($retainedMemNos),
'from' => $from,
]);
}
/**
* @param array $snapshots memNo => search 결과 row
* @param array $exclusion 제외그룹 sno => sno
* @param array $appraisalGroupSnos 평가 대상 등급 sno 배열
*
* @return array
*/
public function resolveRetainedStampTargets(array $snapshots, array $exclusion, array $appraisalGroupSnos)
{
$retainedMemNos = [];
$appraisalGroupSnos = array_map('intval', $appraisalGroupSnos);
foreach ($snapshots as $memNo => $row) {
$groupSno = is_array($row) ? ($row['groupSno'] ?? null) : null;
if ($groupSno !== null && array_key_exists($groupSno, $exclusion)) {
continue;
}
if ($groupSno !== null && in_array((int)$groupSno, $appraisalGroupSnos, true) === false) {
continue;
}
$retainedMemNos[] = (int)$memNo;
}
return $retainedMemNos;
}
/**
* @return array
*/
public function resolveAppraisalGroupSnos()
{
$groupSnos = array_keys((array)$this->groupSort);
$exclusionSnos = array_values((array)$this->apprExclusionOfRating);
return array_values(array_diff($groupSnos, $exclusionSnos));
}
/**
*/
public function addSmsReceiver(SimpleStorage $storage, SimpleStorage $group)
{
 
if ($storage->get('cellPhone') != null && $storage->get('memNm') != null) {
if ($storage->get('beforeGroupSno') != $group->get('sno')) {
$smsAuto = \App::load(\Component\Sms\SmsAuto::class);
'memNm' => $storage->get('memNm'),
'memNo' => $storage->get('memNo'),
'groupNm' => $group->get('groupNm'),
'rc_mallNm' => \Globals::get('gMall.mallNm'),
'shopUrl' => $this->basicInfo['mallDomain'],
]
);
$smsAuto->attach($observer);
} else {
$this->logger->info(sprintf('The membership level is the same. before[%d], now[%d]', $storage->get('beforeGroupSno'), $group->get('sno')));
}
} else {
$this->logger->info(sprintf('Not have a cell phone number or member name. memNo[%d]', $storage->get('memNo', 0)));
}
}
{
return $this->appraisalGroupConfig->get('apprSystem') == 'point';
}
/*
* 회원등급평가 타겟일자 셋팅
*
* @param $targetDateTime 수행일자
*/
public function setTargetDateTime($targetDateTime = null)
{
$this->targetDateTime = empty($targetDateTime) ? date('Y-m-d') : $targetDateTime;
}
/**
* 전체 등급 설정을 스캔하여 실제 필요한 데이터 유형을 결정
* @return array ['order' => bool, 'review' => bool, 'login' => bool]
*/
protected function resolveRequiredData(): array
{
$isPoint = $this->_isAppraisalSystemPoint();
if ($isPoint) {
return [
'order' => $this->appraisalGroupConfig->get('appraisalPointOrderPriceFl', 'n') == 'y'
|| $this->appraisalGroupConfig->get('appraisalPointOrderRepeatFl', 'n') == 'y',
'review' => $this->appraisalGroupConfig->get('appraisalPointReviewRepeatFl', 'n') == 'y',
'login' => $this->appraisalGroupConfig->get('appraisalPointLoginRepeatFl', 'n') == 'y',
];
}
// 수치제: 등급별 설정이므로 전체 등급을 스캔
$allGroups = array_merge($this->groupConfig, [$this->defaultGroupConfig]);
$needsOrder = false;
$needsReview = false;
foreach ($allGroups as $group) {
if (!$needsOrder && (($group['apprFigureOrderPriceFl'] ?? 'n') == 'y' || ($group['apprFigureOrderRepeatFl'] ?? 'n') == 'y')) {
$needsOrder = true;
}
if (!$needsReview && ($group['apprFigureReviewRepeatFl'] ?? 'n') == 'y') {
$needsReview = true;
}
if ($needsOrder && $needsReview) {
break;
}
}
return [
'order' => $needsOrder,
'review' => $needsReview,
'login' => false, // 수치제에서는 로그인 기준 없음
];
}
}
 
Component/Member/Group/AppraisalSearch.php+156-27
변경 전AppraisalSearch.php
private $periodQuery;
/** @var string $periodDateFormat 검색기간 포맷 */
private $periodDateFormat = 'Y-m-d';
 
 
 
 
 
 
 
 
/**
* 조회
unset($this->query, $this->column, $this->where);
$this->periodQuery();
$this->boardQuery();
$this->orderQuery();
$this->loginQuery();
$this->buildQuery();
 
 
 
 
 
 
 
$logger->info(sprintf('Build query complete. %s', $this->query));
$results = $this->db->query_fetch($this->query);
$logger->debug('Appraisal search result keys', gd_array_keys($results[0]));
$return = [];
foreach ($results as $index => $result) {
if (gd_array_key_exists($result['memNo'], $return)) {
$return[$result['memNo']] = $result;
}
 
 
 
 
 
 
 
 
return $return;
}
$query[] = 'SELECT memNo, COUNT(sno) AS cnt';
$query[] = ', COUNT(IF(loginCntMobile > 0, 1, NULL)) AS mcnt';
$query[] = 'FROM ' . DB_MEMBER_LOGINLOG;
 
if ($this->usePeriod()) {
$query[] = 'WHERE regDt ' . $this->periodQuery;
 
 
 
 
 
 
}
$query[] = 'GROUP BY memNo';
$this->loginQuery = '(' . gd_implode(' ', $query) . ')';
$query[] = ', COUNT(IF(isMobile=\'y\', sno, NULL)) AS mcnt';
$query[] = 'FROM ' . DB_BD_ . Board::BASIC_GOODS_REIVEW_ID;
$query[] = 'WHERE memNo > 0';
 
 
 
if ($this->usePeriod()) {
$query[] = 'AND regDt ' . $this->periodQuery;
}
$subQuery[] = 'LEFT JOIN ' . DB_ORDER_GOODS . ' AS og ON o.orderNo = og.orderNo';
$subQuery[] = 'LEFT JOIN ' . DB_ORDER_ADD_GOODS . ' AS oag ON o.orderNo = oag.orderNo';
$subQuery[] = 'WHERE og.orderStatus=\'s1\' AND o.memNo > 0';
 
 
 
if ($this->usePeriod()) {
$subQuery[] = 'AND og.finishDt ' . $this->periodQuery;
}
unset($this->query, $this->column, $this->where);
$this->periodQuery();
$this->boardQuery();
$this->orderQuery();
$this->loginQuery();
$this->buildQuery('m.groupSno!=' . $groupSno);
 
 
 
 
 
 
 
 
 
 
$logger = \App::getInstance('logger');
$logger->info(sprintf('Build query complete. %s', $this->query));
*/
public function searchDefaultGroup($appraisalDateTime)
{
$this->db->strField = 'm.memNo, m.memNm, m.email, m.cellPhone, m.groupSno, m.adminMemo, m.groupModDt';
// 다른등급 대상 회원 조회(_pushWhere())와 조건 동일하게 설정
$this->db->strWhere = 'm.sleepFl=\'n\' AND m.appFl=\'y\'';
$this->db->strLimit = ($this->appraisalOffset * $this->appraisalLimit) . ', ' . $this->appraisalLimit;
 
 
 
 
 
 
 
$this->db->strGroup = 'm.memNo';
$query = $this->db->query_complete();
$return[$result['memNo']] = $result;
}
}
 
return $return;
}
if (!empty($where)) {
$this->where[] = $where;
}
$this->db->strField = gd_implode(',', $this->column);
$this->db->strWhere = gd_implode(' AND ', $this->where);
$this->db->strJoin = 'LEFT JOIN ' . $this->boardQuery . ' AS b ON m.memNo = b.memNo';
$this->db->strJoin .= ' LEFT JOIN ' . $this->orderQuery . ' AS o ON m.memNo = o.memNo';
$this->db->strJoin .= ' LEFT JOIN ' . $this->loginQuery . ' AS l ON m.memNo = l.memNo';
$this->db->strLimit = ($this->appraisalOffset * $this->appraisalLimit) . ', ' . $this->appraisalLimit;
 
 
 
 
 
 
 
 
 
 
 
 
$this->db->strGroup = 'm.memNo';
$query = $this->db->query_complete();
*/
private function _pushColumn()
{
$this->column[] = 'm.memNo, m.memNm, m.email, m.cellPhone, m.groupSno, m.adminMemo, m.groupModDt';
$this->column[] = 'IFNULL(l.cnt, 0) AS loginCount';
$this->column[] = 'IFNULL(b.cnt, 0) AS reviewCount';
$this->column[] = 'IFNULL(o.cnt, 0) AS orderCount';
$this->column[] = 'IFNULL(o.price, 0) AS orderPrice';
$this->column[] = 'IFNULL(l.mcnt, 0) AS loginCountMobile';
$this->column[] = 'IFNULL(b.mcnt, 0) AS reviewCountMobile';
$this->column[] = 'IFNULL(o.mcnt, 0) AS orderCountMobile';
$this->column[] = 'IFNULL(o.mprice, 0) AS orderPriceMobile';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
/**
$column = 'IFNULL(' . $column . ', 0)';
}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
변경 후AppraisalSearch.php
private $periodQuery;
/** @var string $periodDateFormat 검색기간 포맷 */
private $periodDateFormat = 'Y-m-d';
/** @var string 수행일자 */
protected $targetDateTime;
/** @var array airflow 가 지정한 memNo 배열 */
protected $targetMembers = [];
/** @var array 실제 평가된 회원 스냅샷 (memNo => search 결과 row). 유지회원 stamp 대상 판정 기준 */
protected $evaluatedMembers = [];
/** @var array 필요한 서브쿼리 플래그 ['order' => bool, 'review' => bool, 'login' => bool] */
protected $requiredData = ['order' => true, 'review' => true, 'login' => true];
/**
* 조회
unset($this->query, $this->column, $this->where);
$this->periodQuery();
if ($this->requiredData['review']) {
$this->boardQuery();
}
if ($this->requiredData['order']) {
$this->orderQuery();
}
if ($this->requiredData['login']) {
$this->loginQuery();
}
// 수동평가(targetMembers 없음)는 동일자 재평가 허용을 위해 완료대상 제외 필터 미적용
$this->buildQuery(empty($this->targetMembers) ? '' : $this->whereExcludeCompleteMember());
$logger->info(sprintf('Build query complete. %s', $this->query));
$results = $this->db->query_fetch($this->query);
$logger->debug('Appraisal search result keys', empty($results) ? [] : array_keys($results[0]));
$return = [];
foreach ($results as $index => $result) {
if (gd_array_key_exists($result['memNo'], $return)) {
$return[$result['memNo']] = $result;
}
$logger->info(sprintf('Appraisal search matched %d of %d requested members. groupSno[%s]', count($return), count($this->targetMembers), $group->get('sno', '-')));
if (!empty($this->targetMembers)) {
foreach ($return as $memNo => $row) {
$this->evaluatedMembers[$memNo] = $row;
}
}
return $return;
}
$query[] = 'SELECT memNo, COUNT(sno) AS cnt';
$query[] = ', COUNT(IF(loginCntMobile > 0, 1, NULL)) AS mcnt';
$query[] = 'FROM ' . DB_MEMBER_LOGINLOG;
$loginWhere = [];
if ($this->usePeriod()) {
$loginWhere[] = 'regDt ' . $this->periodQuery;
}
if (!empty($this->targetMembers)) {
$loginWhere[] = 'memNo IN (' . implode(',', array_map('intval', $this->targetMembers)) . ')';
}
if (!empty($loginWhere)) {
$query[] = 'WHERE ' . implode(' AND ', $loginWhere);
}
$query[] = 'GROUP BY memNo';
$this->loginQuery = '(' . gd_implode(' ', $query) . ')';
$query[] = ', COUNT(IF(isMobile=\'y\', sno, NULL)) AS mcnt';
$query[] = 'FROM ' . DB_BD_ . Board::BASIC_GOODS_REIVEW_ID;
$query[] = 'WHERE memNo > 0';
if (!empty($this->targetMembers)) {
$query[] = 'AND memNo IN (' . implode(',', array_map('intval', $this->targetMembers)) . ')';
}
if ($this->usePeriod()) {
$query[] = 'AND regDt ' . $this->periodQuery;
}
$subQuery[] = 'LEFT JOIN ' . DB_ORDER_GOODS . ' AS og ON o.orderNo = og.orderNo';
$subQuery[] = 'LEFT JOIN ' . DB_ORDER_ADD_GOODS . ' AS oag ON o.orderNo = oag.orderNo';
$subQuery[] = 'WHERE og.orderStatus=\'s1\' AND o.memNo > 0';
if (!empty($this->targetMembers)) {
$subQuery[] = 'AND o.memNo IN (' . implode(',', array_map('intval', $this->targetMembers)) . ')';
}
if ($this->usePeriod()) {
$subQuery[] = 'AND og.finishDt ' . $this->periodQuery;
}
unset($this->query, $this->column, $this->where);
$this->periodQuery();
if ($this->requiredData['review']) {
$this->boardQuery();
}
if ($this->requiredData['order']) {
$this->orderQuery();
}
if ($this->requiredData['login']) {
$this->loginQuery();
}
$where = 'm.groupSno != ' . $groupSno;
if (!empty($this->targetMembers)) {
$where .= ' AND ' . $this->whereExcludeCompleteMember();
}
$this->buildQuery($where);
$logger = \App::getInstance('logger');
$logger->info(sprintf('Build query complete. %s', $this->query));
*/
public function searchDefaultGroup($appraisalDateTime)
{
$this->db->strField = 'm.memNo, m.memNm, m.email, m.cellPhone, m.smsFl, m.maillingFl, m.groupSno, m.adminMemo, m.groupModDt';
// 다른등급 대상 회원 조회(_pushWhere())와 조건 동일하게 설정
$this->db->strWhere = 'm.sleepFl=\'n\' AND m.appFl=\'y\'';
if (!empty($this->targetMembers)) {
$this->db->strWhere .= ' AND ' . $this->whereExcludeCompleteMember();
}
if (empty($this->targetMembers)) {
$this->db->strLimit = ($this->appraisalOffset * $this->appraisalLimit) . ', ' . $this->appraisalLimit;
} else {
$this->db->strWhere .= ' AND m.memNo IN (' . implode(',', array_map('intval', $this->targetMembers)) . ')';
}
$this->db->strGroup = 'm.memNo';
$query = $this->db->query_complete();
$return[$result['memNo']] = $result;
}
}
return $return;
}
if (!empty($where)) {
$this->where[] = $where;
}
$this->db->strField = implode(',', $this->column);
$this->db->strWhere = implode(' AND ', $this->where);
$joins = [];
if ($this->requiredData['review']) {
$joins[] = 'LEFT JOIN ' . $this->boardQuery . ' AS b ON m.memNo = b.memNo';
}
if ($this->requiredData['order']) {
$joins[] = 'LEFT JOIN ' . $this->orderQuery . ' AS o ON m.memNo = o.memNo';
}
if ($this->requiredData['login']) {
$joins[] = 'LEFT JOIN ' . $this->loginQuery . ' AS l ON m.memNo = l.memNo';
}
$this->db->strJoin = implode(' ', $joins);
if (empty($this->targetMembers)) {
$this->db->strLimit = ($this->appraisalOffset * $this->appraisalLimit) . ', ' . $this->appraisalLimit;
} else {
$this->db->strWhere .= ' AND m.memNo IN (' . implode(',', array_map('intval', $this->targetMembers)) . ')';
}
$this->db->strGroup = 'm.memNo';
$query = $this->db->query_complete();
*/
private function _pushColumn()
{
$this->column[] = 'm.memNo, m.memNm, m.email, m.cellPhone, m.smsFl, m.maillingFl, m.groupSno, m.adminMemo, m.groupModDt';
if ($this->requiredData['login']) {
$this->column[] = 'IFNULL(l.cnt, 0) AS loginCount';
$this->column[] = 'IFNULL(l.mcnt, 0) AS loginCountMobile';
} else {
$this->column[] = '0 AS loginCount';
$this->column[] = '0 AS loginCountMobile';
}
if ($this->requiredData['review']) {
$this->column[] = 'IFNULL(b.cnt, 0) AS reviewCount';
$this->column[] = 'IFNULL(b.mcnt, 0) AS reviewCountMobile';
} else {
$this->column[] = '0 AS reviewCount';
$this->column[] = '0 AS reviewCountMobile';
}
if ($this->requiredData['order']) {
$this->column[] = 'IFNULL(o.cnt, 0) AS orderCount';
$this->column[] = 'IFNULL(o.price, 0) AS orderPrice';
$this->column[] = 'IFNULL(o.mcnt, 0) AS orderCountMobile';
$this->column[] = 'IFNULL(o.mprice, 0) AS orderPriceMobile';
} else {
$this->column[] = '0 AS orderCount';
$this->column[] = '0 AS orderPrice';
$this->column[] = '0 AS orderCountMobile';
$this->column[] = '0 AS orderPriceMobile';
}
}
/**
$column = 'IFNULL(' . $column . ', 0)';
}
}
public function setRequiredData(array $requiredData)
{
$this->requiredData = array_merge($this->requiredData, $requiredData);
}
/*
* 회원등급평가 타겟일자 셋팅
*
* @param string $targetDateTime 수행일자
*/
public function setTargetDateTime($targetDateTime = null)
{
$this->targetDateTime = $targetDateTime;
}
public function setTargetMembers(array $target = [])
{
$this->targetMembers = $target;
}
public function getEvaluatedMemberSnapshots()
{
return $this->evaluatedMembers;
}
public function removeEvaluatedMemberSnapshots(array $memNos)
{
foreach ($memNos as $memNo) {
unset($this->evaluatedMembers[$memNo]);
}
}
/*
* 타겟일자기준 회원등급평가 완료대상 제외 where 조건
*
* @return string
*/
public function whereExcludeCompleteMember()
{
$from = $this->db->escape("{$this->targetDateTime} 00:00:00");
return "(m.autoGroupAppraisalDt IS NULL OR m.autoGroupAppraisalDt < '{$from}')";
}
}
 
Component/Member/Group/GroupDomain.php+13-1
변경 전GroupDomain.php
private $managerNo;
private $regDt;
private $modDt;
 
 
function __construct(array $arr = null)
{
/**
* 객체에 설정된 추가할인적용제외/중복할인적용에 대한 화면에 보여질 정보를 데이터베이스 조회를 통해 설정하는 함수
 
 
*/
public function setGroupDcData()
{
 
 
 
 
 
$this->toStrip('dcExOption')->toJsonDecode('dcExOption', true);
$this->toStrip('dcExScm')->toJsonDecode('dcExScm', true);
$this->toStrip('dcExCategory')->toJsonDecode('dcExCategory', true);
public function toStrip($key = null)
{
$this->$key = StringUtils::htmlSpecialCharsStripSlashes($this->$key);
 
 
 
return $this;
}
 
변경 후GroupDomain.php
private $managerNo;
private $regDt;
private $modDt;
/** @var bool setGroupDcData() 중복 실행 방지 플래그 */
private $isGroupDcDataSet = false;
function __construct(array $arr = null)
{
/**
* 객체에 설정된 추가할인적용제외/중복할인적용에 대한 화면에 보여질 정보를 데이터베이스 조회를 통해 설정하는 함수
*
* 조회 결과를 원본 필드에 덮어쓰므로 두 번 호출하면 값이 깨진다. 인스턴스 재사용 시 최초 1회만 수행한다.
*/
public function setGroupDcData()
{
if ($this->isGroupDcDataSet) {
return;
}
$this->isGroupDcDataSet = true;
$this->toStrip('dcExOption')->toJsonDecode('dcExOption', true);
$this->toStrip('dcExScm')->toJsonDecode('dcExScm', true);
$this->toStrip('dcExCategory')->toJsonDecode('dcExCategory', true);
public function toStrip($key = null)
{
// 캐시된 인스턴스를 회원 수만큼 재사용해도 멱등하게 동작한다.
if (is_string($this->$key)) {
$this->$key = StringUtils::htmlSpecialCharsStripSlashes($this->$key);
}
return $this;
}
 
Component/Member/History.php+9
변경 전History.php
$before = $session->get(Member::SESSION_MODIFY_MEMBER_INFO, null);
$after = $this->db->getData(DB_MEMBER, $this->memNo, 'memNo');
 
 
 
 
 
 
 
 
 
 
MemberUtil::combineMemberData($before);
MemberUtil::combineMemberData($after);
 
 
변경 후History.php
$before = $session->get(Member::SESSION_MODIFY_MEMBER_INFO, null);
$after = $this->db->getData(DB_MEMBER, $this->memNo, 'memNo');
 
// 자동 등급평가 등 세션 미세팅 경로에서 null 이 들어올 수 있음.
// combineMemberData → ArrayUtils::unsetDiff(array $keys) 가 strict 타입이라 TypeError 발생 → 빈 배열로 보정.
if (!is_array($before)) {
$before = [];
}
if (!is_array($after)) {
$after = [];
}
 
MemberUtil::combineMemberData($before);
MemberUtil::combineMemberData($after);
 
 
Controller/Admin/Member/MemberGroupPsController.php+4
변경 전MemberGroupPsController.php
$appraisal = \App::load('\\Component\\Member\\Group\\Appraisal');
$appraisal->setRemoteAddress(Request::getRemoteAddress());
ini_set('max_execution_time', 0);
 
 
 
 
$appraisal->appraisalGroupBySearch();
$appraisal->appraisalMailSend();
if($appraisal->policy['couponConditionComplete'] == 'y' && $appraisal->policy['couponConditionCompleteChange'] != 'y') {
 
변경 후MemberGroupPsController.php
$appraisal = \App::load('\\Component\\Member\\Group\\Appraisal');
$appraisal->setRemoteAddress(Request::getRemoteAddress());
ini_set('max_execution_time', 0);
if (!method_exists($appraisal, 'setTargetDateTime')) {
throw new \Exception('Appraisal 구버전 감지: setTargetDateTime 메서드 누락. Appraisal.php 를 코어와 동기화해주세요.');
}
$appraisal->setTargetDateTime();
$appraisal->appraisalGroupBySearch();
$appraisal->appraisalMailSend();
if($appraisal->policy['couponConditionComplete'] == 'y' && $appraisal->policy['couponConditionCompleteChange'] != 'y') {
 
CRM 레시피 문구 개선
Admin/crm/crm_recipe_send.php+1-1
변경 전crm_recipe_send.php
<h3 class="ncua-help-manual"><?= gd_htmlspecialchars($recipeName ?? '레시피') ?></h3>
<div class="ncua-page-header__actions">
<button id="closeCrmRecipe" type="button" class="ncua-btn ncua-btn--sm ncua-btn--secondary-gray">닫기</button>
<button id="previewCrmRecipe" type="button" class="ncua-btn ncua-btn--sm ncua-btn--primary">발송</button>
</div>
</header>
 
 
변경 후crm_recipe_send.php
<h3 class="ncua-help-manual"><?= gd_htmlspecialchars($recipeName ?? '레시피') ?></h3>
<div class="ncua-page-header__actions">
<button id="closeCrmRecipe" type="button" class="ncua-btn ncua-btn--sm ncua-btn--secondary-gray">닫기</button>
<button id="previewCrmRecipe" type="button" class="ncua-btn ncua-btn--sm ncua-btn--primary">반복발송</button>
</div>
</header>
 
 
Admin/crm/mobile_send/layer_recipe_send_time_setting.php+1
변경 전layer_recipe_send_time_setting.php
<h4 class="ncua-card__title">
발송 설정
</h4>
 
</header>
<section class="ncua-card__body">
<div class="send-time-setting-content">
 
변경 후layer_recipe_send_time_setting.php
<h4 class="ncua-card__title">
발송 설정
</h4>
<p class="send-time-setting-desc">발송 기간과 주기를 설정해 메시지를 반복 발송해보세요.</p>
</header>
<section class="ncua-card__body">
<div class="send-time-setting-content">