id, 'presslearn-click-protection') === false) { return; } wp_register_style( 'presslearn-protection-admin-css', false, array(), PRESSLEARN_PLUGIN_VERSION ); wp_enqueue_style('presslearn-protection-admin-css'); $protection_css = " .presslearn-modal { position: fixed; z-index: 9999; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; } .presslearn-modal-content { background-color: #fff; width: 90%; max-width: 700px; border-radius: 6px; box-shadow: 0 3px 20px rgba(0, 0, 0, 0.2); display: flex; flex-direction: column; max-height: 90vh; } .presslearn-modal-header { padding: 20px; border-bottom: 1px solid #ddd; display: flex; justify-content: space-between; align-items: center; } .presslearn-modal-header h3 { margin: 0; font-size: 20px; } .presslearn-modal-close { font-size: 28px; font-weight: bold; cursor: pointer; color: #666; } .presslearn-modal-close:hover { color: #000; } .presslearn-modal-body { padding: 20px; overflow-y: auto; flex: 1; } .presslearn-modal-footer { padding: 15px 20px; border-top: 1px solid #ddd; text-align: right; display: flex; gap: 10px; justify-content: flex-end; } .settings-form-row { margin-bottom: 20px; } .settings-form-row label { display: block; margin-bottom: 5px; font-weight: bold; } .settings-form-row input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; } .modal-preview { margin-top: 30px; border-top: 1px solid #eee; padding-top: 20px; } .modal-preview h4 { margin-top: 0; margin-bottom: 15px; } .modal-preview-container { border: 1px dashed #ccc; padding: 20px; border-radius: 5px; } .ip-add-form { margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid #eee; } .ip-input-group { display: flex; gap: 10px; margin-bottom: 10px; } .ip-input-group input { flex: 1; } .ip-description { color: #666; font-size: 13px; margin: 5px 0 0; } .ip-list-table { width: 100%; border-collapse: collapse; } .ip-list-table th, .ip-list-table td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #eee; } .ip-list-table th { background-color: #f9f9f9; font-weight: 600; } .ip-list-table tr:hover { background-color: #f5f5f5; } .delete-ip, .delete-blocked-ip { color: #d32f2f; cursor: pointer; background: none; border: none; padding: 0; } .delete-ip:hover, .delete-blocked-ip:hover { text-decoration: underline; } .log-filter { display: flex; gap: 10px; margin-bottom: 20px; align-items: center; } .log-list-table { width: 100%; border-collapse: collapse; } .log-list-table th, .log-list-table td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #eee; } .log-list-table th { background-color: #f9f9f9; font-weight: 600; } .log-list-table tr:hover { background-color: #f5f5f5; } .spinner-container { display: flex; justify-content: center; align-items: center; height: 50px; } .log-expired { color: #999; text-decoration: line-through; } .log-active { color: #d32f2f; } "; wp_add_inline_style('presslearn-protection-admin-css', $protection_css); } /** * Protection admin scripts */ function presslearn_protection_admin_scripts() { if (!is_admin()) { return; } $screen = get_current_screen(); if (!$screen || strpos($screen->id, 'presslearn-click-protection') === false) { return; } wp_register_script( 'presslearn-protection-ip-management-js', false, array('jquery'), PRESSLEARN_PLUGIN_VERSION, true ); wp_enqueue_script('presslearn-protection-ip-management-js'); wp_localize_script('presslearn-protection-ip-management-js', 'presslearn_protection_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('presslearn_ip_nonce') )); $ip_management_js = " document.addEventListener('DOMContentLoaded', function() { let allowedIPs = []; let blockedIPs = []; const ipNonce = presslearn_protection_ajax.nonce; const ajaxUrl = presslearn_protection_ajax.ajax_url; const openAllowedBtn = document.getElementById('open-allowed-ip-editor'); const openBlockedBtn = document.getElementById('open-blocked-ip-editor'); if (openAllowedBtn) { openAllowedBtn.addEventListener('click', function() { loadAllowedIPs(); document.getElementById('allowed-ip-modal').style.display = 'flex'; }); } if (openBlockedBtn) { openBlockedBtn.addEventListener('click', function() { loadBlockedIPs(); document.getElementById('blocked-ip-modal').style.display = 'flex'; }); } document.addEventListener('click', function(e) { if (e.target.classList.contains('presslearn-modal-close') || e.target.id === 'close-modal-button') { document.getElementById('allowed-ip-modal').style.display = 'none'; } if (e.target.classList.contains('presslearn-modal-close') || e.target.id === 'close-blocked-modal-button') { document.getElementById('blocked-ip-modal').style.display = 'none'; } if (e.target.id === 'allowed-ip-modal') { document.getElementById('allowed-ip-modal').style.display = 'none'; } if (e.target.id === 'blocked-ip-modal') { document.getElementById('blocked-ip-modal').style.display = 'none'; } }); const addIpBtn = document.getElementById('add-ip-button'); const addBlockedIpBtn = document.getElementById('add-blocked-ip-button'); const newIpInput = document.getElementById('new-ip-address'); const newBlockedIpInput = document.getElementById('new-blocked-ip'); if (addIpBtn) { addIpBtn.addEventListener('click', addNewIP); } if (addBlockedIpBtn) { addBlockedIpBtn.addEventListener('click', addNewBlockedIP); } if (newIpInput) { newIpInput.addEventListener('keypress', function(e) { if (e.which === 13) { addNewIP(); e.preventDefault(); } }); } if (newBlockedIpInput) { newBlockedIpInput.addEventListener('keypress', function(e) { if (e.which === 13) { addNewBlockedIP(); e.preventDefault(); } }); } function loadAllowedIPs() { const tbody = document.getElementById('allowed-ip-list'); if (tbody) { tbody.innerHTML = '
'; } fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'presslearn_get_allowed_ips', nonce: ipNonce }) }) .then(response => response.json()) .then(data => { if (data.success) { allowedIPs = data.data.ips || []; renderIPTable(); } else { alert('허용 IP 목록을 불러오는데 실패했습니다.'); allowedIPs = []; renderIPTable(); } }) .catch(error => { alert('서버 통신 오류가 발생했습니다.'); allowedIPs = []; renderIPTable(); }); } function loadBlockedIPs() { const tbody = document.getElementById('blocked-ip-list'); if (tbody) { tbody.innerHTML = '
'; } fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'presslearn_get_blocked_ips', nonce: ipNonce }) }) .then(response => response.json()) .then(data => { if (data.success) { blockedIPs = data.data.ips || []; renderBlockedIPTable(); } else { alert('차단 IP 목록을 불러오는데 실패했습니다.'); blockedIPs = []; renderBlockedIPTable(); } }) .catch(error => { alert('서버 통신 오류가 발생했습니다.'); blockedIPs = []; renderBlockedIPTable(); }); } function renderIPTable() { const tbody = document.getElementById('allowed-ip-list'); const noIpsMessage = document.getElementById('no-ips-message'); if (!tbody) return; tbody.innerHTML = ''; if (allowedIPs.length > 0) { if (noIpsMessage) noIpsMessage.style.display = 'none'; allowedIPs.forEach((item) => { const row = document.createElement('tr'); row.innerHTML = ` \${item.ip} \${item.date} `; tbody.appendChild(row); }); document.querySelectorAll('.delete-ip').forEach(btn => { btn.addEventListener('click', function() { const ip = this.getAttribute('data-ip'); deleteIP(ip); }); }); } else { if (noIpsMessage) noIpsMessage.style.display = 'block'; } } function renderBlockedIPTable() { const tbody = document.getElementById('blocked-ip-list'); const noBlockedIpsMessage = document.getElementById('no-blocked-ips-message'); if (!tbody) return; tbody.innerHTML = ''; if (blockedIPs.length > 0) { if (noBlockedIpsMessage) noBlockedIpsMessage.style.display = 'none'; blockedIPs.forEach((item) => { const row = document.createElement('tr'); row.innerHTML = ` \${item.ip} \${item.date} `; tbody.appendChild(row); }); document.querySelectorAll('.delete-blocked-ip').forEach(btn => { btn.addEventListener('click', function() { const ip = this.getAttribute('data-ip'); deleteBlockedIP(ip); }); }); } else { if (noBlockedIpsMessage) noBlockedIpsMessage.style.display = 'block'; } } function addNewIP() { const newIpInput = document.getElementById('new-ip-address'); if (!newIpInput) return; const newIP = newIpInput.value.trim(); if (!isValidIP(newIP)) { alert('유효한 IP 주소를 입력해주세요.'); return; } fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'presslearn_add_allowed_ip', nonce: ipNonce, ip: newIP }) }) .then(response => response.json()) .then(data => { if (data.success) { alert('IP가 성공적으로 허용 목록에 추가되었습니다.'); allowedIPs = data.data.ips; renderIPTable(); newIpInput.value = ''; newIpInput.focus(); } else { alert(data.data.message || '허용 IP 추가에 실패했습니다.'); } }) .catch(error => { alert('서버 통신 오류가 발생했습니다.'); }); } function addNewBlockedIP() { const newBlockedIpInput = document.getElementById('new-blocked-ip'); if (!newBlockedIpInput) return; const newIP = newBlockedIpInput.value.trim(); if (!isValidIP(newIP)) { alert('유효한 IP 주소를 입력해주세요.'); return; } fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'presslearn_add_blocked_ip', nonce: ipNonce, ip: newIP }) }) .then(response => response.json()) .then(data => { if (data.success) { alert('IP가 성공적으로 차단되었습니다.'); blockedIPs = data.data.ips; renderBlockedIPTable(); newBlockedIpInput.value = ''; newBlockedIpInput.focus(); } else { alert(data.data.message || '차단 IP 추가에 실패했습니다.'); } }) .catch(error => { alert('서버 통신 오류가 발생했습니다.'); }); } function deleteIP(ip) { if (confirm('이 IP를 허용 목록에서 삭제하시겠습니까?')) { fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'presslearn_delete_allowed_ip', nonce: ipNonce, ip: ip }) }) .then(response => response.json()) .then(data => { if (data.success) { alert('IP가 성공적으로 허용 목록에서 삭제되었습니다.'); allowedIPs = data.data.ips; renderIPTable(); } else { alert(data.data.message || '허용 IP 삭제에 실패했습니다.'); } }) .catch(error => { alert('서버 통신 오류가 발생했습니다.'); }); } } function deleteBlockedIP(ip) { if (confirm('이 IP를 차단 목록에서 삭제하시겠습니까?')) { fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'presslearn_delete_blocked_ip', nonce: ipNonce, ip: ip }) }) .then(response => response.json()) .then(data => { if (data.success) { alert('IP가 성공적으로 차단 목록에서 삭제되었습니다.'); blockedIPs = data.data.ips; renderBlockedIPTable(); } else { alert(data.data.message || '차단 IP 삭제에 실패했습니다.'); } }) .catch(error => { alert('서버 통신 오류가 발생했습니다.'); }); } } function isValidIP(ip) { const regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; return regex.test(ip); } }); "; wp_add_inline_script('presslearn-protection-ip-management-js', $ip_management_js); wp_register_script( 'presslearn-protection-modal-settings-js', false, array('jquery'), PRESSLEARN_PLUGIN_VERSION, true ); wp_enqueue_script('presslearn-protection-modal-settings-js'); $modal_settings_js = " document.addEventListener('DOMContentLoaded', function() { const openModalSettingsBtn = document.getElementById('open-modal-settings'); const closeModalSettingsBtn = document.getElementById('close-modal-settings'); const saveModalSettingsBtn = document.getElementById('save-modal-settings'); if (openModalSettingsBtn) { openModalSettingsBtn.addEventListener('click', function() { document.getElementById('modal-settings-editor').style.display = 'flex'; }); } if (closeModalSettingsBtn) { closeModalSettingsBtn.addEventListener('click', function() { document.getElementById('modal-settings-editor').style.display = 'none'; }); } document.addEventListener('click', function(e) { if (e.target.classList.contains('presslearn-modal-close')) { document.getElementById('modal-settings-editor').style.display = 'none'; } if (e.target.id === 'modal-settings-editor') { document.getElementById('modal-settings-editor').style.display = 'none'; } }); const modalTitleInput = document.getElementById('modal_title'); const modalMessageInput = document.getElementById('modal_message'); const modalSubmessageInput = document.getElementById('modal_submessage'); const modalButtonTextInput = document.getElementById('modal_button_text'); if (modalTitleInput) { modalTitleInput.addEventListener('input', function() { const previewTitle = document.getElementById('preview-title'); if (previewTitle) previewTitle.textContent = this.value; }); } if (modalMessageInput) { modalMessageInput.addEventListener('input', function() { const previewMessage = document.getElementById('preview-message'); if (previewMessage) previewMessage.textContent = this.value; }); } if (modalSubmessageInput) { modalSubmessageInput.addEventListener('input', function() { const previewSubmessage = document.getElementById('preview-submessage'); if (previewSubmessage) previewSubmessage.textContent = this.value; }); } if (modalButtonTextInput) { modalButtonTextInput.addEventListener('input', function() { const previewButton = document.getElementById('preview-button'); if (previewButton) previewButton.textContent = this.value; }); } if (saveModalSettingsBtn) { saveModalSettingsBtn.addEventListener('click', function() { const modalTitle = modalTitleInput ? modalTitleInput.value : ''; const modalMessage = modalMessageInput ? modalMessageInput.value : ''; const modalSubmessage = modalSubmessageInput ? modalSubmessageInput.value : ''; const modalButtonText = modalButtonTextInput ? modalButtonTextInput.value : ''; const form = document.getElementById('click-protection-settings-form'); if (form) { const existingInputs = form.querySelectorAll('input[name^=\"modal_\"]'); existingInputs.forEach(input => input.remove()); const inputs = [ {name: 'modal_title', value: modalTitle}, {name: 'modal_message', value: modalMessage}, {name: 'modal_submessage', value: modalSubmessage}, {name: 'modal_button_text', value: modalButtonText} ]; inputs.forEach(inputData => { const input = document.createElement('input'); input.type = 'hidden'; input.name = inputData.name; input.value = inputData.value; form.appendChild(input); }); } document.getElementById('modal-settings-editor').style.display = 'none'; alert('모달 설정이 적용되었습니다. 변경사항을 저장하려면 \"저장하기\" 버튼을 클릭하세요.'); }); } }); "; wp_add_inline_script('presslearn-protection-modal-settings-js', $modal_settings_js); wp_register_script( 'presslearn-protection-blocked-logs-js', false, array('jquery'), PRESSLEARN_PLUGIN_VERSION, true ); wp_enqueue_script('presslearn-protection-blocked-logs-js'); wp_localize_script('presslearn-protection-blocked-logs-js', 'presslearn_logs_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('presslearn_ip_nonce') )); $blocked_logs_js = " document.addEventListener('DOMContentLoaded', function() { const openLogModalBtn = document.getElementById('open-blocked-log-modal'); const closeLogModalBtn = document.getElementById('close-log-modal-button'); const refreshLogBtn = document.getElementById('refresh-log'); if (openLogModalBtn) { openLogModalBtn.addEventListener('click', function() { loadBlockedLogs(); document.getElementById('blocked-log-modal').style.display = 'flex'; }); } if (closeLogModalBtn) { closeLogModalBtn.addEventListener('click', function() { document.getElementById('blocked-log-modal').style.display = 'none'; }); } if (refreshLogBtn) { refreshLogBtn.addEventListener('click', loadBlockedLogs); } document.addEventListener('click', function(e) { if (e.target.classList.contains('presslearn-modal-close')) { document.getElementById('blocked-log-modal').style.display = 'none'; } if (e.target.id === 'blocked-log-modal') { document.getElementById('blocked-log-modal').style.display = 'none'; } }); function loadBlockedLogs() { const logList = document.getElementById('blocked-log-list'); const noLogsMessage = document.getElementById('no-blocked-logs-message'); const logLoading = document.getElementById('log-loading'); const periodSelect = document.getElementById('log-filter-period'); if (logList) logList.innerHTML = ''; if (noLogsMessage) noLogsMessage.style.display = 'none'; if (logLoading) logLoading.style.display = 'block'; const period = periodSelect ? periodSelect.value : '30'; fetch(presslearn_logs_ajax.ajax_url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'presslearn_get_blocked_logs', nonce: presslearn_logs_ajax.nonce, period: period }) }) .then(response => response.json()) .then(data => { if (logLoading) logLoading.style.display = 'none'; if (data.success) { const logs = data.data.logs || []; renderLogTable(logs); } else { alert('차단 로그를 불러오는데 실패했습니다.'); if (noLogsMessage) noLogsMessage.style.display = 'block'; } }) .catch(error => { if (logLoading) logLoading.style.display = 'none'; alert('서버 통신 오류가 발생했습니다.'); if (noLogsMessage) noLogsMessage.style.display = 'block'; }); } function renderLogTable(logs) { const tbody = document.getElementById('blocked-log-list'); const noLogsMessage = document.getElementById('no-blocked-logs-message'); if (!tbody) return; tbody.innerHTML = ''; if (logs.length > 0) { if (noLogsMessage) noLogsMessage.style.display = 'none'; const today = new Date(); logs.forEach((item) => { let statusClass = 'log-active'; let expiryText = item.expires || '영구 차단'; if (item.expires) { const expiryDate = new Date(item.expires); if (expiryDate < today) { statusClass = 'log-expired'; expiryText += ' (만료됨)'; } } const row = document.createElement('tr'); row.className = statusClass; row.innerHTML = ` \${item.ip} \${item.block_date || item.date} \${item.reason || '수동 차단'} \${expiryText} `; tbody.appendChild(row); }); } else { if (noLogsMessage) noLogsMessage.style.display = 'block'; } } }); "; wp_add_inline_script('presslearn-protection-blocked-logs-js', $blocked_logs_js); } add_action('admin_enqueue_scripts', 'presslearn_protection_admin_styles'); add_action('admin_enqueue_scripts', 'presslearn_protection_admin_scripts');