본문으로 건너뛰기

소스 변경 전체 보기

PHP 8 · GODO26
변경 파일 수: 160개
변경 파일 목록
총 160개
SMS 발송 보안 강화
auto_send_config_contents.phpAdmin/crm/auto_send_config/auto_send_config_preview.phpAdmin/crm/auto_send_config/auto_send_config_recipient_section.phpAdmin/crm/auto_send_config/layer_auto_send_modify_kakao_template.phpAdmin/crm/auto_send_config/auto_send_config.phpAdmin/crm/auto_send.phpAdmin/crm/crm_recipe_send.phpAdmin/crm/layer_message_template_approval_history.phpAdmin/crm/layer_mobile_message_performance_detail.phpAdmin/crm/layer_mobile_message_performance_summary.phpAdmin/crm/layer_save_as_template.phpAdmin/crm/layer_update_kakao_template.phpAdmin/crm/layer_manage_sms_verification.phpAdmin/crm/message_config/message_config.phpAdmin/crm/message_template_result.phpAdmin/crm/message_template/message_template_search.phpAdmin/crm/message_template/message_template.phpAdmin/crm/basic_config.phpAdmin/crm/message_template_register/message_template_register.phpAdmin/crm/template_contents_sms.phpAdmin/crm/message_template_register/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_recipient_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/sms_auto.phpAdmin/member/sms_send.phpAdmin/member/_manage_permission_menu_list.phpAdmin/policy/manage_permission.phpAdmin/policy/manage_register.phpAdmin/policy/manage_security.phpAdmin/policy/cart_remind_list.phpAdmin/promotion/comeback_coupon_list.phpAdmin/promotion/layer_message_representative_register_guide.phpAdmin/share/layer_message_representative_send_restrict.phpAdmin/share/layer_occupancy_auth.phpAdmin/share/layer_sms_contents.phpAdmin/share/DBTableField.phpComponent/Database/Manager.phpComponent/Member/SmsAdmin.phpComponent/Sms/SmsAuto.phpComponent/Sms/AutoSendConfigController.phpController/Admin/Crm/LayerAutoSendModifyKakaoTemplateController.phpController/Admin/Crm/AutoSendConfig/LayerAutoSendSaveTemplateController.phpController/Admin/Crm/AutoSendConfig/LayerAutoSendTemplateContentController.phpController/Admin/Crm/AutoSendConfig/LayerAutoSendTemplateController.phpController/Admin/Crm/AutoSendConfig/AutoSendConfigPsController.phpController/Admin/Crm/AutoSendController.phpController/Admin/Crm/LayerListController.phpController/Admin/Crm/AutoSend/LayerListRecommendController.phpController/Admin/Crm/AutoSend/LayerListSearchController.phpController/Admin/Crm/AutoSend/AutoSendPsController.phpController/Admin/Crm/CrmRecipeClickLogPsController.phpController/Admin/Crm/CrmRecipeSendController.phpController/Admin/Crm/LayerKakaoTemplateCommentController.phpController/Admin/Crm/LayerMobileMessagePerformanceDetailController.phpController/Admin/Crm/LayerMobileMessagePerformanceSummaryController.phpController/Admin/Crm/LayerMyAppSendGuideController.phpController/Admin/Crm/LayerPreviewAutoSendController.phpController/Admin/Crm/LayerPreviewMobileSendController.phpController/Admin/Crm/LayerRejectRecipientMemberInfoController.phpController/Admin/Crm/LayerSaveAsTemplateController.phpController/Admin/Crm/LayerSelectCouponController.phpController/Admin/Crm/LayerSelectCouponResultController.phpController/Admin/Crm/LayerSelectMemberGroupsController.phpController/Admin/Crm/LayerSelectMemberGroupsResultController.phpController/Admin/Crm/LayerTemplateContentsController.phpController/Admin/Crm/LayerUpdateKaKaoTemplateController.phpController/Admin/Crm/LayerViewMessageLinkTypeController.phpController/Admin/Crm/MessageConfigController.phpController/Admin/Crm/LayerBizmKakaoTalkChannelController.phpController/Admin/Crm/MessageConfig/LayerBizmKakaoTalkChannelPsController.phpController/Admin/Crm/MessageConfig/LayerChargeMessagePointsHistoryController.phpController/Admin/Crm/MessageConfig/LayerChargeMessagePointsHistoryResultController.phpController/Admin/Crm/MessageConfig/LayerCloudKakaoTalkChannelController.phpController/Admin/Crm/MessageConfig/LayerCloudKakaoTalkChannelPsController.phpController/Admin/Crm/MessageConfig/LayerLunaKakaoTalkChannelController.phpController/Admin/Crm/MessageConfig/LayerLunaKakaoTalkChannelPsController.phpController/Admin/Crm/MessageConfig/LayerManageSmsVerificationController.phpController/Admin/Crm/MessageConfig/LayerManageSmsVerificationPsController.phpController/Admin/Crm/MessageConfig/LayerMessagePointController.phpController/Admin/Crm/MessageConfig/LayerRegisterKakaoTalkChannelController.phpController/Admin/Crm/MessageConfig/LayerRegisterKakaoTalkChannelPsController.phpController/Admin/Crm/MessageConfig/LayerRejectNumberListController.phpController/Admin/Crm/MessageConfig/LayerRejectNumberListPsController.phpController/Admin/Crm/MessageConfig/LayerRejectNumberListResultController.phpController/Admin/Crm/MessageConfig/LayerSmsVerificationController.phpController/Admin/Crm/MessageConfig/MessageConfigPsController.phpController/Admin/Crm/MessageTemplateController.phpController/Admin/Crm/MessageTemplatePsController.phpController/Admin/Crm/MessageTemplateRegisterController.phpController/Admin/Crm/MessageTemplateRegisterPsController.phpController/Admin/Crm/MobileHistoryController.phpController/Admin/Crm/MobileHistoryListController.phpController/Admin/Crm/LayerBizmKakaoAlimTalkHistoryResultController.phpController/Admin/Crm/MobileMessageHistory/LayerCloudKakaoAlimTalkHistoryResultController.phpController/Admin/Crm/MobileMessageHistory/LayerKakaoAlimTalkContentsController.phpController/Admin/Crm/MobileMessageHistory/LayerKakaoAlimTalkHistorySearchController.phpController/Admin/Crm/MobileMessageHistory/LayerKakaoFriendTalkContentsController.phpController/Admin/Crm/MobileMessageHistory/LayerKakaoFriendTalkHistoryResultController.phpController/Admin/Crm/MobileMessageHistory/LayerKakaoFriendTalkHistorySearchController.phpController/Admin/Crm/MobileMessageHistory/LayerLunaKakaoAlimTalkHistoryResultController.phpController/Admin/Crm/MobileMessageHistory/LayerMemberBizmKakaoAlimTalkHistoryResultController.phpController/Admin/Crm/MobileMessageHistory/LayerMemberCloudKakaoAlimTalkHistoryResultController.phpController/Admin/Crm/MobileMessageHistory/LayerMemberKakaoAlimTalkHistorySearchController.phpController/Admin/Crm/MobileMessageHistory/LayerMemberKakaoFriendTalkHistoryResultController.phpController/Admin/Crm/MobileMessageHistory/LayerMemberKakaoFriendTalkHistorySearchController.phpController/Admin/Crm/MobileMessageHistory/LayerMemberSmsHistoryResultController.phpController/Admin/Crm/MobileMessageHistory/LayerMemberSmsHistorySearchController.phpController/Admin/Crm/MobileMessageHistory/LayerMessageHistoryDetailController.phpController/Admin/Crm/MobileMessageHistory/LayerMessageHistoryDetailResultController.phpController/Admin/Crm/MobileMessageHistory/LayerMessageHistoryExcelDownloadController.phpController/Admin/Crm/MobileMessageHistory/LayerMessageHistoryExcelRequestController.phpController/Admin/Crm/MobileMessageHistory/LayerMessageHistoryExcelRequestPsController.phpController/Admin/Crm/MobileMessageHistory/LayerMessageHistoryExcelRequestResultController.phpController/Admin/Crm/MobileMessageHistory/LayerMyappPushContentsController.phpController/Admin/Crm/MobileMessageHistory/LayerMyappPushHistoryResultController.phpController/Admin/Crm/MobileMessageHistory/LayerMyappPushHistorySearchController.phpController/Admin/Crm/MobileMessageHistory/LayerSmsContentsController.phpController/Admin/Crm/MobileMessageHistory/LayerSmsHistoryResultController.phpController/Admin/Crm/MobileMessageHistory/LayerSmsHistoryResultPsController.phpController/Admin/Crm/MobileMessageHistory/LayerSmsHistorySearchController.phpController/Admin/Crm/MobileMessageHistory/MobileSendController.phpController/Admin/Crm/LayerKakaoAlimtalkContentSettingController.phpController/Admin/Crm/MobileSend/LayerKakaoFriendtalkContentSettingController.phpController/Admin/Crm/MobileSend/LayerMessagePointController.phpController/Admin/Crm/MobileSend/LayerMobileSendMyappPushTemplateController.phpController/Admin/Crm/MobileSend/LayerMobileSendMyappPushTemplateResultController.phpController/Admin/Crm/MobileSend/LayerMobileSendSaveMyappPushTemplateController.phpController/Admin/Crm/MobileSend/LayerMobileSendSaveSmsTemplateController.phpController/Admin/Crm/MobileSend/LayerMobileSendSmsTemplateController.phpController/Admin/Crm/MobileSend/LayerMobileSendSmsTemplateResultController.phpController/Admin/Crm/MobileSend/LayerMyappPushContentSettingController.phpController/Admin/Crm/MobileSend/LayerPreviewMessageController.phpController/Admin/Crm/MobileSend/LayerRecipeSendTimeSettingController.phpController/Admin/Crm/MobileSend/LayerRecipientSettingController.phpController/Admin/Crm/MobileSend/LayerRecipientSettingPsController.phpController/Admin/Crm/MobileSend/LayerSendMethodSettingController.phpController/Admin/Crm/MobileSend/LayerSendTimeSettingController.phpController/Admin/Crm/MobileSend/LayerSmsContentSettingController.phpController/Admin/Crm/MobileSend/MobileSendPsController.phpController/Admin/Crm/MyappPushTemplatePsController.phpController/Admin/Crm/Myapp/MyappPushTemplatePsController.phpController/Admin/Crm/MyappPush/PopupChargeMessagePointsController.phpController/Admin/Crm/PopupSelectMembersController.phpController/Admin/Crm/LayerSelectedMembersResultController.phpController/Admin/Crm/PopupSelectMembers/LayerSelectMembersResultController.phpController/Admin/Crm/PopupSelectMembers/SmsTemplatePsController.phpController/Admin/Crm/Sms/SmsAutoController.phpController/Admin/Member/SmsSendController.phpController/Admin/Member/SmsSendPsController.phpController/Admin/Member/ManagePsController.phpController/Admin/Policy/ManageRegisterController.phpController/Admin/Policy/CartRemindPsController.phpController/Admin/Promotion/CouponPsController.phpController/Admin/Promotion/LayerManagePermissionController.phpController/Admin/Share/LayerSmsContentsController.phpController/Admin/Share/LayerSmsPasswordPsController.phpController/Admin/Share/
변경 파일 코드
추가삭제
SMS 발송 보안 강화
Admin/crm/auto_send_config/auto_send_config_contents.php+2
변경 전auto_send_config_contents.php
<?php include __DIR__ . '/auto_send_config_recipient_checkboxes.php'; ?>
 
 
 
<?php foreach ($autoSendConfigsResponse as $recipient => $autoSendConfig): ?>
<?php include __DIR__ . '/auto_send_config_recipient_section.php'; ?>
<?php endforeach; ?>
 
변경 후auto_send_config_contents.php
<?php include __DIR__ . '/auto_send_config_recipient_checkboxes.php'; ?>
 
<script type="application/json" class="sms-templates-data"><?= json_encode($smsTemplates, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?></script>
 
<?php foreach ($autoSendConfigsResponse as $recipient => $autoSendConfig): ?>
<?php include __DIR__ . '/auto_send_config_recipient_section.php'; ?>
<?php endforeach; ?>
 
Admin/crm/auto_send_config/auto_send_config_preview.php+3-3
변경 전auto_send_config_preview.php
<div class="swiper swiper-initialized swiper-horizontal">
<div class="swiper-wrapper">
<div class="swiper-slide ncua-horizontal-tab__item">
<button type="button" class="ncua-tab-button is-active" data-preview-tab="sms">SMS</button>
</div>
<div class="swiper-slide ncua-horizontal-tab__item">
<button type="button" class="ncua-tab-button" data-preview-tab="kakao">카카오 알림톡</button>
</div>
<div class="swiper-slide ncua-horizontal-tab__item">
<button type="button" class="ncua-tab-button" data-preview-tab="app">마이앱(앱푸시)</button>
</div>
</div>
</div>
 
변경 후auto_send_config_preview.php
<div class="swiper swiper-initialized swiper-horizontal">
<div class="swiper-wrapper">
<div class="swiper-slide ncua-horizontal-tab__item">
<button type="button" class="ncua-tab-button is-active" data-preview-tab="kakao">카카오 알림톡</button>
</div>
<div class="swiper-slide ncua-horizontal-tab__item">
<button type="button" class="ncua-tab-button" data-preview-tab="app">마이앱(앱푸시)</button>
</div>
<div class="swiper-slide ncua-horizontal-tab__item">
<button type="button" class="ncua-tab-button" data-preview-tab="sms">SMS</button>
</div>
</div>
</div>
 
Admin/crm/auto_send_config/auto_send_config_recipient_section.php+77-29
변경 전auto_send_config_recipient_section.php
// MYAPP_PUSH는 member인 경우에만 표시
$filteredChannels = array_filter($channels, fn($channel) => $channel->name !== 'MYAPP_PUSH' || in_array($recipient, $myappSupportRecipients));
$isSingleChannel = count($filteredChannels) === 1;
foreach ($filteredChannels as $channel) {
if ($isSingleChannel) {
$isChecked = true;
$isDisabled = true;
</span>
<span class="ncua-checkbox-field__text"><?= $channel->getTitle() ?></span>
</label>
<?php } ?>
</div>
<div class="channel-hint" hidden>
<span class="ncua-hint-text destructive ncua-input__hint-text">발송수단을 선택해 주세요.</span>
<div class="ncua-horizontal-tab ncua-horizontal-tab--panel">
<div class="swiper swiper-initialized swiper-horizontal">
<div class="swiper-wrapper">
<?php foreach ($filteredChannels as $channel) { ?>
<div class="swiper-slide ncua-horizontal-tab__item">
<button type="button" class="ncua-tab-button is-active" data-tab-target="<?= $channel->name ?>"><?= $channel->getTitle() ?></button>
</div>
<?php } ?>
</div>
</div>
</div>
</div>
<?php endif; ?>
 
<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>
</section>
<section class="ncua-card__body-block-section">
<div id="sms-chip-selector-container-<?= $recipient ?>"></div>
</section>
 
</div>
<div class="ncua-card__body-block-section-actions">
<button type="button" class="ncua-btn ncua-btn--sm ncua-btn--secondary" onclick="load_as_template('<?= $recipient ?>', 'SMS')">템플릿 불러오기</button>
<button type="button" class="ncua-btn ncua-btn--sm ncua-btn--secondary-gray file-btn" onclick="save_as_template('<?= $recipient ?>', 'SMS')">템플릿으로 저장하기</button>
 
 
 
 
 
 
 
 
</div>
</div>
<?php endif; ?>
<?php if (!empty($autoSendConfig['KAKAO_ALRIM_TALK'])): ?>
<!-- 카카오 알림톡 -->
<div class="tab-content-item" data-tab="KAKAO_ALRIM_TALK" style="display: none;">
<script type="application/json" class="kakao-templates-data"><?= json_encode($kakaoTemplates, JSON_UNESCAPED_UNICODE) ?></script>
<?php if (!$kakaoCrmUseFlag): ?>
<div class="no-ready-send-type">
<p class="ncua-caution-text">
 
변경 후auto_send_config_recipient_section.php
// MYAPP_PUSH는 member인 경우에만 표시
$filteredChannels = array_filter($channels, fn($channel) => $channel->name !== 'MYAPP_PUSH' || in_array($recipient, $myappSupportRecipients));
$isSingleChannel = count($filteredChannels) === 1;
foreach ($filteredChannels as $channel):
if ($isSingleChannel) {
$isChecked = true;
$isDisabled = true;
</span>
<span class="ncua-checkbox-field__text"><?= $channel->getTitle() ?></span>
</label>
<?php endforeach; ?>
</div>
<div class="channel-hint" hidden>
<span class="ncua-hint-text destructive ncua-input__hint-text">발송수단을 선택해 주세요.</span>
<div class="ncua-horizontal-tab ncua-horizontal-tab--panel">
<div class="swiper swiper-initialized swiper-horizontal">
<div class="swiper-wrapper">
<?php foreach ($filteredChannels as $channel): ?>
<div class="swiper-slide ncua-horizontal-tab__item">
<button type="button" class="ncua-tab-button is-active" data-tab-target="<?= $channel->name ?>"><?= $channel->getTitle() ?></button>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<?php endif; ?>
 
<?php
$smsSelection = $smsTemplateSelections[$recipient];
$smsCurrentCategory = $smsSelection['category'];
$smsCurrentType = $smsSelection['templateType'];
$smsSelectedSno = $smsSelection['selectedSno'];
$smsVisibleTemplates = $smsSelection['visibleTemplates'];
?>
<section class="ncua-card__body-block-section">
<div class="ncua-card__body-title--xs tooltip-align" data-tooltip-seq="009">내용 입력</div>
<div class="ncua-border-content-layout" <?= $isSingleChannel ? 'data-sms-template-restrict-target' : '' ?>>
<div class="ncua-border-content">
<div class="ncua-border-content-title required">카테고리</div>
<div class="ncua-border-content-form ncua-flex ncua-flex-wrap ncua-flex-gap">
<?php foreach ($smsTemplateCategories as $smsCategory): ?>
<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" class="sms-template-category-radio" name="recipients[<?= $recipient ?>][SMS][category]" value="<?= $smsCategory['value'] ?>" data-recipient="<?= $recipient ?>" <?= $smsCurrentCategory === $smsCategory['value'] ? 'checked' : '' ?> />
</span>
<span class="ncua-radio-field__text"><?= $smsCategory['name'] ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
<div class="ncua-border-content">
<div class="ncua-border-content-title required">템플릿 명</div>
<div class="ncua-flex ncua-flex-column ncua-gap-8">
<div class="ncua-border-content-form ncua-flex 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" class="sms-template-type-radio" name="recipients[<?= $recipient ?>][SMS][templateType]" value="godomall" data-recipient="<?= $recipient ?>" <?= $smsCurrentType === 'godomall' ? '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" class="sms-template-type-radio" name="recipients[<?= $recipient ?>][SMS][templateType]" value="user" data-recipient="<?= $recipient ?>" <?= $smsCurrentType === 'user' ? 'checked' : '' ?> />
</span>
<span class="ncua-radio-field__text">사용자 템플릿</span>
</label>
</div>
<div class="ncua-border-content-form ncua-flex ncua-gap-4">
<input type="hidden" class="sms-selected-template-godomall" data-recipient="<?= $recipient ?>" value="<?= $smsCurrentType === 'godomall' && $smsSelectedSno > 0 ? $smsSelectedSno : '' ?>" />
<input type="hidden" class="sms-selected-template-user" data-recipient="<?= $recipient ?>" value="<?= $smsCurrentType === 'user' && $smsSelectedSno > 0 ? $smsSelectedSno : '' ?>" />
<input type="hidden" class="sms-template-cleared" name="recipients[<?= $recipient ?>][SMS][templateCleared]" data-recipient="<?= $recipient ?>" value="n" />
<div class="ncua-select ncua-select--xs sms-template-select">
<div class="ncua-select__content">
<select class="ncua-select__tag sms-template-dropdown" name="recipients[<?= $recipient ?>][SMS][sourceTemplateSno]" data-recipient="<?= $recipient ?>">
<option value="">템플릿을 선택하세요</option>
<?php foreach ($smsVisibleTemplates as $smsTemplate): ?>
<option value="<?= $smsTemplate['sno'] ?>" <?= $smsTemplate['sno'] === $smsSelectedSno ? 'selected' : '' ?>><?= htmlspecialchars($smsTemplate['name'], ENT_QUOTES) ?></option>
<?php endforeach; ?>
<?php if ($smsVisibleTemplates === []): ?>
<option value="" disabled>사용할 수 있는 템플릿이 없습니다.</option>
<?php endif; ?>
</select>
</div>
</div>
</div>
</div>
 
 
 
 
 
 
</div>
</div>
</section>
 
<div class="ncua-input ncua-input--textarea ncua-input--textarea--xs sms-template-title">
<textarea class="ncua-input__textarea" rows="1" readonly><?= htmlspecialchars($smsSelection['selectedLmsTitle'], ENT_QUOTES) ?></textarea>
</div>
 
<div class="ncua-input ncua-input--textarea ncua-input--textarea--xs sms-template-preview">
<textarea class="ncua-input__textarea" readonly><?= htmlspecialchars($smsSelection['selectedContents'], ENT_QUOTES) ?></textarea>
</div>
 
<div class="ncua-accordion ncua-accordion--gray notice-accordion">
<div class="ncua-accordion__summary">SMS/LMS 발송 안내</div>
<ul class="ncua-accordion__content">
<li class="ncua-notice-info">승인된 템플릿만 자동 알림으로 발송할 수 있습니다.</li>
<li class="ncua-notice-info">SMS/LMS 템플릿 등록·관리·승인은 <a class="ncua-link" href="./message_template.php?sendMethod=sms" target="_blank">템플릿 관리</a>에서 가능합니다.</li>
</ul>
</div>
</div>
<?php endif; ?>
<?php if (!empty($autoSendConfig['KAKAO_ALRIM_TALK'])): ?>
<!-- 카카오 알림톡 -->
<div class="tab-content-item" data-tab="KAKAO_ALRIM_TALK" style="display: none;">
<script type="application/json" class="kakao-templates-data"><?= json_encode($kakaoTemplates, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?></script>
<?php if (!$kakaoCrmUseFlag): ?>
<div class="no-ready-send-type">
<p class="ncua-caution-text">
 
Admin/crm/auto_send_config/layer_auto_send_modify_kakao_template.php-384
변경 전layer_auto_send_modify_kakao_template.php
<div class="modal-dialog__content layer_update_kakao_template">
<div class="ncua-table ncua-table--vertical">
<table>
<tbody>
<tr>
<th class="ncua-required"><div>구분</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="kakaoType" />
</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="kakaoType" />
</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="kakaoType" />
</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="kakaoType" />
</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="kakaoType" />
</span>
<span class="ncua-radio-field__text">게시판</span>
</label>
</div>
</td>
</tr>
<tr>
<th class="ncua-required"><div>템플릿 카테고리</div></th>
<td>
<div class="ncua-flex-gap">
<div class="ncua-select ncua-select--xs">
<div class="ncua-select__content">
<select class="ncua-select__tag">
<option value="">대분류</option>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</select>
</div>
</div>
<div class="ncua-select ncua-select--xs">
<div class="ncua-select__content">
<select class="ncua-select__tag" disabled>
<option value="">중분류</option>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</select>
</div>
</div>
</div>
</td>
</tr>
<tr>
<th class="ncua-required"><div>템플릿 제목</div></th>
<td>
<div>
<div class="ncua-input ncua-input--xs ">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs ncua-input-width-520">
<input maxlength="10" type="text" placeholder="제목을 입력하세요" class="kakao-template-title" data-charcount-key="kakaoTemplateTitle" /></div>
</div>
<div class="ncua-input__field-text-count" data-charcount-text="kakaoTemplateTitle">
<output class="ncua-input__field-text-count-current">0</output>
<span>/10</span>
</div>
</div>
<!-- NOTE [저장] 유효성 검사 : 내용을 입력하지 않은 경우 -->
<span class="ncua-hint-text destructive ncua-input__hint-text">템플릿 제목을 입력해 주세요.</span>
</div>
</div>
</td>
</tr>
<tr>
<th><div>보안 템플릿 설정</div></th>
<td>
<div>
<div class="ncua-switch ncua-switch--xs">
<label class="ncua-switch__option ncua-switch__option--left ncua-switch__option--active">
<input class="ncua-switch__radio ncua-switch__radio--left" type="radio" value="y" checked="" name="demo-switch" />
<span class="ncua-switch__label">사용함</span>
</label>
<label class="ncua-switch__option ncua-switch__option--right ncua-switch__option--inactive">
<input class="ncua-switch__radio ncua-switch__radio--right" type="radio" value="n" name="demo-switch" />
<span class="ncua-switch__label">사용안함</span>
</label>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
 
<div class="content-border-box">
<div class="template_type">
<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="templateType" />
</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="templateType" />
</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="templateType" />
</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="templateType" />
</span>
<span class="ncua-radio-field__text">아이템리스트형</span>
</label>
</div>
 
<div class="template_content ncua-card__body-block-section-wrap">
<section class="ncua-card__body-block-section">
<div class="ncua-card__body-title--xs">본문 입력</div>
<div class="ncua-input ncua-input--textarea ncua-input--textarea--xs">
<textarea class="ncua-input__textarea kakao-template-textarea-content" placeholder="템플릿 본문을 작성해 주세요." maxlength="1000"></textarea>
<div class="ncua-input__text-count-wrap">
<div class="ncua-hint-text">본문을 입력해 주세요.</div>
<div class="ncua-input__text-count">
<span class="ncua-input__text-count-text">
<span class="ncua-input__text-count-text-count">0</span>
/1000
</span>
</div>
</div>
</div>
</section>
 
<section class="ncua-card__body-block-section">
<div class="ncua-card__body-title--xs" data-tooltip-seq="008">사용 가능한 변수</div>
<div class="chip-selector">
<div class="ncua-select ncua-select--xs">
<div class="ncua-select__content">
<select class=" ncua-select__tag">
<option value="1">회원</option>
<option value="2">게시판</option>
<option value="3">통계</option>
</select>
</div>
</div>
<div class="chip-button-wrap">
<button class="chip-button"><span class="chip-button-text">{rc_mallNm} 쇼핑몰 명/상점명</span></button>
<button class="chip-button"><span class="chip-button-text">{memId} 회원 아이디</span></button>
<button class="chip-button"><span class="chip-button-text">{memNm} 회원명</span></button>
<button class="chip-button"><span class="chip-button-text">{sleepScheduleDt} 휴면회원 전환 예정 일</span></button>
<button class="chip-button"><span class="chip-button-text">{rc_scheduleDt} 일반 회원 전환 일</span></button>
<button class="chip-button"><span class="chip-button-text">{smsAgreementDt} SMS 수신동의일</span></button>
<button class="chip-button"><span class="chip-button-text">{mailAgreementDt}메일 수신동의일</span></button>
<button class="chip-button"><span class="chip-button-text">{mileage} 보유한 마일리지</span></button>
<button class="chip-button"><span class="chip-button-text">{deposit}보유한 예치금</span></button>
<button class="chip-button"><span class="chip-button-text">{groupNm} 회원등급</span></button>
<button class="chip-button"><span class="chip-button-text">{rc_mileage} 보유한 마일리지</span></button>
<button class="chip-button"><span class="chip-button-text">{rc_deposit}보유한 예치금</span></button>
<button class="chip-button"><span class="chip-button-text">{rc_certificationCode} 인증코드</span></button>
<button class="chip-button"><span class="chip-button-text">{wriNm} 작성자</span></button>
<button class="chip-button"><span class="chip-button-text">{orderNo} 주문번호</span></button>
<button class="chip-button"><span class="chip-button-text">{orderName} 주문명</span></button>
<button class="chip-button"><span class="chip-button-text">{settlePrice} 결제금액</span></button>
<button class="chip-button"><span class="chip-button-text">{bankAccount} 입금계좌번호</span></button>
</div>
</div>
</section>
 
<section class="ncua-card__body-block-section">
<div class="ncua-card__body-title-wrap has-checkbox">
<span class="ncua-card__body-title--xs">부가 정보 문구</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" />
</span>
<span class="ncua-checkbox-field__text">추가</span>
</label>
</div>
<div class="ncua-input ncua-input--textarea ncua-input--textarea--xs">
<textarea class="ncua-input__textarea kakao-template-textarea-add-info" placeholder="템플릿 본문을 작성해 주세요." maxlength="200"></textarea>
<div class="ncua-input__text-count-wrap">
<!-- NOTE [저장] 유효성 검사 : 내용을 입력하지 않은 경우 -->
<div class="ncua-hint-text destructive">본문을 입력해 주세요.</div>
<div class="ncua-input__text-count">
<span class="ncua-input__text-count-text">
<span class="ncua-input__text-count-text-count">0</span>
/200
</span>
</div>
</div>
</div>
</section>
 
<section class="ncua-card__body-block-section channel_add_message">
<div class="ncua-card__body-title-wrap has-checkbox">
<span class="ncua-card__body-title--xs">채널 추가 문구(고정 내용)</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" />
</span>
<span class="ncua-checkbox-field__text">추가</span>
</label>
</div>
<div class="ncua-input ncua-input--textarea ncua-input--textarea--xs">
<textarea class="ncua-input__textarea" maxlength="36" readonly>채널 추가하고 이 채널의 광고와 마케팅을 카카오톡으로 받기</textarea>
<div class="ncua-input__text-count-wrap">
<div class="ncua-hint-text"></div>
<div class="ncua-input__text-count">
<span class="ncua-input__text-count-text">
<span class="ncua-input__text-count-text-count">0</span>
/36
</span>
</div>
</div>
</div>
</section>
 
<section class="ncua-card__body-block-section">
<div class="ncua-card__body-title-wrap">
<span class="ncua-card__body-title--xs">버튼</span>
<div>
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray auto-send-icon auto-send-icon--plus">
<span class="ncua-btn__label">페이지 링크 추가</span>
</button>
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray auto-send-icon auto-send-icon--plus">
<span class="ncua-btn__label">배송 조회 추가</span>
</button>
</div>
</div>
<div class="ncua-table ncua-table--horizontal">
<table>
<colgroup>
<col width="79px">
<col width="auto">
<col width="auto">
<col width="88px">
</colgroup>
<thead>
<tr>
<th><div class="ncua-align-center">타입</div></th>
<th class="ncua-required"><div>버튼명</div></th>
<th class="ncua-required"><div>URL</div></th>
<th><div class="ncua-align-center">관리</div></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="ncua-align-center">페이지 링크</div>
</td>
<td>
<div>
<div class="ncua-input ncua-input--xs ncua-input--full-width-with-count">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input maxlength="20" type="text" placeholder="버튼명을 입력해주세요" class="kakao-button-page-link" data-charcount-key="kakaoButtonPageLink" />
</div>
</div>
<div class="ncua-input__field-text-count" data-charcount-text="kakaoButtonPageLink">
<output class="ncua-input__field-text-count-current">0</output>
<span>/20</span>
</div>
</div>
</div>
<!-- NOTE [저장] 유효성 검사 : 내용을 입력하지 않은 경우 -->
<span class="ncua-hint-text destructive ncua-input__hint-text">버튼명을 입력해 주세요.</span>
</div>
</td>
<td>
<div>
<div class="ncua-input ncua-input--xs">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" placeholder="버튼명을 입력해주세요" />
</div>
</div>
</div>
<span class="ncua-hint-text ncua-input__hint-text">외부 링크 입력 시 캠페인 통계가 집계되지 않습니다.</span>
</div>
</div>
</td>
<td>
<div class="ncua-align-center">
<button type="button" class="ncua-btn ncua-btn--xxs ncua-btn--secondary-gray auto-send-icon auto-send-icon--minus">
<span class="ncua-btn__label">삭제</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="ncua-table ncua-table--horizontal">
<table>
<colgroup>
<col width="79px">
<col width="auto">
<col width="auto">
<col width="88px">
</colgroup>
<thead>
<tr>
<th><div class="ncua-align-center">타입</div></th>
<th class="ncua-required"><div>버튼명</div></th>
<th class="ncua-required"><div>URL</div></th>
<th><div class="ncua-align-center">관리</div></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="ncua-align-center">배송 조회</div>
</td>
<td>
<div>
<div class="ncua-input ncua-input--xs ncua-input--full-width-with-count">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input maxlength="20" type="text" placeholder="버튼명을 입력해주세요" class="kakao-button-delivery-search" data-charcount-key="kakaoButtonDeliverySearch" />
</div>
</div>
<div class="ncua-input__field-text-count" data-charcount-text="kakaoButtonDeliverySearch">
<output class="ncua-input__field-text-count-current">0</output>
<span>/20</span>
</div>
</div>
</div>
<!-- NOTE [저장] 유효성 검사 : 내용을 입력하지 않은 경우 -->
<span class="ncua-hint-text destructive ncua-input__hint-text">버튼명을 입력해 주세요.</span>
</div>
</td>
<td>
<div>
<p>본문에 택배사명과 송장번호가 포함되면 버튼이 카카오 배송조회 페이지로 연결됩니다. 단, 미지원 택배사는 조회되지 않습니다.</p>
</div>
</td>
<td>
<div class="ncua-align-center">
<button type="button" class="ncua-btn ncua-btn--xxs ncua-btn--secondary-gray minus-icon">
<span class="ncua-btn__label">삭제</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</section>
</div>
</div>
</div>
<div class="modal-dialog__footer">
<button type="button" class="ncua-btn ncua-btn--sm ncua-btn--secondary-gray js-layer-close">
<span class="ncua-btn__label">취소</span>
</button>
<button type="button" class="ncua-btn ncua-btn--sm ncua-btn--primary js-layer-close">
<span class="ncua-btn__label">저장</span>
</button>
</div>
 
변경 후layer_auto_send_modify_kakao_template.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Admin/crm/auto_send_config.php+222-123
변경 전auto_send_config.php
<script defer src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/ncds-jquery-validator.js')?>"></script>
<script defer src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/unsaved-changes-guard.js')?>"></script>
<script defer src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/scroll-sticky.js')?>"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<!-- CRM Preview -->
<script src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/godo-ui-module/crm-preview/crm-preview-loader.js')?>" data-exclude="friendtalk"></script>
<script defer src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/godo-ui-module/chip-selector/chip-selector.js')?>"></script>
<script type="text/javascript">
const AUTO_SEND_CODE = '<?= $code ?>';
const USE_JOIN_POLICY = <?= $useJoinPolicy ? 'true' : 'false' ?>;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 뒤로가기 버튼 핸들러
// 카카오 알림톡 구분(카테고리) 변경 시 템플릿 드롭다운 업데이트
initKakaoCategoryChange();
 
 
 
// 스크롤 시 미리보기 섹션 전환 초기화
initPreviewScrollSync();
 
const recipient = section.dataset.targetType;
 
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;
messageInputInstances[recipient] = {};
chipSelectorInstances[recipient] = {};
 
// SMS MessageInput
const smsContainer = document.getElementById(`sms-message-input-container-${recipient}`);
if (smsContainer) {
const smsInitialValue = normalizeNewlines(smsContainer.dataset.initialValue || '');
const smsInput = GodoUIModule.render({
type: 'MessageInput',
target: `#sms-message-input-container-${recipient}`,
name: `recipients[${recipient}][SMS][templateContent]`,
maxLength: 2000,
useBytes: true,
hintText: 'SMS 건당 1포인트 차감',
initialValue: smsInitialValue,
allowEmoji: false,
onInput: (input) => {
const normalized = normalizeNewlines(input);
if (normalized !== input && smsInput.textarea) {
const pos = Math.max(0, smsInput.textarea.selectionStart - (input.length - normalized.length));
smsInput.textarea.value = normalized;
smsInput.textarea.setSelectionRange(pos, pos);
smsInput.updateCount();
}
PreviewFacade.setPreview(smsContainer.closest('[data-target-type]'), { content: normalized });
// 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]`);
if (caution) caution.style.display = 'none';
crmPreview.setContent('메시지 내용을 입력하세요.');
}
}
});
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');
if (smsInitialValue) {
const byteLength = smsInput.calculateLength(smsInitialValue);
smsInput.setHint(byteLength > 90 ? 'LMS 건당 3포인트 차감' : 'SMS 건당 1포인트 차감');
}
}
 
// SMS ChipSelector
const smsChipContainer = document.getElementById(`sms-chip-selector-container-${recipient}`);
if (smsChipContainer) {
chipSelectorInstances[recipient].SMS = GodoUIModule.render({
type: 'ChipSelector',
target: `#sms-chip-selector-container-${recipient}`,
title: '사용 가능한 변수',
tooltipSeq: '008',
cautionHTML: cautionHTML,
categories: channelChipSelectorCategories['SMS'] || [],
onSelect: (variable) => {
messageInputInstances[recipient]?.SMS?.insertText(variable);
}
});
}
 
// MYAPP MessageInput
const myappContainer = document.getElementById(`myapp-message-input-container-${recipient}`);
if (myappContainer) {
 
let isSubmitting = false;
 
const submitForm = (form) => {
if (isSubmitting) return;
 
isSubmitting = true;
 
const formData = new FormData(form);
if (!section) continue;
const smsCheckbox = section.querySelector('input[type="checkbox"][name$="[SMS][isEnabled]"]:checked');
if (!smsCheckbox) continue;
const input = messageInputInstances[recipient]?.SMS;
if (input && input.getValue().trim() === '') return section;
}
return null;
};
if (section.dataset[dataAttr] !== 'true') continue;
const channelCheckbox = section.querySelector(`input[type="checkbox"][name$="[${channel}][isEnabled]"]:checked`);
if (!channelCheckbox) continue;
const input = messageInputInstances[recipient]?.SMS;
if (input && input.getValue().trim() === '') return true;
}
}
return false;
if (!section) return;
 
const channel = tabButton.dataset.tabTarget;
 
 
// 모든 탭 숨기고 버튼 비활성화
section.querySelectorAll('.tab-content-item').forEach(item => item.style.display = 'none');
tabButtons.forEach((button) => {
button.addEventListener('click', (event) => {
const targetTab = event.currentTarget.getAttribute('data-preview-tab');
 
 
tabButtons.forEach(btn => btn.classList.remove('is-active'));
event.currentTarget.classList.add('is-active');
};
 
// 카카오 템플릿 드롭다운 업데이트 공통 함수
const updateKakaoTemplateDropdown = (tabContent, recipient, kakaoTemplates, selectedCategory, selectedType, shouldResetSelection = false) => {
const dropdown = tabContent.querySelector('.kakao-template-dropdown');
if (!dropdown) return;
 
return selectedType === 'godomall' ? isGodomall : !isGodomall;
});
 
const currentHiddenInput = tabContent.querySelector(`.kakao-selected-template-${selectedType}[data-recipient="${recipient}"]`);
const savedTemplateCode = shouldResetSelection ? '' : (currentHiddenInput ? currentHiddenInput.value : '');
 
filteredTemplates.forEach((template) => {
const selectedCategory = event.target.value;
const section = event.target.closest('[data-target-type]');
if (!section) return;
const recipient = section.dataset.targetType;
 
const tabContent = section.querySelector('[data-tab="KAKAO_ALRIM_TALK"]');
if (!tabContent) return;
}
 
// 구분 변경 시 템플릿 선택 초기화 (shouldResetSelection = true)
updateKakaoTemplateDropdown(tabContent, recipient, kakaoTemplates, selectedCategory, selectedType, true);
});
});
 
if (!dropdown) return;
 
// 직전 선택 템플릿 저장용 hidden input
const previousHiddenInput = tabContent.querySelector(`.kakao-selected-template-${previousType}[data-recipient="${recipient}"]`);
 
// 현재 선택된 템플릿을 이전 유형의 hidden input에 저장
if (previousHiddenInput && dropdown.value) {
}
 
// 템플릿 타입 변경 시에는 저장된 값 유지 시도
updateKakaoTemplateDropdown(tabContent, recipient, kakaoTemplates, selectedCategory, selectedType, false);
});
});
 
const currentType = checkedRadio ? checkedRadio.value : 'godomall';
 
// 현재 유형의 hidden input에 선택값 저장
const currentHiddenInput = tabContent.querySelector(`.kakao-selected-template-${currentType}[data-recipient="${recipient}"]`);
if (currentHiddenInput) {
currentHiddenInput.value = selectedCode;
}
});
};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
let isLoadingTemplate = false;
 
const load_as_template = (recipient, channel) => {
}
 
// 템플릿 불러오기 후 안내문구 표시
const containerSelector = channel === 'SMS'
? `#sms-chip-selector-container-${recipient}`
: `#myapp-chip-selector-container-${recipient}`;
const cautionEl = document.querySelector(`${containerSelector} [data-caution-template]`);
if (cautionEl) cautionEl.style.display = '';
};
 
 
변경 후auto_send_config.php
<script defer src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/ncds-jquery-validator.js')?>"></script>
<script defer src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/unsaved-changes-guard.js')?>"></script>
<script defer src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/scroll-sticky.js')?>"></script>
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/crm/sms-send-gate.js') ?>"></script>
<script type="text/javascript">
$(function () {
SmsSendGate.useNcdsDialog();
SmsSendGate.applyRestrictOnAjax('<?= gd_isset($smsSendRestrictType, '') ?>', {
tooltip: '<?= gd_isset($smsSendRestrictTooltip, '') ?>',
selectors: ['input[type="checkbox"][name$="[SMS][isEnabled]"]']
});
SmsSendGate.bindClickGate({
message: '<?= gd_isset($smsSendRestrictAlertMessage, '') ?>',
subMessage: '<?= gd_isset($smsSendRestrictAlertSubMessage, '') ?>',
selectors: ['[data-sms-template-restrict-target]']
});
});
</script>
<!-- CRM Preview -->
<script src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/godo-ui-module/crm-preview/crm-preview-loader.js')?>" data-exclude="friendtalk"></script>
<script defer src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/godo-ui-module/chip-selector/chip-selector.js')?>"></script>
<script type="text/javascript">
const AUTO_SEND_CODE = '<?= $code ?>';
const USE_JOIN_POLICY = <?= $useJoinPolicy ? 'true' : 'false' ?>;
const IS_SMS_TAB_ACCESSIBLE = <?= json_encode($isSmsTabAccessible) ?>;
const SMS_NO_ROLE_MESSAGE = '<?= gd_htmlspecialchars_stripslashes($smsNoRoleMessage) ?>';
const SMS_NO_ROLE_SUB_MESSAGE = '<?= gd_htmlspecialchars_stripslashes(gd_isset($smsNoRoleSubMessage, '')) ?>';
 
const SMS_RESTRICT_ALERT_MESSAGE = '<?= gd_htmlspecialchars_stripslashes(gd_isset($smsSendRestrictAlertMessage, '')) ?>';
const SMS_RESTRICT_ALERT_SUB_MESSAGE = '<?= gd_htmlspecialchars_stripslashes(gd_isset($smsSendRestrictAlertSubMessage, '')) ?>';
 
// 탭은 노출 유지가 스펙이라 렌더 차단 대신 클릭 진입점에서 막는다
const blockSmsTabWithoutRole = () => {
if (SMS_RESTRICT_ALERT_MESSAGE) {
NCDSAlert({message: SMS_RESTRICT_ALERT_MESSAGE, subMessage: SMS_RESTRICT_ALERT_SUB_MESSAGE, iconType: 'error'});
return true;
}
if (IS_SMS_TAB_ACCESSIBLE) return false;
NCDSAlert({message: SMS_NO_ROLE_MESSAGE, subMessage: SMS_NO_ROLE_SUB_MESSAGE, iconType: 'error'});
return true;
};
 
/**
* 뒤로가기 버튼 핸들러
// 카카오 알림톡 구분(카테고리) 변경 시 템플릿 드롭다운 업데이트
initKakaoCategoryChange();
 
initSmsCategoryChange();
 
// 스크롤 시 미리보기 섹션 전환 초기화
initPreviewScrollSync();
 
const recipient = section.dataset.targetType;
 
if (tabInfo.channel === 'SMS') {
const tabContent = section.querySelector('[data-tab="SMS"]');
if (!tabContent) return;
 
const dropdown = tabContent.querySelector('.sms-template-dropdown');
const template = findSmsTemplateBySno(tabContent, dropdown?.value);
const smsContent = template ? normalizeNewlines(template.contents) : '';
this.setPreview(section, { title: getSmsLmsTitle(template), content: smsContent || '메시지 내용을 입력하세요.' });
} else if (tabInfo.channel === 'KAKAO_ALRIM_TALK') {
const tabContent = section.querySelector('[data-tab="KAKAO_ALRIM_TALK"]');
if (!tabContent) return;
messageInputInstances[recipient] = {};
chipSelectorInstances[recipient] = {};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
// MYAPP MessageInput
const myappContainer = document.getElementById(`myapp-message-input-container-${recipient}`);
if (myappContainer) {
 
let isSubmitting = false;
 
const submitForm = async (form) => {
if (isSubmitting) return;
 
isSubmitting = true;
 
const formData = new FormData(form);
if (!section) continue;
const smsCheckbox = section.querySelector('input[type="checkbox"][name$="[SMS][isEnabled]"]:checked');
if (!smsCheckbox) continue;
const dropdown = section.querySelector('.sms-template-dropdown');
if (dropdown && dropdown.value === '') return section;
}
return null;
};
if (section.dataset[dataAttr] !== 'true') continue;
const channelCheckbox = section.querySelector(`input[type="checkbox"][name$="[${channel}][isEnabled]"]:checked`);
if (!channelCheckbox) continue;
const dropdown = section.querySelector('.sms-template-dropdown');
if (dropdown && dropdown.value === '') return true;
}
}
return false;
if (!section) return;
 
const channel = tabButton.dataset.tabTarget;
if (channel === 'SMS' && blockSmsTabWithoutRole()) return;
 
// 모든 탭 숨기고 버튼 비활성화
section.querySelectorAll('.tab-content-item').forEach(item => item.style.display = 'none');
tabButtons.forEach((button) => {
button.addEventListener('click', (event) => {
const targetTab = event.currentTarget.getAttribute('data-preview-tab');
if (targetTab === 'sms' && blockSmsTabWithoutRole()) return;
 
tabButtons.forEach(btn => btn.classList.remove('is-active'));
event.currentTarget.classList.add('is-active');
};
 
// 카카오 템플릿 드롭다운 업데이트 공통 함수
const updateKakaoTemplateDropdown = (tabContent, kakaoTemplates, selectedCategory, selectedType, shouldResetSelection = false) => {
const dropdown = tabContent.querySelector('.kakao-template-dropdown');
if (!dropdown) return;
 
return selectedType === 'godomall' ? isGodomall : !isGodomall;
});
 
const currentHiddenInput = tabContent.querySelector(`.kakao-selected-template-${selectedType}`);
const savedTemplateCode = shouldResetSelection ? '' : (currentHiddenInput ? currentHiddenInput.value : '');
 
filteredTemplates.forEach((template) => {
const selectedCategory = event.target.value;
const section = event.target.closest('[data-target-type]');
if (!section) return;
 
 
const tabContent = section.querySelector('[data-tab="KAKAO_ALRIM_TALK"]');
if (!tabContent) return;
}
 
// 구분 변경 시 템플릿 선택 초기화 (shouldResetSelection = true)
updateKakaoTemplateDropdown(tabContent, kakaoTemplates, selectedCategory, selectedType, true);
});
});
 
if (!dropdown) return;
 
// 직전 선택 템플릿 저장용 hidden input
const previousHiddenInput = tabContent.querySelector(`.kakao-selected-template-${previousType}`);
 
// 현재 선택된 템플릿을 이전 유형의 hidden input에 저장
if (previousHiddenInput && dropdown.value) {
}
 
// 템플릿 타입 변경 시에는 저장된 값 유지 시도
updateKakaoTemplateDropdown(tabContent, kakaoTemplates, selectedCategory, selectedType, false);
});
});
 
const currentType = checkedRadio ? checkedRadio.value : 'godomall';
 
// 현재 유형의 hidden input에 선택값 저장
const currentHiddenInput = tabContent.querySelector(`.kakao-selected-template-${currentType}`);
if (currentHiddenInput) {
currentHiddenInput.value = selectedCode;
}
});
};
 
let smsTemplatesCache;
const parseSmsTemplates = () => {
if (smsTemplatesCache !== undefined) return smsTemplatesCache;
 
const dataScript = document.querySelector('.sms-templates-data');
if (!dataScript) {
smsTemplatesCache = null;
return smsTemplatesCache;
}
 
try {
smsTemplatesCache = JSON.parse(dataScript.textContent);
} catch (e) {
console.error('SMS 템플릿 데이터 파싱 오류:', e);
smsTemplatesCache = null;
}
 
return smsTemplatesCache;
};
 
const getSmsLmsTitle = (template) => template?.lmsTitle || '';
 
const setSmsTemplatePreview = (tabContent, template) => {
const lmsTitle = getSmsLmsTitle(template);
const titleTextarea = tabContent.querySelector('.sms-template-title textarea');
if (titleTextarea) titleTextarea.value = lmsTitle;
 
const textarea = tabContent.querySelector('.sms-template-preview textarea');
const contents = template ? normalizeNewlines(template.contents) : '';
if (textarea) textarea.value = contents;
 
PreviewFacade.setPreview(tabContent.closest('[data-target-type]'), {
title: lmsTitle,
content: contents || '메시지 내용을 입력하세요.'
});
};
 
const getSmsSelectedType = (tabContent) => {
const checked = tabContent.querySelector('.sms-template-type-radio:checked');
return checked ? checked.value : 'godomall';
};
 
const getSmsSelectedCategory = (tabContent) => {
const checked = tabContent.querySelector('.sms-template-category-radio:checked');
return checked ? checked.value : '';
};
 
const findSmsTemplateBySno = (tabContent, sno) => {
const smsTemplates = parseSmsTemplates();
if (!smsTemplates || !sno) return null;
 
for (const templates of Object.values(smsTemplates)) {
const found = templates.find(t => String(t.sno) === String(sno));
if (found) return found;
}
return null;
};
 
const setSmsTemplateCleared = (tabContent, value) => {
const clearedInput = tabContent.querySelector('.sms-template-cleared');
if (clearedInput) {
clearedInput.value = value;
}
};
 
const updateSmsTemplateDropdown = (tabContent, selectedCategory, selectedType, shouldResetSelection = false) => {
const dropdown = tabContent.querySelector('.sms-template-dropdown');
const smsTemplates = parseSmsTemplates();
if (!dropdown || !smsTemplates) return;
 
dropdown.innerHTML = '<option value="">템플릿을 선택하세요</option>';
 
const filteredTemplates = (smsTemplates[selectedCategory] || [])
.filter(template => (selectedType === 'godomall') === Boolean(template.isProvided));
 
const hiddenInput = tabContent.querySelector(`.sms-selected-template-${selectedType}`);
const initialSno = hiddenInput?.dataset.initialSno || '';
const restoredSno = shouldResetSelection && initialSno && filteredTemplates.some(template => String(template.sno) === initialSno)
? initialSno
: '';
const savedSno = shouldResetSelection ? restoredSno : (hiddenInput ? hiddenInput.value : '');
 
filteredTemplates.forEach(template => {
const option = document.createElement('option');
option.value = template.sno;
option.textContent = template.name;
if (savedSno && String(template.sno) === String(savedSno)) {
option.selected = true;
}
dropdown.appendChild(option);
});
 
if (filteredTemplates.length === 0) {
const emptyOption = document.createElement('option');
emptyOption.value = '';
emptyOption.disabled = true;
emptyOption.textContent = '사용할 수 있는 템플릿이 없습니다.';
dropdown.appendChild(emptyOption);
}
 
if (shouldResetSelection) {
if (hiddenInput) {
hiddenInput.value = savedSno;
}
setSmsTemplateCleared(tabContent, savedSno ? 'n' : 'y');
}
 
const selectedTemplate = savedSno
? filteredTemplates.find(template => String(template.sno) === String(savedSno))
: null;
setSmsTemplatePreview(tabContent, selectedTemplate || null);
};
 
const initSmsCategoryChange = () => {
document.querySelectorAll('.sms-selected-template-godomall, .sms-selected-template-user').forEach(hiddenInput => {
hiddenInput.dataset.initialSno = hiddenInput.value;
});
 
document.querySelectorAll('.sms-template-category-radio').forEach(radio => {
radio.addEventListener('change', event => {
const recipient = event.target.dataset.recipient;
const tabContent = document.querySelector(`[data-target-type="${recipient}"] [data-tab="SMS"]`);
if (!tabContent) return;
 
updateSmsTemplateDropdown(tabContent, event.target.value, getSmsSelectedType(tabContent), true);
});
});
 
document.querySelectorAll('.sms-template-type-radio').forEach(radio => {
radio.addEventListener('change', event => {
const selectedType = event.target.value;
const recipient = event.target.dataset.recipient;
const tabContent = document.querySelector(`[data-target-type="${recipient}"] [data-tab="SMS"]`);
if (!tabContent) return;
 
const dropdown = tabContent.querySelector('.sms-template-dropdown');
const previousType = selectedType === 'godomall' ? 'user' : 'godomall';
const previousHiddenInput = tabContent.querySelector(`.sms-selected-template-${previousType}`);
if (previousHiddenInput && dropdown?.value) {
previousHiddenInput.value = dropdown.value;
}
 
updateSmsTemplateDropdown(tabContent, getSmsSelectedCategory(tabContent), selectedType, false);
setSmsTemplateCleared(tabContent, dropdown?.value ? 'n' : 'y');
});
});
 
document.querySelectorAll('.sms-template-dropdown').forEach(dropdown => {
dropdown.addEventListener('change', event => {
const recipient = event.target.dataset.recipient;
const tabContent = document.querySelector(`[data-target-type="${recipient}"] [data-tab="SMS"]`);
if (!tabContent) return;
 
const selectedType = getSmsSelectedType(tabContent);
const hiddenInput = tabContent.querySelector(`.sms-selected-template-${selectedType}`);
if (hiddenInput) {
hiddenInput.value = event.target.value;
hiddenInput.dataset.initialSno = event.target.value;
}
 
setSmsTemplateCleared(tabContent, event.target.value ? 'n' : 'y');
setSmsTemplatePreview(tabContent, findSmsTemplateBySno(tabContent, event.target.value));
});
});
};
 
let isLoadingTemplate = false;
 
const load_as_template = (recipient, channel) => {
}
 
// 템플릿 불러오기 후 안내문구 표시
const cautionEl = document.querySelector(`#myapp-chip-selector-container-${recipient} [data-caution-template]`);
 
 
 
if (cautionEl) cautionEl.style.display = '';
};
 
 
Admin/crm/auto_send.php+20
변경 전auto_send.php
const useJoinPolicy = <?= $useJoinPolicy ? 'true' : 'false' ?>;
const MEMBER_JOIN_APPROVE_CODE = 'APPROVAL';
const FORCE_SEND_CODE = 'PRESENT';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
document.addEventListener('DOMContentLoaded', () => {
onClickAutoSendSearch();
// 저장 버튼 클릭
document.querySelector('.auto-send-save-button').addEventListener('click', function (e) {
e.preventDefault();
 
const changedCodes = getChangedAutoSendCodes();
const loadingModal = window.spinnerModal({ message: '저장 중...' });
 
updateInitialValues(changedCodes);
window.unsavedGuard?.reset();
NCDSToast({message: '저장되었습니다.', color: 'success'});
 
 
} else {
NCDSToast({message: '일시적인 오류가 발생하였습니다. </br>다시 시도해 주세요.', color: 'error'});
}
 
변경 후auto_send.php
const useJoinPolicy = <?= $useJoinPolicy ? 'true' : 'false' ?>;
const MEMBER_JOIN_APPROVE_CODE = 'APPROVAL';
const FORCE_SEND_CODE = 'PRESENT';
const IS_SMS_SEND_ACCESSIBLE = <?= json_encode($isSmsSendAccessible) ?>;
const SMS_NO_ROLE_MESSAGE = '<?= gd_htmlspecialchars_stripslashes($smsNoRoleMessage) ?>';
const SMS_NO_ROLE_SUB_MESSAGE = '<?= gd_htmlspecialchars_stripslashes(gd_isset($smsNoRoleSubMessage, '')) ?>';
 
const SMS_RESTRICT_ALERT_MESSAGE = '<?= gd_htmlspecialchars_stripslashes(gd_isset($smsSendRestrictAlertMessage, '')) ?>';
const SMS_RESTRICT_ALERT_SUB_MESSAGE = '<?= gd_htmlspecialchars_stripslashes(gd_isset($smsSendRestrictAlertSubMessage, '')) ?>';
 
// 저장 버튼은 노출 유지가 스펙이라 렌더 차단 대신 클릭 진입점에서 막는다
const blockSaveWithoutSmsRole = () => {
if (SMS_RESTRICT_ALERT_MESSAGE) {
NCDSAlert({message: SMS_RESTRICT_ALERT_MESSAGE, subMessage: SMS_RESTRICT_ALERT_SUB_MESSAGE, iconType: 'error'});
return true;
}
if (IS_SMS_SEND_ACCESSIBLE) return false;
NCDSAlert({message: SMS_NO_ROLE_MESSAGE, subMessage: SMS_NO_ROLE_SUB_MESSAGE, iconType: 'error'});
return true;
};
 
document.addEventListener('DOMContentLoaded', () => {
onClickAutoSendSearch();
// 저장 버튼 클릭
document.querySelector('.auto-send-save-button').addEventListener('click', function (e) {
e.preventDefault();
if (blockSaveWithoutSmsRole()) return;
const changedCodes = getChangedAutoSendCodes();
const loadingModal = window.spinnerModal({ message: '저장 중...' });
 
updateInitialValues(changedCodes);
window.unsavedGuard?.reset();
NCDSToast({message: '저장되었습니다.', color: 'success'});
} else if (data.restricted) {
NCDSAlert({message: data.message, subMessage: data.subMessage || '', iconType: 'error'});
} else {
NCDSToast({message: '일시적인 오류가 발생하였습니다. </br>다시 시도해 주세요.', color: 'error'});
}
 
Admin/crm/crm_recipe_send.php+25-34
변경 전crm_recipe_send.php
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/jquery/jquery.multi_select_box.js') ?>"></script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/crm/mobile_message_replace_code.js"></script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/crm/message_send_common.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/crm/crm_group_status.js"></script>
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/ncds-checkbox-group/ncds-checkbox-group.js') ?>"></script>
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/ncds-datepicker-factory/ncds-datepicker-factory.js') ?>"></script>
};
}
 
function validateHasMessagePoint() {
return messagePoint > 0;
}
 
function validateMessagePoint() {
const sendMethod = document.querySelector('input[name="sendMethod"]:checked')?.value ?? 'SMS';
const expectCount = getExpectTargetCount();
let requiredPoint;
switch (sendMethod) {
case 'SMS':
const messageLength = window.messageInput?.calculateLength(window.messageInput.getValue()) ?? 0;
requiredPoint = messageLength > 90 ? lmsPointEach * expectCount : smsPointEach * expectCount;
break;
case 'ALIMTALK':
requiredPoint = kakaoAlrimtalkPointEach * expectCount;
break;
case 'FRIENDTALK':
requiredPoint = kakaoFriendtalkPointEach * expectCount;
break;
default:
requiredPoint = 1;
}
return messagePoint >= requiredPoint;
}
 
function generateSendSchedulePayload() {
const sendCycle = document.querySelector('#sendCycle').value;
const [repeatStartDate, repeatEndDate] = [...document.querySelectorAll('input[name="repeatDate[]"]')].map(el => el.value);
$button.prop('disabled', false);
};
 
if (!messageConfig.hasCallNumber) {
return fail('발신번호가 없어 발송할 수 없습니다.', '설정에서 발신번호 등록 후 발송 시도를 해주세요.');
}
if (!validateHasMessagePoint()) {
return fail('메시지 포인트를 충전해 주세요');
}
if (!validateMessagePoint()) {
return fail('포인트가 부족하여 발송할 수 없습니다.', '발송 대상 인원보다 포인트가 적어 발송이 불가합니다. <br /> 충전 후 다시 시도해주세요.');
 
 
 
}
const result = validateSendMethodContent();
if (!result.success) {
}
 
// 대체 메시지 미작성 시 본문 치환코드 변환해 자동 세팅
const sendMethod = document.querySelector('input[name="sendMethod"]:checked').value;
const mainContents = window.messageInput?.getValue() ?? '';
if (enableAlternative(sendMethod) && (window.alternativeMessageInput?.getValue() ?? '') === '') {
const allKeys = [
 
변경 후crm_recipe_send.php
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/jquery/jquery.multi_select_box.js') ?>"></script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/crm/mobile_message_replace_code.js"></script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/crm/message_send_common.js"></script>
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/crm/sms-send-gate.js') ?>"></script>
<script type="text/javascript">
$(function () {
SmsSendGate.useNcdsDialog();
SmsSendGate.applyRestrictOnAjax('<?= gd_isset($smsSendRestrictType, '') ?>', {
tooltip: '<?= gd_isset($smsSendRestrictTooltip, '') ?>'
});
SmsSendGate.bindClickGate({
message: '<?= gd_isset($smsSendRestrictAlertMessage, '') ?>',
subMessage: '<?= gd_isset($smsSendRestrictAlertSubMessage, '') ?>',
selectors: ['[data-send-tab-type="ALTERNATIVE"]', '[data-preview-message-type="ALTERNATIVE"]']
});
});
</script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/crm/crm_group_status.js"></script>
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/ncds-checkbox-group/ncds-checkbox-group.js') ?>"></script>
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/ncds-datepicker-factory/ncds-datepicker-factory.js') ?>"></script>
};
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
function generateSendSchedulePayload() {
const sendCycle = document.querySelector('#sendCycle').value;
const [repeatStartDate, repeatEndDate] = [...document.querySelectorAll('input[name="repeatDate[]"]')].map(el => el.value);
$button.prop('disabled', false);
};
 
const sendMethod = document.querySelector('input[name="sendMethod"]:checked').value;
const smsIncluded = sendMethod === 'SMS' || enableAlternative(sendMethod);
 
try {
await SmsSendGate.ensure({
requiredPoint: getRequiredPoint(),
smsIncluded
});
} catch (e) {
$button.prop('disabled', false);
return;
}
const result = validateSendMethodContent();
if (!result.success) {
}
 
// 대체 메시지 미작성 시 본문 치환코드 변환해 자동 세팅
 
const mainContents = window.messageInput?.getValue() ?? '';
if (enableAlternative(sendMethod) && (window.alternativeMessageInput?.getValue() ?? '') === '') {
const allKeys = [
 
Admin/crm/layer_message_template_approval_history.php+36
변경 전layer_message_template_approval_history.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후layer_message_template_approval_history.php
<div class="modal-dialog__content">
<div class="ncua-table ncua-table--horizontal">
<table>
<colgroup>
<col width="60px" />
<col width="180px" />
<col />
<col />
</colgroup>
<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>
</tr>
</thead>
<tbody>
<?php if (empty($histories)): ?>
<tr>
<td colspan="4" height="50" class="no-data"><div>조회된 승인 이력이 없습니다.</div></td>
</tr>
<?php else: ?>
<?php foreach ($histories as $history): ?>
<tr>
<td><div class="ncua-align-center"><?= gd_htmlspecialchars($history['number']) ?></div></td>
<td><div class="ncua-align-center"><?= gd_htmlspecialchars($history['processedAt']) ?></div></td>
<td><div><?= gd_htmlspecialchars($history['eventName']) ?></div></td>
<td><div><?= gd_htmlspecialchars($history['processedBy']) ?></div></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
 
Admin/crm/layer_mobile_message_performance_detail.php-6
변경 전layer_mobile_message_performance_detail.php
<div>
발송 성과 상세 contents
</div>
 
<script type="text/javascript">
</script>
 
변경 후layer_mobile_message_performance_detail.php
 
 
 
 
 
 
 
Admin/crm/layer_mobile_message_performance_summary.php-6
변경 전layer_mobile_message_performance_summary.php
<div>
항목별 집계 기준 contents
</div>
 
<script type="text/javascript">
</script>
 
변경 후layer_mobile_message_performance_summary.php
 
 
 
 
 
 
 
Admin/crm/layer_save_as_template.php-60
변경 전layer_save_as_template.php
<script defer src="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/charcount.js')?>"></script>
<link rel="stylesheet" href="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/css/crm/layer-save-as-template.css')?>">
 
<div class="modal-dialog__content layer-save-as-template">
<div class="ncua-table ncua-table--vertical">
<table>
<tbody>
<tr>
<th><div>카테고리</div></th>
<td>
<div>
<div class="ncua-select ncua-select--xs">
<div class="ncua-select__content">
<select class="ncua-select__tag">
<option value="">카테고리 선택</option>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</select>
</div>
</div>
</div>
</td>
</tr>
<tr>
<th><div>제목</div></th>
<td>
<div>
<div class="ncua-input ncua-input--xs ncua-input--full-width-with-count">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input maxlength="10" type="text" placeholder="제목을 입력하세요" class="layer-template-title" data-charcount-key="layerTemplateTitle" />
</div>
</div>
<div class="ncua-input__field-text-count" data-charcount-text="layerTemplateTitle">
<output class="ncua-input__field-text-count-current">0</output>
<span>/10</span>
</div>
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="modal-dialog__footer">
<button type="button" class="ncua-btn ncua-btn--sm ncua-btn--primary js-layer-close">
<span class="ncua-btn__label">저장</span>
</button>
</div>
 
<script type="text/javascript">
const charCountManager = createCharCountManager({
targetClasses: ['ncua-title'],
});
charCountManager.init();
</script>
 
변경 후layer_save_as_template.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Admin/crm/layer_update_kakao_template.php-384
변경 전layer_update_kakao_template.php
<div class="modal-dialog__content layer_update_kakao_template">
<div class="ncua-table ncua-table--vertical">
<table>
<tbody>
<tr>
<th class="ncua-required"><div>구분</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="kakaoType" />
</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="kakaoType" />
</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="kakaoType" />
</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="kakaoType" />
</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="kakaoType" />
</span>
<span class="ncua-radio-field__text">게시판</span>
</label>
</div>
</td>
</tr>
<tr>
<th class="ncua-required"><div>템플릿 카테고리</div></th>
<td>
<div class="ncua-flex-gap">
<div class="ncua-select ncua-select--xs">
<div class="ncua-select__content">
<select class="ncua-select__tag">
<option value="">대분류</option>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</select>
</div>
</div>
<div class="ncua-select ncua-select--xs">
<div class="ncua-select__content">
<select class="ncua-select__tag" disabled>
<option value="">중분류</option>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</select>
</div>
</div>
</div>
</td>
</tr>
<tr>
<th class="ncua-required"><div>템플릿 제목</div></th>
<td>
<div>
<div class="ncua-input ncua-input--xs ">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs ncua-input-width-520">
<input maxlength="10" type="text" placeholder="제목을 입력하세요" class="kakao-template-title" data-charcount-key="kakaoTemplateTitle" /></div>
</div>
<div class="ncua-input__field-text-count" data-charcount-text="kakaoTemplateTitle">
<output class="ncua-input__field-text-count-current">0</output>
<span>/10</span>
</div>
</div>
<!-- NOTE [저장] 유효성 검사 : 내용을 입력하지 않은 경우 -->
<span class="ncua-hint-text destructive ncua-input__hint-text">템플릿 제목을 입력해 주세요.</span>
</div>
</div>
</td>
</tr>
<tr>
<th><div>보안 템플릿 설정</div></th>
<td>
<div>
<div class="ncua-switch ncua-switch--xs">
<label class="ncua-switch__option ncua-switch__option--left ncua-switch__option--active">
<input class="ncua-switch__radio ncua-switch__radio--left" type="radio" value="y" checked="" name="demo-switch" />
<span class="ncua-switch__label">사용함</span>
</label>
<label class="ncua-switch__option ncua-switch__option--right ncua-switch__option--inactive">
<input class="ncua-switch__radio ncua-switch__radio--right" type="radio" value="n" name="demo-switch" />
<span class="ncua-switch__label">사용안함</span>
</label>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
 
<div class="content-border-box">
<div class="template_type">
<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="templateType" />
</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="templateType" />
</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="templateType" />
</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="templateType" />
</span>
<span class="ncua-radio-field__text">아이템리스트형</span>
</label>
</div>
 
<div class="template_content ncua-card__body-block-section-wrap">
<section class="ncua-card__body-block-section">
<div class="ncua-card__body-title--xs">본문 입력</div>
<div class="ncua-input ncua-input--textarea ncua-input--textarea--xs">
<textarea class="ncua-input__textarea kakao-template-textarea-content" placeholder="템플릿 본문을 작성해 주세요." maxlength="1000"></textarea>
<div class="ncua-input__text-count-wrap">
<div class="ncua-hint-text">본문을 입력해 주세요.</div>
<div class="ncua-input__text-count">
<span class="ncua-input__text-count-text">
<span class="ncua-input__text-count-text-count">0</span>
/1000
</span>
</div>
</div>
</div>
</section>
 
<section class="ncua-card__body-block-section">
<div class="ncua-card__body-title--xs" data-tooltip-seq="008">사용 가능한 변수</div>
<div class="chip-selector">
<div class="ncua-select ncua-select--xs">
<div class="ncua-select__content">
<select class=" ncua-select__tag">
<option value="1">회원</option>
<option value="2">게시판</option>
<option value="3">통계</option>
</select>
</div>
</div>
<div class="chip-button-wrap">
<button class="chip-button"><span class="chip-button-text">{rc_mallNm} 쇼핑몰 명/상점명</span></button>
<button class="chip-button"><span class="chip-button-text">{memId} 회원 아이디</span></button>
<button class="chip-button"><span class="chip-button-text">{memNm} 회원명</span></button>
<button class="chip-button"><span class="chip-button-text">{sleepScheduleDt} 휴면회원 전환 예정 일</span></button>
<button class="chip-button"><span class="chip-button-text">{rc_scheduleDt} 일반 회원 전환 일</span></button>
<button class="chip-button"><span class="chip-button-text">{smsAgreementDt} SMS 수신동의일</span></button>
<button class="chip-button"><span class="chip-button-text">{mailAgreementDt}메일 수신동의일</span></button>
<button class="chip-button"><span class="chip-button-text">{mileage} 보유한 마일리지</span></button>
<button class="chip-button"><span class="chip-button-text">{deposit}보유한 예치금</span></button>
<button class="chip-button"><span class="chip-button-text">{groupNm} 회원등급</span></button>
<button class="chip-button"><span class="chip-button-text">{rc_mileage} 보유한 마일리지</span></button>
<button class="chip-button"><span class="chip-button-text">{rc_deposit}보유한 예치금</span></button>
<button class="chip-button"><span class="chip-button-text">{rc_certificationCode} 인증코드</span></button>
<button class="chip-button"><span class="chip-button-text">{wriNm} 작성자</span></button>
<button class="chip-button"><span class="chip-button-text">{orderNo} 주문번호</span></button>
<button class="chip-button"><span class="chip-button-text">{orderName} 주문명</span></button>
<button class="chip-button"><span class="chip-button-text">{settlePrice} 결제금액</span></button>
<button class="chip-button"><span class="chip-button-text">{bankAccount} 입금계좌번호</span></button>
</div>
</div>
</section>
 
<section class="ncua-card__body-block-section">
<div class="ncua-card__body-title-wrap has-checkbox">
<span class="ncua-card__body-title--xs">부가 정보 문구</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" />
</span>
<span class="ncua-checkbox-field__text">추가</span>
</label>
</div>
<div class="ncua-input ncua-input--textarea ncua-input--textarea--xs">
<textarea class="ncua-input__textarea kakao-template-textarea-add-info" placeholder="템플릿 본문을 작성해 주세요." maxlength="200"></textarea>
<div class="ncua-input__text-count-wrap">
<!-- NOTE [저장] 유효성 검사 : 내용을 입력하지 않은 경우 -->
<div class="ncua-hint-text destructive">본문을 입력해 주세요.</div>
<div class="ncua-input__text-count">
<span class="ncua-input__text-count-text">
<span class="ncua-input__text-count-text-count">0</span>
/200
</span>
</div>
</div>
</div>
</section>
 
<section class="ncua-card__body-block-section channel_add_message">
<div class="ncua-card__body-title-wrap has-checkbox">
<span class="ncua-card__body-title--xs">채널 추가 문구(고정 내용)</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" />
</span>
<span class="ncua-checkbox-field__text">추가</span>
</label>
</div>
<div class="ncua-input ncua-input--textarea ncua-input--textarea--xs">
<textarea class="ncua-input__textarea" maxlength="36" readonly>채널 추가하고 이 채널의 광고와 마케팅을 카카오톡으로 받기</textarea>
<div class="ncua-input__text-count-wrap">
<div class="ncua-hint-text"></div>
<div class="ncua-input__text-count">
<span class="ncua-input__text-count-text">
<span class="ncua-input__text-count-text-count">0</span>
/36
</span>
</div>
</div>
</div>
</section>
 
<section class="ncua-card__body-block-section">
<div class="ncua-card__body-title-wrap">
<span class="ncua-card__body-title--xs">버튼</span>
<div>
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray auto-send-icon auto-send-icon--plus">
<span class="ncua-btn__label">페이지 링크 추가</span>
</button>
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray auto-send-icon auto-send-icon--plus">
<span class="ncua-btn__label">배송 조회 추가</span>
</button>
</div>
</div>
<div class="ncua-table ncua-table--horizontal">
<table>
<colgroup>
<col width="79px">
<col width="auto">
<col width="auto">
<col width="88px">
</colgroup>
<thead>
<tr>
<th><div class="ncua-align-center">타입</div></th>
<th class="ncua-required"><div>버튼명</div></th>
<th class="ncua-required"><div>URL</div></th>
<th><div class="ncua-align-center">관리</div></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="ncua-align-center">페이지 링크</div>
</td>
<td>
<div>
<div class="ncua-input ncua-input--xs ncua-input--full-width-with-count">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input maxlength="20" type="text" placeholder="버튼명을 입력해주세요" class="kakao-button-page-link" data-charcount-key="kakaoButtonPageLink" />
</div>
</div>
<div class="ncua-input__field-text-count" data-charcount-text="kakaoButtonPageLink">
<output class="ncua-input__field-text-count-current">0</output>
<span>/20</span>
</div>
</div>
</div>
<!-- NOTE [저장] 유효성 검사 : 내용을 입력하지 않은 경우 -->
<span class="ncua-hint-text destructive ncua-input__hint-text">버튼명을 입력해 주세요.</span>
</div>
</td>
<td>
<div>
<div class="ncua-input ncua-input--xs">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" placeholder="버튼명을 입력해주세요" />
</div>
</div>
</div>
<span class="ncua-hint-text ncua-input__hint-text">외부 링크 입력 시 캠페인 통계가 집계되지 않습니다.</span>
</div>
</div>
</td>
<td>
<div class="ncua-align-center">
<button type="button" class="ncua-btn ncua-btn--xxs ncua-btn--secondary-gray auto-send-icon auto-send-icon--minus">
<span class="ncua-btn__label">삭제</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="ncua-table ncua-table--horizontal">
<table>
<colgroup>
<col width="79px">
<col width="auto">
<col width="auto">
<col width="88px">
</colgroup>
<thead>
<tr>
<th><div class="ncua-align-center">타입</div></th>
<th class="ncua-required"><div>버튼명</div></th>
<th class="ncua-required"><div>URL</div></th>
<th><div class="ncua-align-center">관리</div></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="ncua-align-center">배송 조회</div>
</td>
<td>
<div>
<div class="ncua-input ncua-input--xs ncua-input--full-width-with-count">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input maxlength="20" type="text" placeholder="버튼명을 입력해주세요" class="kakao-button-delivery-search" data-charcount-key="kakaoButtonDeliverySearch" />
</div>
</div>
<div class="ncua-input__field-text-count" data-charcount-text="kakaoButtonDeliverySearch">
<output class="ncua-input__field-text-count-current">0</output>
<span>/20</span>
</div>
</div>
</div>
<!-- NOTE [저장] 유효성 검사 : 내용을 입력하지 않은 경우 -->
<span class="ncua-hint-text destructive ncua-input__hint-text">버튼명을 입력해 주세요.</span>
</div>
</td>
<td>
<div>
<p>본문에 택배사명과 송장번호가 포함되면 버튼이 카카오 배송조회 페이지로 연결됩니다. 단, 미지원 택배사는 조회되지 않습니다.</p>
</div>
</td>
<td>
<div class="ncua-align-center">
<button type="button" class="ncua-btn ncua-btn--xxs ncua-btn--secondary-gray minus-icon">
<span class="ncua-btn__label">삭제</span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</section>
</div>
</div>
</div>
<div class="modal-dialog__footer">
<button type="button" class="ncua-btn ncua-btn--sm ncua-btn--secondary-gray js-layer-close">
<span class="ncua-btn__label">취소</span>
</button>
<button type="button" class="ncua-btn ncua-btn--sm ncua-btn--primary js-layer-close">
<span class="ncua-btn__label">저장</span>
</button>
</div>
 
변경 후layer_update_kakao_template.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Admin/crm/message_config/layer_manage_sms_verification.php+2
변경 전layer_manage_sms_verification.php
if (validatorInstance) validatorInstance.showErrors({ smsPassword: '10~16자의 영대/소문자, 숫자, 특수문자 중 2가지 이상의 조합을 사용해 주세요.' });
}
}
 
 
});
}
 
 
변경 후layer_manage_sms_verification.php
if (validatorInstance) validatorInstance.showErrors({ smsPassword: '10~16자의 영대/소문자, 숫자, 특수문자 중 2가지 이상의 조합을 사용해 주세요.' });
}
}
}).fail(function (xhr) {
NCDSAlert({ message: xhr.responseJSON?.message || '일시적인 오류가 발생하였습니다.<br>다시 시도해 주세요.', subMessage: xhr.responseJSON?.subMessage || '', iconType: 'error' });
});
}
 
 
Admin/crm/message_config.php+28-15
변경 전message_config.php
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/switch.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/unsaved-changes-guard.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/ncds-jquery-validator.js') ?>"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
<article class="ncua-content message-config-wrapper">
<form id="frmMessageConfig" action="message_config_ps.php" method="post">
<input type="hidden" name="mode" value="save"/>
<td>
<div class="ncua-radio-group ncua-flex ncua-flex-gap">
<?php foreach ($smsAutoSendOverTypes as $smsAutoSendOverType): ?>
<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="smsAutoConfig[smsAutoSendOver]" value="<?= $smsAutoSendOverType->value ?>" <?= $smsAutoConfig->getSmsAutoSendOver() === $smsAutoSendOverType ? 'checked' : '' ?>/>
</span>
<td>
<div class="ncua-flex ncua-gap-8 ncua-align-items-center">
<div class="ncua-switch ncua-switch--xs">
<label class="ncua-switch__option ncua-switch__option--left ncua-switch__option--active">
<input class="ncua-switch__radio ncua-switch__radio--left" type="radio" value="y" name="kakaoFriendTalkConfig[smsAlternativeSendFlag]" <?= $kakaoFriendTalkConfig->getSmsAlternativeSendFlag() === 'y' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용함</span>
</label>
<label class="ncua-switch__option ncua-switch__option--right ncua-switch__option--inactive">
<input class="ncua-switch__radio ncua-switch__radio--right" type="radio" value="n" name="kakaoFriendTalkConfig[smsAlternativeSendFlag]" <?= $kakaoFriendTalkConfig->getSmsAlternativeSendFlag() === 'n' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용안함</span>
</label>
<div class="ncua-flex ncua-gap-8 ncua-align-items-center kakao-alim-talk-manual-sms-alternative-send-setting <?= $kakaoAlimTalkConfig->getSender()->value === 'kakaoAlrim' ? 'display-none' : '' ?>">
모바일 메시지 발송
<div class="ncua-switch ncua-switch--xs">
<label class="ncua-switch__option ncua-switch__option--left ncua-switch__option--active">
<input class="ncua-switch__radio ncua-switch__radio--left" type="radio" value="y" name="kakaoAlimTalkConfig[manualSmsAlternativeSendFlag]" <?= $kakaoAlimTalkConfig->getManualSmsAlternativeSendFlag() === 'y' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용함</span>
</label>
<label class="ncua-switch__option ncua-switch__option--right ncua-switch__option--inactive">
<input class="ncua-switch__radio ncua-switch__radio--right" type="radio" value="n" name="kakaoAlimTalkConfig[manualSmsAlternativeSendFlag]" <?= $kakaoAlimTalkConfig->getManualSmsAlternativeSendFlag() === 'n' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용안함</span>
</label>
<div class="ncua-flex ncua-gap-8 ncua-align-items-center">
자동 알림 발송
<div class="ncua-switch ncua-switch--xs">
<label class="ncua-switch__option ncua-switch__option--left ncua-switch__option--active">
<input class="ncua-switch__radio ncua-switch__radio--left" type="radio" value="y" name="kakaoAlimTalkConfig[autoSmsAlternativeSendFlag]" <?= $kakaoAlimTalkConfig->getAutoSmsAlternativeSendFlag() === 'y' ? 'checked' : '' ?>>
<span class="ncua-switch__label">사용함</span>
</label>
<label class="ncua-switch__option ncua-switch__option--right ncua-switch__option--inactive">
<input class="ncua-switch__radio ncua-switch__radio--right" type="radio" value="n" name="kakaoAlimTalkConfig[autoSmsAlternativeSendFlag]" <?= $kakaoAlimTalkConfig->getAutoSmsAlternativeSendFlag() === 'n' ? 'checked' : '' ?>>
<span class="ncua-switch__label">사용안함</span>
</label>
<div class="ncua-flex ncua-align-items-center" data-tooltip-seq="017">대체발송 처리 방법</div>
<div class="ncua-radio-group ncua-flex ncua-flex-gap">
<?php foreach ($autoSmsAlternativeSendTypes as $autoSmsAlternativeSendType): ?>
<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="kakaoAlimTalkConfig[autoSmsAlternativeSendType]" value="<?= $autoSmsAlternativeSendType->name ?>" <?= $kakaoAlimTalkConfig->getAutoSmsAlternativeSendType() === $autoSmsAlternativeSendType ? 'checked' : '' ?>/>
</span>
<div class="ncua-flex ncua-align-items-center" data-tooltip-seq="014">90 byte 초과 시 메시지 전송 방법</div>
<div class="ncua-radio-group ncua-flex ncua-flex-gap">
<?php foreach ($failedMessageSendTypes as $failedMessageSendType): ?>
<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="kakaoAlimTalkConfig[failedMessageSendType]" value="<?= $failedMessageSendType->name ?>" <?= $kakaoAlimTalkConfig->getFailedMessageSendType() === $failedMessageSendType ? 'checked' : '' ?>/>
</span>
<div class="ncua-flex ncua-gap-8 ncua-align-items-center">
모바일 메시지 발송
<div class="ncua-switch ncua-switch--xs">
<label class="ncua-switch__option ncua-switch__option--left ncua-switch__option--active">
<input class="ncua-switch__radio ncua-switch__radio--left" type="radio" value="y" name="myappPushConfig[manualSmsAlternativeSendFlag]" <?= $myappPushConfig->getManualSmsAlternativeSendFlag() === 'y' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용함</span>
</label>
<label class="ncua-switch__option ncua-switch__option--right ncua-switch__option--inactive">
<input class="ncua-switch__radio ncua-switch__radio--right" type="radio" value="n" name="myappPushConfig[manualSmsAlternativeSendFlag]" <?= $myappPushConfig->getManualSmsAlternativeSendFlag() === 'n' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용안함</span>
</label>
<div class="ncua-flex ncua-gap-8 ncua-align-items-center">
자동 알림 발송
<div class="ncua-switch ncua-switch--xs">
<label class="ncua-switch__option ncua-switch__option--left ncua-switch__option--active">
<input class="ncua-switch__radio ncua-switch__radio--left" type="radio" value="y" name="myappPushConfig[autoSmsAlternativeSendFlag]" <?= $myappPushConfig->getAutoSmsAlternativeSendFlag() === 'y' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용함</span>
</label>
<label class="ncua-switch__option ncua-switch__option--right ncua-switch__option--inactive">
<input class="ncua-switch__radio ncua-switch__radio--right" type="radio" value="n" name="myappPushConfig[autoSmsAlternativeSendFlag]" <?= $myappPushConfig->getAutoSmsAlternativeSendFlag() === 'n' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용안함</span>
</label>
<div class="ncua-flex ncua-align-items-center" data-tooltip-seq="013">대체발송 처리 방법</div>
<div class="ncua-radio-group ncua-flex ncua-flex-gap">
<?php foreach ($autoSmsAlternativeSendTypes as $autoSmsAlternativeSendType): ?>
<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="myappPushConfig[autoSmsAlternativeSendType]" value="<?= $autoSmsAlternativeSendType->name ?>" <?= $myappPushConfig->getAutoSmsAlternativeSendType() === $autoSmsAlternativeSendType ? 'checked' : '' ?>/>
</span>
<div class="ncua-flex ncua-align-items-center" data-tooltip-seq="014">90 byte 초과 시 메시지 전송 방법</div>
<div class="ncua-radio-group ncua-flex ncua-flex-gap">
<?php foreach ($failedMessageSendTypes as $failedMessageSendType): ?>
<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="myappPushConfig[failedMessageSendType]" value="<?= $failedMessageSendType->name ?>" <?= $myappPushConfig->getFailedMessageSendType() == $failedMessageSendType ? 'checked' : '' ?>/>
</span>
 
변경 후message_config.php
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/switch.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/unsaved-changes-guard.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/ncds-jquery-validator.js') ?>"></script>
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/crm/sms-send-gate.js') ?>"></script>
<script type="text/javascript">
$(function () {
SmsSendGate.bindClickGate({
message: '<?= gd_isset($smsSendRestrictAlertMessage, '') ?>',
subMessage: '<?= gd_isset($smsSendRestrictAlertSubMessage, '') ?>',
selectors: <?= json_encode(array_values(array_filter([
'[data-sms-click-gate]',
gd_isset($smsCallNumberManageRestricted, false) ? '#manageCallNumber' : null,
]))) ?>
});
});
</script>
<article class="ncua-content message-config-wrapper">
<form id="frmMessageConfig" action="message_config_ps.php" method="post">
<input type="hidden" name="mode" value="save"/>
<td>
<div class="ncua-radio-group ncua-flex ncua-flex-gap">
<?php foreach ($smsAutoSendOverTypes as $smsAutoSendOverType): ?>
<label class="ncua-radio-field ncua-radio-field--xs has-text" data-sms-click-gate>
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="smsAutoConfig[smsAutoSendOver]" value="<?= $smsAutoSendOverType->value ?>" <?= $smsAutoConfig->getSmsAutoSendOver() === $smsAutoSendOverType ? 'checked' : '' ?>/>
</span>
<td>
<div class="ncua-flex ncua-gap-8 ncua-align-items-center">
<div class="ncua-switch ncua-switch--xs">
<label class="ncua-switch__option ncua-switch__option--left ncua-switch__option--active" data-sms-click-gate>
<input class="ncua-switch__radio ncua-switch__radio--left" type="radio" value="y" name="kakaoFriendTalkConfig[smsAlternativeSendFlag]" <?= $kakaoFriendTalkConfig->getSmsAlternativeSendFlag() === 'y' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용함</span>
</label>
<label class="ncua-switch__option ncua-switch__option--right ncua-switch__option--inactive" data-sms-click-gate>
<input class="ncua-switch__radio ncua-switch__radio--right" type="radio" value="n" name="kakaoFriendTalkConfig[smsAlternativeSendFlag]" <?= $kakaoFriendTalkConfig->getSmsAlternativeSendFlag() === 'n' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용안함</span>
</label>
<div class="ncua-flex ncua-gap-8 ncua-align-items-center kakao-alim-talk-manual-sms-alternative-send-setting <?= $kakaoAlimTalkConfig->getSender()->value === 'kakaoAlrim' ? 'display-none' : '' ?>">
모바일 메시지 발송
<div class="ncua-switch ncua-switch--xs">
<label class="ncua-switch__option ncua-switch__option--left ncua-switch__option--active" data-sms-click-gate>
<input class="ncua-switch__radio ncua-switch__radio--left" type="radio" value="y" name="kakaoAlimTalkConfig[manualSmsAlternativeSendFlag]" <?= $kakaoAlimTalkConfig->getManualSmsAlternativeSendFlag() === 'y' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용함</span>
</label>
<label class="ncua-switch__option ncua-switch__option--right ncua-switch__option--inactive" data-sms-click-gate>
<input class="ncua-switch__radio ncua-switch__radio--right" type="radio" value="n" name="kakaoAlimTalkConfig[manualSmsAlternativeSendFlag]" <?= $kakaoAlimTalkConfig->getManualSmsAlternativeSendFlag() === 'n' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용안함</span>
</label>
<div class="ncua-flex ncua-gap-8 ncua-align-items-center">
자동 알림 발송
<div class="ncua-switch ncua-switch--xs">
<label class="ncua-switch__option ncua-switch__option--left ncua-switch__option--active" data-sms-click-gate>
<input class="ncua-switch__radio ncua-switch__radio--left" type="radio" value="y" name="kakaoAlimTalkConfig[autoSmsAlternativeSendFlag]" <?= $kakaoAlimTalkConfig->getAutoSmsAlternativeSendFlag() === 'y' ? 'checked' : '' ?>>
<span class="ncua-switch__label">사용함</span>
</label>
<label class="ncua-switch__option ncua-switch__option--right ncua-switch__option--inactive" data-sms-click-gate>
<input class="ncua-switch__radio ncua-switch__radio--right" type="radio" value="n" name="kakaoAlimTalkConfig[autoSmsAlternativeSendFlag]" <?= $kakaoAlimTalkConfig->getAutoSmsAlternativeSendFlag() === 'n' ? 'checked' : '' ?>>
<span class="ncua-switch__label">사용안함</span>
</label>
<div class="ncua-flex ncua-align-items-center" data-tooltip-seq="017">대체발송 처리 방법</div>
<div class="ncua-radio-group ncua-flex ncua-flex-gap">
<?php foreach ($autoSmsAlternativeSendTypes as $autoSmsAlternativeSendType): ?>
<label class="ncua-radio-field ncua-radio-field--xs has-text" data-sms-click-gate>
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="kakaoAlimTalkConfig[autoSmsAlternativeSendType]" value="<?= $autoSmsAlternativeSendType->name ?>" <?= $kakaoAlimTalkConfig->getAutoSmsAlternativeSendType() === $autoSmsAlternativeSendType ? 'checked' : '' ?>/>
</span>
<div class="ncua-flex ncua-align-items-center" data-tooltip-seq="014">90 byte 초과 시 메시지 전송 방법</div>
<div class="ncua-radio-group ncua-flex ncua-flex-gap">
<?php foreach ($failedMessageSendTypes as $failedMessageSendType): ?>
<label class="ncua-radio-field ncua-radio-field--xs has-text" data-sms-click-gate>
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="kakaoAlimTalkConfig[failedMessageSendType]" value="<?= $failedMessageSendType->name ?>" <?= $kakaoAlimTalkConfig->getFailedMessageSendType() === $failedMessageSendType ? 'checked' : '' ?>/>
</span>
<div class="ncua-flex ncua-gap-8 ncua-align-items-center">
모바일 메시지 발송
<div class="ncua-switch ncua-switch--xs">
<label class="ncua-switch__option ncua-switch__option--left ncua-switch__option--active" data-sms-click-gate>
<input class="ncua-switch__radio ncua-switch__radio--left" type="radio" value="y" name="myappPushConfig[manualSmsAlternativeSendFlag]" <?= $myappPushConfig->getManualSmsAlternativeSendFlag() === 'y' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용함</span>
</label>
<label class="ncua-switch__option ncua-switch__option--right ncua-switch__option--inactive" data-sms-click-gate>
<input class="ncua-switch__radio ncua-switch__radio--right" type="radio" value="n" name="myappPushConfig[manualSmsAlternativeSendFlag]" <?= $myappPushConfig->getManualSmsAlternativeSendFlag() === 'n' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용안함</span>
</label>
<div class="ncua-flex ncua-gap-8 ncua-align-items-center">
자동 알림 발송
<div class="ncua-switch ncua-switch--xs">
<label class="ncua-switch__option ncua-switch__option--left ncua-switch__option--active" data-sms-click-gate>
<input class="ncua-switch__radio ncua-switch__radio--left" type="radio" value="y" name="myappPushConfig[autoSmsAlternativeSendFlag]" <?= $myappPushConfig->getAutoSmsAlternativeSendFlag() === 'y' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용함</span>
</label>
<label class="ncua-switch__option ncua-switch__option--right ncua-switch__option--inactive" data-sms-click-gate>
<input class="ncua-switch__radio ncua-switch__radio--right" type="radio" value="n" name="myappPushConfig[autoSmsAlternativeSendFlag]" <?= $myappPushConfig->getAutoSmsAlternativeSendFlag() === 'n' ? 'checked' : '' ?>/>
<span class="ncua-switch__label">사용안함</span>
</label>
<div class="ncua-flex ncua-align-items-center" data-tooltip-seq="013">대체발송 처리 방법</div>
<div class="ncua-radio-group ncua-flex ncua-flex-gap">
<?php foreach ($autoSmsAlternativeSendTypes as $autoSmsAlternativeSendType): ?>
<label class="ncua-radio-field ncua-radio-field--xs has-text" data-sms-click-gate>
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="myappPushConfig[autoSmsAlternativeSendType]" value="<?= $autoSmsAlternativeSendType->name ?>" <?= $myappPushConfig->getAutoSmsAlternativeSendType() === $autoSmsAlternativeSendType ? 'checked' : '' ?>/>
</span>
<div class="ncua-flex ncua-align-items-center" data-tooltip-seq="014">90 byte 초과 시 메시지 전송 방법</div>
<div class="ncua-radio-group ncua-flex ncua-flex-gap">
<?php foreach ($failedMessageSendTypes as $failedMessageSendType): ?>
<label class="ncua-radio-field ncua-radio-field--xs has-text" data-sms-click-gate>
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input type="radio" name="myappPushConfig[failedMessageSendType]" value="<?= $failedMessageSendType->name ?>" <?= $myappPushConfig->getFailedMessageSendType() == $failedMessageSendType ? 'checked' : '' ?>/>
</span>
 
Admin/crm/message_template/message_template_result.php+55-44
변경 전message_template_result.php
<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="templateCodeList[]" value="<?= $result->getTemplateCode() ?>" <?= ( $result->getBasicFl() === 'y') ? 'disabled' : '' ?> />
</span>
</label>
</div></td>
<div class="ncua-search-result__actions">
<div class="ncua-search-result__actions-button">
<div class="ncua-search-result__actions-text">
 
 
 
 
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--destructive ncua-select-delete js-btn-delete">선택 삭제</button>
</div>
</div>
<col>
<col width="120px">
<col>
<col>
<?php if ($sendMethod === 'kakao') :?>
<col>
<col>
<col>
<?php endif; ?>
 
 
 
 
 
<col width="180px">
 
 
 
</colgroup>
<thead>
<tr>
</div>
</th>
<th rowspan="2"><div data-tooltip-seq="007">템플릿코드</div></th>
<th rowspan="2"><div data-tooltip-seq="008"><?= $sendMethod === 'kakao' ? '구분' : '카테고리' ?></div></th>
<th rowspan="2"><div data-tooltip-seq="009">템플릿명</div></th>
<?php if ($sendMethod === 'kakao') :?>
<th colspan="2"><div data-tooltip-seq="010">알림톡 검수</div></th>
<?php endif ?>
<th rowspan="2"><div data-tooltip-seq="011">등록일</div></th>
<?php if ($sendMethod === 'kakao') :?>
<th rowspan="2"><div data-tooltip-seq="012">사용 중인 자동 알림</div></th>
 
<?php endif ?>
<th rowspan="2"><div data-tooltip-seq="013">관리</div></th>
 
 
 
</tr>
<?php if ($sendMethod === 'kakao') :?>
<tr>
<th><div data-tooltip-seq="014">검수 상태</div></th>
<th><div data-tooltip-seq="015">검수 답변</div></th>
<?php endif ?>
</thead>
<tbody>
<?php if (
($sendMethod === 'kakao' && (
($search->getProvider() === 'bizm' && $config->getBizmFl() === 'n') ||
($search->getProvider() === 'cloud' && $config->getCloudFl() === 'n') ||
($config->getKakaoUseFl() === 'n')
)) ||
($sendMethod === 'myapp' && $config->getMyappUseFl() === 'n')
) :?>
<tr>
<td colspan="<?= ($sendMethod === 'kakao') ? '9' : '6' ?>" height="50" class="no-data">
<div class="message-config-info">이 발송수단은 현재 '사용 안함'으로 설정되어 있습니다.<br>템플릿 등록 및 메시지를 발송하려면 <a href="./message_config.php" class="ncua-link">메시지 설정</a>에서 사용을 활성화해 주세요.</div>
</td>
</tr>
<?php elseif (empty($results)):?>
<tr>
<td colspan="<?= ($sendMethod === 'kakao') ? '9' : '6' ?>" height="50" class="no-data"><div>검색된 정보가 없습니다.</div></td>
</tr>
<?php else: ?>
<?php foreach ($results as $result) : ?>
<tr>
<td><div>
<label class="ncua-checkbox-field ncua-checkbox-field--xs has-text">
<button type="button" class="ncua-left-align message-list-content-btn" onclick="check_template_contents('<?= $result->getTemplateCode() ?>', '<?= $sendMethod ?>', '<?= $search->getProvider() ?>')"><?= $result->getTemplateName() ?></button>
</div>
</td>
<?php if ($sendMethod === 'kakao') :?>
<td><div><?= $result->getTemplateStatus() ?></div></td>
<?php if ($result->getTemplateStatus() === '반려') :?>
<td><div><button type="button" class="ncua-btn ncua-btn--xxs ncua-btn--secondary-gray" onclick="check_kakao_template_comment('<?= $search->getProvider() ?>','<?= $result->getTemplateCode() ?>')">답변 확인</button></div></td>
<?php else: ?>
<td><div>-</div></td>
<?php endif; ?>
<?php endif; ?>
<td><div><?= date('Y. m. d', strtotime($result->getRegDt())) . ' ' . ((date('A', strtotime($result->getRegDt())) === 'AM' ? '오전' : '오후') . ' ' . date('h:i:s', strtotime($result->getRegDt()))) ?></div></td>
<?php if ($sendMethod === 'kakao') :?>
<?php if ($result->getAutoFl() === 'y') :?>
<td>
<div class="ncua-custom-tooltip">
<div data-tooltip-type="auto-notification-count">
<div class="ncua-left-align message-list-content-btn" ><?= $result->getAutoName() ?></div>
<div class="ncua-custom-tooltip__content">
<?php foreach ($result->getAutoList() as $auto): ?>
<p><?= $auto ?></p>
<?php endforeach; ?>
</div>
</div>
</div>
</td>
<?php else : ?>
<td><div><?= $result->getAutoName() ?></div></td>
<?php endif; ?>
<?php endif; ?>
<td><div class="ncua-flex-gap ncua-gap-4">
<button type="button" class="ncua-btn ncua-btn--xxs ncua-btn--secondary-gray js-btn-copy" value='<?= $result->getTemplateCode() ?>'>복제</button>
<?php
$canModify = $sendMethod !== 'kakao' || in_array($result->getTemplateStatus(), ['등록', '반려']);
?>
<?php if ($canModify): ?>
<button type="button" class="ncua-btn ncua-btn--xxs ncua-btn--secondary-gray js-btn-modify" value='<?= $result->getTemplateCode() ?>'>수정</button>
<?php endif; ?>
</div></td>
 
 
 
 
 
 
 
</tr>
<?php endforeach ?>
<?php endif; ?>
 
변경 후message_template_result.php
<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="templateCodeList[]" value="<?= $result->getTemplateCode() ?>" <?= $result->isSelectable() ? '' : 'disabled' ?> />
</span>
</label>
</div></td>
<div class="ncua-search-result__actions">
<div class="ncua-search-result__actions-button">
<div class="ncua-search-result__actions-text">
<?php if ($isSms): ?>
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray js-btn-approve">선택 승인</button>
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--secondary-gray js-btn-reject">선택 반려</button>
<?php endif; ?>
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--destructive ncua-select-delete js-btn-delete">선택 삭제</button>
</div>
</div>
<col>
<col width="120px">
<col>
<?php if ($isKakao): ?>
 
 
<col>
<col>
<?php endif; ?>
<col>
<col>
<?php if ($isSms): ?>
<col width="100px">
<?php endif; ?>
<col width="180px">
<?php if ($isSms): ?>
<col width="120px">
<?php endif; ?>
</colgroup>
<thead>
<tr>
</div>
</th>
<th rowspan="2"><div data-tooltip-seq="007">템플릿코드</div></th>
<th rowspan="2"><div data-tooltip-seq="008"><?= $isKakao ? '구분' : '카테고리' ?></div></th>
<th rowspan="2"><div data-tooltip-seq="009">템플릿명</div></th>
<?php if ($isKakao): ?>
<th colspan="2"><div data-tooltip-seq="010">알림톡 검수</div></th>
<?php endif ?>
<th rowspan="2"><div data-tooltip-seq="011">등록일</div></th>
<th rowspan="2"><div data-tooltip-seq="012">사용 중인 자동 알림</div></th>
<?php if ($isSms): ?>
<th rowspan="2"><div data-tooltip-seq="014">검수 상태</div></th>
<?php endif ?>
<th rowspan="2"><div data-tooltip-seq="013">관리</div></th>
<?php if ($isSms): ?>
<th rowspan="2"><div data-tooltip-seq="016">승인 이력</div></th>
<?php endif ?>
</tr>
<?php if ($isKakao): ?>
<tr>
<th><div data-tooltip-seq="014">검수 상태</div></th>
<th><div data-tooltip-seq="015">검수 답변</div></th>
<?php endif ?>
</thead>
<tbody>
<?php $columnCount = 7 + ($isKakao ? 2 : 0) + ($isSms ? 2 : 0); ?>
<?php if ($sendMethodDisabled): ?>
 
 
 
 
 
 
<tr>
<td colspan="<?= $columnCount ?>" height="50" class="no-data">
<div class="message-config-info">이 발송수단은 현재 '사용 안함'으로 설정되어 있습니다.<br>템플릿 등록 및 메시지를 발송하려면 <a href="./message_config.php" class="ncua-link">메시지 설정</a>에서 사용을 활성화해 주세요.</div>
</td>
</tr>
<?php elseif (empty($results)): ?>
<tr>
<td colspan="<?= $columnCount ?>" height="50" class="no-data"><div>검색된 정보가 없습니다.</div></td>
</tr>
<?php else: ?>
<?php foreach ($results as $result): ?>
<tr>
<td><div>
<label class="ncua-checkbox-field ncua-checkbox-field--xs has-text">
<button type="button" class="ncua-left-align message-list-content-btn" onclick="check_template_contents('<?= $result->getTemplateCode() ?>', '<?= $sendMethod ?>', '<?= $search->getProvider() ?>')"><?= $result->getTemplateName() ?></button>
</div>
</td>
<?php if ($isKakao): ?>
<td><div><?= $result->getTemplateStatus() ?></div></td>
<?php if ($result->isRejected()): ?>
<td><div><button type="button" class="ncua-btn ncua-btn--xxs ncua-btn--secondary-gray" onclick="check_kakao_template_comment('<?= $search->getProvider() ?>','<?= $result->getTemplateCode() ?>')">답변 확인</button></div></td>
<?php else: ?>
<td><div>-</div></td>
<?php endif; ?>
<?php endif; ?>
<td><div><?= date('Y. m. d', strtotime($result->getRegDt())) . ' ' . ((date('A', strtotime($result->getRegDt())) === 'AM' ? '오전' : '오후') . ' ' . date('h:i:s', strtotime($result->getRegDt()))) ?></div></td>
<?php if ($result->getAutoFl() === 'y'): ?>
<td><div>
<button type="button" class="ncua-btn ncua-btn--xs has-underline ncua-btn--text-gray ncua-message-auto-alam">
<span class="ncua-btn__label"><?= $result->getAutoName() ?></span>
<span class="ncua-tooltip-panel ncua-tooltip__bg ncua-tooltip__bg--white ncua-tooltip__bg--bottom">
<?php foreach ($result->getAutoList() as $auto): ?>
<span class="ncua-tooltip__content"><?= $auto ?></span>
<?php endforeach; ?>
</span>
</button>
</div></td>
<?php else : ?>
<td><div><?= $result->getAutoName() ?></div></td>
<?php endif; ?>
<?php if ($isSms): ?>
<td><div><?= $result->getTemplateStatus() ?></div></td>
 
<?php endif; ?>
<td><div class="ncua-flex-gap ncua-gap-4">
<button type="button" class="ncua-btn ncua-btn--xxs ncua-btn--secondary-gray js-btn-copy" value='<?= $result->getTemplateCode() ?>'>복제</button>
<?php if ($result->isModifiable()): ?>
 
 
 
<button type="button" class="ncua-btn ncua-btn--xxs ncua-btn--secondary-gray js-btn-modify" value='<?= $result->getTemplateCode() ?>'>수정</button>
<?php endif; ?>
</div></td>
<?php if ($isSms): ?>
<td>
<div>
<button type="button" class="ncua-btn ncua-btn--xxs ncua-btn--secondary-gray js-btn-detail" value="<?= $result->getTemplateCode() ?>">상세</button>
</div>
</td>
<?php endif; ?>
</tr>
<?php endforeach ?>
<?php endif; ?>
 
Admin/crm/message_template/message_template_search.php+7-31
변경 전message_template_search.php
<col>
</colgroup>
<tbody>
<?php if ($sendMethod === 'kakao') : ?>
<tr>
<th>
<div data-tooltip-seq="001">제공사</div>
</span>
<span class="ncua-radio-field__text">고도몰</span>
</label>
<?php if ($isNewMall === false) : ?>
<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="provider"
</div>
</td>
</tr>
<?php
$categories = [
['value' => 'all', 'name' => '전체'],
];
if ($sendMethod === 'sms') {
$categories = array_merge($categories, $searchCategory->getSmsCategory());
} elseif ($sendMethod === 'myapp') {
$categories = array_merge($categories, $searchCategory->getMyappCategory());
} elseif ($search->getProvider() === 'cloud') {
$categories = array_merge($categories, $searchCategory->getCloudCategory());
} elseif ($search->getProvider() === 'bizm') {
$categories = array_merge($categories, $searchCategory->getBizmCategory());
}
?>
<tr>
<th>
<div data-tooltip-seq="003"><?= $sendMethod === 'kakao' ? '구분' : '카테고리' ?></div>
</th>
<td colspan="3">
<div class="ncua-flex-gap js-trigger-type">
<?php foreach ($categories as $cat): ?>
<?php
$checked = in_array($cat['value'], $search->getCategory()) ? 'checked' : '';
?>
</div>
</td>
</tr>
<?php if ($sendMethod === 'kakao') : ?>
<?php
$statusOptions = [
['value' => 'all', 'label' => '전체'],
];
if ($search->getProvider() === 'cloud') {
$statusOptions = array_merge($statusOptions, $searchStatus->getCloudStatus());
} elseif ($search->getProvider() === 'bizm') {
$statusOptions = array_merge($statusOptions, $searchStatus->getBizmStatus());
}
?>
<tr>
<th>
<div data-tooltip-seq="004">검수 상태</div>
</th>
<td>
<div class="ncua-flex-gap js-trigger-status">
<?php foreach ($statusOptions as $option): ?>
<?php if (!isset($option['provider']) || $search->getProvider() === $option['provider']): ?>
<label class="ncua-checkbox-field ncua-checkbox-field--xs has-text">
<span class="ncua-checkbox-input ncua-checkbox-input--xs ncua-checkbox-field__input">
</table>
</div>
<div class="ncua-flex ncua-justify-between ncua-align-items-start search-footer-container">
<?php if ($sendMethod === 'kakao') : ?>
<p class="ncua-notice-info">블룸에이아이의 템플릿은 <a class="ncua-link" href="https://bizmsg.blumn.ai/template"
target="_blank">블룸에이아이 파트너스 센터</a>에서 확인 가능합니다.</p>
<?php else : ?>
 
변경 후message_template_search.php
<col>
</colgroup>
<tbody>
<?php if ($isKakao): ?>
<tr>
<th>
<div data-tooltip-seq="001">제공사</div>
</span>
<span class="ncua-radio-field__text">고도몰</span>
</label>
<?php if ($isNewMall === false): ?>
<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="provider"
</div>
</td>
</tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<tr>
<th>
<div data-tooltip-seq="003"><?= $isKakao ? '구분' : '카테고리' ?></div>
</th>
<td colspan="3">
<div class="ncua-flex-gap js-trigger-type">
<?php foreach ($searchCategories as $cat): ?>
<?php
$checked = in_array($cat['value'], $search->getCategory()) ? 'checked' : '';
?>
</div>
</td>
</tr>
<?php if ($isKakao || $isSms): ?>
 
 
 
 
 
 
 
 
 
 
<tr>
<th>
<div data-tooltip-seq="004">검수 상태</div>
</th>
<td>
<div class="ncua-flex-gap js-trigger-status">
<?php foreach ($searchStatusOptions as $option): ?>
<?php if (!isset($option['provider']) || $search->getProvider() === $option['provider']): ?>
<label class="ncua-checkbox-field ncua-checkbox-field--xs has-text">
<span class="ncua-checkbox-input ncua-checkbox-input--xs ncua-checkbox-field__input">
</table>
</div>
<div class="ncua-flex ncua-justify-between ncua-align-items-start search-footer-container">
<?php if ($isKakao): ?>
<p class="ncua-notice-info">블룸에이아이의 템플릿은 <a class="ncua-link" href="https://bizmsg.blumn.ai/template"
target="_blank">블룸에이아이 파트너스 센터</a>에서 확인 가능합니다.</p>
<?php else : ?>
 
Admin/crm/message_template.php+179-31
변경 전message_template.php
 
if (hasBasic) {
NCDSAlert({
message: '고도몰에서 제공하는 기본 템플릿은 수정할 수 없습니다.<br>복제 후 내용을 수정해 신규 템플릿으로 등록해 주세요.',
iconType: 'error'
});
return false;
 
<div class="swiper ncua-horizontal-tab ncua-horizontal-tab--md ncua-horizontal-tab--underline-fill">
<div class="swiper-wrapper ncua-gap-8">
<div class="swiper-slide ncua-horizontal-tab__item">
<a href="javascript:" onclick="redirectWithParams('sendMethod','sms',false)"
class="ncua-tab-button <?= ($sendMethod === 'sms') ? ' is-active' : '' ?>" data-html="true"
data-content="SMS" data-placement="top">SMS</a>
</div>
<div class="swiper-slide ncua-horizontal-tab__item">
<a href="javascript:" onclick="redirectWithParams('sendMethod','kakao',false)"
class="ncua-tab-button <?= ($sendMethod === 'kakao') ? ' is-active' : '' ?>" data-html="true"
data-content="카카오 알림톡" data-placement="top">카카오 알림톡</a>
</div>
<div class="swiper-slide ncua-horizontal-tab__item">
<a href="javascript:" onclick="redirectWithParams('sendMethod','myapp',false)"
class="ncua-tab-button <?= ($sendMethod === 'myapp') ? ' is-active' : '' ?>" data-html="true"
data-content="마이앱(앱푸시)" data-placement="top">마이앱(앱푸시)</a>
</div>
 
 
 
 
 
</div>
</div>
 
</section>
</section>
</article>
 
<script type="text/javascript">
const start = new Date('1970-01-01');
const end = new Date(); // 오늘 날짜
NCDSAlert({ message, iconType: 'error' });
});
 
// 템플릿 코드별 autoFl 매핑
const templateAutoMap = {
<?php foreach ($results as $item): ?>
'<?= $item->getTemplateCode() ?>': '<?= $item->getAutoFl() ?>',
<?php endforeach; ?>
};
 
const templateBasicMap = {
<?php foreach ($results as $item): ?>
'<?= $item->getTemplateCode() ?>': '<?= $item->getBasicFl() ?>',
<?php endforeach; ?>
};
 
const searchData = JSON.parse('<?= json_encode($search->toArray(), JSON_UNESCAPED_UNICODE) ?>');
 
}
 
 
function redirectWithParams(key, value, isAll = true) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
const form = document.createElement('form');
form.method = 'POST';
form.action = '';
 
form.appendChild(createHiddenInput('page', 1));
form.appendChild(createHiddenInput(key, value));
 
document.body.appendChild(form);
form.submit();
}
form.submit();
}
 
 
 
 
 
 
// 페이지 이동
function loadByPage(page) {
const [key, value] = page.split('=');
// 수정 버튼 (기본 템플릿 수정 불가 안내)
if (target.classList.contains('js-btn-modify')) {
const templateCode = target.value;
const hasBasic = templateBasicMap[templateCode] === 'y';
 
if (hasBasic) {
NCDSAlert({
return false;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
const data = {
mode: 'modify',
sendMethod: '<?= $sendMethod ?>',
}
});
 
 
 
 
 
 
 
 
 
 
// 선택삭제 유효성 검사
$('#frmList').validate({
ignore: ':hidden', dialog: false, submitHandler: function (form) {
const selectedCodes = Array.from(form.querySelectorAll('input[name="templateCodeList[]"]:checked'))
.map(input => input.value);
 
// 자동알림 사용 중인 템플릿 체크
const hasAutoAlrim = selectedCodes.some(code => {
return templateAutoMap[code] === 'y';
});
 
if (hasAutoAlrim) {
NCDSAlert({
message: '자동알림에 사용 중인 템플릿은 삭제할 수 없습니다.',
iconType: 'error'
});
return false;
const data = {
mode: mode,
sendMethod: '<?= $sendMethod ?>',
templateCodeList: selectedCodes,
provider: '<?= $search->getProvider() ?>'
}
 
 
$.post('./message_template_ps.php', data, function (response) {
if (response.success) {
NCDSToast({message: '템플릿이 삭제되었습니다.', color: 'success'});
 
 
 
 
 
 
 
 
 
 
 
setTimeout(() => {
redirectWithParams('page', 1, true);
},
messages: {
'templateCode[]': {
required: '선택하신 글이 없습니다.'
},
 
},
const btnDelete = document.querySelector('.js-btn-delete');
if (btnDelete) {
btnDelete.addEventListener('click', handleDeleteClick);
 
 
 
 
 
 
 
 
 
 
 
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
const code = '251219001';
// COS 가이드 초기화
if (window.GodoCosGuide) {
 
변경 후message_template.php
 
if (hasBasic) {
NCDSAlert({
message: '<?= $basicNotModifiableMessage ?>',
iconType: 'error'
});
return false;
 
<div class="swiper ncua-horizontal-tab ncua-horizontal-tab--md ncua-horizontal-tab--underline-fill">
<div class="swiper-wrapper ncua-gap-8">
 
 
 
 
 
<div class="swiper-slide ncua-horizontal-tab__item">
<a href="javascript:" onclick="redirectWithParams('sendMethod','kakao',false)"
class="ncua-tab-button <?= $isKakao ? ' is-active' : '' ?>" data-html="true"
data-content="카카오 알림톡" data-placement="top">카카오 알림톡</a>
</div>
<div class="swiper-slide ncua-horizontal-tab__item">
<a href="javascript:" onclick="redirectWithParams('sendMethod','myapp',false)"
class="ncua-tab-button <?= $isMyapp ? ' is-active' : '' ?>" data-html="true"
data-content="마이앱(앱푸시)" data-placement="top">마이앱(앱푸시)</a>
</div>
<div class="swiper-slide ncua-horizontal-tab__item">
<a href="javascript:" onclick="selectSmsTab()"
class="ncua-tab-button <?= $isSms ? ' is-active' : '' ?>" data-html="true"
data-content="SMS" data-placement="top">SMS</a>
</div>
</div>
</div>
 
</section>
</section>
</article>
 
<script type="text/javascript">
const start = new Date('1970-01-01');
const end = new Date(); // 오늘 날짜
NCDSAlert({ message, iconType: 'error' });
});
 
const templateMap = <?= json_encode($templateMap) ?>;
 
 
 
 
 
 
const templateApprovalStatus = <?= json_encode($approvalStatusValues) ?>;
 
const hasSmsApprovalPermission = <?= $hasSmsApprovalPermission ? 'true' : 'false' ?>;
 
 
 
const searchData = JSON.parse('<?= json_encode($search->toArray(), JSON_UNESCAPED_UNICODE) ?>');
 
}
 
 
function selectSmsTab() {
const restrictAlertMessage = '<?= gd_htmlspecialchars_stripslashes(gd_isset($smsSendRestrictAlertMessage, '')) ?>';
if (restrictAlertMessage) {
NCDSAlert({
message: restrictAlertMessage,
subMessage: '<?= gd_htmlspecialchars_stripslashes(gd_isset($smsSendRestrictAlertSubMessage, '')) ?>',
iconType: 'error'
});
return;
}
if (<?= json_encode($isSmsTabVisible) ?>) {
redirectWithParams('sendMethod', 'sms', false, {smsTabReturnMethod: '<?= $sendMethod ?>'});
return;
}
NCDSAlert({
message: '<?= gd_htmlspecialchars_stripslashes($smsNoRoleMessage) ?>',
subMessage: '<?= gd_htmlspecialchars_stripslashes(gd_isset($smsNoRoleSubMessage, '')) ?>',
iconType: 'error'
});
}
 
function redirectWithParams(key, value, isAll = true, extraParams = {}) {
const form = document.createElement('form');
form.method = 'POST';
form.action = '';
 
form.appendChild(createHiddenInput('page', 1));
form.appendChild(createHiddenInput(key, value));
Object.entries(extraParams).forEach(([k, v]) => form.appendChild(createHiddenInput(k, v)));
document.body.appendChild(form);
form.submit();
}
form.submit();
}
 
// 점유인증 완료 후 복귀 — POST 로만 유지되는 현재 탭·검색조건을 재요청한다
window.occupancyAuthReload = function () {
redirectWithParams('sendMethod', '<?= gd_isset($requestedSendMethod, $sendMethod) ?>', true);
};
 
// 페이지 이동
function loadByPage(page) {
const [key, value] = page.split('=');
// 수정 버튼 (기본 템플릿 수정 불가 안내)
if (target.classList.contains('js-btn-modify')) {
const templateCode = target.value;
const hasBasic = templateMap[templateCode]?.basicFl === 'y';
 
if (hasBasic) {
NCDSAlert({
return false;
}
 
if (templateMap[templateCode]?.approvalStatus === templateApprovalStatus.PENDING) {
NCDSAlert({
message: '<?= $notModifiableMessage ?>',
iconType: 'info'
});
return false;
}
 
if ('<?= $sendMethod ?>' === 'sms' && templateMap[templateCode]?.usedFl === 'y') {
NCDSAlert({
message: '<?= $linkedNotModifiableMessage ?>',
iconType: 'info'
});
return false;
}
 
const data = {
mode: 'modify',
sendMethod: '<?= $sendMethod ?>',
}
});
 
$('.js-btn-detail').on('click', function () {
$.post('./layer_message_template_approval_history.php', {templateCode: this.value}, function (data) {
ncds_layer_popup({
message: data,
title: 'SMS 발송 템플릿 승인 이력',
});
});
});
 
// 선택삭제 유효성 검사
$('#frmList').validate({
ignore: ':hidden', dialog: false, submitHandler: function (form) {
const selectedCodes = Array.from(form.querySelectorAll('input[name="templateCodeList[]"]:checked'))
.map(input => input.value);
 
const deletableCodes = selectedCodes.filter(code => templateMap[code]?.usedFl !== 'y');
const usedCount = selectedCodes.length - deletableCodes.length;
 
 
 
if (deletableCodes.length === 0) {
NCDSAlert({
message: '<?= $usedNotDeletableMessage ?>',
iconType: 'error'
});
return false;
const data = {
mode: mode,
sendMethod: '<?= $sendMethod ?>',
templateCodeList: deletableCodes,
provider: '<?= $search->getProvider() ?>'
}
 
 
$.post('./message_template_ps.php', data, function (response) {
if (response.success) {
const excludedCount = usedCount + (response.mappedCount || 0);
if (excludedCount > 0) {
NCDSAlert({
message: `자동 알림 발송 목록 ${excludedCount}건 제외 ${response.deletedCount}건의 템플릿이 삭제되었습니다.`,
iconType: 'warning',
onClose: () => redirectWithParams('page', 1, true)
});
return;
}
 
NCDSToast({message: response.message, color: 'success'});
 
setTimeout(() => {
redirectWithParams('page', 1, true);
},
messages: {
'templateCode[]': {
required: '선택하신 템플릿이 없습니다.'
},
 
},
const btnDelete = document.querySelector('.js-btn-delete');
if (btnDelete) {
btnDelete.addEventListener('click', handleDeleteClick);
 
const syncDeleteButton = () => {
btnDelete.disabled = document.querySelectorAll('input[name="templateCodeList[]"]:checked').length === 0;
};
// 전체선택 체크박스는 공용 스크립트가 각 행을 갱신한 뒤 change 를 올리므로 위임으로 함께 받는다
document.addEventListener('change', (e) => {
if (e.target.matches('input[name="templateCodeList[]"], .ncua-checkall')) {
syncDeleteButton();
}
});
syncDeleteButton();
}
 
const handleApprovalClick = (mode) => {
if (!hasSmsApprovalPermission) {
NCDSAlert({message: '<?= $smsNoApprovalPermissionMessage ?>', iconType: 'error'});
return;
}
 
const actionName = mode === 'approve' ? '승인' : '반려';
const selectedCodes = Array.from(document.querySelectorAll('input[name="templateCodeList[]"]:checked'))
.map(input => input.value);
 
if (selectedCodes.length === 0) {
NCDSAlert({message: '선택하신 템플릿이 없습니다.', iconType: 'error'});
return;
}
 
const pendingCodes = selectedCodes.filter(code => templateMap[code]?.approvalStatus === templateApprovalStatus.PENDING);
if (pendingCodes.length === 0) {
if (selectedCodes.length === 1) {
NCDSAlert({
message: '이미 승인 또는 반려 처리가 된 신청 건입니다.<br>페이지 새로고침 후 확인해 주세요.',
iconType: 'error',
callback: () => location.reload()
});
return;
}
NCDSAlert({
message: `선택한 템플릿 중 ${actionName} 가능한 템플릿이 없습니다. 검수중 상태의 템플릿만 처리할 수 있습니다.`,
iconType: 'error'
});
return;
}
 
const approvedCount = selectedCodes.filter(code => templateMap[code]?.approvalStatus === templateApprovalStatus.APPROVED).length;
const rejectedCount = selectedCodes.filter(code => templateMap[code]?.approvalStatus === templateApprovalStatus.REJECTED).length;
const tailMessage = mode === 'approve'
? '승인한 템플릿은 승인 취소가 불가합니다.'
: '반려한 템플릿은 수정 후 재검수가 가능합니다.';
 
NCDSConfirm({
message: `선택한 템플릿을 ${actionName}하시겠습니까?`,
subMessage: `선택한 ${selectedCodes.length}건 중 검수중 ${pendingCodes.length}건을 ${actionName}합니다. (승인 ${approvedCount}건·반려 ${rejectedCount}건 제외). ${tailMessage}`,
callback: function (result) {
if (!result) return false;
 
const data = {
mode: mode,
sendMethod: '<?= $sendMethod ?>',
templateCodeList: pendingCodes,
provider: '<?= $search->getProvider() ?>'
};
 
$.post('./message_template_ps.php', data, function (response) {
if (response.noPermission) {
NCDSAlert({
message: response.message,
iconType: 'error',
callback: () => location.reload()
});
return;
}
 
if (!response.success) {
NCDSAlert({
message: response.message || `${actionName} 처리에 실패했습니다.`,
iconType: 'error'
});
return;
}
 
NCDSToast({
message: response.message,
color: 'success'
});
 
setTimeout(() => {
redirectWithParams('page', 1, true);
}, 3000);
}, 'json');
},
});
};
 
document.querySelector('.js-btn-approve')?.addEventListener('click', () => handleApprovalClick('approve'));
document.querySelector('.js-btn-reject')?.addEventListener('click', () => handleApprovalClick('reject'));
 
const code = '251219001';
// COS 가이드 초기화
if (window.GodoCosGuide) {
 
Admin/crm/message_template_register/basic_config.php+9-6
변경 전basic_config.php
<th><div>발송수단</div></th>
<td>
<div class="ncua-flex ncua-flex-gap ncua-custom-tooltip <?= in_array($request->getMode(), ['copy', 'modify']) ? 'is-edit-mode' : '' ?>">
<label class="ncua-radio-field ncua-radio-field--xs has-text" data-tooltip-type="disabled-send-method">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input name="sendMethod" type="radio" value="sms" <?= ($request->getSendMethod() === 'sms') ? 'checked="checked"' : '' ?> <?= in_array($request->getMode(), ['copy', 'modify']) ? 'disabled' : '' ?> />
</span>
<span class="ncua-radio-field__text">SMS/LMS</span>
</label>
<label class="ncua-radio-field ncua-radio-field--xs has-text" data-tooltip-type="disabled-send-method">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input name="sendMethod" type="radio" value="kakao" <?= ($request->getSendMethod() === 'kakao') ? 'checked="checked"' : '' ?> <?= (in_array($request->getMode(), ['copy', 'modify']) || $config->getKakaoUseFl() === 'n' || ($config->getCloudFl() === 'n' && $config->getBizmFl() === 'n')) ? 'disabled' : '' ?> />
</span>
<span class="ncua-radio-field__text">마이앱 (앱푸시)</span>
</label>
 
 
 
 
 
 
</div>
</td>
</tr>
$templateTitleMaxLengthAttr = '';
} elseif ($request->getSendMethod() === 'kakao' && $request->getProvider() === 'bizm') {
$templateTitleMaxLengthAttr = 'maxlength="30"';
 
 
} else {
$templateTitleMaxLengthAttr = 'maxlength="10"';
}
<output class="ncua-input__field-text-count-current"><?= mb_strlen($templateTitleValue) ?></output><span class="js-template-title-max-length"><?= $templateTitleMaxLength ? '/' . $templateTitleMaxLength : '' ?></span>
</div>
</div>
 
</div>
</div>
</td>
 
변경 후basic_config.php
<th><div>발송수단</div></th>
<td>
<div class="ncua-flex ncua-flex-gap ncua-custom-tooltip <?= in_array($request->getMode(), ['copy', 'modify']) ? 'is-edit-mode' : '' ?>">
 
 
 
 
 
 
<label class="ncua-radio-field ncua-radio-field--xs has-text" data-tooltip-type="disabled-send-method">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input name="sendMethod" type="radio" value="kakao" <?= ($request->getSendMethod() === 'kakao') ? 'checked="checked"' : '' ?> <?= (in_array($request->getMode(), ['copy', 'modify']) || $config->getKakaoUseFl() === 'n' || ($config->getCloudFl() === 'n' && $config->getBizmFl() === 'n')) ? 'disabled' : '' ?> />
</span>
<span class="ncua-radio-field__text">마이앱 (앱푸시)</span>
</label>
<label class="ncua-radio-field ncua-radio-field--xs has-text" data-tooltip-type="disabled-send-method" data-tooltip-text="<?= gd_htmlspecialchars(gd_isset($smsSendMethodRestrictTooltip, '')) ?>">
<span class="ncua-radio-input ncua-radio-input--xs ncua-radio-field__input">
<input name="sendMethod" type="radio" value="sms" <?= ($request->getSendMethod() === 'sms') ? 'checked="checked"' : '' ?> <?= (in_array($request->getMode(), ['copy', 'modify']) || gd_isset($smsSendMethodRestricted, false)) ? 'disabled' : '' ?> />
</span>
<span class="ncua-radio-field__text">SMS/LMS</span>
</label>
</div>
</td>
</tr>
$templateTitleMaxLengthAttr = '';
} elseif ($request->getSendMethod() === 'kakao' && $request->getProvider() === 'bizm') {
$templateTitleMaxLengthAttr = 'maxlength="30"';
} elseif (mb_strlen($templateTitleValue) > 10) {
$templateTitleMaxLengthAttr = '';
} else {
$templateTitleMaxLengthAttr = 'maxlength="10"';
}
<output class="ncua-input__field-text-count-current"><?= mb_strlen($templateTitleValue) ?></output><span class="js-template-title-max-length"><?= $templateTitleMaxLength ? '/' . $templateTitleMaxLength : '' ?></span>
</div>
</div>
<span class="ncua-hint-text ncua-input__hint-text">템플릿 제목은 템플릿을 구분하기 위한 항목이며, 실제 발송되는 내용에는 포함되지 않습니다.</span>
</div>
</div>
</td>
 
Admin/crm/message_template_register.php+38-5
변경 전message_template_register.php
const sendMethodRadios = document.querySelectorAll('input[name="sendMethod"]');
sendMethodRadios.forEach((radio) => {
radio.addEventListener('change', (event) => {
const selectedValue = event.target.value;
 
 
 
 
// 이전 발송 수단의 필드 값 저장
if (previousSendMethod) {
saveFieldValues(previousSendMethod);
if (selectedValue === 'sms') {
updateSmsMessageHint(smsMessageInput.getValue());
}
});
});
 
/**
* 제공사 라디오 버튼 변경 시 조건부 표시 업데이트
// 제목 입력 — 이모지 제거, 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();
getSmsTitleInput().addEventListener('input', handleSmsTitleInput);
handleSmsTitleInput();
 
 
 
 
 
 
 
 
 
 
 
 
 
const smsVariableSelector = GodoUIModule.render({
type: 'ChipSelector',
target: '#sms-variable-selector-container',
return true;
};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 검증 에러 표시 (공통)
*/
},
},
invalidHandler: function (event, validator) {
 
 
const templateTypeRadio = document.querySelector('input[name="templateType"]:checked');
 
// IMAGE 타입: 상단 이미지 미등록 시 highlight + alert (ITEM_LIST는 submitHandler에서 별도 검증)
 
변경 후message_template_register.php
const sendMethodRadios = document.querySelectorAll('input[name="sendMethod"]');
sendMethodRadios.forEach((radio) => {
radio.addEventListener('change', (event) => {
switchSendMethod(event.target.value);
});
});
 
const switchSendMethod = (selectedValue) => {
// 이전 발송 수단의 필드 값 저장
if (previousSendMethod) {
saveFieldValues(previousSendMethod);
if (selectedValue === 'sms') {
updateSmsMessageHint(smsMessageInput.getValue());
}
};
 
 
/**
* 제공사 라디오 버튼 변경 시 조건부 표시 업데이트
// 제목 입력 — 이모지 제거, 40bytes 초과분 자동 삭제 (byte 기준은 본문 MessageInput 과 동일)
const handleSmsTitleInput = () => {
const input = getSmsTitleInput();
const stripped = smsMessageInput.removeEmoji(input.value, input.selectionStart);
const value = smsMessageInput.truncateBytes(stripped.value, SMS_TITLE_MAX_BYTES);
if (input.value !== value) {
const caret = Math.min(stripped.cursorPos, value.length);
input.value = value;
input.setSelectionRange(caret, caret);
}
document.querySelector('[data-sms-title-current]').textContent = smsMessageInput.getByteLength(value);
updateSmsTitlePreview();
getSmsTitleInput().addEventListener('input', handleSmsTitleInput);
handleSmsTitleInput();
 
const templateTitleInput = document.querySelector('.js-template-title');
if (templateTitleInput) {
templateTitleInput.addEventListener('input', () => {
const stripped = smsMessageInput.removeEmoji(templateTitleInput.value, templateTitleInput.selectionStart);
if (templateTitleInput.value === stripped.value) return;
 
templateTitleInput.value = stripped.value;
templateTitleInput.setSelectionRange(stripped.cursorPos, stripped.cursorPos);
templateTitleInput.dispatchEvent(new Event('keyup', {bubbles: true}));
});
}
 
const smsVariableSelector = GodoUIModule.render({
type: 'ChipSelector',
target: '#sms-variable-selector-container',
return true;
};
 
const scrollToTopmostInvalidField = (validator) => {
const fields = (validator.errorList || [])
.map(error => error.element)
.filter(element => element && element.offsetParent !== null);
 
if (fields.length < 2) {
return;
}
 
const topmost = fields.reduce((top, element) => (
element.getBoundingClientRect().top < top.getBoundingClientRect().top ? element : top
));
topmost.scrollIntoView({behavior: 'smooth', block: 'center'});
};
 
/**
* 검증 에러 표시 (공통)
*/
},
},
invalidHandler: function (event, validator) {
scrollToTopmostInvalidField(validator);
 
const templateTypeRadio = document.querySelector('input[name="templateType"]:checked');
 
// IMAGE 타입: 상단 이미지 미등록 시 highlight + alert (ITEM_LIST는 submitHandler에서 별도 검증)
 
Admin/crm/message_template_register/template_contents_sms.php+1
변경 전template_contents_sms.php
<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>
 
변경 후template_contents_sms.php
<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>
<li>등록/수정한 템플릿은 승인 후 발송 가능합니다.</li>
</ul>
</div>
</details>
 
Admin/crm/mobile_send/layer_kakao_friendtalk_content_setting.php+7-3
변경 전layer_kakao_friendtalk_content_setting.php
kakaoFriendtalkTabSection.querySelector('input[name="friendtalkCampaignImageLink"]').value = carousel.imageLink;
 
// 메시지 (message)
window.messageInput.setValue(carousel.message);
<?php if ($isRecipeContext): ?>
// 레시피: 슬라이드 전환은 setValue 라 onInput 미발생 → 포인트 힌트·안내문구 직접 반영
window.messageInput.setHint(carousel.message ? friendtalkUnitPointHint(getCampaignMessageType()) : null);
MobileMessageReplaceCode.updateExpireCodeGuide('#replaceCodeContainer', carousel.message);
<?php endif; ?>
 
}
 
function renderMessageInput(campaignMessageType) {
 
 
window.messageInput.clear();
window.messageInput.setHint(null);
window.preview.reset();
kakaoFriendtalkButtonSection.querySelectorAll('.ncua-table').forEach(t => t.remove());
kakaoFriendtalkButtonSection.querySelector('.empty-coupon-data').classList.remove('display-none');
 
window.preview.reset();
checkTable();
 
// 아이템 리스트 테이블 row 수 초기화
}
 
function updateDefaultPreview(campaignType) {
 
 
switch (campaignType) {
case 'TEXT':
setDefaultPreview(['TEXT']);
 
변경 후layer_kakao_friendtalk_content_setting.php
kakaoFriendtalkTabSection.querySelector('input[name="friendtalkCampaignImageLink"]').value = carousel.imageLink;
 
// 메시지 (message)
window.messageInput?.setValue(carousel.message);
<?php if ($isRecipeContext): ?>
// 레시피: 슬라이드 전환은 setValue 라 onInput 미발생 → 포인트 힌트·안내문구 직접 반영
window.messageInput?.setHint(carousel.message ? friendtalkUnitPointHint(getCampaignMessageType()) : null);
MobileMessageReplaceCode.updateExpireCodeGuide('#replaceCodeContainer', carousel.message);
<?php endif; ?>
 
}
 
function renderMessageInput(campaignMessageType) {
if (!window.messageInput || !window.preview) return;
 
window.messageInput.clear();
window.messageInput.setHint(null);
window.preview.reset();
kakaoFriendtalkButtonSection.querySelectorAll('.ncua-table').forEach(t => t.remove());
kakaoFriendtalkButtonSection.querySelector('.empty-coupon-data').classList.remove('display-none');
 
window.preview?.reset();
checkTable();
 
// 아이템 리스트 테이블 row 수 초기화
}
 
function updateDefaultPreview(campaignType) {
if (!window.preview) return;
 
switch (campaignType) {
case 'TEXT':
setDefaultPreview(['TEXT']);
 
Admin/crm/mobile_send/layer_mobile_send_save_sms_template.php+5-1
변경 전layer_mobile_send_save_sms_template.php
<link rel="stylesheet" href="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/css/crm/layer-save-as-template.css')?>">
 
<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">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input maxlength="10" type="text" id="templateSubject" placeholder="제목을 입력하세요" class="layer-template-title" data-charcount-key="layerTemplateTitle" />
</div>
</div>
<div class="ncua-input__field-text-count" data-charcount-text="layerTemplateTitle">
 
변경 후layer_mobile_send_save_sms_template.php
<link rel="stylesheet" href="<?=gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/css/crm/layer-save-as-template.css')?>">
 
<div class="modal-dialog__content layer-save-as-template">
<ul>
<li class="ncua-notice-info">작성한 내용을 저장할 카테고리와 템플릿명을 입력하여 주시기 바랍니다.</li>
<li class="ncua-notice-info">저장한 템플릿은 검수중 상태로 등록되며, 승인 후 발송 가능합니다.</li>
</ul>
<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">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input maxlength="10" type="text" id="templateSubject" placeholder="템플릿명을 입력하여 주세요." class="layer-template-title" data-charcount-key="layerTemplateTitle" />
</div>
</div>
<div class="ncua-input__field-text-count" data-charcount-text="layerTemplateTitle">
 
Admin/crm/mobile_send/layer_mobile_send_sms_template.php+1
변경 전layer_mobile_send_sms_template.php
 
<div class="modal-dialog__content layer-load-template js-sms-template-section">
<ul>
 
<li class="ncua-caution-text">치환코드는 지정된 페이지에서만 사용하실 수 있습니다.</li>
<li class="ncua-notice-info">예) 주문 관련 치환코드는 주문리스트에서 SMS 발송 시에만 사용하실 수 있습니다.</li>
<li class="ncua-notice-info">중복 선택은 불가합니다.</li>
 
변경 후layer_mobile_send_sms_template.php
 
<div class="modal-dialog__content layer-load-template js-sms-template-section">
<ul>
<li class="ncua-notice-info">템플릿 작성 및 수정은 템플릿 관리에서 가능합니다. <a href="./message_template.php?sendMethod=sms" target="_blank" class="ncua-link">바로가기</a></li>
<li class="ncua-caution-text">치환코드는 지정된 페이지에서만 사용하실 수 있습니다.</li>
<li class="ncua-notice-info">예) 주문 관련 치환코드는 주문리스트에서 SMS 발송 시에만 사용하실 수 있습니다.</li>
<li class="ncua-notice-info">중복 선택은 불가합니다.</li>
 
Admin/crm/mobile_send/layer_recipient_setting.php+2
변경 전layer_recipient_setting.php
 
function updateFriendtalkCampainUnitPoint() {
if (document.querySelector('input[name="sendMethod"]:checked')?.value === 'FRIENDTALK') {
 
 
const campaignMessageType = document.querySelector('input[name="campaignMessageType"]:checked')?.value;
updateCampaignUnitPoint(campaignMessageType);
}
 
변경 후layer_recipient_setting.php
 
function updateFriendtalkCampainUnitPoint() {
if (document.querySelector('input[name="sendMethod"]:checked')?.value === 'FRIENDTALK') {
if (typeof updateCampaignUnitPoint !== 'function') return;
 
const campaignMessageType = document.querySelector('input[name="campaignMessageType"]:checked')?.value;
updateCampaignUnitPoint(campaignMessageType);
}
 
Admin/crm/mobile_send/layer_send_method_setting.php+47-81
변경 전layer_send_method_setting.php
<?php foreach ($sendMethods as $sendMethod): ?>
<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 name="sendMethod" type="radio" value="<?= $sendMethod->name ?>" <?= $sendMethod->name === 'SMS' ? 'checked="checked"' : ''?> />
</span>
<span>
<span class="ncua-radio-field__text"><?= $sendMethod->value ?></span>
}
}
 
const validateResult = validateSendMethodUseFlag(this.value);
if (!validateResult.result) {
e.preventDefault();
NCDSAlert({
message: validateResult.message,
subMessage: validateResult.subMessage,
iconType: 'error'
});
return;
});
}
 
function validateSendMethodUseFlag(sendMethod) {
switch (sendMethod) {
case 'FRIENDTALK':
if (messageConfig.kakaoFriendTalk.useFlag === 'y') {
return { result: true };
} else {
return {
result: false,
message: '발송 수단이 준비되지 않았습니다.',
subMessage: '메시지를 발송하려면 발송 수단 인증 및 설치가 필요합니다.<br />통합 설정 페이지에서 먼저 완료해 주세요.'
};
}
case 'ALIMTALK':
if (messageConfig.kakaoAlimTalk.useFlag === 'y') {
if (messageConfig.kakaoAlimTalk.sender !== 'kakaoAlrimCloud') {
return {
result: false,
message: '비즈엠/블룸에이아이 발송 불가 안내',
subMessage: '비즈엠, 블룸에이아이 채널은 알림톡으로 메시지를 발송할 수 없습니다.<br />CRM > 메시지 > 메시지 설정에서 제공사를 고도몰로 변경해 주세요.'
};
}
 
return {result: true};
} else {
return {
result: false,
message: '발송 수단이 준비되지 않았습니다.',
subMessage: '메시지를 발송하려면 발송 수단 인증 및 설치가 필요합니다.<br />통합 설정 페이지에서 먼저 완료해 주세요.'
};
}
case 'MYAPP':
if (messageConfig.myappPush.useFlag === 'y') {
return { result: true };
} else {
return {
result: false,
message: '발송 수단이 준비되지 않았습니다.',
subMessage: '메시지를 발송하려면 발송 수단 인증 및 설치가 필요합니다.<br />통합 설정 페이지에서 먼저 완료해 주세요.'
};
}
default:
return { result: true };
}
}
 
function initializeData() {
}
 
function loadSendMethodContentSettingLayer(sendMethod) {
 
 
 
 
 
 
const urlMap = {
SMS: './mobile_send/layer_sms_content_setting.php',
FRIENDTALK: './mobile_send/layer_kakao_friendtalk_content_setting.php',
});
}
 
 
 
 
 
 
 
 
 
 
 
 
function getActivatedSendTabType() {
return document.querySelector('.ncua-tab-button[data-send-tab-type].is-active')?.dataset.sendTabType ?? null;
}
 
// 제목 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;
 
// 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;
if (titleInput.value !== value) {
titleInput.value = value;
if (caret !== null) {
titleInput.setSelectionRange(caret, caret);
 
}
}
return value;
* 대체메시지 사용 가능여부 검증
*/
function enableAlternative(activatedSendMethod) {
switch (activatedSendMethod) {
case 'SMS':
return false;
case 'FRIENDTALK':
if (messageConfig.kakaoFriendTalk.useAlternative !== 'y') {
return false;
}
 
const campaignMessageType = document.querySelector('input[name="campaignMessageType"]:checked')?.value;
if (['CAROUSEL_FEED', 'WIDE_ITEM_LIST'].includes(campaignMessageType)) {
return false;
}
break;
case 'ALIMTALK':
if (messageConfig.kakaoAlimTalk.useAlternative !== 'y') {
return false;
}
break;
case 'MYAPP':
if (messageConfig.myappPush.useAlternative !== 'y') {
return false;
}
 
const recipientType = document.querySelector('input[name="recipientType"]:checked')?.value ?? 'ALL'
const isOnlyAgreed = document.querySelector('input[name="isOnlyAgreed"]')?.checked;
const myappSendCondition = document.querySelector('input[name="myappSendCondition"]:checked').value;
 
 
if (recipientType === 'ALL' && !isOnlyAgreed && myappSendCondition === 'APP_INSTALLED') {
return false;
}
break;
 
 
}
 
return true;
}
 
function initializeSendMethodSettingSectionElement() {
document.querySelector('input[name="sendMethod"]:checked')?.dispatchEvent(new Event('change'));
 
}
 
function isEditing() {
 
변경 후layer_send_method_setting.php
<?php foreach ($sendMethods as $sendMethod): ?>
<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 name="sendMethod" type="radio" value="<?= $sendMethod->name ?>" <?= $sendMethod->name === 'FRIENDTALK' ? 'checked="checked"' : ''?> />
</span>
<span>
<span class="ncua-radio-field__text"><?= $sendMethod->value ?></span>
}
}
 
const restriction = getSendMethodRestriction(this.value);
if (restriction?.reason === 'ALIMTALK_SENDER') {
e.preventDefault();
NCDSAlert({
...buildSendMethodRestrictionText(restriction),
 
iconType: 'error'
});
return;
});
}
 
function getSendMethodRestriction(sendMethod) {
return messageConfig.sendMethods?.[sendMethod]?.restriction ?? null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
function initializeData() {
}
 
function loadSendMethodContentSettingLayer(sendMethod) {
const restriction = getSendMethodRestriction(sendMethod);
if (restriction) {
renderSendMethodRestrictionNotice(restriction);
return;
}
 
const urlMap = {
SMS: './mobile_send/layer_sms_content_setting.php',
FRIENDTALK: './mobile_send/layer_kakao_friendtalk_content_setting.php',
});
}
 
function renderSendMethodRestrictionNotice(restriction) {
window.preview?.reset();
window.alternativePreview?.reset();
const { message, subMessage } = buildSendMethodRestrictionText(restriction);
$('#layerSendMethodContent').html(`
<div class="no-ready-send-type">
<p class="ncua-caution-text">${message} ${subMessage}</p>
</div>
`);
}
 
function getActivatedSendTabType() {
return document.querySelector('.ncua-tab-button[data-send-tab-type].is-active')?.dataset.sendTabType ?? null;
}
 
// 제목 byte 규칙 적용(이모지 제거 포함) 후 최종 값 반환
function applySmsTitleByteRule(titleInput, messageInputModule) {
const stripped = messageInputModule.removeEmoji(titleInput.value, titleInput.selectionStart ?? titleInput.value.length);
let value = stripped.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 = value === titleInput.value ? null : stripped.cursorPos;
 
delete titleInput.dataset.titleAdInsert;
 
// C4-2 — 절삭하지 않는다. 초과 상태에서는 늘리는 입력만 되돌리고 삭제는 허용
if (!adInserted && base !== undefined && messageInputModule.getByteLength(value) > messageInputModule.getByteLength(base)) {
// 되돌린 만큼 커서를 입력 직전 위치로 복원
caret = Math.max(0, stripped.cursorPos - (value.length - base.length));
value = base;
}
titleInput.dataset.titleOverBase = value;
if (titleInput.value !== value) {
titleInput.value = value;
if (caret !== null) {
const caretPos = Math.min(caret, value.length);
titleInput.setSelectionRange(caretPos, caretPos);
}
}
return value;
* 대체메시지 사용 가능여부 검증
*/
function enableAlternative(activatedSendMethod) {
if (messageConfig.isSmsManager !== true) {
return false;
}
 
 
 
 
 
if (messageConfig.sendMethods?.[activatedSendMethod]?.useAlternative !== 'y') {
return false;
}
 
 
 
 
 
 
 
 
 
 
 
 
if (activatedSendMethod === 'FRIENDTALK') {
const campaignMessageType = document.querySelector('input[name="campaignMessageType"]:checked')?.value;
return !['CAROUSEL_FEED', 'WIDE_ITEM_LIST'].includes(campaignMessageType);
}
 
if (activatedSendMethod === 'MYAPP') {
const recipientType = document.querySelector('input[name="recipientType"]:checked')?.value ?? 'ALL'
const isOnlyAgreed = document.querySelector('input[name="isOnlyAgreed"]')?.checked;
const myappSendCondition = document.querySelector('input[name="myappSendCondition"]:checked').value;
 
return !(recipientType === 'ALL' && !isOnlyAgreed && myappSendCondition === 'APP_INSTALLED');
}
 
return true;
}
 
function initializeSendMethodSettingSectionElement() {
sendMethodSettingSection.querySelector('input[name="sendMethod"]:checked')
?.dispatchEvent(new Event('change'));
}
 
function isEditing() {
 
Admin/crm/mobile_send/layer_sms_content_setting.php+2
변경 전layer_sms_content_setting.php
<li>SMS 작성 시 90byte를 초과하면 LMS로 자동 전환되어 발송됩니다.</li>
<li>변수에 실제 데이터가 적용된 최종 메시지가 90byte를 초과하는 경우에도 LMS로 전환될 수 있습니다.</li>
<li>본문 내용이 LMS인 경우에만 제목 입력이 가능하며, 제목 영역에는 직접 (광고) 문구를 입력하지 않도록 하고 광고 삽입이 필요한 경우 광고성 문구 추가 기능을 사용해야 합니다.</li>
 
 
</ul>
</div>
</details>
 
변경 후layer_sms_content_setting.php
<li>SMS 작성 시 90byte를 초과하면 LMS로 자동 전환되어 발송됩니다.</li>
<li>변수에 실제 데이터가 적용된 최종 메시지가 90byte를 초과하는 경우에도 LMS로 전환될 수 있습니다.</li>
<li>본문 내용이 LMS인 경우에만 제목 입력이 가능하며, 제목 영역에는 직접 (광고) 문구를 입력하지 않도록 하고 광고 삽입이 필요한 경우 광고성 문구 추가 기능을 사용해야 합니다.</li>
<li>SMS/LMS 템플릿 등록·관리·승인은 <a class="ncua-link" href="./message_template.php?sendMethod=sms" target="_blank">템플릿 관리</a>에서 가능합니다.</li>
<li>SMS/LMS 템플릿은 승인 상태에서만 발송이 가능합니다. (검수중·반려 상태의 템플릿은 발송이 제한됩니다)</li>
</ul>
</div>
</details>
 
Admin/crm/mobile_send.php+22-53
변경 전mobile_send.php
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/crm/crm_group_description.js"></script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/crm/mobile_message_replace_code.js"></script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/crm/message_send_common.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/crm/crm_group_status.js"></script>
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/ncds-checkbox-group/ncds-checkbox-group.js') ?>"></script>
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/ncds-datepicker-factory/ncds-datepicker-factory.js') ?>"></script>
const lmsPointEach = Number(<?= $lmsPointEach ?>);
const kakaoAlrimtalkPointEach = Number(<?= $kakaoAlrimTalkPointEach ?>);
const kakaoFriendtalkPointEach = Number(<?= $kakaoFriendTalkPointEach ?>);
const messageConfig = JSON.parse('<?= json_encode($messageConfig, JSON_UNESCAPED_UNICODE) ?>');
 
$(document).ready(function () {
commonLogic();
refreshOverSmsTitleState();
}
 
// 발신번호 검증
if (!messageConfig.hasCallNumber) {
NCDSAlert({message: '발신번호가 없어 발송할 수 없습니다.', subMessage: '설정에서 발신번호 등록 후 발송 시도를 해주세요.', iconType: 'error'});
$button.prop('disabled', false);
return;
}
 
// 포인트 0인 경우
if (!validateHasMessagePoint()) {
NCDSAlert({
message: '메시지 포인트를 충전해 주세요',
iconType: 'error'
});
$button.prop('disabled', false);
return;
}
 
// 발송인원 X 수단별 포인트 검증
if (!validateMessagePoint()) {
NCDSAlert({
message: '포인트가 부족하여 발송할 수 없습니다.',
subMessage: '발송 대상 인원보다 포인트가 적어 발송이 불가합니다. <br /> 충전 후 다시 시도해주세요.',
iconType: 'error'
});
 
$button.prop('disabled', false);
return;
}
break;
}
 
const sendMethod = document.querySelector('input[name="sendMethod"]:checked').value;
const mainContents = sendMethod === 'ALIMTALK'
? (document.querySelector('textarea[name="messageContent"]')?.value ?? '')
: (window.messageInput?.getValue() ?? '');
});
}
 
function validateHasMessagePoint() {
return messagePoint > 0;
}
 
function validateMessagePoint() {
const sendMethod = document.querySelector('input[name="sendMethod"]:checked')?.value ?? "SMS";
const expectCount = getExpectTargetCount();
 
let requiredPoint;
 
switch (sendMethod) {
case 'SMS':
const messageLength = window.messageInput.calculateLength(window.messageInput.getValue());
requiredPoint = messageLength > 90 ? lmsPointEach * expectCount : smsPointEach * expectCount;
break;
case 'ALIMTALK':
requiredPoint = kakaoAlrimtalkPointEach * expectCount;
break;
case 'FRIENDTALK':
requiredPoint = kakaoFriendtalkPointEach * expectCount;
break;
default:
requiredPoint = 1;
}
 
return messagePoint >= requiredPoint;
}
 
 
function getExpectTargetCount() {
const recipientType = document.querySelector('[name="recipientType"]:checked')?.value
if (recipientType == null || recipientType === '') {
 
변경 후mobile_send.php
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/crm/crm_group_description.js"></script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/crm/mobile_message_replace_code.js"></script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/crm/message_send_common.js"></script>
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/crm/sms-send-gate.js') ?>"></script>
<script type="text/javascript">
$(function () {
SmsSendGate.useNcdsDialog();
SmsSendGate.applyRestrictOnAjax('<?= gd_isset($smsSendRestrictType, '') ?>', {
tooltip: '<?= gd_isset($smsSendRestrictTooltip, '') ?>'
});
SmsSendGate.bindClickGate({
message: '<?= gd_isset($smsSendRestrictAlertMessage, '') ?>',
subMessage: '<?= gd_isset($smsSendRestrictAlertSubMessage, '') ?>',
selectors: ['[data-send-tab-type="ALTERNATIVE"]', '[data-preview-message-type="ALTERNATIVE"]']
});
});
</script>
<script type="text/javascript" src="<?= PATH_ADMIN_GD_SHARE ?>script/crm/crm_group_status.js"></script>
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/ncds-checkbox-group/ncds-checkbox-group.js') ?>"></script>
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/ncds-datepicker-factory/ncds-datepicker-factory.js') ?>"></script>
const lmsPointEach = Number(<?= $lmsPointEach ?>);
const kakaoAlrimtalkPointEach = Number(<?= $kakaoAlrimTalkPointEach ?>);
const kakaoFriendtalkPointEach = Number(<?= $kakaoFriendTalkPointEach ?>);
const messageConfig = <?= json_encode($messageConfig, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
 
$(document).ready(function () {
commonLogic();
refreshOverSmsTitleState();
}
 
const sendMethod = document.querySelector('input[name="sendMethod"]:checked').value;
const smsIncluded = sendMethod === 'SMS' || enableAlternative(sendMethod);
 
 
 
 
 
try {
await SmsSendGate.ensure({
requiredPoint: getRequiredPoint(),
smsIncluded
 
 
 
 
 
 
 
 
 
 
 
 
});
} catch (e) {
$button.prop('disabled', false);
return;
}
break;
}
 
 
const mainContents = sendMethod === 'ALIMTALK'
? (document.querySelector('textarea[name="messageContent"]')?.value ?? '')
: (window.messageInput?.getValue() ?? '');
});
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
function getExpectTargetCount() {
const recipientType = document.querySelector('[name="recipientType"]:checked')?.value
if (recipientType == null || recipientType === '') {
 
Admin/member/sms_auto.php+56-17
변경 전sms_auto.php
<!-- //@formatter:off -->
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/aggregator-message-router.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/sms/sms-caller-hub-layer.js') ?>"></script>
 
<input type="hidden" id="kakaoUse" name="kakaoUse" value="<?php echo $kakaoSetting['useFlag']; ?>"/>
<input type="hidden" id="kakaoOrderUse" name="kakaoOrderUse" value="<?php echo $kakaoSetting['orderUseFlag']; ?>"/>
<input type="hidden" id="kakaoMemberUse" name="kakaoMemberUse" value="<?php echo $kakaoSetting['memberUseFlag']; ?>"/>
</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>
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();
 
변경 후sms_auto.php
<!-- //@formatter:off -->
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/aggregator-message-router.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/sms/sms-caller-hub-layer.js') ?>"></script>
<script src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/sms/sms-emoji.js') ?>"></script>
<input type="hidden" id="kakaoUse" name="kakaoUse" value="<?php echo $kakaoSetting['useFlag']; ?>"/>
<input type="hidden" id="kakaoOrderUse" name="kakaoOrderUse" value="<?php echo $kakaoSetting['orderUseFlag']; ?>"/>
<input type="hidden" id="kakaoMemberUse" name="kakaoMemberUse" value="<?php echo $kakaoSetting['memberUseFlag']; ?>"/>
</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']; ?>"/>
<?php if (isset($typeData['recipientLinkedContents'])) { ?>
<input type="hidden" name="<?= $sKey ?>[<?= $typeData['code'] ?>][recipientMessageTitle]" value="<?= gd_isset($typeData['recipientMessageTitle']) ?>"/>
<input type="hidden" name="<?= $sKey ?>[<?= $typeData['code'] ?>][recipientContents]" value="<?= $typeData['recipientContents'] ?>"/>
<div class="js-sms-auto-subject-wrap mgb5">
<input type="text" class="form-control js-sms-auto-subject" value="<?= $typeData['recipientLinkedMessageTitle'] ?>" readonly/>
</div>
<textarea rows="4" class="form-control js-sms-auto-contents" readonly><?= $typeData['recipientLinkedContents'] ?></textarea>
<?php } else { ?>
<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>
<?php } ?>
</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']; ?>"/>
<?php if (isset($typeData['memberLinkedContents'])) { ?>
<input type="hidden" name="<?= $sKey ?>[<?= $typeData['code'] ?>][memberMessageTitle]" value="<?= gd_isset($typeData['memberMessageTitle']) ?>"/>
<input type="hidden" name="<?= $sKey ?>[<?= $typeData['code'] ?>][memberContents]" value="<?= $typeData['memberContents'] ?>"/>
<div class="js-sms-auto-subject-wrap mgb5">
<input type="text" class="form-control js-sms-auto-subject" value="<?= $typeData['memberLinkedMessageTitle'] ?>" readonly/>
</div>
<textarea rows="4" class="form-control js-sms-auto-contents" readonly><?= $typeData['memberLinkedContents'] ?></textarea>
<?php } else { ?>
<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 } ?>
<?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']; ?>"/>
<?php if (isset($typeData['adminLinkedContents'])) { ?>
<input type="hidden" name="<?= $sKey ?>[<?= $typeData['code'] ?>][adminMessageTitle]" value="<?= gd_isset($typeData['adminMessageTitle']) ?>"/>
<input type="hidden" name="<?= $sKey ?>[<?= $typeData['code'] ?>][adminContents]" value="<?= $typeData['adminContents'] ?>"/>
<div class="js-sms-auto-subject-wrap mgb5">
<input type="text" class="form-control js-sms-auto-subject" value="<?= $typeData['adminLinkedMessageTitle'] ?>" readonly/>
</div>
<textarea rows="4" class="form-control js-sms-auto-contents" readonly><?= $typeData['adminLinkedContents'] ?></textarea>
<?php } else { ?>
<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 } ?>
<?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']; ?>"/>
<?php if (isset($typeData['providerLinkedContents'])) { ?>
<input type="hidden" name="<?= $sKey ?>[<?= $typeData['code'] ?>][providerMessageTitle]" value="<?= gd_isset($typeData['providerMessageTitle']) ?>"/>
<input type="hidden" name="<?= $sKey ?>[<?= $typeData['code'] ?>][providerContents]" value="<?= $typeData['providerContents'] ?>"/>
<div class="js-sms-auto-subject-wrap mgb5">
<input type="text" class="form-control js-sms-auto-subject" value="<?= $typeData['providerLinkedMessageTitle'] ?>" readonly/>
</div>
<textarea rows="4" class="form-control js-sms-auto-contents" readonly><?= $typeData['providerLinkedContents'] ?></textarea>
<?php } else { ?>
<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 } ?>
<?php } else { ?>
<?php if ($sKey == 'admin') { ?>
<div class="text-center text-orange-red">본사 전용</div>
while (smsAutoStringToByte(text) > <?= $lmsSubjectLimit; ?>) { text = text.substring(0, text.length - 1); }
input.val(text);
});
smsEmoji.bind('#frmSmsAuto textarea');
 
$('.btn-change-password').click(function () {
$.get('../share/layer_sms_password.php?mode=change', function () {
BootstrapDialog.closeAll();
 
Admin/member/sms_send.php+23
변경 전sms_send.php
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/aggregator-message-router.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/sms/sms-caller-hub-layer.js') ?>"></script>
 
 
 
 
 
 
 
 
 
<script type="text/javascript">
<!--
/**
$('input[name=receiverCnt]').val(receiverCount * pointCnt);
$('input[name=agreeCnt]').val(agreeCount * pointCnt);
$('input[name=rejectCnt]').val(rejectCount * pointCnt);
 
}
 
$(document).ready(function () {
 
var $formSendSms = gd_sms_send.get_form();
 
 
 
 
 
 
 
 
 
 
 
 
 
// 폼 체크
$formSendSms.validate({
dialog: false,
<input type="hidden" name="receiverCnt" value="" />
<input type="hidden" name="agreeCnt" value="" />
<input type="hidden" name="rejectCnt" value="<?=$countSmsFlN?>" />
 
<input type="hidden" name="sendFl" value="sms" />
<input type="hidden" name="rejectSend" value="n" />
<input type="hidden" name="receiverList" value="" />
 
변경 후sms_send.php
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/aggregator-message-router.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/sms/sms-caller-hub-layer.js') ?>"></script>
<script type="text/javascript" src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/crm/sms-send-gate.js') ?>"></script>
<script type="text/javascript">
$(function () {
SmsSendGate.applyRestrict('<?= gd_isset($smsSendRestrictType, '') ?>', {
tooltip: '<?= gd_isset($smsSendRestrictTooltip, '') ?>',
selectors: ['#formSendSms input[type="submit"]', '#formSendSms textarea[name="smsContents"]']
});
});
</script>
<script type="text/javascript">
<!--
/**
$('input[name=receiverCnt]').val(receiverCount * pointCnt);
$('input[name=agreeCnt]').val(agreeCount * pointCnt);
$('input[name=rejectCnt]').val(rejectCount * pointCnt);
$('input[name=sendTargetCnt]').val(receiverCount);
}
 
$(document).ready(function () {
 
var $formSendSms = gd_sms_send.get_form();
 
// 점유인증이 입력값 유효성보다 앞이라 submitHandler 로 옮기면 안 된다
$formSendSms.on('click', 'input[type="submit"]', function (event) {
event.preventDefault();
// 발송 건수는 유효성 검증 단계에서 채워지는데 게이트가 그보다 앞이라 여기서 먼저 세운다
setReceiverCount();
SmsSendGate.ensure({
targetCount: parseInt($('input[name=sendTargetCnt]').val()) || 0
}).then(function () {
$formSendSms.submit();
}).catch(function () {});
});
 
// 폼 체크
$formSendSms.validate({
dialog: false,
<input type="hidden" name="receiverCnt" value="" />
<input type="hidden" name="agreeCnt" value="" />
<input type="hidden" name="rejectCnt" value="<?=$countSmsFlN?>" />
<input type="hidden" name="sendTargetCnt" value="" />
<input type="hidden" name="sendFl" value="sms" />
<input type="hidden" name="rejectSend" value="n" />
<input type="hidden" name="receiverList" value="" />
 
Admin/policy/_manage_permission_menu_list.php+1-1
변경 전_manage_permission_menu_list.php
?>
</table>
</div>
<input type="hidden" id="messageSenderMeta" data-editor-approver="<?= !empty($isEditorMessageApprover) ? 'y' : 'n'; ?>" data-editor-representative="<?= !empty($isEditorMessageRepresentative) ? 'y' : 'n'; ?>" data-caller-blocked="<?= !empty($isCallerBlocked) ? 'y' : 'n'; ?>" data-sender-count="<?= (int)(gd_isset($messageSenderCount, 0)); ?>" data-sender-limit="<?= (int)(gd_isset($messageSenderLimit, 0)); ?>"/>
<div class="access-control-reason <?=(!empty($sno) || $changeType == 'init') ? '' : 'display-none'?>" style="clear: both;">
<div class="table-action-dropdown">
<div class="table-action mgt0 mgb15">
 
변경 후_manage_permission_menu_list.php
?>
</table>
</div>
<input type="hidden" id="messageSenderMeta" data-editor-approver="<?= !empty($isEditorMessageApprover) ? 'y' : 'n'; ?>" data-editor-representative="<?= !empty($isEditorMessageRepresentative) ? 'y' : 'n'; ?>" data-caller-blocked="<?= !empty($isCallerBlocked) ? 'y' : 'n'; ?>" data-caller-uncertified-delegate="<?= !empty($isCallerUncertifiedDelegate) ? 'y' : 'n'; ?>" data-sender-count="<?= (int)(gd_isset($messageSenderCount, 0)); ?>" data-sender-limit="<?= (int)(gd_isset($messageSenderLimit, 0)); ?>"/>
<div class="access-control-reason <?=(!empty($sno) || $changeType == 'init') ? '' : 'display-none'?>" style="clear: both;">
<div class="table-action-dropdown">
<div class="table-action mgt0 mgb15">
 
Admin/policy/manage_permission.php+4
변경 전manage_permission.php
dialog_alert('SMS 발송 권한은 발신관리책임자 또는 발신관리위임자만 부여할 수 있습니다.<br>해당 권한을 해제한 후 다시 저장해 주세요.', '경고');
return false;
}
 
 
 
 
if ($smsMeta.data('editorRepresentative') === 'y' && $smsMeta.data('callerBlocked') === 'y') {
dialog_alert('SMS 발송 메뉴 접근이 제한되었습니다.<br>스팸 또는 부정 사용으로 차단되어 접근할 수 없습니다.<br>자세한 사항은 1:1문의로 문의하시기 바랍니다.', '경고');
return false;
 
변경 후manage_permission.php
dialog_alert('SMS 발송 권한은 발신관리책임자 또는 발신관리위임자만 부여할 수 있습니다.<br>해당 권한을 해제한 후 다시 저장해 주세요.', '경고');
return false;
}
if ($smsMeta.data('callerUncertifiedDelegate') === 'y') {
dialog_alert('SMS 발송 권한은 발신관리책임자 또는 발신관리위임자만 부여할 수 있습니다.<br>본인인증을 완료해야 발신관리위임자 역할을 수행할 수 있습니다.<br>운영자 수정 페이지에서 본인인증을 먼저 완료하시기 바랍니다.', '경고');
return false;
}
if ($smsMeta.data('editorRepresentative') === 'y' && $smsMeta.data('callerBlocked') === 'y') {
dialog_alert('SMS 발송 메뉴 접근이 제한되었습니다.<br>스팸 또는 부정 사용으로 차단되어 접근할 수 없습니다.<br>자세한 사항은 1:1문의로 문의하시기 바랍니다.', '경고');
return false;
 
Admin/policy/manage_register.php+8-4
변경 전manage_register.php
<tbody>
<?php if (!empty($messageDelegate['visible'])): // SMS 발신 위임자 권한 (권한 설정 테이블 row, 지정/회수는 최고운영자만) ?>
<tr class="js-message-delegate-area">
<th>SMS 발신 위임자 권한 설정</th>
<td class="form-inline">
<input type="hidden" name="messageDelegateAction" id="messageDelegateAction" value="">
<?php if ($messageDelegate['assigned'] === true): ?>
<span class="notice-info">미지정</span>
<?php endif; ?>
<br>
<span class="notice-info">문자를 발송할 '발송담당자' 지정은 위임과 다르므로 <a href="/policy/manage_permission.php" target="_blank" class="btn-link">[설정 > 관리 정책 > 운영자 관리]</a> 페이지의 CRM 추가설정 중 'SMS 발송' 권한을 설정해 주세요.</span>
</td>
</tr>
<?php if ($messageDelegate['assigned'] === false && $messageDelegate['editable'] === true): ?>
<tr class="js-message-delegate-set-reason-row" style="display:none;">
<th>SMS 발신 위임자 권한 설정 사유</th>
<td>
<input type="text" id="messageDelegateSetReason" class="form-control width-2xl" maxlength="50">
<span class="notice-info">SMS 발송 위임 권한을 설정하기 위한 명확한 사유를 입력해 주세요.</span>
<?php endif; ?>
<?php if ($messageDelegate['assigned'] === true && $messageDelegate['editable'] === true): ?>
<tr class="js-message-delegate-release-reason-row" style="display:none;">
<th>SMS 발신 위임자 권한 해제 사유</th>
<td>
<input type="text" id="messageDelegateReleaseReason" class="form-control width-2xl" maxlength="50">
<span class="notice-info">SMS 발송 위임 권한을 해제하기 위한 명확한 사유를 입력해 주세요.</span>
});
});
 
 
 
 
 
$('.js-check-authno').click(function () {
var cell_phone_auth_no = $('input[name=cellPhoneAuthNo]').val();
if (!manage_register.regex.auth_no.test(cell_phone_auth_no)) {
 
변경 후manage_register.php
<tbody>
<?php if (!empty($messageDelegate['visible'])): // SMS 발신 위임자 권한 (권한 설정 테이블 row, 지정/회수는 최고운영자만) ?>
<tr class="js-message-delegate-area">
<th>발신관리위임자 권한 설정</th>
<td class="form-inline">
<input type="hidden" name="messageDelegateAction" id="messageDelegateAction" value="">
<?php if ($messageDelegate['assigned'] === true): ?>
<span class="notice-info">미지정</span>
<?php endif; ?>
<br>
<span class="notice-info">문자를 발송할 '발송담당자' 지정은 위임과 다르므로 <a href="/policy/manage_permission.php" target="_blank" class="btn-link">[설정 > 관리 정책 > 운영자 권한 설정]</a> 페이지의 CRM 추가설정 중 'SMS 발송' 권한을 설정해 주세요.</span>
</td>
</tr>
<?php if ($messageDelegate['assigned'] === false && $messageDelegate['editable'] === true): ?>
<tr class="js-message-delegate-set-reason-row" style="display:none;">
<th>발신관리위임자 권한 설정 사유</th>
<td>
<input type="text" id="messageDelegateSetReason" class="form-control width-2xl" maxlength="50">
<span class="notice-info">SMS 발송 위임 권한을 설정하기 위한 명확한 사유를 입력해 주세요.</span>
<?php endif; ?>
<?php if ($messageDelegate['assigned'] === true && $messageDelegate['editable'] === true): ?>
<tr class="js-message-delegate-release-reason-row" style="display:none;">
<th>발신관리위임자 권한 해제 사유</th>
<td>
<input type="text" id="messageDelegateReleaseReason" class="form-control width-2xl" maxlength="50">
<span class="notice-info">SMS 발송 위임 권한을 해제하기 위한 명확한 사유를 입력해 주세요.</span>
});
});
 
$('input[name="cellPhone"]').on('input', function () {
$('.my-sms-auth').text('(미인증)');
});
 
$('.js-check-authno').click(function () {
var cell_phone_auth_no = $('input[name=cellPhoneAuthNo]').val();
if (!manage_register.regex.auth_no.test(cell_phone_auth_no)) {
 
Admin/policy/manage_security.php+1-2
변경 전manage_security.php
화면보안접속 사용 시 보안접속 인증화면이 노출되는 화면<br>
- 설정 > 관리정책 > 운영자관리/운영자등록<br>
- 설정 > 관리정책 > 운영보안설정<br>
- 설정 > 결제정책 > 무통장입금은행관리<br>
- CRM > 메시지 발송 > 모바일 메시지 발송
</p>
</div>
</td>
 
변경 후manage_security.php
화면보안접속 사용 시 보안접속 인증화면이 노출되는 화면<br>
- 설정 > 관리정책 > 운영자관리/운영자등록<br>
- 설정 > 관리정책 > 운영보안설정<br>
- 설정 > 결제정책 > 무통장입금은행관리
 
</p>
</div>
</td>
 
Admin/promotion/cart_remind_list.php+10
변경 전cart_remind_list.php
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/aggregator-message-router.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/sms/sms-caller-hub-layer.js') ?>"></script>
 
<div class="page-header js-affix">
<h3><?php echo end($naviMenu->location); ?>
</h3>
menual_send();
}
function menual_send() {
 
 
 
 
 
 
 
 
 
var params = {
mode: 'sendCartRemindManual',
cartRemindNo: cartRemindNo,
 
변경 후cart_remind_list.php
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/aggregator-message-router.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/sms/sms-caller-hub-layer.js') ?>"></script>
<script src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/crm/sms-send-gate.js') ?>"></script>
<div class="page-header js-affix">
<h3><?php echo end($naviMenu->location); ?>
</h3>
menual_send();
}
function menual_send() {
if (window.__cartRemindGatePassed !== true) {
SmsSendGate.ensure().then(function () {
window.__cartRemindGatePassed = true;
menual_send();
window.__cartRemindGatePassed = false;
}).catch(function () {});
return;
}
 
var params = {
mode: 'sendCartRemindManual',
cartRemindNo: cartRemindNo,
 
Admin/promotion/comeback_coupon_list.php+13
변경 전comeback_coupon_list.php
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/aggregator-message-router.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/sms/sms-caller-hub-layer.js') ?>"></script>
 
<div class="page-header js-affix">
<h3><?php echo end($naviMenu->location); ?></h3>
<div class="btn-group">
}
});
 
 
 
function sendAction(sno, couponNo) {
var params = {
mode : 'ajaxComebackCouponActionCount',
var smsCount = $('#sms-point').val();
var smsFl = $('#dataInfo' + sno).attr('data-smsFl');
 
 
 
 
 
 
 
 
 
 
 
$.ajax({
method: "POST",
cache: false,
 
변경 후comeback_coupon_list.php
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/aggregator-message-router.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/sms/sms-caller-hub-layer.js') ?>"></script>
<script src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/crm/sms-send-gate.js') ?>"></script>
<div class="page-header js-affix">
<h3><?php echo end($naviMenu->location); ?></h3>
<div class="btn-group">
}
});
 
var comebackCouponGatePassed = false;
 
function sendAction(sno, couponNo) {
var params = {
mode : 'ajaxComebackCouponActionCount',
var smsCount = $('#sms-point').val();
var smsFl = $('#dataInfo' + sno).attr('data-smsFl');
 
if (smsFl === 'y' && comebackCouponGatePassed !== true) {
SmsSendGate.ensure().then(function () {
comebackCouponGatePassed = true;
sendAction(sno, couponNo);
comebackCouponGatePassed = false;
}).catch(function () {});
 
return false;
}
 
$.ajax({
method: "POST",
cache: false,
 
Admin/share/layer_message_representative_register_guide.php-155
변경 전layer_message_representative_register_guide.php
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/aggregator-message-router.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/sms/sms-caller-hub-layer.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/manager-certification.js') ?>"></script>
<link rel="stylesheet" href="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/css/share/layer-message-representative-register-guide.css') ?>">
<div class="modal-dialog__content ncua-sms-representative-guide">
<div class="ncua-sms-representative-guide-hero">
<p class="ncua-sms-representative-guide-hero-title"><b>우리 쇼핑몰 번호, 도용되지 않게 지켜드려요</b></p>
<p class="ncua-sms-representative-guide-hero-desc">발신관리책임자와 담당자 정보로 발신번호 사용을 관리해<br/>다른 사람이 SMS를 보내지 못하게 막아줘요.</p>
</div>
<ul class="ncua-sms-representative-guide-list">
<li class="ncua-flex"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="none"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 6 9 17l-5-5"></path></svg>등록은 한 번, 1분이면 완료</li>
<li class="ncua-flex">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="none"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 6 9 17l-5-5"></path></svg>
<span class="ncua-sms-representative-guide-data">
등록 시, 최고운영자 정보가 <em class="ncua-sms-representative-guide-emphasis">통합회원 정보로 자동 변경</em>
<span>등록 이후 이름·휴대폰번호·이메일은 <a href="https://accounts.godo.co.kr/mypage/acc" target="_blank" rel="noopener noreferrer">통합회원 마이페이지</a>에서만 수정할 수 있어요.</span>
</span>
</li>
<li class="ncua-flex">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="none"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 6 9 17l-5-5"></path></svg>
<span class="ncua-sms-representative-guide-data">
26년 10월 25일까지 등록 필수
<span>미등록 시 SMS 관련 기능 이용이 제한됩니다.</span>
</span>
</li>
</ul>
 
<ul class="ncua-sms-representative-guide-caution">
<li>※ <?= !empty($noticeUri) ? '<a href="' . $noticeUri . '" target="_blank">공지사항</a>' : '공지사항' ?>에서 등록 방법, 미등록 시 제한 사항에 대해 자세하게 확인하실 수 있습니다.</li>
<li>※ 급증하는 불법스팸·스미싱 피해 예방을 위한 전기통신사업법 제22조의11 및 전송자격인증 기준 등에 관한 고시(방통위 고시 제2026-1호) 개정에 따른 조치입니다.</li>
</ul>
</div>
<div class="modal-dialog__footer">
<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" class="js-hide-representative-guide" /></span>
<span class="ncua-checkbox-field__text">7일동안 보지 않기</span>
</label>
<div>
<button type="button" class="ncua-btn ncua-btn--md ncua-btn--secondary-gray" onclick="layer_close();"><span class="ncua-btn__label">나중에 하기</span></button>
<button type="button" class="ncua-btn ncua-btn--md ncua-btn--primary js-representative-register">등록하기</button>
</div>
</div>
<script>
(function () {
// 안내 레이어 쿠키 규약 — 'false' 저장이 노출 억제를 뜻한다 (layer_legalRequirements.php 와 동일)
var displayCookie = <?= json_encode($displayCookie) ?>;
var expireDay = <?= json_encode($displayCookieExpireDay) ?>;
var isCommerceConnected = <?= json_encode($isCommerceConnected) ?>;
var isSelfCertified = <?= json_encode($isSelfCertified) ?>;
var isCommerceLoginRequired = <?= json_encode($isCommerceLoginRequired) ?>;
$('.js-hide-representative-guide').on('change', function () {
if (this.checked) {
$.cookie(displayCookie, 'false', {expires: expireDay, path: '/'});
return;
}
$.removeCookie(displayCookie, {path: '/'});
});
 
$('.js-representative-register').on('click', function () {
layer_close();
 
// 미연동 최고운영자는 기존 통합회원 연동 프로세스로 보낸다 (복귀 주소는 컨트롤러가 URL 에 담아둔다)
if (!isCommerceConnected) {
NCDSAlert({
message: '발신관리책임자 등록을 위해 통합회원 연동을 진행합니다.',
subMessage: '통합회원 연동을 한 최고운영자만 발신관리책임자 등록이 가능합니다.',
iconType: 'warning',
callback: function () {
location.href = <?= json_encode($commerceLoginUri) ?>;
}
});
return;
}
 
// 이미 본인인증한 운영자도 일반 로그인이면 등록을 진행시키지 않는다 — 로그인 방식은 인증 이력과 무관하게 판정한다
if (isCommerceLoginRequired) {
managerCertification.alertCommerceLoginRequired();
return;
}
 
// 본인인증 이력이 없으면 인증부터 진행한다
if (!isSelfCertified) {
managerCertification.certifyWithGuide({onSuccess: checkBlockAndRegister});
return;
}
 
// 이미 본인인증(솔루션 diValue 존재)한 운영자는 곧바로 등록 흐름을 탄다
checkBlockAndRegister();
});
 
// 인증을 마쳐도 차단된 책임자면 등록을 진행시키지 않는다
function checkBlockAndRegister() {
$.post('/share/message_representative_register_ps.php', {mode: 'checkBlock'}, function (response) {
if (!response || response.success !== true) {
showRequestFailed(response);
return;
}
 
if (response.data.blocked) {
NCDSAlert({message: response.data.message, subMessage: response.data.subMessage, iconType: 'error'});
return;
}
 
checkMigrationCaller();
}, 'json').fail(function () {
showRequestFailed();
});
}
 
// 통합회원 전환 전에 등록해 둔 기존 발신관리책임자가 있으면 이관 여부를 묻는다
function checkMigrationCaller() {
$.post('/share/message_representative_register_ps.php', {mode: 'checkMigration'}, function (response) {
if (!response || response.success !== true) {
showRequestFailed(response);
return;
}
 
if (response.data.exists) {
NCDSConfirm({
message: '발신관리책임자 등록이 완료됐습니다.<br>이전에 사용하던 발신번호를 가져오겠습니까?',
subMessage: '가져오기를 완료해야 새로운 발신번호를 등록할 수 있습니다.',
callback: function (confirmed) {
if (!confirmed) {
showRegisterCompleted();
return;
}
 
SmsCallerHubLayer.openMigration({onCompleted: showRegisterCompleted});
}
});
 
return;
}
 
showRegisterCompleted();
}, 'json').fail(function () {
showRequestFailed();
});
}
 
// 응답이 비어 있거나 파싱에 실패하면 백단 문구가 없으므로 기본 문구로 안내한다
function showRequestFailed(response) {
NCDSAlert({message: (response && response.message) || '일시적인 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.', iconType: 'error'});
}
 
// 이관하지 않고 끝나는 경로는 모두 등록 완료를 알린다
function showRegisterCompleted() {
NCDSAlert({
message: '발신관리책임자 등록이 완료되었습니다.',
subMessage: "최고운영자 정보(이름·휴대폰번호·이메일)는<br>이제 통합회원 마이페이지에서만 수정할 수 있습니다.<br><br>부운영자와 함께 SMS 업무를 하려면<br>'발신관리위임자'로 설정하거나 'SMS 발송'권한을 부여해 주세요.",
iconType: 'success'
});
}
})();
</script>
 
변경 후layer_message_representative_register_guide.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Admin/share/layer_message_representative_send_restrict.php+135
변경 전layer_message_representative_send_restrict.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후layer_message_representative_send_restrict.php
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/script/common/aggregator-message-router.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/sms/sms-caller-hub-layer.js') ?>"></script>
<script defer src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/manager-certification.js') ?>"></script>
<link rel="stylesheet" href="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'ncds/css/share/layer-message-representative-send-restrict.css') ?>">
<div class="modal-dialog__content ncua-sms-period-expire">
<p class="ncua-sms-period-expire-caution"><em>SMS 메뉴 접근과 발송 기능 이용이 제한된 상태입니다.</em></p>
<ul>
<li class="ncua-notice-info">발신관리책임자 등록을 완료하면 즉시 다시 이용할 수 있습니다.</li>
<li class="ncua-notice-info">발신관리책임자 등록은 최고운영자만 할 수 있으며,<br>등록되면 최고운영자 정보가 <a class="ncua-btn--tertiary has-underline" href="https://accounts.godo.co.kr/mypage/acc" target="_blank" rel="noopener noreferrer"><strong>통합회원 정보</strong></a><strong>로 자동 변경</strong>됩니다.</li>
<li class="ncua-notice-info"><?= !empty($guideUri) ? '<a class="ncua-btn--tertiary has-underline" href="' . gd_htmlspecialchars($guideUri) . '" target="_blank">관련 가이드</a>' : '관련 가이드' ?>에서 등록 방법, 미등록 시 제한 사항에 대해 자세하게 확인하실 수 있습니다.</li>
</ul>
<p class="ncua-sms-period-expire-info">※ 「전기통신사업법」 제22조의11 및 「전송자격인증 기준 등에 관한 고시」(방통위 고시 제2026-1호) 2.1 계정관리 기준에 따른 안내입니다.</p>
</div>
<div class="modal-dialog__footer ncua-justify-between">
<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" class="js-hide-representative-guide" /></span>
<span class="ncua-checkbox-field__text">7일동안 보지 않기</span>
</label>
<div>
<button type="button" class="ncua-btn ncua-btn--sm ncua-btn--secondary-gray" onclick="layer_close();"><span class="ncua-btn__label">다음에 등록하기</span></button>
<?php if (!empty($isRegisterAvailable)) { ?>
<button type="button" class="ncua-btn ncua-btn--sm ncua-btn--primary js-representative-register"><span class="ncua-btn__label">발신관리책임자 등록</span></button>
<?php } ?>
</div>
</div>
<script>
(function () {
// 안내 레이어 쿠키 규약 — 'false' 저장이 노출 억제를 뜻한다 (layer_legalRequirements.php 와 동일)
var displayCookie = <?= json_encode($displayCookie) ?>;
var expireDay = <?= json_encode($displayCookieExpireDay) ?>;
var isCommerceConnected = <?= json_encode($isCommerceConnected) ?>;
var isSelfCertified = <?= json_encode($isSelfCertified) ?>;
var isCommerceLoginRequired = <?= json_encode($isCommerceLoginRequired) ?>;
 
$('.js-hide-representative-guide').on('change', function () {
if (this.checked) {
$.cookie(displayCookie, 'false', {expires: expireDay, path: '/'});
return;
}
$.removeCookie(displayCookie, {path: '/'});
});
 
$('.js-representative-register').on('click', function () {
layer_close();
 
// 미연동 최고운영자는 기존 통합회원 연동 프로세스로 보낸다 (복귀 주소는 컨트롤러가 URL 에 담아둔다)
if (!isCommerceConnected) {
NCDSAlert({
message: '발신관리책임자 등록을 위해 통합회원 연동을 진행합니다.',
subMessage: '통합회원 연동을 한 최고운영자만 발신관리책임자 등록이 가능합니다.',
iconType: 'warning',
callback: function () {
location.href = <?= json_encode($commerceLoginUri) ?>;
}
});
return;
}
 
// 이미 본인인증한 운영자도 일반 로그인이면 등록을 진행시키지 않는다 — 로그인 방식은 인증 이력과 무관하게 판정한다
if (isCommerceLoginRequired) {
managerCertification.alertCommerceLoginRequired();
return;
}
 
// 본인인증 이력이 없으면 인증부터 진행한다
if (!isSelfCertified) {
managerCertification.certifyWithGuide({onSuccess: checkBlockAndRegister});
return;
}
 
// 이미 본인인증(솔루션 diValue 존재)한 운영자는 곧바로 등록 흐름을 탄다
checkBlockAndRegister();
});
 
// 인증을 마쳐도 차단된 책임자면 등록을 진행시키지 않는다
function checkBlockAndRegister() {
$.post('/share/message_representative_register_ps.php', {mode: 'checkBlock'}, function (response) {
if (!response || response.success !== true) {
showRequestFailed(response);
return;
}
 
if (response.data.blocked) {
NCDSAlert({message: response.data.message, subMessage: response.data.subMessage, iconType: 'error'});
return;
}
 
checkMigrationCaller();
}, 'json').fail(function () {
showRequestFailed();
});
}
 
// 통합회원 전환 전에 등록해 둔 기존 발신관리책임자가 있으면 이관 여부를 묻는다
function checkMigrationCaller() {
$.post('/share/message_representative_register_ps.php', {mode: 'checkMigration'}, function (response) {
if (!response || response.success !== true) {
showRequestFailed(response);
return;
}
 
if (response.data.exists) {
NCDSConfirm({
message: '발신관리책임자 등록이 완료됐습니다.<br>이전에 사용하던 발신번호를 가져오겠습니까?',
subMessage: '가져오기를 완료해야 새로운 발신번호를 등록할 수 있습니다.',
callback: function (confirmed) {
if (!confirmed) {
showRegisterCompleted();
return;
}
 
SmsCallerHubLayer.openMigration({onCompleted: showRegisterCompleted});
}
});
 
return;
}
 
showRegisterCompleted();
}, 'json').fail(function () {
showRequestFailed();
});
}
 
// 응답이 비어 있거나 파싱에 실패하면 백단 문구가 없으므로 기본 문구로 안내한다
function showRequestFailed(response) {
NCDSAlert({message: (response && response.message) || '일시적인 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.', iconType: 'error'});
}
 
// 이관하지 않고 끝나는 경로는 모두 등록 완료를 알린다
function showRegisterCompleted() {
managerCertification.registerCompletedAlert(function () { window.location.reload(); });
}
})();
</script>
 
Admin/share/layer_occupancy_auth.php+409
변경 전layer_occupancy_auth.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후layer_occupancy_auth.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
?>
<link rel="stylesheet" href="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'css/layer-occupancy-auth.css') ?>">
<script type="text/javascript">
(function () {
if (BootstrapDialog.occupancyAuthGuarded) {
return;
}
BootstrapDialog.occupancyAuthGuarded = true;
 
BootstrapDialog.closeAll = function () {
$.each(BootstrapDialog.dialogs, function (id, instance) {
var cssClass = instance.getCssClass ? String(instance.getCssClass() || '') : '';
if (cssClass.indexOf('occupancy-auth-dialog') === -1) {
instance.close();
}
});
};
}());
</script>
<?php if (!empty($isLocked)): ?>
<div class="ncua-sms-auth-modal">
<p class="ncua-sms-auth-caution js-occupancy-auth-caution"><strong><?= $lockedMessage ?></strong><br>잠금 해제를 위해서는 본인확인 절차를 진행해야 합니다.</p>
<div class="text-center mgt20">
<button type="button" class="ncua-btn ncua-btn--sm ncua-btn--secondary-gray js-layer-close"><span class="ncua-btn__label">닫기</span></button>
<button type="button" class="ncua-btn ncua-btn--sm ncua-btn--primary js-unlock-occupancy-auth"><span class="ncua-btn__label">본인 인증</span></button>
</div>
</div>
 
<script type="text/javascript">
(function () {
var AUTH_CONTEXT = <?= json_encode($authContext, JSON_UNESCAPED_UNICODE) ?>;
 
$('.js-unlock-occupancy-auth').on('click', function () {
$.getScript('<?= PATH_ADMIN_GD_SHARE ?>script/manager-certification.js').done(function () {
managerCertification.certify({
onComplete: function (success, reason) {
if (success === false) {
if (reason === 'mismatch') {
// 불일치는 잠금 모달을 유지한 채 안내 문구만 바꿔 재인증을 유도한다
$('.js-occupancy-auth-caution').html('<strong>인증하신 정보가 등록된 운영자의 정보와 일치하지 않습니다. 다시 인증해 주세요.</strong>');
return;
}
NCDSAlert({message: '본인인증에 실패했습니다. 인증 정보를 확인 후 다시 시도해 주세요.', iconType: 'error'});
return;
}
 
// 발송 화면에서는 리로드하면 작성 중인 발송 내용이 사라지므로 게이트만 다시 태운다
var reloadsAfterAuth = (AUTH_CONTEXT !== 'send' || typeof SmsSendGate === 'undefined');
if (reloadsAfterAuth && typeof window.occupancyAuthCompleted === 'function') {
window.occupancyAuthCompleted();
$('.js-layer-close').trigger('click');
return;
}
$('.js-layer-close').trigger('click');
if (reloadsAfterAuth) {
location.reload();
return;
}
SmsSendGate.ensure({}).catch(function () {});
}
});
});
});
})();
</script>
<?php else: ?>
<div class="ncua-sms-auth-modal">
<details class="ncua-accordion ncua-accordion--blue" open>
<summary>안내사항</summary>
<ul>
<li class="ncua-notice-info">「전기통신사업법」 제22조의11 및 전송자격인증 기준(방송통신위원회 고시)에 따라 계정 탈취 및 무단 사용을 방지하기 위해 <?= gd_htmlspecialchars($authNotice) ?> 적용됩니다. 등록된 인증수단으로 본인 확인을 완료한 후 이용해 주세요.</li>
</ul>
</details>
<div class="ncua-table ncua-table--vertical mgt20">
<table>
<tbody>
<?php if (!empty($hasAuthTarget)): ?>
<tr>
<th><div>인증수단</div></th>
<td>
<div class="ncua-flex-gap">
<?php foreach ($authTypeStates as $authType => $selectable): ?>
<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="occupancyAuthType" value="<?= gd_htmlspecialchars($authType) ?>" <?= gd_isset($checked['occupancyAuthType'][$authType]); ?> <?= gd_isset($disabled['occupancyAuthType'][$authType]); ?> />
</span>
<span><span class="ncua-radio-field__text"><?= gd_htmlspecialchars(gd_isset($authTypeLabels[$authType], $authType)) ?></span></span>
</label>
<?php endforeach; ?>
</div>
<p class="ncua-radio-field__support-text mgl5">운영자 관리 페이지에서 설정한 인증수단 정보로 인증번호가 발송됩니다.</p>
</td>
</tr>
<tr>
<th><div>인증정보</div></th>
<td>
<div class="ncua-flex ncua-flex-column ncua-align-items-start">
<div class="js-otp-info-line">
<span class="js-auth-target-display"></span>
<button type="button" class="ncua-btn ncua-btn--xs ncua-btn--secondary js-request-occupancy-otp"><span class="ncua-btn__label">인증번호 발송</span></button>
<span class="ncua-caution-text js-otp-status"></span>
<span class="ncua-caution-text js-resend-notice"></span>
</div>
<div class="js-otp-code-row mgt10" style="display:none;">
<div class="ncua-input ncua-input--xs ncua-input-width-320 js-otp-input">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input type="text" name="occupancyAuthCode" id="occupancyAuthCode" maxlength="6" placeholder="인증번호를 입력해주세요." autocomplete="off">
<div class="ncua-input__icon-wrap ncua-input__right-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="none" class="ncua-input__destructive-icon"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 16v-4m0-4h.01M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10"></path></svg>
</div>
</div>
</div>
</div>
</div>
<p class="ncua-caution-text js-otp-error mgt5" style="display:none;"><em></em></p>
</div>
</td>
</tr>
<?php else: ?>
<tr>
<th><div>인증수단</div></th>
<td><span class="ncua-caution-text js-no-target"><em><?= nl2br(gd_htmlspecialchars($noAuthTargetMessage)) ?></em></span></td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<div class="text-center mgt20">
<button type="button" class="ncua-btn ncua-btn--sm ncua-btn--secondary-gray js-layer-close"><span class="ncua-btn__label">닫기</span></button>
<?php if (!empty($hasAuthTarget)): ?>
<button type="button" class="ncua-btn ncua-btn--sm ncua-btn--primary js-complete-occupancy-auth"><span class="ncua-btn__label">인증 완료</span></button>
<?php endif; ?>
</div>
</div>
 
<script type="text/javascript">
(function () {
var PS_URL = '../share/occupancy_auth_ps.php';
var LAYER_URL = '../share/layer_occupancy_auth.php';
var CODE_INPUT = '#occupancyAuthCode';
var CODE_PATTERN = /^[0-9]{1,6}$/;
 
var AUTH_CONTEXT = <?= json_encode($authContext, JSON_UNESCAPED_UNICODE) ?>;
 
var popupSession = <?= (int)$popupSessionSeconds ?>;
var otpTtl = <?= (int)$otpTtlSeconds ?>;
var resendInterval = <?= (int)$resendIntervalSeconds ?>;
var authTargets = <?= json_encode($authTypeTargets, JSON_UNESCAPED_UNICODE) ?>;
 
var otpTimer = null;
var restRemain = 0;
var sessionHandle = null;
var resendHandle = null;
var resendRemain = 0;
var lastSentAt = 0;
 
function selectedType() {
return $('input[name="occupancyAuthType"]:checked').val() || 'SMS';
}
 
function updateAuthTargetDisplay() {
$('.js-auth-target-display').text(authTargets[selectedType()] || '');
}
 
function nowSec() {
return Math.floor(Date.now() / 1000);
}
 
function pad2(value) {
return value < 10 ? '0' + value : '' + value;
}
 
function renderStatus() {
$('.js-otp-status').text(pad2(Math.floor(restRemain / 60)) + ':' + pad2(restRemain % 60));
}
 
function renderResendCoolDown() {
$('.js-resend-notice').text(resendInterval + '초 후에 다시 시도해주세요.');
}
 
function clearResendCoolDown() {
resendRemain = 0;
if (resendHandle) {
clearInterval(resendHandle);
resendHandle = null;
}
$('.js-resend-notice').text('');
}
 
function startResendCoolDown() {
clearResendCoolDown();
resendRemain = resendInterval - (nowSec() - lastSentAt);
if (resendRemain <= 0) {
return;
}
renderResendCoolDown();
resendHandle = setInterval(tickResendCoolDown, 1000);
}
 
function tickResendCoolDown() {
resendRemain -= 1;
if (resendRemain <= 0) {
clearResendCoolDown();
return;
}
renderResendCoolDown();
}
 
function tickCountDown() {
restRemain -= 1;
if (restRemain <= 0) {
restRemain = 0;
stopOtpTimer();
$('.js-otp-status').text('00:00');
return;
}
renderStatus();
}
 
function startOtpTimer() {
stopOtpTimer();
restRemain = otpTtl;
renderStatus();
otpTimer = setInterval(tickCountDown, 1000);
}
 
function stopOtpTimer() {
if (otpTimer) {
clearInterval(otpTimer);
otpTimer = null;
}
}
 
function stopTimers() {
stopOtpTimer();
clearResendCoolDown();
if (sessionHandle) {
clearTimeout(sessionHandle);
sessionHandle = null;
}
}
 
function startPopupSession() {
if (sessionHandle) {
clearTimeout(sessionHandle);
}
sessionHandle = setTimeout(function () {
stopTimers();
if (typeof window.occupancyAuthMarkExpired === 'function') {
window.occupancyAuthMarkExpired();
}
// callback 은 [확인] 에서만 실행돼 백드롭 클릭으로 닫으면 만료된 인증 모달이 남는다
NCDSAlert({message: '인증시간이 만료되었습니다. 다시 시도해주세요.', iconType: 'error', onClose: closeLayer});
}, popupSession * 1000);
}
 
function clearError() {
$('.js-otp-input').removeClass('destructive');
$('.js-otp-error').hide().find('em').text('');
}
 
function showError(message) {
$('.js-otp-input').addClass('destructive');
$('.js-otp-error').find('em').text(message);
$('.js-otp-error').show();
}
 
function showSendFailure(message) {
$('.js-otp-error').find('em').text(message);
$('.js-otp-error').show();
}
 
function showResendRequired(message) {
stopOtpTimer();
restRemain = 0;
$('.js-otp-status').text('00:00');
showError(message);
}
 
function showLockedLayer() {
stopTimers();
var $layerBody = $('.js-otp-error').closest('.bootstrap-dialog-message');
if ($layerBody.length === 0) {
closeLayer();
return;
}
$.ajax({
url: LAYER_URL,
data: {authContext: AUTH_CONTEXT},
cache: false,
dataType: 'html'
}).done(function (lockedHtml) {
$layerBody.html(lockedHtml);
}).fail(closeLayer);
}
 
function resetToInitial() {
stopOtpTimer();
restRemain = 0;
lastSentAt = 0;
clearResendCoolDown();
$('.js-otp-code-row').hide();
clearError();
$(CODE_INPUT).val('');
$('.js-otp-status').text('');
$('.js-request-occupancy-otp').removeClass('ncua-btn--secondary-gray').addClass('ncua-btn--secondary');
$('.js-request-occupancy-otp .ncua-btn__label').text('인증번호 발송');
}
 
function requestOtp() {
if (lastSentAt > 0 && nowSec() - lastSentAt < resendInterval) {
startResendCoolDown();
return;
}
clearError();
$.post(PS_URL, {mode: 'sendOtp', authType: selectedType(), authContext: AUTH_CONTEXT}, function (data) {
if (data.success === false) {
if ((data.data || {}).locked) {
showLockedLayer();
return;
}
if ((data.data || {}).sendBlocked) {
NCDSAlert({message: data.message, iconType: 'error'});
return;
}
showSendFailure(data.message);
return;
}
lastSentAt = nowSec();
clearError();
$('.js-otp-code-row').show();
$(CODE_INPUT).val('').focus();
$('.js-request-occupancy-otp').removeClass('ncua-btn--secondary').addClass('ncua-btn--secondary-gray');
$('.js-request-occupancy-otp .ncua-btn__label').text('재전송');
startOtpTimer();
}, 'json');
}
 
function completeAuth() {
var code = $(CODE_INPUT).val();
if (code === '') {
showError('인증번호를 입력해 주세요.');
$(CODE_INPUT).focus();
return;
}
if (CODE_PATTERN.test(code) === false) {
showError('인증번호가 일치하지 않습니다.');
$(CODE_INPUT).focus();
return;
}
 
$.post(PS_URL, {mode: 'verifyOtp', code: code, authContext: AUTH_CONTEXT}, function (data) {
if (data.success) {
if (typeof window.occupancyAuthCompleted === 'function') {
window.occupancyAuthCompleted();
}
closeLayer();
return;
}
 
var result = data.data || {};
if (result.expired) {
stopTimers();
NCDSAlert({message: data.message, iconType: 'error', onClose: closeLayer});
return;
}
if (result.locked) {
showLockedLayer();
return;
}
if (result.resendRequired) {
showResendRequired(data.message);
return;
}
showError(data.message);
$(CODE_INPUT).focus();
}, 'json');
}
 
function closeLayer() {
stopTimers();
$('.js-layer-close').trigger('click');
}
 
$('.js-layer-close').on('click', stopTimers);
$('.js-request-occupancy-otp').on('click', requestOtp);
$('.js-complete-occupancy-auth').on('click', completeAuth);
$(CODE_INPUT).on('input', clearError);
$('input[name="occupancyAuthType"]').on('change', function () {
updateAuthTargetDisplay();
resetToInitial();
});
updateAuthTargetDisplay();
if (window.occupancyAuthCleanup) {
window.occupancyAuthCleanup();
}
startPopupSession();
window.occupancyAuthCleanup = stopTimers;
})();
</script>
<?php endif; ?>
 
Admin/share/layer_sms_contents.php+16-5
변경 전layer_sms_contents.php
 
 
<div>
<div class="table-title gd-help-manual">SMS / LMS 문구 등록</div>
<table class="table table-cols">
<tr class="text-center">
<td><input name="sno[]" type="checkbox" value="<?php echo $val['sno']; ?>" /></td>
<td class="font-num"><?php echo number_format($page->idx--); ?></td>
<td><?php echo $arrGroup[$val['smsAutoCode']]; ?></td>
<td class="smsContents-<?php echo $val['sno']; ?>"><?php echo $val['contents']; ?></td>
<td><input type="button" value="선택" class="btn btn-sm btn-black js-sms-contents-select" data-sno="<?php echo $val['sno']; ?>" /></span></td>
</tr>
// 글자수 체크
$('textarea[name=smsContentsText]').keyup(getSmsContentsLength).change(getSmsContentsLength);
 
 
 
// SMS 문구 등록
$('.js-sms-contents-register').click(function (e) {
var smsContentsGroup = $('select[name=\'smsContentsGroup\']').val();
var smsContentsText = $('textarea[name=\'smsContentsText\']').val();
 
if (smsContentsGroup == '' || smsContentsGroup == '0') {
alert('그룹을 선택해 주세요.');
return;
}
 
if (smsContentsText == '') {
alert('내용을 작성해 주세요.');
return;
}
 
if (data == 'OK') {
layer_list_search('page=<?php echo $search['page'];?>');
} else {
alert('등록시 오류가 발생하였습니다.');
}
});
});
if (data == 'OK') {
layer_list_search('page=<?php echo $search['page'];?>');
} else {
alert('삭제시 오류가 발생하였습니다.');
 
 
 
 
 
 
 
}
});
}
 
변경 후layer_sms_contents.php
<script src="<?= gd_set_browser_cache(PATH_ADMIN_GD_SHARE . 'script/sms/sms-emoji.js') ?>"></script>
 
<div>
<div class="table-title gd-help-manual">SMS / LMS 문구 등록</div>
<table class="table table-cols">
<tr class="text-center">
<td><input name="sno[]" type="checkbox" value="<?php echo $val['sno']; ?>" /></td>
<td class="font-num"><?php echo number_format($page->idx--); ?></td>
<td><?= $arrGroup[$val['smsAutoCode']] ?? '' ?></td>
<td class="smsContents-<?php echo $val['sno']; ?>"><?php echo $val['contents']; ?></td>
<td><input type="button" value="선택" class="btn btn-sm btn-black js-sms-contents-select" data-sno="<?php echo $val['sno']; ?>" /></span></td>
</tr>
// 글자수 체크
$('textarea[name=smsContentsText]').keyup(getSmsContentsLength).change(getSmsContentsLength);
 
smsEmoji.bind('textarea[name=smsContentsText]', getSmsContentsLength);
 
// SMS 문구 등록
$('.js-sms-contents-register').click(function (e) {
var smsContentsGroup = $('select[name=\'smsContentsGroup\']').val();
var smsContentsText = $('textarea[name=\'smsContentsText\']').val();
 
if (smsContentsGroup == '' || smsContentsGroup == '0') {
NCDSAlert({message: '그룹을 선택해 주세요.'});
return;
}
 
if (smsContentsText == '') {
NCDSAlert({message: '내용을 작성해 주세요.'});
return;
}
 
if (data == 'OK') {
layer_list_search('page=<?php echo $search['page'];?>');
} else {
NCDSAlert({message: data && data != 'ERROR' ? data : '등록시 오류가 발생하였습니다.', iconType: 'error'});
}
});
});
if (data == 'OK') {
layer_list_search('page=<?php echo $search['page'];?>');
} else {
// 자동알림 사용 건만 제외하고 삭제된 경우에도 문구가 오므로 닫을 때 목록을 다시 읽는다
NCDSAlert({
message: data && data != 'ERROR' ? data : '삭제시 오류가 발생하였습니다.',
iconType: 'error',
onClose: function () {
layer_list_search('page=<?php echo $search['page'];?>');
}
});
}
});
}
 
Component/Database/DBTableField.php+5
변경 전DBTableField.php
// @formatter:off
$arrField = [
['val' => 'sno', 'typ' => 'i', 'def' => null], // 일련번호
 
 
 
 
 
['val' => 'smsType', 'typ' => 's', 'def' => 'user'], // SMS 전송타입 (order, member, promotion, user)
['val' => 'smsAutoCode', 'typ' => 's', 'def' => null], // SMS 자동발송 코드
['val' => 'smsAutoType', 'typ' => 's', 'def' => null], // SMS 자동발송 수신 종류 (member, admin, provider)
 
변경 후DBTableField.php
// @formatter:off
$arrField = [
['val' => 'sno', 'typ' => 'i', 'def' => null], // 일련번호
['val' => 'templateCode', 'typ' => 's', 'def' => null],
['val' => 'templateCategoryCode', 'typ' => 's', 'def' => null],
['val' => 'referenceSmsContentNo', 'typ' => 'i', 'def' => null],
['val' => 'approvalStatus', 'typ' => 's', 'def' => 'PENDING'],
['val' => 'autoConversionFl', 'typ' => 's', 'def' => 'n'],
['val' => 'smsType', 'typ' => 's', 'def' => 'user'], // SMS 전송타입 (order, member, promotion, user)
['val' => 'smsAutoCode', 'typ' => 's', 'def' => null], // SMS 자동발송 코드
['val' => 'smsAutoType', 'typ' => 's', 'def' => null], // SMS 자동발송 수신 종류 (member, admin, provider)
 
Component/Member/Manager.php+17-4
변경 전Manager.php
if ($sessionManager['managerId'] === $arrData['managerId']) {
$sessionManager['isSmsAuth'] = $arrData['isSmsAuth'];
$sessionManager['isEmailAuth'] = $arrData['isEmailAuth'];
$sessionManager['email'] = $arrData['email'];
$sessionManager['cellPhone'] = $arrData['cellPhone'];
 
 
 
 
$session->set(self::SESSION_MANAGER_LOGIN, $sessionManager);
}
 
if ($smsRoleGuard->isDelegateOrHigher() === false) {
throw new Exception(__('SMS 발송 권한은 발신관리책임자 또는 발신관리위임자만 부여할 수 있습니다.<br>해당 권한을 해제한 후 다시 저장해 주세요.'));
}
 
 
 
if ($smsRoleGuard->isRepresentative() && $smsRoleService->isBlockedRepresentative()) {
throw new Exception(__('SMS 발송 메뉴 접근이 제한되었습니다.<br>스팸 또는 부정 사용으로 차단되어 접근할 수 없습니다.<br>자세한 사항은 1:1문의로 문의하시기 바랍니다.'));
}
*/
protected function setAuthenticationInfo(&$arrData, $smsAuth, $emailAuth)
{
 
 
 
 
 
 
if ($smsAuth['isAdminSmsAuth'] === true) {
if (str_replace('-', '', $smsAuth['authCellPhone']) !== str_replace('-', '', $arrData['cellPhone'])) {
throw new ManagerAuthException(ManagerAuthException::CODE_NOT_EQUALS_CELLPHONE);
}
$arrData['isSmsAuth'] = 'y';
 
$adminSecuritySmsAuthNumber = Otp::getOtp(8);
$contents = sprintf(__('관리자님 인증번호는 [%s] 입니다.'), $adminSecuritySmsAuthNumber);
$isSent = \App::getInstance(OccupancyOtpSmsService::class)->send($managerInfo['cellPhone'], $contents, $adminSecuritySmsAuthNumber);
 
$smsAuth['smsAuthNumber'] = $adminSecuritySmsAuthNumber;
$smsAuth['message'] = $isSent ? 'OK' : 'SMS Send Fail';
 
변경 후Manager.php
if ($sessionManager['managerId'] === $arrData['managerId']) {
$sessionManager['isSmsAuth'] = $arrData['isSmsAuth'];
$sessionManager['isEmailAuth'] = $arrData['isEmailAuth'];
if (array_key_exists('email', $arrData)) {
$sessionManager['email'] = $arrData['email'];
}
if (array_key_exists('cellPhone', $arrData)) {
$sessionManager['cellPhone'] = $arrData['cellPhone'];
}
$session->set(self::SESSION_MANAGER_LOGIN, $sessionManager);
}
 
if ($smsRoleGuard->isDelegateOrHigher() === false) {
throw new Exception(__('SMS 발송 권한은 발신관리책임자 또는 발신관리위임자만 부여할 수 있습니다.<br>해당 권한을 해제한 후 다시 저장해 주세요.'));
}
if ($smsRoleService->isUncertifiedDelegateActor()) {
throw new Exception(__('SMS 발송 권한은 발신관리책임자 또는 발신관리위임자만 부여할 수 있습니다.<br>본인인증을 완료해야 발신관리위임자 역할을 수행할 수 있습니다.<br>운영자 수정 페이지에서 본인인증을 먼저 완료하시기 바랍니다.'));
}
if ($smsRoleGuard->isRepresentative() && $smsRoleService->isBlockedRepresentative()) {
throw new Exception(__('SMS 발송 메뉴 접근이 제한되었습니다.<br>스팸 또는 부정 사용으로 차단되어 접근할 수 없습니다.<br>자세한 사항은 1:1문의로 문의하시기 바랍니다.'));
}
*/
protected function setAuthenticationInfo(&$arrData, $smsAuth, $emailAuth)
{
$isAuthCellPhoneReplaced = $smsAuth['isAdminSmsAuth'] === true
&& str_replace('-', '', $smsAuth['authCellPhone']) !== str_replace('-', '', $arrData['cellPhone']);
// 수정 저장에서 점유인증 뒤 번호를 다시 고치면 미인증으로 보고 기존 번호를 유지한다
if ($isAuthCellPhoneReplaced && ($arrData['mode'] ?? '') === 'modify') {
$smsAuth['isAdminSmsAuth'] = false;
}
if ($smsAuth['isAdminSmsAuth'] === true) {
if ($isAuthCellPhoneReplaced) {
throw new ManagerAuthException(ManagerAuthException::CODE_NOT_EQUALS_CELLPHONE);
}
$arrData['isSmsAuth'] = 'y';
 
$adminSecuritySmsAuthNumber = Otp::getOtp(8);
$contents = sprintf(__('관리자님 인증번호는 [%s] 입니다.'), $adminSecuritySmsAuthNumber);
$isSent = \App::getInstance(OccupancyOtpSmsService::class)->send($managerInfo['cellPhone'], $contents);
 
$smsAuth['smsAuthNumber'] = $adminSecuritySmsAuthNumber;
$smsAuth['message'] = $isSent ? 'OK' : 'SMS Send Fail';
 
Component/Sms/SmsAdmin.php+61-53
변경 전SmsAdmin.php
use Origin\Service\Crm\Message\SmsPersonalInfoAccessLogService;
use Respect\Validation\Validator;
use Origin\Service\Member\CrmGroupService;
 
 
 
 
 
/**
* SMS 관리자 관련 처리
// CLI(스케줄러) 세션은 SessionManager 가 아니라 발행 서비스 생성이 매번 실패하고 수신대상 루프 안에서 호출돼, 첫 실패 후에는 같은 프로세스에서 재시도·재로그하지 않는다
protected static $personalInfoAccessLogPublishFailed = false;
 
 
 
 
 
 
 
 
 
public function __construct($config = [])
{
// 기존 DBTool 파라미터를 보장하기 위한 로직
*/
public function saveSmsAuto($request)
{
// 디비에 저장될 항목을 정리 (insert , update)
$smsAutoReceiveKind = Sms::SMS_AUTO_RECEIVE_LIST;
$smsAutoType = [
'member',
'admin',
'provider',
'board',
'recipient',
];
foreach ($smsAutoReceiveKind as $smsType => $val) {
foreach ($request[$smsType] as $autoCode => $autoData) {
foreach ($smsAutoType as $autoType) {
if (isset($autoData[$autoType . 'Mode'])) {
$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']);
}
}
}
}
// 디비 정보 저장
foreach ($dbData as $saveMode => $tmpData1) {
foreach ($tmpData1 as $smsType => $tmpData2) {
foreach ($tmpData2 as $smsAutoCode => $tmpData3) {
foreach ($tmpData3 as $smsAutoType => $saveData) {
$arrBind = [];
// 저장 방법
if ($saveMode === 'insert') {
$saveData['groupNm'] = __('자동발송');
$saveData['smsAutoCode'] = $smsAutoCode;
$saveData['smsAutoType'] = $smsAutoType;
$saveData['smsType'] = $smsType;
$arrBind = $this->db->get_binding(DBTableField::tableSmsContents(), $saveData, 'insert');
$this->db->set_insert_db(DB_SMS_CONTENTS, $arrBind['param'], $arrBind['bind'], 'y');
} elseif ($saveMode === 'update') {
$arrBind = $this->db->get_binding(DBTableField::tableSmsContents(), $saveData, 'update', gd_array_keys($saveData));
$this->db->bind_param_push($arrBind['bind'], 'i', $saveData['sno']);
$this->db->set_update_db(DB_SMS_CONTENTS, $arrBind['param'], 'sno=?', $arrBind['bind']);
}
unset($arrBind);
}
}
}
}
 
// 기 저장된 항목 삭제
unset($request['mode'], $request['smsCallNum']);
} else {
$this->db->strWhere = 'smsType IN (\'order\', \'member\', \'promotion\', \'board\', \'regular\', \'present\') AND smsAutoCode IS NOT NULL AND smsAutoType IS NOT NULL';
}
$this->db->strOrder = 'smsType ASC';
 
$query = $this->db->query_complete();
 
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_SMS_CONTENTS . array_shift($query) . ' ' . gd_implode(' ', $query);
$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);
// SMS 자동발송 설정값 불러오기
$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'] = '';
 
 
}
}
 
// 기본 검색
$arrWhere[] = 'smsType = ?';
$this->db->bind_param_push($arrBind, 's', $smsType);
 
 
 
// 키워드 검색
if ($search['key'] && $search['keyword']) {
}
$postValue['smsType'] = 'user';
StringUtils::strIsSet($postValue['subject'], '');
 
 
 
 
 
 
 
if (gd_array_key_exists('sno', $postValue)) {
$arrBind = $this->db->get_binding(DBTableField::tableSmsContents(), $postValue, 'update');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$this->db->bind_param_push($arrBind['bind'], 'i', $postValue['sno']);
$this->db->set_update_db(DB_SMS_CONTENTS, $arrBind['param'], 'sno = ?', $arrBind['bind']);
 
 
 
} else {
 
$arrBind = $this->db->get_binding(DBTableField::tableSmsContents(), $postValue, 'insert');
$this->db->set_insert_db(DB_SMS_CONTENTS, $arrBind['param'], $arrBind['bind'], 'y');
 
}
 
return true;
 
변경 후SmsAdmin.php
use Origin\Service\Crm\Message\SmsPersonalInfoAccessLogService;
use Respect\Validation\Validator;
use Origin\Service\Member\CrmGroupService;
use Origin\Enum\Crm\Message\SmsTemplateApprovalStatus;
use Origin\Repository\Member\Sms\SmsContentsRepository;
use Origin\Service\Crm\Message\SmsTemplateService;
use Origin\Service\Sms\AutoSend\SaveAutoSendContentsService;
 
/**
* SMS 관리자 관련 처리
// CLI(스케줄러) 세션은 SessionManager 가 아니라 발행 서비스 생성이 매번 실패하고 수신대상 루프 안에서 호출돼, 첫 실패 후에는 같은 프로세스에서 재시도·재로그하지 않는다
protected static $personalInfoAccessLogPublishFailed = false;
 
const SMS_AUTO_TYPES = [
'member',
'admin',
'provider',
'board',
'recipient',
];
 
public function __construct($config = [])
{
// 기존 DBTool 파라미터를 보장하기 위한 로직
*/
public function saveSmsAuto($request)
{
/** @var SaveAutoSendContentsService $saveAutoSendContentsService */
$saveAutoSendContentsService = \App::getInstance(SaveAutoSendContentsService::class);
$request = $saveAutoSendContentsService->saveAutoSendContents(
$request,
array_keys(Sms::SMS_AUTO_RECEIVE_LIST),
self::SMS_AUTO_TYPES
);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
// 기 저장된 항목 삭제
unset($request['mode'], $request['smsCallNum']);
} else {
$this->db->strWhere = 'smsType IN (\'order\', \'member\', \'promotion\', \'board\', \'regular\', \'present\') AND smsAutoCode IS NOT NULL AND smsAutoType IS NOT NULL';
}
$this->db->strOrder = 'smsType ASC, sno ASC';
 
$query = $this->db->query_complete();
 
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_SMS_CONTENTS . array_shift($query) . ' ' . gd_implode(' ', $query);
$getData = gd_htmlspecialchars_stripslashes($this->db->query_fetch($strSQL, null, false));
foreach ($getData as $data) {
if ((int)($data['referenceSmsContentNo'] ?? 0) === SmsContentsRepository::CLEARED_REFERENCE_SMS_CONTENT_NO) {
$data['contents'] = '';
$data['messageTitle'] = '';
}
$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'];
$setData[$data['smsType']][$data['smsAutoCode']][$data['smsAutoType']]['templateCategoryCode'] = $data['templateCategoryCode'];
$setData[$data['smsType']][$data['smsAutoCode']][$data['smsAutoType']]['referenceSmsContentNo'] = $data['referenceSmsContentNo'] ?? null;
}
unset($getData);
// SMS 자동발송 설정값 불러오기
$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'];
$smsAutoType[$keyType][$key][$sendType . 'TemplateCategoryCode'] = gd_isset($setData[$keyType][$val['code']][$sendType]['templateCategoryCode'], '');
$smsAutoType[$keyType][$key][$sendType . 'ReferenceSmsContentNo'] = gd_isset($setData[$keyType][$val['code']][$sendType]['referenceSmsContentNo'], '');
} 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'] = '';
$smsAutoType[$keyType][$key][$sendType . 'TemplateCategoryCode'] = '';
$smsAutoType[$keyType][$key][$sendType . 'ReferenceSmsContentNo'] = '';
}
}
 
// 기본 검색
$arrWhere[] = 'smsType = ?';
$this->db->bind_param_push($arrBind, 's', $smsType);
$arrWhere[] = 'approvalStatus = ?';
$this->db->bind_param_push($arrBind, 's', SmsTemplateApprovalStatus::APPROVED->value);
 
// 키워드 검색
if ($search['key'] && $search['keyword']) {
}
$postValue['smsType'] = 'user';
StringUtils::strIsSet($postValue['subject'], '');
/** @var SmsTemplateService $smsTemplateService */
$smsTemplateService = \App::getInstance(SmsTemplateService::class);
$postValue = $smsTemplateService->normalizeSendText(
$postValue,
gd_array_key_exists('sno', $postValue) ? (int)$postValue['sno'] : null
);
$contents = $postValue['contents'];
if (gd_array_key_exists('sno', $postValue)) {
$smsTemplateService->assertSavable((int)$postValue['sno']);
$modifyResult = $smsTemplateService->resolveModifyResult(
(int)$postValue['sno'],
$contents,
isset($postValue['subject']) ? (string)$postValue['subject'] : null,
isset($postValue['messageTitle']) ? (string)$postValue['messageTitle'] : null
);
if ($modifyResult['status'] !== null) {
$postValue['approvalStatus'] = $modifyResult['status'];
}
if ($modifyResult['contentsChanged'] === false) {
unset($postValue['contents']);
}
if (!$modifyResult['messageTitleChanged']) {
unset($postValue['messageTitle']);
}
$arrBind = $this->db->get_binding(DBTableField::tableSmsContents(), $postValue, 'update', gd_array_keys($postValue));
$this->db->bind_param_push($arrBind['bind'], 'i', $postValue['sno']);
$this->db->set_update_db(DB_SMS_CONTENTS, $arrBind['param'], 'sno = ?', $arrBind['bind']);
if ($modifyResult['modified']) {
$smsTemplateService->writeModifiedHistory((int)$postValue['sno']);
}
} else {
$postValue['approvalStatus'] = SmsTemplateApprovalStatus::PENDING->value;
$arrBind = $this->db->get_binding(DBTableField::tableSmsContents(), $postValue, 'insert');
$this->db->set_insert_db(DB_SMS_CONTENTS, $arrBind['param'], $arrBind['bind'], 'y');
$smsTemplateService->issueTemplateCodeWithHistory((int)$this->db->insert_id());
}
 
return true;
 
Component/Sms/SmsAuto.php+43-4
변경 전SmsAuto.php
use Origin\Enum\AutoSend\AutoSendSupport;
use Origin\Enum\Crm\Message\FailedMessageSendType;
use Origin\Enum\Crm\Message\SmsAutoSendOverType;
 
use Origin\Service\AutoSend\AutoSendMyappService;
use Origin\Service\AutoSend\AutoSendTemplateVariableReplacer;
use Origin\Service\AutoSend\FindAutoSendConfigService;
{
$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);
$this->db->bind_param_push($binds, 's', $this->smsAutoCodeType);
 
 
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . array_shift($query) . ' ' . gd_implode(' ', $query);
$getData = $this->db->query_fetch($strSQL, $binds);
throw new \Exception(__('자동발송 내용을 찾을 수 없습니다.'));
}
$getData = StringUtils::htmlSpecialCharsStripSlashes($getData);
 
 
 
 
 
return $getData;
}
 
/**
 
변경 후SmsAuto.php
use Origin\Enum\AutoSend\AutoSendSupport;
use Origin\Enum\Crm\Message\FailedMessageSendType;
use Origin\Enum\Crm\Message\SmsAutoSendOverType;
use Origin\Enum\Crm\Message\SmsTemplateApprovalStatus;
use Origin\Service\AutoSend\AutoSendMyappService;
use Origin\Service\AutoSend\AutoSendTemplateVariableReplacer;
use Origin\Service\AutoSend\FindAutoSendConfigService;
{
$logger = $this->logger;
$binds = [];
$approvedStatus = SmsTemplateApprovalStatus::APPROVED->value;
$latestApprovedOf = static fn(string $column): string => 'SELECT stah.' . $column
. ' FROM ' . DB_SMS_TEMPLATE_APPROVAL_HISTORY . ' stah'
. ' WHERE stah.smsContentNo = rsc.sno'
. ' AND stah.approvalStatus = ?'
. ' ORDER BY stah.historyNo DESC LIMIT 1';
$latestApproved = $latestApprovedOf('templateContent');
$latestApprovedTitle = $latestApprovedOf('messageTitle');
$isApproved = 'rsc.approvalStatus = ?';
 
$field = [];
$field[] = 'sc.smsType';
$field[] = 'sc.smsAutoType';
$field[] = 'IF(' . $isApproved . ', rsc.contents, (' . $latestApproved . ')) AS contents';
$field[] = 'sc.subject';
$field[] = 'IF(' . $isApproved . ', rsc.messageTitle, COALESCE((' . $latestApprovedTitle . '), rsc.messageTitle)) AS messageTitle';
 
$where = [];
$where[] = 'sc.smsType = ?';
$where[] = 'sc.smsAutoCode = ?';
$where[] = 'sc.smsAutoType IN (\'' . gd_implode('\', \'', $wheres) . '\')';
$where[] = 'rsc.contents != \'\'';
$where[] = 'rsc.contents IS NOT NULL';
$where[] = '(' . $isApproved . ' OR (' . $latestApproved . ') IS NOT NULL)';
 
$this->db->strField = gd_implode(', ', $field);
$this->db->strJoin = DB_SMS_CONTENTS . ' sc INNER JOIN ' . DB_SMS_CONTENTS . ' rsc ON rsc.sno = COALESCE(sc.referenceSmsContentNo, sc.sno)';
$this->db->strWhere = gd_implode(' AND ', $where);
$this->db->strOrder = 'sc.sno ASC';
 
// query_complete() 가 SELECT → FROM → WHERE 순으로 조립하므로 바인드도 그 순서로 밀어 넣는다
$this->db->bind_param_push($binds, 's', $approvedStatus);
$this->db->bind_param_push($binds, 's', $approvedStatus);
$this->db->bind_param_push($binds, 's', $approvedStatus);
$this->db->bind_param_push($binds, 's', $approvedStatus);
$this->db->bind_param_push($binds, 's', $this->smsType);
$this->db->bind_param_push($binds, 's', $this->smsAutoCodeType);
$this->db->bind_param_push($binds, 's', $approvedStatus);
$this->db->bind_param_push($binds, 's', $approvedStatus);
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . array_shift($query) . ' ' . gd_implode(' ', $query);
$getData = $this->db->query_fetch($strSQL, $binds);
throw new \Exception(__('자동발송 내용을 찾을 수 없습니다.'));
}
$getData = StringUtils::htmlSpecialCharsStripSlashes($getData);
$latestByAutoType = [];
foreach ($getData as $row) {
$latestByAutoType[$row['smsAutoType']] = $row;
}
 
return array_values($latestByAutoType);
}
 
/**
 
Controller/Admin/Crm/AutoSendConfigController.php-192
변경 전AutoSendConfigController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use App;
use Bundle\Component\Sms\Code;
use Bundle\Component\Sms\SmsAutoCode;
use Bundle\Controller\Admin\Controller;
use Framework\Debug\Exception\AlertRedirectException;
use GuzzleHttp\Exception\ConnectException;
use Origin\Enum\AutoSend\AutoSendChannelPolicy;
use Origin\Enum\AutoSend\AutoSendRecipient;
use Origin\Enum\AutoSend\AutoSendRecipientPolicy;
use Origin\Enum\AutoSend\AutoSendOptionVisibilityPolicy;
use Origin\Enum\AutoSend\AutoSendSupport;
use Origin\Enum\AutoSend\AutoSendChannel;
use Origin\Enum\AutoSend\AutoSendTemplateVariableCatalog;
use Origin\Service\AutoSend\FindAutoSendCodeConfigService;
use Origin\Service\AutoSend\FindAutoSendConfigService;
use Origin\Service\Crm\Message\MyappPushTemplateService;
use Origin\Service\Crm\Message\SmsPasswordService;
use Request;
 
class AutoSendConfigController extends Controller
{
public function index()
{
$this->setNcdsSubBodyClass();
$code = Request::get()->get('code');
$isBlock = false;
$blockMode = null;
$validAutoSendSms = false;
 
if (!AutoSendSupport::canSendIfClaim($code)) {
$isBlock = true;
$blockMode = 'claim';
}
 
try {
$validAutoSendSms = $this->validAutoSendSms();
} catch (ConnectException) {
$isBlock = true;
$blockMode = 'smsConnect';
}
 
$this->applyBlockPage();
 
if ($isBlock) {
$this->setData('blockMode', $blockMode);
} else {
/** @var SmsAutoCode $smsAutoCode */
$smsAutoCode = \App::load('\\Component\\Sms\\SmsAutoCode');
$groupCode = $smsAutoCode->getByCode($code);
 
$recipients = AutoSendRecipientPolicy::getCodesWithRecipientsWithBoard()[$code] ?? [];
$channels = AutoSendChannelPolicy::getCodesWithChannelsWithBoard()[$code] ?? [];
 
/** @var FindAutoSendCodeConfigService $codeConfigService */
$codeConfigService = App::getInstance(FindAutoSendCodeConfigService::class);
[$options, $autoSendConfigsResponse, $channelCodeConfigDto] = $codeConfigService->createRecipientConfigByCode($code);
 
/** @var FindAutoSendConfigService $configService */
$configService = App::getInstance(FindAutoSendConfigService::class);
$crmMyappConfig = $configService->getCrmMyappConfig();
$kakaoCrmConfig = $configService->getCrmKakaoConfig();
$autoSendOptionVisibility = AutoSendOptionVisibilityPolicy::getOptionVisibilityByCode($code)
?? AutoSendOptionVisibilityPolicy::getDefaultBoardVisibility($code);
$checkedRecipients = $this->collectGroupsHavingAutoSendEnabled($autoSendConfigsResponse);
$shouldChangeKakaoTemplate = in_array($kakaoCrmConfig['sender'] ?? '', ['kakaoAlrim', 'kakaoAlrimCloud'], true);
$kakaoVendorLabel = match ($kakaoCrmConfig['sender'] ?? '') {
'kakaoAlrimCloud' => '클라우드',
'kakaoAlrim' => '비즈엠',
'kakaoAlrimLuna' => '블룸에이아이',
default => '',
};
 
/** @var MyappPushTemplateService $myappPushTemplateService */
$myappPushTemplateService = App::getInstance(MyappPushTemplateService::class);
 
$this->callMenu('crm', 'messageSend', 'autoSendConfig');
$this->setData('mode', 'save');
$this->setData('code', $code);
$this->setData('groupCode', $groupCode);
$this->setData('displayOptions', $autoSendOptionVisibility->bindToDisplay());
$this->setData('options', $options);
$this->setData('defaultRecipient', AutoSendRecipientPolicy::getDefaultRecipient($code));
$this->setData('recipientTitles', $this->buildRecipientTitles($recipients));
$this->setData('forceCheckRecipients', $this->getForceCheckRecipients($code));
$this->setData('recipients', $recipients);
$this->setData('channels', $channels);
$this->setData('useJoinPolicy', AutoSendSupport::useJoinPolicy());
$this->setData('myappSupportRecipients', ['member']);
$this->setData('kakaoCrmUseFlag', ($kakaoCrmConfig['useFlag'] ?? '') === 'y');
$this->setData('myappCrmUseFlag', ($crmMyappConfig['useFlag'] ?? '') === 'y');
$this->setData('isKakaoAlternativeBySms', ($kakaoCrmConfig['autoSmsAlternativeSendFlag'] ?? '') === 'y' && ($kakaoCrmConfig['autoSmsAlternativeSendType'] ?? '') === 'SMS');
$this->setData('isMyappAlternativeBySms', ($crmMyappConfig['autoSmsAlternativeSendFlag'] ?? '') === 'y' && ($crmMyappConfig['autoSmsAlternativeSendType'] ?? '') === 'SMS');
$this->setData('validAutoSendSms', $validAutoSendSms);
$this->setData('checkedRecipients', $checkedRecipients);
$this->setData('autoSendConfigsResponse', $autoSendConfigsResponse);
$this->setData('channelTemplateVariables', [
AutoSendChannel::SMS->name => AutoSendTemplateVariableCatalog::groupCodeVariablesByCategoryAndChannel($code, AutoSendChannel::SMS, true),
AutoSendChannel::KAKAO_ALRIM_TALK->name => AutoSendTemplateVariableCatalog::groupCodeVariablesByCategoryAndChannel($code, AutoSendChannel::KAKAO_ALRIM_TALK, true),
AutoSendChannel::MYAPP_PUSH->name => AutoSendTemplateVariableCatalog::groupCodeVariablesByCategoryAndChannel($code, AutoSendChannel::MYAPP_PUSH, true),
]);
$this->setData('groupedTemplateAllVariables', AutoSendTemplateVariableCatalog::groupVariablesByCategory(true));
$this->setData('shouldChangeKakaoTemplate', $shouldChangeKakaoTemplate);
$this->setData('kakaoTemplates', $channelCodeConfigDto->kakaoTemplates);
$this->setData('kakaoVendorLabel', $kakaoVendorLabel);
$this->setData('myappDomain', $myappPushTemplateService->getMyappDomainUrl());
$this->getView()->setDefine('autoSendConfigOptions', 'crm/auto_send_config/auto_send_config_options.php');
$this->getView()->setDefine('autoSendConfigContents', 'crm/auto_send_config/auto_send_config_contents.php');
$this->getView()->setDefine('autoSendConfigPreview', 'crm/auto_send_config/auto_send_config_preview.php');
}
}
 
private function buildRecipientTitles(array $recipients): array
{
$titles = [];
foreach ($recipients as $recipient) {
$titles[strtolower($recipient->name)] = $recipient->getTitle();
}
return $titles;
}
 
/**
* @throws ConnectException
*/
private function validAutoSendSms(): bool
{
try {
/** @var SmsPasswordService $smsPasswordService */
$smsPasswordService = App::getInstance(SmsPasswordService::class);
$smsPassword = $smsPasswordService->getSmsPassword(true);
if (empty($smsPassword)) {
return false;
}
 
if (empty(gd_policy('sms.config')['smsCallNum'])) {
return false;
}
 
return $smsPasswordService->verifySmsPassword($smsPassword);
} catch (ConnectException $e) {
throw $e;
} catch (\Exception) {
return false;
}
}
 
private function collectGroupsHavingAutoSendEnabled(array $autoSendConfigsResponse): array
{
$result = [];
foreach ($autoSendConfigsResponse as $groupKey => $channels) {
foreach ($channels as $channel) {
if (($channel['isAutoSend'] ?? null) === 'y') {
$result[] = $groupKey;
break;
}
}
}
return $result;
}
 
/**
* 정책상 무조건 수신 대상 설정에 체크 상태로 있어야 하는 수신자
* @param string $code
* @return array
*/
public function getForceCheckRecipients(mixed $code): array
{
$forceCheckedRecipients = [];
if ($code == Code::PRESENT) {
$forceCheckedRecipients[] = AutoSendRecipient::RECIPIENT;
}
 
return $forceCheckedRecipients;
}
 
/**
* @return void
*/
public function applyBlockPage(): void
{
$this->getView()->setPageName('crm/auto_send_config.php');
$this->getView()->setDefine('autoSendConfigBlock', 'crm/auto_send_config/auto_send_config_block.php');
}
}
 
변경 후AutoSendConfigController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/AutoSendConfig/LayerAutoSendModifyKakaoTemplateController.php-28
변경 전LayerAutoSendModifyKakaoTemplateController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\AutoSendConfig;
 
use App;
use Origin\Service\Member\KakaoAlrim\KakaoAlrimCloudMessageTemplateService;
use Request;
 
class LayerAutoSendModifyKakaoTemplateController extends \Controller\Admin\Controller
{
public function index()
{
$request = Request::post()->toArray();
$templateCode = $request['templateCode'] ?? '';
 
// todo test
$templateCode = 'custom_order_0011';
 
$this->setData('template', $template);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerAutoSendModifyKakaoTemplateController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/AutoSendConfig/LayerAutoSendSaveTemplateController.php-49
변경 전LayerAutoSendSaveTemplateController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\AutoSendConfig;
 
use Exception;
use Origin\Enum\Crm\Message\MyappPushTemplateCategory;
use Origin\Enum\Crm\Message\SmsTemplateCategory;
use Request;
 
class LayerAutoSendSaveTemplateController extends \Controller\Admin\Controller
{
public function index()
{
$request = Request::post()->toArray();
$contents = $request['contents'] ?? '';
$title = $request['title'] ?? '';
$channel = $request['channel'];
 
switch ($channel) {
case 'MYAPP_PUSH':
$categories = MyappPushTemplateCategory::getFindOptions();
break;
case 'SMS':
$categories = SmsTemplateCategory::getFindOptions();
break;
default:
throw new Exception("지원하지 않는 채널");
}
 
$url = $request['url'] ?? '';
$image = $request['image'] ?? '';
 
$this->setData('channel', $channel);
$this->setData('categories', $categories);
$this->setData('contents', $contents);
$this->setData('title', $title);
$this->setData('url', $url);
$this->setData('image', $image);
 
$this->getView()->setDefine('layout', 'layout_layer.php');
$this->getView()->setPageName('crm/auto_send_config/layer_auto_send_save_template.php');
}
}
 
변경 후LayerAutoSendSaveTemplateController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/AutoSendConfig/LayerAutoSendTemplateContentController.php-75
변경 전LayerAutoSendTemplateContentController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\AutoSendConfig;
 
use Bundle\Component\Page\Page;
use Exception;
use Origin\Enum\Crm\Message\MyappPushTemplateCategory;
use Origin\Enum\Crm\Message\SmsTemplateCategory;
use Origin\Repository\Member\Myapp\MyappPushRepository;
use Origin\Repository\Member\Sms\SmsContentsRepository;
use Request;
 
class LayerAutoSendTemplateContentController extends \Controller\Admin\Controller
{
private const PER_PAGE = 10;
 
public function index()
{
$request = Request::post()->toArray();
$channel = $request['channel'];
$categoryValue = $request['category'] ?? '';
$keyword = $request['keyword'] ?? '';
$page = max(1, (int)($request['page'] ?? 1));
$offset = ($page - 1) * self::PER_PAGE;
 
switch ($channel) {
case 'MYAPP_PUSH':
$category = MyappPushTemplateCategory::tryFrom($categoryValue);
$repository = new MyappPushRepository();
$totalCount = $repository->searchCount($category, $keyword);
$templates = array_map(function ($template) {
return [
'sno' => $template['pushSno'],
'subject' => $template['templateName'] ?? '',
'contents' => $template['pushContent'],
'title' => $template['pushSubject'] ?? '',
'myappImage' => $template['pushImage'],
'url' => $template['pushUrl'] ?? '',
];
}, $repository->searchPaginated($category, $keyword, self::PER_PAGE, $offset));
$categories = MyappPushTemplateCategory::getFindOptions();
break;
case 'SMS':
$category = SmsTemplateCategory::tryFrom($categoryValue);
$repository = new SmsContentsRepository();
$totalCount = $repository->searchCount($category, $keyword);
$templates = $repository->searchPaginated($category, $keyword, self::PER_PAGE, $offset);
$categories = SmsTemplateCategory::getFindOptions();
break;
default:
throw new Exception();
}
 
$page = new Page($page, $totalCount, $totalCount, self::PER_PAGE);
 
$templates = array_map(function ($template) {
$template['subject'] = nl2br(gd_htmlspecialchars_stripslashes($template['subject']));
$template['contents'] = gd_htmlspecialchars_stripslashes($template['contents']);
return $template;
}, $templates);
 
$this->setData('channel', $channel);
$this->setData('templates', $templates);
$this->setData('categories', $categories);
$this->setData('page', $page);
 
$this->getView()->setDefine('layout', 'layout_layer.php');
$this->getView()->setPageName('crm/auto_send_config/layer_auto_send_template_content.php');
}
}
 
변경 후LayerAutoSendTemplateContentController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/AutoSendConfig/LayerAutoSendTemplateController.php-24
변경 전LayerAutoSendTemplateController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\AutoSendConfig;
 
use Request;
 
class LayerAutoSendTemplateController extends \Controller\Admin\Controller
{
 
public function index()
{
$request = Request::post()->toArray();
$channel = $request['channel'] ?? 'SMS';
 
$this->setData('channel', $channel);
$this->getView()->setDefine('layout', 'layout_layer.php');
$this->getView()->setPageName('crm/auto_send_config/layer_auto_send_template.php');
}
}
 
변경 후LayerAutoSendTemplateController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/AutoSendConfigPsController.php-125
변경 전AutoSendConfigPsController.php
<?php
 
namespace Bundle\Controller\Admin\Crm;
 
use App;
use Bundle\Component\Sms\SmsAutoCode;
use Exception;
use Origin\DTO\AutoSend\AutoSendOptionCodeConfigDto;
use Origin\Enum\AutoSend\AutoSendChannel;
use Origin\Enum\AutoSend\AutoSendChannelPolicy;
use Origin\Enum\AutoSend\AutoSendRecipient;
use Origin\Enum\AutoSend\AutoSendRecipientPolicy;
use Origin\Enum\AutoSend\AutoSendSupport;
use Origin\Service\AutoSend\FindAutoSendConfigService;
use Origin\Service\AutoSend\Request\UpdateAutoSendConfigRequest;
use Origin\Service\AutoSend\SaveAutoSendConfigService;
use Origin\Service\AutoSend\SaveAutoSendTemplateService;
use Origin\Service\Member\KakaoAlrim\KakaoAlrimCloudSyncService;
use Request;
 
class AutoSendConfigPsController extends \Controller\Admin\Controller
{
public function index()
{
$request = Request::post()->toArray();
$files = Request::files()->toArray();
$mode = $request['mode'] ?? ''; // todo throw
$code = $request['code'];
$options = $request['options'] ?? [];
 
try {
switch ($mode) {
case 'save':
$configOptions = AutoSendOptionCodeConfigDto::createCodeConfigByOption($code, $options);
/** @var SmsAutoCode $smsAutoCode */
$smsAutoCode = App::load('Component\\Sms\\SmsAutoCode');
$smsAutoCode->getCodes();
/** @var SaveAutoSendConfigService $saveAutoSendConfigService */
$saveAutoSendConfigService = App::getInstance(SaveAutoSendConfigService::class);
$channels = AutoSendChannelPolicy::getCodesWithChannelsWithBoard()[$code] ?? [];
/** @var SaveAutoSendTemplateService $saveAutoSendTemplateService */
$saveAutoSendTemplateService = App::getInstance(SaveAutoSendTemplateService::class);
$recipientFiles = $files['recipients'] ?? [];
$flagMap = [];
$templateItems = [];
 
foreach ($request['recipients'] as $requestRecipientName => $requestRecipientConfig) {
$autoSendRecipient = AutoSendRecipient::fromName(strtoupper($requestRecipientName));
foreach ($channels as $channel) {
$channelConfig = $requestRecipientConfig[$channel->name] ?? [];
if (!empty($channelConfig)) {
$imageMode = $channelConfig['imageMode'] ?? null;
if ($imageMode === 'FILE') {
$uploadedFile = $this->extractUploadedFile($recipientFiles, $requestRecipientName, $channel->name);
if ($uploadedFile !== null) {
$channelConfig['uploadedImage'] = $uploadedFile;
}
}
 
$flagMap[] = [$channel, $requestRecipientName, $channelConfig['isEnabled'] ?? 'n'];
$templateItems[] = [
'channel' => $channel,
'recipient' => $autoSendRecipient,
'config' => $channelConfig,
];
}
}
}
 
 
$insertedSnoMap = \DB::transaction(function () use ($saveAutoSendConfigService, $code, $flagMap, $configOptions, $saveAutoSendTemplateService, $templateItems) {
$saveAutoSendConfigService->updateAutoSendConfigBatch($code, $flagMap, $configOptions);
$insertedSnoMap = $saveAutoSendTemplateService->updateTemplatesBatch($code, $templateItems);
 
/** @var FindAutoSendConfigService $findAutoSendConfigService */
$findAutoSendConfigService = App::getInstance(FindAutoSendConfigService::class);
 
if ($findAutoSendConfigService->getActiveCrmKakaoAlrimSender() == 'kakaoAlrimCloud') {
/** @var KakaoAlrimCloudSyncService $kakaoAlrimCloudSyncService */
$kakaoAlrimCloudSyncService = App::getInstance(KakaoAlrimCloudSyncService::class);
$kakaoAlrimCloudSyncService->syncMessageTemplate();
}
 
return $insertedSnoMap;
});
 
$this->json(['result'=> true, 'message' => '', 'insertedSnoMap' => $insertedSnoMap]);
 
break;
 
default:
break;
}
} catch (Exception $exception) {
$this->json(['result'=> false, 'message' => $exception->getMessage()]);
}
}
 
/**
* $_FILES 중첩 배열에서 수신자/채널별 업로드 파일 정보 추출
*
* @param array $recipientFiles $_FILES['recipients']
* @param string $recipientName 수신자 (member, admin, provider)
* @param string $channelName 채널 (SMS, KAKAO_ALRIM_TALK, MYAPP_PUSH)
* @return array|null ['name', 'type', 'tmp_name', 'error', 'size'] 또는 null
*/
private function extractUploadedFile(array $recipientFiles, string $recipientName, string $channelName): ?array
{
$tmpName = $recipientFiles['tmp_name'][$recipientName][$channelName]['image'] ?? null;
$error = $recipientFiles['error'][$recipientName][$channelName]['image'] ?? UPLOAD_ERR_NO_FILE;
$size = $recipientFiles['size'][$recipientName][$channelName]['image'] ?? 0;
 
if (empty($tmpName) || (int)$error === UPLOAD_ERR_NO_FILE || (int)$size === 0) {
return null;
}
 
return [
'name' => $recipientFiles['name'][$recipientName][$channelName]['image'] ?? '',
'type' => $recipientFiles['type'][$recipientName][$channelName]['image'] ?? '',
'tmp_name' => $tmpName,
'error' => $error,
'size' => $recipientFiles['size'][$recipientName][$channelName]['image'] ?? 0,
];
}
}
 
변경 후AutoSendConfigPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/AutoSendController.php-59
변경 전AutoSendController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Bundle\Component\Sms\Code;
use Bundle\Component\Sms\Sms;
use Bundle\Component\Sms\SmsUtil;
use Component\Admin\AdminMenu;
use Origin\Enum\AutoSend\AutoSendRecipient;
use Origin\Enum\AutoSend\AutoSendSupport;
use Origin\Service\AutoSend\SearchAutoSendListService;
use Origin\Service\AutoSend\SearchAutoSendsRequest;
use Request;
 
class AutoSendController extends \Controller\Admin\Controller
{
public function index()
{
$this->setNcdsSubBodyClass();
$request = Request::get()->toArray();
 
$this->callMenu('crm', 'messageSend', 'autoSend');
 
$smsPoint = (float) Sms::getPoint();
$sendableMethodCountByPoint = [
'sms' => SmsUtil::getAvailableCount('sms', $smsPoint),
'lms' => SmsUtil::getAvailableCount('lms', $smsPoint),
'kakaoAlrimTalk' => SmsUtil::getAvailableCount('kakaoAlrimTalk', $smsPoint),
'kakaoFriendTalk' => SmsUtil::getAvailableCount('kakaoFriendTalk', $smsPoint),
];
Sms::saveSmsPoint();
 
/** @var SearchAutoSendListService $findAutoSendService */
$findAutoSendService = \App::getInstance(SearchAutoSendListService::class);
$response = $findAutoSendService->search(SearchAutoSendsRequest::fromArray($request));
 
$this->setData('shouldExpandRecommend', AutoSendSupport::shouldExpandRecommend());
$this->setData('newIconCodes', AutoSendSupport::getNewIconCodes());
$this->setData('useJoinPolicy', AutoSendSupport::useJoinPolicy());
$this->setData('autoSends', $response['autoSends']);
$this->setData('recommendAutoSends', $response['recommendAutoSends']);
$this->setData('pageCounts', $response['pageCounts']);
$this->setData('smsPoint', $smsPoint);
$this->setData('sendableMethodCountByPoint', $sendableMethodCountByPoint);
$this->setData('recipients', AutoSendRecipient::cases());
$this->setData('request', $request);
$this->getView()->setPageName('crm/auto_send.php');
$this->getView()->setDefine('messagePoint', 'crm/auto_send/layer_list_message_point.php');
$this->getView()->setDefine('autoRecommend', 'crm/auto_send/layer_list_recommend.php');
$this->getView()->setDefine('autoListSearch', 'crm/auto_send/layer_list_search.php');
$this->getView()->setDefine('autoList', 'crm/auto_send/layer_list.php');
}
}
 
변경 후AutoSendController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/AutoSend/LayerListController.php-30
변경 전LayerListController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\AutoSend;
 
use Origin\Enum\AutoSend\AutoSendSupport;
use Origin\Service\AutoSend\SearchAutoSendListService;
use Origin\Service\AutoSend\SearchAutoSendsRequest;
use Request;
 
class LayerListController extends \Controller\Admin\Controller
{
public function index()
{
$request = SearchAutoSendsRequest::fromArray(Request::post()->toArray());
 
/** @var SearchAutoSendListService $findAutoSendService */
$findAutoSendService = \App::getInstance(SearchAutoSendListService::class);
$response = $findAutoSendService->search($request);
$this->setData('newIconCodes', AutoSendSupport::getNewIconCodes());
$this->setData('autoSends', $response['autoSends']);
$this->setData('pageCounts', $response['pageCounts']);
$this->getView()->setPageName('crm/auto_send/layer_list.php');
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerListController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/AutoSend/LayerListRecommendController.php-20
변경 전LayerListRecommendController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\AutoSend;
 
use Origin\Service\AutoSend\SearchAutoSendListService;
 
class LayerListRecommendController extends \Controller\Admin\Controller
{
public function index()
{
$this->getView()->setPageName('crm/auto_send/layer_list_recommend.php');
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerListRecommendController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/AutoSend/LayerListSearchController.php-19
변경 전LayerListSearchController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\AutoSend;
 
 
class LayerListSearchController extends \Controller\Admin\Controller
{
public function index()
{
$this->getView()->setPageName('crm/auto_send/layer_list_search.php');
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerListSearchController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/AutoSendPsController.php-51
변경 전AutoSendPsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Exception;
use Framework\Http\Response;
use Origin\Enum\AutoSend\AutoSendSupport;
use Origin\Service\AutoSend\Request\UpdateAutoSendConfigRequest;
use Origin\Service\AutoSend\SaveAutoSendConfigService;
use Request;
 
class AutoSendPsController extends \Controller\Admin\Controller
{
public function index() {
$request = Request::post()->toArray();
try {
switch ($request['mode']) {
case 'updateEnabledFlags':
if (!empty($request['codes'])) {
/** @var SaveAutoSendConfigService $saveAutoSendConfigService */
$saveAutoSendConfigService = \App::getInstance(SaveAutoSendConfigService::class);
$saveAutoSendConfigService->updateAutoSendConfig(
new UpdateAutoSendConfigRequest(
codesWithFlag: $request['codes'],
)
);
}
$this->json(['result'=> true, 'message' => '']);
break;
case 'saveRecommendCollapseState':
$collapsed = ($request['collapsed'] ?? '') === 'true';
AutoSendSupport::saveRecommendCollapseState($collapsed);
$this->json(['result' => true, 'message' => '']);
break;
default:
break;
}
} catch (Exception $exception) {
// todo logging
$this->json(['result'=> false, 'message' => $exception->getMessage()]);
}
}
 
 
}
 
변경 후AutoSendPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/CrmRecipeClickLogPsController.php-46
변경 전CrmRecipeClickLogPsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Framework\Http\Request;
use Origin\Service\Crm\CrmRecipeClickLogCollectorService;
 
class CrmRecipeClickLogPsController extends \Controller\Admin\Controller
{
private const ALLOWED_ACTIONS = [
CrmRecipeClickLogCollectorService::ACTION_SEND,
CrmRecipeClickLogCollectorService::ACTION_CLOSE,
];
 
public function index()
{
$this->callMenu('crm', 'crm', 'crmRecipe');
$this->setMenuCode('crm', 'crm', 'crmRecipe');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
$action = $postData['action'] ?? '';
if (!in_array($action, self::ALLOWED_ACTIONS, true)) {
$this->json(['success' => false]);
return;
}
 
/** @var CrmRecipeClickLogCollectorService $clickLogCollector */
$clickLogCollector = \App::getInstance(CrmRecipeClickLogCollectorService::class);
$clickLogCollector->collect([
'action' => $action,
'recipeType' => $postData['recipeType'] ?? '',
'currentPage' => $postData['currentPage'] ?? '',
]);
 
$this->json(['success' => true]);
}
}
 
변경 후CrmRecipeClickLogPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/CrmRecipeSendController.php-148
변경 전CrmRecipeSendController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Component\Sms\Sms;
use Component\Validator\Validator;
use Framework\Debug\Exception\AlertBackException;
use Origin\DTO\ApiClient\Commerce\Crm\TargetsRepeatRuleStatusRequestDTO;
use Origin\Enum\ApiClient\Crm\RecipeType;
use Origin\Service\ApiClient\Commerce\Crm\CrmAdminApiClientService;
use Origin\Service\Crm\Message\MessageConfigService;
use Origin\Service\Crm\Message\SmsManageCallNumberService;
 
class CrmRecipeSendController extends \Controller\Admin\Controller
{
protected $messageLogger;
 
public function index()
{
$this->setNcdsSubBodyClass();
$this->callMenu('crm', 'crm', 'crmRecipe');
$this->setMenuCode('crm', 'crm', 'crmRecipe');
 
$this->messageLogger = \Logger::channel('mobileMessage');
 
$recipeType = (string) \Request::get()->get('recipeType', '');
 
if (!empty($recipeType) && RecipeType::tryFromSlug($recipeType) === null) {
throw new AlertBackException(__('잘못된 접근입니다.'));
}
 
$crmGroupNoRaw = \Request::get()->get('no', '');
 
if (Validator::number($crmGroupNoRaw, 1) === false) {
throw new AlertBackException(__('잘못된 접근입니다.'));
}
 
$crmGroupNo = (int) $crmGroupNoRaw;
 
/** @var CrmAdminApiClientService $crmAdminApiClientService */
$crmAdminApiClientService = \App::getInstance(CrmAdminApiClientService::class);
 
$crmGroup = $crmGroupNo > 0
? $this->fetchSelectedCrmGroup($crmAdminApiClientService, $crmGroupNo)
: [];
 
$recipeDefault = [];
if (!empty($recipeType)) {
try {
$recipeDefault = $crmAdminApiClientService->getRecipeDefault($recipeType)->toRecursiveArray();
} catch (\Throwable $e) {
$this->messageLogger->error('CRM 레시피 기본값 처리 실패', [$e->getMessage(), $recipeType, __METHOD__]);
$recipeDefault = [];
}
}
 
$recipeName = $recipeDefault['recipeTitle'] ?? '레시피';
$conditionGuideView = $this->buildConditionGuideView($recipeDefault['conditionGuide'] ?? []);
 
Sms::saveSmsPoint();
 
/** @var SmsManageCallNumberService $smsManageCallNumberService */
$smsManageCallNumberService = \App::getInstance(SmsManageCallNumberService::class);
$hasCallNumber = $smsManageCallNumberService->hasUsableCallNumber();
 
/** @var MessageConfigService $messageConfigService */
$messageConfigService = \App::getInstance(MessageConfigService::class);
$messageConfig = $messageConfigService->buildSendableMessageConfig($hasCallNumber);
 
$this->setData('messageConfig', $messageConfig);
$this->setData('smsPointEach', Sms::SMS_POINT);
$this->setData('lmsPointEach', Sms::LMS_POINT);
$this->setData('kakaoAlrimTalkPointEach', Sms::KAKAO_POINT);
$this->setData('kakaoFriendTalkPointEach', Sms::KAKAO_FRIEND_TALK_POINT);
$this->setData('recipeType', $recipeType);
$this->setData('crmGroup', $crmGroup);
$this->setData('recipeName', $recipeName);
$this->setData('recipeDefault', $recipeDefault);
$this->setData('conditionGuideView', $conditionGuideView);
 
// 어드민 헤더·네비 없는 blank 레이아웃 (드로어용)
$this->getView()->setDefine('layout', 'layout_blank_noiframe.php');
$this->getView()->setDefine('layoutHeader', 'header_no_breadcrumb.php');
$this->getView()->setPageName('crm/crm_recipe_send.php');
 
$this->setData('adminBodyClass', trim($this->getData('adminBodyClass') . ' ncds'));
}
 
protected function fetchSelectedCrmGroup(CrmAdminApiClientService $crmAdminApiClientService, int $crmGroupNo): array
{
try {
$crmGroup = $crmAdminApiClientService
->getNotificationTargetsRepeatRuleStatus(new TargetsRepeatRuleStatusRequestDTO(), $crmGroupNo)
->toRecursiveArray();
} catch (\Throwable $e) {
$this->messageLogger->error('CRM 그룹 상태 조회 실패', [$e->getMessage(), $crmGroupNo, __METHOD__]);
return [];
}
 
if (empty($crmGroup)) {
return [];
}
 
// status API 응답의 no 는 내부 레코드 PK라 crmGroupNo 로 덮어씀
$crmGroup['no'] = $crmGroupNo;
 
// status 응답엔 title 이 없어 규칙 단건 조회로 보강
try {
$crmGroup['title'] = $crmAdminApiClientService
->getNotificationTargetsRepeatRule($crmGroupNo)
->getTitle();
} catch (\Throwable $e) {
$this->messageLogger->error('CRM 그룹명 조회 실패', [$e->getMessage(), $crmGroupNo, __METHOD__]);
}
 
return $crmGroup;
}
 
protected function buildConditionGuideView(array $conditionGuide): array
{
$hasConditionGuide = !empty($conditionGuide['selectionGuide'])
&& !empty($conditionGuide['selectableValues'])
&& is_array($conditionGuide['selectableValues']);
 
if (!$hasConditionGuide) {
return ['hasConditionGuide' => false];
}
 
$selectableValues = $conditionGuide['selectableValues'];
$currentValue = $conditionGuide['value'] ?? $selectableValues[0];
$guideParts = explode('{no}', $conditionGuide['selectionGuide'], 2);
 
// 미리보기 문구는 JS(updateConditionPreview)가 단독 계산·렌더 — 날짜 규칙 단일화
return [
'hasConditionGuide' => true,
'selectableValues' => $selectableValues,
'currentValue' => $currentValue,
'guideParts' => $guideParts,
'productSelectableCount' => (int) ($conditionGuide['productSelectableCount'] ?? 0),
];
}
}
 
변경 후CrmRecipeSendController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerKakaoTemplateCommentController.php-40
변경 전LayerKakaoTemplateCommentController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Framework\StaticProxy\Proxy\Logger;
use Origin\Service\Member\KakaoAlrim\KakaoAlrimCloudMessageTemplateService;
use Bundle\Component\Member\KakaoAlrim;
 
class LayerKakaoTemplateCommentController extends \Controller\Admin\Controller
{
public function index()
{
$this->getView()->setDefine('layout', 'layout_layer.php');
 
$request = \App::getInstance('request');
$requestData = $request->request()->toArray();
$provider = $requestData['provider'] ?? "";
$templateCode = $requestData['templateCode'] ?? "";
 
if ($provider === 'bizm'){
$kakao = new KakaoAlrim;
$messageTemplateComments = $kakao->getTemplateComment($templateCode);
$comments = $messageTemplateComments['comments'] ?? [];
$comment = !empty($comments) ? end($comments)->content ?? '' : '';
$this->setData('comment', $comment);
} else{
/** @var KakaoAlrimCloudMessageTemplateService $service */
$service = \App::getInstance(KakaoAlrimCloudMessageTemplateService::class);
$messageTemplateComments = $service->getMessageTemplateComments($templateCode);
$this->setData('comment', $messageTemplateComments);
}
 
}
}
 
변경 후LayerKakaoTemplateCommentController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerMobileMessagePerformanceDetailController.php-17
변경 전LayerMobileMessagePerformanceDetailController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
class LayerMobileMessagePerformanceDetailController extends \Controller\Admin\Controller
{
public function index()
{
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMobileMessagePerformanceDetailController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerMobileMessagePerformanceSummaryController.php-17
변경 전LayerMobileMessagePerformanceSummaryController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
class LayerMobileMessagePerformanceSummaryController extends \Controller\Admin\Controller
{
public function index()
{
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMobileMessagePerformanceSummaryController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerMyAppSendGuideController.php-17
변경 전LayerMyAppSendGuideController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
class LayerMyAppSendGuideController extends \Controller\Admin\Controller
{
public function index()
{
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMyAppSendGuideController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerPreviewAutoSendController.php-98
변경 전LayerPreviewAutoSendController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use App;
use Bundle\Component\Sms\SmsAutoCode;
use Origin\Enum\AutoSend\AutoSendChannel;
use Origin\Enum\AutoSend\AutoSendChannelPolicy;
use Origin\Enum\AutoSend\AutoSendRecipient;
use Origin\Enum\AutoSend\AutoSendRecipientPolicy;
use Origin\Enum\AutoSend\AutoSendSupport;
use Origin\Enum\Crm\Message\MyappPushTemplateCategory;
use Origin\Enum\Crm\Message\SmsTemplateCategory;
use Origin\Repository\Member\Myapp\MyappPushRepository;
use Origin\Repository\Member\Sms\SmsContentsRepository;
use Origin\Service\AutoSend\FindAutoSendCodeConfigService;
use Origin\Service\AutoSend\FindAutoSendConfigService;
use Origin\Service\AutoSend\FindAutoSendTemplateService;
use Request;
 
class LayerPreviewAutoSendController extends \Controller\Admin\Controller
{
public function index()
{
$request = Request::get()->toArray();
$code = $request['code'];
$supportChannels = AutoSendChannelPolicy::getCodesWithChannelsWithBoard()[$code] ?? [];
 
// 수신대상 라벨 (key => label)
$recipientLabels = [];
foreach (AutoSendRecipient::cases() as $recipient) {
$recipientLabels[strtolower($recipient->name)] = $recipient->getTitle();
}
 
// 발송 수단 라벨 (key => label)
$channelLabels = [];
foreach ($supportChannels as $channel) {
$channelLabels[$channel->name] = $channel->getShortTitle();
}
 
/** @var FindAutoSendCodeConfigService $codeConfigService */
$codeConfigService = App::getInstance(FindAutoSendCodeConfigService::class);
[$_, $autoSendConfigsResponse, $channelCodeConfigDto] = $codeConfigService->createRecipientConfigByCode($code, false);
 
// kakaoTemplates를 templateCode 기준 flat map으로 변환
$kakaoTemplateMap = [];
foreach ($channelCodeConfigDto->kakaoTemplates as $categoryTemplates) {
foreach ($categoryTemplates as $template) {
if (!empty($template['templateCode'])) {
$kakaoTemplateMap[$template['templateCode']] = $template;
}
}
}
 
 
$this->setData('allRecipients', $this->getSortedAllRecipients());
$this->setData('recipientLabels', $recipientLabels);
$this->setData('channelLabels', $channelLabels);
$this->setData('templates', $autoSendConfigsResponse);
$this->setData('kakaoTemplateMap', $kakaoTemplateMap);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
 
private function getAutoSendMeta($code)
{
/** @var SmsAutoCode $smsAutoCode */
$smsAutoCode = App::load('Component\\Sms\\SmsAutoCode');
$groupKey = $smsAutoCode->getGroupByCode($code);
$recipients = AutoSendRecipientPolicy::getCodesWithRecipientsWithBoard()[$code] ?? [];
$channels = AutoSendChannelPolicy::getCodesWithChannelsWithBoard()[$code] ?? [];
return [$groupKey, $recipients, $channels];
}
 
/**
* 전체 수신 대상들 반환 (탭 순서용)
* 순서 정책: 회원 - 수령자 - 본사 운영자 - 공급사
*
* @return array
*/
public function getSortedAllRecipients(): array
{
return array_map(
fn(AutoSendRecipient $r) => strtolower($r->name),
[
AutoSendRecipient::MEMBER,
AutoSendRecipient::RECIPIENT,
AutoSendRecipient::ADMIN,
AutoSendRecipient::PROVIDER,
]
);
}
}
 
변경 후LayerPreviewAutoSendController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerPreviewMobileSendController.php-144
변경 전LayerPreviewMobileSendController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Framework\StaticProxy\Proxy\UserFilePath;
use Origin\Service\Crm\Message\KakaoFriendTalkSettingService;
use Origin\Service\Crm\Message\MyappPushSettingService;
use Request;
use Logger;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Validator;
 
class LayerPreviewMobileSendController extends \Controller\Admin\Controller
{
public function index()
{
try {
$postValue = Request::post()->toArray();
 
// 요청 검증
Validator::key('sendMethod', Validator::in(['SMS', 'FRIENDTALK', 'ALIMTALK', 'MYAPP']))
->setTemplate('유효하지 않은 요청입니다.')
->assert($postValue);
Validator::key('viewType', Validator::in(['layer', 'side']))
->setTemplate('유효하지 않은 요청입니다.')
->assert($postValue);
Validator::key('payload', Validator::stringType()->notEmpty())
->setTemplate('유효하지 않은 요청입니다.')
->assert($postValue);
 
$payload = json_decode($postValue['payload'], true);
$sendMethod = $postValue['sendMethod'];
 
// 공통 데이터
$this->setData('sendMethod', $sendMethod);
$this->setData('payloadJson', $postValue['payload']);
$useAlternative = !empty($payload['enableAlternative']) ? 'y' : 'n';
$this->setData('useAlternative', $useAlternative);
 
$previewContents = "";
$alternativePreviewContents = 'crm/preview_mobile_send/layer_preview_mobile_send_alternative.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';
break;
case 'FRIENDTALK':
$friendTalkData = $payload['kakaoFriendtalkRequest'] ?? [];
 
/** @var KakaoFriendTalkSettingService $kakaoFriendTalkSettingService */
$kakaoFriendTalkSettingService = \App::getInstance(KakaoFriendTalkSettingService::class);
$kakaoFriendTalkConfig = $kakaoFriendTalkSettingService->getKakaoFriendTalkConfig();
 
$showAdWarningText = 'n';
if ($kakaoFriendTalkConfig->getSmsAlternativeSendFlag() === 'y' && !$friendTalkData['alternativeInfo']['isAdText'] && $friendTalkData['type'] !== 'WIDE_ITEM_LIST' && $friendTalkData['type'] !== 'CAROUSEL_FEED') {
$showAdWarningText = 'y';
}
 
$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;
case 'ALIMTALK':
$alimTalkData = $payload['kakaoAlimtalkRequest'] ?? [];
$previewMeta = json_decode($postValue['previewMeta'] ?? '{}', true);
$hasAddChannelButton = !empty(array_filter(
$previewMeta['templateButtons'] ?? [],
fn($button) => ($button['type'] ?? '') === 'ADD_CHANNEL'
));
$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;
case 'MYAPP':
$myappPushData = $payload['myappRequest'] ?? [];
 
/** @var MyappPushSettingService $myappPushSettingService */
$myappPushSettingService = \App::getInstance(MyappPushSettingService::class);
$myappPushConfig = $myappPushSettingService->getMyappPushConfig();
$showAdWarningText = 'n';
if ($myappPushData['notificationType'] === 'AD' && $myappPushConfig->getManualSmsAlternativeSendFlag() === 'y' && !$myappPushData['alternativeInfo']['isAdText']) {
$showAdWarningText = 'y';
}
$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;
}
 
if($postValue['viewType'] === 'side') {
ob_start();
$paths = explode('/', $previewContents);
include UserFilePath::adminSkin(...$paths);
if($out = ob_get_clean()) {
echo $out;
}
exit;
} else {
$this->getView()->setDefine('layout', 'layout_layer.php');
$this->getView()->setDefine('previewContents', $previewContents);
$this->getView()->setDefine('alternativePreviewContents', $alternativePreviewContents);
}
} catch (ValidationException $e) {
Logger::channel('mobileMessage')->warning(__CLASS__ . ' 모바일 메시지 전송 처리 검증 실패 : ', [
'error' => $e->getMainMessage(),
'postValue' => $postValue ?? []
]);
 
$this->alert(__('잘못된 요청입니다 발송전 미리보기 로드에 실패하였습니다. 잠시후 다시 시도해주세요.'));
} catch (\Throwable $e) {
Logger::channel('mobileMessage')->warning(__CLASS__ . ' 모바일 메시지 전송 처리 중 에러 발생 : ', [
'error' => $e->getMessage(),
'postValue' => $postValue ?? []
]);
$this->alert(__('발송전 미리보기 로드에 실패하였습니다. 잠시후 다시 시도해주세요.'));
}
}
}
 
변경 후LayerPreviewMobileSendController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerRejectRecipientMemberInfoController.php-22
변경 전LayerRejectRecipientMemberInfoController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Request;
 
class LayerRejectRecipientMemberInfoController extends \Controller\Admin\Controller
{
public function index()
{
$postValue = Request::post()->toArray();
$this->setData('totalCount', $postValue['totalCount']);
$this->setData('rejectedCount', $postValue['rejectedCount']);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerRejectRecipientMemberInfoController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerSaveAsTemplateController.php-17
변경 전LayerSaveAsTemplateController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
class LayerSaveAsTemplateController extends \Controller\Admin\Controller
{
public function index()
{
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerSaveAsTemplateController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerSelectCouponController.php-17
변경 전LayerSelectCouponController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
class LayerSelectCouponController extends \Controller\Admin\Controller
{
public function index()
{
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerSelectCouponController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerSelectCouponResultController.php-49
변경 전LayerSelectCouponResultController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Component\Coupon\CouponAdmin;
 
class LayerSelectCouponResultController extends \Controller\Admin\Controller
{
public function index()
{
$request = \App::getInstance('request');
$getParams = $request->get()->all();
 
/** @var \Bundle\Component\Coupon\CouponAdmin $couponAdmin */
$couponAdmin = \App::load('\\Component\\Coupon\\CouponAdmin');
$useTypeArray = [];
if (!empty($getParams['couponUseTypeList'])) {
$useTypeArray = explode(',', $getParams['couponUseTypeList']);
$useTypeArray = array_filter($useTypeArray); // 빈 문자열 제거
}
$deviceTypeArray = [];
if (!empty($getParams['couponDeviceTypeList'])) {
$deviceTypeArray = explode(',', $getParams['couponDeviceTypeList']);
$deviceTypeArray = array_filter($deviceTypeArray); // 빈 문자열 제거
$deviceTypeArray = array_map('strtolower', $deviceTypeArray); // 소문자로 변환
}
$couponAdminList = $couponAdmin->getCouponAdminList(addUseTypeWhereIn: $useTypeArray, addDeviceTypeWhereIn: $deviceTypeArray);
 
$couponData = [];
foreach ($couponAdminList['data'] as $index => $item) {
$couponData[$item['couponNo']] = $item;
}
 
$page = \App::load('\\Component\\Page\\Page');
$searchCount = $page->recode['total'];
 
$this->setData('page', $page);
$this->setData('couponData', $couponData);
$this->setData('couponSearchCount', $searchCount);
$this->setData('requestData', $getParams);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerSelectCouponResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerSelectMemberGroupsController.php-22
변경 전LayerSelectMemberGroupsController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Component\Page\Page;
use Component\Member\MemberGroup;
use Framework\StaticProxy\Proxy\UserFilePath;
use Framework\Utility\StringUtils;
 
class LayerSelectMemberGroupsController extends \Controller\Admin\Controller
{
public function index()
{
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerSelectMemberGroupsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerSelectMemberGroupsResultController.php-42
변경 전LayerSelectMemberGroupsResultController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Component\Page\Page;
use Component\Member\MemberGroup;
use Framework\StaticProxy\Proxy\UserFilePath;
use Framework\Utility\StringUtils;
 
class LayerSelectMemberGroupsResultController extends \Controller\Admin\Controller
{
public function index()
{
$request = \App::getInstance('request');
$getParams = $request->get()->all();
 
/* @var \Bundle\Component\Member\MemberGroup $memberGroupComponent */
$memberGroupComponent = \App::load(MemberGroup::class);
$groupData = $memberGroupComponent->getGroupListSearch();
$groups = $groupData['data'] ?? [];
 
// getGroupListSearch 내부 페이징 처리 포함
$page = \App::load('\\Component\\Page\\Page');
$searchCount = $page->recode['total'];
$totalCount = $memberGroupComponent->getAllGroupCount()[0]['cnt'];
 
$this->setData('search', StringUtils::strIsSet($groupData['search']));
$this->setData('groups', $groups);
$this->setData('groupSearchCount', $searchCount);
$this->setData('groupTotalCount', $totalCount);
$this->setData('page', $page);
$this->setData('requestData', $getParams);
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerSelectMemberGroupsResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerTemplateContentsController.php-33
변경 전LayerTemplateContentsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateResultDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateSearchDTO;
use Origin\Service\Crm\Message\MessageTemplateFindService;
 
class LayerTemplateContentsController extends \Controller\Admin\Controller
{
public function index()
{
$this->getView()->setDefine('layout', 'layout_layer.php');
 
$request = \App::getInstance('request');
$searchDto = new MessageTemplateSearchDTO($request->request()->toArray());
 
/** @var MessageTemplateFindService $service */
$service = \App::getInstance(MessageTemplateFindService::class);
$responseDto = $service->getMessageTemplate($searchDto);
 
$this->getView()->setDefine('layerTemplateContentsSms', 'crm/layer_template_contents_sms.php');
$this->getView()->setDefine('layerTemplateContentsKakao', 'crm/layer_template_contents_kakao.php');
$this->getView()->setDefine('layerTemplateContentsMyapp', 'crm/layer_template_contents_myapp.php');
$this->setData('request', $searchDto);
$this->setData('response', $responseDto);
}
}
 
변경 후LayerTemplateContentsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerUpdateKaKaoTemplateController.php-17
변경 전LayerUpdateKaKaoTemplateController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
class LayerUpdateKaKaoTemplateController extends \Controller\Admin\Controller
{
public function index()
{
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerUpdateKaKaoTemplateController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/LayerViewMessageLinkTypeController.php-18
변경 전LayerViewMessageLinkTypeController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
class LayerViewMessageLinkTypeController extends \Controller\Admin\Controller
{
public function index()
{
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
 
변경 후LayerViewMessageLinkTypeController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfigController.php-95
변경 전MessageConfigController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Framework\Utility\GodoUtils;
use Origin\DTO\ApiClient\Commerce\Apps\MyappStatusResponseDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessagePointConfigResponseDTO;
use Origin\Enum\Crm\Message\AutoSmsAlternativeSendType;
use Origin\Enum\Crm\Message\FailedMessageSendType;
use Origin\Enum\Crm\Message\KakaoAlimTalkSender;
use Origin\Enum\Crm\Message\LinkPlatformType;
use Origin\Enum\Crm\Message\SmsAutoSendOverType;
use Origin\Service\Crm\Message\KakaoAlimTalkSettingService;
use Origin\Service\Crm\Message\KakaoFriendTalkSettingService;
use Origin\Service\Crm\Message\MessagePointConfigService;
use Origin\Service\Crm\Message\MyappPushSettingService;
use Origin\Service\Crm\Message\SmsSettingService;
use Origin\Service\MyApp\MyappSettingService;
use Origin\Traits\Member\KakaoAlrim\KakaoAlrimCloudAvailable;
 
class MessageConfigController extends \Controller\Admin\Controller
{
use KakaoAlrimCloudAvailable;
 
public function index()
{
$this->setNcdsSubBodyClass();
// 메뉴 설정
$this->callMenu('crm', 'messageConfig', 'messageConfig');
$this->setMenuCode('crm', 'messageConfig', 'messageConfig');
 
try {
/** @var MessagePointConfigService $messagePointConfigService */
$messagePointConfigService = \App::getInstance(MessagePointConfigService::class);
$messagePointConfig = $messagePointConfigService->getMessagePointConfig();
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage());
$messagePointConfig = new MessagePointConfigResponseDTO(null);
}
 
/** @var SmsSettingService $smsSettingService */
$smsSettingService = \App::getInstance(SmsSettingService::class);
$smsAutoConfig = $smsSettingService->getSmsAutoConfig();
$smsRejectPolicy = $smsSettingService->getSmsRejectConfig();
 
/** @var KakaoFriendTalkSettingService $kakaoFriendTalkSettingService */
$kakaoFriendTalkSettingService = \App::getInstance(KakaoFriendTalkSettingService::class);
$kakaoFriendTalkConfig = $kakaoFriendTalkSettingService->getKakaoFriendTalkConfig();
 
/** @var KakaoAlimTalkSettingService $kakaoAlimTalkSettingService */
$kakaoAlimTalkSettingService = \App::getInstance(KakaoAlimTalkSettingService::class);
$kakaoAlimTalkConfig = $kakaoAlimTalkSettingService->getKakaoAlimTalkConfig();
 
/** @var MyappPushSettingService $myappPushSettingService */
$myappPushSettingService = \App::getInstance(MyappPushSettingService::class);
$myappPushConfig = $myappPushSettingService->getMyappPushConfig();
 
try {
/** @var MyappSettingService $myappSettingService */
$myappSettingService = \App::getInstance(MyappSettingService::class);
$myappStatus = $myappSettingService->getMyappStatus();
} catch (\Throwable $e) {
\Logger::channel('myapp')->error($e->getMessage());
$myappStatus = new MyappStatusResponseDTO(['isInstalled' => false, 'isReleased' => false]);
}
 
$isKakaoAlrimAvailable = !$this->isNewMall();
$isKakaoAlrimLunaInstalled = GodoUtils::isPlusShop(PLUSSHOP_CODE_KAKAOALRIMLUNA);
 
$this->setData('messagePointConfig', $messagePointConfig);
$this->setData('smsRejectPolicy', $smsRejectPolicy);
$this->setData('smsAutoConfig', $smsAutoConfig);
$this->setData('kakaoFriendTalkConfig', $kakaoFriendTalkConfig);
$this->setData('isKakaoAlrimAvailable', $isKakaoAlrimAvailable);
$this->setData('isKakaoAlrimLunaInstalled', $isKakaoAlrimLunaInstalled);
$this->setData('kakaoAlimTalkConfig', $kakaoAlimTalkConfig);
$this->setData('isMyappInstalled', $myappStatus->isInstalled());
$this->setData('isMyappReleased', $myappStatus->isReleased());
$this->setData('myappPushConfig', $myappPushConfig);
 
$this->setData('linkPlatformTypes', LinkPlatformType::cases());
$this->setData('kakaoAlimTalkSenders', KakaoAlimTalkSender::cases());
$this->setData('autoSmsAlternativeSendTypes', AutoSmsAlternativeSendType::cases());
$this->setData('failedMessageSendTypes', FailedMessageSendType::cases());
$this->setData('smsAutoSendOverTypes', SmsAutoSendOverType::cases());
 
$this->getView()->setPageName('crm/message_config.php');
}
}
 
변경 후MessageConfigController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerBizmKakaoTalkChannelController.php-37
변경 전LayerBizmKakaoTalkChannelController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
use Bundle\Component\Member\KakaoAlrim;
 
class LayerBizmKakaoTalkChannelController extends \Controller\Admin\Controller
{
public function index()
{
$kakaoAlrimBizmConfig = gd_policy('kakaoAlrim.config');
 
gd_isset($kakaoAlrimBizmConfig['plusId'], '');
gd_isset($kakaoAlrimBizmConfig['kakaoKey'], '');
gd_isset($kakaoAlrimBizmConfig['phoneNumber'], '');
gd_isset($kakaoAlrimBizmConfig['useFlag'], 'n');
gd_isset($kakaoAlrimBizmConfig['approvalFl'], 'n');
 
$oKakao = new KakaoAlrim;
$senderProfileLookup = $oKakao->senderProfileLookup();
$kakaoAlrimBizmConfig['status'] = $senderProfileLookup->senderProfileStatus;
 
// PG 설정 불러오기
$pgConfig = gd_pgs();
$isConnectedPg = ($pgConfig['pgAutoSetting'] === 'y' || $pgConfig['pgApprovalSetting'] === 'y') ? 'y' : 'n';
 
$this->setData('isConnectedPg', $isConnectedPg);
$this->setData('kakaoAlrimBizmConfig', $kakaoAlrimBizmConfig);
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerBizmKakaoTalkChannelController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerBizmKakaoTalkChannelPsController.php-74
변경 전LayerBizmKakaoTalkChannelPsController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
use Bundle\Component\Member\KakaoAlrim;
use Bundle\Component\Policy\Policy;
use Framework\Debug\Exception\AlertCloseException;
use Framework\Http\Request;
use Origin\Enum\Crm\Message\KakaoAlimTalkSender;
use Origin\Service\Crm\Message\KakaoAlimTalkSettingService;
use Origin\Service\Crm\Message\KakaoFriendTalkSettingService;
use Origin\Service\Crm\Message\MessageConfigService;
use Origin\Service\Crm\Message\MyappPushSettingService;
 
class LayerBizmKakaoTalkChannelPsController extends \Controller\Admin\Controller
{
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
$mode = $postData['mode'];
 
switch ($mode) {
case 'delete':
try {
$oKakao = new KakaoAlrim;
$result = $oKakao->deleteKakaoKey();
if ($result['result'] !== 'success') {
$this->json(['success' => false, 'message' => '프로필키 삭제에 실패하였습니다.', 'data' => ['type' => 'fail_delete_kakao_key']]);
}
 
/** @var Policy $policy */
$policy = \App::load('\\Component\\Policy\\Policy');
$result = $policy->setValue('kakaoAlrim.config', []);
 
if (!$result) {
$this->json(['success' => false, 'message' => '설정 업데이트에 실패하였습니다.', 'data' => ['type' => 'fail_update_policy']]);
break;
}
 
$oKakao->deleteAllTemplate();
 
/** @var KakaoAlimTalkSettingService $kakaoAlimTalkSettingService */
$kakaoAlimTalkSettingService = \App::getInstance(KakaoAlimTalkSettingService::class);
$kakaoAlimTalkConfig = $kakaoAlimTalkSettingService->getKakaoAlimTalkConfig();
 
// 비즈엠 알림톡 사용안함 처리
if ($kakaoAlimTalkConfig->getSender() === KakaoAlimTalkSender::BIZM && $kakaoAlimTalkConfig->getUseFlag() === 'y') {
$kakaoAlimTalkConfig->setUseFlag('n');
 
/** @var MessageConfigService $messageConfigService */
$messageConfigService = \App::getInstance(MessageConfigService::class);
$messageConfigService->save(null, null, null, null, $kakaoAlimTalkConfig, null);
}
 
$this->json(['success' => true, 'message' => '프로필키 삭제에 성공하였습니다.']);
break;
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->warning($e->getMessage(), [__METHOD__, $e->getTraceAsString()]);
$this->json(['success' => false, 'message' => $e->getMessage()]);
}
break;
default:
throw new AlertCloseException(__("잘못된 접근입니다."));
}
}
}
 
변경 후LayerBizmKakaoTalkChannelPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerChargeMessagePointsHistoryController.php-16
변경 전LayerChargeMessagePointsHistoryController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
class LayerChargeMessagePointsHistoryController extends \Controller\Admin\Controller
{
public function index()
{
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerChargeMessagePointsHistoryController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerChargeMessagePointsHistoryResultController.php-47
변경 전LayerChargeMessagePointsHistoryResultController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
use Framework\Http\Request;
use Origin\DTO\Member\Sms\SmsPointChargeDTO;
use Origin\Service\Member\Sms\SmsPointChargeService;
 
class LayerChargeMessagePointsHistoryResultController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageConfig', 'chargeMessagePoints');
$naviMenu = $this->getData('naviMenu');
 
/** @var Request $request */
$request = \App::getInstance('request');
$requestData = $request->post()->toArray();
 
/** @var SmsPointChargeService $smsPointChargeService */
$smsPointChargeService = \App::getInstance(SmsPointChargeService::class);
 
$smsPointChargeListResponseDTO = $smsPointChargeService->getSmsPointChargeList(
$naviMenu->lno['2'],
$requestData['page'] ?? SmsPointChargeService::DEFAULT_PAGE,
$requestData['pageSize'] ?? SmsPointChargeService::DEFAULT_CRM_PAGE_SIZE
);
 
$smsPointChargeList = array_map(function(SmsPointChargeDTO $dto) {
$item = $dto->toArray();
$item['payTypeName'] = $dto->getPayTypeName(); // 원하는 값으로 변경
return $item;
}, $smsPointChargeListResponseDTO->getSmsPointChargeList());
 
$page = $smsPointChargeListResponseDTO->getPage();
 
$this->setData('smsPointChargeList', $smsPointChargeList);
$this->setData('page', $page);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerChargeMessagePointsHistoryResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerCloudKakaoTalkChannelController.php-30
변경 전LayerCloudKakaoTalkChannelController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
use Origin\Service\Member\KakaoAlrim\KakaoAlrimCloudSettingService;
 
class LayerCloudKakaoTalkChannelController extends \Controller\Admin\Controller
{
public function index()
{
/** @var KakaoAlrimCloudSettingService $kakaoAlrimCloudSettingService */
$kakaoAlrimCloudSettingService = \App::getInstance(KakaoAlrimCloudSettingService::class);
$kakaoAlrimCloudConfig = $kakaoAlrimCloudSettingService->getKakaoAlrimCloudData();
 
// PG 설정 불러오기
$pgConfig = gd_pgs();
$isConnectedPg = ($pgConfig['pgAutoSetting'] === 'y' || $pgConfig['pgApprovalSetting'] === 'y') ? 'y' : 'n';
 
$this->setData('isConnectedPg', $isConnectedPg);
$this->setData('kakaoAlrimCloudConfig', $kakaoAlrimCloudConfig);
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerCloudKakaoTalkChannelController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerCloudKakaoTalkChannelPsController.php-74
변경 전LayerCloudKakaoTalkChannelPsController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
use Framework\Debug\Exception\AlertCloseException;
use Framework\Http\Request;
use Origin\Enum\Crm\Message\KakaoAlimTalkSender;
use Origin\Service\Crm\Message\KakaoAlimTalkSettingService;
use Origin\Service\Crm\Message\KakaoFriendTalkSettingService;
use Origin\Service\Crm\Message\MessageConfigService;
use Origin\Service\Crm\Message\MyappPushSettingService;
use Origin\Service\Member\KakaoAlrim\KakaoAlrimCloudPsService;
 
class LayerCloudKakaoTalkChannelPsController extends \Controller\Admin\Controller
{
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
$mode = $postData['mode'];
 
switch ($mode) {
case 'delete':
try {
/** @var KakaoAlrimCloudPsService $service */
$service = \App::getInstance(KakaoAlrimCloudPsService::class);
$result = $service->deletePlusId($postData);
 
/** @var KakaoFriendTalkSettingService $kakaoFriendTalkSettingService */
$kakaoFriendTalkSettingService = \App::getInstance(KakaoFriendTalkSettingService::class);
$kakaoFriendTalkConfig = $kakaoFriendTalkSettingService->getKakaoFriendTalkConfig();
 
/** @var KakaoAlimTalkSettingService $kakaoAlimTalkSettingService */
$kakaoAlimTalkSettingService = \App::getInstance(KakaoAlimTalkSettingService::class);
$kakaoAlimTalkConfig = $kakaoAlimTalkSettingService->getKakaoAlimTalkConfig();
 
$needConfigUpdate = false;
// 친구톡 사용안함 처리
if ($kakaoFriendTalkConfig->getUseFlag() === 'y') {
$kakaoFriendTalkConfig->setUseFlag('n');
$needConfigUpdate = true;
}
 
// 클라우드 알림톡 사용안함 처리
if ($kakaoAlimTalkConfig->getSender() === KakaoAlimTalkSender::NHN_CLOUD && $kakaoAlimTalkConfig->getUseFlag() === 'y') {
$kakaoAlimTalkConfig->setUseFlag('n');
$needConfigUpdate = true;
}
 
if ($needConfigUpdate) {
/** @var MessageConfigService $messageConfigService */
$messageConfigService = \App::getInstance(MessageConfigService::class);
$messageConfigService->save(null, null, null, $kakaoFriendTalkConfig, $kakaoAlimTalkConfig, null);
}
 
$this->json(['success' => $result]);
break;
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->warning($e->getMessage(), [__METHOD__, $e->getTraceAsString()]);
$this->json(['success' => false, 'message' => $e->getMessage()]);
}
break;
default:
throw new AlertCloseException(__("잘못된 접근입니다."));
}
}
}
 
변경 후LayerCloudKakaoTalkChannelPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerLunaKakaoTalkChannelController.php-29
변경 전LayerLunaKakaoTalkChannelController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
 
use Framework\Enum\ExternalUrl;
 
class LayerLunaKakaoTalkChannelController extends \Controller\Admin\Controller
{
public function index()
{
$kakaoAlrimLunaConfig = gd_policy('kakaoAlrimLuna.config');
 
gd_isset($kakaoAlrimLunaConfig['useFlag'], 'n');
gd_isset($kakaoAlrimLunaConfig['lunaCliendId'], '');
gd_isset($kakaoAlrimLunaConfig['lunaClientKey'], '');
 
$this->setData('kakaoAlrimLunaConfig', $kakaoAlrimLunaConfig);
$this->setData('lunaKakaoRequestUrl', ExternalUrl::GODO_KAKAO_API->getUrl('/lunaKakaoRequest.php'));
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerLunaKakaoTalkChannelController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerLunaKakaoTalkChannelPsController.php-88
변경 전LayerLunaKakaoTalkChannelPsController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
use Bundle\Component\Member\KakaoAlrimLuna;
use Bundle\Component\Policy\Policy;
use Framework\Debug\Exception\AlertCloseException;
use Framework\Http\Request;
use Origin\Enum\Crm\Message\KakaoAlimTalkSender;
use Origin\Service\Crm\Message\KakaoAlimTalkSettingService;
use Origin\Service\Crm\Message\KakaoFriendTalkSettingService;
use Origin\Service\Crm\Message\MessageConfigService;
use Origin\Service\Crm\Message\MyappPushSettingService;
 
class LayerLunaKakaoTalkChannelPsController extends \Controller\Admin\Controller
{
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
$mode = $postData['mode'];
 
switch ($mode) {
case 'generateSendData':
try {
unset($postData['mode']);
 
$oKakao = new KakaoAlrimLuna;
$encodedContent = $oKakao->sendLunaId($postData);
 
$this->json(['success' => true, 'data' => $encodedContent]);
} catch (\Throwable $e) {
\Logger::channel('kakao')->warning($e->getMessage(), [__METHOD__, $e->getTraceAsString()]);
$this->json(['success' => false, 'message' => $e->getMessage()]);
}
break;
case 'logout':
try {
$oKakao = new KakaoAlrimLuna;
$deleteResult = $oKakao->deleteLunaKey();
 
if ($deleteResult['result'] !== 'success') {
$this->json(['success' => false, 'message' => '처리중에 오류가 발생하여 실패되었습니다.']);
}
 
$updateData = ['lunaKeyDel' => 'y'];
 
/** @var Policy $policy */
$policy = \App::load('\\Component\\Policy\\Policy');
$result = $policy->saveKakaoAlrimLunaConfig($updateData);
if ($result) {
$tmpkakaoAutoSet = gd_policy('kakaoAlrimLuna.kakaoAuto');
$tmpLunaSet = gd_array_merge($tmpkakaoAutoSet, array('useFlag' => 'n'));
gd_set_policy('kakaoAlrimLuna.kakaoAuto', $tmpLunaSet);
 
/** @var KakaoAlimTalkSettingService $kakaoAlimTalkSettingService */
$kakaoAlimTalkSettingService = \App::getInstance(KakaoAlimTalkSettingService::class);
$kakaoAlimTalkConfig = $kakaoAlimTalkSettingService->getKakaoAlimTalkConfig();
 
// 루나 알림톡 사용안함 처리
if ($kakaoAlimTalkConfig->getSender() === KakaoAlimTalkSender::BLUMN_AI && $kakaoAlimTalkConfig->getUseFlag() === 'y') {
$kakaoAlimTalkConfig->setUseFlag('n');
 
/** @var MessageConfigService $messageConfigService */
$messageConfigService = \App::getInstance(MessageConfigService::class);
$messageConfigService->save(null, null, null, null, $kakaoAlimTalkConfig, null);
}
}
 
$this->json(['success' => $result]);
break;
} catch (\Throwable $e) {
\Logger::channel('kakao')->warning($e->getMessage(), [__METHOD__, $e->getTraceAsString()]);
$this->json(['success' => false, 'message' => $e->getMessage()]);
}
break;
default:
throw new AlertCloseException(__("잘못된 접근입니다."));
}
}
}
 
변경 후LayerLunaKakaoTalkChannelPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerManageSmsVerificationController.php-37
변경 전LayerManageSmsVerificationController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
use Framework\Http\Request;
use Origin\Service\Crm\Message\SmsPasswordService;
 
class LayerManageSmsVerificationController extends \Controller\Admin\Controller
{
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$session = \App::getInstance('session');
$displayInfo = [
'title' => 'SMS 인증번호',
'useCaptcha' => false,
'retry' => $session->get('captchaRetry', 1),
];
 
if ($request->request()->get('mode', 'input') === 'change') {
$displayInfo['useCaptcha'] = true;
}
 
$restrictedKeywords = SmsPasswordService::RESTRICTED_KEYWORDS;
 
$this->setData('displayInfo', $displayInfo);
$this->setData('restrictedKeywords', $restrictedKeywords);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerManageSmsVerificationController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerManageSmsVerificationPsController.php-69
변경 전LayerManageSmsVerificationPsController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
use Bundle\Component\Captcha\Captcha;
use Bundle\Component\Sms\Exception\PasswordException;
use Component\Sms\SmsSender;
use Framework\Http\Request;
use Framework\Security\Encryptor;
use Framework\Utility\ComponentUtils;
 
class LayerManageSmsVerificationPsController extends \Controller\Admin\Controller
{
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
 
$isChangePassword = $request->post()->get('mode', 'changePassword') === 'changePassword';
if ($isChangePassword) {
$captchaNumber = $request->post()->get('captcha');
$captcha = new Captcha();
$result = $captcha->verify($captchaNumber, 1);
if ($result['code'] !== '0000') {
$this->json(['success' => false, 'message' => '자동등록 방지문자가 맞지 않습니다.', 'data' => ['type' => 'invalid_captcha']]);
}
}
 
$smsPassword = $request->post()->get('password');
 
/** @var \Bundle\Component\Sms\SmsSender $smsSender */
$smsSender = \App::load(SmsSender::class);
try {
$smsSender->validPassword($smsPassword, !$isChangePassword);
 
/** @var Encryptor $encryptor */
$encryptor = \App::getInstance('encryptor');
$policy = ComponentUtils::getPolicy('sms.config');
if ($isChangePassword) {
$policy['authentication'] = [
'failLog' => [],
'failCnt' => 0,
'password' => $encryptor->encrypt($smsPassword),
];
} else {
$policy['authentication']['failLog'] = [];
$policy['authentication']['failCnt'] = 0;
}
 
ComponentUtils::setPolicy('sms.config', $policy);
 
$this->json(['success' => true, 'message' => '메시지 인증번호 등록이 완료되었습니다.']);
} catch (PasswordException $e) {
$message = $e->getMessage();
if ($isChangePassword) {
$message = '메시지 인증번호는 영문대문자/영문소문자/숫자/특수문자 중 2가지 이상 조합, 10~16자리로만 설정할 수 있습니다.';
$message .= '<br/>[마이페이지 > 쇼핑몰 관리]에서 메시지 인증번호를 수정 후 다시 시도해주세요.';
}
 
$this->json(['success' => false, 'message' => $message, 'data' => ['type' => 'invalid_format']]);
}
}
}
 
변경 후LayerManageSmsVerificationPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerMessagePointController.php-50
변경 전LayerMessagePointController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
use Component\Sms\SmsUtil;
use Component\Sms\Sms;
use Origin\Service\Member\Sms\SmsPointChargeService;
 
class LayerMessagePointController extends \Controller\Admin\Controller
{
public function index()
{
// 메뉴 설정
$this->callMenu('crm', 'messageConfig', 'messageConfig');
$naviMenu = $this->getData('naviMenu');
 
// 현재 SMS 포인트 싱크
Sms::saveSmsPoint();
 
// 포인트 충전 내역
/** @var SmsPointChargeService $smsPointChargeService */
$smsPointChargeService = \App::getInstance(SmsPointChargeService::class);
$existsChargeHistory = $smsPointChargeService->existsSmsPointChargeHistory($naviMenu->lno['2']);
 
if ($existsChargeHistory) {
// 현재 메시지 포인트
$nowSmsPoint = (float) Sms::getPoint();
 
$availableCount = [
'sms' => SmsUtil::getAvailableCount('sms', $nowSmsPoint),
'lms' => SmsUtil::getAvailableCount('lms', $nowSmsPoint),
'kakaoAlrimTalk' => SmsUtil::getAvailableCount('kakaoAlrimTalk', $nowSmsPoint),
'kakaoFriendTalk' => SmsUtil::getAvailableCount('kakaoFriendTalk', $nowSmsPoint),
];
 
$this->setData('availableCount', $availableCount);
}
 
// 포인트 충전내역 존재 여부 set
$this->setData('existsChargeHistory', $existsChargeHistory);
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMessagePointController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerRegisterKakaoTalkChannelController.php-38
변경 전LayerRegisterKakaoTalkChannelController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\Service\Member\KakaoAlrim\KakaoAlrimCloudRegistService;
 
class LayerRegisterKakaoTalkChannelController extends \Controller\Admin\Controller
{
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$sender = $request->post()->get('sender');
 
try {
/** @var KakaoAlrimCloudRegistService $service */
$service = \App::getInstance(KakaoAlrimCloudRegistService::class);
[$firstLevelCategories, $secondLevelCategories, $thirdLevelCategories] = $service->getCategoryInfo();
} catch (\Throwable $e) {
$this->json(['success' => false], Response::HTTP_INTERNAL_SERVER_ERROR);
}
 
$this->setData('sender', $sender);
$this->setData('firstLevelCategories', gd_isset($firstLevelCategories));
$this->setData('secondLevelCategories', gd_isset($secondLevelCategories));
$this->setData('thirdLevelCategories', gd_isset($thirdLevelCategories));
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerRegisterKakaoTalkChannelController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerRegisterKakaoTalkChannelPsController.php-154
변경 전LayerRegisterKakaoTalkChannelPsController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
use Bundle\Component\Member\KakaoAlrim;
use Bundle\Component\Policy\Policy;
use Framework\Debug\Exception\AlertCloseException;
use Framework\Http\Request;
use Framework\Http\Response;
use GuzzleHttp\Exception\GuzzleException;
use Origin\DTO\Member\KakaoAlrim\KakaoAlirmCloudRegisterDto;
use Origin\DTO\Member\KakaoAlrim\KakaoAlirmGetTokenDto;
use Origin\Service\Member\KakaoAlrim\KakaoAlrimCloudRegistService;
use Origin\Service\Member\KakaoAlrim\KakaoAlrimCloudSyncService;
use Origin\Util\RequestValidation;
 
class LayerRegisterKakaoTalkChannelPsController extends \Controller\Admin\Controller
{
/**
* @throws AlertCloseException
* @throws GuzzleException
*/
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$requestData = $request->request()->toArray();
$mode = $requestData['mode'];
 
switch ($mode) {
case 'getToken':
try {
switch ($requestData['sender']) {
case 'kakaoAlrimCloud':
RequestValidation::validate($requestData, ['plusId', 'phoneNumber', 'category']);
 
$dto = new KakaoAlirmGetTokenDto($requestData);
 
/** @var KakaoAlrimCloudRegistService $service */
$service = \App::getInstance(KakaoAlrimCloudRegistService::class);
 
$result = $service->getToken($dto);
$this->json(['success' => $result, 'message' => $result ? null : '인증 번호 발송 실패']);
break;
case 'kakaoAlrim':
$bizmRequestData = [
'yellowId' => $requestData['plusId'],
'phoneNumber' => $requestData['phoneNumber']
];
 
$kakaoAlrim = new KakaoAlrim();
$result = $kakaoAlrim->getToken($bizmRequestData);
 
if (!empty($result['code']) && $result['code'] == 'success') {
$this->json(['success' => true, 'message' => '인증 번호 발송 성공']);
} else {
$this->json(['success' => false, 'message' => $result['message'] ?? '인증 번호 발송 실패']);
}
break;
}
} catch (\Throwable $e) {
\Logger::channel('kakao')->warning($e->getMessage(), [__METHOD__, $e->getTraceAsString()]);
$this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
break;
case 'register':
try {
RequestValidation::validate($requestData, ['sender', 'plusId', 'token', 'phoneNumber']);
$requestData['approvalFl'] = 'y';
 
switch ($requestData['sender']) {
case 'kakaoAlrimCloud':
$registerDto = new KakaoAlirmCloudRegisterDto($requestData);
/** @var KakaoAlrimCloudRegistService $registerService */
$registerService = \App::getInstance(KakaoAlrimCloudRegistService::class);
$result = $registerService->register($registerDto);
/** @var KakaoAlrimCloudSyncService $kakaoAlrimCloudSyncService */
$kakaoAlrimCloudSyncService = \App::getInstance(KakaoAlrimCloudSyncService::class);
$kakaoAlrimCloudSyncService->syncMessageTemplate();
 
if ($result) {
$this->json([
'success' => true,
'message' => '카카오톡 채널이 등록되었습니다.',
'data' => [
'alertMessage' => [
'message' => '카카오톡 채널이 등록되었습니다.<br>메시지 설정 화면에서 등록된 채널 정보를 확인할 수 있습니다.'
],
'requestData' => $requestData
]
]);
} else {
\Logger::channel('kakao')->warning('카카오톡 채널 등록에 실패했습니다.', [$requestData['sender']]);
throw new \Exception("카카오톡 채널 등록에 실패했습니다.");
}
break;
case 'kakaoAlrim':
$kakaoAlrim = new KakaoAlrim();
$profile = $kakaoAlrim->getKakaoKey($requestData);
 
if ($profile['result'] == 'success') {
$requestData['approvalFl'] = 'y';
$requestData['approvalDt'] = date('Y-m-d');
$requestData['approvalId'] = \Session::get('manager.managerId');
$requestData['kakaoKey'] = $profile['data'];
 
/** @var Policy $policy */
$policy = \App::load('\\Component\\Policy\\Policy');
$result = $policy->saveKakaoAlrimConfig($requestData);
 
if($result) {
$kakaoAlrim->saveKakaoAuto($requestData);
$this->json([
'success' => true,
'message' => '카카오톡 채널이 등록되었습니다.',
'data' => [
'alertMessage' => [
'message' => '카카오톡 채널이 등록되었습니다.<br>메시지 설정 화면에서 등록된 채널 정보를 확인할 수 있습니다.'
],
'requestData' => $requestData
]
]);
} else {
\Logger::channel('kakao')->warning('카카오톡 채널 등록에 실패하였습니다.', [$requestData['sender']]);
throw new \Exception("카카오톡 채널 등록에 실패했습니다.");
}
}
break;
}
} catch (\Throwable $e) {
\Logger::channel('kakao')->warning($e->getMessage(), [__METHOD__, $e->getTraceAsString()]);
$this->json([
'success' => false,
'message' => $e->getMessage(),
'data' => [
'alertMessage' => [
'message' => '카카오톡 채널 등록에 실패했습니다.',
'subMessage' => '카카오 채널의 관리자 설정 또는 비즈니스 인증 상태를 확인 후 다시 시도해 주세요.'
]
]
]);
}
break;
default:
throw new AlertCloseException(__("잘못된 접근입니다."));
}
}
}
 
변경 후LayerRegisterKakaoTalkChannelPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerRejectNumberListController.php-17
변경 전LayerRejectNumberListController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
class LayerRejectNumberListController extends \Controller\Admin\Controller
{
public function index()
{
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerRejectNumberListController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerRejectNumberListPsController.php-49
변경 전LayerRejectNumberListPsController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
use Bundle\Component\Sms\Sms080;
use Framework\Debug\Exception\AlertCloseException;
use Framework\Http\Request;
use Framework\Utility\ComponentUtils;
use Framework\Utility\StringUtils;
 
class LayerRejectNumberListPsController extends \Controller\Admin\Controller
{
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$requestData = $request->request();
$mode = $requestData->get('mode');
 
switch ($mode) {
case 'checkActivation':
$policy = ComponentUtils::getPolicy('sms.sms080');
StringUtils::strIsSet($policy['status'], '');
$isOpened = $policy['status'] == 'O';
 
$this->json(['success' => $isOpened]);
break;
case 'manualSync':
try {
/** @var Sms080 $sms080 */
$sms080 = \App::load('Component\\Sms\\Sms080');
$sms080->syncListByManual();
$this->json(['success' => true, 'message' => "동기화가 완료되었습니다."]);
} catch (\Throwable $e) {
\Logger::channel('sms')->warning($e->getMessage(), [__METHOD__, $e->getTraceAsString()]);
$this->json(['success' => false, 'message' => "회원 정보와 동기화 중 오류가 발생했습니다."]);
}
break;
default:
throw new AlertCloseException(__("잘못된 접근입니다."));
}
}
}
 
변경 후LayerRejectNumberListPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerRejectNumberListResultController.php-44
변경 전LayerRejectNumberListResultController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
use Bundle\Component\Page\Page;
use Bundle\Component\Sms\Sms080DAO;
use Framework\Http\Request;
 
class LayerRejectNumberListResultController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageConfig', 'messageConfig');
$naviMenu = $this->getData('naviMenu');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post();
$page = $postData->get('page', 1);
$pageSize = $postData->get('pageSize', 10);
 
$keyword = str_replace('-', '', $postData->get('keyword', ''));
 
/** @var Sms080DAO $dao */
$dao = \App::load('Component\\Sms\\Sms080DAO');
$sms080RejectList = $dao->selectList(['offset' => $page, 'limit' => $pageSize, 'keyword' => $keyword]);
 
$totalCount = $dao->countList(['keyword' => $keyword]);
$amountCount = $dao->countList([]);
$page = new Page($page, $totalCount, $amountCount, $pageSize);
 
$this->setData('sms080RejectList', $sms080RejectList);
$this->setData('keyword', $keyword);
$this->setData('page', $page);
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerRejectNumberListResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfig/LayerSmsVerificationController.php-34
변경 전LayerSmsVerificationController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MessageConfig;
 
use Origin\Service\Crm\Message\SmsPasswordService;
 
class LayerSmsVerificationController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageConfig', 'messageConfig');
$this->setMenuCode('crm', 'messageConfig', 'messageConfig');
 
try {
/** @var SmsPasswordService $smsPasswordService */
$smsPasswordService = \App::getInstance(SmsPasswordService::class);
$smsPassword = $smsPasswordService->getSmsPassword(true);
$isVerified = $smsPasswordService->verifySmsPassword($smsPassword);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage());
$isVerified = false;
}
 
$this->setData('smsPassword', $smsPassword);
$this->setData('isVerified', $isVerified);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerSmsVerificationController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageConfigPsController.php-65
변경 전MessageConfigPsController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Framework\Http\Request;
use Origin\DTO\Crm\Message\InsufficMessagePointAlertConfigDTO;
use Origin\DTO\Crm\Message\KakaoAlimTalkConfigDTO;
use Origin\DTO\Crm\Message\KakaoFriendTalkConfigDTO;
use Origin\DTO\Crm\Message\MessagePointConfigDTO;
use Origin\DTO\Crm\Message\MyappPushConfigDTO;
use Origin\DTO\Crm\Message\SmsAutoConfigDTO;
use Origin\DTO\Crm\Message\SmsRejectConfigDTO;
use Origin\Service\Crm\Message\MessageConfigService;
 
class MessageConfigPsController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageConfig', 'messageConfig');
$this->setMenuCode('crm', 'messageConfig', 'messageConfig');
 
/** @var Request $request */
$request = \App::getInstance('request');
$mode = $request->request()->get('mode');
 
switch ($mode) {
case 'save':
$postValue = $request->post()->toArray();
 
try {
$messagePointConfig = new MessagePointConfigDTO($postValue['messagePointConfig']);
$smsAutoConfig = new SmsAutoConfigDTO($postValue['smsAutoConfig']);
$smsRejectConfig = $postValue['smsRejectUse'] ? new SmsRejectConfigDTO(['use' => $postValue['smsRejectUse']]) : null;
$kakaoFriendTalkConfig = new KakaoFriendTalkConfigDTO($postValue['kakaoFriendTalkConfig']);
$kakaoAlimTalkConfig = new KakaoAlimTalkConfigDTO($postValue['kakaoAlimTalkConfig']);
$myappPushConfig = new MyappPushConfigDTO($postValue['myappPushConfig']);
 
/** @var MessageConfigService $messageConfigService */
$messageConfigService = \App::getInstance(MessageConfigService::class);
$messageConfigService->save(
$messagePointConfig,
$smsAutoConfig,
$smsRejectConfig,
$kakaoFriendTalkConfig,
$kakaoAlimTalkConfig,
$myappPushConfig
);
 
$this->json(['success' => true, 'message' => '메시지 설정이 저장되었습니다.']);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->warning($e->getMessage(), ['requestData' => $postValue]);
$this->json(['success' => false, 'message' => $e->getMessage()]);
}
break;
default:
throw new \InvalidArgumentException('잘못된 접근입니다.');
}
}
}
 
변경 후MessageConfigPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageTemplateController.php-52
변경 전MessageTemplateController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Bundle\Component\Page\Page;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateCategoryDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateKakaoStatusDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateListSearchDTO;
use Origin\Service\Crm\Message\MessageTemplateFindService;
use Origin\Traits\Member\KakaoAlrim\KakaoAlrimCloudAvailable;
 
 
class MessageTemplateController extends \Controller\Admin\Controller
{
use KakaoAlrimCloudAvailable;
 
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');
$this->getView()->setDefine('messageTemplateResult', 'crm/message_template/message_template_result.php');
 
$request = \App::getInstance('request');
$requestData = $request->request()->toArray();
 
/** @var MessageTemplateFindService $messageHistoryService */
$service = \App::getInstance(MessageTemplateFindService::class);
$configDto = $service->getUseFlagDto();
 
$searchDto = new MessageTemplateListSearchDTO($requestData);
$results = $service->getMessageTemplateList($searchDto);
 
$isNewMall = $this->isNewMall();
 
$this->setData('sendMethod', $searchDto->getSendMethod());
$this->setData('search', $searchDto);
$this->setData('config', $configDto);
$this->setData('results', $results['result']);
$this->setData('page', $results['page']);
$this->setData('searchCategory', new MessageTemplateCategoryDTO());
$this->setData('searchStatus', new MessageTemplateKakaoStatusDTO());
$this->setData('isNewMall', $isNewMall);
}
}
 
변경 후MessageTemplateController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageTemplatePsController.php-47
변경 전MessageTemplatePsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Framework\Http\Request;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateCopyRequestDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateMyappDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateRemoveRequestDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateSmsDTO;
use Origin\Service\Crm\Message\MessageTemplateFindService;
use Origin\Service\Crm\Message\MessageTemplateRegisterService;
use Origin\Service\Crm\Message\MessageTemplateRemoveService;
 
class MessageTemplatePsController extends \Controller\Admin\Controller
{
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$requestData = $request->request()->toArray();
$mode = $requestData['mode'] ?? '';
 
switch ($mode) {
case 'delete':
$requestDto = new MessageTemplateRemoveRequestDTO($requestData);
/** @var MessageTemplateRemoveService $service */
$service = \App::getInstance(MessageTemplateRemoveService::class);
$result = $service->deleteTemplate($requestDto);
break;
case 'copy':
$requestDto = new MessageTemplateCopyRequestDTO($requestData);
/** @var MessageTemplateRegisterService $service */
$service = \App::getInstance(MessageTemplateRegisterService::class);
$result = $service->copyTemplate($requestDto);
break;
default:
$result = ['success' => false, 'message' => '잘못된 요청입니다.'];
break;
}
$this->json($result);
}
}
 
변경 후MessageTemplatePsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageTemplateRegisterController.php-75
변경 전MessageTemplateRegisterController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Framework\Http\Request;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateCategoryDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateKakaoStatusDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateRegisterRequestDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateResultDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateSearchDTO;
use Origin\Service\Crm\Message\MessageTemplateFindService;
use Origin\Service\Crm\Message\MyappPushTemplateService;
use Origin\Traits\Member\KakaoAlrim\KakaoAlrimCloudAvailable;
use function JmesPath\search;
 
class MessageTemplateRegisterController extends \Controller\Admin\Controller
{
use KakaoAlrimCloudAvailable;
 
public function index()
{
$this->setNcdsSubBodyClass();
$request = \App::getInstance('request');
$requestData = $request->request()->toArray();
 
 
/** @var MessageTemplateFindService $messageHistoryService */
$service = \App::getInstance(MessageTemplateFindService::class);
$configDto = $service->getUseFlagDto();
 
/** @var MessageTemplateRegisterRequestDTO $messageTemplateRegisterRequestDTO */
$requestDto = new MessageTemplateRegisterRequestDTO($requestData);
$searchDto = new MessageTemplateSearchDTO($requestData);
 
$this->getView()->setPageName('crm/message_template_register.php');
$this->getView()->setDefine('basicConfig', 'crm/message_template_register/basic_config.php');
$this->getView()->setDefine('templateContentsSms', 'crm/message_template_register/template_contents_sms.php');
$this->getView()->setDefine('templateContentsKakao', 'crm/message_template_register/template_contents_kakao.php');
$this->getView()->setDefine('templateContentsMyapp', 'crm/message_template_register/template_contents_myapp.php');
$this->getView()->setDefine('previewMessage', 'crm/message_template_register/preview_message.php');
 
switch ($requestDto->getMode()) {
case 'register':
$requestDto = $service->changeRegisterPormat($requestDto,$configDto);
$this->callMenu('crm', 'messageSend', 'messageTemplateRegister');
$responseDto = new MessageTemplateResultDTO();
break;
case 'copy':
$this->callMenu('crm', 'messageSend', 'messageTemplateRegister');
$responseDto = $service->getMessageTemplate($searchDto);
break;
case 'modify':
$this->callMenu('crm', 'messageSend', 'messageTemplateModify');
$responseDto = $service->getMessageTemplate($searchDto);
break;
default:
$responseDto = new MessageTemplateResultDTO();
}
$isNewMall = $this->isNewMall();
 
$myappService = \App::getInstance(MyappPushTemplateService::class);
$this->getView()->setData('myappUrl', $myappService->getMyappDomainUrl());
$this->getView()->setData('config', $configDto);
$this->getView()->setData('request', $requestDto);
$this->getView()->setData('response', $responseDto);
$this->setData('category', new MessageTemplateCategoryDTO());
$this->setData('status', new MessageTemplateKakaoStatusDTO());
$this->setData('isNewMall', $isNewMall);
}
}
 
변경 후MessageTemplateRegisterController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MessageTemplateRegisterPsController.php-50
변경 전MessageTemplateRegisterPsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateKakaoBizmDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateKakaoCloudDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateMyappDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateRegisterRequestDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateSmsDTO;
use Origin\Service\Crm\Message\MessageTemplateModifyService;
use Origin\Service\Crm\Message\MessageTemplateRegisterService;
 
class MessageTemplateRegisterPsController extends \Controller\Admin\Controller
{
public function index()
{
try {
$request = \App::getInstance('request');
$requestData = $request->request()->toArray();
$filesData = $request->files()->toArray();
 
$requestData['mode'] = ($requestData['mode'] === 'copy') ? 'register' : $requestData['mode'];
$requestDto = new MessageTemplateRegisterRequestDTO($requestData);
 
switch ($requestDto->getMode()){
case 'register':
/** @var MessageTemplateRegisterService $service */
$service = \App::getInstance(MessageTemplateRegisterService::class);
$result = $service->saveTemplate($requestDto, $requestData, $filesData);
break;
case 'modify':
/** @var MessageTemplateModifyService $service */
$service = \App::getInstance(MessageTemplateModifyService::class);
$result = $service->updateTemplate($requestDto, $requestData, $filesData);
break;
default:
$result = ['success' => false, 'message' => '잘못된 모드입니다.'];
break;
}
$this->json($result);
} catch (\Throwable $e) {
$this->json(['success' => false, 'message' => '등록, 수정에 실패하였습니다']);
}
}
}
 
변경 후MessageTemplateRegisterPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileHistoryController.php-109
변경 전MobileHistoryController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use App;
use Component\Sms\Sms;
use Component\Sms\SmsUtil;
use DateTime;
use Origin\DTO\ApiClient\Commerce\Apps\MyappPushHistorySummaryRequestDTO;
use Origin\DTO\ApiClient\Commerce\Apps\MyappPushHistorySummaryResponseDTO;
use Origin\DTO\ApiClient\Commerce\Crm\CampaignCountsRequestDTO;
use Origin\DTO\ApiClient\Commerce\Crm\CampaignCountsResponseDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageHistorySummaryResponseDTO;
use Origin\Enum\ApiClient\Crm\CampaignSendType;
use Origin\Enum\ApiClient\Crm\CampaignStatusType;
use Origin\Service\AutoSend\ActiveAutoSendService;
use Origin\Service\Crm\Message\CrmNotificationCampaignService;
use Origin\Service\Crm\Message\MessageHistoryService;
use Origin\Service\Crm\Message\MyappPushHistoryService;
 
class MobileHistoryController extends \Controller\Admin\Controller
{
public function index()
{
$this->setNcdsSubBodyClass();
$this->callMenu('crm', 'messageHistory', 'mobileHistory');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistory');
 
try {
/** @var MessageHistoryService $messageHistoryService */
$messageHistoryService = \App::getInstance(MessageHistoryService::class);
$messageHistorySummaryResponseDTO = $messageHistoryService->getMessageHistorySummary();
 
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage());
$messageHistorySummaryResponseDTO = new MessageHistorySummaryResponseDTO(null);
}
 
try {
$requestDTO = (new MyappPushHistorySummaryRequestDTO())
->setStartDate(new DateTime('-7 days'))
->setEndDate(new DateTime('-1 days'));
 
/** @var MyappPushHistoryService $myappPushHistoryService */
$myappPushHistoryService = \App::getInstance(MyappPushHistoryService::class);
$myappPushHistorySummaryResponseDTO = $myappPushHistoryService->getMyappPushHistorySummary($requestDTO);
 
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage());
$myappPushHistorySummaryResponseDTO = new MyappPushHistorySummaryResponseDTO(null);
}
 
try {
$requestDTO = (new CampaignCountsRequestDTO())
->setSendType([CampaignSendType::SCHEDULED, CampaignSendType::REPEAT])
->setStatusType([CampaignStatusType::WAITING, CampaignStatusType::PROCESSING]);
 
 
/** @var CrmNotificationCampaignService $crmNotificationCampaignService */
$crmNotificationCampaignService = \App::getInstance(CrmNotificationCampaignService::class);
$crmNotificationCampaignCountsResponseDTO = $crmNotificationCampaignService->getNotificationCampaignCounts($requestDTO);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage());
$crmNotificationCampaignCountsResponseDTO = new CampaignCountsResponseDTO(null);
}
 
// 현재 SMS 포인트 싱크
Sms::saveSmsPoint();
 
// 현재 메시지 포인트
$nowSmsPoint = (float) Sms::getPoint();
 
$availableCount = [
'sms' => SmsUtil::getAvailableCount('sms', $nowSmsPoint),
'lms' => SmsUtil::getAvailableCount('lms', $nowSmsPoint),
'kakaoAlrimTalk' => SmsUtil::getAvailableCount('kakaoAlrimTalk', $nowSmsPoint),
'kakaoFriendTalk' => SmsUtil::getAvailableCount('kakaoFriendTalk', $nowSmsPoint),
];
 
/** @var ActiveAutoSendService $activeAutoSendService */
$activeAutoSendService = \App::getInstance(ActiveAutoSendService::class);
$autoSendCounts = [
'active' => $activeAutoSendService->count(),
];
 
$this->setData('availableCount', $availableCount);
 
$this->setData('messageCountLast7Days', $messageHistorySummaryResponseDTO->getMessageCountLast7Days());
$this->setData('friendtalkCountLast7Days', $messageHistorySummaryResponseDTO->getFriendtalkCountLast7Days());
$this->setData('alimtalkCountLast7Days', $messageHistorySummaryResponseDTO->getAlimtalkCountLast7Days());
$this->setData('myappPushCountLast7Days', $myappPushHistorySummaryResponseDTO->getTotalPushMessageCount());
$this->setData('pointUsageLast7Days', $messageHistorySummaryResponseDTO->getPointUsageLast7Days());
$this->setData('pointUsageLast28Days', $messageHistorySummaryResponseDTO->getPointUsageLast28Days());
$this->setData('crmNotificationCampaignCounts', $crmNotificationCampaignCountsResponseDTO->getStatusCount());
$this->setData('autoSendCounts', $autoSendCounts);
 
$this->setData('scheduledSendWaitingUrl', './scheduled_send.php?statusType=WAITING');
$this->setData('scheduledSendRepeatUrl', './scheduled_send.php?formType=REPEAT&statusType=PROCESSING&periodType=1YEAR&createdAtFrom=' . date('Y-m-d', strtotime('-1 year')) . '&createdAtTo=' . date('Y-m-d'));
$this->setData('autoSendUrl', './auto_send.php?shouldAutoSend=y');
 
$this->getView()->setPageName('crm/mobile_history.php');
}
}
 
변경 후MobileHistoryController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileHistoryListController.php-27
변경 전MobileHistoryListController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Framework\Http\Request;
 
class MobileHistoryListController extends \Controller\Admin\Controller
{
public function index()
{
$this->setNcdsSubBodyClass();
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$getData = $request->get()->toArray();
 
$this->setData('sendMethod', $getData['sendMethod']);
$this->getView()->setPageName('crm/mobile_history_list.php');
}
}
 
변경 후MobileHistoryListController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerBizmKakaoAlimTalkHistoryResultController.php-114
변경 전LayerBizmKakaoAlimTalkHistoryResultController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\DTO\ApiClient\Commerce\Notification\BizmKakaoAlimTalkHistoryDTO;
use Origin\DTO\ApiClient\Commerce\Notification\BizmKakaoAlimTalkHistoryListRequestDTO;
use Origin\DTO\ApiClient\Commerce\Notification\BizmKakaoAlimTalkHistoryListResponseDTO;
use Origin\DTO\ApiClient\Commerce\Notification\BizmKakaoAlimTalkHistorySearchFormDTO;
use Origin\Enum\ApiClient\Excel\ExcelGenerateMenu;
use Origin\Enum\ApiClient\Notification\KakaoAlimTalkSendStatus;
use Origin\Service\Crm\Message\KakaoAlimTalkHistoryService;
use Origin\Service\Crm\Message\KakaoAlimTalkSettingService;
 
class LayerBizmKakaoAlimTalkHistoryResultController extends \Controller\Admin\Controller
{
public function index()
{
try {
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
$searchFormDTO = new BizmKakaoAlimTalkHistorySearchFormDTO($postData);
$searchFromArray = $searchFormDTO->toRecursiveArray();
$preparedData = $this->prepareRequestDataForDto($searchFromArray);
$requestDTO = new BizmKakaoAlimTalkHistoryListRequestDTO($preparedData);
 
/** @var KakaoAlimTalkSettingService $kakaoAlimTalkSettingService */
$kakaoAlimTalkSettingService = \App::getInstance(KakaoAlimTalkSettingService::class);
$kakaoAlimTalkConfig = $kakaoAlimTalkSettingService->getKakaoAlimTalkConfig();
$isEnabled = $kakaoAlimTalkConfig->getUseFlag() === 'y';
 
try {
/** @var KakaoAlimTalkHistoryService $kakaoAlimTalkHistoryService */
$kakaoAlimTalkHistoryService = \App::getInstance(KakaoAlimTalkHistoryService::class);
$bizmKakaoAlimTalkHistoryListResponseDTO = $kakaoAlimTalkHistoryService->getBizmKakaoAlimTalkHistoryList($requestDTO);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage(), ['requestDTO' => $requestDTO->toArray()]);
$bizmKakaoAlimTalkHistoryListResponseDTO = new BizmKakaoAlimTalkHistoryListResponseDTO(['totalCount' => 0, 'contents' => []], $requestDTO->getPage(), $requestDTO->getPageSize());
}
$page = $bizmKakaoAlimTalkHistoryListResponseDTO->getPage();
 
$this->setData('searchFormData', $searchFormDTO->toRecursiveArray());
$this->setData('excelSearchQuery', $requestDTO->toRecursiveArray());
$this->setData('excelMenu', ExcelGenerateMenu::ADMIN_BIZM_ALIMTALK_SEND_RESULT);
$this->setData('sort', KakaoAlimTalkHistoryService::SORT_LIST);
$this->setData('bizmKakaoAlimTalkHistoryList', $this->sanitizeHistoryList($bizmKakaoAlimTalkHistoryListResponseDTO->getBizmKakaoAlimTalkHistoryList()));
$this->setData('isEnabled', $isEnabled);
$this->setData('page', $page);
$this->getView()->setDefine('layout', 'layout_layer.php');
} catch (\Throwable $e) {
$this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
 
private function prepareRequestDataForDto(array $postData): array
{
$preparedData = $postData;
// TODO 정렬 기준 추가를 대비하여 분리
$sort = explode(' ', $preparedData['sort'] ?? 'reservedAt desc');
$preparedData['sortDescending'] = ($sort[1] === 'desc') ? 'true' : 'false';
 
unset($preparedData['triggerTypes']); // bizm 에서는 사용하지 않음
 
$allSendStatuses = array_map(fn($e) => $e->name, KakaoAlimTalkSendStatus::availableForSearch());
if (!empty($preparedData['sendStatuses']) && !array_diff($preparedData['sendStatuses'], $allSendStatuses) && !array_diff($allSendStatuses, $preparedData['sendStatuses'])) {
unset($preparedData['sendStatuses']);
}
 
if (empty($preparedData['searchType'])) {
unset($preparedData['searchType']);
}
 
if (empty($preparedData['keyword'])) {
unset($preparedData['keyword']);
}
 
return $preparedData;
}
 
/**
* DTO 리스트의 문자열 필드에 htmlspecialchars 처리
*
* @param BizmKakaoAlimTalkHistoryDTO[] $historyList
* @return BizmKakaoAlimTalkHistoryDTO[]
*/
private function sanitizeHistoryList(array $historyList): array
{
foreach ($historyList as $dto) {
$sendKey = $dto->getSendKey();
if (!empty($sendKey)) {
$dto->setSendKey(htmlspecialchars($sendKey, ENT_QUOTES, 'UTF-8'));
}
 
$content = $dto->getContent();
if (!empty($content)) {
$dto->setContent(htmlspecialchars($content, ENT_QUOTES, 'UTF-8'));
}
}
 
return $historyList;
}
}
 
변경 후LayerBizmKakaoAlimTalkHistoryResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerCloudKakaoAlimTalkHistoryResultController.php-118
변경 전LayerCloudKakaoAlimTalkHistoryResultController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\DTO\ApiClient\Commerce\Notification\CloudKakaoAlimTalkHistoryDTO;
use Origin\DTO\ApiClient\Commerce\Notification\CloudKakaoAlimTalkHistoryListRequestDTO;
use Origin\DTO\ApiClient\Commerce\Notification\CloudKakaoAlimTalkHistoryListResponseDTO;
use Origin\DTO\ApiClient\Commerce\Notification\CloudKakaoAlimTalkHistorySearchFormDTO;
use Origin\Enum\ApiClient\Excel\ExcelGenerateMenu;
use Origin\Enum\ApiClient\Notification\KakaoAlimTalkSendStatus;
use Origin\Enum\ApiClient\Notification\KakaoAlimTalkTriggerType;
use Origin\Service\Crm\Message\KakaoAlimTalkHistoryService;
use Origin\Service\Crm\Message\KakaoAlimTalkSettingService;
 
class LayerCloudKakaoAlimTalkHistoryResultController extends \Controller\Admin\Controller
{
public function index()
{
try {
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
$searchFormDTO = new CloudKakaoAlimTalkHistorySearchFormDTO($postData);
$searchFromArray = $searchFormDTO->toRecursiveArray();
$preparedData = $this->prepareRequestDataForDto($searchFromArray);
$requestDTO = new CloudKakaoAlimTalkHistoryListRequestDTO($preparedData);
 
/** @var KakaoAlimTalkSettingService $kakaoAlimTalkSettingService */
$kakaoAlimTalkSettingService = \App::getInstance(KakaoAlimTalkSettingService::class);
$kakaoAlimTalkConfig = $kakaoAlimTalkSettingService->getKakaoAlimTalkConfig();
$isEnabled = $kakaoAlimTalkConfig->getUseFlag() === 'y';
 
try {
/** @var KakaoAlimTalkHistoryService $kakaoAlimTalkHistoryService */
$kakaoAlimTalkHistoryService = \App::getInstance(KakaoAlimTalkHistoryService::class);
$cloudKakaoAlimTalkHistoryListResponseDTO = $kakaoAlimTalkHistoryService->getCloudKakaoAlimTalkHistoryList($requestDTO);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage(), ['requestDTO' => $requestDTO->toArray()]);
$cloudKakaoAlimTalkHistoryListResponseDTO = new CloudKakaoAlimTalkHistoryListResponseDTO(['totalCount' => 0, 'contents' => []], $requestDTO->getPage(), $requestDTO->getPageSize());
}
$page = $cloudKakaoAlimTalkHistoryListResponseDTO->getPage();
 
$this->setData('searchFormData', $searchFromArray);
$this->setData('excelSearchQuery', $requestDTO->toRecursiveArray());
$this->setData('excelMenu', ExcelGenerateMenu::ADMIN_ALIMTALK_SEND_RESULT);
$this->setData('sort', KakaoAlimTalkHistoryService::SORT_LIST);
$this->setData('cloudKakaoAlimTalkHistoryList', $this->sanitizeHistoryList($cloudKakaoAlimTalkHistoryListResponseDTO->getCloudKakaoAlimTalkHistoryList()));
$this->setData('isEnabled', $isEnabled);
$this->setData('page', $page);
$this->getView()->setDefine('layout', 'layout_layer.php');
} catch (\Throwable $e) {
$this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
 
/**
* DTO 리스트의 문자열 필드에 htmlspecialchars 처리
*
* @param CloudKakaoAlimTalkHistoryDTO[] $historyList
* @return CloudKakaoAlimTalkHistoryDTO[]
*/
private function sanitizeHistoryList(array $historyList): array
{
foreach ($historyList as $dto) {
$sendKey = $dto->getSendKey();
if (!empty($sendKey)) {
$dto->setSendKey(htmlspecialchars($sendKey, ENT_QUOTES, 'UTF-8'));
}
 
$content = $dto->getContent();
if (!empty($content)) {
$dto->setContent(htmlspecialchars($content, ENT_QUOTES, 'UTF-8'));
}
}
 
return $historyList;
}
 
private function prepareRequestDataForDto(array $postData): array
{
$preparedData = $postData;
// TODO 정렬 기준 추가를 대비하여 분리
$sort = explode(' ', $preparedData['sort'] ?? 'reservedAt desc');
$preparedData['sortDescending'] = ($sort[1] === 'desc') ? 'true' : 'false';
 
$allTriggerTypes = array_map(fn($e) => $e->name, KakaoAlimTalkTriggerType::availableForSearch());
if (!empty($preparedData['triggerTypes']) && !array_diff($preparedData['triggerTypes'], $allTriggerTypes) && !array_diff($allTriggerTypes, $preparedData['triggerTypes'])) {
unset($preparedData['triggerTypes']);
}
 
$allSendStatuses = array_map(fn($e) => $e->name, KakaoAlimTalkSendStatus::availableForSearch());
if (!empty($preparedData['sendStatuses']) && !array_diff($preparedData['sendStatuses'], $allSendStatuses) && !array_diff($allSendStatuses, $preparedData['sendStatuses'])) {
unset($preparedData['sendStatuses']);
}
 
if (empty($preparedData['searchType'])) {
unset($preparedData['searchType']);
}
 
if (empty($preparedData['keyword'])) {
unset($preparedData['keyword']);
}
 
return $preparedData;
}
}
 
변경 후LayerCloudKakaoAlimTalkHistoryResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerKakaoAlimTalkContentsController.php-84
변경 전LayerKakaoAlimTalkContentsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\DTO\ApiClient\Commerce\Notification\KakaoAlimTalkHistoryContentRequestDTO;
use Origin\DTO\ApiClient\Commerce\Notification\KakaoAlimTalkTemplateButtonDTO;
use Origin\Enum\ApiClient\Notification\KakaoAlimTalkTemplateButtonType;
use Origin\Enum\ApiClient\Notification\NotificationSendType;
use Origin\Service\Crm\Message\KakaoAlimTalkHistoryService;
 
class LayerKakaoAlimTalkContentsController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$sendGroupKey = $request->post()->get('sendGroupKey');
 
try {
$requestDTO = (new KakaoAlimTalkHistoryContentRequestDTO(['type' => NotificationSendType::ALIMTALK->name]));
/** @var KakaoAlimTalkHistoryService $kakaoAlimTalkHistoryService */
$kakaoAlimTalkHistoryService = \App::getInstance(KakaoAlimTalkHistoryService::class);
$kakaoAlimTalkTalkHistoryContentDTO = $kakaoAlimTalkHistoryService->getKakaoAlimTalkHistoryContent($sendGroupKey, $requestDTO);
} catch (\Throwable $e) {
$this->json(['success' => false], Response::HTTP_INTERNAL_SERVER_ERROR);
}
 
$templateButtons = $kakaoAlimTalkTalkHistoryContentDTO->getTemplateButtons();
$hasAddChannelButton = !empty(array_filter($templateButtons ?? [], fn($button) => $button->getType() === KakaoAlimTalkTemplateButtonType::ADD_CHANNEL));
$this->sortButtons($templateButtons);
 
$previewMessage = null;
if ($kakaoAlimTalkTalkHistoryContentDTO->isPendingOrRequestFailed()) {
$previewMessage = '발송 중이거나 발송 요청 단계에서 실패한 메시지는 본문 내용만 제공됩니다.';
} elseif (!$kakaoAlimTalkTalkHistoryContentDTO->isTemplateExists() || !empty($kakaoAlimTalkTalkHistoryContentDTO->getTemplateImageUrl())) {
$previewMessage = '상단 이미지는 현재 템플릿 기준으로 제공되며, 템플릿이 삭제된 경우 이미지를 표시할 수 없습니다.';
}
 
$this->setData('kakaoAlimTalkTalkHistoryContentDTO', $kakaoAlimTalkTalkHistoryContentDTO);
$this->setData('templateContent', $kakaoAlimTalkTalkHistoryContentDTO->getTemplateContent());
$this->setData('templateExtra', $kakaoAlimTalkTalkHistoryContentDTO->getTemplateExtra());
$this->setData('hasAddChannelButton', $hasAddChannelButton);
$this->setData('templateButtons', $templateButtons);
$this->setData('templateTitle', $kakaoAlimTalkTalkHistoryContentDTO->getTemplateTitle());
$this->setData('templateSubTitle', $kakaoAlimTalkTalkHistoryContentDTO->getTemplateSubTitle());
$this->setData('templateItemHighlight', $kakaoAlimTalkTalkHistoryContentDTO->getTemplateItemHighlight());
$this->setData('templateItems', $kakaoAlimTalkTalkHistoryContentDTO->getTemplateItems());
$this->setData('templateImageUrl', $kakaoAlimTalkTalkHistoryContentDTO->getTemplateImageUrl());
$this->setData('isTemplateExists', $kakaoAlimTalkTalkHistoryContentDTO->isTemplateExists());
$this->setData('previewMessage', $previewMessage);
$this->setData('isPendingOrRequestFailed', $kakaoAlimTalkTalkHistoryContentDTO->isPendingOrRequestFailed());
$this->setData('templateHeader', $kakaoAlimTalkTalkHistoryContentDTO->getTemplateHeader());
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
 
/**
* 버튼 유형에 따라 분리
*
* @param KakaoAlimTalkTemplateButtonDTO[] $templateButtons
* @return void
*/
protected function sortButtons(?array &$templateButtons): void
{
if (empty($templateButtons)) {
return;
}
 
usort($templateButtons, function ($a, $b) {
return ($a->getOrder() ?? 0) <=> ($b->getOrder() ?? 0);
});
}
}
 
변경 후LayerKakaoAlimTalkContentsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerKakaoAlimTalkHistorySearchController.php-49
변경 전LayerKakaoAlimTalkHistorySearchController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Enum\ExternalUrl;
use Framework\Utility\GodoUtils;
use Origin\Enum\ApiClient\Notification\KakaoAlimTalkResultSearchType;
use Origin\Enum\ApiClient\Notification\KakaoAlimTalkSendStatus;
use Origin\Enum\ApiClient\Notification\KakaoAlimTalkTriggerType;
use Origin\Enum\Crm\Message\KakaoAlimTalkSender;
use Origin\Service\Crm\Message\KakaoAlimTalkSettingService;
use Origin\Traits\Member\KakaoAlrim\KakaoAlrimCloudAvailable;
 
class LayerKakaoAlimTalkHistorySearchController extends \Controller\Admin\Controller
{
use KakaoAlrimCloudAvailable;
 
public function index()
{
$triggerTypes = KakaoAlimTalkTriggerType::availableForSearch();
$sendStatuses = KakaoAlimTalkSendStatus::availableForSearch();
$searchType = KakaoAlimTalkResultSearchType::generateSelectBoxData();
 
$isKakaoAlrimAvailable = !$this->isNewMall();
$isKakaoAlrimLunaInstalled = GodoUtils::isPlusShop(PLUSSHOP_CODE_KAKAOALRIMLUNA);
 
/** @var KakaoAlimTalkSettingService $kakaoAlimTalkSettingService */
$kakaoAlimTalkSettingService = \App::getInstance(KakaoAlimTalkSettingService::class);
$kakaoAlimTalkConfig = $kakaoAlimTalkSettingService->getKakaoAlimTalkConfig();
 
$isSelectedBizm = $kakaoAlimTalkConfig->getSender() === KakaoAlimTalkSender::BIZM;
 
$this->setData('searchType', $searchType);
$this->setData('isSelectedBizm', $isSelectedBizm);
$this->setData('triggerTypes', $triggerTypes);
$this->setData('sendStatuses', $sendStatuses);
$this->setData('isKakaoAlrimAvailable', $isKakaoAlrimAvailable);
$this->setData('isKakaoAlrimLunaInstalled', $isKakaoAlrimLunaInstalled);
$this->setData('blumnAiStatisticsUrl', ExternalUrl::BIZMSG_BLUMN_AI->getUrl('/statistics-alimtalk'));
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerKakaoAlimTalkHistorySearchController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerKakaoFriendTalkContentsController.php-56
변경 전LayerKakaoFriendTalkContentsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkType;
use Origin\Service\Crm\Message\KakaoFriendTalkHistoryService;
 
class LayerKakaoFriendTalkContentsController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$sendHistoryNo = $request->post()->get('sendHistoryNo');
 
try {
/** @var KakaoFriendTalkHistoryService $kakaoFriendTalkHistoryService */
$kakaoFriendTalkHistoryService = \App::getInstance(KakaoFriendTalkHistoryService::class);
$kakaoFriendTalkHistoryContentDTO = $kakaoFriendTalkHistoryService->getKakaoFriendTalkHistoryContent($sendHistoryNo);
} catch (\Throwable $e) {
$this->json(['success' => false], Response::HTTP_INTERNAL_SERVER_ERROR);
}
 
\Logger::channel('mobileMessage')->info('content', $kakaoFriendTalkHistoryContentDTO->getContent());
 
$this->setData('type', $kakaoFriendTalkHistoryContentDTO->getType());
$this->setData('isText', $kakaoFriendTalkHistoryContentDTO->getType() === KakaoFriendTalkType::TEXT);
$this->setData('isCarousel', $kakaoFriendTalkHistoryContentDTO->getType() === KakaoFriendTalkType::CAROUSEL);
$this->setData('isWideItemList', $kakaoFriendTalkHistoryContentDTO->getType() === KakaoFriendTalkType::WIDE_ITEM_LIST);
$this->setData('wideTypes', KakaoFriendTalkType::wideTypes());
$this->setData('availableImageTypes', KakaoFriendTalkType::availableImageTypes());
$this->setData('singleButtonTypes', KakaoFriendTalkType::singleButtonTypes());
$this->setData('doubleButtonTypes', KakaoFriendTalkType::doubleButtonTypes());
$this->setData('header', $kakaoFriendTalkHistoryContentDTO->getHeader());
$this->setData('imageUrl', $kakaoFriendTalkHistoryContentDTO->getImageUrl());
$this->setData('imageLink', $kakaoFriendTalkHistoryContentDTO->getImageLink());
$this->setData('items', $kakaoFriendTalkHistoryContentDTO->getItems());
$this->setData('content', $kakaoFriendTalkHistoryContentDTO->getContent());
$this->setData('buttons', $kakaoFriendTalkHistoryContentDTO->getButtons());
$this->setData('coupon', $kakaoFriendTalkHistoryContentDTO->getCoupon());
$this->setData('carousels', $kakaoFriendTalkHistoryContentDTO->getCarousels());
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerKakaoFriendTalkContentsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerKakaoFriendTalkHistoryResultController.php-121
변경 전LayerKakaoFriendTalkHistoryResultController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Origin\DTO\ApiClient\Commerce\Apps\KakaoFriendTalkHistoryDTO;
use Origin\DTO\ApiClient\Commerce\Apps\KakaoFriendTalkHistoryListRequestDTO;
use Origin\DTO\ApiClient\Commerce\Apps\KakaoFriendTalkHistoryListResponseDTO;
use Origin\DTO\ApiClient\Commerce\Apps\KakaoFriendTalkHistorySearchFormDTO;
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkResultDirection;
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkResultSortType;
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkScheduleType;
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkSendStatus;
use Origin\Enum\ApiClient\Excel\ExcelGenerateMenu;
use Origin\Service\Crm\Message\KakaoFriendTalkHistoryService;
use Origin\Service\Crm\Message\KakaoFriendTalkSettingService;
 
class LayerKakaoFriendTalkHistoryResultController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
$searchFormDTO = new KakaoFriendTalkHistorySearchFormDTO($postData);
$searchFormArray = $searchFormDTO->toRecursiveArray();
$preparedData = $this->prepareRequestDataForDto($searchFormArray);
$requestDTO = new KakaoFriendTalkHistoryListRequestDTO($preparedData);
 
/** @var KakaoFriendTalkSettingService $kakaoFriendTalkSettingService */
$kakaoFriendTalkSettingService = \App::getInstance(KakaoFriendTalkSettingService::class);
$kakaoFriendTalkConfig = $kakaoFriendTalkSettingService->getKakaoFriendTalkConfig();
$isEnabled = $kakaoFriendTalkConfig->getUseFlag() === 'y';
 
try {
/** @var KakaoFriendTalkHistoryService $kakaoFriendTalkHistoryService */
$kakaoFriendTalkHistoryService = \App::getInstance(KakaoFriendTalkHistoryService::class);
$kakaoFriendTalkHistoryListResponseDTO = $kakaoFriendTalkHistoryService->getKakaoFriendTalkHistoryList($requestDTO);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage(), ['requestDTO' => $requestDTO->toArray()]);
$kakaoFriendTalkHistoryListResponseDTO = new KakaoFriendTalkHistoryListResponseDTO(['totalCount' => 0, 'contents' => []], $requestDTO->getPage(), $requestDTO->getPageSize());
}
$page = $kakaoFriendTalkHistoryListResponseDTO->getPage();
 
$this->setData('searchFormData', $searchFormArray);
$this->setData('excelSearchQuery', $requestDTO->toRecursiveArray());
$this->setData('excelMenu', ExcelGenerateMenu::ADMIN_FRIENDTALK_SEND_RESULT);
$this->setData('sort', KakaoFriendTalkHistoryService::generateSortList());
$this->setData('kakaoFriendTalkHistoryList', $this->sanitizeHistoryList($kakaoFriendTalkHistoryListResponseDTO->getKakaoFriendTalkHistoryList()));
$this->setData('isEnabled', $isEnabled);
$this->setData('page', $page);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
 
/**
* DTO 리스트의 문자열 필드에 htmlspecialchars 처리
*
* @param KakaoFriendTalkHistoryDTO[] $historyList
* @return KakaoFriendTalkHistoryDTO[]
*/
private function sanitizeHistoryList(array $historyList): array
{
foreach ($historyList as $dto) {
$sendGroupKey = $dto->getSendGroupKey();
if (!empty($sendGroupKey)) {
$dto->setSendGroupKey(htmlspecialchars($sendGroupKey, ENT_QUOTES, 'UTF-8'));
}
 
$campaignName = $dto->getCampaignName();
if (!empty($campaignName)) {
$dto->setCampaignName(htmlspecialchars($campaignName, ENT_QUOTES, 'UTF-8'));
}
 
$snapshot = $dto->getSnapshot();
if (!empty($snapshot)) {
$dto->setSnapshot(htmlspecialchars($snapshot, ENT_QUOTES, 'UTF-8'));
}
}
 
return $historyList;
}
 
private function prepareRequestDataForDto(array $postData): array
{
$preparedData = $postData;
 
$sort = explode(' ', $preparedData['sort'] ?? (KakaoFriendTalkResultSortType::CREATED_DATE_TIME->name . ' ' . KakaoFriendTalkResultDirection::DESC->name));
$preparedData['sortType'] = (KakaoFriendTalkResultSortType::tryFromName($sort[0]) ?? KakaoFriendTalkResultSortType::CREATED_DATE_TIME)->name;
$preparedData['direction'] = (KakaoFriendTalkResultDirection::tryFromName(strtoupper($sort[1])) ?? KakaoFriendTalkResultDirection::DESC)->name;
 
$allScheduleTypes = array_map(fn($e) => $e->name, KakaoFriendTalkScheduleType::cases());
if (!empty($preparedData['scheduleTypes']) && !array_diff($preparedData['scheduleTypes'], $allScheduleTypes) && !array_diff($allScheduleTypes, $preparedData['scheduleTypes'])) {
unset($preparedData['scheduleTypes']);
}
 
$allSendStatuses = array_map(fn($e) => $e->name, KakaoFriendTalkSendStatus::availableForSearch());
if (!empty($preparedData['sendResultStatuses']) && !array_diff($preparedData['sendResultStatuses'], $allSendStatuses) && !array_diff($allSendStatuses, $preparedData['sendResultStatuses'])) {
unset($preparedData['sendResultStatuses']);
}
 
if (empty($preparedData['keyword'])) {
unset($preparedData['keyword']);
}
 
if (empty($preparedData['searchType'])) {
unset($preparedData['searchType']);
}
 
return $preparedData;
}
}
 
변경 후LayerKakaoFriendTalkHistoryResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerKakaoFriendTalkHistorySearchController.php-28
변경 전LayerKakaoFriendTalkHistorySearchController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkResultSearchType;
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkScheduleType;
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkSendStatus;
 
class LayerKakaoFriendTalkHistorySearchController extends \Controller\Admin\Controller
{
public function index()
{
$scheduleTypes = KakaoFriendTalkScheduleType::cases();
$sendResultStatuses = KakaoFriendTalkSendStatus::availableForSearch();
$searchType = KakaoFriendTalkResultSearchType::generateSelectBoxData();
 
$this->setData('searchType', $searchType);
$this->setData('scheduleTypes', $scheduleTypes);
$this->setData('sendResultStatuses', $sendResultStatuses);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerKakaoFriendTalkHistorySearchController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerLunaKakaoAlimTalkHistoryResultController.php-26
변경 전LayerLunaKakaoAlimTalkHistoryResultController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
 
class LayerLunaKakaoAlimTalkHistoryResultController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$naviMenu = $this->getData('naviMenu');
 
/** @var Request $request */
$request = \App::getInstance('request');
$requestData = $request->request()->toArray();
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerLunaKakaoAlimTalkHistoryResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMemberBizmKakaoAlimTalkHistoryResultController.php-115
변경 전LayerMemberBizmKakaoAlimTalkHistoryResultController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\DTO\ApiClient\Commerce\Notification\MemberBizmKakaoAlimTalkHistorySearchFormDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MemberBizmKakaoAlimTalkHistoryDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MemberBizmKakaoAlimTalkHistoryListRequestDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MemberBizmKakaoAlimTalkHistoryListResponseDTO;
use Origin\Enum\ApiClient\Notification\KakaoAlimTalkSendStatus;
use Origin\Service\Crm\Message\KakaoAlimTalkHistoryService;
use Origin\Service\Crm\Message\KakaoAlimTalkSettingService;
 
class LayerMemberBizmKakaoAlimTalkHistoryResultController extends \Controller\Admin\Controller
{
public function index()
{
try {
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
$searchFormDTO = new MemberBizmKakaoAlimTalkHistorySearchFormDTO($postData);
$searchFromArray = $searchFormDTO->toRecursiveArray();
$preparedData = $this->prepareRequestDataForDto($searchFromArray);
$requestDTO = new MemberBizmKakaoAlimTalkHistoryListRequestDTO($preparedData);
 
/** @var KakaoAlimTalkSettingService $kakaoAlimTalkSettingService */
$kakaoAlimTalkSettingService = \App::getInstance(KakaoAlimTalkSettingService::class);
$kakaoAlimTalkConfig = $kakaoAlimTalkSettingService->getKakaoAlimTalkConfig();
$isEnabled = $kakaoAlimTalkConfig->getUseFlag() === 'y';
 
try {
/** @var KakaoAlimTalkHistoryService $kakaoAlimTalkHistoryService */
$kakaoAlimTalkHistoryService = \App::getInstance(KakaoAlimTalkHistoryService::class);
$bizmKakaoAlimTalkHistoryListResponseDTO = $kakaoAlimTalkHistoryService->getMemberBizmKakaoAlimTalkHistoryList($requestDTO);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage(), ['requestDTO' => $requestDTO->toArray()]);
$bizmKakaoAlimTalkHistoryListResponseDTO = new MemberBizmKakaoAlimTalkHistoryListResponseDTO(['totalCount' => 0, 'contents' => []], $requestDTO->getPage(), $requestDTO->getPageSize());
}
$page = $bizmKakaoAlimTalkHistoryListResponseDTO->getPage();
 
$this->setData('searchFormData', $searchFormDTO->toRecursiveArray());
$this->setData('sort', KakaoAlimTalkHistoryService::SORT_LIST);
$this->setData('bizmKakaoAlimTalkHistoryList', $this->sanitizeHistoryList($bizmKakaoAlimTalkHistoryListResponseDTO->getBizmKakaoAlimTalkHistoryList()));
$this->setData('isEnabled', $isEnabled);
$this->setData('page', $page);
$this->getView()->setDefine('layout', 'layout_layer.php');
} catch (\Throwable $throwable) {
$this->json(['success' => false, 'message' => $throwable->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
 
private function prepareRequestDataForDto(array $postData): array
{
$preparedData = $postData;
// TODO 정렬 기준 추가를 대비하여 분리
$sort = explode(' ', $preparedData['sort'] ?? 'reservedAt desc');
$preparedData['sortDescending'] = ($sort[1] === 'desc') ? 'true' : 'false';
 
unset($preparedData['triggerTypes']); // bizm 에서는 사용하지 않음
 
$allSendStatuses = array_map(fn($e) => $e->name, KakaoAlimTalkSendStatus::availableForMemberHistorySearch());
if (
!empty($preparedData['sendStatuses']) &&
!array_diff($preparedData['sendStatuses'], $allSendStatuses) &&
!array_diff($allSendStatuses, $preparedData['sendStatuses'])
) {
unset($preparedData['sendStatuses']);
}
 
if (empty($preparedData['searchType'])) {
unset($preparedData['searchType']);
}
 
if (empty($preparedData['keyword'])) {
unset($preparedData['keyword']);
}
 
return $preparedData;
}
 
/**
* DTO 리스트의 문자열 필드에 htmlspecialchars 처리
*
* @param MemberBizmKakaoAlimTalkHistoryDTO[] $historyList
* @return MemberBizmKakaoAlimTalkHistoryDTO[]
*/
private function sanitizeHistoryList(array $historyList): array
{
foreach ($historyList as $dto) {
$sendKey = $dto->getSendKey();
if (!empty($sendKey)) {
$dto->setSendKey(htmlspecialchars($sendKey, ENT_QUOTES, 'UTF-8'));
}
 
$content = $dto->getContent();
if (!empty($content)) {
$dto->setContent(htmlspecialchars($content, ENT_QUOTES, 'UTF-8'));
}
}
 
return $historyList;
}
}
 
변경 후LayerMemberBizmKakaoAlimTalkHistoryResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMemberCloudKakaoAlimTalkHistoryResultController.php-119
변경 전LayerMemberCloudKakaoAlimTalkHistoryResultController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\DTO\ApiClient\Commerce\Notification\MemberCloudKakaoAlimTalkHistoryDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MemberCloudKakaoAlimTalkHistoryListRequestDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MemberCloudKakaoAlimTalkHistoryListResponseDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MemberCloudKakaoAlimTalkHistorySearchFormDTO;
use Origin\Enum\ApiClient\Notification\KakaoAlimTalkSendStatus;
use Origin\Enum\ApiClient\Notification\KakaoAlimTalkTriggerType;
use Origin\Service\Crm\Message\KakaoAlimTalkHistoryService;
use Origin\Service\Crm\Message\KakaoAlimTalkSettingService;
 
class LayerMemberCloudKakaoAlimTalkHistoryResultController extends \Controller\Admin\Controller
{
public function index()
{
try {
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
$searchFormDTO = new MemberCloudKakaoAlimTalkHistorySearchFormDTO($postData);
$searchFromArray = $searchFormDTO->toRecursiveArray();
$preparedData = $this->prepareRequestDataForDto($searchFromArray);
$requestDTO = new MemberCloudKakaoAlimTalkHistoryListRequestDTO($preparedData);
 
/** @var KakaoAlimTalkSettingService $kakaoAlimTalkSettingService */
$kakaoAlimTalkSettingService = \App::getInstance(KakaoAlimTalkSettingService::class);
$kakaoAlimTalkConfig = $kakaoAlimTalkSettingService->getKakaoAlimTalkConfig();
$isEnabled = $kakaoAlimTalkConfig->getUseFlag() === 'y';
 
try {
/** @var KakaoAlimTalkHistoryService $kakaoAlimTalkHistoryService */
$kakaoAlimTalkHistoryService = \App::getInstance(KakaoAlimTalkHistoryService::class);
$cloudKakaoAlimTalkHistoryListResponseDTO = $kakaoAlimTalkHistoryService->getMemberCloudKakaoAlimTalkHistoryList($requestDTO);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage(), ['requestDTO' => $requestDTO->toArray()]);
$cloudKakaoAlimTalkHistoryListResponseDTO = new MemberCloudKakaoAlimTalkHistoryListResponseDTO(['totalCount' => 0, 'contents' => []], $requestDTO->getPage(), $requestDTO->getPageSize());
}
$page = $cloudKakaoAlimTalkHistoryListResponseDTO->getPage();
 
$this->setData('searchFormData', $searchFromArray);
$this->setData('sort', KakaoAlimTalkHistoryService::SORT_LIST);
$this->setData('cloudKakaoAlimTalkHistoryList', $this->sanitizeHistoryList($cloudKakaoAlimTalkHistoryListResponseDTO->getMemberCloudKakaoAlimTalkHistoryList()));
$this->setData('isEnabled', $isEnabled);
$this->setData('page', $page);
$this->getView()->setDefine('layout', 'layout_layer.php');
} catch (\Throwable $throwable) {
$this->json(['success' => false, 'message' => $throwable->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
 
/**
* DTO 리스트의 문자열 필드에 htmlspecialchars 처리
*
* @param MemberCloudKakaoAlimTalkHistoryDTO[] $historyList
* @return MemberCloudKakaoAlimTalkHistoryDTO[]
*/
private function sanitizeHistoryList(array $historyList): array
{
foreach ($historyList as $dto) {
$sendKey = $dto->getSendKey();
if (!empty($sendKey)) {
$dto->setSendKey(htmlspecialchars($sendKey, ENT_QUOTES, 'UTF-8'));
}
 
$content = $dto->getContent();
if (!empty($content)) {
$dto->setContent(htmlspecialchars($content, ENT_QUOTES, 'UTF-8'));
}
}
 
return $historyList;
}
 
private function prepareRequestDataForDto(array $postData): array
{
$preparedData = $postData;
// TODO 정렬 기준 추가를 대비하여 분리
$sort = explode(' ', $preparedData['sort'] ?? 'reservedAt desc');
$preparedData['sortDescending'] = ($sort[1] === 'desc') ? 'true' : 'false';
 
$allTriggerTypes = array_map(fn($e) => $e->name, KakaoAlimTalkTriggerType::availableForSearch());
if (!empty($preparedData['triggerTypes']) && !array_diff($preparedData['triggerTypes'], $allTriggerTypes) && !array_diff($allTriggerTypes, $preparedData['triggerTypes'])) {
unset($preparedData['triggerTypes']);
}
 
$allSendStatuses = array_map(fn($e) => $e->name, KakaoAlimTalkSendStatus::availableForMemberHistorySearch());
if (
!empty($preparedData['sendStatuses']) &&
!array_diff($preparedData['sendStatuses'], $allSendStatuses) &&
!array_diff($allSendStatuses, $preparedData['sendStatuses'])
) {
unset($preparedData['sendStatuses']);
}
 
if (empty($preparedData['searchType'])) {
unset($preparedData['searchType']);
}
 
if (empty($preparedData['keyword'])) {
unset($preparedData['keyword']);
}
 
return $preparedData;
}
}
 
변경 후LayerMemberCloudKakaoAlimTalkHistoryResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMemberKakaoAlimTalkHistorySearchController.php-56
변경 전LayerMemberKakaoAlimTalkHistorySearchController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Enum\ExternalUrl;
use Framework\Http\Request;
use Framework\Utility\GodoUtils;
use Origin\Enum\ApiClient\Notification\KakaoAlimTalkResultSearchType;
use Origin\Enum\ApiClient\Notification\KakaoAlimTalkSendStatus;
use Origin\Enum\ApiClient\Notification\KakaoAlimTalkTriggerType;
use Origin\Enum\Crm\Message\KakaoAlimTalkSender;
use Origin\Service\Crm\Message\KakaoAlimTalkSettingService;
use Origin\Traits\Member\KakaoAlrim\KakaoAlrimCloudAvailable;
 
class LayerMemberKakaoAlimTalkHistorySearchController extends \Controller\Admin\Controller
{
use KakaoAlrimCloudAvailable;
 
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
$memberNo = filter_var($postData['memberNo'] ?? 0, FILTER_VALIDATE_INT) ?: 0;
 
$triggerTypes = KakaoAlimTalkTriggerType::availableForSearch();
$sendStatuses = KakaoAlimTalkSendStatus::availableForMemberHistorySearch();
$searchType = KakaoAlimTalkResultSearchType::generateSelectBoxData();
 
$isKakaoAlrimAvailable = !$this->isNewMall();
$isKakaoAlrimLunaInstalled = GodoUtils::isPlusShop(PLUSSHOP_CODE_KAKAOALRIMLUNA);
 
/** @var KakaoAlimTalkSettingService $kakaoAlimTalkSettingService */
$kakaoAlimTalkSettingService = \App::getInstance(KakaoAlimTalkSettingService::class);
$kakaoAlimTalkConfig = $kakaoAlimTalkSettingService->getKakaoAlimTalkConfig();
 
$isSelectedBizm = $kakaoAlimTalkConfig->getSender() === KakaoAlimTalkSender::BIZM;
 
$this->setData('memberNo', $memberNo);
$this->setData('searchType', $searchType);
$this->setData('isSelectedBizm', $isSelectedBizm);
$this->setData('triggerTypes', $triggerTypes);
$this->setData('sendStatuses', $sendStatuses);
$this->setData('isKakaoAlrimAvailable', $isKakaoAlrimAvailable);
$this->setData('isKakaoAlrimLunaInstalled', $isKakaoAlrimLunaInstalled);
$this->setData('blumnAiStatisticsUrl', ExternalUrl::BIZMSG_BLUMN_AI->getUrl('/statistics-alimtalk'));
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMemberKakaoAlimTalkHistorySearchController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMemberKakaoFriendTalkHistoryResultController.php-126
변경 전LayerMemberKakaoFriendTalkHistoryResultController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\DTO\ApiClient\Commerce\Apps\MemberKakaoFriendTalkHistoryDTO;
use Origin\DTO\ApiClient\Commerce\Apps\MemberKakaoFriendTalkHistoryListRequestDTO;
use Origin\DTO\ApiClient\Commerce\Apps\MemberKakaoFriendTalkHistoryListResponseDTO;
use Origin\DTO\ApiClient\Commerce\Apps\MemberKakaoFriendTalkHistorySearchFormDTO;
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkResultDirection;
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkResultSortType;
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkScheduleType;
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkSendStatus;
use Origin\Service\Crm\Message\KakaoFriendTalkHistoryService;
use Origin\Service\Crm\Message\KakaoFriendTalkSettingService;
 
class LayerMemberKakaoFriendTalkHistoryResultController extends \Controller\Admin\Controller
{
public function index()
{
try {
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
$searchFormDTO = new MemberKakaoFriendTalkHistorySearchFormDTO($postData);
$searchFormArray = $searchFormDTO->toRecursiveArray();
$preparedData = $this->prepareRequestDataForDto($searchFormArray);
$requestDTO = new MemberKakaoFriendTalkHistoryListRequestDTO($preparedData);
 
/** @var KakaoFriendTalkSettingService $kakaoFriendTalkSettingService */
$kakaoFriendTalkSettingService = \App::getInstance(KakaoFriendTalkSettingService::class);
$kakaoFriendTalkConfig = $kakaoFriendTalkSettingService->getKakaoFriendTalkConfig();
$isEnabled = $kakaoFriendTalkConfig->getUseFlag() === 'y';
 
try {
/** @var KakaoFriendTalkHistoryService $kakaoFriendTalkHistoryService */
$kakaoFriendTalkHistoryService = \App::getInstance(KakaoFriendTalkHistoryService::class);
$kakaoFriendTalkHistoryListResponseDTO = $kakaoFriendTalkHistoryService->getMemberKakaoFriendTalkHistoryList($requestDTO);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage(), ['requestDTO' => $requestDTO->toArray()]);
$kakaoFriendTalkHistoryListResponseDTO = new MemberKakaoFriendTalkHistoryListResponseDTO(['totalCount' => 0, 'contents' => []], $requestDTO->getPage(), $requestDTO->getPageSize());
}
$page = $kakaoFriendTalkHistoryListResponseDTO->getPage();
 
$this->setData('searchFormData', $searchFormArray);
$this->setData('sort', KakaoFriendTalkHistoryService::generateSortList());
$this->setData('kakaoFriendTalkHistoryList', $this->sanitizeHistoryList($kakaoFriendTalkHistoryListResponseDTO->getKakaoFriendTalkHistoryList()));
$this->setData('isEnabled', $isEnabled);
$this->setData('page', $page);
$this->getView()->setDefine('layout', 'layout_layer.php');
} catch (\Throwable $throwable) {
$this->json(['success' => false, 'message' => $throwable->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
 
/**
* DTO 리스트의 문자열 필드에 htmlspecialchars 처리
*
* @param MemberKakaoFriendTalkHistoryDTO[] $historyList
* @return MemberKakaoFriendTalkHistoryDTO[]
*/
private function sanitizeHistoryList(array $historyList): array
{
foreach ($historyList as $dto) {
$sendGroupKey = $dto->getSendGroupKey();
if (!empty($sendGroupKey)) {
$dto->setSendGroupKey(htmlspecialchars($sendGroupKey, ENT_QUOTES, 'UTF-8'));
}
 
$campaignName = $dto->getCampaignName();
if (!empty($campaignName)) {
$dto->setCampaignName(htmlspecialchars($campaignName, ENT_QUOTES, 'UTF-8'));
}
}
 
return $historyList;
}
 
private function prepareRequestDataForDto(array $postData): array
{
$preparedData = $postData;
 
// 정렬 기준 추가를 대비하여 분리
$sortType = explode(' ', $preparedData['sort'] ?? (KakaoFriendTalkResultSortType::CREATED_DATE_TIME->name . ' ' . KakaoFriendTalkResultDirection::DESC->name));
$preparedData['sortDescending'] = ($sortType[1] === KakaoFriendTalkResultDirection::DESC->name) ? 'true' : 'false';
 
$allScheduleTypes = array_map(fn($e) => $e->name, KakaoFriendTalkScheduleType::cases());
if (
!empty($preparedData['triggerTypes']) &&
!array_diff($preparedData['triggerTypes'], $allScheduleTypes) &&
!array_diff($allScheduleTypes, $preparedData['triggerTypes'])
) {
unset($preparedData['triggerTypes']);
}
 
$allSendStatuses = array_map(fn($e) => $e->name, KakaoFriendTalkSendStatus::availableForMemberHistorySearch());
if (
!empty($preparedData['sendStatuses']) &&
!array_diff($preparedData['sendStatuses'], $allSendStatuses) &&
!array_diff($allSendStatuses, $preparedData['sendStatuses'])
) {
unset($preparedData['sendStatuses']);
}
 
if (empty($preparedData['keyword'])) {
unset($preparedData['keyword']);
}
 
if (empty($preparedData['searchType'])) {
unset($preparedData['searchType']);
}
 
return $preparedData;
}
}
 
변경 후LayerMemberKakaoFriendTalkHistoryResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMemberKakaoFriendTalkHistorySearchController.php-35
변경 전LayerMemberKakaoFriendTalkHistorySearchController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkResultSearchType;
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkSendStatus;
use Origin\Enum\ApiClient\Apps\KakaoFriendTalkTriggerType;
 
class LayerMemberKakaoFriendTalkHistorySearchController extends \Controller\Admin\Controller
{
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
$memberNo = filter_var($postData['memberNo'] ?? 0, FILTER_VALIDATE_INT) ?: 0;
 
$triggerTypes = KakaoFriendTalkTriggerType::cases();
$sendStatuses = KakaoFriendTalkSendStatus::availableForMemberHistorySearch();
$searchType = KakaoFriendTalkResultSearchType::generateSelectBoxData();
 
$this->setData('memberNo', $memberNo);
$this->setData('searchType', $searchType);
$this->setData('triggerTypes', $triggerTypes);
$this->setData('sendStatuses', $sendStatuses);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMemberKakaoFriendTalkHistorySearchController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMemberSmsHistoryResultController.php-122
변경 전LayerMemberSmsHistoryResultController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\DTO\ApiClient\Commerce\Notification\MemberSmsHistoryDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MemberSmsHistoryListRequestDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MemberSmsHistoryListResponseDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MemberSmsHistorySearchFormDTO;
use Origin\Enum\ApiClient\Notification\SmsSendStatus;
use Origin\Enum\ApiClient\Notification\SmsTriggerType;
use Origin\Service\Crm\Message\SmsHistoryService;
 
class LayerMemberSmsHistoryResultController extends \Controller\Admin\Controller
{
public function index()
{
try {
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
$searchFormDTO = new MemberSmsHistorySearchFormDTO($postData);
$searchFormArray = $searchFormDTO->toRecursiveArray();
$preparedData = $this->prepareRequestDataForDto($searchFormArray);
$requestDTO = new MemberSmsHistoryListRequestDTO($preparedData);
 
try {
/** @var SmsHistoryService $smsHistoryService */
$smsHistoryService = \App::getInstance(SmsHistoryService::class);
$smsHistoryListResponseDTO = $smsHistoryService->getMemberSmsHistoryList($requestDTO);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage(), ['requestDTO' => $requestDTO]);
$smsHistoryListResponseDTO = new MemberSmsHistoryListResponseDTO(['totalCount' => 0, 'contents' => []], $requestDTO->getPage(), $requestDTO->getPageSize());
}
$page = $smsHistoryListResponseDTO->getPage();
 
$this->setData('searchFormData', $searchFormArray);
$this->setData('sortType', SmsHistoryService::SORT_LIST);
$this->setData('smsHistoryList', $this->sanitizeHistoryList($smsHistoryListResponseDTO->getMemberSmsHistoryList()));
$this->setData('page', $page);
$this->getView()->setDefine('layout', 'layout_layer.php');
} catch (\Throwable $throwable) {
$this->json(['success' => false, 'message' => $throwable->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
 
/**
* DTO 리스트의 문자열 필드에 htmlspecialchars 처리
*
* @param MemberSmsHistoryDTO[] $historyList
* @return MemberSmsHistoryDTO[]
*/
private function sanitizeHistoryList(array $historyList): array
{
foreach ($historyList as $dto) {
$sendKey = $dto->getSendKey();
if (!empty($sendKey)) {
$dto->setSendKey(htmlspecialchars($sendKey, ENT_QUOTES, 'UTF-8'));
}
 
$senderName = $dto->getSenderName();
if (!empty($senderName)) {
$dto->setSenderName(htmlspecialchars($senderName, ENT_QUOTES, 'UTF-8'));
}
 
$from = $dto->getFrom();
if (!empty($from)) {
$dto->setFrom(htmlspecialchars($from, ENT_QUOTES, 'UTF-8'));
}
 
$content = $dto->getContent();
if (!empty($content)) {
$dto->setContent(htmlspecialchars($content, ENT_QUOTES, 'UTF-8'));
}
}
 
return $historyList;
}
 
private function prepareRequestDataForDto(array $postData): array
{
$preparedData = $postData;
// TODO 정렬 기준 추가를 대비하여 분리
$sortType = explode(' ', $preparedData['sort'] ?? 'reservedAt desc');
$preparedData['sortDescending'] = ($sortType[1] === 'desc') ? 'true' : 'false';
 
$allTriggerTypes = array_map(fn($e) => $e->name, SmsTriggerType::cases());
if (!empty($preparedData['triggerTypes']) && !array_diff($preparedData['triggerTypes'], $allTriggerTypes) && !array_diff($allTriggerTypes, $preparedData['triggerTypes'])) {
unset($preparedData['triggerTypes']);
}
 
$allSendStatuses = array_map(fn($e) => $e->name, SmsSendStatus::availableForMemberHistorySearch());
if (
!empty($preparedData['sendStatuses']) &&
!array_diff($preparedData['sendStatuses'], $allSendStatuses) &&
!array_diff($allSendStatuses, $preparedData['sendStatuses'])
) {
unset($preparedData['sendStatuses']);
}
 
if (empty($preparedData['keyword'])) {
unset($preparedData['keyword']);
}
 
if (empty($preparedData['smsType'])) {
unset($preparedData['smsType']);
}
 
return $preparedData;
}
}
 
변경 후LayerMemberSmsHistoryResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMemberSmsHistorySearchController.php-38
변경 전LayerMemberSmsHistorySearchController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Origin\Enum\ApiClient\Notification\SmsResultSearchType;
use Origin\Enum\ApiClient\Notification\SmsSendStatus;
use Origin\Enum\ApiClient\Notification\SmsTriggerType;
use Origin\Enum\ApiClient\Notification\SmsType;
 
class LayerMemberSmsHistorySearchController extends \Controller\Admin\Controller
{
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
$triggerTypes = SmsTriggerType::cases();
$sendStatuses = SmsSendStatus::availableForMemberHistorySearch();
$smsTypes = SmsType::cases();
$memberNo = filter_var($postData['memberNo'] ?? 0, FILTER_VALIDATE_INT) ?: 0;
$searchType = SmsResultSearchType::generateSelectBoxData((bool) $memberNo);
 
$this->setData('memberNo', $memberNo);
$this->setData('triggerTypes', $triggerTypes);
$this->setData('smsTypes', $smsTypes);
$this->setData('sendStatuses', $sendStatuses);
$this->setData('searchType', $searchType);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMemberSmsHistorySearchController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMessageHistoryDetailController.php-36
변경 전LayerMessageHistoryDetailController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Origin\Enum\ApiClient\Notification\MessageResultType;
use Origin\Enum\Crm\Message\SendMethod;
 
class LayerMessageHistoryDetailController extends \Controller\Admin\Controller
{
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
$sendGroupKey = $postData['sendGroupKey'];
$type = $postData['type'];
 
$this->setData('sendGroupKey', $sendGroupKey);
$this->setData('type', $type);
if ($type === SendMethod::SMS->name) {
$this->setData('messageResultTypes', MessageResultType::cases());
} else {
$this->setData('messageResultTypes', MessageResultType::availableForSearch());
}
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMessageHistoryDetailController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMessageHistoryDetailResultController.php-52
변경 전LayerMessageHistoryDetailResultController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\DTO\ApiClient\Commerce\Notification\MessageHistoryDetailRequestDTO;
use Origin\DTO\ApiClient\Commerce\Notification\MessageHistoryDetailResponseDTO;
use Origin\Enum\ApiClient\Crm\WorkingType;
use Origin\Enum\ApiClient\Notification\MessageAccessLocationType;
use Origin\Service\Crm\Message\MessageHistoryService;
 
class LayerMessageHistoryDetailResultController extends \Controller\Admin\Controller
{
public function index()
{
try {
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
$requestDTO = (new MessageHistoryDetailRequestDTO($postData))
->setAccessLocation(MessageAccessLocationType::HISTORY)
->setWorkingType(WorkingType::tryFromName($postData['workingType'] ?? null) ?? WorkingType::VIEW);
try {
/** @var MessageHistoryService $messageHistoryService */
$messageHistoryService = \App::getInstance(MessageHistoryService::class);
$messageHistoryDetailResponseDTO = $messageHistoryService->getMessageHistoryDetail($requestDTO);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage(), ['requestDTO' => $requestDTO]);
$messageHistoryDetailResponseDTO = new MessageHistoryDetailResponseDTO(['totalCount' => 0, 'contents' => []], $requestDTO->getPage(), $requestDTO->getPageSize());
}
$page = $messageHistoryDetailResponseDTO->getPage();
 
$this->setData('postData', $postData);
$this->setData('messageHistoryDetailList', $messageHistoryDetailResponseDTO->getMessageHistoryDetailList());
$this->setData('page', $page);
$this->getView()->setDefine('layout', 'layout_layer.php');
} catch (\Throwable $e) {
$this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
}
 
변경 후LayerMessageHistoryDetailResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMessageHistoryExcelDownloadController.php-34
변경 전LayerMessageHistoryExcelDownloadController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Origin\Enum\ApiClient\Excel\ExcelGenerateMenu;
use Framework\Http\Request;
use Framework\Http\Response;
use Session;
 
class LayerMessageHistoryExcelDownloadController extends \Controller\Admin\Controller
{
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
if (Session::get('manager.functionAuthState') == 'check' && Session::get('manager.functionAuth.messageExcelDown') != 'y') {
$this->json(['success' => false, 'message' => '권한이 없습니다.<br>권한은 대표운영자에게 문의하시기 바랍니다.'], Response::HTTP_FORBIDDEN);
}
// 대응되는 es_code itemCd를 이용해 사유들 가져오기
$excelDownloadReasonList = ExcelGenerateMenu::fromName($request->post()->get('menu'))->getCode();
 
$this->setData('menu', $excelDownloadReasonList);
$this->setData('excelNo', $postData['excelNo']);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMessageHistoryExcelDownloadController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMessageHistoryExcelRequestController.php-38
변경 전LayerMessageHistoryExcelRequestController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\Enum\ApiClient\Excel\ExcelDownloadRange;
use Origin\Enum\ApiClient\Excel\ExcelGenerateMenu;
use Session;
 
class LayerMessageHistoryExcelRequestController extends \Controller\Admin\Controller
{
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post();
$menu = ExcelGenerateMenu::fromName($postData->get('type'));
$selectedSendKeys = $postData->get('selectedSendKeys', []);
$searchQuery = $postData->get('searchQuery', []);
 
if (Session::get('manager.functionAuthState') == 'check' && Session::get('manager.functionAuth.messageExcelDown') != 'y') {
$this->json(['success' => false, 'message' => '권한이 없습니다.<br>권한은 대표운영자에게 문의하시기 바랍니다.'], Response::HTTP_FORBIDDEN);
}
 
$this->setData('menu', $menu);
$this->setData('searchQuery', $searchQuery);
$this->setData('selectedSendKeys', $selectedSendKeys);
$this->setData('excelRequestDownloadRanges', ExcelDownloadRange::cases());
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMessageHistoryExcelRequestController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMessageHistoryExcelRequestPsController.php-79
변경 전LayerMessageHistoryExcelRequestPsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\Enum\ApiClient\Excel\ExcelDownloadRange;
use Origin\Enum\ApiClient\Excel\ExcelGenerateMenu;
use Origin\Service\Admin\Excel\CommerceExcelService;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Validator;
 
class LayerMessageHistoryExcelRequestPsController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
$mode = $postData['mode'];
 
switch ($mode) {
case 'generate':
try {
Validator::arrayVal()
->key('menu', Validator::stringType()->notEmpty()->in(array_map(fn($case) => $case->name, ExcelGenerateMenu::cases())))
->key('downloadRange', Validator::stringType()->notEmpty()->in(array_map(fn($case) => $case->name, ExcelDownloadRange::cases())))
->key('excelPassword', Validator::stringType()->notEmpty())
->assert($postData);
 
/** @var CommerceExcelService $commerceExcelService */
$commerceExcelService = \App::getInstance(CommerceExcelService::class);
$requestDTO = $commerceExcelService->prepareCommerceExcelGenerateRequestDTO($postData);
$commerceExcelService->requestExcelGenerate($requestDTO);
 
$this->json(['success' => true, 'message' => '다운로드할 엑셀 파일 생성이 완료되었습니다.<br />파일을 선택하여 다운로드하시기 바랍니다.'], Response::HTTP_OK);
} catch (ValidationException $e) {
\Logger::channel('excel')->error("엑셀 생성 요청 파라미터가 잘못 되었습니다.", [$e->getMessage(), __METHOD__]);
$this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_BAD_REQUEST);
} catch (\Throwable $e) {
\Logger::channel('excel')->error("엑셀 생성 요청에 실패 하였습니다.", [$e->getMessage(), __METHOD__]);
$this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
break;
case 'download':
try {
Validator::arrayVal()
->key('reason', Validator::stringType()->notEmpty())
->key('excelNo', Validator::stringType()->notEmpty())
->assert($postData);
 
$naviMenu = $this->getData('naviMenu');
 
/** @var CommerceExcelService $commerceExcelService */
$commerceExcelService = \App::getInstance(CommerceExcelService::class);
$requestDTO = $commerceExcelService->prepareCommerceExcelDownloadRequestDTO($postData['reason'], $naviMenu->location[0], $naviMenu->location[2]);
$commerceExcelService->downloadExcel($requestDTO, $postData['excelNo']);
} catch (ValidationException $e) {
\Logger::channel('excel')->error("엑셀 다운로드 파라미터가 잘못 되었습니다.", [$e->getMessage(), __METHOD__]);
$this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_BAD_REQUEST);
} catch (\Throwable $e) {
\Logger::channel('excel')->error("엑셀 다운로드에 실패 하였습니다.", [$e->getMessage(), __METHOD__]);
$this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
break;
default:
throw new \InvalidArgumentException('잘못된 접근입니다.');
}
}
}
 
변경 후LayerMessageHistoryExcelRequestPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMessageHistoryExcelRequestResultController.php-58
변경 전LayerMessageHistoryExcelRequestResultController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\DTO\Excel\CommerceExcelGenerateHistoryListResponseDTO;
use Origin\DTO\Excel\CommerceExcelGenerateHistoryRequestDTO;
use Origin\Enum\ApiClient\Excel\ExcelGenerateStatus;
use Origin\Service\Admin\Excel\CommerceExcelService;
 
class LayerMessageHistoryExcelRequestResultController extends \Controller\Admin\Controller
{
public function index()
{
try {
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
$requestData = [
'excelMenu' => $postData['menu'],
'page' => $postData['page'] ?? CommerceExcelGenerateHistoryRequestDTO::DEFAULT_PAGE,
'pageSize' => CommerceExcelGenerateHistoryRequestDTO::DEFAULT_PAGE_SIZE
];
$requestDTO = new CommerceExcelGenerateHistoryRequestDTO($requestData);
 
try {
/** @var CommerceExcelService $commerceExcelService */
$commerceExcelService = \App::getInstance(CommerceExcelService::class);
$excelGenerateHistoryListResponseDTO = $commerceExcelService->getExcelGenerateHistoryList($requestDTO);
} catch (\Throwable $e) {
\Logger::channel('excel')->error($e->getMessage(), ['requestDTO' => $requestDTO]);
$excelGenerateHistoryListResponseDTO = new CommerceExcelGenerateHistoryListResponseDTO(
['totalCount' => 0, 'contents' => []], $requestDTO->getPage(), $requestDTO->getPageSize()
);
}
$page = $excelGenerateHistoryListResponseDTO->getPage();
 
$this->setData('postData', $postData);
$this->setData('excelGenerateHistoryList', $excelGenerateHistoryListResponseDTO->getExcelGenerateHistoryList());
$this->setData('downloadableStatuses', ExcelGenerateStatus::getDownloadableStatuses());
$this->setData('page', $page);
$this->getView()->setDefine('layout', 'layout_layer.php');
} catch (\Throwable $e) {
$this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
}
 
변경 후LayerMessageHistoryExcelRequestResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMyappPushContentsController.php-43
변경 전LayerMyappPushContentsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Origin\Enum\ApiClient\Apps\MyappPushNotificationType;
use Origin\Service\Crm\Message\MyappPushHistoryService;
 
class LayerMyappPushContentsController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$pushNo = $request->post()->get('pushNo');
 
try {
/** @var MyappPushHistoryService $myappPushHistoryService */
$myappPushHistoryService = \App::getInstance(MyappPushHistoryService::class);
$myappPushHistoryContentDTO = $myappPushHistoryService->getMyappPushHistoryContent($pushNo);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage(), ['pushNo' => $pushNo]);
exit;
}
 
$this->setData('title', $myappPushHistoryContentDTO->getTitle());
$this->setData('content', $myappPushHistoryContentDTO->getContent());
$this->setData('unsubscribeGuide', $myappPushHistoryContentDTO->getUnsubscribeGuide());
$this->setData('imageUrl', $myappPushHistoryContentDTO->getImageUrl());
$this->setData('pushUrl', $myappPushHistoryContentDTO->getPushUrl());
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMyappPushContentsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMyappPushHistoryResultController.php-129
변경 전LayerMyappPushHistoryResultController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\DTO\ApiClient\Commerce\Apps\MyappPushHistoryDTO;
use Origin\DTO\ApiClient\Commerce\Apps\MyappPushHistoryListRequestDTO;
use Origin\DTO\ApiClient\Commerce\Apps\MyappPushHistoryListResponseDTO;
use Origin\DTO\ApiClient\Commerce\Apps\MyappPushHistorySearchFormDTO;
use Origin\Enum\ApiClient\Apps\MyappPushResultDirection;
use Origin\Enum\ApiClient\Apps\MyappPushResultSortType;
use Origin\Enum\ApiClient\Apps\MyappPushScheduleType;
use Origin\Enum\ApiClient\Apps\MyappPushSendStatus;
use Origin\Service\Crm\Message\MyappPushHistoryService;
use Origin\Service\Crm\Message\MyappPushSettingService;
 
class LayerMyappPushHistoryResultController extends \Controller\Admin\Controller
{
public function index()
{
try {
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
$searchFormDTO = new MyappPushHistorySearchFormDTO($postData);
$searchFormArray = $searchFormDTO->toRecursiveArray();
$preparedData = $this->prepareRequestDataForDto($searchFormArray);
$requestDTO = new MyappPushHistoryListRequestDTO($preparedData);
 
/** @var MyappPushSettingService $myappPushSettingService */
$myappPushSettingService = \App::getInstance(MyappPushSettingService::class);
$myappPushConfig = $myappPushSettingService->getMyappPushConfig();
$isEnabled = $myappPushConfig->getUseFlag() === 'y';
 
try {
/** @var MyappPushHistoryService $myappPushHistoryService */
$myappPushHistoryService = \App::getInstance(MyappPushHistoryService::class);
$myappPushHistoryListResponseDTO = $myappPushHistoryService->getMyappPushHistoryList($requestDTO);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage(), ['requestDTO' => $requestDTO->toArray()]);
$myappPushHistoryListResponseDTO = new MyappPushHistoryListResponseDTO(['totalCount' => 0, 'contents' => []], $requestDTO->getPage(), $requestDTO->getPageSize());
}
$page = $myappPushHistoryListResponseDTO->getPage();
 
$this->setData('searchFormData', $searchFormArray);
$this->setData('sort', MyappPushHistoryService::generateSortList());
$this->setData('myappPushHistoryList', $this->sanitizeHistoryList($myappPushHistoryListResponseDTO->getMyappPushHistoryList()));
$this->setData('isEnabled', $isEnabled);
$this->setData('page', $page);
$this->getView()->setDefine('layout', 'layout_layer.php');
} catch (\Throwable $e) {
$this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
 
/**
* DTO 리스트의 문자열 필드에 htmlspecialchars 처리
*
* @param MyappPushHistoryDTO[] $historyList
* @return MyappPushHistoryDTO[]
*/
private function sanitizeHistoryList(array $historyList): array
{
foreach ($historyList as $dto) {
$title = $dto->getTitle();
if (!empty($title)) {
$dto->setTitle(htmlspecialchars($title, ENT_QUOTES, 'UTF-8'));
}
 
$content = $dto->getContent();
if (!empty($content)) {
$dto->setContent(htmlspecialchars($content, ENT_QUOTES, 'UTF-8'));
}
 
$failReason = $dto->getFailReason();
if (!empty($failReason)) {
$dto->setFailReason(htmlspecialchars($failReason, ENT_QUOTES, 'UTF-8'));
}
}
 
return $historyList;
}
 
/**
* DTO 생성을 위한 요청 데이터 전처리
*
* @param array $postData
* @return array
*/
private function prepareRequestDataForDto(array $postData): array
{
$preparedData = $postData;
 
$sort = explode(' ', $preparedData['sort'] ?? (MyappPushResultSortType::SEND_DATE_TIME->name . ' ' . MyappPushResultDirection::DESC->name));
$preparedData['sortType'] = (MyappPushResultSortType::tryFromName($sort[0]) ?? MyappPushResultSortType::SEND_DATE_TIME)->name;
$preparedData['direction'] = (MyappPushResultDirection::tryFromName(strtoupper($sort[1])) ?? MyappPushResultDirection::DESC)->name;
 
$allScheduleTypes = array_map(fn($e) => $e->name, MyappPushScheduleType::cases());
if (!empty($preparedData['scheduleTypes']) && !array_diff($preparedData['scheduleTypes'], $allScheduleTypes) && !array_diff($allScheduleTypes, $preparedData['scheduleTypes'])) {
unset($preparedData['scheduleTypes']);
}
 
$allSendStatuses = array_map(fn($e) => $e->name, MyappPushSendStatus::availableForSearch());
if (!empty($preparedData['sendStatuses']) && !array_diff($preparedData['sendStatuses'], $allSendStatuses) && !array_diff($allSendStatuses, $preparedData['sendStatuses'])) {
unset($preparedData['sendStatuses']);
}
 
if (empty($preparedData['keyword'])) {
unset($preparedData['keyword']);
}
 
if (empty($preparedData['searchType'])) {
unset($preparedData['searchType']);
}
 
return $preparedData;
}
}
 
변경 후LayerMyappPushHistoryResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerMyappPushHistorySearchController.php-28
변경 전LayerMyappPushHistorySearchController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Origin\Enum\ApiClient\Apps\MyappPushScheduleType;
use Origin\Enum\ApiClient\Apps\MyappPushSendStatus;
use Origin\Enum\ApiClient\Notification\MyappPushResultSearchType;
 
class LayerMyappPushHistorySearchController extends \Controller\Admin\Controller
{
public function index()
{
$scheduleTypes = MyappPushScheduleType::cases();
$sendStatuses = MyappPushSendStatus::availableForSearch();
$searchType = MyappPushResultSearchType::generateSelectBoxData();
 
$this->setData('searchType', $searchType);
$this->setData('scheduleTypes', $scheduleTypes);
$this->setData('sendStatuses', $sendStatuses);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMyappPushHistorySearchController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerSmsContentsController.php-42
변경 전LayerSmsContentsController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\DTO\ApiClient\Commerce\Notification\SmsHistoryContentRequestDTO;
use Origin\Enum\ApiClient\Notification\NotificationSendType;
use Origin\Service\Crm\Message\SmsHistoryService;
 
class LayerSmsContentsController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$sendGroupKey = $request->post()->get('sendGroupKey');
 
try {
$requestDTO = (new SmsHistoryContentRequestDTO(['type' => NotificationSendType::SMS->name]));
/** @var SmsHistoryService $smsHistoryService */
$smsHistoryService = \App::getInstance(SmsHistoryService::class);
$smsHistoryContentDTO = $smsHistoryService->getSmsHistoryContent($sendGroupKey, $requestDTO);
} catch (\Throwable $e) {
$this->json(['success' => false], Response::HTTP_INTERNAL_SERVER_ERROR);
}
 
$this->setData('title', $smsHistoryContentDTO->getTitle());
$this->setData('content', $smsHistoryContentDTO->getContent());
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerSmsContentsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerSmsHistoryResultController.php-121
변경 전LayerSmsHistoryResultController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\DTO\ApiClient\Commerce\Notification\SmsHistoryDTO;
use Origin\DTO\ApiClient\Commerce\Notification\SmsHistoryListRequestDTO;
use Origin\DTO\ApiClient\Commerce\Notification\SmsHistoryListResponseDTO;
use Origin\DTO\ApiClient\Commerce\Notification\SmsHistorySearchFormDTO;
use Origin\Enum\ApiClient\Excel\ExcelGenerateMenu;
use Origin\Enum\ApiClient\Notification\SmsSendStatus;
use Origin\Enum\ApiClient\Notification\SmsTriggerType;
use Origin\Service\Crm\Message\SmsHistoryService;
 
class LayerSmsHistoryResultController extends \Controller\Admin\Controller
{
public function index()
{
try {
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
 
$searchFormDTO = new SmsHistorySearchFormDTO($postData);
$searchFormArray = $searchFormDTO->toRecursiveArray();
$preparedData = $this->prepareRequestDataForDto($searchFormArray);
$requestDTO = new SmsHistoryListRequestDTO($preparedData);
 
try {
/** @var SmsHistoryService $smsHistoryService */
$smsHistoryService = \App::getInstance(SmsHistoryService::class);
$smsHistoryListResponseDTO = $smsHistoryService->getSmsHistoryList($requestDTO);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage(), ['requestDTO' => $requestDTO]);
$smsHistoryListResponseDTO = new SmsHistoryListResponseDTO(['totalCount' => 0, 'contents' => []], $requestDTO->getPage(), $requestDTO->getPageSize());
}
$page = $smsHistoryListResponseDTO->getPage();
 
$this->setData('searchFormData', $searchFormArray);
$this->setData('excelSearchQuery', $requestDTO->toRecursiveArray());
$this->setData('excelMenu', ExcelGenerateMenu::ADMIN_SMS_SEND_RESULT);
$this->setData('sortType', SmsHistoryService::SORT_LIST);
$this->setData('smsHistoryList', $this->sanitizeHistoryList($smsHistoryListResponseDTO->getSmsHistoryList()));
$this->setData('page', $page);
$this->getView()->setDefine('layout', 'layout_layer.php');
} catch (\Throwable $e) {
$this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
 
/**
* DTO 리스트의 문자열 필드에 htmlspecialchars 처리
*
* @param SmsHistoryDTO[] $historyList
* @return SmsHistoryDTO[]
*/
private function sanitizeHistoryList(array $historyList): array
{
foreach ($historyList as $dto) {
$sendKey = $dto->getSendKey();
if (!empty($sendKey)) {
$dto->setSendKey(htmlspecialchars($sendKey, ENT_QUOTES, 'UTF-8'));
}
 
$senderName = $dto->getSenderName();
if (!empty($senderName)) {
$dto->setSenderName(htmlspecialchars($senderName, ENT_QUOTES, 'UTF-8'));
}
 
$from = $dto->getFrom();
if (!empty($from)) {
$dto->setFrom(htmlspecialchars($from, ENT_QUOTES, 'UTF-8'));
}
 
$content = $dto->getContent();
if (!empty($content)) {
$dto->setContent(htmlspecialchars($content, ENT_QUOTES, 'UTF-8'));
}
}
 
return $historyList;
}
 
private function prepareRequestDataForDto(array $postData): array
{
$preparedData = $postData;
// TODO 정렬 기준 추가를 대비하여 분리
$sortType = explode(' ', $preparedData['sort'] ?? 'reservedAt desc');
$preparedData['sortDescending'] = ($sortType[1] === 'desc') ? 'true' : 'false';
 
$allTriggerTypes = array_map(fn($e) => $e->name, SmsTriggerType::cases());
if (!empty($preparedData['triggerTypes']) && !array_diff($preparedData['triggerTypes'], $allTriggerTypes) && !array_diff($allTriggerTypes, $preparedData['triggerTypes'])) {
unset($preparedData['triggerTypes']);
}
 
$allSendStatuses = array_map(fn($e) => $e->name, SmsSendStatus::availableForSearch());
if (!empty($preparedData['sendStatuses']) && !array_diff($preparedData['sendStatuses'], $allSendStatuses) && !array_diff($allSendStatuses, $preparedData['sendStatuses'])) {
unset($preparedData['sendStatuses']);
}
 
if (empty($preparedData['keyword'])) {
unset($preparedData['keyword']);
}
 
if (empty($preparedData['smsType'])) {
unset($preparedData['smsType']);
}
 
return $preparedData;
}
}
 
변경 후LayerSmsHistoryResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerSmsHistoryResultPsController.php-63
변경 전LayerSmsHistoryResultPsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\DTO\ApiClient\Commerce\Notification\RemoveReservedSmsSendRequestDTO;
use Origin\Service\Crm\Message\SmsHistoryService;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Validator;
 
class LayerSmsHistoryResultPsController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageHistory', 'mobileHistoryList');
$this->setMenuCode('crm', 'messageHistory', 'mobileHistoryList');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
$mode = $postData['mode'];
 
switch ($mode) {
case 'cancel':
try {
Validator::arrayVal()
->key('sendGroupKey', Validator::stringType()->notEmpty())
->key('type', Validator::stringType()->notEmpty())
->key('cancelMode', Validator::stringType()->notEmpty())
->when(
Validator::key('cancelMode', Validator::equals('SELECT')),
Validator::key('ids', Validator::stringType()->notEmpty()),
Validator::alwaysValid()
)
->assert($postData);
 
$requestDTO = new RemoveReservedSmsSendRequestDTO($postData);
 
/** @var SmsHistoryService $smsHistoryService */
$smsHistoryService = \App::getInstance(SmsHistoryService::class);
$smsHistoryService->removeReservedSmsSend($postData['sendGroupKey'], $requestDTO);
 
$this->json(['success' => true, 'message' => '예약 발송이 취소되었습니다.'], Response::HTTP_OK);
} catch (ValidationException $e) {
\Logger::channel('mobileMessage')->error("예약 취소 요청 파라미터가 잘못 되었습니다.", [$e->getMessage(), __METHOD__]);
$this->json(['success' => false, 'message' => $e->getMessage(), 'data' => $postData], Response::HTTP_BAD_REQUEST);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error("예약 취소 요청에 실패 하였습니다.", [$e->getMessage(), __METHOD__]);
$this->json(['success' => false, 'message' => $e->getMessage(), 'data' => $postData], Response::HTTP_INTERNAL_SERVER_ERROR);
}
break;
default:
$this->json(['success' => false, 'message' => '잘못된 접근입니다.'], Response::HTTP_BAD_REQUEST);
}
}
}
 
변경 후LayerSmsHistoryResultPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileMessageHistory/LayerSmsHistorySearchController.php-31
변경 전LayerSmsHistorySearchController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileMessageHistory;
 
use Origin\Enum\ApiClient\Notification\SmsResultSearchType;
use Origin\Enum\ApiClient\Notification\SmsSendStatus;
use Origin\Enum\ApiClient\Notification\SmsTriggerType;
use Origin\Enum\ApiClient\Notification\SmsType;
 
class LayerSmsHistorySearchController extends \Controller\Admin\Controller
{
public function index()
{
$triggerTypes = SmsTriggerType::cases();
$sendStatuses = SmsSendStatus::availableForSearch();
$smsTypes = SmsType::cases();
$searchType = SmsResultSearchType::generateSelectBoxData();
 
$this->setData('triggerTypes', $triggerTypes);
$this->setData('smsTypes', $smsTypes);
$this->setData('sendStatuses', $sendStatuses);
$this->setData('searchType', $searchType);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerSmsHistorySearchController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSendController.php-39
변경 전MobileSendController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Component\Sms\Sms;
use Origin\Service\Crm\Message\MessageConfigService;
use Origin\Service\Crm\Message\SmsManageCallNumberService;
 
class MobileSendController extends \Controller\Admin\Controller
{
public function index()
{
$this->setNcdsSubBodyClass();
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
// 현재 SMS 포인트 싱크
Sms::saveSmsPoint();
 
/** @var SmsManageCallNumberService $smsManageCallNumberService */
$smsManageCallNumberService = \App::getInstance(SmsManageCallNumberService::class);
$hasCallNumber = $smsManageCallNumberService->hasUsableCallNumber();
 
/** @var MessageConfigService $messageConfigService */
$messageConfigService = \App::getInstance(MessageConfigService::class);
 
$this->setData('messageConfig', $messageConfigService->buildSendableMessageConfig($hasCallNumber));
$this->setData('smsPointEach', Sms::SMS_POINT);
$this->setData('lmsPointEach', Sms::LMS_POINT);
$this->setData('kakaoAlrimTalkPointEach', Sms::KAKAO_POINT);
$this->setData('kakaoFriendTalkPointEach', Sms::KAKAO_FRIEND_TALK_POINT);
$this->getView()->setPageName('crm/mobile_send.php');
}
}
 
변경 후MobileSendController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerKakaoAlimtalkContentSettingController.php-41
변경 전LayerKakaoAlimtalkContentSettingController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Framework\Utility\StringUtils;
use Origin\Enum\Crm\Message\KakaoAlimTalkTemplateType;
use Origin\Service\Crm\Message\Sms080RejectService;
use Origin\Service\Member\KakaoAlrim\KakaoAlrimCloudSettingService;
 
class LayerKakaoAlimtalkContentSettingController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
/* @var KakaoAlrimCloudSettingService $kakaoAlimCloudsSettingService*/
$kakaoAlimCloudsSettingService = \App::getInstance(KakaoAlrimCloudSettingService::class);
 
$kakaoAlimTalkTemplateTypes = KakaoAlimTalkTemplateType::cases();
$allowedKeys = array_map(fn($type) => strtolower($type->name), $kakaoAlimTalkTemplateTypes);
$kakaoAlimtalkTemplates = array_filter(
$kakaoAlimCloudsSettingService->getTemplates(),
fn($key) => in_array($key, $allowedKeys),
ARRAY_FILTER_USE_KEY
);
 
/** @var Sms080RejectService $sms080RejectService */
$sms080RejectService = \App::getInstance(Sms080RejectService::class);
$is080RejectAvailable = $sms080RejectService->isAvailable();
 
$this->setData('is080RejectAvailable', $is080RejectAvailable);
$this->setData('kakaoAlimTalkTemplateTypes', $kakaoAlimTalkTemplateTypes);
$this->setData('kakaoAlimtalkTemplates', $kakaoAlimtalkTemplates);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerKakaoAlimtalkContentSettingController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerKakaoFriendtalkContentSettingController.php-59
변경 전LayerKakaoFriendtalkContentSettingController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Bundle\Util\Crm\RecipeContextResolverTrait;
use Origin\Enum\Crm\Message\CampaignMessageType;
use Origin\Service\Crm\Message\KakaoFriendTalkSettingService;
use Origin\Service\Crm\Message\Sms080RejectService;
 
class LayerKakaoFriendtalkContentSettingController extends \Controller\Admin\Controller
{
use RecipeContextResolverTrait;
 
public function index()
{
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
/** @var KakaoFriendTalkSettingService $kakaoFriendTalkSettingService */
$kakaoFriendTalkSettingService = \App::getInstance(KakaoFriendTalkSettingService::class);
$kakaoFriendTalkConfig = $kakaoFriendTalkSettingService->getKakaoFriendTalkConfig();
 
/** @var Sms080RejectService $sms080RejectService */
$sms080RejectService = \App::getInstance(Sms080RejectService::class);
$is080RejectAvailable = $sms080RejectService->isAvailable();
 
$campaignMessageTypeUnitPoint = [];
foreach (CampaignMessageType::cases() as $type) {
$campaignMessageTypeUnitPoint[$type->name] = $type->unitPoint();
}
 
$recipeContext = $this->resolveRecipeContext();
$isRecipeContext = $this->hasRecipeContext();
$defaultCampaignRequest = $recipeContext['defaultCampaignRequest'] ?? [];
$campaignName = $recipeContext['recipeTitle'] ?? '';
$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('campaignMessageTypeUnitPoints', $campaignMessageTypeUnitPoint);
$this->setData('useSmsAlternativeSendFlag', $kakaoFriendTalkConfig->getSmsAlternativeSendFlag());
$this->setData('linkPlatformType', $kakaoFriendTalkConfig->getLinkPlatformType()->name);
$this->setData('isRecipeContext', $isRecipeContext);
$this->setData('campaignName', $campaignName);
$this->setData('friendtalkRequest', $friendtalkRequest);
$this->setData('initialTextContent', $initialTextContent);
$this->setData('initialAlternativeMessage', $initialAlternativeMessage);
$this->setData('initialAlternativeTitle', $initialAlternativeTitle);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerKakaoFriendtalkContentSettingController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerMessagePointController.php-36
변경 전LayerMessagePointController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Component\Sms\SmsUtil;
use Component\Sms\Sms;
 
class LayerMessagePointController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
// 현재 SMS 포인트 싱크
Sms::saveSmsPoint();
 
// 현재 메시지 포인트
$nowSmsPoint = (float) Sms::getPoint();
 
$availableCount = [
'sms' => SmsUtil::getAvailableCount('sms', $nowSmsPoint),
'lms' => SmsUtil::getAvailableCount('lms', $nowSmsPoint),
'kakaoAlrimTalk' => SmsUtil::getAvailableCount('kakaoAlrimTalk', $nowSmsPoint),
'kakaoFriendTalk' => SmsUtil::getAvailableCount('kakaoFriendTalk', $nowSmsPoint),
];
 
$this->setData('availableCount', $availableCount);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMessagePointController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerMobileSendMyappPushTemplateController.php-21
변경 전LayerMobileSendMyappPushTemplateController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Origin\Enum\Crm\Message\MyappPushTemplateCategory;
 
class LayerMobileSendMyappPushTemplateController extends \Controller\Admin\Controller
{
public function index()
{
$categories = MyappPushTemplateCategory::getFindOptions();
 
$this->setData('categories', $categories);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMobileSendMyappPushTemplateController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerMobileSendMyappPushTemplateResultController.php-48
변경 전LayerMobileSendMyappPushTemplateResultController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Component\Page\Page;
use Framework\StaticProxy\Proxy\UserFilePath;
use Origin\Enum\Sms\SmsContentsType;
use Origin\Repository\Member\Myapp\MyappPushRepository;
use Origin\Repository\Member\Sms\SmsContentsRepository;
use Request;
 
class LayerMobileSendMyappPushTemplateResultController extends \Controller\Admin\Controller
{
public function index()
{
$request = \App::getInstance('request');
$getParams = $request->get()->all();
 
$pageNum = 10;
$page = $getParams['page'] ?? 1;
$pushType = $getParams['pushType'] ?? '';
$keyword = $getParams['keyword'] ?? '';
$searchParams = ['keyword' => $keyword, 'templateFl'=> 'y', 'pushType' => $pushType];
 
$repository = \App::getInstance(MyappPushRepository::class);
$templates = $repository->findAllWithPagination($searchParams, $pageNum, ($page - 1) * $pageNum);
$totalCount = $repository->countAll($searchParams);
 
/** @var \Bundle\Component\Page\Page $pageComponent */
$pageComponent = \App::load('\\Component\\Page\\Page', $page);
$pageComponent->page['list'] = $pageNum;
$pageComponent->recode['amount'] = $totalCount;
$pageComponent->recode['total'] = $totalCount;
$pageComponent->setPage();
$pageComponent->setUrl(\Request::getQueryString());
 
$this->setData('templates', $templates);
$this->setData('page', $pageComponent);
$this->setData('requestData', $getParams);
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMobileSendMyappPushTemplateResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerMobileSendSaveMyappPushTemplateController.php-37
변경 전LayerMobileSendSaveMyappPushTemplateController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
 
use Origin\Enum\Crm\Message\MyappPushTemplateCategory;
use Request;
 
class LayerMobileSendSaveMyappPushTemplateController extends \Controller\Admin\Controller
{
public function index()
{
$request = Request::post()->toArray();
$pushSubject = $request['pushSubject'] ?? '';
$pushContent = $request['pushContent'] ?? '';
$pushImage = $request['pushImage'] ?? '';
$pushUrl = $request['pushUrl'] ?? '';
$pushWithdraw = $request['pushWithdraw'] ?? '';
 
$categories = MyappPushTemplateCategory::getFindOptions();
 
$this->setData('categories', $categories);
$this->setData('pushSubject', $pushSubject);
$this->setData('pushContent', $pushContent);
$this->setData('pushImage', $pushImage);
$this->setData('pushUrl', $pushUrl);
$this->setData('pushWithdraw', $pushWithdraw);
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMobileSendSaveMyappPushTemplateController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerMobileSendSaveSmsTemplateController.php-30
변경 전LayerMobileSendSaveSmsTemplateController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Origin\Enum\Crm\Message\SmsTemplateCategory;
use Request;
 
class LayerMobileSendSaveSmsTemplateController extends \Controller\Admin\Controller
{
public function index()
{
$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');
}
}
 
변경 후LayerMobileSendSaveSmsTemplateController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerMobileSendSmsTemplateController.php-22
변경 전LayerMobileSendSmsTemplateController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Origin\Enum\Crm\Message\SmsTemplateCategory;
 
class LayerMobileSendSmsTemplateController extends \Controller\Admin\Controller
{
public function index()
{
$categories = SmsTemplateCategory::getFindOptions();
 
$this->setData('categories', $categories);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
 
}
 
변경 후LayerMobileSendSmsTemplateController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerMobileSendSmsTemplateResultController.php-50
변경 전LayerMobileSendSmsTemplateResultController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Component\Page\Page;
use Framework\Http\Response;
use Origin\DTO\Crm\Message\SmsTemplateSearchDTO;
use Origin\Service\Crm\Message\SmsTemplateService;
 
class LayerMobileSendSmsTemplateResultController extends \Controller\Admin\Controller
{
public function index()
{
try {
$request = \App::getInstance('request');
$getParams = $request->get()->all();
 
/** @var SmsTemplateService $smsTemplateService */
$smsTemplateService = \App::getInstance(SmsTemplateService::class);
$dto = new SmsTemplateSearchDTO($getParams);
$result = $smsTemplateService->getSmsTemplateList($dto);
$templates = $result['templates'];
$totalCount = $result['totalCount'];
$pageNum = $result['pageNum'];
$page = $result['page'];
 
/** @var \Bundle\Component\Page\Page $pageComponent */
$pageComponent = \App::load('\\Component\\Page\\Page', $page);
$pageComponent->page['list'] = $pageNum;
$pageComponent->recode['amount'] = $totalCount;
$pageComponent->recode['total'] = $totalCount;
$pageComponent->setPage();
$pageComponent->setUrl(\Request::getQueryString());
 
$this->setData('templates', $templates);
$this->setData('page', $pageComponent);
$this->setData('requestData', $getParams);
 
$this->getView()->setDefine('layout', 'layout_layer.php');
} catch (\Throwable $e) {
$this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
}
 
변경 후LayerMobileSendSmsTemplateResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerMyappPushContentSettingController.php-55
변경 전LayerMyappPushContentSettingController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Bundle\Util\Crm\RecipeContextResolverTrait;
use Framework\Utility\StringUtils;
use Origin\Enum\Crm\Message\MyappPushNotificationType;
use Origin\Enum\Crm\Message\MyappPushSendCondition;
use Origin\Enum\Crm\Message\MyappPushSendPlatform;
use Origin\Service\Crm\Message\MyappPushTemplateService;
use Origin\Service\Crm\Message\Sms080RejectService;
 
class LayerMyappPushContentSettingController extends \Controller\Admin\Controller
{
use RecipeContextResolverTrait;
 
public function index()
{
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
/** @var Sms080RejectService $sms080RejectService */
$sms080RejectService = \App::getInstance(Sms080RejectService::class);
$is080RejectAvailable = $sms080RejectService->isAvailable();
 
/** @var MyappPushTemplateService $myappPushTemplateService */
$myappPushTemplateService = \App::getInstance(MyappPushTemplateService::class);
$myappPushUrl = $myappPushTemplateService->getMyappDomainUrl();
 
$recipeContext = $this->resolveRecipeContext();
$isRecipeContext = $this->hasRecipeContext();
$myappRequest = $recipeContext['defaultCampaignRequest']['myappRequest'] ?? [];
$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('myappPushNotificationTypes', MyappPushNotificationType::cases());
$this->setData('myappPushSendPlatforms', MyappPushSendPlatform::cases());
$this->setData('myappPushUrl', $myappPushUrl);
$this->setData('isRecipeContext', $isRecipeContext);
$this->setData('initialTitle', $initialTitle);
$this->setData('initialMessage', $initialMessage);
$this->setData('initialAlternativeMessage', $initialAlternativeMessage);
$this->setData('initialAlternativeTitle', $initialAlternativeTitle);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerMyappPushContentSettingController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerPreviewMessageController.php-19
변경 전LayerPreviewMessageController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
class LayerPreviewMessageController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerPreviewMessageController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerRecipeSendTimeSettingController.php-45
변경 전LayerRecipeSendTimeSettingController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Bundle\Util\Crm\RecipeContextResolverTrait;
use Origin\Enum\Common\WeekDay;
use Origin\Enum\Crm\Message\MessageRepeatCycle;
 
class LayerRecipeSendTimeSettingController extends \Controller\Admin\Controller
{
use RecipeContextResolverTrait;
 
public function index()
{
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
$sendSchedule = $this->resolveRecipeContext()['sendSchedule'] ?? [];
$sendTimeView = $this->buildSendTimeView($sendSchedule);
 
$this->setData('messageRepeatCycles', MessageRepeatCycle::cases());
$this->setData('sendTimeView', $sendTimeView);
$this->setData('weekDays', WeekDay::cases());
$this->getView()->setDefine('layout', 'layout_layer.php');
}
 
protected function buildSendTimeView(array $sendSchedule): array
{
$selectedRepeatType = $sendSchedule['repeatType'] ?? 'WEEKLY';
$repeatConfig = $sendSchedule['repeatConfig'] ?? [];
$sendTime = $repeatConfig['sendTime'] ?? '';
 
return [
'selectedRepeatType' => $selectedRepeatType,
'selectedRepeatHour' => $sendTime !== '' ? substr($sendTime, 0, 2) : '',
'selectedRepeatMinutes' => $sendTime !== '' ? substr($sendTime, 3, 2) : '',
'selectedRepeatDays' => $repeatConfig['repeatDays'] ?? [],
];
}
}
 
변경 후LayerRecipeSendTimeSettingController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerRecipientSettingController.php-35
변경 전LayerRecipientSettingController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Component\Sms\SmsUtil;
use Component\Sms\Sms;
use Framework\Http\Request;
use Origin\Enum\Crm\Message\RecipientType;
 
class LayerRecipientSettingController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
/** @var Request $request */
$request = \App::getInstance('request');
$selectedCrmGroup = $request->post()->get('selectedCrmGroup');
 
// PG 설정 불러오기
$pgConfig = gd_pgs();
$isConnectedPg = ($pgConfig['pgAutoSetting'] === 'y' || $pgConfig['pgApprovalSetting'] === 'y') ? 'y' : 'n';
 
$this->setData('recipientTypes', RecipientType::cases());
$this->setData('isConnectedPg', $isConnectedPg);
$this->setData('selectedCrmGroup', $selectedCrmGroup);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerRecipientSettingController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerRecipientSettingPsController.php-112
변경 전LayerRecipientSettingPsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Component\Crm\MobileMessage;
use Bundle\Component\Excel\Enum\ExcelSampleType;
use Bundle\Component\Excel\ExcelSample;
use Bundle\Component\Excel\ExcelSmsConvert;
use Bundle\Component\Sms\SmsExcelLog;
use Framework\Debug\Exception\AlertCloseException;
use Framework\Http\Request;
use Framework\Http\Response;
use Origin\DTO\ApiClient\Commerce\Crm\TargetsRepeatRulesRequestDTO;
use Origin\DTO\ApiClient\Commerce\Crm\TargetsRepeatRuleStatusRequestDTO;
use Origin\Service\ApiClient\Commerce\Crm\CrmAdminApiClientService;
 
class LayerRecipientSettingPsController extends \Controller\Admin\Controller
{
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$mode = $request->request()->get('mode');
 
switch ($mode) {
case 'getCountAll':
case 'getCountMembers':
try {
$getData = $request->get()->toArray();
$memberNos = json_decode($getData['selectedMemberNos']);
 
/* @var MobileMessage $mobileMessage*/
$mobileMessage = \App::getInstance(MobileMessage::class);
 
$counts = $mobileMessage->getReceiverCounts($memberNos, null);
$this->json(['success' => true, 'data' => $counts], Response::HTTP_OK);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage());
$this->json(['success' => false], Response::HTTP_INTERNAL_SERVER_ERROR);
}
break;
case 'getCountGroupMembers':
try {
$getData = $request->get()->toArray();
$groupSnos = json_decode($getData['selectedMemberGroups']);
 
/* @var MobileMessage $mobileMessage*/
$mobileMessage = \App::getInstance(MobileMessage::class);
 
$counts = $mobileMessage->getReceiverCounts(null, $groupSnos);
$this->json(['success' => true, 'data' => $counts], Response::HTTP_OK);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage());
$this->json(['success' => false], Response::HTTP_INTERNAL_SERVER_ERROR);
}
break;
case 'getCrmGroupStatus':
try {
$getData = $request->get()->toArray();
$requestDTO = new TargetsRepeatRuleStatusRequestDTO($getData);
 
/** @var $crmAdminApiClient CrmAdminApiClientService */
$crmAdminApiClient = \App::getInstance(CrmAdminApiClientService::class);
$targetsRepeatRuleStatusResponseDTO = $crmAdminApiClient->getNotificationTargetsRepeatRuleStatus($requestDTO, $getData['crmGroupNo']);
 
$this->json([
'success' => true,
'message' => 'CRN 그룹 개수 조회 성공',
'data' => $targetsRepeatRuleStatusResponseDTO->toRecursiveArray()
]);
} catch (\Throwable $e) {
\Logger::channel('mobileMessage')->error($e->getMessage(), ['requestDTO' => $requestDTO->toArray()]);
$this->json(['success' => false], Response::HTTP_INTERNAL_SERVER_ERROR);
}
break;
case 'downloadExcelSample':
(new ExcelSample(ExcelSampleType::SMS))->outputSample();
break;
case 'uploadExcelTarget':
$requestFiles = $request->files()->toArray();
$excelFile = $requestFiles['excel'] ?? null;
if (!$excelFile) {
$this->json(['success' => false, 'message' => '엑셀 파일이 없습니다.'], Response::HTTP_BAD_REQUEST);
break;
}
 
set_time_limit(RUN_TIME_LIMIT);
/* @var ExcelSmsConvert $excelSmsConvert */
$excelSmsConvert = \App::load('Component\\Excel\\ExcelSmsConvert');
/* @var SmsExcelLog $smsExcelLog */
$smsExcelLog = \App::load('Component\\Sms\\SmsExcelLog');
try {
$excelSmsConvert->upload();
$uploadKey = $excelSmsConvert->getUploadKey();
$counts = $smsExcelLog->countValidationLogByUploadKey($uploadKey);
$this->json(['success' => true, 'data' => ['uploadedExcelKey' => $uploadKey, 'successTargetCount' => $counts]], Response::HTTP_OK);
} catch (\Throwable $e) {
Logger::channel('mobileMessage')->error(__CLASS__ . ' 엑셀 업로드 처리 중 에러 발생 : ', ['error' => $e->getMessage()]);
$this->json(['success' => false, 'message' => '엑셀 파일 처리에 실패했습니다.'], Response::HTTP_INTERNAL_SERVER_ERROR);
}
break;
default:
throw new AlertCloseException(__("잘못된 접근입니다."));
}
}
}
 
변경 후LayerRecipientSettingPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerSendMethodSettingController.php-38
변경 전LayerSendMethodSettingController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Bundle\Util\Crm\RecipeContextResolverTrait;
use Origin\Enum\Crm\Message\SendMethod;
use Origin\Service\Crm\Message\Sms080RejectService;
 
class LayerSendMethodSettingController extends \Controller\Admin\Controller
{
use RecipeContextResolverTrait;
 
public function index()
{
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
/** @var Sms080RejectService $sms080RejectService */
$sms080RejectService = \App::getInstance(Sms080RejectService::class);
$smsRejectPolicy = $sms080RejectService->getConfig();
$smsRejectText = ($smsRejectPolicy['rejectNumber'] != '' && $smsRejectPolicy['status'] == 'O' && $smsRejectPolicy['use'] == 'y') ? "수신거부 {$smsRejectPolicy['rejectNumber']}" : '';
 
$recipeContext = $this->resolveRecipeContext();
$isRecipeContext = $this->hasRecipeContext();
$messageSendGuide = $recipeContext['guideMessages']['messageSendGuide'] ?? '';
 
$this->setData('sendMethods', SendMethod::cases());
$this->setData('smsRejectText', $smsRejectText);
$this->setData('isRecipeContext', $isRecipeContext);
$this->setData('messageSendGuide', $messageSendGuide);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerSendMethodSettingController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerSendTimeSettingController.php-26
변경 전LayerSendTimeSettingController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Origin\Enum\Common\WeekDay;
use Origin\Enum\Crm\Message\MessageRepeatCycle;
use Origin\Enum\Crm\Message\MessageSendType;
 
class LayerSendTimeSettingController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
$this->setData('messageSendTypes', MessageSendType::cases());
$this->setData('messageRepeatCycles', MessageRepeatCycle::cases());
$this->setData('weekDays', WeekDay::cases());
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerSendTimeSettingController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSend/LayerSmsContentSettingController.php-37
변경 전LayerSmsContentSettingController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MobileSend;
 
use Bundle\Util\Crm\RecipeContextResolverTrait;
use Origin\Service\Crm\Message\Sms080RejectService;
 
class LayerSmsContentSettingController extends \Controller\Admin\Controller
{
use RecipeContextResolverTrait;
 
public function index()
{
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
/** @var Sms080RejectService $sms080RejectService */
$sms080RejectService = \App::getInstance(Sms080RejectService::class);
$is080RejectAvailable = $sms080RejectService->isAvailable();
 
$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');
}
}
 
변경 후LayerSmsContentSettingController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MobileSendPsController.php-89
변경 전MobileSendPsController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Framework\Debug\Exception\AlertCloseException;
use Framework\Http\Request;
use GuzzleHttp\Exception\RequestException;
use Origin\Enum\ApiClient\Crm\RecipeType;
use Origin\Service\Crm\Message\MessageSendService;
use Origin\Service\Crm\Message\SmsPasswordService;
use Component\Crm\MobileMessage;
use Framework\Http\Response;
 
class MobileSendPsController extends \Controller\Admin\Controller
{
public function index()
{
$this->callMenu('crm', 'messageSend', 'mobileSend');
$this->setMenuCode('crm', 'messageSend', 'mobileSend');
 
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
$mode = $postData['mode'];
 
switch ($mode) {
case 'send':
try {
$requestData = json_decode($postData['payload'], true);
 
// CRM 레시피 발송은 레시피 메뉴 권한으로 인가
if (!empty($requestData['recipeType'])) {
if (RecipeType::tryFromSlug($requestData['recipeType']) === null) {
throw new AlertCloseException(__('잘못된 접근입니다.'));
}
$this->setMenuCode('crm', 'crm', 'crmRecipe');
}
 
// 수신대상 - 엑셀업로드, 업로드된 정보로 수신자 설정
if (($requestData['targetInfo']['targetType'] ?? null) === 'EXCEL') {
/* @var \Bundle\Component\Crm\MobileMessage $mobileMessage*/
$mobileMessage = \App::getInstance(MobileMessage::class);
 
$requestData = $mobileMessage->setExcelTargets($requestData);
}
 
/* @var MessageSendService $messageSendService*/
$messageSendService = \App::getInstance(MessageSendService::class);
$messageSendService->send($requestData);
$this->json(['success' => true]);
} catch (RequestException $e) {
if ($e->hasResponse()) {
$response = $e->getResponse();
if ($response->getStatusCode() === Response::HTTP_BAD_REQUEST) {
$contents = json_decode($response->getBody()->getContents(), true);
$this->json(['success' => false, 'message' => $contents['message']], Response::HTTP_BAD_REQUEST);
}
}
throw $e;
} catch (\Throwable $e) {
$this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
break;
case 'validateSendPassword':
try {
/** @var SmsPasswordService $smsPasswordService */
$smsPasswordService = \App::getInstance(SmsPasswordService::class);
$smsPassword = $smsPasswordService->getSmsPassword(true);
 
if ($postData['sendPassword'] === $smsPassword) {
$this->json(['success' => true]);
} else {
$this->json(['success' => false, 'message' => '설정 메뉴에 저장된 메시지 인증번호와 일치하지 않습니다.', 'data' => ['subMessage' => '설정 페이지에서 메시지 인증번호를 먼저 확인해주세요.']]);
}
} catch (\Throwable $e) {
$this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
break;
default:
throw new AlertCloseException(__("잘못된 접근입니다."));
}
}
}
 
변경 후MobileSendPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/Myapp/MyappPushTemplatePsController.php-76
변경 전MyappPushTemplatePsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\Myapp;
 
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateMyappDTO;
use Origin\Enum\Crm\Message\MyappPushTemplateCategory;
use Origin\Repository\Member\Myapp\MyappPushRepository;
use Origin\Service\Crm\Message\MyappPushTemplateService;
use Request;
 
class MyappPushTemplatePsController extends \Controller\Admin\Controller
{
public function index()
{
$request = Request::post()->toArray();
$mode = $request['mode'] ?? '';
 
if ($mode === 'save') {
$this->save($request);
} else {
$this->json(['result' => true, 'message' => '잘못된 요청입니다.']);
}
}
 
private function save(array $request): void
{
$categoryValue = $request['category'] ?? '';
$subject = $request['subject'] ?? '';
$contents = $request['contents'] ?? '';
$title = $request['title'] ?? '';
$url = $request['url'] ?? '';
$image = $request['image'] ?? '';
 
$category = MyappPushTemplateCategory::tryFrom($categoryValue);
 
if ($category === null) {
$this->json(['success' => false, 'message' => '카테고리를 선택해주세요.']);
}
 
if (empty($subject)) {
$this->json(['success' => false, 'message' => '제목을 입력해주세요.']);
}
 
if (empty($contents)) {
$this->json(['success' => false, 'message' => '내용을 입력해주세요.']);
}
 
try {
$repository = \App::getInstance(MyappPushRepository::class);
$sno = $repository->saveByGodomallTemplate($category, $subject, $contents, $title, $url, $image);
 
// 파일 업로드된 이미지가 있으면 서버에 저장
$files = Request::files()->toArray();
if (!empty($files['imageFile']['tmp_name']) && (int)$files['imageFile']['error'] === UPLOAD_ERR_OK) {
/** @var MyappPushTemplateService $templateService */
$templateService = \App::getInstance(MyappPushTemplateService::class);
$files['imageFile']['pushImage'] = $files['imageFile']['tmp_name'];
$templateService->processAndSaveImage(
$sno,
new MessageTemplateMyappDTO(['pushSno' => $sno]),
$files['imageFile']
);
}
 
$this->json(['success' => true, 'message' => '템플릿이 저장되었습니다.']);
} catch (\Exception $e) {
$this->json(['success' => false, 'message' => '저장에 실패했습니다.']);
}
}
}
 
변경 후MyappPushTemplatePsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/MyappPush/MyappPushTemplatePsController.php-72
변경 전MyappPushTemplatePsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\MyappPush;
 
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateMyappDTO;
use Origin\Enum\Crm\Message\MyappPushTemplateCategory;
use Origin\Service\Crm\Message\MyappPushTemplateService;
use Request;
 
class MyappPushTemplatePsController extends \Controller\Admin\Controller
{
public function index()
{
$request = Request::post()->toArray();
$mode = $request['mode'] ?? '';
 
$service = \App::getInstance(MyappPushTemplateService::class);
 
switch ($mode) {
case 'save':
$categoryValue = $request['category'] ?? '';
$category = MyappPushTemplateCategory::tryFrom($categoryValue);
if ($category === null) {
$this->json(['success' => false, 'message' => '카테고리를 선택해주세요.']);
return;
}
 
$templateName = trim($request['templateName'] ?? '');
if ($templateName === '') {
$this->json(['success' => false, 'message' => '템플릿명을 입력해주세요.']);
return;
}
 
$pushContent = trim($request['pushContent'] ?? '');
if ($pushContent === '') {
$this->json(['success' => false, 'message' => '푸시 내용을 입력해주세요.']);
return;
}
 
$pushSubject = trim($request['pushSubject'] ?? '');
 
$dto = new MessageTemplateMyappDTO([
'pushType' => $category->value,
'templateName' => $templateName,
'pushSubject' => $pushSubject,
'pushContent' => $pushContent,
'pushImage' => $request['pushImage'],
'pushUrl' => $request['pushUrl'],
'pushWithdraw' => $request['pushWithdraw'],
]);
 
$pushSno = $service->saveMyappTemplate($dto);
 
if ($pushSno == 0) {
$this->json(['success' => false, 'message' => '템플릿 저장에 실패하였습니다.']);
} else {
$this->json(['success' => true, 'data' => ['sno' => $pushSno], 'message' => '템플릿이 저장되었습니다.']);
}
 
return;
 
default:
$this->json(['error' => 'Invalid mode', 'data' => []]);
return;
}
}
}
 
변경 후MyappPushTemplatePsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/PopupChargeMessagePointsController.php-72
변경 전PopupChargeMessagePointsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Bundle\Component\Member\Manager;
use Bundle\Component\Sms\SmsUtil;
use Bundle\Component\Sms\SmsAdmin;
use Bundle\Component\Sms\Sms;
use Core\Base\Interceptor\AdminCertification;
use Framework\Debug\Exception\AlertRedirectException;
 
class PopupChargeMessagePointsController extends \Controller\Admin\Controller
{
public function index()
{
$this->setNcdsSubBodyClass();
$request = \App::getInstance('request');
$session = \App::getInstance('session');
$globals = \App::getInstance('globals');
 
$this->callMenu('crm', 'messageConfig', 'chargeMessagePoints');
 
if ($request->get()->get('popupMode', '') === 'yes') {
$this->getView()->setDefine('layout', 'layout_blank.php');
} elseif ($session->has(Manager::SESSION_TEMP_MANAGER)
&& $session->has(AdminCertification::SESSION_TEMP_CERTIFICATION)) {
if (strpos($request->getReferer(), '/base/login') === false) {
$session->del(Manager::SESSION_TEMP_MANAGER);
$session->del(AdminCertification::SESSION_TEMP_CERTIFICATION);
throw new AlertRedirectException('잘못된 경로로 접근되었습니다.', 403, null, '/base/login.php', 'top');
}
$this->getView()->setDefine('layout', 'layout_layer.php');
$this->setData('guestCharge', true);
}
 
$shopNo = $globals->get('gLicense.godosno');
 
// SMS 포인트 Sync
Sms::saveSmsPoint();
 
// 현재 메시지 포인트
$nowSmsPoint = (float) Sms::getPoint();
$availableCount = [
'sms' => SmsUtil::getAvailableCount('sms', $nowSmsPoint),
'lms' => SmsUtil::getAvailableCount('lms', $nowSmsPoint),
'kakaoAlrimTalk' => SmsUtil::getAvailableCount('kakaoAlrimTalk', $nowSmsPoint),
'kakaoFriendTalk' => SmsUtil::getAvailableCount('kakaoFriendTalk', $nowSmsPoint),
];
 
$smsAdmin = \App::load(SmsAdmin::class);
 
$webConfig = \App::getSystemConfig('internal')['commerce-web'];
$messagePointPayUrl = $webConfig['url'] . '/' . $webConfig['path']['message-point-pay-popup'];
 
$this->setData('lmsPoint', Sms::LMS_POINT);
$this->setData('kakaoPoint', Sms::KAKAO_POINT);
$this->setData('kakaoFriendTalkPoint', Sms::KAKAO_FRIEND_TALK_POINT);
$this->setData('kakaoFriendTalkImagePoint', Sms::KAKAO_FRIEND_TALK_IMAGE_POINT ?? 2.2);
$this->setData('kakaoFriendTalkWideItemPoint', Sms::KAKAO_FRIEND_TALK_WIDE_ITEM_POINT ?? 2.7);
$this->setData('availableCount', $availableCount);
$this->setData('smsPriceList', $smsAdmin->getSmsPriceList());
$this->setData('shopSno', $shopNo);
$this->setData('messagePointPayUrl', $messagePointPayUrl);
 
$this->getView()->setDefine('layout', 'layout_blank.php');
}
}
 
변경 후PopupChargeMessagePointsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/PopupSelectMembersController.php-71
변경 전PopupSelectMembersController.php
<?php
/*
* Copyright (C) 2025 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm;
 
use Component\Member\Group\Util;
use Component\Member\Member;
use Component\Member\Util\MemberUtil;
use Component\Page\Page;
use Framework\Http\Request;
 
class PopupSelectMembersController extends \Controller\Admin\Controller
{
public function index()
{
$this->setNcdsSubBodyClass();
$this->callMenu('crm', 'messageSend', 'mobileSend');
 
/** @var Request $request */
$request = \App::getInstance('request');
$this->setDefaultRequestParams($request);
 
$postData = $request->post()->toArray();
 
/* @var \Bundle\Component\Member\Member $memberComponent */
$memberComponent = \App::load(Member::class);
 
$groups = Util::getGroupName();
$checked = MemberUtil::checkedByMemberListSearch($postData);
$selected = MemberUtil::selectedByMemberListSearch($postData);
 
$this->setData('combineSearch', $memberComponent->getCombineSearchSelectBox());
$this->setData('searchKind', $memberComponent->getSearchKindASelectBox());
$this->setData('groups', $groups);
$this->setData('search', $postData);
$this->setData('checked', $checked);
$this->setData('selected', $selected);
 
// 필요
$this->setData('sendMode', $getParams['sendMode']);
 
$this->getView()->setDefine('layout', 'layout_blank.php');
$this->getView()->setPageName('crm/popup_select_members.php');
}
 
private function setDefaultRequestParams($request): void
{
$post = $request->post();
 
if (!$post->has('mallSno')) {
$post->set('mallSno', '');
}
if (!$post->get('sendMode', '') != 'mail') {
$post->set('mallSno', DEFAULT_MALL_NUMBER);
}
 
if (!$post->has('maillingFl')) {
$post->set('maillingFl', 'y');
}
if (!$post->has('smsFl')) {
$post->set('smsFl', 'y');
}
 
$post->set('sendMode', $request->get()->get('sendMode'));
}
}
 
변경 후PopupSelectMembersController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/PopupSelectMembers/LayerSelectedMembersResultController.php-114
변경 전LayerSelectedMembersResultController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\PopupSelectMembers;
 
use Bundle\Component\Page\Page;
use Component\Member\Group\Util;
use Component\Member\Member;
use Framework\Http\Request;
use Origin\Enum\ApiClient\Notification\SmsSendStatus;
use Origin\Enum\ApiClient\Notification\SmsTriggerType;
 
class LayerSelectedMembersResultController extends \Controller\Admin\Controller
{
const SORT_LIST = [
'entryDt desc' => '회원가입일 내림차순',
'entryDt asc' => '회원가입일 오름차순',
'lastLoginDt desc' => '최종로그인 내림차순',
'lastLoginDt asc' => '최종로그인 오름차순',
'loginCnt desc' => '방문횟수 내림차순',
'loginCnt asc' => '방문횟수 오름차순',
'memNm desc' => '이름 내림차순',
'memNm asc' => '이름 오름차순',
'memId desc' => '아이디 내림차순',
'memId asc' => '아이디 오름차순',
'mileage desc' => '마일리지 내림차순',
'mileage asc' => '마일리지 오름차순',
'saleAmt desc' => '주문금액 내림차순',
'saleAmt asc' => '주문금액 오름차순',
];
 
const DEFAULT_PAGE = 1;
const DEFAULT_PER_PAGE = 10;
 
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
$postData['sendMode'] = $request->request()->get('sendMode');
 
/* @var \Bundle\Component\Member\Member $memberComponent */
$memberComponent = \App::load(Member::class);
 
$members = [];
$searchCount = 0;
 
if (!empty($postData['selectedMemberNos'])) {
$getParams = [
'sendMode' => $postData['sendMode'],
'chk' => $postData['selectedMemberNos'],
'sort' => $postData['sort'],
'mallSno' => $postData['mallSno'] ?? '',
];
 
$page = $postData['page'] ?? 1;
$pageSize = $postData['pageSize'] ?? self::DEFAULT_PER_PAGE;
 
$members = $memberComponent->listsWithCoupon($getParams, $page, $pageSize);
$searchCount = $memberComponent->foundRowsByListsWithCoupon($getParams);
}
 
$page = new Page($postData['page'] ?? self::DEFAULT_PAGE, $searchCount, $searchCount, $postData['pageSize'] ?? self::DEFAULT_PER_PAGE);
 
$sysIcons = [
'payco' => ['icon' => PATH_ADMIN_GD_SHARE . 'ncds/image/payco_logo.png', 'name' => '페이코'],
'facebook' => ['icon' => PATH_ADMIN_GD_SHARE . 'ncds/image/facebook_logo.png', 'name' => '페이스북'],
'naver' => ['icon' => PATH_ADMIN_GD_SHARE . 'ncds/image/naver_logo.png', 'name' => '네이버'],
'kakao' => ['icon' => PATH_ADMIN_GD_SHARE . 'ncds/image/kakao_logo.png', 'name' => '카카오'],
'wonder' => ['icon' => PATH_ADMIN_GD_SHARE . 'ncds/image/wemakeprice_logo.png', 'name' => '위메프'],
'apple' => ['icon' => PATH_ADMIN_GD_SHARE . 'ncds/image/apple_logo.png', 'name' => '애플'],
'google' => ['icon' => PATH_ADMIN_GD_SHARE . 'ncds/image/google_logo.png', 'name' => '구글'],
];
 
$this->setData('selectedFormData', $postData);
$this->setData('snsIcons', $sysIcons);
$this->setData('sortType', self::SORT_LIST);
$this->setData('members', $members);
$this->setData('groups', Util::getGroupName());
$this->setData('page', $page);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
 
private function prepareRequestDataForDto(array $postData): array
{
$preparedData = $postData;
// TODO 정렬 기준 추가를 대비하여 분리
$sortType = explode(' ', $preparedData['sort'] ?? 'reservedAt desc');
$preparedData['sortDescending'] = ($sortType[1] === 'desc') ? 'true' : 'false';
 
if (!empty($preparedData['triggerTypes']) && empty(array_diff(array_map(fn($e) => $e->name, SmsTriggerType::cases()), $preparedData['triggerTypes']))) {
unset($preparedData['triggerTypes']);
}
 
if (!empty($preparedData['sendStatuses']) && empty(array_diff(array_map(fn($e) => $e->name, SmsSendStatus::availableForSearch()), $preparedData['sendStatuses']))) {
unset($preparedData['sendStatuses']);
}
 
if (empty($preparedData['keyword'])) {
unset($preparedData['keyword']);
}
 
if (empty($preparedData['smsType'])) {
unset($preparedData['smsType']);
}
 
return $preparedData;
}
}
 
변경 후LayerSelectedMembersResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/PopupSelectMembers/LayerSelectMembersResultController.php-73
변경 전LayerSelectMembersResultController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\PopupSelectMembers;
 
use Bundle\Component\Page\Page;
use Component\Member\Group\Util;
use Component\Member\Member;
use Framework\Http\Request;
 
class LayerSelectMembersResultController extends \Controller\Admin\Controller
{
const SORT_LIST = [
'entryDt desc' => '회원가입일 내림차순',
'entryDt asc' => '회원가입일 오름차순',
'lastLoginDt desc' => '최종로그인 내림차순',
'lastLoginDt asc' => '최종로그인 오름차순',
'loginCnt desc' => '방문횟수 내림차순',
'loginCnt asc' => '방문횟수 오름차순',
'memNm desc' => '이름 내림차순',
'memNm asc' => '이름 오름차순',
'memId desc' => '아이디 내림차순',
'memId asc' => '아이디 오름차순',
'mileage desc' => '마일리지 내림차순',
'mileage asc' => '마일리지 오름차순',
'saleAmt desc' => '주문금액 내림차순',
'saleAmt asc' => '주문금액 오름차순',
];
 
const DEFAULT_PAGE = 1;
const DEFAULT_PER_PAGE = 10;
 
public function index()
{
/** @var Request $request */
$request = \App::getInstance('request');
$postData = $request->post()->toArray();
$postData['sendMode'] = $request->request()->get('sendMode');
if (in_array($postData['key'], ['company', 'busiNo'])) $postData['searchKind'] = 'fullLikeSearch';
 
/* @var \Bundle\Component\Member\Member $memberComponent */
$memberComponent = \App::load(Member::class);
 
$members = $memberComponent->listsWithCoupon($postData, $postData['page'] ?? self::DEFAULT_PAGE, $postData['pageSize'] ?? self::DEFAULT_PER_PAGE);
$searchCount = $memberComponent->foundRowsByListsWithCoupon($postData);
$totalCount = $memberComponent->foundRowsByListsWithCoupon(['sendMode' => $postData['sendMode'], 'mallSno' => $postData['mallSno']]); // sendMode만 포함하여 전체 회원수 조회
 
$page = new Page($postData['page'] ?? self::DEFAULT_PAGE, $searchCount, $totalCount, $postData['pageSize'] ?? self::DEFAULT_PER_PAGE);
 
$sysIcons = [
'payco' => ['icon' => PATH_ADMIN_GD_SHARE . 'ncds/image/payco_logo.png', 'name' => '페이코'],
'facebook' => ['icon' => PATH_ADMIN_GD_SHARE . 'ncds/image/facebook_logo.png', 'name' => '페이스북'],
'naver' => ['icon' => PATH_ADMIN_GD_SHARE . 'ncds/image/naver_logo.png', 'name' => '네이버'],
'kakao' => ['icon' => PATH_ADMIN_GD_SHARE . 'ncds/image/kakao_logo.png', 'name' => '카카오'],
'wonder' => ['icon' => PATH_ADMIN_GD_SHARE . 'ncds/image/wemakeprice_logo.png', 'name' => '위메프'],
'apple' => ['icon' => PATH_ADMIN_GD_SHARE . 'ncds/image/apple_logo.png', 'name' => '애플'],
'google' => ['icon' => PATH_ADMIN_GD_SHARE . 'ncds/image/google_logo.png', 'name' => '구글'],
];
 
$this->setData('searchFormData', $postData);
$this->setData('snsIcons', $sysIcons);
$this->setData('sortType', self::SORT_LIST);
$this->setData('members', $members);
$this->setData('groups', Util::getGroupName());
$this->setData('page', $page);
$this->getView()->setDefine('layout', 'layout_layer.php');
}
}
 
변경 후LayerSelectMembersResultController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Crm/Sms/SmsTemplatePsController.php-69
변경 전SmsTemplatePsController.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Controller\Admin\Crm\Sms;
 
use Origin\DTO\ApiClient\Commerce\Notification\MessageTemplateSmsDTO;
use Origin\Enum\Crm\Message\SmsTemplateCategory;
use Origin\Repository\Member\Sms\SmsContentsRepository;
use Origin\Service\Crm\Message\SmsTemplateService;
use Request;
 
class SmsTemplatePsController extends \Controller\Admin\Controller
{
public function index()
{
$request = Request::post()->toArray();
$mode = $request['mode'] ?? '';
 
$service = \App::getInstance(SmsTemplateService::class);
 
switch ($mode) {
case 'save':
$categoryValue = $request['category'] ?? '';
$category = SmsTemplateCategory::tryFrom($categoryValue);
if ($category === null) {
$this->json(['success' => false, 'message' => '카테고리를 선택해주세요.']);
return;
}
 
$subject = trim($request['subject'] ?? '');
if ($subject === '') {
$this->json(['success' => false, 'message' => '제목을 입력해주세요.']);
return;
}
 
$contents = trim($request['contents'] ?? '');
if ($contents === '') {
$this->json(['success' => false, 'message' => '내용을 입력해주세요.']);
return;
}
 
$templateDto = MessageTemplateSmsDTO::fromRegisterRequest([
'templateCategory' => $category->value,
'templateTitle' => $subject,
'templateMessageContent' => $contents,
'templateMessageTitle' => trim($request['messageTitle'] ?? ''),
]);
 
$sno = $service->setSmsContents($templateDto);
 
if ($sno == 0) {
$this->json(['success' => false, 'message' => '템플릿 저장에 실패하였습니다.']);
} else {
$this->json(['success' => true, 'data' => ['sno' => $sno], 'message' => '템플릿이 저장되었습니다.']);
}
 
return;
 
default:
$this->json(['error' => 'Invalid mode', 'data' => []]);
return;
}
}
}
 
변경 후SmsTemplatePsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Member/SmsAutoController.php+4
변경 전SmsAutoController.php
use Framework\Utility\ComponentUtils;
use Framework\Utility\ConfigUrlUtils;
use Origin\Service\Crm\Message\SmsManageCallNumberService;
 
 
/**
* SMS 자동 발송 설정
 
$smsAdmin = \App::load('Component\\Sms\\SmsAdmin');
$smsAutoData = $smsAdmin->getSmsAutoData();
 
 
 
 
/** @var SmsManageCallNumberService $smsManageCallNumberService */
$smsManageCallNumberService = \App::getInstance(SmsManageCallNumberService::class);
 
변경 후SmsAutoController.php
use Framework\Utility\ComponentUtils;
use Framework\Utility\ConfigUrlUtils;
use Origin\Service\Crm\Message\SmsManageCallNumberService;
use Origin\Service\Crm\Message\SmsTemplateService;
 
/**
* SMS 자동 발송 설정
 
$smsAdmin = \App::load('Component\\Sms\\SmsAdmin');
$smsAutoData = $smsAdmin->getSmsAutoData();
/** @var SmsTemplateService $smsTemplateService */
$smsTemplateService = \App::getInstance(SmsTemplateService::class);
$smsAutoData = $smsTemplateService->attachLinkedAutoSendContents($smsAutoData, array_keys(Sms::SMS_AUTO_RECEIVE_LIST), \Bundle\Component\Sms\SmsAdmin::SMS_AUTO_TYPES);
 
/** @var SmsManageCallNumberService $smsManageCallNumberService */
$smsManageCallNumberService = \App::getInstance(SmsManageCallNumberService::class);
 
Controller/Admin/Member/SmsSendController.php+4-1
변경 전SmsSendController.php
use Framework\Utility\DateTimeUtils;
use Framework\Utility\GodoUtils;
use Framework\Utility\StringUtils;
 
 
use Origin\Service\Crm\Message\SmsManageCallNumberService;
use Origin\Service\Member\CrmGroupService;
 
$getSmsContentsByGoodsRestock = function () {
$db = \App::getInstance('DB');
$db->strField = '*';
$db->strWhere = 'smsType = \'user\' AND smsAutoCode LIKE CONCAT(\'01007501\', \'%\')';
 
$query = $db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_SMS_CONTENTS . ' ' . gd_implode(' ', $query);
$resultData = $db->query_fetch($strSQL);
 
변경 후SmsSendController.php
use Framework\Utility\DateTimeUtils;
use Framework\Utility\GodoUtils;
use Framework\Utility\StringUtils;
use Origin\Enum\Crm\Message\SmsTemplateApprovalStatus;
use Origin\Enum\Crm\Message\LegacyCallNumberStatus;
use Origin\Service\Crm\Message\SmsManageCallNumberService;
use Origin\Service\Member\CrmGroupService;
 
$getSmsContentsByGoodsRestock = function () {
$db = \App::getInstance('DB');
$db->strField = '*';
$db->strWhere = 'smsType = \'user\' AND smsAutoCode LIKE CONCAT(\'01007501\', \'%\')'
. ' AND approvalStatus = \'' . SmsTemplateApprovalStatus::APPROVED->value . '\'';
$query = $db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_SMS_CONTENTS . ' ' . gd_implode(' ', $query);
$resultData = $db->query_fetch($strSQL);
 
Controller/Admin/Member/SmsSendPsController.php+31-2
변경 전SmsSendPsController.php
use Bundle\Component\Excel\Enum\ExcelSampleType;
use Bundle\Component\Excel\ExcelSample;
use Framework\Utility\StringUtils;
 
 
 
/**
* Class SmsSendPsController
$db->strWhere = 'smsType = \'user\' AND smsAutoCode = ?';
$db->bind_param_push($binds, $fieldTypes['smsAutoCode'], $request->post()->get('smsAutoCode'));
}
 
 
if ($request->post()->get('searchWord', '') != '') {
$db->strWhere .= ' AND (subject LIKE CONCAT(\'%\', ?, \'%\') OR contents LIKE CONCAT(\'%\', ?, \'%\'))';
$db->bind_param_push($binds, $fieldTypes['subject'], $request->post()->get('searchWord'));
$db->strWhere = 'smsType = \'user\' AND smsAutoCode = ?';
$db->bind_param_push($binds, $fieldTypes['smsAutoCode'], $request->post()->get('smsAutoCode'));
}
 
 
$query = $db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_SMS_CONTENTS . ' ' . gd_implode(' ', $query);
$data = $db->query_fetch($strSQL, $binds, false);
$db->strWhere = 'smsType = \'user\' AND smsAutoCode = ?';
$db->bind_param_push($binds, $fieldTypes['smsAutoCode'], $request->post()->get('smsAutoCode'));
}
 
 
if ($request->post()->get('searchWord', '') != '') {
$db->strWhere .= ' AND (subject LIKE CONCAT(\'%\', ?, \'%\') OR contents LIKE CONCAT(\'%\', ? , \'%\'))';
$db->bind_param_push($binds, $fieldTypes['subject'], $request->post()->get('searchWord'));
$request = \App::getInstance('request');
$smsAdmin = \App::load('Component\\Sms\\SmsAdmin');
try {
 
 
 
 
 
$isUpdate = $request->post()->has('sno');
$smsAdmin->validateSmsContents($request->post(), $isUpdate);
if ($smsAdmin->saveSmsContentsData($request->post()->all())) {
case 'deleteSmsContents': // 선택 발송내역 삭제
$request = \App::getInstance('request');
$smsAdmin = \App::load('Component\\Sms\\SmsAdmin');
if ($smsAdmin->deleteSmsContentsData($request->post()->get('sno', []))) {
 
 
 
 
 
 
 
 
 
 
 
 
 
$this->json(
[
'result' => 'OK',
'message' => '삭제되었습니다.',
 
 
 
]
);
} else {
 
변경 후SmsSendPsController.php
use Bundle\Component\Excel\Enum\ExcelSampleType;
use Bundle\Component\Excel\ExcelSample;
use Framework\Utility\StringUtils;
use Origin\Enum\Crm\Message\SmsTemplateApprovalStatus;
use Origin\Service\Crm\Message\SmsTemplateService;
 
/**
* Class SmsSendPsController
$db->strWhere = 'smsType = \'user\' AND smsAutoCode = ?';
$db->bind_param_push($binds, $fieldTypes['smsAutoCode'], $request->post()->get('smsAutoCode'));
}
$db->strWhere .= ' AND approvalStatus = ?';
$db->bind_param_push($binds, 's', SmsTemplateApprovalStatus::APPROVED->value);
if ($request->post()->get('searchWord', '') != '') {
$db->strWhere .= ' AND (subject LIKE CONCAT(\'%\', ?, \'%\') OR contents LIKE CONCAT(\'%\', ?, \'%\'))';
$db->bind_param_push($binds, $fieldTypes['subject'], $request->post()->get('searchWord'));
$db->strWhere = 'smsType = \'user\' AND smsAutoCode = ?';
$db->bind_param_push($binds, $fieldTypes['smsAutoCode'], $request->post()->get('smsAutoCode'));
}
$db->strWhere .= ' AND approvalStatus = ?';
$db->bind_param_push($binds, 's', SmsTemplateApprovalStatus::APPROVED->value);
$query = $db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_SMS_CONTENTS . ' ' . gd_implode(' ', $query);
$data = $db->query_fetch($strSQL, $binds, false);
$db->strWhere = 'smsType = \'user\' AND smsAutoCode = ?';
$db->bind_param_push($binds, $fieldTypes['smsAutoCode'], $request->post()->get('smsAutoCode'));
}
$db->strWhere .= ' AND approvalStatus = ?';
$db->bind_param_push($binds, 's', SmsTemplateApprovalStatus::APPROVED->value);
if ($request->post()->get('searchWord', '') != '') {
$db->strWhere .= ' AND (subject LIKE CONCAT(\'%\', ?, \'%\') OR contents LIKE CONCAT(\'%\', ? , \'%\'))';
$db->bind_param_push($binds, $fieldTypes['subject'], $request->post()->get('searchWord'));
$request = \App::getInstance('request');
$smsAdmin = \App::load('Component\\Sms\\SmsAdmin');
try {
$restrictMessage = \App::getInstance(SmsTemplateService::class)->getWriteRestrictMessage();
if ($restrictMessage !== null) {
$this->json(['result' => 'FAIL', 'message' => $restrictMessage,]);
break;
}
$isUpdate = $request->post()->has('sno');
$smsAdmin->validateSmsContents($request->post(), $isUpdate);
if ($smsAdmin->saveSmsContentsData($request->post()->all())) {
case 'deleteSmsContents': // 선택 발송내역 삭제
$request = \App::getInstance('request');
$smsAdmin = \App::load('Component\\Sms\\SmsAdmin');
$smsTemplateService = \App::getInstance(SmsTemplateService::class);
$restrictMessage = $smsTemplateService->getWriteRestrictMessage();
if ($restrictMessage !== null) {
$this->json(['result' => 'FAIL', 'message' => $restrictMessage,]);
break;
}
$snoList = $request->post()->get('sno', []);
$excluded = $smsTemplateService->excludeUsedByAutoSend(is_array($snoList) ? $snoList : []);
$deletableSnoList = $smsTemplateService->filterLegacyDeletableSnoList($excluded['deletableSnoList']);
if (empty($deletableSnoList) && $excluded['mappedCount'] > 0) {
$this->json(['result' => 'FAIL', 'message' => SmsTemplateService::SMS_USED_NOT_DELETABLE_MESSAGE,]);
break;
}
if ($smsAdmin->deleteSmsContentsData($deletableSnoList)) {
$this->json(
[
'result' => 'OK',
'message' => $excluded['mappedCount'] > 0
? sprintf(SmsTemplateService::SMS_PARTIAL_DELETED_MESSAGE, $excluded['mappedCount'], count($deletableSnoList))
: '삭제되었습니다.',
'mappedCount' => $excluded['mappedCount'],
]
);
} else {
 
Controller/Admin/Policy/ManagePsController.php+3-1
변경 전ManagePsController.php
$this->setData('sno', $request->get()->get('sno'));
// 최고운영자/위임자면 발송담당자-SMS(messageSendSms) 체크 고정+잠금 (뷰에서 사용)
$this->setData('isMessageApprover', $messageOperatorRoleService->isSenderPermissionLocked((int)$request->get()->get('sno')));
$this->setData('isEditorMessageApprover', $messageOperatorRoleService->isApprover((int)$session->get('manager.sno')));
/** @var MessageOperatorRoleGuard $messageOperatorRoleGuard */
$messageOperatorRoleGuard = \App::getInstance(MessageOperatorRoleGuard::class);
$isEditorMessageRepresentative = $messageOperatorRoleGuard->isRepresentative();
$this->setData('isEditorMessageRepresentative', $isEditorMessageRepresentative);
 
// 차단 조회는 외부 API 호출이라 책임자 화면에서만 수행한다
$this->setData('isCallerBlocked', $isEditorMessageRepresentative && $messageOperatorRoleService->isBlockedRepresentative());
 
 
$this->setData('messageSenderCount', $messageOperatorRoleService->countSenders());
$this->setData('messageSenderLimit', MessageOperatorRoleService::MAX_SENDER_COUNT);
break;
 
변경 후ManagePsController.php
$this->setData('sno', $request->get()->get('sno'));
// 최고운영자/위임자면 발송담당자-SMS(messageSendSms) 체크 고정+잠금 (뷰에서 사용)
$this->setData('isMessageApprover', $messageOperatorRoleService->isSenderPermissionLocked((int)$request->get()->get('sno')));
 
/** @var MessageOperatorRoleGuard $messageOperatorRoleGuard */
$messageOperatorRoleGuard = \App::getInstance(MessageOperatorRoleGuard::class);
$isEditorMessageRepresentative = $messageOperatorRoleGuard->isRepresentative();
$this->setData('isEditorMessageRepresentative', $isEditorMessageRepresentative);
$this->setData('isEditorMessageApprover', $messageOperatorRoleGuard->isDelegateOrHigher());
// 차단 조회는 외부 API 호출이라 책임자 화면에서만 수행한다
$this->setData('isCallerBlocked', $isEditorMessageRepresentative && $messageOperatorRoleService->isBlockedRepresentative());
// 위임자 아님 안내가 우선이라 위임자 이상일 때만 판정한다 — isUncertifiedDelegateActor 는 위임자가 아닌 운영자도 DI 가 없으면 true 다
$this->setData('isCallerUncertifiedDelegate', $messageOperatorRoleGuard->isDelegateOrHigher() && $messageOperatorRoleService->isUncertifiedDelegateActor());
$this->setData('messageSenderCount', $messageOperatorRoleService->countSenders());
$this->setData('messageSenderLimit', MessageOperatorRoleService::MAX_SENDER_COUNT);
break;
 
Controller/Admin/Policy/ManageRegisterController.php+19-2
변경 전ManageRegisterController.php
$this->setData('messageDelegate', $messageDelegate);
 
// 발송 역할 계정(책임자·위임자·발송담당자)의 휴대폰번호는 본인인증(DI 주입)으로 처리한다
// 본인인증은 본인 계정에서만 성립하므로 책임자·제3자가 여는 화면은 기존 점유인증(SMS) UI 를 그대로 쓴다
$isSelfTarget = $isModify && (int)$session->get('manager.sno') === $managerNo;
// 최고운영자·발송 역할 계정은 정보 수정 주체를 책임자와 본인으로 제한한다
$isRestrictedEditTarget = $isModify
$isSelfCertificationProvided = $managerNo === ManagerService::SUPER_MANAGER_SNO
? $managerEditPolicy->isRepresentativeSelfCertificationProvided($managerNo)
: $isRestrictedEditTarget;
 
 
 
 
 
 
if ($isSelfTarget && $isSelfCertificationProvided) {
$selfCertification = [
'enabled' => true,
$this->addScript([
'manager-certification.js?' . time(),
]);
 
 
 
 
 
 
}
$this->setData('selfCertification', $selfCertification);
// 인증 방식 분기와 분리해 둔다 — 본인인증 화면이 본인 한정이 되어도 책임자·제3자 화면의 1차 차단은 남아야 한다
 
// 이름·휴대폰번호는 본인인증 명의라 직접 수정할 수 없다 — 발송 역할 계정 본인 화면과 책임자 본인 화면에서 잠근다
// 이름은 발송 역할 계정이면 책임자에게도 잠기고, 휴대폰번호는 책임자에게만 열린다
 
$this->setData('senderNameLocked', ($isModify && $managerEditPolicy->isSenderNameLocked($managerNo)) || $isRepresentativeSelf);
$this->setData('senderCellPhoneLocked', ($isModify && $managerEditPolicy->isSenderCellPhoneLocked($managerNo)) || $isRepresentativeSelf);
 
 
 
 
 
// --- 관리자 디자인 템플릿
$this->setData('isPasswordChangeRestricted', $isCompanySuperManager && $isMallInstalledAfterPatch);
 
변경 후ManageRegisterController.php
$this->setData('messageDelegate', $messageDelegate);
 
// 발송 역할 계정(책임자·위임자·발송담당자)의 휴대폰번호는 본인인증(DI 주입)으로 처리한다
// 본인인증은 본인 계정에서만 성립하므로 책임자가 여는 화면은 기존 점유인증(SMS) UI 를 그대로 쓴다
$isSelfTarget = $isModify && (int)$session->get('manager.sno') === $managerNo;
// 최고운영자·발송 역할 계정은 정보 수정 주체를 책임자와 본인으로 제한한다
$isRestrictedEditTarget = $isModify
$isSelfCertificationProvided = $managerNo === ManagerService::SUPER_MANAGER_SNO
? $managerEditPolicy->isRepresentativeSelfCertificationProvided($managerNo)
: $isRestrictedEditTarget;
// 책임자도 본인도 아닌 운영자에게도 발송 역할 계정의 인증 방식은 본인인증으로 노출한다 — 인증 스크립트는 싣지 않고 클릭은 본인 아님 안내로 끝난다
$isOtherEditorOfSenderRole = $isModify
&& $managerNo !== ManagerService::SUPER_MANAGER_SNO
&& $isRestrictedEditTarget
&& !$isSelfTarget
&& !$messageOperatorRoleGuard->isRepresentative();
if ($isSelfTarget && $isSelfCertificationProvided) {
$selfCertification = [
'enabled' => true,
$this->addScript([
'manager-certification.js?' . time(),
]);
} elseif ($isOtherEditorOfSenderRole) {
$selfCertification = [
'enabled' => true,
'certified' => $messageOperatorRoleGuard->isConnected($managerNo),
'isSelf' => false,
];
}
$this->setData('selfCertification', $selfCertification);
// 인증 방식 분기와 분리해 둔다 — 본인인증 화면이 본인 한정이 되어도 책임자·제3자 화면의 1차 차단은 남아야 한다
 
// 이름·휴대폰번호는 본인인증 명의라 직접 수정할 수 없다 — 발송 역할 계정 본인 화면과 책임자 본인 화면에서 잠근다
// 이름은 발송 역할 계정이면 책임자에게도 잠기고, 휴대폰번호는 책임자에게만 열린다
// 그 외 운영자 화면의 휴대폰 입력은 기획상 활성으로 노출한다 — 저장은 화면 1차 차단과 저장측 되돌림이 막는다
$this->setData('senderNameLocked', ($isModify && $managerEditPolicy->isSenderNameLocked($managerNo)) || $isRepresentativeSelf);
$this->setData(
'senderCellPhoneLocked',
!$isOtherEditorOfSenderRole
&& (($isModify && $managerEditPolicy->isSenderCellPhoneLocked($managerNo)) || $isRepresentativeSelf)
);
 
// --- 관리자 디자인 템플릿
$this->setData('isPasswordChangeRestricted', $isCompanySuperManager && $isMallInstalledAfterPatch);
 
Controller/Admin/Promotion/CartRemindPsController.php-77
변경 전CartRemindPsController.php
<?php
 
/**
* This is commercial software, only users who have purchased a valid license
* and accept to the terms of the License Agreement can install and use this
* program.
*
* Do not edit or add to this file if you wish to upgrade Godomall5 to newer
* versions in the future.
*
* @copyright ⓒ 2016, NHN godo: Corp.
* @link http://www.godo.co.kr
*/
namespace Bundle\Controller\Admin\Promotion;
 
use Component\CartRemind\CartRemind;
use Component\Sms\Exception\PasswordException;
use Framework\Debug\Exception;
use Framework\Debug\Exception\LayerNotReloadException;
use Message;
use Request;
 
class CartRemindPsController extends \Controller\Admin\Controller
{
 
/**
* 장바구니 알림 처리
* [관리자 모드] 장바구니 알림 처리
*
* @author su
*/
public function index()
{
try {
$cartRemind = new CartRemind();
switch (Request::post()->get('mode')) {
case 'insertCartRemind':
case 'modifyCartRemind':
// 모듈 호출
$postValue = Request::post()->toArray();
$cartRemind->setCartRemind($postValue);
$this->layer(__('저장이 완료되었습니다.'), 'parent.location.href="cart_remind_list.php";');
break;
case 'modifyCartRemindMessage':
$postValue = Request::post()->toArray();
$cartRemind->setCartRemindSendMessage($postValue);
$this->layer(__('수정되었습니다.'), 'parent.location.href="cart_remind_list.php";');
break;
case 'sendCartRemindManual':
$postValue = Request::post()->toArray();
try {
$result = $cartRemind->sendCartRemind($postValue);
} catch (PasswordException $e) {
$result['msg'] = $e->getMessage();
}
echo json_encode($result);
exit;
break;
case 'setCartRemindAutoState':
$postValue = Request::post()->toArray();
$cartRemind->setCartRemindAutoState($postValue);
$result['code'] = 99;
$result['msg'] = __('변경되었습니다.');
echo json_encode($result);
exit;
break;
case 'deleteCartRemind':
$postValue = Request::post()->toArray();
$cartRemind->deleteCartRemind($postValue);
$this->layer(__('삭제되었습니다.'), 'parent.location.href="cart_remind_list.php";');
break;
}
} catch (\Exception $e) {
throw new LayerNotReloadException($e->getMessage()); //새로고침안됨
}
}
}
 
변경 후CartRemindPsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Promotion/CouponPsController.php+8
변경 전CouponPsController.php
use Framework\File\FileValidator;
use Origin\DTO\ApiClient\Commerce\Crm\TargetsRepeatRuleLatestResultsRequestDTO;
use Origin\Service\ApiClient\Commerce\Crm\CrmAdminApiClientService;
 
use Request;
 
class CouponPsController extends \Controller\Admin\Controller
break;
case 'sendComebackCoupon':
$aSno = Request::post()->get('chkCoupon');
 
 
 
 
 
 
 
$couponAdmin->sendComebackCoupon($aSno[0]);
$this->layer(__('컴백쿠폰 발송이 완료되었습니다.'), 'top.location.href="comeback_coupon_list.php";');
break;
 
변경 후CouponPsController.php
use Framework\File\FileValidator;
use Origin\DTO\ApiClient\Commerce\Crm\TargetsRepeatRuleLatestResultsRequestDTO;
use Origin\Service\ApiClient\Commerce\Crm\CrmAdminApiClientService;
use Origin\Service\Sms\OccupancyAuth\SendGateService;
use Request;
 
class CouponPsController extends \Controller\Admin\Controller
break;
case 'sendComebackCoupon':
$aSno = Request::post()->get('chkCoupon');
$comebackCouponInfo = $couponAdmin->getComebackCouponInfo($aSno[0], 'smsFl');
if (($comebackCouponInfo['smsFl'] ?? '') === 'y') {
/** @var SendGateService $sendGateService */
$sendGateService = \App::getInstance(SendGateService::class);
$sendGateService->assertSendable();
$sendGateService->assertCallNumberUsable();
}
$couponAdmin->sendComebackCoupon($aSno[0]);
$this->layer(__('컴백쿠폰 발송이 완료되었습니다.'), 'top.location.href="comeback_coupon_list.php";');
break;
 
Controller/Admin/Share/LayerManagePermissionController.php+3-1
변경 전LayerManagePermissionController.php
$this->setData('disabled', $disabled);
$this->setData('sno', $request->post()->get('sno'));
$this->setData('isMessageApprover', $isMessageApprover);
$this->setData('isEditorMessageApprover', $messageOperatorRoleService->isApprover((int)$session->get('manager.sno')));
$this->setData('isEditorMessageRepresentative', $isEditorMessageRepresentative);
// 차단 조회는 외부 API 호출이라 책임자 화면에서만 수행한다
$this->setData('isCallerBlocked', $isEditorMessageRepresentative && $messageOperatorRoleService->isBlockedRepresentative());
 
 
$this->setData('messageSenderCount', $messageOperatorRoleService->countSenders());
$this->setData('messageSenderLimit', MessageOperatorRoleService::MAX_SENDER_COUNT);
$this->setData('scmFl', $scmFl);
 
변경 후LayerManagePermissionController.php
$this->setData('disabled', $disabled);
$this->setData('sno', $request->post()->get('sno'));
$this->setData('isMessageApprover', $isMessageApprover);
$this->setData('isEditorMessageApprover', $messageOperatorRoleGuard->isDelegateOrHigher());
$this->setData('isEditorMessageRepresentative', $isEditorMessageRepresentative);
// 차단 조회는 외부 API 호출이라 책임자 화면에서만 수행한다
$this->setData('isCallerBlocked', $isEditorMessageRepresentative && $messageOperatorRoleService->isBlockedRepresentative());
// 위임자 아님 안내가 우선이라 위임자 이상일 때만 판정한다 — isUncertifiedDelegateActor 는 위임자가 아닌 운영자도 DI 가 없으면 true 다
$this->setData('isCallerUncertifiedDelegate', $messageOperatorRoleGuard->isDelegateOrHigher() && $messageOperatorRoleService->isUncertifiedDelegateActor());
$this->setData('messageSenderCount', $messageOperatorRoleService->countSenders());
$this->setData('messageSenderLimit', MessageOperatorRoleService::MAX_SENDER_COUNT);
$this->setData('scmFl', $scmFl);
 
Controller/Admin/Share/LayerSmsContentsController.php-95
변경 전LayerSmsContentsController.php
<?php
 
/**
* This is commercial software, only users who have purchased a valid license
* and accept to the terms of the License Agreement can install and use this
* program.
*
* Do not edit or add to this file if you wish to upgrade Godomall5 to newer
* versions in the future.
*
* @copyright ⓒ 2016, NHN godo: Corp.
* @link http://www.godo.co.kr
*/
namespace Bundle\Controller\Admin\Share;
 
use Component\Sms\SmsAdmin;
use Request;
 
/**
* SMS 문구 선택하기
*
* @author Shin Donggyu <artherot@godo.co.kr>
*/
class LayerSmsContentsController extends \Controller\Admin\Controller
{
 
/**
* index
*
*/
public function index()
{
// --- SMS 모듈
$smsAdmin = new SmsAdmin();
 
// mode 가 있는 경우
if (Request::post()->has('mode')) {
// _POST 데이터
$postValue = Request::post()->toArray();
 
switch (Request::post()->get('mode')) {
// 등록 / 수정
case 'registerSmsContents':
case 'modifySmsContents':
$result = $smsAdmin->saveSmsContentsData($postValue);
if ($result === true) {
echo 'OK';
} else {
echo 'ERROR';
}
break;
 
// 선택 삭제
case 'deleteSmsContents':
$result = $smsAdmin->deleteSmsContentsData(Request::post()->get('delSno'));
if ($result === true) {
echo 'OK';
} else {
echo 'ERROR';
}
break;
}
exit();
}
 
// SMS 문구 그룹
$smsContentsGroup = gd_code('01007');
$smsContentsGroup = gd_array_merge(['0' => '문구 선택'], $smsContentsGroup);
 
// SMS 문구 리스트
$smsContentsList = $smsAdmin->getSmsContentsList('user');
 
// _GET Data
$getValue = Request::get()->toArray();
 
// page Url
$pageUrl = '../' . Request::getDirectoryUri() . '/' . Request::getFileUri();
 
//--- 관리자 디자인 템플릿
$this->getView()->setDefine('layout', 'layout_layer.php');
$this->getView()->setDefine('layoutContent', Request::getDirectoryUri() . '/' . Request::getFileUri());
 
$this->setData('layerFormID', $getValue['layerFormID']);
$this->setData('parentFormID', $getValue['parentFormID']);
$this->setData('dataFormID', $getValue['dataFormID']);
$this->setData('dataInputNm', $getValue['dataInputNm']);
$this->setData('mode', gd_isset($getValue['mode'], 'search'));
$this->setData('disabled', gd_isset($getValue['disabled'], ''));
$this->setData('callFunc', gd_isset($getValue['callFunc'], ''));
 
$this->setData('pageUrl', $pageUrl);
$this->setData('smsContentsGroup', $smsContentsGroup);
$this->setData($smsContentsList);
}
}
 
변경 후LayerSmsContentsController.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Controller/Admin/Share/LayerSmsPasswordPsController.php+35
변경 전LayerSmsPasswordPsController.php
use Component\Sms\Exception\PasswordException;
use Component\Sms\SmsSender;
use Framework\Utility\ComponentUtils;
 
 
 
 
class LayerSmsPasswordPsController extends \Controller\Admin\Controller
{
$request = \App::getInstance('request');
$isChangePassword = $request->post()->get('mode', 'changePassword') === 'changePassword';
if ($isChangePassword) {
 
 
 
 
 
$captchaNumber = $request->post()->get('captcha');
$captcha = new Captcha();
$result = $captcha->verify($captchaNumber, 1);
);
}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
변경 후LayerSmsPasswordPsController.php
use Component\Sms\Exception\PasswordException;
use Component\Sms\SmsSender;
use Framework\Utility\ComponentUtils;
use Origin\Enum\Crm\Message\MessageSendMenuAccessVerdict;
use Origin\Enum\Crm\Message\SmsSendRestrictType;
use Origin\Service\Crm\Message\MessageSendMenuAccessService;
 
class LayerSmsPasswordPsController extends \Controller\Admin\Controller
{
$request = \App::getInstance('request');
$isChangePassword = $request->post()->get('mode', 'changePassword') === 'changePassword';
if ($isChangePassword) {
$restrictMessage = $this->getSmsRestrictMessage();
if ($restrictMessage !== null) {
$this->json(['error' => 1, 'message' => $restrictMessage]);
}
 
$captchaNumber = $request->post()->get('captcha');
$captcha = new Captcha();
$result = $captcha->verify($captchaNumber, 1);
);
}
}
 
 
/**
* SMS 인증번호 변경은 SMS 관리자만 가능하다.
* ps 는 인터셉터 게이트를 타지 않아 화면 제한을 우회한 직접 호출이 가능하므로 조작 시점에 다시 본다.
* 판정축은 진입 게이트와 동일(역할·차단·IP·진입 점유인증). 책임자 미연동은 등록 안내 플로우라 차단하지 않는다.
*/
protected function getSmsRestrictMessage(): ?string
{
/** @var MessageSendMenuAccessService $menuAccessService */
$menuAccessService = \App::getInstance(MessageSendMenuAccessService::class);
$verdict = $menuAccessService->evaluateEntry((int)\App::getInstance('session')->get('manager.sno'));
 
$writableVerdicts = [
null,
MessageSendMenuAccessVerdict::NOT_CONNECTED_REPRESENTATIVE,
MessageSendMenuAccessVerdict::NOT_CONNECTED_MANAGER,
];
 
if (in_array($verdict, $writableVerdicts, true)) {
return null;
}
 
$restrictType = SmsSendRestrictType::fromVerdict($verdict);
 
return $restrictType->getClickAlertMessage() . ' ' . $restrictType->getClickAlertSubMessage();
}
}