PluginProbe
AL Pack / trunk
AL Pack vtrunk
1.3.13 trunk 1.0.0 1.1.1 1.1.2 1.2.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.11 1.3.12
alpack / presslearn-plugin.php

presslearn-plugin.php in AL Pack trunk, at presslearn-plugin.php

5,073 lines 178.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: AL Pack - 워드프레스를 위한 통계, 광고 도구
4 * Plugin URI: https://alpack.dev
5 * Description: 통계, 글쓰기 SEO, 무효 트래픽 차단, 빠른 버튼 생성, 카카오 공유 버튼, 스크롤 팝�
6 , 애드클리커 등 워드프레스를 위한 통합 플러그인
7 * Version: 1.3.13
8 * Author: 프레스런
9 * Author URI: https://alpack.dev
10 * Text Domain: alpack
11 * License: GPLv2 or later
12 */
13
14 if (!defined('ABSPATH')) {
15 exit;
16 }
17
18 define('PRESSLEARN_PLUGIN_DIR', plugin_dir_path(__FILE__));
19 define('PRESSLEARN_PLUGIN_URL', plugin_dir_url(__FILE__));
20 define('PRESSLEARN_PLUGIN_VERSION', time());
21
22 register_activation_hook(__FILE__, 'presslearn_plugin_activate');
23 register_deactivation_hook(__FILE__, 'presslearn_plugin_deactivate');
24 register_uninstall_hook(__FILE__, 'presslearn_plugin_uninstall');
25
26 function presslearn_plugin_activate() {
27 $is_first_install = empty(get_option('presslearn_plugin_header_version', ''));
28
29 update_option('presslearn_plugin_key', '');
30 add_option('presslearn_plugin_activated_time', time());
31 add_option('presslearn_activation_logs', array());
32 update_option('presslearn_plugin_settings', array(
33 'initialized' => true,
34 'version' => PRESSLEARN_PLUGIN_VERSION,
35 'activation_date' => current_time('mysql')
36 ));
37
38 if ($is_first_install) {
39 update_option('presslearn_scroll_depth_enabled', 'no');
40 update_option('presslearn_analytics_enabled', 'no');
41 update_option('presslearn_dynamic_banner_enabled', 'no');
42 update_option('presslearn_click_protection_enabled', 'no');
43 update_option('presslearn_ad_clicker_enabled', 'no');
44 update_option('presslearn_social_share_enabled', 'no');
45 add_option('presslearn_quick_button_enabled', 'no');
46 add_option('presslearn_button_transition_enabled', 'no');
47 add_option('presslearn_auto_index_enabled', 'no');
48 add_option('presslearn_header_footer_enabled', 'no');
49 add_option('presslearn_header_footer_user_set', 'no');
50 } else {
51 add_option('presslearn_scroll_depth_enabled', 'no');
52 add_option('presslearn_analytics_enabled', 'no');
53 add_option('presslearn_dynamic_banner_enabled', 'no');
54 add_option('presslearn_click_protection_enabled', 'no');
55 add_option('presslearn_ad_clicker_enabled', 'no');
56 add_option('presslearn_social_share_enabled', 'no');
57 add_option('presslearn_quick_button_enabled', 'no');
58 add_option('presslearn_button_transition_enabled', 'no');
59 add_option('presslearn_auto_index_enabled', 'no');
60 add_option('presslearn_header_footer_enabled', 'no');
61 add_option('presslearn_header_footer_user_set', 'no');
62 }
63 presslearn_create_tables();
64
65 set_transient('presslearn_plugin_activation_redirect', true, 30);
66 }
67
68 function presslearn_create_tables() {
69 global $wpdb;
70
71 $charset_collate = $wpdb->get_charset_collate();
72
73 $table_name = $wpdb->prefix . 'presslearn_logs';
74
75 $sql = "CREATE TABLE IF NOT EXISTS $table_name (
76 id bigint(20) NOT NULL AUTO_INCREMENT,
77 time datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
78 event varchar(255) NOT NULL,
79 details longtext NOT NULL,
80 PRIMARY KEY (id)
81 ) $charset_collate;";
82
83 $banners_table = $wpdb->prefix . 'presslearn_banners';
84 $sql .= "CREATE TABLE IF NOT EXISTS $banners_table (
85 id bigint(20) NOT NULL AUTO_INCREMENT,
86 name varchar(255) NOT NULL,
87 type varchar(50) NOT NULL DEFAULT 'custom',
88 banner_url text,
89 cover_banner_url text,
90 link text,
91 iframe_code longtext,
92 width int(11),
93 height int(11),
94 status tinyint(1) DEFAULT 1,
95 created_at datetime DEFAULT CURRENT_TIMESTAMP,
96 updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
97 PRIMARY KEY (id)
98 ) $charset_collate;";
99
100 $indexing_table = $wpdb->prefix . 'presslearn_indexing_logs';
101 $sql .= "CREATE TABLE IF NOT EXISTS $indexing_table (
102 id bigint(20) NOT NULL AUTO_INCREMENT,
103 post_id bigint(20) NOT NULL,
104 post_url text NOT NULL,
105 post_title text NOT NULL,
106 request_type varchar(50) NOT NULL DEFAULT 'auto',
107 response_code int(11) DEFAULT NULL,
108 response_message text DEFAULT NULL,
109 indexed_at datetime DEFAULT CURRENT_TIMESTAMP,
110 PRIMARY KEY (id),
111 KEY post_id (post_id),
112 KEY indexed_at (indexed_at)
113 ) $charset_collate;";
114
115 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
116 dbDelta($sql);
117 }
118
119 function presslearn_ensure_banners_table_exists() {
120 global $wpdb;
121
122 $table_name = $wpdb->prefix . 'presslearn_banners';
123
124 $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name));
125
126 if ($table_exists != $table_name) {
127 $charset_collate = $wpdb->get_charset_collate();
128
129 $sql = "CREATE TABLE $table_name (
130 id bigint(20) NOT NULL AUTO_INCREMENT,
131 name varchar(255) NOT NULL,
132 type varchar(50) NOT NULL DEFAULT 'custom',
133 banner_url text,
134 cover_banner_url text,
135 link text,
136 iframe_code longtext,
137 width int(11),
138 height int(11),
139 status tinyint(1) DEFAULT 1,
140 created_at datetime DEFAULT CURRENT_TIMESTAMP,
141 updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
142 PRIMARY KEY (id)
143 ) $charset_collate;";
144
145 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
146 dbDelta($sql);
147
148 if (function_exists('error_log')) {
149 error_log('PressLearn: wp_presslearn_banners �
150 �이블이 생성되었습니다.');
151 }
152 }
153 }
154
155 function presslearn_plugin_deactivate() {
156 delete_transient('presslearn_plugin_activation_redirect');
157 }
158
159 function presslearn_plugin_activation_redirect() {
160 if (get_transient('presslearn_plugin_activation_redirect')) {
161 delete_transient('presslearn_plugin_activation_redirect');
162 if (is_admin() && !isset($_GET['activate-multi'])) {
163 wp_redirect(admin_url('admin.php?page=presslearn-settings'));
164 exit;
165 }
166 }
167 }
168 add_action('admin_init', 'presslearn_plugin_activation_redirect');
169
170
171 function presslearn_plugin_uninstall() {
172 delete_option('presslearn_plugin_key');
173 delete_option('presslearn_plugin_activated_time');
174 delete_option('presslearn_activation_logs');
175 delete_option('presslearn_plugin_settings');
176
177 delete_option('presslearn_scroll_depth_enabled');
178 delete_option('presslearn_analytics_enabled');
179 delete_option('presslearn_dynamic_banner_enabled');
180 delete_option('presslearn_click_protection_enabled');
181 delete_option('presslearn_ad_clicker_enabled');
182 delete_option('presslearn_social_share_enabled');
183 delete_option('presslearn_quick_button_enabled');
184 delete_option('presslearn_button_transition_enabled');
185 delete_option('presslearn_auto_index_enabled');
186 delete_option('presslearn_header_footer_enabled');
187 delete_option('presslearn_header_footer_user_set');
188 delete_option('presslearn_click_protection_blocked_countries');
189 delete_transient('presslearn_plugin_activation_redirect');
190
191 $users = get_users(array('fields' => 'ID'));
192 foreach($users as $user_id) {
193 delete_user_meta($user_id, 'presslearn_user_data');
194 }
195
196 presslearn_drop_tables();
197 }
198
199
200 function presslearn_drop_tables() {
201 global $wpdb;
202
203 $table_name = $wpdb->prefix . 'presslearn_logs';
204 $wpdb->query("DROP TABLE IF EXISTS $table_name");
205
206 $banners_table = $wpdb->prefix . 'presslearn_banners';
207 $wpdb->query("DROP TABLE IF EXISTS $banners_table");
208
209 $indexing_table = $wpdb->prefix . 'presslearn_indexing_logs';
210 $wpdb->query("DROP TABLE IF EXISTS $indexing_table");
211 }
212
213 require_once PRESSLEARN_PLUGIN_DIR . 'includes/admin.php';
214 require_once PRESSLEARN_PLUGIN_DIR . 'includes/api.php';
215 require_once PRESSLEARN_PLUGIN_DIR . 'includes/share.php';
216 require_once PRESSLEARN_PLUGIN_DIR . 'includes/buttons.php';
217 require_once PRESSLEARN_PLUGIN_DIR . 'includes/analytics.php';
218 require_once PRESSLEARN_PLUGIN_DIR . 'includes/protection.php';
219 require_once PRESSLEARN_PLUGIN_DIR . 'includes/dynamic.php';
220 require_once PRESSLEARN_PLUGIN_DIR . 'includes/scroll.php';
221 require_once PRESSLEARN_PLUGIN_DIR . 'includes/indexing.php';
222
223 function presslearn_scroll_depth_frontend_script() {
224 if (is_admin()) {
225 return;
226 }
227
228 $scroll_depth_enabled = get_option('presslearn_scroll_depth_enabled', 'no');
229 $popup_content = get_option('presslearn_popup_content', '');
230
231 if ($scroll_depth_enabled !== 'yes' || empty($popup_content)) {
232 return;
233 }
234
235 $scroll_percentage = get_option('presslearn_scroll_percentage', 50);
236 $popup_animation = get_option('presslearn_popup_animation', 'fade');
237 $repeat_setting = get_option('presslearn_repeat_setting', 'once');
238
239 $animation_class = 'popupFadeIn';
240 if ($popup_animation === 'slide') {
241 $animation_class = 'popupSlideIn';
242 } elseif ($popup_animation === 'zoom') {
243 $animation_class = 'popupZoomIn';
244 }
245
246 $cookie_check = $repeat_setting === 'once' ? 'true' : 'false';
247
248 wp_register_style('presslearn-popup-css', false);
249 wp_enqueue_style('presslearn-popup-css');
250
251 $popup_styles = "
252 .pl-popup-overlay {
253 position: fixed;
254 top: 0;
255 left: 0;
256 width: 100%;
257 height: 100%;
258 background-color: rgba(0, 0, 0, 0.7);
259 z-index: 99999;
260 display: none;
261 justify-content: center;
262 align-items: center;
263 }
264
265 .pl-popup-container {
266 display: flex;
267 justify-content: center;
268 align-items: center;
269 width: 100%;
270 height: 100%;
271 }
272
273 .pl-popup-window {
274 background-color: #fff;
275 width: 90%;
276 max-width: 500px;
277 border-radius: 8px;
278 box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
279 display: flex;
280 flex-direction: column;
281 max-height: 80vh;
282 }
283
284 .pl-popup-body {
285 padding: 30px;
286 overflow-y: auto;
287 flex: 1;
288 line-height: 1.6;
289 }
290
291 .pl-popup-body img {
292 max-width: 100%;
293 height: auto;
294 display: block;
295 margin: 0 auto;
296 }
297
298 .pl-close-popup {
299 position: fixed;
300 top: 20px;
301 right: 20px;
302 background: none;
303 border: none;
304 font-size: 40px;
305 cursor: pointer;
306 color: #fff;
307 z-index: 100000;
308 padding: 0;
309 line-height: 1;
310 }
311
312 .pl-close-popup:hover {
313 color: #ddd;
314 }
315
316 @keyframes popupFadeIn {
317 from {
318 opacity: 0;
319 transform: scale(0.9);
320 }
321 to {
322 opacity: 1;
323 transform: scale(1);
324 }
325 }
326
327 @keyframes popupSlideIn {
328 from {
329 opacity: 0;
330 transform: translateY(-50px);
331 }
332 to {
333 opacity: 1;
334 transform: translateY(0);
335 }
336 }
337
338 @keyframes popupZoomIn {
339 from {
340 opacity: 0;
341 transform: scale(0.5);
342 }
343 to {
344 opacity: 1;
345 transform: scale(1);
346 }
347 }
348
349 .popup-animation {
350 animation: " . $animation_class . " 0.3s ease-out;
351 }
352
353 @media screen and (max-width: 768px) {
354 .pl-popup-window {
355 width: 95%;
356 max-width: 95%;
357 }
358
359 .pl-popup-body {
360 padding: 20px;
361 }
362
363 .pl-close-popup {
364 top: 10px;
365 right: 10px;
366 font-size: 32px;
367 }
368 }";
369
370 wp_add_inline_style('presslearn-popup-css', $popup_styles);
371
372 add_action('wp_footer', function() use ($popup_content) {
373 ?>
374 <div id="pl-popup-overlay" class="pl-popup-overlay">
375 <button type="button" class="pl-close-popup">&times;</button>
376 <div class="pl-popup-container">
377 <div class="pl-popup-window popup-animation">
378 <div class="pl-popup-body">
379 <?php echo wp_kses_post(wpautop($popup_content)); ?>
380 </div>
381 </div>
382 </div>
383 </div>
384 <?php
385 });
386
387 wp_register_script('presslearn-popup-js', false, array(), PRESSLEARN_PLUGIN_VERSION, true);
388 wp_enqueue_script('presslearn-popup-js');
389
390 $popup_script = "
391 (function() {
392 function setCookie(name, value, days) {
393 var expires = '';
394 if (days) {
395 var date = new Date();
396 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
397 expires = '; expires=' + date.toUTCString();
398 }
399 document.cookie = name + '=' + (value || '') + expires + '; path=/';
400 }
401
402 function getCookie(name) {
403 var nameEQ = name + '=';
404 var ca = document.cookie.split(';');
405 for(var i=0; i < ca.length; i++) {
406 var c = ca[i];
407 while (c.charAt(0) == ' ') c = c.substring(1, c.length);
408 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
409 }
410 return null;
411 }
412
413 var cookieName = 'pl_popup_shown';
414 var checkCookie = " . esc_js($cookie_check) . ";
415
416 if (checkCookie && getCookie(cookieName)) {
417 return;
418 }
419
420 var scrollTriggered = false;
421 window.addEventListener('scroll', function() {
422 if (scrollTriggered) return;
423
424 var scrollPercentage = " . esc_js($scroll_percentage) . ";
425 var scrollPosition = window.scrollY;
426 var documentHeight = document.documentElement.scrollHeight - window.innerHeight;
427 var currentScrollPercent = (scrollPosition / documentHeight) * 100;
428
429 if (currentScrollPercent >= scrollPercentage) {
430 scrollTriggered = true;
431 document.getElementById('pl-popup-overlay').style.display = 'flex';
432
433 if (checkCookie) {
434 setCookie(cookieName, 'true', 30);
435 }
436 }
437 });
438
439 document.querySelector('.pl-close-popup').addEventListener('click', function() {
440 document.getElementById('pl-popup-overlay').style.display = 'none';
441 });
442
443 document.getElementById('pl-popup-overlay').addEventListener('click', function(e) {
444 if (e.target === this || e.target.classList.contains('pl-popup-container')) {
445 this.style.display = 'none';
446 }
447 });
448 })();";
449
450 wp_add_inline_script('presslearn-popup-js', $popup_script);
451 }
452
453 add_action('wp_enqueue_scripts', 'presslearn_scroll_depth_frontend_script');
454
455 class PressLearn_Plugin {
456 private static $instance = null;
457
458 private $is_activated = false;
459
460 private $option_key = 'presslearn_plugin_key';
461
462 private $exempt_pages = array('presslearn-settings');
463
464 private $db_version = '1.0';
465
466 public static function get_instance() {
467 if (null === self::$instance) {
468 self::$instance = new self();
469 }
470 return self::$instance;
471 }
472
473
474 private function __construct() {
475 add_action('init', array($this, 'init'));
476
477 add_action('admin_menu', array($this, 'register_admin_menu'));
478
479 add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
480
481 $this->check_activation();
482
483 add_action('admin_init', array($this, 'check_page_access'));
484
485 add_action('admin_init', array($this, 'check_version_upgrade'));
486
487 add_action('admin_notices', array($this, 'show_update_notice'));
488
489 add_action('admin_bar_menu', array($this, 'add_admin_bar_menu'), 100);
490 }
491
492
493 public function check_version_upgrade() {
494 if (!function_exists('get_plugin_data')) {
495 require_once(ABSPATH . 'wp-admin/includes/plugin.php');
496 }
497
498 $plugin_data = get_plugin_data(__FILE__);
499 $current_header_version = $plugin_data['Version'];
500
501 $saved_header_version = get_option('presslearn_plugin_header_version', '');
502
503 if (!empty($saved_header_version) && $saved_header_version !== $current_header_version) {
504 set_transient('presslearn_plugin_updated_notice', array(
505 'from_version' => $saved_header_version,
506 'to_version' => $current_header_version,
507 'updated_time' => current_time('mysql')
508 ), 86400);
509
510 $this->log_upgrade($saved_header_version, $current_header_version);
511 }
512
513 update_option('presslearn_plugin_header_version', $current_header_version);
514
515 $settings = get_option('presslearn_plugin_settings', array());
516 $current_version = isset($settings['version']) ? $settings['version'] : '0';
517 $current_db_version = isset($settings['db_version']) ? $settings['db_version'] : '0';
518
519 if ($current_version != PRESSLEARN_PLUGIN_VERSION) {
520 if (version_compare($current_db_version, $this->db_version, '<')) {
521 $this->run_db_migration($current_db_version);
522 }
523
524 $settings['version'] = PRESSLEARN_PLUGIN_VERSION;
525 $settings['db_version'] = $this->db_version;
526 $settings['last_upgraded'] = current_time('mysql');
527
528 update_option('presslearn_plugin_settings', $settings);
529 }
530 }
531
532
533 private function run_db_migration($from_version) {
534 global $wpdb;
535
536 if (version_compare($from_version, '0.5', '<')) {
537 $table_name = $wpdb->prefix . 'presslearn_logs';
538
539 $wpdb->query("ALTER TABLE $table_name ADD ip_address varchar(45) DEFAULT '' AFTER event");
540 }
541
542 if (version_compare($from_version, '0.8', '<')) {
543
544 }
545
546 }
547
548 private function log_upgrade($from_version, $to_version) {
549 global $wpdb;
550
551 $table_name = $wpdb->prefix . 'presslearn_logs';
552
553 $wpdb->insert(
554 $table_name,
555 array(
556 'time' => current_time('mysql'),
557 'event' => 'plugin_upgrade',
558 'details' => json_encode(array(
559 'from_version' => $from_version,
560 'to_version' => $to_version,
561 'user_id' => get_current_user_id(),
562 'site_url' => get_site_url()
563 ))
564 )
565 );
566 }
567
568 public function show_update_notice() {
569 $update_notice = get_transient('presslearn_plugin_updated_notice');
570
571 if ($update_notice && is_array($update_notice)) {
572 $from_version = esc_html($update_notice['from_version']);
573 $to_version = esc_html($update_notice['to_version']);
574 $updated_time = esc_html($update_notice['updated_time']);
575
576 ?>
577
578 <script type="text/javascript">
579 jQuery(document).ready(function($) {
580 $('#presslearn-update-notice .notice-dismiss').on('click', function() {
581 $.post(ajaxurl, {
582 action: 'presslearn_dismiss_update_notice',
583 nonce: '<?php echo esc_js(wp_create_nonce('presslearn_dismiss_notice')); ?>'
584 });
585 });
586 });
587 </script>
588 <?php
589 }
590 }
591
592 public function check_page_access() {
593 $current_page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
594
595 if (strpos($current_page, 'presslearn-') === 0 && !in_array($current_page, $this->exempt_pages)) {
596 if (!$this->is_activated) {
597 wp_redirect(admin_url('admin.php?page=presslearn-settings&access_denied=true'));
598 exit;
599 }
600 }
601 }
602
603 /**
604 * Initialize
605 */
606 public function init() {
607 if (isset($_GET['presslearn_key']) && !empty($_GET['presslearn_key'])) {
608 if (!current_user_can('manage_options')) {
609 wp_die('관리자 권한이 필요합니다.');
610 }
611
612 if (!isset($_GET['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'presslearn_activate_plugin')) {
613 wp_die('보안 검증에 실패했습니다.');
614 }
615
616 $this->activate_plugin(sanitize_text_field(wp_unslash($_GET['presslearn_key'])));
617 wp_redirect(admin_url('admin.php?page=presslearn-settings&activated=true'));
618 exit;
619 }
620
621 if (get_option('presslearn_analytics_enabled', 'no') === 'yes') {
622 $tables_created = $this->create_analytics_tables();
623
624 if ($tables_created) {
625 add_action('wp_head', array($this, 'add_tracking_code'));
626 $this->register_tracking_ajax();
627 } else {
628 update_option('presslearn_analytics_enabled', 'no');
629
630 if (is_admin()) {
631 add_action('admin_notices', function() {
632 ?>
633 <div class="notice notice-error">
634 <p><?php echo esc_html('씬 애널리틱스 기능 활성화 중 오류가 발생하여 비활성화되었습니다. 데이터베이스 �
635 �이블을 생성할 수 없습니다.'); ?></p>
636 </div>
637 <?php
638 });
639 }
640 }
641 }
642 }
643
644 public function enqueue_admin_scripts($hook) {
645 if (strpos($hook, 'presslearn') === false) {
646 return;
647 }
648
649 wp_enqueue_style('presslearn-admin-css', PRESSLEARN_PLUGIN_URL . 'assets/css/admin.css', array(), PRESSLEARN_PLUGIN_VERSION);
650
651 wp_enqueue_script('jquery');
652 wp_enqueue_script('presslearn-admin-js', PRESSLEARN_PLUGIN_URL . 'assets/js/admin.js', array('jquery'), PRESSLEARN_PLUGIN_VERSION, true);
653
654 wp_register_script('presslearn-chart-js', PRESSLEARN_PLUGIN_URL . 'assets/js/chart.min.js', array(), '3.7.1', true);
655
656 if (strpos($hook, 'presslearn-analytics') !== false) {
657 wp_enqueue_script('presslearn-chart-js');
658 wp_register_style('presslearn-analytics-css', false);
659 wp_enqueue_style('presslearn-analytics-css');
660
661 wp_register_script('presslearn-analytics-js', false, array('jquery', 'presslearn-chart-js', 'moment'), PRESSLEARN_PLUGIN_VERSION, true);
662 wp_enqueue_script('presslearn-analytics-js');
663 }
664
665 if (strpos($hook, 'presslearn-scroll-depth') !== false) {
666 wp_register_style('presslearn-scroll-depth-css', false);
667 wp_enqueue_style('presslearn-scroll-depth-css');
668
669 wp_register_script('presslearn-scroll-depth-js', false, array('jquery'), PRESSLEARN_PLUGIN_VERSION, true);
670 wp_enqueue_script('presslearn-scroll-depth-js');
671 }
672
673 if (strpos($hook, 'presslearn-click-protection') !== false) {
674 wp_register_style('presslearn-click-protection-css', false);
675 wp_enqueue_style('presslearn-click-protection-css');
676
677 wp_register_script('presslearn-click-protection-js', false, array('jquery'), PRESSLEARN_PLUGIN_VERSION, true);
678 wp_enqueue_script('presslearn-click-protection-js');
679 }
680
681 if (strpos($hook, 'presslearn-ad-clicker') !== false) {
682 wp_register_style('presslearn-ad-clicker-css', false);
683 wp_enqueue_style('presslearn-ad-clicker-css');
684
685 wp_register_script('presslearn-ad-clicker-js', false, array('jquery'), PRESSLEARN_PLUGIN_VERSION, true);
686 wp_enqueue_script('presslearn-ad-clicker-js');
687
688 wp_register_script('presslearn-adclicker-admin', false, array(), PRESSLEARN_PLUGIN_VERSION, true);
689 wp_enqueue_script('presslearn-adclicker-admin');
690
691 $adclicker_overlay_range = get_option('presslearn_adclicker_overlay_range', 100);
692 $adclicker_overlay_color = get_option('presslearn_adclicker_overlay_color', '#000000');
693 $adclicker_button_color = get_option('presslearn_adclicker_button_color', '#2196F3');
694 $adclicker_button_text_color = get_option('presslearn_adclicker_button_text_color', '#ffffff');
695 $adclicker_display_time = get_option('presslearn_adclicker_display_time', 'null');
696
697 wp_localize_script('presslearn-adclicker-admin', 'pressleanAdclickerConfig', array(
698 'overlayRange' => $adclicker_overlay_range,
699 'overlayColor' => $adclicker_overlay_color,
700 'buttonColor' => $adclicker_button_color,
701 'buttonTextColor' => $adclicker_button_text_color,
702 'displayTime' => $adclicker_display_time
703 ));
704
705 $adclicker_script = "
706 document.addEventListener('DOMContentLoaded', function() {
707 const previewButton = document.getElementById('preview-adclicker');
708
709 if (previewButton) {
710 previewButton.addEventListener('click', function() {
711 showAdClickerPreview();
712 });
713 }
714
715 const urlParams = new URLSearchParams(window.location.search);
716 const showPreview = urlParams.get('preview');
717
718 if (previewButton && (showPreview === '1' || showPreview === 'true')) {
719 setTimeout(function() {
720 showAdClickerPreview();
721 }, 500);
722 }
723
724 function showAdClickerPreview() {
725 const overlay = document.createElement('div');
726 overlay.id = 'adclicker-overlay-preview';
727 overlay.style.position = 'fixed';
728 overlay.style.bottom = '0';
729 overlay.style.left = '0';
730 overlay.style.width = '100%';
731 overlay.style.height = pressleanAdclickerConfig.overlayRange + 'vh';
732 overlay.style.background = 'linear-gradient(to bottom, rgba(255, 255, 255, 0.1) 0%, ' + pressleanAdclickerConfig.overlayColor + ' 100%)';
733 overlay.style.zIndex = '999999';
734
735 const closeButton = document.createElement('div');
736 closeButton.id = 'adclicker-close-button-preview';
737 closeButton.setAttribute('data-ad-link', '#previewAdLink');
738 closeButton.style.position = 'fixed';
739 closeButton.style.bottom = '60px';
740 closeButton.style.left = '50%';
741 closeButton.style.transform = 'translateX(-50%)';
742 closeButton.style.padding = '15px 30px';
743 closeButton.style.backgroundColor = pressleanAdclickerConfig.buttonColor;
744 closeButton.style.color = pressleanAdclickerConfig.buttonTextColor;
745 closeButton.style.border = 'none';
746 closeButton.style.borderRadius = '8px';
747 closeButton.style.fontSize = '20px';
748 closeButton.style.cursor = 'pointer';
749 closeButton.style.zIndex = '1000001';
750 closeButton.style.textDecoration = 'none';
751 closeButton.style.display = 'inline-block';
752 closeButton.style.textAlign = 'center';
753
754 const previewLink = document.createElement('a');
755 previewLink.href = '#previewAdLink';
756 previewLink.textContent = '미리보기 예시용';
757 previewLink.setAttribute('target', '_blank');
758 previewLink.style.color = 'inherit';
759 previewLink.style.textDecoration = 'inherit';
760 previewLink.style.display = 'block';
761
762 closeButton.appendChild(previewLink);
763
764 const hoverStyle = document.createElement('style');
765 hoverStyle.innerHTML =
766 '#adclicker-close-button-preview:hover,' +
767 '#adclicker-close-button-preview:focus,' +
768 '#adclicker-close-button-preview:active {' +
769 'background-color: ' + closeButton.style.backgroundColor + ' !important;' +
770 'color: ' + closeButton.style.color + ' !important;' +
771 'text-decoration: none !important;' +
772 'outline: none !important;' +
773 '}';
774 document.head.appendChild(hoverStyle);
775
776 const backButtonMessage = document.createElement('div');
777 backButtonMessage.textContent = '원치않으시면 뒤로가기를 해주세요';
778 backButtonMessage.style.position = 'fixed';
779 backButtonMessage.style.bottom = '50px';
780 backButtonMessage.style.left = '50%';
781 backButtonMessage.style.transform = 'translateX(-50%)';
782 backButtonMessage.style.fontSize = '12px';
783 backButtonMessage.style.color = pressleanAdclickerConfig.buttonTextColor;
784 backButtonMessage.style.opacity = '0.8';
785 backButtonMessage.style.zIndex = '1000001';
786 backButtonMessage.style.marginBottom = '-15px';
787
788 const countdownLabel = document.createElement('span');
789 countdownLabel.id = 'adclicker-countdown-label-preview';
790 countdownLabel.style.position = 'absolute';
791 countdownLabel.style.top = '-10px';
792 countdownLabel.style.right = '-10px';
793 countdownLabel.style.width = '30px';
794 countdownLabel.style.height = '30px';
795 countdownLabel.style.borderRadius = '50%';
796 countdownLabel.style.backgroundColor = '#ff3b30';
797 countdownLabel.style.color = 'white';
798 countdownLabel.style.fontSize = '14px';
799 countdownLabel.style.fontWeight = 'bold';
800 countdownLabel.style.textAlign = 'center';
801 countdownLabel.style.lineHeight = '30px';
802 countdownLabel.style.zIndex = '1000002';
803
804 closeButton.addEventListener('click', function(e) {
805 if (e.target.tagName.toLowerCase() !== 'a') {
806 e.preventDefault();
807
808 document.body.removeChild(overlay);
809 document.body.removeChild(closeButton);
810 document.body.removeChild(backButtonMessage);
811 if (countdownInterval) {
812 clearInterval(countdownInterval);
813 }
814 }
815 });
816
817 document.body.appendChild(overlay);
818 document.body.appendChild(closeButton);
819 document.body.appendChild(backButtonMessage);
820
821 const displayTime = pressleanAdclickerConfig.displayTime;
822 let countdownInterval;
823
824 if (displayTime !== 'null') {
825 let timeLeft = parseInt(displayTime);
826 countdownLabel.textContent = timeLeft;
827 closeButton.appendChild(countdownLabel);
828
829 countdownInterval = setInterval(function() {
830 timeLeft--;
831 countdownLabel.textContent = timeLeft;
832
833 if (timeLeft <= 0) {
834 clearInterval(countdownInterval);
835 countdownLabel.textContent = '✕';
836 }
837 }, 1000);
838 }
839 }
840 });
841 ";
842
843 wp_add_inline_script('presslearn-adclicker-admin', $adclicker_script);
844 }
845
846 if (strpos($hook, 'presslearn-dynamic-banner') !== false) {
847 wp_register_style('presslearn-dynamic-banner-css', false);
848 wp_enqueue_style('presslearn-dynamic-banner-css');
849
850 wp_register_script('presslearn-dynamic-banner-js', false, array('jquery'), PRESSLEARN_PLUGIN_VERSION, true);
851 wp_enqueue_script('presslearn-dynamic-banner-js');
852 }
853
854 if (strpos($hook, 'presslearn-social-share') !== false) {
855 wp_register_style('presslearn-social-share-css', false);
856 wp_enqueue_style('presslearn-social-share-css');
857
858 wp_register_script('presslearn-social-share-js', false, array('jquery'), PRESSLEARN_PLUGIN_VERSION, true);
859 wp_enqueue_script('presslearn-social-share-js');
860 }
861
862 if (strpos($hook, 'presslearn-quick-button') !== false) {
863 wp_register_style('presslearn-quick-button-css', false);
864 wp_enqueue_style('presslearn-quick-button-css');
865
866 wp_register_script('presslearn-quick-button-js', false, array('jquery'), PRESSLEARN_PLUGIN_VERSION, true);
867 wp_enqueue_script('presslearn-quick-button-js');
868 }
869
870 wp_localize_script('presslearn-admin-js', 'presslearn_admin', array(
871 'ajax_url' => admin_url('admin-ajax.php'),
872 'nonce' => wp_create_nonce('presslearn_admin_nonce'),
873 'api_url' => rest_url('presslearn/v1'),
874 'site_url' => site_url(),
875 'ip_address' => presslearn_plugin()->get_ip_address(),
876 'is_default_permalink' => empty(get_option('permalink_structure')),
877 'block_expiry_days' => get_option('presslearn_click_protection_block_expiry_days', 30)
878 ));
879 }
880
881 public function register_admin_menu() {
882 $icon_url = PRESSLEARN_PLUGIN_URL . 'assets/images/admin_badge.png';
883
884 add_menu_page(
885 'AL Pack 설정',
886 'AL Pack',
887 'manage_options',
888 'presslearn-settings',
889 array($this, 'render_settings_page'),
890 $icon_url,
891 2
892 );
893
894 add_submenu_page(
895 'presslearn-settings',
896 'AL Pack 대시보드',
897 '대시보드',
898 'manage_options',
899 'presslearn-settings',
900 array($this, 'render_settings_page')
901 );
902
903 if ($this->is_activated) {
904 add_submenu_page(
905 'presslearn-settings',
906 '스마트 스크롤',
907 '스마트 스크롤',
908 'manage_options',
909 'presslearn-scroll-depth',
910 array($this, 'render_advanced_page')
911 );
912
913 add_submenu_page(
914 'presslearn-settings',
915 '씬 애널리틱스',
916 '씬 애널리틱스',
917 'manage_options',
918 'presslearn-analytics',
919 array($this, 'render_analytics_page')
920 );
921
922 add_submenu_page(
923 'presslearn-settings',
924 '애드 프로�
925 �터',
926 '애드 프로�
927 �터',
928 'manage_options',
929 'presslearn-click-protection',
930 array($this, 'render_click_protection_page')
931 );
932
933 add_submenu_page(
934 'presslearn-settings',
935 '애드클리커',
936 '애드클리커',
937 'manage_options',
938 'presslearn-ad-clicker',
939 array($this, 'render_ad_clicker_page')
940 );
941
942 add_submenu_page(
943 'presslearn-settings',
944 '다이나믹 배너',
945 '다이나믹 배너',
946 'manage_options',
947 'presslearn-dynamic-banner',
948 array($this, 'render_dynamic_banner_page')
949 );
950
951 add_submenu_page(
952 'presslearn-settings',
953 '소�
954 � 공유',
955 '소�
956 � 공유',
957 'manage_options',
958 'presslearn-social-share',
959 array($this, 'render_social_share_page')
960 );
961
962 add_submenu_page(
963 'presslearn-settings',
964 '빠른 버튼 생성',
965 '빠른 버튼 생성',
966 'manage_options',
967 'presslearn-quick-button',
968 array($this, 'render_quick_button_page')
969 );
970
971 add_submenu_page(
972 'presslearn-settings',
973 'Ads 매니저',
974 'Ads 매니저',
975 'manage_options',
976 'presslearn-ads',
977 array($this, 'render_ads_page')
978 );
979
980 add_submenu_page(
981 'presslearn-settings',
982 '자동 인덱싱',
983 '자동 인덱싱',
984 'manage_options',
985 'presslearn-auto-index',
986 array($this, 'render_auto_index_page')
987 );
988
989 add_submenu_page(
990 'presslearn-settings',
991 '헤더 & 푸터',
992 '헤더 & 푸터',
993 'manage_options',
994 'presslearn-header-footer',
995 array($this, 'render_header_footer_page')
996 );
997 }
998 }
999
1000 public function render_settings_page() {
1001 $show_activated_notice = false;
1002 $show_access_denied_notice = false;
1003
1004 if (current_user_can('manage_options') && is_admin()) {
1005 if (isset($_GET['activated']) && sanitize_text_field(wp_unslash($_GET['activated'])) === 'true') {
1006 $show_activated_notice = true;
1007 }
1008
1009 if (isset($_GET['access_denied']) && sanitize_text_field(wp_unslash($_GET['access_denied'])) === 'true') {
1010 $show_access_denied_notice = true;
1011 }
1012 }
1013
1014 if ($show_activated_notice) {
1015 ?>
1016 <div class="notice notice-success is-dismissible">
1017 <p>프레스런 통합 플러그인이 성공적으로 활성화되었습니다</p>
1018 </div>
1019 <?php
1020 }
1021
1022 if ($show_access_denied_notice) {
1023 ?>
1024 <div class="notice notice-error is-dismissible">
1025 <p>프레스런 통합 플러그인이 활성화되지 않았습니다. 플러그인을 활성화해야 모든 기능에 접근할 있습니다.</p>
1026 </div>
1027 <?php
1028 }
1029
1030 include_once PRESSLEARN_PLUGIN_DIR . 'templates/admin-settings.php';
1031 }
1032
1033
1034 public function render_analytics_page() {
1035 if (!$this->verify_activation()) {
1036 return;
1037 }
1038
1039 include_once PRESSLEARN_PLUGIN_DIR . 'templates/admin-analytics.php';
1040 }
1041
1042 public function render_advanced_page() {
1043 if (!$this->verify_activation()) {
1044 return;
1045 }
1046
1047 include_once PRESSLEARN_PLUGIN_DIR . 'templates/admin-scroll-depth.php';
1048 }
1049
1050 public function render_click_protection_page() {
1051 if (!$this->verify_activation()) {
1052 return;
1053 }
1054
1055 include_once PRESSLEARN_PLUGIN_DIR . 'templates/admin-click-protection.php';
1056 }
1057
1058 public function render_ad_clicker_page() {
1059 if (!$this->verify_activation()) {
1060 return;
1061 }
1062
1063 include_once PRESSLEARN_PLUGIN_DIR . 'templates/admin-adclicker.php';
1064 }
1065
1066 public function render_dynamic_banner_page() {
1067 if (!$this->verify_activation()) {
1068 return;
1069 }
1070
1071 include_once PRESSLEARN_PLUGIN_DIR . 'templates/admin-dynamic-banner.php';
1072 }
1073
1074 public function render_social_share_page() {
1075 if (!$this->verify_activation()) {
1076 return;
1077 }
1078
1079 include_once PRESSLEARN_PLUGIN_DIR . 'templates/admin-social-share.php';
1080 }
1081
1082 public function render_quick_button_page() {
1083 if (!$this->verify_activation()) {
1084 return;
1085 }
1086
1087 include_once PRESSLEARN_PLUGIN_DIR . 'templates/admin-quick-button.php';
1088 }
1089
1090 public function render_ads_page() {
1091 if (!$this->verify_activation()) {
1092 return;
1093 }
1094
1095 include_once PRESSLEARN_PLUGIN_DIR . 'templates/admin-ads.php';
1096 }
1097
1098 public function render_auto_index_page() {
1099 if (!$this->verify_activation()) {
1100 return;
1101 }
1102
1103 include_once PRESSLEARN_PLUGIN_DIR . 'templates/admin-auto-index.php';
1104 }
1105
1106 public function render_header_footer_page() {
1107 if (!$this->verify_activation()) {
1108 return;
1109 }
1110
1111 include_once PRESSLEARN_PLUGIN_DIR . 'templates/admin-header-footer.php';
1112 }
1113
1114 private function verify_activation() {
1115 if (!$this->is_activated) {
1116 wp_redirect(admin_url('admin.php?page=presslearn-settings&access_denied=true'));
1117 exit;
1118 }
1119
1120 return true;
1121 }
1122
1123
1124 public function activate_plugin($key) {
1125 $is_valid = strlen($key) >= 32;
1126
1127 if ($is_valid) {
1128 update_option($this->option_key, $key);
1129 $this->is_activated = true;
1130 return true;
1131 }
1132
1133 return false;
1134 }
1135
1136 private function check_activation() {
1137 $key = get_option($this->option_key, '');
1138 $this->is_activated = !empty($key);
1139 }
1140
1141 public function is_plugin_activated() {
1142 return $this->is_activated;
1143 }
1144
1145 public function get_kakao_login_url() {
1146 $https = isset($_SERVER['HTTPS']) && sanitize_text_field(wp_unslash($_SERVER['HTTPS'])) === 'on';
1147 $host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '';
1148 $request_uri = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '';
1149
1150 $current_url = ($https ? "https" : "http") . "://" . $host . $request_uri;
1151 $kakao_login_url = 'https://presslearn.co.kr/login?redirect_url=' . urlencode($current_url) . '&popup=true';
1152 return $kakao_login_url;
1153 }
1154
1155 public function add_tracking_code() {
1156 $analytics_enabled = get_option('presslearn_analytics_enabled', 'no');
1157 $exclude_admin = get_option('presslearn_analytics_exclude_admin', '');
1158
1159 if ($analytics_enabled !== 'yes') {
1160 return;
1161 }
1162
1163 if (current_user_can('manage_options') && get_option('presslearn_analytics_exclude_admin') === 'yes') {
1164 return;
1165 }
1166
1167 $excluded_ips = array_map('trim', explode(',', $exclude_admin));
1168 $user_ip = $this->get_ip_address();
1169
1170 if (in_array($user_ip, $excluded_ips)) {
1171 return;
1172 }
1173
1174 wp_enqueue_script(
1175 'presslearn-analytics-tracking',
1176 PRESSLEARN_PLUGIN_URL . 'assets/js/analytics-tracking.js',
1177 array('jquery'),
1178 PRESSLEARN_PLUGIN_VERSION,
1179 true
1180 );
1181
1182 wp_localize_script(
1183 'presslearn-analytics-tracking',
1184 'pressleanAnalytics',
1185 array(
1186 'ajaxurl' => admin_url('admin-ajax.php'),
1187 'nonce' => wp_create_nonce('presslearn_tracking_nonce')
1188 )
1189 );
1190 }
1191
1192 public function get_ip_address() {
1193 $ip = '';
1194 $use_cloudflare = get_option('presslearn_analytics_use_cloudflare', 'no');
1195
1196 if ($use_cloudflare === 'yes' && isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
1197 $ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_CF_CONNECTING_IP']));
1198 } else {
1199 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
1200 $ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_CLIENT_IP']));
1201 } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1202 $ip_list = explode(',', sanitize_text_field(wp_unslash($_SERVER['HTTP_X_FORWARDED_FOR'])));
1203 $ip = trim($ip_list[0]);
1204 } else {
1205 $ip = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '';
1206 }
1207 }
1208
1209 return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : '';
1210 }
1211
1212 public function get_visitor_country($ip) {
1213 $ip = filter_var($ip, FILTER_VALIDATE_IP);
1214
1215 if (empty($ip) || $ip == '127.0.0.1' || $ip == '::1') {
1216 return '';
1217 }
1218
1219 $api_url = 'http://ip-api.com/json/' . esc_attr($ip);
1220 $response = wp_remote_get($api_url, array(
1221 'timeout' => 5,
1222 'headers' => array(
1223 'Accept' => 'application/json'
1224 )
1225 ));
1226
1227 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
1228 $data = json_decode(wp_remote_retrieve_body($response), true);
1229 if (isset($data['country']) && $data['status'] === 'success') {
1230 return sanitize_text_field($data['country']);
1231 }
1232 }
1233
1234 return '';
1235 }
1236
1237 public function get_visitor_country_code($ip) {
1238 $ip = filter_var($ip, FILTER_VALIDATE_IP);
1239
1240 if (empty($ip) || $ip == '127.0.0.1' || $ip == '::1') {
1241 return '';
1242 }
1243
1244 $cache_key = 'presslearn_country_code_' . md5($ip);
1245 $cached_code = get_transient($cache_key);
1246 if ($cached_code !== false) {
1247 return $cached_code;
1248 }
1249
1250 $api_url = 'http://ip-api.com/json/' . esc_attr($ip) . '?fields=countryCode,status';
1251 $response = wp_remote_get($api_url, array(
1252 'timeout' => 5,
1253 'headers' => array(
1254 'Accept' => 'application/json'
1255 )
1256 ));
1257
1258 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
1259 $data = json_decode(wp_remote_retrieve_body($response), true);
1260 if (isset($data['countryCode']) && $data['status'] === 'success') {
1261 $country_code = sanitize_text_field($data['countryCode']);
1262 set_transient($cache_key, $country_code, 86400);
1263 return $country_code;
1264 }
1265 }
1266
1267 return '';
1268 }
1269
1270 public function register_tracking_ajax() {
1271 add_action('wp_ajax_presslearn_track_pageview', array($this, 'track_pageview'));
1272 add_action('wp_ajax_nopriv_presslearn_track_pageview', array($this, 'track_pageview'));
1273 }
1274
1275 public function track_pageview() {
1276 check_ajax_referer('presslearn_tracking_nonce', 'nonce');
1277
1278 $visitor_id = isset($_POST['visitor_id']) ? sanitize_text_field(wp_unslash($_POST['visitor_id'])) : '';
1279 $url = isset($_POST['url']) ? esc_url_raw(wp_unslash($_POST['url'])) : '';
1280 $title = isset($_POST['title']) ? sanitize_text_field(wp_unslash($_POST['title'])) : '';
1281 $referrer = isset($_POST['referrer']) ? esc_url_raw(wp_unslash($_POST['referrer'])) : '';
1282 $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
1283 $ip = $this->get_ip_address();
1284 $country = $this->get_visitor_country($ip);
1285
1286 if (!empty($referrer)) {
1287 if (strpos($referrer, 'http') !== 0 && strpos($referrer, '//') !== 0) {
1288 $referrer = 'https://' . $referrer;
1289 }
1290
1291 if (strpos($referrer, '//') === 0) {
1292 $referrer = 'https:' . $referrer;
1293 }
1294
1295 $site_host = wp_parse_url(site_url(), PHP_URL_HOST);
1296 $referrer_host = wp_parse_url($referrer, PHP_URL_HOST);
1297
1298 if ($referrer_host === $site_host) {
1299 }
1300
1301 } else {
1302 }
1303
1304 $this->create_analytics_tables();
1305
1306 global $wpdb;
1307 $table_pageviews = $wpdb->prefix . 'presslearn_pageviews';
1308 $table_visitors = $wpdb->prefix . 'presslearn_visitors';
1309
1310 if ($wpdb->get_var("SHOW TABLES LIKE '$table_pageviews'") != $table_pageviews) {
1311 $this->create_analytics_tables();
1312 if ($wpdb->get_var("SHOW TABLES LIKE '$table_pageviews'") != $table_pageviews) {
1313 wp_send_json_error('데이터베이스 �
1314 �이블을 생성할 수 없습니다.');
1315 wp_die();
1316 }
1317 }
1318
1319 $result = $wpdb->insert(
1320 $table_pageviews,
1321 array(
1322 'url' => $url,
1323 'title' => $title,
1324 'visitor_id' => $visitor_id,
1325 'referrer' => $referrer,
1326 'user_agent' => $user_agent,
1327 'country' => $country,
1328 'ip' => $ip,
1329 'created_at' => current_time('mysql')
1330 )
1331 );
1332
1333 if ($wpdb->last_error) {
1334 wp_send_json_error('데이터베이스 오류: ' . $wpdb->last_error);
1335 wp_die();
1336 } else {
1337 error_log('Pageview record inserted successfully: ' . $result);
1338
1339 $post_id = url_to_postid($url);
1340 if ($post_id > 0) {
1341 $cache_date = current_time('Y-m-d');
1342 wp_cache_delete("presslearn_visitors_{$post_id}_{$cache_date}", 'presslearn_analytics');
1343 wp_cache_delete("presslearn_today_{$post_id}_{$cache_date}", 'presslearn_analytics');
1344 wp_cache_delete("presslearn_week_{$post_id}_{$cache_date}", 'presslearn_analytics');
1345 wp_cache_delete("presslearn_month_{$post_id}_{$cache_date}", 'presslearn_analytics');
1346
1347 delete_post_meta($post_id, '_presslearn_post_views');
1348 }
1349 }
1350
1351 $visitor = $wpdb->get_row($wpdb->prepare(
1352 "SELECT * FROM $table_visitors WHERE visitor_id = %s",
1353 $visitor_id
1354 ));
1355
1356 if ($visitor) {
1357 $wpdb->update(
1358 $table_visitors,
1359 array(
1360 'last_visit' => current_time('mysql'),
1361 'visits' => $visitor->visits + 1
1362 ),
1363 array('visitor_id' => $visitor_id)
1364 );
1365 } else {
1366 $wpdb->insert(
1367 $table_visitors,
1368 array(
1369 'visitor_id' => $visitor_id,
1370 'first_visit' => current_time('mysql'),
1371 'last_visit' => current_time('mysql')
1372 )
1373 );
1374 }
1375
1376 if (!empty($referrer)) {
1377 $referrer_host = wp_parse_url($referrer, PHP_URL_HOST);
1378 error_log('Referrer host: ' . $referrer_host);
1379
1380 if (!empty($referrer_host)) {
1381 $table_referrers = $wpdb->prefix . 'presslearn_referrers';
1382
1383 if ($wpdb->get_var("SHOW TABLES LIKE '$table_referrers'") != $table_referrers) {
1384 $this->create_analytics_tables();
1385 }
1386
1387 $existing_referrer = $wpdb->get_row($wpdb->prepare(
1388 "SELECT * FROM $table_referrers WHERE referrer_host = %s",
1389 $referrer_host
1390 ));
1391
1392 if ($existing_referrer) {
1393 $wpdb->update(
1394 $table_referrers,
1395 array(
1396 'count' => $existing_referrer->count + 1,
1397 'last_visit' => current_time('mysql')
1398 ),
1399 array('id' => $existing_referrer->id)
1400 );
1401 } else {
1402 $wpdb->insert(
1403 $table_referrers,
1404 array(
1405 'referrer_host' => $referrer_host,
1406 'referrer_url' => $referrer,
1407 'count' => 1,
1408 'last_visit' => current_time('mysql')
1409 )
1410 );
1411 }
1412
1413 if ($wpdb->last_error) {
1414 error_log('Referrer insert/update error: ' . $wpdb->last_error);
1415 } else {
1416 error_log('Referrer record processed for: ' . $referrer_host);
1417 }
1418 }
1419 }
1420
1421 wp_send_json_success();
1422 wp_die();
1423 }
1424
1425 public function create_analytics_tables() {
1426 global $wpdb;
1427 $charset_collate = $wpdb->get_charset_collate();
1428
1429 $table_pageviews = $wpdb->prefix . 'presslearn_pageviews';
1430 $table_visitors = $wpdb->prefix . 'presslearn_visitors';
1431 $table_referrers = $wpdb->prefix . 'presslearn_referrers';
1432
1433 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
1434
1435 $wpdb->suppress_errors();
1436
1437 $tables_created = true;
1438
1439 if($wpdb->get_var("SHOW TABLES LIKE '$table_pageviews'") != $table_pageviews) {
1440 $sql = "CREATE TABLE IF NOT EXISTS $table_pageviews (
1441 id bigint(20) NOT NULL AUTO_INCREMENT,
1442 url varchar(255) NOT NULL,
1443 title text NOT NULL,
1444 visitor_id varchar(32) NOT NULL,
1445 referrer text,
1446 user_agent text,
1447 country varchar(50),
1448 ip varchar(100),
1449 created_at datetime DEFAULT CURRENT_TIMESTAMP,
1450 PRIMARY KEY (id),
1451 KEY visitor_id (visitor_id),
1452 KEY url (url),
1453 KEY created_at (created_at)
1454 ) $charset_collate;";
1455
1456 $result = dbDelta($sql);
1457 if (empty($result)) {
1458 $tables_created = false;
1459 error_log('Error creating presslearn_pageviews table');
1460 }
1461 }
1462
1463 if($wpdb->get_var("SHOW TABLES LIKE '$table_visitors'") != $table_visitors) {
1464 $sql = "CREATE TABLE IF NOT EXISTS $table_visitors (
1465 id bigint(20) NOT NULL AUTO_INCREMENT,
1466 visitor_id varchar(32) NOT NULL,
1467 first_visit datetime DEFAULT CURRENT_TIMESTAMP,
1468 last_visit datetime DEFAULT CURRENT_TIMESTAMP,
1469 visits int(11) DEFAULT 1,
1470 PRIMARY KEY (id),
1471 UNIQUE KEY visitor_id (visitor_id),
1472 KEY first_visit (first_visit),
1473 KEY last_visit (last_visit)
1474 ) $charset_collate;";
1475
1476 $result = dbDelta($sql);
1477 if (empty($result)) {
1478 $tables_created = false;
1479 error_log('Error creating presslearn_visitors table');
1480 }
1481 }
1482
1483 if($wpdb->get_var("SHOW TABLES LIKE '$table_referrers'") != $table_referrers) {
1484 $sql = "CREATE TABLE IF NOT EXISTS $table_referrers (
1485 id bigint(20) NOT NULL AUTO_INCREMENT,
1486 referrer_host varchar(255),
1487 referrer_url text,
1488 count int(11) DEFAULT 1,
1489 last_visit datetime DEFAULT CURRENT_TIMESTAMP,
1490 PRIMARY KEY (id),
1491 KEY referrer_host (referrer_host),
1492 KEY last_visit (last_visit)
1493 ) $charset_collate;";
1494
1495 $result = dbDelta($sql);
1496 if (empty($result)) {
1497 $tables_created = false;
1498 error_log('Error creating presslearn_referrers table');
1499 }
1500 }
1501
1502 return $tables_created;
1503 }
1504
1505 public function get_visitor_ip_for_protection() {
1506 $use_cloudflare = get_option('presslearn_click_protection_use_cloudflare', 'no');
1507
1508 $ip = '';
1509
1510 if ($use_cloudflare === 'yes' && isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
1511 $ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_CF_CONNECTING_IP']));
1512 }
1513 else {
1514 if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
1515 $ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_CLIENT_IP']));
1516 } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1517 $ip_list = explode(',', sanitize_text_field(wp_unslash($_SERVER['HTTP_X_FORWARDED_FOR'])));
1518 $ip = trim($ip_list[0]);
1519 } else {
1520 $ip = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '';
1521 }
1522 }
1523
1524 return $ip;
1525 }
1526
1527 public function add_admin_bar_menu($wp_admin_bar) {
1528 if (!$this->is_activated) {
1529 return;
1530 }
1531
1532 $wp_admin_bar->add_node(array(
1533 'id' => 'presslearn-menu',
1534 'title' => 'AL Pack',
1535 'href' => admin_url('admin.php?page=presslearn-settings'),
1536 'meta' => array(
1537 'title' => 'AL Pack'
1538 )
1539 ));
1540
1541 $wp_admin_bar->add_node(array(
1542 'id' => 'presslearn-settings',
1543 'parent' => 'presslearn-menu',
1544 'title' => '대시보드',
1545 'href' => admin_url('admin.php?page=presslearn-settings')
1546 ));
1547
1548 $wp_admin_bar->add_node(array(
1549 'id' => 'presslearn-analytics',
1550 'parent' => 'presslearn-menu',
1551 'title' => '씬 애널리틱스',
1552 'href' => admin_url('admin.php?page=presslearn-analytics')
1553 ));
1554
1555 $wp_admin_bar->add_node(array(
1556 'id' => 'presslearn-scroll-depth',
1557 'parent' => 'presslearn-menu',
1558 'title' => '스마트 스크롤',
1559 'href' => admin_url('admin.php?page=presslearn-scroll-depth')
1560 ));
1561
1562 $wp_admin_bar->add_node(array(
1563 'id' => 'presslearn-click-protection',
1564 'parent' => 'presslearn-menu',
1565 'title' => '애드 프로�
1566 �터',
1567 'href' => admin_url('admin.php?page=presslearn-click-protection')
1568 ));
1569
1570 $wp_admin_bar->add_node(array(
1571 'id' => 'presslearn-ad-clicker',
1572 'parent' => 'presslearn-menu',
1573 'title' => '애드클리커',
1574 'href' => admin_url('admin.php?page=presslearn-ad-clicker')
1575 ));
1576
1577 $wp_admin_bar->add_node(array(
1578 'id' => 'presslearn-dynamic-banner',
1579 'parent' => 'presslearn-menu',
1580 'title' => '다이나믹 배너',
1581 'href' => admin_url('admin.php?page=presslearn-dynamic-banner')
1582 ));
1583
1584 $wp_admin_bar->add_node(array(
1585 'id' => 'presslearn-social-share',
1586 'parent' => 'presslearn-menu',
1587 'title' => '소�
1588 � 공유',
1589 'href' => admin_url('admin.php?page=presslearn-social-share')
1590 ));
1591
1592 $wp_admin_bar->add_node(array(
1593 'id' => 'presslearn-quick-button',
1594 'parent' => 'presslearn-menu',
1595 'title' => '빠른 버튼 생성',
1596 'href' => admin_url('admin.php?page=presslearn-quick-button')
1597 ));
1598
1599 $wp_admin_bar->add_node(array(
1600 'id' => 'presslearn-ads',
1601 'parent' => 'presslearn-menu',
1602 'title' => 'Ads 매니저',
1603 'href' => admin_url('admin.php?page=presslearn-ads')
1604 ));
1605
1606 $wp_admin_bar->add_node(array(
1607 'id' => 'presslearn-auto-index',
1608 'parent' => 'presslearn-menu',
1609 'title' => '자동 인덱싱',
1610 'href' => admin_url('admin.php?page=presslearn-auto-index')
1611 ));
1612
1613 $wp_admin_bar->add_node(array(
1614 'id' => 'presslearn-header-footer',
1615 'parent' => 'presslearn-menu',
1616 'title' => '헤더 & 푸터',
1617 'href' => admin_url('admin.php?page=presslearn-header-footer')
1618 ));
1619 }
1620 }
1621
1622 function presslearn_plugin() {
1623 return PressLearn_Plugin::get_instance();
1624 }
1625
1626 presslearn_plugin();
1627
1628 add_action('init', 'presslearn_check_country_access_block', 1);
1629
1630 function presslearn_check_country_access_block() {
1631 if (is_admin()) {
1632 return;
1633 }
1634
1635 $click_protection_enabled = get_option('presslearn_click_protection_enabled', 'no');
1636 if ($click_protection_enabled !== 'yes') {
1637 return;
1638 }
1639
1640 $blocked_countries = get_option('presslearn_click_protection_blocked_countries', array());
1641 if (empty($blocked_countries)) {
1642 return;
1643 }
1644
1645 $user_ip = presslearn_plugin()->get_visitor_ip_for_protection();
1646
1647 $user_country_code = presslearn_plugin()->get_visitor_country_code($user_ip);
1648
1649 $is_blocked = false;
1650 foreach ($blocked_countries as $country) {
1651 if (isset($country['code']) && $country['code'] === $user_country_code) {
1652 $is_blocked = true;
1653 break;
1654 }
1655 }
1656
1657 if ($is_blocked) {
1658 while (ob_get_level()) {
1659 ob_end_clean();
1660 }
1661
1662 header('HTTP/1.1 403 Forbidden');
1663 header('Content-Type: text/html; charset=UTF-8');
1664
1665 echo '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Access Denied</title></head><body></body></html>';
1666 exit;
1667 }
1668 }
1669
1670 add_action('wp_ajax_presslearn_get_allowed_ips', 'presslearn_get_allowed_ips');
1671 add_action('wp_ajax_presslearn_add_allowed_ip', 'presslearn_add_allowed_ip');
1672 add_action('wp_ajax_presslearn_delete_allowed_ip', 'presslearn_delete_allowed_ip');
1673 add_action('wp_ajax_presslearn_get_blocked_ips', 'presslearn_get_blocked_ips');
1674 add_action('wp_ajax_presslearn_add_blocked_ip', 'presslearn_add_blocked_ip');
1675 add_action('wp_ajax_presslearn_delete_blocked_ip', 'presslearn_delete_blocked_ip');
1676 add_action('wp_ajax_presslearn_delete_analytics_data', 'presslearn_delete_analytics_data');
1677 add_action('wp_ajax_presslearn_get_blocked_countries', 'presslearn_get_blocked_countries');
1678 add_action('wp_ajax_presslearn_save_blocked_countries', 'presslearn_save_blocked_countries');
1679
1680 function presslearn_get_blocked_countries() {
1681 check_ajax_referer('presslearn_ip_nonce', 'nonce');
1682
1683 if (current_user_can('manage_options')) {
1684 $blocked_countries = get_option('presslearn_click_protection_blocked_countries', array());
1685
1686 wp_send_json_success(array(
1687 'countries' => $blocked_countries
1688 ));
1689 }
1690
1691 wp_send_json_error(array('message' => '권한이 없습니다.'));
1692 wp_die();
1693 }
1694
1695 function presslearn_save_blocked_countries() {
1696 check_ajax_referer('presslearn_ip_nonce', 'nonce');
1697
1698 if (current_user_can('manage_options')) {
1699 $countries_json = isset($_POST['countries']) ? wp_unslash($_POST['countries']) : '[]';
1700 $countries = json_decode($countries_json, true);
1701
1702 if (json_last_error() !== JSON_ERROR_NONE) {
1703 wp_send_json_error(array('message' => '잘못된 데이터 형식�
1704 니다.'));
1705 wp_die();
1706 }
1707
1708 $valid_countries = array();
1709 foreach ($countries as $country) {
1710 if (isset($country['code']) && isset($country['name'])) {
1711 $valid_countries[] = array(
1712 'code' => sanitize_text_field($country['code']),
1713 'name' => sanitize_text_field($country['name'])
1714 );
1715 }
1716 }
1717
1718 update_option('presslearn_click_protection_blocked_countries', $valid_countries);
1719
1720 wp_send_json_success(array(
1721 'message' => '차단 국가 목록이 저장되었습니다.',
1722 'countries' => $valid_countries
1723 ));
1724 }
1725
1726 wp_send_json_error(array('message' => '권한이 없습니다.'));
1727 wp_die();
1728 }
1729
1730 function presslearn_get_allowed_ips() {
1731 check_ajax_referer('presslearn_ip_nonce', 'nonce');
1732
1733 if (current_user_can('manage_options')) {
1734 $allowed_ips = get_option('presslearn_click_protection_allowed_ips', array());
1735
1736 wp_send_json_success(array(
1737 'ips' => $allowed_ips
1738 ));
1739 }
1740
1741 wp_send_json_error(array('message' => '권한이 없습니다.'));
1742 wp_die();
1743 }
1744
1745 function presslearn_add_allowed_ip() {
1746 check_ajax_referer('presslearn_ip_nonce', 'nonce');
1747
1748 if (current_user_can('manage_options')) {
1749 $ip = isset($_POST['ip']) ? sanitize_text_field(wp_unslash($_POST['ip'])) : '';
1750
1751 if (empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) {
1752 wp_send_json_error(array('message' => '유효하지 않은 IP 주소�
1753 니다.'));
1754 wp_die();
1755 }
1756
1757 $allowed_ips = get_option('presslearn_click_protection_allowed_ips', array());
1758
1759 foreach ($allowed_ips as $item) {
1760 if ($item['ip'] === $ip) {
1761 wp_send_json_error(array('message' => '이미 등록된 IP 주소�
1762 니다.'));
1763 wp_die();
1764 }
1765 }
1766
1767 $allowed_ips[] = array(
1768 'ip' => $ip,
1769 'date' => current_time('Y-m-d')
1770 );
1771
1772 update_option('presslearn_click_protection_allowed_ips', $allowed_ips);
1773
1774 wp_send_json_success(array(
1775 'message' => 'IP가 성공적으로 추가되었습니다.',
1776 'ips' => $allowed_ips
1777 ));
1778 }
1779
1780 wp_die();
1781 }
1782
1783 function presslearn_delete_allowed_ip() {
1784 check_ajax_referer('presslearn_ip_nonce', 'nonce');
1785
1786 if (current_user_can('manage_options')) {
1787 $ip = isset($_POST['ip']) ? sanitize_text_field(wp_unslash($_POST['ip'])) : '';
1788
1789 if (empty($ip)) {
1790 wp_send_json_error(array('message' => '삭제할 IP 주소가 지정되지 않았습니다.'));
1791 wp_die();
1792 }
1793
1794 $allowed_ips = get_option('presslearn_click_protection_allowed_ips', array());
1795
1796 foreach ($allowed_ips as $key => $item) {
1797 if ($item['ip'] === $ip) {
1798 unset($allowed_ips[$key]);
1799 break;
1800 }
1801 }
1802
1803 $allowed_ips = array_values($allowed_ips);
1804
1805 update_option('presslearn_click_protection_allowed_ips', $allowed_ips);
1806
1807 wp_send_json_success(array(
1808 'message' => 'IP가 성공적으로 삭제되었습니다.',
1809 'ips' => $allowed_ips
1810 ));
1811 }
1812
1813 wp_die();
1814 }
1815
1816 function presslearn_get_blocked_ips() {
1817 check_ajax_referer('presslearn_ip_nonce', 'nonce');
1818
1819 if (current_user_can('manage_options')) {
1820 $blocked_ips = get_option('presslearn_click_protection_blocked_ips', array());
1821
1822 wp_send_json_success(array(
1823 'ips' => $blocked_ips
1824 ));
1825 }
1826
1827 wp_send_json_error(array('message' => '권한이 없습니다.'));
1828 wp_die();
1829 }
1830
1831 function presslearn_add_blocked_ip() {
1832 check_ajax_referer('presslearn_ip_nonce', 'nonce');
1833
1834 if (current_user_can('manage_options')) {
1835 $ip = isset($_POST['ip']) ? sanitize_text_field(wp_unslash($_POST['ip'])) : '';
1836 $permanent = isset($_POST['permanent']) ? sanitize_text_field(wp_unslash($_POST['permanent'])) : '0';
1837
1838 if (empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) {
1839 wp_send_json_error(array('message' => '유효하지 않은 IP 주소�
1840 니다.'));
1841 wp_die();
1842 }
1843
1844 $blocked_ips = get_option('presslearn_click_protection_blocked_ips', array());
1845
1846 foreach ($blocked_ips as $item) {
1847 if ($item['ip'] === $ip) {
1848 wp_send_json_error(array('message' => '이미 차단된 IP 주소�
1849 니다.'));
1850 wp_die();
1851 }
1852 }
1853
1854 $current_date = current_time('Y-m-d');
1855
1856 if ($permanent === '1') {
1857 $expires = '';
1858 $reason = '수동 차단 (영구)';
1859 } else {
1860 $block_expiry_days = get_option('presslearn_click_protection_block_expiry_days', 30);
1861 $expires = $block_expiry_days > 0 ? gmdate('Y-m-d', strtotime("+{$block_expiry_days} days")) : '';
1862 $reason = '수동 차단';
1863 }
1864
1865 $blocked_ips[] = array(
1866 'ip' => $ip,
1867 'date' => $current_date,
1868 'block_date' => $current_date,
1869 'reason' => $reason,
1870 'expires' => $expires
1871 );
1872
1873 update_option('presslearn_click_protection_blocked_ips', $blocked_ips);
1874
1875 wp_send_json_success(array(
1876 'message' => 'IP가 성공적으로 차단되었습니다.',
1877 'ips' => $blocked_ips
1878 ));
1879 }
1880
1881 wp_die();
1882 }
1883
1884 function presslearn_delete_blocked_ip() {
1885 check_ajax_referer('presslearn_ip_nonce', 'nonce');
1886
1887 if (current_user_can('manage_options')) {
1888 $ip = isset($_POST['ip']) ? sanitize_text_field(wp_unslash($_POST['ip'])) : '';
1889
1890 if (empty($ip)) {
1891 wp_send_json_error(array('message' => '삭제할 IP 주소가 지정되지 않았습니다.'));
1892 wp_die();
1893 }
1894
1895 $lock_key = 'presslearn_delete_blocked_' . md5($ip);
1896 if (get_transient($lock_key)) {
1897 wp_send_json_error(array('message' => '다른 요청이 처리 중�
1898 니다. 잠시 후 다시 시도해주세요.'));
1899 wp_die();
1900 }
1901
1902 set_transient($lock_key, true, 3);
1903
1904 try {
1905 $blocked_ips = get_option('presslearn_click_protection_blocked_ips', array());
1906 $ip_found = false;
1907
1908 foreach ($blocked_ips as $key => $item) {
1909 if ($item['ip'] === $ip) {
1910 unset($blocked_ips[$key]);
1911 $ip_found = true;
1912 break;
1913 }
1914 }
1915
1916 if (!$ip_found) {
1917 delete_transient($lock_key);
1918 wp_send_json_error(array('message' => '차단 목록에서 해당 IP를 찾을 수 없습니다.'));
1919 wp_die();
1920 }
1921
1922 $blocked_ips = array_values($blocked_ips);
1923
1924 update_option('presslearn_click_protection_blocked_ips_backup_delete', get_option('presslearn_click_protection_blocked_ips', array()));
1925
1926 update_option('presslearn_click_protection_blocked_ips', $blocked_ips);
1927
1928 $reset_signals = get_option('presslearn_reset_localStorage_signals', array());
1929 $reset_signals[$ip] = array(
1930 'timestamp' => time(),
1931 'expires' => time() + 3600
1932 );
1933 update_option('presslearn_reset_localStorage_signals', $reset_signals);
1934
1935 delete_transient($lock_key);
1936
1937 wp_send_json_success(array(
1938 'message' => 'IP 차단이 성공적으로 해제되었습니다.',
1939 'ips' => $blocked_ips,
1940 'reset_signal_sent' => true
1941 ));
1942
1943 } catch (Exception $e) {
1944 delete_transient($lock_key);
1945 wp_send_json_error(array('message' => '서버 오류가 발생했습니다.'));
1946 }
1947 }
1948
1949 wp_die();
1950 }
1951
1952 add_action('init', 'presslearn_setup_cron_for_ip_unblock');
1953
1954 function presslearn_setup_cron_for_ip_unblock() {
1955 if (!wp_next_scheduled('presslearn_check_blocked_ips_expiry')) {
1956 wp_schedule_event(strtotime('tomorrow midnight'), 'daily', 'presslearn_check_blocked_ips_expiry');
1957 }
1958 }
1959
1960 add_action('presslearn_check_blocked_ips_expiry', 'presslearn_check_and_remove_expired_blocked_ips');
1961
1962 function presslearn_cleanup_expired_reset_signals() {
1963 $reset_signals = get_option('presslearn_reset_localStorage_signals', array());
1964 $current_time = time();
1965 $updated = false;
1966
1967 foreach ($reset_signals as $ip => $signal) {
1968 if (isset($signal['expires']) && $current_time > $signal['expires']) {
1969 unset($reset_signals[$ip]);
1970 $updated = true;
1971 }
1972 }
1973
1974 if ($updated) {
1975 update_option('presslearn_reset_localStorage_signals', $reset_signals);
1976 }
1977 }
1978
1979 add_action('presslearn_check_blocked_ips_expiry', 'presslearn_cleanup_expired_reset_signals');
1980
1981 function presslearn_check_and_remove_expired_blocked_ips() {
1982 $blocked_ips = get_option('presslearn_click_protection_blocked_ips', array());
1983 if (empty($blocked_ips)) {
1984 return;
1985 }
1986
1987 $block_expiry_days = get_option('presslearn_click_protection_block_expiry_days', 30);
1988
1989 $current_date = new DateTime(current_time('Y-m-d'));
1990 $updated = false;
1991
1992 $new_blocked_ips = array();
1993
1994 foreach ($blocked_ips as $item) {
1995 $block_date = isset($item['block_date']) ? $item['block_date'] : $item['date'];
1996
1997 $block_date_obj = new DateTime($block_date);
1998
1999 $interval = $current_date->diff($block_date_obj);
2000 $days_since_blocked = $interval->days;
2001
2002 if ($days_since_blocked < $block_expiry_days) {
2003 $new_blocked_ips[] = $item;
2004 } else {
2005 $updated = true;
2006 }
2007 }
2008
2009 if ($updated) {
2010 update_option('presslearn_click_protection_blocked_ips', $new_blocked_ips);
2011 }
2012 }
2013
2014 register_deactivation_hook(__FILE__, 'presslearn_clear_cron_for_ip_unblock');
2015
2016 function presslearn_clear_cron_for_ip_unblock() {
2017 $timestamp = wp_next_scheduled('presslearn_check_blocked_ips_expiry');
2018 if ($timestamp) {
2019 wp_unschedule_event($timestamp, 'presslearn_check_blocked_ips_expiry');
2020 }
2021
2022 delete_option('presslearn_reset_localStorage_signals');
2023
2024 delete_option('presslearn_click_protection_blocked_ips_backup');
2025 delete_option('presslearn_click_protection_blocked_ips_backup_delete');
2026 }
2027
2028 function presslearn_block_ads_for_blocked_ips() {
2029 if (is_admin()) {
2030 return;
2031 }
2032
2033 $click_protection_enabled = get_option('presslearn_click_protection_enabled', 'no');
2034 if ($click_protection_enabled !== 'yes') {
2035 return;
2036 }
2037
2038 $use_cloudflare = get_option('presslearn_click_protection_use_cloudflare', 'no');
2039
2040 $blocked_ips = get_option('presslearn_click_protection_blocked_ips', array());
2041 if (empty($blocked_ips)) {
2042 return;
2043 }
2044
2045 $user_ip = presslearn_plugin()->get_visitor_ip_for_protection();
2046
2047 $is_blocked = false;
2048
2049 foreach ($blocked_ips as $item) {
2050 if ($item['ip'] === $user_ip) {
2051 $is_blocked = true;
2052 break;
2053 }
2054 }
2055
2056 if (!$is_blocked) {
2057 return;
2058 }
2059
2060 setcookie('pl_ad_blocked', '1', time() + 86400, '/');
2061
2062 wp_register_style('presslearn-ad-block-css', false);
2063 wp_enqueue_style('presslearn-ad-block-css');
2064
2065 $ad_block_css = "
2066 #google_esf,
2067 .adsbygoogle,
2068 iframe[id^=\"google_ads_\"],
2069 ins.adsbygoogle,
2070 ins.adsbygoogle-noablate,
2071 iframe[id^=\"aswift_\"],
2072 div[id^=\"aswift_\"],
2073 [data-ad-status],
2074 [data-adsbygoogle-status],
2075 [data-google-query-id],
2076 [data-google-container-id],
2077 [data-ad-format],
2078 ins[class*=\"adsbygoogle\"] {
2079 display: none !important;
2080 visibility: hidden !important;
2081 opacity: 0 !important;
2082 width: 0 !important;
2083 height: 0 !important;
2084 position: absolute !important;
2085 left: -9999px !important;
2086 top: -9999px !important;
2087 pointer-events: none !important;
2088 max-width: 0 !important;
2089 max-height: 0 !important;
2090 overflow: hidden !important;
2091 }
2092
2093 .right-side-rail-edge,
2094 .right-side-rail-dismiss-btn,
2095 [data-side-rail-status] {
2096 display: none !important;
2097 visibility: hidden !important;
2098 opacity: 0 !important;
2099 }
2100 ";
2101
2102 wp_add_inline_style('presslearn-ad-block-css', $ad_block_css);
2103
2104 wp_register_script('presslearn-ad-block-js', false, array(), PRESSLEARN_PLUGIN_VERSION, true);
2105 wp_enqueue_script('presslearn-ad-block-js');
2106
2107 $ad_block_script = "
2108 (function() {
2109 function preventAdLoad() {
2110
2111 window.adsbygoogle = [];
2112 window.google_ad_status = 1;
2113
2114 var adScripts = document.querySelectorAll('script[src*=\"pagead2.googlesyndication.com\"], script[src*=\"googleads\"], script[src*=\"adsbygoogle\"]');
2115 adScripts.forEach(function(script) {
2116 if (script && script.parentNode) {
2117 script.parentNode.removeChild(script);
2118 }
2119 });
2120 }
2121
2122 preventAdLoad();
2123 })();
2124
2125 document.addEventListener('DOMContentLoaded', function() {
2126
2127 function removeAdsElements() {
2128 const esfElements = document.querySelectorAll('#google_esf');
2129 esfElements.forEach(element => {
2130 element.style.display = 'none !important';
2131 if (element.parentNode) {
2132 element.parentNode.removeChild(element);
2133 }
2134 });
2135
2136 const adsSelectors = [
2137 '#adsbygoogle',
2138 '.adsbygoogle',
2139 'ins.adsbygoogle',
2140 'ins.adsbygoogle-noablate',
2141 'iframe[id^=\"google_ads_\"]',
2142 'iframe[id^=\"aswift_\"]',
2143 'div[id^=\"aswift_\"]',
2144 '[data-ad-status]',
2145 '[data-adsbygoogle-status]',
2146 '[data-google-query-id]',
2147 '[data-google-container-id]',
2148 '[data-ad-format]'
2149 ];
2150
2151 adsSelectors.forEach(selector => {
2152 const elements = document.querySelectorAll(selector);
2153 elements.forEach(element => {
2154 element.style.display = 'none !important';
2155 if (element.parentNode) {
2156 element.parentNode.removeChild(element);
2157 }
2158 });
2159 });
2160
2161 const styleElement = document.createElement('style');
2162 styleElement.textContent =
2163 '#google_esf, ' +
2164 '#adsbygoogle,' +
2165 '.adsbygoogle,' +
2166 'ins.adsbygoogle,' +
2167 'ins.adsbygoogle-noablate,' +
2168 'iframe[id^=\"google_ads_\"],' +
2169 'iframe[id^=\"aswift_\"],' +
2170 'div[id^=\"aswift_\"],' +
2171 '[data-ad-status],' +
2172 '[data-adsbygoogle-status],' +
2173 '[data-google-query-id],' +
2174 '[data-google-container-id],' +
2175 '[data-ad-format] { ' +
2176 'display: none !important; ' +
2177 'visibility: hidden !important;' +
2178 'opacity: 0 !important;' +
2179 'pointer-events: none !important;' +
2180 'width: 0px !important;' +
2181 'height: 0px !important;' +
2182 'position: absolute !important;' +
2183 'top: -9999px !important;' +
2184 'left: -9999px !important;' +
2185 '}';
2186 document.head.appendChild(styleElement);
2187 }
2188
2189 if (document.readyState === 'loading') {
2190 document.addEventListener('DOMContentLoaded', removeAdsElements);
2191 } else {
2192 removeAdsElements();
2193 }
2194
2195 const adObserver = new MutationObserver(function(mutations) {
2196 mutations.forEach(function(mutation) {
2197 if (mutation.addedNodes && mutation.addedNodes.length > 0) {
2198 for (let i = 0; i < mutation.addedNodes.length; i++) {
2199 const node = mutation.addedNodes[i];
2200 if (node.id === 'google_esf' || node.id === 'adsbygoogle') {
2201 node.style.display = 'none !important';
2202 if (node.parentNode) {
2203 node.parentNode.removeChild(node);
2204 }
2205 }
2206 }
2207 }
2208 });
2209 });
2210
2211 adObserver.observe(document.documentElement, {
2212 childList: true,
2213 subtree: true
2214 });
2215 });
2216 ";
2217
2218 wp_add_inline_script('presslearn-ad-block-js', $ad_block_script);
2219
2220 wp_register_script('presslearn-clear-blocked-flag-js', false, array(), PRESSLEARN_PLUGIN_VERSION, true);
2221 wp_enqueue_script('presslearn-clear-blocked-flag-js');
2222
2223 $clear_flag_script = "
2224 (function() {
2225 try {
2226 localStorage.setItem('adsenseBlocked', 'false');
2227 localStorage.setItem('adsenseClickCount', '0');
2228 localStorage.setItem('adsenseFirstClickTime', '0');
2229 } catch (e) {}
2230 })();
2231 ";
2232
2233 wp_add_inline_script('presslearn-clear-blocked-flag-js', $clear_flag_script);
2234 }
2235
2236 add_action('wp_body_open', 'presslearn_block_ads_for_blocked_ips', 1);
2237 add_action('wp_footer', 'presslearn_check_blocked_status', 1);
2238
2239 function presslearn_check_blocked_status() {
2240 if (is_admin()) {
2241 return;
2242 }
2243
2244 $click_protection_enabled = get_option('presslearn_click_protection_enabled', 'no');
2245 if ($click_protection_enabled !== 'yes') {
2246 return;
2247 }
2248
2249 $blocked_ips = get_option('presslearn_click_protection_blocked_ips', array());
2250 if (empty($blocked_ips)) {
2251 return;
2252 }
2253
2254 $user_ip = presslearn_plugin()->get_visitor_ip_for_protection();
2255 $is_blocked = false;
2256
2257 foreach ($blocked_ips as $item) {
2258 if ($item['ip'] === $user_ip) {
2259 $is_blocked = true;
2260 break;
2261 }
2262 }
2263
2264 if (!$is_blocked) {
2265 return;
2266 }
2267
2268 wp_register_script('presslearn-blocked-status-js', false, array(), PRESSLEARN_PLUGIN_VERSION, true);
2269 wp_enqueue_script('presslearn-blocked-status-js');
2270
2271 $blocked_status_script = "
2272 (function() {
2273
2274 function removeAds() {
2275 var selectors = [
2276 '#google_esf',
2277 '.adsbygoogle',
2278 'ins.adsbygoogle',
2279 'ins.adsbygoogle-noablate',
2280 'iframe[id^=\"google_ads_\"]',
2281 'iframe[id^=\"aswift_\"]',
2282 'div[id^=\"aswift_\"]',
2283 '[data-ad-status]',
2284 '[data-adsbygoogle-status]',
2285 '[data-google-query-id]',
2286 '[data-google-container-id]',
2287 '[data-ad-format]',
2288 'ins[class*=\"adsbygoogle\"],
2289 .right-side-rail-edge,
2290 .right-side-rail-dismiss-btn,
2291 '[data-side-rail-status]'
2292 ];
2293
2294 selectors.forEach(function(selector) {
2295 var elements = document.querySelectorAll(selector);
2296 elements.forEach(function(element) {
2297 if (element && element.parentNode) {
2298 element.parentNode.removeChild(element);
2299 }
2300 });
2301 });
2302 }
2303
2304 removeAds();
2305 setTimeout(removeAds, 1000);
2306 })();
2307 ";
2308
2309 wp_add_inline_script('presslearn-blocked-status-js', $blocked_status_script);
2310 }
2311
2312 function presslearn_add_ad_protection_script() {
2313 if (is_admin()) {
2314 return;
2315 }
2316
2317 $click_protection_enabled = get_option('presslearn_click_protection_enabled', 'no');
2318 if ($click_protection_enabled !== 'yes') {
2319 return;
2320 }
2321
2322 $blocked_ips = get_option('presslearn_click_protection_blocked_ips', array());
2323 $user_ip = presslearn_plugin()->get_visitor_ip_for_protection();
2324
2325 $allowed_ips = get_option('presslearn_click_protection_allowed_ips', array());
2326 $is_allowed = false;
2327
2328 foreach ($allowed_ips as $item) {
2329 if ($item['ip'] === $user_ip) {
2330 $is_allowed = true;
2331 break;
2332 }
2333 }
2334
2335 $is_blocked = false;
2336 foreach ($blocked_ips as $item) {
2337 if ($item['ip'] === $user_ip) {
2338 $is_blocked = true;
2339 break;
2340 }
2341 }
2342
2343 if ($is_allowed) {
2344 return;
2345 }
2346
2347 if ($is_blocked) {
2348 presslearn_block_ads_for_blocked_ips();
2349 return;
2350 }
2351
2352 $max_click_count = intval(get_option('presslearn_max_click_count', 10));
2353 $click_time_window = intval(get_option('presslearn_click_time_window', 30));
2354
2355 wp_register_style('presslearn-protection-modal-css', false);
2356 wp_enqueue_style('presslearn-protection-modal-css');
2357
2358 $modal_css = "
2359 .pl-modal-overlay {
2360 display: none;
2361 position: fixed;
2362 top: 0;
2363 left: 0;
2364 width: 100%;
2365 height: 100%;
2366 background-color: rgba(0, 0, 0, 0.7);
2367 z-index: 999999;
2368 justify-content: center;
2369 align-items: center;
2370 overflow: hidden;
2371 }
2372
2373 .pl-modal {
2374 background: white;
2375 padding: 30px;
2376 border-radius: 8px;
2377 max-width: 500px;
2378 width: 90%;
2379 text-align: center;
2380 box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
2381 position: relative;
2382 }
2383
2384 .pl-modal h2 {
2385 margin-top: 0;
2386 font-size: 24px;
2387 color: #d32f2f;
2388 }
2389
2390 .pl-modal p {
2391 margin: 15px 0;
2392 font-size: 16px;
2393 line-height: 1.5;
2394 }
2395
2396 .pl-modal-close {
2397 display: inline-block;
2398 background: #d32f2f;
2399 color: white;
2400 padding: 10px 20px;
2401 margin-top: 20px;
2402 border-radius: 4px;
2403 cursor: pointer;
2404 font-weight: bold;
2405 border: none;
2406 }
2407
2408 .pl-modal-close:hover {
2409 background: #b71c1c;
2410 }
2411
2412 .pl-blocked-ad-notice {
2413 display: block;
2414 padding: 15px;
2415 background-color: #fff8f8;
2416 border: 1px solid #ffdddd;
2417 text-align: center;
2418 font-size: 14px;
2419 color: #d32f2f;
2420 margin: 15px 0;
2421 border-radius: 4px;
2422 font-weight: bold;
2423 }
2424
2425 body.pl-modal-open {
2426 overflow: hidden;
2427 }
2428 ";
2429
2430 wp_add_inline_style('presslearn-protection-modal-css', $modal_css);
2431
2432 add_action('wp_footer', function() {
2433 ?>
2434 <div class="pl-modal-overlay" id="pl-block-modal">
2435 <div class="pl-modal">
2436 <h2><?php echo esc_html(get_option('presslearn_modal_title', '광고 차단 알림')); ?></h2>
2437 <p><?php echo esc_html(get_option('presslearn_modal_message', '광고 클릭 제한을 초과하여 광고가 차단되었습니다.')); ?></p>
2438 <p><?php echo esc_html(get_option('presslearn_modal_submessage', '단시간에 반복적인 광고 클릭은 시스�
2439 �에 의해 감지되며, IP가 수집되어 사이트 관리자가 확인 가능합니다.')); ?></p>
2440 <button class="pl-modal-close" id="pl-modal-close"><?php echo esc_html(get_option('presslearn_modal_button_text', '확인')); ?></button>
2441 </div>
2442 </div>
2443 <?php
2444 });
2445
2446 wp_register_script('presslearn-protection-script-js', false, array(), PRESSLEARN_PLUGIN_VERSION, true);
2447 wp_enqueue_script('presslearn-protection-script-js');
2448
2449 $user_ip = presslearn_plugin()->get_visitor_ip_for_protection();
2450 $reset_signals = get_option('presslearn_reset_localStorage_signals', array());
2451 $should_reset = false;
2452
2453 if (isset($reset_signals[$user_ip])) {
2454 $signal = $reset_signals[$user_ip];
2455 if (time() - $signal['timestamp'] < 3600) {
2456 $should_reset = true;
2457 unset($reset_signals[$user_ip]);
2458 update_option('presslearn_reset_localStorage_signals', $reset_signals);
2459 }
2460 }
2461
2462 $protection_script = "
2463 (function() {
2464 function getStorageItem(name, defaultValue) {
2465 try {
2466 const item = localStorage.getItem(name);
2467 return item !== null ? item : defaultValue;
2468 } catch (e) {
2469 return defaultValue;
2470 }
2471 }
2472
2473 function setStorageItem(name, value) {
2474 try {
2475 localStorage.setItem(name, value);
2476 return true;
2477 } catch (e) {
2478 return false;
2479 }
2480 }
2481
2482 function resetLocalStorageForUnblock() {
2483 try {
2484 localStorage.removeItem('adsenseBlocked');
2485 localStorage.removeItem('adsenseClickCount');
2486 localStorage.removeItem('adsenseBlockedTime');
2487 localStorage.removeItem('lastClickTime');
2488 localStorage.removeItem('adsenseFirstClickTime');
2489
2490 setStorageItem('adsenseBlocked', 'false');
2491 setStorageItem('adsenseClickCount', '0');
2492 setStorageItem('adsenseFirstClickTime', '0');
2493
2494 return true;
2495 } catch (e) {
2496 return false;
2497 }
2498 }
2499
2500 " . ($should_reset ? "resetLocalStorageForUnblock();" : "") . "
2501
2502 const maxClickCount = " . esc_js($max_click_count) . ";
2503 const clickTimeWindow = " . esc_js($click_time_window) . ";
2504
2505 function checkServerBlockStatus() {
2506 fetch('" . esc_js(admin_url('admin-ajax.php')) . "', {
2507 method: 'POST',
2508 headers: {
2509 'Content-Type': 'application/x-www-form-urlencoded',
2510 },
2511 body: new URLSearchParams({
2512 action: 'presslearn_check_ip_block_status',
2513 nonce: '" . esc_js(wp_create_nonce('presslearn_check_block_status')) . "'
2514 })
2515 })
2516 .then(response => response.json())
2517 .then(data => {
2518 if (data.success) {
2519 if (!data.data.is_blocked) {
2520 const localBlocked = getStorageItem('adsenseBlocked') === 'true';
2521 if (localBlocked) {
2522 setStorageItem('adsenseBlocked', 'false');
2523 setStorageItem('adsenseBlockedTime', '0');
2524 }
2525 } else {
2526 setStorageItem('adsenseBlocked', 'true');
2527 if (!getStorageItem('adsenseBlockedTime')) {
2528 setStorageItem('adsenseBlockedTime', Date.now().toString());
2529 }
2530 }
2531 }
2532 })
2533 .catch(error => {
2534 console.error('Block status check failed:', error);
2535 });
2536 }
2537
2538 checkServerBlockStatus();
2539
2540 function checkAndResetClickCount() {
2541 const firstClickTime = getStorageItem('adsenseFirstClickTime', '0');
2542 const currentTime = Date.now();
2543 const timeWindowMs = clickTimeWindow * 60 * 1000;
2544
2545 if (firstClickTime !== '0' && (currentTime - parseInt(firstClickTime)) > timeWindowMs) {
2546 setStorageItem('adsenseClickCount', '0');
2547 setStorageItem('adsenseFirstClickTime', '0');
2548 return 0;
2549 }
2550
2551 return getStorageItem('adsenseClickCount') ?
2552 parseInt(getStorageItem('adsenseClickCount')) : 0;
2553 }
2554
2555 const clickCount = checkAndResetClickCount();
2556
2557 let isBlocked = getStorageItem('adsenseBlocked') === 'true';
2558 const blockedTime = getStorageItem('adsenseBlockedTime');
2559
2560 if (isBlocked) {
2561 if (!blockedTime || isNaN(parseInt(blockedTime))) {
2562 setStorageItem('adsenseBlocked', 'false');
2563 setStorageItem('adsenseBlockedTime', '0');
2564 isBlocked = false;
2565 } else {
2566 const timeDiff = Date.now() - parseInt(blockedTime);
2567 const blockExpiryDays = window.presslearn_protection_config ? window.presslearn_protection_config.block_expiry_days : 30;
2568 if (timeDiff > blockExpiryDays * 24 * 60 * 60 * 1000) {
2569 setStorageItem('adsenseBlocked', 'false');
2570 setStorageItem('adsenseClickCount', '0');
2571 setStorageItem('adsenseFirstClickTime', '0');
2572 setStorageItem('adsenseBlockedTime', '0');
2573 isBlocked = false;
2574 }
2575 }
2576 }
2577
2578 if (clickCount >= maxClickCount || isBlocked) {
2579 removeAllAds();
2580 }
2581
2582 function isAdsenseAd(element) {
2583 if (!element) return false;
2584
2585 if (element.tagName === 'IFRAME' && element.src &&
2586 element.src.includes('google')) {
2587 return true;
2588 }
2589
2590 if (element.tagName === 'INS' &&
2591 element.hasAttribute('data-ad-client') &&
2592 element.getAttribute('data-ad-client').includes('pub-')) {
2593 return true;
2594 }
2595
2596 if (element.tagName === 'DIV' &&
2597 (element.id === 'google_vignette' ||
2598 element.id === 'google_anchor' ||
2599 element.className.includes('google-auto-placed') ||
2600 element.hasAttribute('data-vignette-loaded') ||
2601 element.hasAttribute('data-anchor-status'))) {
2602 return true;
2603 }
2604
2605 if (element.tagName === 'IFRAME' &&
2606 (element.id.includes('google_ads_iframe') ||
2607 element.hasAttribute('data-google-container-id') ||
2608 (element.src && (element.src.includes('googleads') ||
2609 element.src.includes('doubleclick'))))) {
2610 return true;
2611 }
2612
2613 return false;
2614 }
2615
2616 function removeAllAds() {
2617 const selectors = [
2618 '#google_esf',
2619 '.adsbygoogle',
2620 'ins.adsbygoogle',
2621 'ins.adsbygoogle-noablate',
2622 'iframe[id^=\"google_ads_\"]',
2623 'iframe[id^=\"aswift_\"]',
2624 'div[id^=\"aswift_\"]',
2625 '[data-ad-status]',
2626 '[data-adsbygoogle-status]',
2627 '[data-google-query-id]',
2628 '[data-google-container-id]',
2629 '[data-ad-format]',
2630 '#google_vignette',
2631 '#google_anchor',
2632 '.google-auto-placed',
2633 '[data-vignette-loaded]',
2634 '[data-anchor-status]',
2635 'iframe[id*=\"google_ads_iframe\"]',
2636 'iframe[src*=\"googleads\"]',
2637 'iframe[src*=\"doubleclick\"]',
2638 'div[id^=\"google_ads_iframe\"]',
2639 'div.right-side-rail-edge',
2640 'div[data-side-rail-status]'
2641 ];
2642
2643 const style = document.createElement('style');
2644 style.textContent =
2645 '#google_esf, ' +
2646 '.adsbygoogle,' +
2647 'iframe[id^=\"google_ads_\"],' +
2648 'ins.adsbygoogle,' +
2649 'ins.adsbygoogle-noablate,' +
2650 'iframe[id^=\"aswift_\"],' +
2651 'div[id^=\"aswift_\"],' +
2652 '[data-ad-status],' +
2653 '[data-adsbygoogle-status],' +
2654 '[data-google-query-id],' +
2655 '[data-google-container-id],' +
2656 '[data-ad-format],' +
2657 '#google_vignette,' +
2658 '#google_anchor,' +
2659 '.google-auto-placed,' +
2660 '[data-vignette-loaded],' +
2661 '[data-anchor-status],' +
2662 'iframe[id*=\"google_ads_iframe\"],' +
2663 'iframe[src*=\"googleads\"],' +
2664 'iframe[src*=\"doubleclick\"],' +
2665 'div[id^=\"google_ads_iframe\"],' +
2666 'div.right-side-rail-edge,' +
2667 'div[data-side-rail-status] {' +
2668 'display: none !important;' +
2669 'visibility: hidden !important;' +
2670 'opacity: 0 !important;' +
2671 'width: 0 !important;' +
2672 'height: 0 !important;' +
2673 'position: absolute !important;' +
2674 'left: -9999px !important;' +
2675 'top: -9999px !important;' +
2676 '}';
2677 document.head.appendChild(style);
2678
2679 selectors.forEach(selector => {
2680 document.querySelectorAll(selector).forEach(element => {
2681 if (element && element.parentNode) {
2682 element.parentNode.removeChild(element);
2683 }
2684 });
2685 });
2686 }
2687
2688 function blockCurrentIP() {
2689 const formData = new FormData();
2690 formData.append('action', 'presslearn_block_current_ip');
2691 formData.append('nonce', '" . esc_js(wp_create_nonce('presslearn_block_ip_nonce')) . "');
2692
2693 fetch('" . esc_url(admin_url('admin-ajax.php')) . "', {
2694 method: 'POST',
2695 credentials: 'same-origin',
2696 body: formData
2697 });
2698 }
2699
2700 function showBlockModal() {
2701 removeAllAds();
2702
2703 const modal = document.getElementById('pl-block-modal');
2704 modal.style.display = 'flex';
2705 document.body.classList.add('pl-modal-open');
2706 }
2707
2708 document.getElementById('pl-modal-close').addEventListener('click', function() {
2709 const modal = document.getElementById('pl-block-modal');
2710 modal.style.display = 'none';
2711 document.body.classList.remove('pl-modal-open');
2712 removeAllAds();
2713
2714 setInterval(removeAllAds, 1000);
2715 });
2716
2717 function addClickCount() {
2718 let clickCount = checkAndResetClickCount();
2719
2720 if (clickCount < maxClickCount) {
2721 if (clickCount === 0) {
2722 setStorageItem('adsenseFirstClickTime', Date.now().toString());
2723 }
2724
2725 clickCount++;
2726 setStorageItem('adsenseClickCount', clickCount.toString());
2727
2728 setStorageItem('lastClickTime', Date.now().toString());
2729 }
2730
2731 if (clickCount >= maxClickCount) {
2732 removeAllAds();
2733
2734 blockCurrentIP();
2735
2736 showBlockModal();
2737
2738 setStorageItem('adsenseBlocked', 'true');
2739 setStorageItem('adsenseBlockedTime', Date.now().toString());
2740 }
2741 }
2742
2743 window.addEventListener('blur', function() {
2744 const activeElement = document.activeElement;
2745
2746 if (isAdsenseAd(activeElement)) {
2747 if (window.location.href.includes('#google_vignette')) {
2748 return;
2749 }
2750
2751 addClickCount();
2752
2753 setTimeout(function() {
2754 activeElement.blur();
2755 }, 1);
2756 }
2757 });
2758
2759 document.addEventListener('click', function(e) {
2760 const target = e.target;
2761
2762 let currentElement = target;
2763 for (let i = 0; i < 5; i++) {
2764 if (!currentElement) break;
2765
2766 if (isAdsenseAd(currentElement)) {
2767 addClickCount();
2768 break;
2769 }
2770
2771 currentElement = currentElement.parentElement;
2772 }
2773 }, true);
2774
2775 window.addEventListener('message', function(event) {
2776 try {
2777 if (typeof event.data === 'string' &&
2778 (event.data.includes('google_ads') ||
2779 event.data.includes('doubleclick') ||
2780 event.data.includes('GoogleAdServingTest'))) {
2781
2782 const lastClickTime = getStorageItem('lastClickTime', '0');
2783 const now = Date.now();
2784
2785 if (now - parseInt(lastClickTime) < 2000) {
2786 addClickCount();
2787 }
2788 }
2789 } catch (e) {}
2790 });
2791 })();
2792 ";
2793
2794 wp_localize_script('presslearn-protection-script-js', 'presslearn_protection_config', array(
2795 'block_expiry_days' => get_option('presslearn_click_protection_block_expiry_days', 30)
2796 ));
2797
2798 wp_add_inline_script('presslearn-protection-script-js', $protection_script);
2799 }
2800
2801 add_action('wp_body_open', 'presslearn_add_ad_protection_script', 1);
2802
2803 function presslearn_block_current_ip_ajax() {
2804 check_ajax_referer('presslearn_block_ip_nonce', 'nonce');
2805
2806 $click_protection_enabled = get_option('presslearn_click_protection_enabled', 'no');
2807 if ($click_protection_enabled !== 'yes') {
2808 wp_send_json_error(array('message' => '애드 프로�
2809 �터가 비활성화되어 있습니다.'));
2810 wp_die();
2811 }
2812
2813 $user_ip = presslearn_plugin()->get_visitor_ip_for_protection();
2814
2815 $allowed_ips = get_option('presslearn_click_protection_allowed_ips', array());
2816 foreach ($allowed_ips as $item) {
2817 if ($item['ip'] === $user_ip) {
2818 wp_send_json_error(array('message' => '허용된 IP�
2819 니다.'));
2820 wp_die();
2821 }
2822 }
2823
2824 $blocked_ips = get_option('presslearn_click_protection_blocked_ips', array());
2825 $is_already_blocked = false;
2826
2827 foreach ($blocked_ips as $item) {
2828 if ($item['ip'] === $user_ip) {
2829 $is_already_blocked = true;
2830 break;
2831 }
2832 }
2833
2834 if (!$is_already_blocked) {
2835 $current_date = current_time('Y-m-d');
2836
2837 $block_expiry_days = get_option('presslearn_click_protection_block_expiry_days', 30);
2838
2839 $blocked_ips[] = array(
2840 'ip' => $user_ip,
2841 'date' => $current_date,
2842 'block_date' => $current_date,
2843 'reason' => '광고 클릭 제한 초과',
2844 'expires' => $block_expiry_days > 0 ? gmdate('Y-m-d', strtotime("+{$block_expiry_days} days")) : ''
2845 );
2846
2847 update_option('presslearn_click_protection_blocked_ips', $blocked_ips);
2848
2849 wp_send_json_success(array(
2850 'message' => 'IP가 성공적으로 차단되었습니다.',
2851 'blocked_ip' => $user_ip,
2852 'block_date' => $current_date,
2853 'is_new' => true
2854 ));
2855 } else {
2856 wp_send_json_success(array(
2857 'message' => '이미 차단된 IP�
2858 니다.',
2859 'blocked_ip' => $user_ip,
2860 'is_new' => false
2861 ));
2862 }
2863
2864 wp_die();
2865 }
2866
2867 add_action('wp_ajax_presslearn_block_current_ip', 'presslearn_block_current_ip_ajax');
2868 add_action('wp_ajax_nopriv_presslearn_block_current_ip', 'presslearn_block_current_ip_ajax');
2869
2870 function presslearn_check_ip_block_status_ajax() {
2871 check_ajax_referer('presslearn_check_block_status', 'nonce');
2872
2873 $click_protection_enabled = get_option('presslearn_click_protection_enabled', 'no');
2874 if ($click_protection_enabled !== 'yes') {
2875 wp_send_json_success(array('is_blocked' => false));
2876 wp_die();
2877 }
2878
2879 $user_ip = presslearn_plugin()->get_visitor_ip_for_protection();
2880
2881 $allowed_ips = get_option('presslearn_click_protection_allowed_ips', array());
2882 foreach ($allowed_ips as $item) {
2883 if ($item['ip'] === $user_ip) {
2884 wp_send_json_success(array('is_blocked' => false, 'reason' => 'allowed'));
2885 wp_die();
2886 }
2887 }
2888
2889 $blocked_ips = get_option('presslearn_click_protection_blocked_ips', array());
2890 $is_blocked = false;
2891
2892 foreach ($blocked_ips as $item) {
2893 if ($item['ip'] === $user_ip) {
2894 $is_blocked = true;
2895 break;
2896 }
2897 }
2898
2899 wp_send_json_success(array(
2900 'is_blocked' => $is_blocked,
2901 'ip' => $user_ip
2902 ));
2903
2904 wp_die();
2905 }
2906
2907 add_action('wp_ajax_presslearn_check_ip_block_status', 'presslearn_check_ip_block_status_ajax');
2908 add_action('wp_ajax_nopriv_presslearn_check_ip_block_status', 'presslearn_check_ip_block_status_ajax');
2909
2910 function presslearn_unblock_ip_with_reset() {
2911 check_ajax_referer('presslearn_ip_nonce', 'nonce');
2912
2913 if (!current_user_can('manage_options')) {
2914 wp_send_json_error(array('message' => '권한이 없습니다.'));
2915 wp_die();
2916 }
2917
2918 $ip = isset($_POST['ip']) ? sanitize_text_field(wp_unslash($_POST['ip'])) : '';
2919
2920 if (empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) {
2921 wp_send_json_error(array('message' => '유효하지 않은 IP 주소�
2922 니다.'));
2923 wp_die();
2924 }
2925
2926 $lock_key = 'presslearn_unblock_' . md5($ip);
2927 if (get_transient($lock_key)) {
2928 wp_send_json_error(array('message' => '다른 요청이 처리 중�
2929 니다. 잠시 후 다시 시도해주세요.'));
2930 wp_die();
2931 }
2932
2933 set_transient($lock_key, true, 5);
2934
2935 try {
2936 $blocked_ips = get_option('presslearn_click_protection_blocked_ips', array());
2937 $ip_found = false;
2938 $new_blocked_ips = array();
2939
2940 foreach ($blocked_ips as $item) {
2941 if ($item['ip'] === $ip) {
2942 $ip_found = true;
2943 } else {
2944 $new_blocked_ips[] = $item;
2945 }
2946 }
2947
2948 if (!$ip_found) {
2949 delete_transient($lock_key);
2950 wp_send_json_error(array('message' => '차단 목록에서 해당 IP를 찾을 수 없습니다.'));
2951 wp_die();
2952 }
2953
2954 update_option('presslearn_click_protection_blocked_ips_backup', $blocked_ips);
2955
2956 update_option('presslearn_click_protection_blocked_ips', $new_blocked_ips);
2957
2958 $reset_signals = get_option('presslearn_reset_localStorage_signals', array());
2959 $reset_signals[$ip] = array(
2960 'timestamp' => time(),
2961 'expires' => time() + 3600
2962 );
2963 update_option('presslearn_reset_localStorage_signals', $reset_signals);
2964
2965 delete_transient($lock_key);
2966
2967 wp_send_json_success(array(
2968 'message' => 'IP 차단이 성공적으로 해제되었습니다.',
2969 'ip' => $ip,
2970 'reset_signal_sent' => true
2971 ));
2972
2973 } catch (Exception $e) {
2974 delete_transient($lock_key);
2975 wp_send_json_error(array('message' => '서버 오류가 발생했습니다.'));
2976 }
2977
2978 wp_die();
2979 }
2980
2981 add_action('wp_ajax_presslearn_unblock_ip_with_reset', 'presslearn_unblock_ip_with_reset');
2982
2983 add_action('wp_head', 'presslearn_add_adclicker_script', 1);
2984
2985 add_action('wp_ajax_presslearn_get_blocked_logs', 'presslearn_get_blocked_logs');
2986
2987 function presslearn_get_blocked_logs() {
2988 check_ajax_referer('presslearn_ip_nonce', 'nonce');
2989
2990 if (current_user_can('manage_options')) {
2991 $blocked_ips = get_option('presslearn_click_protection_blocked_ips', array());
2992 $period = isset($_POST['period']) ? sanitize_text_field(wp_unslash($_POST['period'])) : '30';
2993
2994 if ($period !== 'all' && !empty($blocked_ips)) {
2995 $filtered_logs = array();
2996 $cutoff_date = gmdate('Y-m-d', strtotime("-{$period} days"));
2997
2998 foreach ($blocked_ips as $item) {
2999 $block_date = isset($item['block_date']) ? $item['block_date'] : $item['date'];
3000 if ($block_date >= $cutoff_date) {
3001 $filtered_logs[] = $item;
3002 }
3003 }
3004
3005 $blocked_ips = $filtered_logs;
3006 }
3007
3008 usort($blocked_ips, function($a, $b) {
3009 $date_a = isset($a['block_date']) ? $a['block_date'] : $a['date'];
3010 $date_b = isset($b['block_date']) ? $b['block_date'] : $b['date'];
3011 return strtotime($date_b) - strtotime($date_a);
3012 });
3013
3014 wp_send_json_success(array(
3015 'logs' => $blocked_ips
3016 ));
3017 }
3018
3019 wp_send_json_error(array('message' => '권한이 없습니다.'));
3020 wp_die();
3021 }
3022
3023 function presslearn_add_adclicker_script() {
3024 if (!function_exists('presslearn_plugin') || !presslearn_plugin()->is_plugin_activated()) {
3025 return;
3026 }
3027
3028 $adclicker_enabled = get_option('presslearn_ad_clicker_enabled', 'no');
3029 if ($adclicker_enabled !== 'yes') {
3030 return;
3031 }
3032
3033 if (!is_single()) {
3034 return;
3035 }
3036
3037 global $post;
3038 $post_adclicker_enabled = get_post_meta($post->ID, '_presslearn_adclicker_enabled', true);
3039 $adclicker_global_enabled = get_option('presslearn_adclicker_global_enabled', 'no');
3040
3041 if ($adclicker_global_enabled === 'yes') {
3042 } else {
3043 if ($post_adclicker_enabled === '' || $post_adclicker_enabled === 'no') {
3044 return;
3045 }
3046 }
3047
3048 $adclicker_button_text = get_post_meta($post->ID, '_presslearn_adclicker_button_text', true);
3049 if (empty($adclicker_button_text)) {
3050 $adclicker_button_text = '광고보고 콘�
3051 �츠 계속 읽기';
3052 }
3053
3054 $adclicker_ad_link = get_post_meta($post->ID, '_presslearn_adclicker_ad_link', true);
3055
3056 $adclicker_frequency = get_option('presslearn_adclicker_frequency', 'once');
3057 $adclicker_overlay_color = get_option('presslearn_adclicker_overlay_color', '#000000');
3058 $adclicker_overlay_range = get_option('presslearn_adclicker_overlay_range', 100);
3059 $adclicker_display_time = get_option('presslearn_adclicker_display_time', 'null');
3060 $adclicker_button_color = get_option('presslearn_adclicker_button_color', '#2196F3');
3061 $adclicker_button_text_color = get_option('presslearn_adclicker_button_text_color', '#ffffff');
3062
3063 wp_register_style('presslearn-adclicker-css', false);
3064 wp_enqueue_style('presslearn-adclicker-css');
3065
3066 $adclicker_css = "
3067 .pl-adclicker-overlay {
3068 display: none;
3069 position: fixed;
3070 bottom: 0;
3071 left: 0;
3072 width: 100%;
3073 height: " . esc_attr($adclicker_overlay_range) . "vh;
3074 background: linear-gradient(to bottom, rgba(255, 255, 255, 0.1) 0%, " . esc_attr($adclicker_overlay_color) . " 100%);
3075 z-index: 999999;
3076 }
3077
3078 .pl-adclicker-close-button {
3079 display: none;
3080 position: fixed;
3081 bottom: 60px;
3082 left: 50%;
3083 transform: translateX(-50%);
3084 padding: 15px 30px;
3085 background-color: " . esc_attr($adclicker_button_color) . ";
3086 color: " . esc_attr($adclicker_button_text_color) . ";
3087 border: none;
3088 border-radius: 8px;
3089 font-size: 20px;
3090 font-weight: bold;
3091 cursor: pointer;
3092 z-index: 1000001;
3093 text-decoration: none;
3094 text-align: center;
3095 }
3096
3097 .pl-adclicker-close-button:hover,
3098 .pl-adclicker-close-button:focus,
3099 .pl-adclicker-close-button:active {
3100 background-color: " . esc_attr($adclicker_button_color) . ";
3101 color: " . esc_attr($adclicker_button_text_color) . ";
3102 text-decoration: none;
3103 outline: none;
3104 }
3105
3106 .pl-back-message {
3107 display: none;
3108 position: fixed;
3109 bottom: 40px;
3110 left: 50%;
3111 transform: translateX(-50%);
3112 font-size: 12px;
3113 color: " . esc_attr($adclicker_button_text_color) . ";
3114 opacity: 0.8;
3115 z-index: 1000001;
3116 }
3117
3118 @media screen and (max-width: 768px) {
3119 .pl-adclicker-close-button {
3120 width: 80%;
3121 }
3122 .pl-back-message {
3123 bottom: 30px;
3124 }
3125 }
3126
3127 .pl-countdown-label {
3128 display: none;
3129 position: absolute;
3130 top: -10px;
3131 right: -10px;
3132 width: 30px;
3133 height: 30px;
3134 border-radius: 50%;
3135 background-color: #ff3b30;
3136 color: white;
3137 font-size: 14px;
3138 font-weight: bold;
3139 text-align: center;
3140 line-height: 30px;
3141 z-index: 1000002;
3142 }
3143
3144 body.pl-adclicker-open {
3145 overflow: hidden;
3146 }
3147
3148 .presslearn-ai-icon {
3149 display: inline-block;
3150 width: 16px;
3151 height: 16px;
3152 background-image: url('" . esc_url(PRESSLEARN_PLUGIN_URL) . "assets/images/icons-meta.png');
3153 background-repeat: no-repeat;
3154 background-size: contain;
3155 vertical-align: middle;
3156 margin-left: 8px;
3157 position: relative;
3158 top: -1px;
3159 }
3160
3161 #presslearn_ai_writer .postbox-header .hndle {
3162 display: flex !important;
3163 justify-content: flex-start !important;
3164 align-items: center !important;
3165 }
3166
3167 .presslearn-ai-title-container {
3168 display: flex;
3169 align-items: center;
3170 }
3171 ";
3172
3173 wp_add_inline_style('presslearn-adclicker-css', $adclicker_css);
3174
3175 wp_register_script('presslearn-adclicker-js', false, array(), PRESSLEARN_PLUGIN_VERSION, true);
3176 wp_enqueue_script('presslearn-adclicker-js');
3177
3178 $adclicker_js = "
3179 (function() {
3180 function getAdClickerStorage(name, defaultValue) {
3181 try {
3182 const item = localStorage.getItem('adclicker_' + name);
3183 return item !== null ? item : defaultValue;
3184 } catch (e) {
3185 return defaultValue;
3186 }
3187 }
3188
3189 function setAdClickerStorage(name, value) {
3190 try {
3191 localStorage.setItem('adclicker_' + name, value);
3192 return true;
3193 } catch (e) {
3194 return false;
3195 }
3196 }
3197
3198 function showAdClicker() {
3199 const overlay = document.getElementById('pl-adclicker-overlay');
3200 const closeButton = document.getElementById('pl-adclicker-close-button');
3201 const countdownLabel = document.getElementById('pl-countdown-label');
3202 const backMessage = document.getElementById('pl-back-message');
3203
3204 overlay.style.display = 'block';
3205 closeButton.style.display = 'block';
3206 backMessage.style.display = 'block';
3207 document.body.classList.add('pl-adclicker-open');
3208
3209 const displayTime = '" . esc_js($adclicker_display_time) . "';
3210 if (displayTime !== 'null') {
3211 let timeLeft = parseInt(displayTime);
3212 countdownLabel.textContent = timeLeft;
3213 countdownLabel.style.display = 'block';
3214
3215 const countdownInterval = setInterval(function() {
3216 timeLeft--;
3217 countdownLabel.textContent = timeLeft;
3218
3219 if (timeLeft <= 0) {
3220 clearInterval(countdownInterval);
3221 countdownLabel.textContent = '✕';
3222 }
3223 }, 1000);
3224 }
3225 }
3226
3227 function hideAdClicker() {
3228 const overlay = document.getElementById('pl-adclicker-overlay');
3229 const closeButton = document.getElementById('pl-adclicker-close-button');
3230 const countdownLabel = document.getElementById('pl-countdown-label');
3231 const backMessage = document.getElementById('pl-back-message');
3232
3233 overlay.style.display = 'none';
3234 closeButton.style.display = 'none';
3235 countdownLabel.style.display = 'none';
3236 backMessage.style.display = 'none';
3237 document.body.classList.remove('pl-adclicker-open');
3238
3239 const frequency = '" . esc_js($adclicker_frequency) . "';
3240 if (frequency === 'once') {
3241 setAdClickerStorage('shown', 'true');
3242 } else if (frequency === '5min') {
3243 const now = new Date().getTime();
3244 setAdClickerStorage('last_shown', now.toString());
3245 }
3246 }
3247
3248 document.addEventListener('DOMContentLoaded', function() {
3249 const closeButton = document.getElementById('pl-adclicker-close-button');
3250 if (closeButton) {
3251 closeButton.addEventListener('click', function(e) {
3252 const adLink = this.getAttribute('data-ad-link');
3253
3254 if (e.target.tagName.toLowerCase() === 'a') {
3255 hideAdClicker();
3256 } else {
3257 e.preventDefault();
3258 hideAdClicker();
3259 }
3260 });
3261 }
3262
3263 const frequency = '" . esc_js($adclicker_frequency) . "';
3264 let shouldShow = false;
3265
3266 if (frequency === 'once') {
3267 shouldShow = getAdClickerStorage('shown', '') !== 'true';
3268 } else if (frequency === '5min') {
3269 const lastShown = parseInt(getAdClickerStorage('last_shown', '0'));
3270 const now = new Date().getTime();
3271 const fiveMinutes = 5 * 60 * 1000;
3272 shouldShow = (now - lastShown) > fiveMinutes;
3273 } else if (frequency === 'loop') {
3274 shouldShow = true;
3275 }
3276
3277 if (shouldShow) {
3278 setTimeout(function() {
3279 showAdClicker();
3280 }, 1000);
3281 }
3282 });
3283 })();
3284 ";
3285
3286 wp_add_inline_script('presslearn-adclicker-js', $adclicker_js);
3287
3288 add_action('wp_footer', function() use ($adclicker_ad_link, $adclicker_button_text) {
3289 ?>
3290 <div class="pl-adclicker-overlay" id="pl-adclicker-overlay"></div>
3291 <div class="pl-adclicker-close-button" id="pl-adclicker-close-button" data-ad-link="<?php echo esc_attr($adclicker_ad_link); ?>">
3292 <?php if ($adclicker_ad_link): ?>
3293 <a href="<?php echo esc_url($adclicker_ad_link); ?>" target="_blank" style="color: inherit; text-decoration: inherit; display: block;"><?php echo esc_html($adclicker_button_text); ?></a>
3294 <?php else: ?>
3295 <?php echo esc_html($adclicker_button_text); ?>
3296 <?php endif; ?>
3297 <span class="pl-countdown-label" id="pl-countdown-label"></span>
3298 </div>
3299 <div class="pl-back-message" id="pl-back-message"><?php echo esc_html('원치않으시면 뒤로가기를 해주세요'); ?></div>
3300 <?php
3301 });
3302 }
3303
3304 add_action('wp_body_open', 'presslearn_add_adclicker_script', 1);
3305
3306
3307 function presslearn_add_adclicker_metabox() {
3308 $adclicker_enabled = get_option('presslearn_ad_clicker_enabled', 'no');
3309 if ($adclicker_enabled !== 'yes') {
3310 return;
3311 }
3312
3313 add_meta_box(
3314 'presslearn_adclicker_settings',
3315 '<span class="presslearn-ai-title-container"><span class="presslearn-ai-icon"></span>애드클리커 설정</span>',
3316 'presslearn_render_adclicker_metabox',
3317 'post',
3318 'side',
3319 'default'
3320 );
3321 }
3322 add_action('add_meta_boxes', 'presslearn_add_adclicker_metabox');
3323
3324 function presslearn_render_adclicker_metabox($post) {
3325 wp_nonce_field('presslearn_adclicker_metabox_nonce', 'presslearn_adclicker_metabox_nonce');
3326
3327 $adclicker_enabled = get_post_meta($post->ID, '_presslearn_adclicker_enabled', true);
3328 $adclicker_button_text = get_post_meta($post->ID, '_presslearn_adclicker_button_text', true);
3329 $adclicker_ad_link = get_post_meta($post->ID, '_presslearn_adclicker_ad_link', true);
3330 $adclicker_global_enabled = get_option('presslearn_adclicker_global_enabled', 'no');
3331
3332 if ($adclicker_enabled === '') {
3333 $adclicker_enabled = $adclicker_global_enabled === 'yes' ? 'yes' : 'no';
3334 }
3335
3336 if (empty($adclicker_button_text)) {
3337 $adclicker_button_text = '광고보고 콘�
3338 �츠 계속 읽기';
3339 }
3340
3341 ?>
3342 <p>
3343 <label for="presslearn_adclicker_enabled"> 게시글에서 애드클리커 동작 여부:</label><br>
3344 <select name="presslearn_adclicker_enabled" id="presslearn_adclicker_enabled" class="widefat" style="box-sizing: border-box;" <?php echo $adclicker_global_enabled === 'yes' ? 'disabled' : ''; ?>>
3345 <option value="yes" <?php selected($adclicker_enabled, 'yes'); ?>></option>
3346 <option value="no" <?php selected($adclicker_enabled, 'no'); ?>>아니오</option>
3347 </select>
3348 <?php if ($adclicker_global_enabled === 'yes'): ?>
3349 <small style="color: #666;">전체 활성화가 켜져있어 설정은 무시되고 애드클리커가 강제 활성화됩니다.</small>
3350 <?php else: ?>
3351 <small style="color: #666;">전체 활성화가 비활성화되어 있어 설정에 따라 동작합니다.</small>
3352 <?php endif; ?>
3353 </p>
3354
3355 <p>
3356 <label for="presslearn_adclicker_button_text">애드클리커 버튼 �
3357 �스트:</label><br>
3358 <input type="text" name="presslearn_adclicker_button_text" id="presslearn_adclicker_button_text"
3359 class="widefat" value="<?php echo esc_attr($adclicker_button_text); ?>">
3360 </p>
3361
3362 <p>
3363 <label for="presslearn_adclicker_ad_link">광고 링크 URL:</label><br>
3364 <input type="url" name="presslearn_adclicker_ad_link" id="presslearn_adclicker_ad_link"
3365 class="widefat" value="<?php echo esc_url($adclicker_ad_link); ?>" placeholder="https://...">
3366 <small>버튼 클릭 이동할 광고 링크</small>
3367 </p>
3368 <?php
3369 }
3370
3371 function presslearn_save_adclicker_metabox_data($post_id) {
3372 if (!isset($_POST['presslearn_adclicker_metabox_nonce'])) {
3373 return;
3374 }
3375
3376 if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['presslearn_adclicker_metabox_nonce'])), 'presslearn_adclicker_metabox_nonce')) {
3377 return;
3378 }
3379
3380 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
3381 return;
3382 }
3383
3384 if (isset($_POST['post_type']) && sanitize_text_field(wp_unslash($_POST['post_type'])) === 'post') {
3385 if (!current_user_can('edit_post', $post_id)) {
3386 return;
3387 }
3388 }
3389
3390 if (isset($_POST['presslearn_adclicker_enabled'])) {
3391 $adclicker_enabled = sanitize_text_field(wp_unslash($_POST['presslearn_adclicker_enabled']));
3392 if ($adclicker_enabled === 'yes' || $adclicker_enabled === 'no') {
3393 update_post_meta($post_id, '_presslearn_adclicker_enabled', $adclicker_enabled);
3394 }
3395 }
3396
3397 if (isset($_POST['presslearn_adclicker_button_text'])) {
3398 $adclicker_button_text = sanitize_text_field(wp_unslash($_POST['presslearn_adclicker_button_text']));
3399 update_post_meta($post_id, '_presslearn_adclicker_button_text', $adclicker_button_text);
3400 }
3401
3402 if (isset($_POST['presslearn_adclicker_ad_link'])) {
3403 $adclicker_ad_link = esc_url_raw(wp_unslash($_POST['presslearn_adclicker_ad_link']));
3404 update_post_meta($post_id, '_presslearn_adclicker_ad_link', $adclicker_ad_link);
3405 }
3406 }
3407 add_action('save_post', 'presslearn_save_adclicker_metabox_data');
3408
3409 add_action('wp_ajax_presslearn_upload_banner', 'presslearn_handle_banner_upload');
3410 add_action('wp_ajax_nopriv_presslearn_upload_banner', 'presslearn_handle_banner_upload');
3411
3412 function presslearn_handle_banner_upload() {
3413 $dynamic_banner_enabled = get_option('presslearn_dynamic_banner_enabled', 'no');
3414 if ($dynamic_banner_enabled !== 'yes') {
3415 wp_send_json_error(array('message' => '다이나믹 배너 기능이 활성화되어 있지 않습니다.'));
3416 wp_die();
3417 }
3418
3419 check_ajax_referer('presslearn_upload_banner_nonce', 'nonce');
3420
3421 if (!current_user_can('manage_options')) {
3422 wp_send_json_error(array('message' => '파일 �
3423 로드 권한이 없습니다.'));
3424 wp_die();
3425 }
3426
3427 if (!function_exists('wp_handle_upload')) {
3428 require_once(ABSPATH . 'wp-admin/includes/file.php');
3429 }
3430
3431 if (!isset($_FILES['banner_image'])) {
3432 wp_send_json_error(array('message' => '�
3433 로드할 파일이 없습니다.'));
3434 wp_die();
3435 }
3436
3437 $file = $_FILES['banner_image'];
3438
3439 $allowed_types = array('image/jpeg', 'image/png', 'image/gif', 'image/webp');
3440 $file_type = sanitize_mime_type($file['type']);
3441 if (!in_array($file_type, $allowed_types)) {
3442 wp_send_json_error(array('message' => '허용되지 않는 파일 형식�
3443 니다. JPG, PNG, GIF, WEBP 이미지만 �
3444 로드 가능합니다.'));
3445 wp_die();
3446 }
3447
3448 $upload_dir = wp_upload_dir();
3449
3450 $banner_type = isset($_POST['banner_type']) ? sanitize_text_field(wp_unslash($_POST['banner_type'])) : 'normal';
3451
3452 $upload_path = $upload_dir['path'];
3453 if ($banner_type === 'cover') {
3454 $upload_path = trailingslashit($upload_dir['path']) . 'cover_banners';
3455 if (!file_exists($upload_path)) {
3456 wp_mkdir_p($upload_path);
3457 }
3458 }
3459
3460 $file_name = wp_unique_filename($upload_path, sanitize_file_name($file['name']));
3461
3462 $upload_overrides = array(
3463 'test_form' => false,
3464 'test_size' => true,
3465 'test_upload' => true,
3466 'mimes' => array(
3467 'jpg|jpeg|jpe' => 'image/jpeg',
3468 'png' => 'image/png',
3469 'gif' => 'image/gif',
3470 'webp' => 'image/webp'
3471 )
3472 );
3473
3474 $movefile = wp_handle_upload($file, $upload_overrides);
3475
3476 if ($movefile && !isset($movefile['error'])) {
3477 wp_send_json_success(array(
3478 'url' => esc_url_raw($movefile['url']),
3479 'message' => '이미지가 성공적으로 �
3480 로드되었습니다.'
3481 ));
3482 } else {
3483 wp_send_json_error(array(
3484 'message' => isset($movefile['error']) ? esc_html($movefile['error']) : '파일 �
3485 로드 오류가 발생했습니다.'
3486 ));
3487 }
3488
3489 wp_die();
3490 }
3491
3492 add_action('init', 'presslearn_setup_cron_for_ip_unblock');
3493
3494 add_action('wp_ajax_presslearn_add_campaign', 'presslearn_add_campaign');
3495 add_action('wp_ajax_presslearn_get_campaigns', 'presslearn_get_campaigns');
3496 add_action('wp_ajax_presslearn_delete_campaign', 'presslearn_delete_campaign');
3497 add_action('wp_ajax_presslearn_get_campaign', 'presslearn_get_campaign');
3498 add_action('wp_ajax_presslearn_update_campaign', 'presslearn_update_campaign');
3499
3500 function presslearn_add_campaign() {
3501 if (!current_user_can('manage_options')) {
3502 wp_send_json_error(array('message' => '권한이 없습니다.'));
3503 wp_die();
3504 }
3505
3506 $dynamic_banner_enabled = get_option('presslearn_dynamic_banner_enabled', 'no');
3507 if ($dynamic_banner_enabled !== 'yes') {
3508 wp_send_json_error(array('message' => '다이나믹 배너 기능이 활성화되어 있지 않습니다.'));
3509 wp_die();
3510 }
3511
3512 presslearn_ensure_banners_table_exists();
3513
3514 check_ajax_referer('presslearn_campaign_nonce', 'nonce');
3515
3516 $campaign_data = isset($_POST['campaign_data']) ? wp_unslash($_POST['campaign_data']) : array();
3517
3518 if (empty($campaign_data) || !is_array($campaign_data)) {
3519 wp_send_json_error(array('message' => '유효하지 않은 캠페인 데이터�
3520 니다.'));
3521 wp_die();
3522 }
3523
3524 if (empty($campaign_data['name'])) {
3525 wp_send_json_error(array('message' => '캠페인 이름은 필수 항목�
3526 니다.'));
3527 wp_die();
3528 }
3529
3530 if ($campaign_data['type'] === 'custom') {
3531 if (empty($campaign_data['banner_url']) || empty($campaign_data['link']) ||
3532 empty($campaign_data['width']) || empty($campaign_data['height'])) {
3533 wp_send_json_error(array('message' => '배너 URL, 링크 URL, 가로/세로 크기는 필수 항목�
3534 니다.'));
3535 wp_die();
3536 }
3537 } else if ($campaign_data['type'] === 'iframe') {
3538 if (empty($campaign_data['iframe_code']) || empty($campaign_data['width']) || empty($campaign_data['height'])) {
3539 wp_send_json_error(array('message' => 'iframe 코드, 가로/세로 크기는 필수 항목�
3540 니다.'));
3541 wp_die();
3542 }
3543 } else {
3544 wp_send_json_error(array('message' => '유효하지 않은 배너 유형�
3545 니다.'));
3546 wp_die();
3547 }
3548
3549 global $wpdb;
3550 $table_name = $wpdb->prefix . 'presslearn_banners';
3551
3552 $data = array(
3553 'name' => sanitize_text_field($campaign_data['name']),
3554 'type' => sanitize_text_field($campaign_data['type']),
3555 'width' => intval($campaign_data['width']),
3556 'height' => intval($campaign_data['height']),
3557 'status' => 1,
3558 'created_at' => current_time('mysql')
3559 );
3560
3561 if ($campaign_data['type'] === 'custom') {
3562 $data['banner_url'] = esc_url_raw($campaign_data['banner_url']);
3563 $data['link'] = esc_url_raw($campaign_data['link']);
3564 if (!empty($campaign_data['cover_banner_url'])) {
3565 $data['cover_banner_url'] = esc_url_raw($campaign_data['cover_banner_url']);
3566 }
3567 } else if ($campaign_data['type'] === 'iframe') {
3568 $data['iframe_code'] = stripslashes($campaign_data['iframe_code']);
3569 if (!empty($campaign_data['link'])) {
3570 $data['link'] = esc_url_raw($campaign_data['link']);
3571 }
3572 if (!empty($campaign_data['cover_banner_url'])) {
3573 $data['cover_banner_url'] = esc_url_raw($campaign_data['cover_banner_url']);
3574 }
3575 }
3576
3577 $result = $wpdb->insert($table_name, $data);
3578
3579 if ($result === false) {
3580 wp_send_json_error(array('message' => '캠페인 추가 중 오류가 발생했습니다: ' . $wpdb->last_error));
3581 wp_die();
3582 }
3583
3584 $campaign_id = $wpdb->insert_id;
3585
3586 wp_send_json_success(array(
3587 'id' => $campaign_id,
3588 'message' => '캠페인이 성공적으로 추가되었습니다.',
3589 'shortcode' => $campaign_data['type'] === 'custom' ?
3590 '[presslearn_banner id="' . $campaign_id . '"]' :
3591 '[presslearn_iframe id="' . $campaign_id . '"]'
3592 ));
3593
3594 wp_die();
3595 }
3596
3597 function presslearn_get_campaigns() {
3598 if (!current_user_can('manage_options')) {
3599 wp_send_json_error(array('message' => '권한이 없습니다.'));
3600 wp_die();
3601 }
3602
3603 $dynamic_banner_enabled = get_option('presslearn_dynamic_banner_enabled', 'no');
3604 if ($dynamic_banner_enabled !== 'yes') {
3605 wp_send_json_error(array('message' => '다이나믹 배너 기능이 활성화되어 있지 않습니다.'));
3606 wp_die();
3607 }
3608
3609 presslearn_ensure_banners_table_exists();
3610
3611 check_ajax_referer('presslearn_campaign_nonce', 'nonce');
3612
3613 global $wpdb;
3614 $table_name = $wpdb->prefix . 'presslearn_banners';
3615
3616 $campaigns = $wpdb->get_results("SELECT * FROM $table_name ORDER BY created_at DESC", ARRAY_A);
3617
3618 if ($campaigns === null) {
3619 wp_send_json_error(array('message' => '캠페인 목록을 가져오는 중 오류가 발생했습니다: ' . $wpdb->last_error));
3620 wp_die();
3621 }
3622
3623 foreach ($campaigns as &$campaign) {
3624 $campaign['shortcode'] = $campaign['type'] === 'custom' ?
3625 '[presslearn_banner id="' . $campaign['id'] . '"]' :
3626 '[presslearn_iframe id="' . $campaign['id'] . '"]';
3627 }
3628
3629 wp_send_json_success(array('campaigns' => $campaigns));
3630 wp_die();
3631 }
3632
3633 function presslearn_delete_campaign() {
3634 if (!current_user_can('manage_options')) {
3635 wp_send_json_error(array('message' => '권한이 없습니다.'));
3636 wp_die();
3637 }
3638
3639 $dynamic_banner_enabled = get_option('presslearn_dynamic_banner_enabled', 'no');
3640 if ($dynamic_banner_enabled !== 'yes') {
3641 wp_send_json_error(array('message' => '다이나믹 배너 기능이 활성화되어 있지 않습니다.'));
3642 wp_die();
3643 }
3644
3645 presslearn_ensure_banners_table_exists();
3646
3647 check_ajax_referer('presslearn_campaign_nonce', 'nonce');
3648
3649 $campaign_id = isset($_POST['id']) ? intval($_POST['id']) : 0;
3650
3651 if ($campaign_id <= 0) {
3652 wp_send_json_error(array('message' => '유효하지 않은 캠페인 ID�
3653 니다.'));
3654 wp_die();
3655 }
3656
3657 global $wpdb;
3658 $table_name = $wpdb->prefix . 'presslearn_banners';
3659
3660 $result = $wpdb->delete($table_name, array('id' => $campaign_id), array('%d'));
3661
3662 if ($result === false) {
3663 wp_send_json_error(array('message' => '캠페인 삭제 오류가 발생했습니다: ' . $wpdb->last_error));
3664 wp_die();
3665 }
3666
3667 wp_send_json_success(array('message' => '캠페인이 성공적으로 삭제되었습니다.'));
3668 wp_die();
3669 }
3670
3671 function presslearn_get_campaign() {
3672 if (!current_user_can('manage_options')) {
3673 wp_send_json_error(array('message' => '권한이 없습니다.'));
3674 wp_die();
3675 }
3676
3677 $dynamic_banner_enabled = get_option('presslearn_dynamic_banner_enabled', 'no');
3678 if ($dynamic_banner_enabled !== 'yes') {
3679 wp_send_json_error(array('message' => '다이나믹 배너 기능이 활성화되어 있지 않습니다.'));
3680 wp_die();
3681 }
3682
3683 presslearn_ensure_banners_table_exists();
3684
3685 check_ajax_referer('presslearn_campaign_nonce', 'nonce');
3686
3687 $campaign_id = isset($_POST['id']) ? intval($_POST['id']) : 0;
3688
3689 if ($campaign_id <= 0) {
3690 wp_send_json_error(array('message' => '유효하지 않은 캠페인 ID�
3691 니다.'));
3692 wp_die();
3693 }
3694
3695 global $wpdb;
3696 $table_name = $wpdb->prefix . 'presslearn_banners';
3697
3698 $campaign = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $campaign_id), ARRAY_A);
3699
3700 if ($campaign === null) {
3701 wp_send_json_error(array('message' => '캠페인 정보를 가져오는 중 오류가 발생했습니다: ' . $wpdb->last_error));
3702 wp_die();
3703 }
3704
3705 wp_send_json_success(array('campaign' => $campaign));
3706 wp_die();
3707 }
3708
3709 function presslearn_update_campaign() {
3710 if (!current_user_can('manage_options')) {
3711 wp_send_json_error(array('message' => '권한이 없습니다.'));
3712 wp_die();
3713 }
3714
3715 $dynamic_banner_enabled = get_option('presslearn_dynamic_banner_enabled', 'no');
3716 if ($dynamic_banner_enabled !== 'yes') {
3717 wp_send_json_error(array('message' => '다이나믹 배너 기능이 활성화되어 있지 않습니다.'));
3718 wp_die();
3719 }
3720
3721 presslearn_ensure_banners_table_exists();
3722
3723 check_ajax_referer('presslearn_campaign_nonce', 'nonce');
3724
3725 $campaign_data = isset($_POST['campaign_data']) ? wp_unslash($_POST['campaign_data']) : array();
3726
3727 if (empty($campaign_data) || !is_array($campaign_data) || empty($campaign_data['id'])) {
3728 wp_send_json_error(array('message' => '유효하지 않은 캠페인 데이터�
3729 니다.'));
3730 wp_die();
3731 }
3732
3733 $campaign_id = intval($campaign_data['id']);
3734
3735 if (empty($campaign_data['name'])) {
3736 wp_send_json_error(array('message' => '캠페인 이름은 필수 항목�
3737 니다.'));
3738 wp_die();
3739 }
3740
3741 if ($campaign_data['type'] === 'custom') {
3742 if (empty($campaign_data['banner_url']) || empty($campaign_data['link']) ||
3743 empty($campaign_data['width']) || empty($campaign_data['height'])) {
3744 wp_send_json_error(array('message' => '배너 URL, 링크 URL, 가로/세로 크기는 필수 항목�
3745 니다.'));
3746 wp_die();
3747 }
3748 } else if ($campaign_data['type'] === 'iframe') {
3749 if (empty($campaign_data['iframe_code']) || empty($campaign_data['width']) || empty($campaign_data['height'])) {
3750 wp_send_json_error(array('message' => 'iframe 코드, 가로/세로 크기는 필수 항목�
3751 니다.'));
3752 wp_die();
3753 }
3754 } else {
3755 wp_send_json_error(array('message' => '유효하지 않은 배너 유형�
3756 니다.'));
3757 wp_die();
3758 }
3759
3760 global $wpdb;
3761 $table_name = $wpdb->prefix . 'presslearn_banners';
3762
3763 $data = array(
3764 'name' => sanitize_text_field($campaign_data['name']),
3765 'type' => sanitize_text_field($campaign_data['type']),
3766 'width' => intval($campaign_data['width']),
3767 'height' => intval($campaign_data['height']),
3768 'status' => 1,
3769 'updated_at' => current_time('mysql')
3770 );
3771
3772 if ($campaign_data['type'] === 'custom') {
3773 $data['banner_url'] = esc_url_raw($campaign_data['banner_url']);
3774 $data['link'] = esc_url_raw($campaign_data['link']);
3775 $data['iframe_code'] = '';
3776
3777 if (!empty($campaign_data['cover_banner_url'])) {
3778 $data['cover_banner_url'] = esc_url_raw($campaign_data['cover_banner_url']);
3779 } else {
3780 $data['cover_banner_url'] = '';
3781 }
3782 } else if ($campaign_data['type'] === 'iframe') {
3783 $data['iframe_code'] = stripslashes($campaign_data['iframe_code']);
3784 $data['banner_url'] = '';
3785
3786 if (!empty($campaign_data['link'])) {
3787 $data['link'] = esc_url_raw($campaign_data['link']);
3788 } else {
3789 $data['link'] = '';
3790 }
3791
3792 if (!empty($campaign_data['cover_banner_url'])) {
3793 $data['cover_banner_url'] = esc_url_raw($campaign_data['cover_banner_url']);
3794 } else {
3795 $data['cover_banner_url'] = '';
3796 }
3797 }
3798
3799 $result = $wpdb->update(
3800 $table_name,
3801 $data,
3802 array('id' => $campaign_id),
3803 array('%s', '%s', '%d', '%d', '%d', '%s', '%s', '%s', '%s'),
3804 array('%d')
3805 );
3806
3807 if ($result === false) {
3808 wp_send_json_error(array('message' => '캠페인 수정 오류가 발생했습니다: ' . $wpdb->last_error));
3809 wp_die();
3810 }
3811
3812 wp_send_json_success(array(
3813 'message' => '캠페인이 성공적으로 수정되었습니다.',
3814 'shortcode' => $campaign_data['type'] === 'custom' ?
3815 '[presslearn_banner id="' . $campaign_id . '"]' :
3816 '[presslearn_iframe id="' . $campaign_id . '"]'
3817 ));
3818
3819 wp_die();
3820 }
3821
3822 function presslearn_register_banner_shortcodes() {
3823 add_shortcode('presslearn_banner', 'presslearn_banner_shortcode');
3824 add_shortcode('presslearn_iframe', 'presslearn_iframe_shortcode');
3825
3826 wp_register_style('presslearn-banner-styles', false);
3827 wp_enqueue_style('presslearn-banner-styles');
3828
3829 $custom_css = "
3830 .pl-dynamic-area {
3831 display: inline-block;
3832 position: relative;
3833 overflow: hidden;
3834 }
3835 .pl-dynamic-cover {
3836 position: absolute;
3837 top: 0;
3838 left: -40px;
3839 width: 100%;
3840 height: 100%;
3841 display: none;
3842 z-index: 1;
3843 touch-action: pan-x;
3844 user-select: none;
3845 cursor: grab;
3846 }
3847 .pl-dynamic-cover.enable {
3848 display: block !important;
3849 animation: sliding 1.5s ease-in-out infinite;
3850 }
3851 .pl-dynamic-cover.dragging {
3852 animation: none !important;
3853 cursor: grabbing;
3854 }
3855 .pl-dynamic-cover::after {
3856 content: attr(data-message);
3857 position: absolute;
3858 right: 15px;
3859 top: 50%;
3860 transform: translateY(-50%);
3861 background: rgba(0,0,0,0.7);
3862 color: white;
3863 padding: 8px 12px;
3864 border-radius: 20px;
3865 display: flex;
3866 align-items: center;
3867 justify-content: center;
3868 font-size: 14px;
3869 transition: opacity 0.3s;
3870 }
3871 .pl-dynamic-cover img {
3872 max-width: 100%;
3873 height: auto;
3874 }
3875 @keyframes sliding {
3876 0% {
3877 transform: translate3d(-7%, 0, 0);
3878 }
3879
3880 20% {
3881 transform: translate3d(-10%, 0, 0);
3882 }
3883
3884 40% {
3885 transform: translate3d(-5%, 0, 0);
3886 }
3887
3888 60% {
3889 transform: translate3d(-10%, 0, 0);
3890 }
3891
3892 80% {
3893 transform: translate3d(-5%, 0, 0);
3894 }
3895
3896 100% {
3897 transform: translate3d(-7%, 0, 0);
3898 }
3899 }
3900 ";
3901
3902 wp_add_inline_style('presslearn-banner-styles', $custom_css);
3903
3904 wp_register_script('presslearn-banner-script', false, array(), PRESSLEARN_PLUGIN_VERSION, true);
3905 wp_enqueue_script('presslearn-banner-script');
3906
3907 $script = "
3908 document.addEventListener('DOMContentLoaded', function() {
3909 let startX = 0;
3910 let isDragging = false;
3911 let currentCover = null;
3912 let parentWidth = 0;
3913 let dragThreshold = false;
3914
3915 function triggerIframeClick(coverElement) {
3916 const dynamicArea = coverElement.closest('.pl-dynamic-area');
3917 if (!dynamicArea) return;
3918
3919 const customLink = dynamicArea.getAttribute('data-link');
3920 const iframeContainer = dynamicArea.querySelector('.presslearn-iframe-container iframe');
3921 const bannerLink = dynamicArea.querySelector('.pl-dynamic-area-item a');
3922
3923 if (customLink) {
3924 window.location.href = customLink;
3925 } else if (iframeContainer && iframeContainer.src) {
3926 window.location.href = iframeContainer.src;
3927 } else if (bannerLink && bannerLink.href) {
3928 try {
3929 bannerLink.click();
3930 } catch (e) {
3931 window.location.href = bannerLink.href;
3932 }
3933 }
3934
3935 coverElement.classList.remove('enable', 'dragging');
3936 coverElement.style.transform = '';
3937 }
3938
3939 window.addEventListener('blur', function() {
3940 if (currentCover && dragThreshold) {
3941 triggerIframeClick(currentCover);
3942 isDragging = false;
3943 currentCover = null;
3944 dragThreshold = false;
3945 }
3946 });
3947
3948 document.addEventListener('touchstart', function(e) {
3949 const target = e.target.closest('.pl-dynamic-cover');
3950 if (target && target.classList.contains('enable')) {
3951 e.preventDefault();
3952 startX = e.touches[0].clientX;
3953 isDragging = true;
3954 currentCover = target;
3955 parentWidth = target.parentElement.offsetWidth;
3956 dragThreshold = false;
3957
3958 currentCover.classList.add('dragging');
3959 }
3960 });
3961
3962 document.addEventListener('mousedown', function(e) {
3963 const target = e.target.closest('.pl-dynamic-cover');
3964 if (target && target.classList.contains('enable')) {
3965 e.preventDefault();
3966 startX = e.clientX;
3967 isDragging = true;
3968 currentCover = target;
3969 parentWidth = target.parentElement.offsetWidth;
3970 dragThreshold = false;
3971
3972 currentCover.classList.add('dragging');
3973 }
3974 });
3975
3976 document.addEventListener('click', function(e) {
3977 const target = e.target.closest('.pl-dynamic-cover');
3978 if (target && target.classList.contains('enable')) {
3979 e.preventDefault();
3980 triggerIframeClick(target);
3981 }
3982 });
3983
3984 document.addEventListener('touchmove', function(e) {
3985 if (!isDragging || !currentCover) return;
3986
3987 const currentX = e.touches[0].clientX;
3988 const diff = currentX - startX;
3989
3990 if (diff < 0) {
3991 currentCover.style.transform = 'translate3d(' + (diff/3) + 'px, 0, 0)';
3992
3993 const threshold = parentWidth * 0.2;
3994 if (Math.abs(diff) > threshold) {
3995 dragThreshold = true;
3996 triggerIframeClick(currentCover);
3997 isDragging = false;
3998 currentCover = null;
3999 dragThreshold = false;
4000 }
4001 }
4002 });
4003
4004 document.addEventListener('mousemove', function(e) {
4005 if (!isDragging || !currentCover) return;
4006
4007 const currentX = e.clientX;
4008 const diff = currentX - startX;
4009
4010 if (diff < 0) {
4011 currentCover.style.transform = 'translate3d(' + (diff/3) + 'px, 0, 0)';
4012
4013 const threshold = parentWidth * 0.2;
4014 if (Math.abs(diff) > threshold) {
4015 dragThreshold = true;
4016 triggerIframeClick(currentCover);
4017 isDragging = false;
4018 currentCover = null;
4019 dragThreshold = false;
4020 }
4021 }
4022 });
4023
4024 document.addEventListener('touchend', function() {
4025 if (currentCover) {
4026 currentCover.classList.remove('dragging');
4027 currentCover.style.transform = '';
4028 isDragging = false;
4029 dragThreshold = false;
4030 currentCover = null;
4031 }
4032 });
4033
4034 document.addEventListener('mouseup', function() {
4035 if (currentCover) {
4036 currentCover.classList.remove('dragging');
4037 currentCover.style.transform = '';
4038 isDragging = false;
4039 dragThreshold = false;
4040 currentCover = null;
4041 }
4042 });
4043
4044 document.addEventListener('pointerdown', function(e) {
4045 const target = e.target.closest('.pl-dynamic-cover');
4046 if (target && target.classList.contains('enable')) {
4047 setTimeout(function() {
4048 if (target.classList.contains('enable')) {
4049 dragThreshold = true;
4050 currentCover = target;
4051 }
4052 }, 100);
4053 }
4054 });
4055 });
4056 ";
4057
4058 wp_add_inline_script('presslearn-banner-script', $script);
4059 }
4060 add_action('init', 'presslearn_register_banner_shortcodes');
4061
4062 function presslearn_banner_shortcode($atts) {
4063 $dynamic_banner_enabled = get_option('presslearn_dynamic_banner_enabled', 'no');
4064 if ($dynamic_banner_enabled !== 'yes') {
4065 return '';
4066 }
4067
4068 presslearn_ensure_banners_table_exists();
4069
4070 $atts = shortcode_atts(array(
4071 'id' => 0,
4072 ), $atts, 'presslearn_banner');
4073
4074 $banner_id = intval($atts['id']);
4075
4076 if ($banner_id <= 0) {
4077 return '';
4078 }
4079
4080 global $wpdb;
4081 $table_name = $wpdb->prefix . 'presslearn_banners';
4082
4083 $banner = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d AND type = 'custom' AND status = 1", $banner_id), ARRAY_A);
4084
4085 if ($banner === null || empty($banner)) {
4086 return '';
4087 }
4088
4089 $banner_url = esc_url($banner['banner_url']);
4090 $cover_banner_url = !empty($banner['cover_banner_url']) ? esc_url($banner['cover_banner_url']) : '';
4091
4092 $link_url = esc_url($banner['link']);
4093 $width = intval($banner['width']);
4094 $height = intval($banner['height']);
4095
4096 if (empty($cover_banner_url)) {
4097 return '
4098 <div class="pl-dynamic-area">
4099 <div class="pl-dynamic-area-item">
4100 <a href="' . esc_url($link_url) . '" target="_blank" rel="nofollow noopener">
4101 <img src="' . esc_url($banner_url) . '" alt="' . esc_attr($banner['name']) . '" width="' . esc_attr($width) . '" height="' . esc_attr($height) . '" style="max-width:100%;height:auto;display:block;" />
4102 </a>
4103 </div>
4104 </div>
4105 ';
4106 }
4107
4108 return '
4109 <div class="pl-dynamic-area">
4110 <div class="pl-dynamic-area-item">
4111 <a href="' . esc_url($link_url) . '" target="_blank" rel="nofollow noopener">
4112 <img src="' . esc_url($banner_url) . '" alt="' . esc_attr($banner['name']) . '" width="' . esc_attr($width) . '" height="' . esc_attr($height) . '" style="max-width:100%;height:auto;display:block;" />
4113 </a>
4114 </div>
4115 <div class="pl-dynamic-cover enable" data-message="밀어서 제거">
4116 <img src="' . esc_url($cover_banner_url) . '" alt="' . esc_attr($banner['name']) . '" width="' . esc_attr($width) . '" height="' . esc_attr($height) . '" style="max-width:100%;height:auto;display:block;" draggable="true" />
4117 </div>
4118 </div>
4119 ';
4120 }
4121
4122
4123 function presslearn_iframe_shortcode($atts) {
4124 $dynamic_banner_enabled = get_option('presslearn_dynamic_banner_enabled', 'no');
4125 if ($dynamic_banner_enabled !== 'yes') {
4126 return '';
4127 }
4128
4129 presslearn_ensure_banners_table_exists();
4130
4131 $atts = shortcode_atts(array(
4132 'id' => 0,
4133 ), $atts, 'presslearn_iframe');
4134
4135 $banner_id = intval($atts['id']);
4136
4137 if ($banner_id <= 0) {
4138 return '';
4139 }
4140
4141 global $wpdb;
4142 $table_name = $wpdb->prefix . 'presslearn_banners';
4143
4144 $banner = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d AND type = 'iframe' AND status = 1", $banner_id), ARRAY_A);
4145
4146 if ($banner === null || empty($banner)) {
4147 return '';
4148 }
4149
4150 $iframe_code = $banner['iframe_code'];
4151 $width = intval($banner['width']);
4152 $height = intval($banner['height']);
4153 $link_url = !empty($banner['link']) ? esc_url($banner['link']) : '';
4154 $cover_banner_url = !empty($banner['cover_banner_url']) ? esc_url($banner['cover_banner_url']) : '';
4155
4156 $iframe_code = stripslashes($iframe_code);
4157
4158 $iframe_code = preg_replace('/width\s*=\s*(["\']?)([^"\'\s>]*)(["\']?)/', 'width="' . esc_attr($width) . 'px"', $iframe_code);
4159 $iframe_code = preg_replace('/height\s*=\s*(["\']?)([^"\'\s>]*)(["\']?)/', 'height="' . esc_attr($height) . 'px"', $iframe_code);
4160
4161 if (strpos($iframe_code, 'style=') !== false) {
4162 $iframe_code = preg_replace('/style\s*=\s*(["\'])(.*?)(["\'])/', 'style=${1}max-width:100%;${2}${3}', $iframe_code);
4163 } else {
4164 $iframe_code = str_replace('<iframe', '<iframe style="max-width:100%;"', $iframe_code);
4165 }
4166
4167 $data_link_attr = !empty($link_url) ? ' data-link="' . esc_attr($link_url) . '"' : '';
4168
4169 if (empty($cover_banner_url)) {
4170 $safe_iframe_code = presslearn_sanitize_iframe($iframe_code);
4171 return '<div class="pl-dynamic-area"' . $data_link_attr . '><div class="presslearn-iframe-container" style="width:' . esc_attr($width) . 'px;max-width:100%;margin:0 auto;">' . $safe_iframe_code . '</div></div>';
4172 }
4173
4174 $safe_iframe_code = presslearn_sanitize_iframe($iframe_code);
4175
4176 return '
4177 <div class="pl-dynamic-area"' . $data_link_attr . '>
4178 <div class="pl-dynamic-area-item">
4179 <div class="presslearn-iframe-container" style="width:' . esc_attr($width) . 'px;max-width:100%;margin:0 auto;">' . $safe_iframe_code . '</div>
4180 </div>
4181 <div class="pl-dynamic-cover enable" data-message="밀어서 제거">
4182 <img src="' . esc_url($cover_banner_url) . '" alt="' . esc_attr($banner['name']) . '" width="' . esc_attr($width) . '" height="' . esc_attr($height) . '" style="max-width:100%;height:auto;display:block;" draggable="true" />
4183 </div>
4184 </div>
4185 ';
4186 }
4187
4188 function presslearn_sanitize_iframe($iframe_code) {
4189 if (empty($iframe_code)) {
4190 return '';
4191 }
4192
4193 if (!preg_match('/<iframe[^>]*>/i', $iframe_code)) {
4194 return '';
4195 }
4196
4197 $iframe_code = preg_replace('/<script[^>]*>.*?<\/script>/is', '', $iframe_code);
4198 $iframe_code = preg_replace('/<style[^>]*>.*?<\/style>/is', '', $iframe_code);
4199 $iframe_code = preg_replace('/\s*on\w+\s*=\s*["\'][^"\']*["\']/i', '', $iframe_code);
4200 $iframe_code = preg_replace('/\s*javascript\s*:/i', '', $iframe_code);
4201
4202 if (preg_match('/<iframe[^>]*>.*?<\/iframe>/is', $iframe_code, $matches)) {
4203 $iframe_tag = $matches[0];
4204 } else if (preg_match('/<iframe[^>]*\/?>/i', $iframe_code, $matches)) {
4205 $iframe_tag = $matches[0];
4206 if (!preg_match('/\/\s*>$/', $iframe_tag)) {
4207 $iframe_tag .= '</iframe>';
4208 }
4209 } else {
4210 return '';
4211 }
4212
4213 $trusted_domains = array(
4214 'ads-partners.coupang.com',
4215 'partner.googleadservices.com',
4216 'googleads.g.doubleclick.net',
4217 'www.googletagmanager.com',
4218 'youtube.com',
4219 'www.youtube.com',
4220 'player.vimeo.com',
4221 'cdnjs.cloudflare.com',
4222 'fonts.googleapis.com'
4223 );
4224
4225 if (preg_match('/src\s*=\s*["\']([^"\']*)["\']/', $iframe_tag, $src_matches)) {
4226 $src_url = $src_matches[1];
4227 $parsed_url = parse_url($src_url);
4228
4229 if (!empty($parsed_url['host'])) {
4230 $is_trusted = false;
4231 foreach ($trusted_domains as $trusted_domain) {
4232 if ($parsed_url['host'] === $trusted_domain ||
4233 substr($parsed_url['host'], -strlen('.' . $trusted_domain)) === '.' . $trusted_domain) {
4234 $is_trusted = true;
4235 break;
4236 }
4237 }
4238
4239 if (!$is_trusted && !current_user_can('manage_options')) {
4240 return '';
4241 }
4242 }
4243 }
4244
4245 $allowed_attributes = array(
4246 'src', 'width', 'height', 'frameborder', 'scrolling',
4247 'allowfullscreen', 'loading', 'title', 'name', 'id',
4248 'class', 'style', 'referrerpolicy', 'browsingtopics'
4249 );
4250
4251 $iframe_tag = preg_replace_callback(
4252 '/(\w+)\s*=\s*["\']([^"\']*)["\']/',
4253 function($matches) use ($allowed_attributes) {
4254 if (in_array(strtolower($matches[1]), $allowed_attributes)) {
4255 return $matches[1] . '="' . esc_attr($matches[2]) . '"';
4256 }
4257 return '';
4258 },
4259 $iframe_tag
4260 );
4261
4262 return $iframe_tag;
4263 }
4264
4265 function presslearn_add_analytics_column($columns) {
4266 $columns['presslearn_analytics'] = '<span class="dashicons dashicons-chart-bar" style="font-size: 16px; vertical-align: text-top;"></span> 통계';
4267 return $columns;
4268 }
4269 add_filter('manage_posts_columns', 'presslearn_add_analytics_column');
4270 add_filter('manage_pages_columns', 'presslearn_add_analytics_column');
4271
4272 function presslearn_analytics_column_content($column, $post_id) {
4273 if ($column !== 'presslearn_analytics') {
4274 return;
4275 }
4276
4277 global $wpdb;
4278 $permalink = get_permalink($post_id);
4279 $table_pageviews = $wpdb->prefix . 'presslearn_pageviews';
4280
4281 if ($wpdb->get_var("SHOW TABLES LIKE '$table_pageviews'") != $table_pageviews) {
4282 ?>
4283 <div class="presslearn-analytics-stats">
4284 <span class="presslearn-views">0 </span>
4285 <span class="presslearn-visitors">0 방문</span>
4286 </div>
4287 <?php
4288 return;
4289 }
4290
4291 $cache_time = get_option('presslearn_analytics_cache_time', 300);
4292 $cache_date = current_time('Y-m-d');
4293 $cache_key_visitors = "presslearn_visitors_{$post_id}_{$cache_date}";
4294 $cache_key_today = "presslearn_today_{$post_id}_{$cache_date}";
4295 $cache_key_week = "presslearn_week_{$post_id}_{$cache_date}";
4296 $cache_key_month = "presslearn_month_{$post_id}_{$cache_date}";
4297
4298 $total_views = get_post_meta($post_id, '_presslearn_post_views', true);
4299 if (empty($total_views)) {
4300 $total_views = $wpdb->get_var($wpdb->prepare(
4301 "SELECT COUNT(*) FROM $table_pageviews WHERE url = %s",
4302 $permalink
4303 ));
4304 $total_views = intval($total_views);
4305
4306 update_post_meta($post_id, '_presslearn_post_views', $total_views);
4307 }
4308
4309 $total_visitors = wp_cache_get($cache_key_visitors, 'presslearn_analytics');
4310 if (false === $total_visitors) {
4311 $total_visitors = $wpdb->get_var($wpdb->prepare(
4312 "SELECT COUNT(DISTINCT visitor_id) FROM $table_pageviews WHERE url = %s",
4313 $permalink
4314 ));
4315 $total_visitors = intval($total_visitors);
4316 wp_cache_set($cache_key_visitors, $total_visitors, 'presslearn_analytics', $cache_time);
4317 }
4318
4319 $today_views = wp_cache_get($cache_key_today, 'presslearn_analytics');
4320 if (false === $today_views) {
4321 $today_views = $wpdb->get_var($wpdb->prepare(
4322 "SELECT COUNT(*) FROM $table_pageviews WHERE url = %s AND DATE(created_at) = CURDATE()",
4323 $permalink
4324 ));
4325 $today_views = intval($today_views);
4326 wp_cache_set($cache_key_today, $today_views, 'presslearn_analytics', $cache_time);
4327 }
4328
4329 $week_views = wp_cache_get($cache_key_week, 'presslearn_analytics');
4330 if (false === $week_views) {
4331 $week_views = $wpdb->get_var($wpdb->prepare(
4332 "SELECT COUNT(*) FROM $table_pageviews WHERE url = %s AND created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)",
4333 $permalink
4334 ));
4335 $week_views = intval($week_views);
4336 wp_cache_set($cache_key_week, $week_views, 'presslearn_analytics', $cache_time);
4337 }
4338
4339 $month_views = wp_cache_get($cache_key_month, 'presslearn_analytics');
4340 if (false === $month_views) {
4341 $month_views = $wpdb->get_var($wpdb->prepare(
4342 "SELECT COUNT(*) FROM $table_pageviews WHERE url = %s AND created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)",
4343 $permalink
4344 ));
4345 $month_views = intval($month_views);
4346 wp_cache_set($cache_key_month, $month_views, 'presslearn_analytics', $cache_time);
4347 }
4348
4349 ?>
4350 <div class="presslearn-analytics-stats">
4351 <div class="presslearn-stats-row">
4352 <span class="presslearn-total">
4353 <span class="dashicons dashicons-chart-bar" style="font-size: 16px; color: #2196F3; opacity: 0.8;"></span>
4354 <?php echo esc_html(number_format($total_views)); ?>
4355 </span>
4356 </div>
4357
4358 <div class="presslearn-stats-periods">
4359 <div class="presslearn-period today" title="<?php echo esc_attr('오늘 조회수'); ?>">
4360 <span><?php echo esc_html('오늘'); ?></span> <?php echo esc_html(number_format($today_views)); ?>
4361 </div>
4362 <div class="presslearn-period week" title="<?php echo esc_attr('최근 7일 조회수'); ?>">
4363 <span><?php echo esc_html('7일'); ?></span> <?php echo esc_html(number_format($week_views)); ?>
4364 </div>
4365 <div class="presslearn-period month" title="<?php echo esc_attr('최근 30일 조회수'); ?>">
4366 <span><?php echo esc_html('30일'); ?></span> <?php echo esc_html(number_format($month_views)); ?>
4367 </div>
4368 </div>
4369 </div>
4370 <?php
4371 }
4372 add_action('manage_posts_custom_column', 'presslearn_analytics_column_content', 10, 2);
4373 add_action('manage_pages_custom_column', 'presslearn_analytics_column_content', 10, 2);
4374
4375 function presslearn_analytics_column_sortable($columns) {
4376 $columns['presslearn_analytics'] = 'presslearn_analytics';
4377 return $columns;
4378 }
4379 add_filter('manage_edit-post_sortable_columns', 'presslearn_analytics_column_sortable');
4380 add_filter('manage_edit-page_sortable_columns', 'presslearn_analytics_column_sortable');
4381
4382 function presslearn_analytics_column_orderby($query) {
4383 if (!is_admin()) {
4384 return;
4385 }
4386
4387 $orderby = $query->get('orderby');
4388
4389 if ($orderby == 'presslearn_analytics') {
4390 global $wpdb;
4391 $table_pageviews = $wpdb->prefix . 'presslearn_pageviews';
4392
4393 if ($wpdb->get_var("SHOW TABLES LIKE '$table_pageviews'") != $table_pageviews) {
4394 return;
4395 }
4396
4397 $query->set('meta_key', '_presslearn_post_views');
4398 $query->set('orderby', 'meta_value_num');
4399 }
4400 }
4401 add_action('pre_get_posts', 'presslearn_analytics_column_orderby');
4402
4403 function presslearn_schedule_post_views_update() {
4404 if (!wp_next_scheduled('presslearn_daily_post_views_update')) {
4405 wp_schedule_event(strtotime('today 00:00:00'), 'daily', 'presslearn_daily_post_views_update');
4406 }
4407 }
4408 add_action('wp', 'presslearn_schedule_post_views_update');
4409
4410 function presslearn_add_analytics_column_styles() {
4411 wp_register_style('presslearn-analytics-column-css', false);
4412 wp_enqueue_style('presslearn-analytics-column-css');
4413
4414 $analytics_column_css = "
4415 .presslearn-analytics-stats {
4416 display: block;
4417 line-height: 1.5;
4418 text-align: left;
4419 }
4420
4421 .presslearn-stats-row {
4422 display: flex;
4423 align-items: center;
4424 margin-bottom: 5px
4425 }
4426
4427 .presslearn-label {
4428 font-size: 13px;
4429 color: #666;
4430 margin-right: 5px;
4431 }
4432
4433 .presslearn-total {
4434 font-size: 16px;
4435 font-weight: bold;
4436 color: #2196F3;
4437 display: flex;
4438 align-items: center;
4439 }
4440
4441 .presslearn-total .dashicons {
4442 margin-right: 3px;
4443 }
4444
4445 .presslearn-unit {
4446 font-size: 12px;
4447 font-weight: normal;
4448 color: #666;
4449 margin-left: 1px;
4450 }
4451
4452 .presslearn-stats-periods {
4453 display: flex;
4454 flex-wrap: wrap;
4455 gap: 5px;
4456 flex-flow: column;
4457 }
4458
4459 .presslearn-period {
4460 display: flex;
4461 align-items: center;
4462 justify-content: space-between;
4463 font-size: 11px;
4464 padding: 2px 6px;
4465 border-radius: 5px;
4466 white-space: nowrap;
4467 }
4468
4469 .presslearn-period .dashicons {
4470 margin-right: 2px;
4471 }
4472
4473 .presslearn-period.today {
4474 background-color: #E8F5E9;
4475 color: #4CAF50;
4476 }
4477
4478 .presslearn-period.week {
4479 background-color: #E3F2FD;
4480 color: #2196F3;
4481 }
4482
4483 .presslearn-period.month {
4484 background-color: #EDE7F6;
4485 color: #673AB7;
4486 }
4487
4488 .column-presslearn_analytics {
4489 width: 165px;
4490 }
4491 ";
4492
4493 wp_add_inline_style('presslearn-analytics-column-css', $analytics_column_css);
4494 }
4495 add_action('admin_head', 'presslearn_add_analytics_column_styles');
4496
4497 function presslearn_update_post_views() {
4498 global $wpdb;
4499 $table_pageviews = $wpdb->prefix . 'presslearn_pageviews';
4500
4501 if ($wpdb->get_var("SHOW TABLES LIKE '$table_pageviews'") != $table_pageviews) {
4502 return;
4503 }
4504
4505 $args = array(
4506 'post_type' => array('post', 'page'),
4507 'posts_per_page' => -1,
4508 'post_status' => 'publish'
4509 );
4510 $posts = get_posts($args);
4511
4512 foreach ($posts as $post) {
4513 $permalink = get_permalink($post->ID);
4514
4515 $views = $wpdb->get_var($wpdb->prepare(
4516 "SELECT COUNT(*) FROM $table_pageviews WHERE url = %s",
4517 $permalink
4518 ));
4519
4520 update_post_meta($post->ID, '_presslearn_post_views', $views);
4521 }
4522 }
4523 add_action('presslearn_daily_post_views_update', 'presslearn_update_post_views');
4524
4525 register_activation_hook(__FILE__, 'presslearn_update_post_views');
4526
4527 function presslearn_button_animation_styles() {
4528 if (!function_exists('presslearn_plugin') || !presslearn_plugin()->is_plugin_activated()) {
4529 return;
4530 }
4531
4532 $quick_button_enabled = get_option('presslearn_quick_button_enabled', 'no');
4533 if ($quick_button_enabled !== 'yes') {
4534 return;
4535 }
4536
4537 wp_register_style('presslearn-button-animation-css', false);
4538 wp_enqueue_style('presslearn-button-animation-css');
4539
4540 $animation_styles = '
4541 .presslearn-button {
4542 transition: all 0.3s ease;
4543 }
4544
4545 @keyframes presslearn-pulse {
4546 0% { transform: scale(1); }
4547 50% { transform: scale(1.05); }
4548 100% { transform: scale(1); }
4549 }
4550
4551 .presslearn-button-animation-pulse:hover {
4552 animation: presslearn-pulse 1s infinite;
4553 }
4554
4555 .presslearn-button-animation-zoom:hover {
4556 transform: scale(1.1);
4557 }
4558
4559 .presslearn-button-animation-fade:hover {
4560 opacity: 0.8;
4561 }
4562
4563 @keyframes presslearn-shake {
4564 0%, 100% { transform: translateX(0); }
4565 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
4566 20%, 40%, 60%, 80% { transform: translateX(5px); }
4567 }
4568
4569 .presslearn-button-animation-shake:hover {
4570 animation: presslearn-shake 0.5s;
4571 }';
4572
4573 wp_add_inline_style('presslearn-button-animation-css', $animation_styles);
4574
4575 wp_register_script('presslearn-button-animation-js', false, array(), PRESSLEARN_PLUGIN_VERSION, true);
4576 wp_enqueue_script('presslearn-button-animation-js');
4577
4578 $animation_script = '
4579 document.addEventListener("DOMContentLoaded", function() {
4580 const buttons = document.querySelectorAll(".presslearn-button");
4581
4582 buttons.forEach(function(button) {
4583 const originalColor = button.style.backgroundColor;
4584 const hoverColor = button.getAttribute("data-hover-color");
4585
4586 if (hoverColor && hoverColor !== originalColor) {
4587 button.addEventListener("mouseenter", function() {
4588 this.style.backgroundColor = hoverColor;
4589 });
4590
4591 button.addEventListener("mouseleave", function() {
4592 this.style.backgroundColor = originalColor;
4593 });
4594 }
4595 });
4596 });';
4597
4598 wp_add_inline_script('presslearn-button-animation-js', $animation_script);
4599 }
4600 add_action('wp_body_open', 'presslearn_button_animation_styles');
4601
4602 function presslearn_button_animation_admin_styles() {
4603 if (!function_exists('presslearn_plugin') || !presslearn_plugin()->is_plugin_activated()) {
4604 return;
4605 }
4606
4607 $quick_button_enabled = get_option('presslearn_quick_button_enabled', 'no');
4608 if ($quick_button_enabled !== 'yes') {
4609 return;
4610 }
4611
4612 wp_register_style('presslearn-button-animation-admin-css', false);
4613 wp_enqueue_style('presslearn-button-animation-admin-css');
4614
4615 $admin_animation_styles = '
4616 .editor-styles-wrapper .presslearn-button {
4617 transition: all 0.3s ease;
4618 }
4619
4620 @keyframes presslearn-pulse {
4621 0% { transform: scale(1); }
4622 50% { transform: scale(1.05); }
4623 100% { transform: scale(1); }
4624 }
4625
4626 .editor-styles-wrapper .presslearn-button-animation-pulse:hover {
4627 animation: presslearn-pulse 1s infinite;
4628 }
4629
4630 .editor-styles-wrapper .presslearn-button-animation-zoom:hover {
4631 transform: scale(1.1);
4632 }
4633
4634 .editor-styles-wrapper .presslearn-button-animation-fade:hover {
4635 opacity: 0.8;
4636 }
4637
4638 @keyframes presslearn-shake {
4639 0%, 100% { transform: translateX(0); }
4640 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
4641 20%, 40%, 60%, 80% { transform: translateX(5px); }
4642 }
4643
4644 .editor-styles-wrapper .presslearn-button-animation-shake:hover {
4645 animation: presslearn-shake 0.5s;
4646 }';
4647
4648 wp_add_inline_style('presslearn-button-animation-admin-css', $admin_animation_styles);
4649 }
4650 add_action('admin_enqueue_scripts', 'presslearn_button_animation_admin_styles');
4651
4652 function presslearn_delete_analytics_data() {
4653 if (!current_user_can('manage_options')) {
4654 wp_die('권한이 없습니다.');
4655 }
4656
4657 check_ajax_referer('presslearn_delete_analytics_data_nonce', 'nonce');
4658
4659 global $wpdb;
4660 $table_pageviews = $wpdb->prefix . 'presslearn_pageviews';
4661 $table_visitors = $wpdb->prefix . 'presslearn_visitors';
4662 $table_referrers = $wpdb->prefix . 'presslearn_referrers';
4663
4664 $tables_exist = true;
4665
4666 if ($wpdb->get_var("SHOW TABLES LIKE '$table_pageviews'") != $table_pageviews) {
4667 $tables_exist = false;
4668 }
4669
4670 if ($wpdb->get_var("SHOW TABLES LIKE '$table_visitors'") != $table_visitors) {
4671 $tables_exist = false;
4672 }
4673
4674 if ($wpdb->get_var("SHOW TABLES LIKE '$table_referrers'") != $table_referrers) {
4675 $tables_exist = false;
4676 }
4677
4678 if (!$tables_exist) {
4679 wp_send_json_error(array('message' => '�
4680 �이블이 존재하지 않습니다.'));
4681 return;
4682 }
4683
4684 $wpdb->query("TRUNCATE TABLE $table_pageviews");
4685 $wpdb->query("TRUNCATE TABLE $table_visitors");
4686 $wpdb->query("TRUNCATE TABLE $table_referrers");
4687
4688 $wpdb->query("UPDATE $wpdb->posts SET presslearn_post_views = 0 WHERE presslearn_post_views > 0");
4689
4690 $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_alpack_%'");
4691 $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_alpack_%'");
4692
4693 wp_cache_flush();
4694
4695 wp_send_json_success(array('message' => '모든 통계 데이터와 캐시가 성공적으로 삭제되었습니다.'));
4696 }
4697
4698 add_action('wp_ajax_presslearn_dismiss_update_notice', 'presslearn_dismiss_update_notice');
4699
4700 function presslearn_dismiss_update_notice() {
4701 check_ajax_referer('presslearn_dismiss_notice', 'nonce');
4702
4703 if (current_user_can('manage_options')) {
4704 delete_transient('presslearn_plugin_updated_notice');
4705 wp_send_json_success();
4706 } else {
4707 wp_send_json_error('권한이 없습니다.');
4708 }
4709
4710 wp_die();
4711 }
4712
4713 add_action('wp_head', 'presslearn_output_header_code', 999);
4714 add_action('wp_body_open', 'presslearn_output_body_open_code');
4715 add_action('wp_footer', 'presslearn_output_before_closing_body_code', 1);
4716 add_action('wp_footer', 'presslearn_output_footer_code', 999);
4717
4718 add_action('wp_dashboard_setup', 'presslearn_add_dashboard_widget');
4719
4720 function presslearn_add_dashboard_widget() {
4721 $is_activated = presslearn_plugin()->is_plugin_activated();
4722 $analytics_enabled = get_option('presslearn_analytics_enabled', 'no');
4723
4724 if ($is_activated && $analytics_enabled === 'yes') {
4725 wp_add_dashboard_widget(
4726 'presslearn_analytics_highlight',
4727 '씬 애널리틱스 하이라이트',
4728 'presslearn_render_dashboard_widget'
4729 );
4730
4731 global $wp_meta_boxes;
4732 $normal_dashboard = $wp_meta_boxes['dashboard']['normal']['core'];
4733 $widget_backup = array('presslearn_analytics_highlight' => $normal_dashboard['presslearn_analytics_highlight']);
4734 unset($normal_dashboard['presslearn_analytics_highlight']);
4735 $sorted_dashboard = array_merge($widget_backup, $normal_dashboard);
4736 $wp_meta_boxes['dashboard']['normal']['core'] = $sorted_dashboard;
4737 }
4738 }
4739
4740 function presslearn_calculate_change($current, $previous) {
4741 if ($previous == 0) {
4742 return [
4743 'percentage' => 0,
4744 'show' => false
4745 ];
4746 }
4747
4748 $change = $current - $previous;
4749 $percentage = round(($change / $previous) * 100);
4750
4751 return [
4752 'change' => $change,
4753 'percentage' => $percentage,
4754 'show' => true,
4755 'is_increase' => $change > 0
4756 ];
4757 }
4758
4759 function presslearn_build_dashboard_stats() {
4760 global $wpdb;
4761 $table_pageviews = $wpdb->prefix . 'presslearn_pageviews';
4762
4763 $stats = [
4764 'pageviews' => 0,
4765 'visitors' => 0,
4766 'ip_visitors' => 0,
4767 'prev_pageviews' => 0,
4768 'prev_visitors' => 0,
4769 'prev_ip_visitors' => 0
4770 ];
4771
4772 if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table_pageviews)) === $table_pageviews) {
4773 $current = $wpdb->get_row("
4774 SELECT COUNT(*) AS pageviews,
4775 COUNT(DISTINCT visitor_id) AS visitors,
4776 COUNT(DISTINCT ip) AS ip_visitors
4777 FROM $table_pageviews
4778 WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
4779 ", ARRAY_A);
4780
4781 $previous = $wpdb->get_row("
4782 SELECT COUNT(*) AS pageviews,
4783 COUNT(DISTINCT visitor_id) AS visitors,
4784 COUNT(DISTINCT ip) AS ip_visitors
4785 FROM $table_pageviews
4786 WHERE created_at BETWEEN DATE_SUB(CURDATE(), INTERVAL 14 DAY) AND DATE_SUB(CURDATE(), INTERVAL 8 DAY)
4787 ", ARRAY_A);
4788
4789 if (is_array($current)) {
4790 $stats['pageviews'] = intval($current['pageviews']);
4791 $stats['visitors'] = intval($current['visitors']);
4792 $stats['ip_visitors'] = intval($current['ip_visitors']);
4793 }
4794
4795 if (is_array($previous)) {
4796 $stats['prev_pageviews'] = intval($previous['pageviews']);
4797 $stats['prev_visitors'] = intval($previous['visitors']);
4798 $stats['prev_ip_visitors'] = intval($previous['ip_visitors']);
4799 }
4800 }
4801
4802 set_transient('presslearn_dashboard_stats', $stats, 10 * MINUTE_IN_SECONDS);
4803
4804 return $stats;
4805 }
4806
4807 function presslearn_render_dashboard_stats_html($stats) {
4808 $items = [
4809 ['label' => '조회', 'value' => $stats['pageviews'], 'prev' => $stats['prev_pageviews']],
4810 ['label' => '방문자 (IP 기준)', 'value' => $stats['ip_visitors'], 'prev' => $stats['prev_ip_visitors']],
4811 ['label' => '방문자 (브라우저 기준)', 'value' => $stats['visitors'], 'prev' => $stats['prev_visitors']],
4812 ];
4813
4814 ob_start();
4815
4816 foreach ($items as $item) {
4817 $change = presslearn_calculate_change($item['value'], $item['prev']);
4818 ?>
4819 <div class="pl-widget-stat">
4820 <div class="pl-widget-stat-label"><?php echo esc_html($item['label']); ?></div>
4821 <div class="pl-widget-stat-value"><?php echo esc_html(number_format($item['value'])); ?></div>
4822 <?php if ($change['show']): ?>
4823 <div class="pl-widget-stat-change <?php echo esc_attr($change['is_increase'] ? 'increase' : 'decrease'); ?>">
4824 <?php echo esc_html($change['is_increase'] ? '' : ''); ?>
4825 <?php echo esc_html(number_format(abs($change['change']))); ?>
4826 (<?php echo esc_html(abs($change['percentage'])); ?>%)
4827 </div>
4828 <?php endif; ?>
4829 </div>
4830 <?php
4831 }
4832
4833 return ob_get_clean();
4834 }
4835
4836 add_action('wp_ajax_presslearn_dashboard_stats', 'presslearn_ajax_dashboard_stats');
4837
4838 function presslearn_ajax_dashboard_stats() {
4839 check_ajax_referer('presslearn_dashboard_stats', 'nonce');
4840
4841 if (!current_user_can('read')) {
4842 wp_send_json_error(array('message' => '권한이 없습니다.'));
4843 }
4844
4845 $stats = presslearn_build_dashboard_stats();
4846
4847 wp_send_json_success(array('html' => presslearn_render_dashboard_stats_html($stats)));
4848 }
4849
4850 function presslearn_render_dashboard_widget() {
4851 $stats = get_transient('presslearn_dashboard_stats');
4852
4853 ?>
4854 <style>
4855 .pl-dashboard-widget {
4856 margin: -10px -12px;
4857 }
4858 .pl-widget-header {
4859 display: flex;
4860 justify-content: space-between;
4861 align-items: center;
4862 padding: 12px 15px;
4863 background: #f8f9fa;
4864 border-bottom: 1px solid #e0e0e0;
4865 }
4866 .pl-widget-period {
4867 color: #666;
4868 font-size: 12px;
4869 font-weight: normal;
4870 }
4871 .pl-widget-stats {
4872 padding: 15px;
4873 }
4874 .pl-widget-placeholder {
4875 color: #666;
4876 font-size: 13px;
4877 padding: 20px 0;
4878 text-align: center;
4879 }
4880 .pl-widget-stat {
4881 margin-bottom: 15px;
4882 padding-bottom: 15px;
4883 border-bottom: 1px solid #f0f0f0;
4884 }
4885 .pl-widget-stat:last-child {
4886 margin-bottom: 0;
4887 padding-bottom: 0;
4888 border-bottom: none;
4889 }
4890 .pl-widget-stat-label {
4891 color: #666;
4892 font-size: 12px;
4893 margin-bottom: 5px;
4894 }
4895 .pl-widget-stat-value {
4896 font-size: 24px;
4897 font-weight: 600;
4898 color: #2196F3;
4899 margin-bottom: 3px;
4900 }
4901 .pl-widget-stat-change {
4902 font-size: 12px;
4903 font-weight: normal;
4904 }
4905 .pl-widget-stat-change.increase {
4906 color: #4caf50;
4907 }
4908 .pl-widget-stat-change.decrease {
4909 color: #ef4444;
4910 }
4911 .pl-widget-footer {
4912 padding: 12px 15px;
4913 background: #f8f9fa;
4914 border-top: 1px solid #e0e0e0;
4915 text-align: center;
4916 }
4917 .pl-widget-footer a {
4918 color: #2271b1;
4919 text-decoration: none;
4920 font-size: 13px;
4921 }
4922 .pl-widget-footer a:hover {
4923 text-decoration: underline;
4924 }
4925 </style>
4926
4927 <div class="pl-dashboard-widget">
4928 <div class="pl-widget-header">
4929 <h3 style="margin: 0; font-size: 14px; font-weight: 600;">최근 7 통계</h3>
4930 <span class="pl-widget-period">최근 7</span>
4931 </div>
4932
4933 <div class="pl-widget-stats" id="pl-widget-stats">
4934 <?php if (is_array($stats)): ?>
4935 <?php echo presslearn_render_dashboard_stats_html($stats); ?>
4936 <?php else: ?>
4937 <div class="pl-widget-placeholder">통계를 불러오는 중�
4938 니다.</div>
4939 <?php endif; ?>
4940 </div>
4941
4942 <div class="pl-widget-footer">
4943 <a href="<?php echo esc_url(admin_url('admin.php?page=presslearn-analytics&tab=statistics')); ?>">전체 통계 보기 </a>
4944 </div>
4945 </div>
4946
4947 <?php if (!is_array($stats)): ?>
4948 <script>
4949 (function() {
4950 var target = document.getElementById('pl-widget-stats');
4951 if (!target) {
4952 return;
4953 }
4954
4955 var params = new URLSearchParams();
4956 params.append('action', 'presslearn_dashboard_stats');
4957 params.append('nonce', <?php echo wp_json_encode(wp_create_nonce('presslearn_dashboard_stats')); ?>);
4958
4959 fetch(<?php echo wp_json_encode(admin_url('admin-ajax.php')); ?>, {
4960 method: 'POST',
4961 credentials: 'same-origin',
4962 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
4963 body: params.toString()
4964 })
4965 .then(function(response) { return response.json(); })
4966 .then(function(result) {
4967 if (result && result.success && result.data && result.data.html) {
4968 target.innerHTML = result.data.html;
4969 } else {
4970 target.innerHTML = '<div class="pl-widget-placeholder">통계를 불러오지 못했습니다.</div>';
4971 }
4972 })
4973 .catch(function() {
4974 target.innerHTML = '<div class="pl-widget-placeholder">통계를 불러오지 못했습니다.</div>';
4975 });
4976 })();
4977 </script>
4978 <?php endif; ?>
4979 <?php
4980 }
4981
4982 /**
4983 * Output header code in <head> section
4984 *
4985 * Security Note: Code is intentionally output without escaping as this is
4986 * an admin-only feature for inserting HTML/CSS/JS code. Only users with
4987 * 'manage_options' capability can save/output code.
4988 */
4989 function presslearn_output_header_code() {
4990 if (get_option('presslearn_header_footer_enabled', 'no') !== 'yes') {
4991 return;
4992 }
4993
4994 $header_code = get_option('presslearn_header_code', '');
4995 if (!empty($header_code)) {
4996 if (current_user_can('manage_options') || !is_admin()) {
4997 echo "\n <!-- PressLearn Header Code -->\n";
4998 echo wp_unslash($header_code) . "\n";
4999 echo " <!-- /PressLearn Header Code -->\n\n";
5000 }
5001 }
5002 }
5003
5004 function presslearn_output_body_open_code() {
5005 if (get_option('presslearn_header_footer_enabled', 'no') !== 'yes') {
5006 return;
5007 }
5008
5009 $body_open_code = get_option('presslearn_body_open_code', '');
5010 if (!empty($body_open_code)) {
5011 if (current_user_can('manage_options') || !is_admin()) {
5012 echo "\n<!-- PressLearn Body Open Code -->\n";
5013 echo wp_unslash($body_open_code) . "\n";
5014 echo "<!-- /PressLearn Body Open Code -->\n\n";
5015 }
5016 }
5017 }
5018
5019 function presslearn_output_before_closing_body_code() {
5020 if (get_option('presslearn_header_footer_enabled', 'no') !== 'yes') {
5021 return;
5022 }
5023
5024 $before_closing_code = get_option('presslearn_before_closing_body_code', '');
5025 if (!empty($before_closing_code)) {
5026 if (current_user_can('manage_options') || !is_admin()) {
5027 echo "\n<!-- PressLearn Before Closing Body Code -->\n";
5028 echo wp_unslash($before_closing_code) . "\n";
5029 echo "<!-- /PressLearn Before Closing Body Code -->\n\n";
5030 }
5031 }
5032 }
5033
5034 function presslearn_output_footer_code() {
5035 if (get_option('presslearn_header_footer_enabled', 'no') !== 'yes') {
5036 return;
5037 }
5038
5039 $footer_code = get_option('presslearn_footer_code', '');
5040 if (!empty($footer_code)) {
5041 if (current_user_can('manage_options') || !is_admin()) {
5042 echo "\n<!-- PressLearn Footer Code -->\n";
5043 echo wp_unslash($footer_code) . "\n";
5044 echo "<!-- /PressLearn Footer Code -->\n\n";
5045 }
5046 }
5047 }
5048
5049 /**
5050 * 사용자가 헤더 & 푸터 토글을 직접 조작한 적이 없을 때만 기능을 켠다.
5051 * 수동으로 끈 경우에는 그 선택을 덮어쓰지 않는다.
5052 */
5053 function presslearn_auto_enable_header_footer() {
5054 if (get_option('presslearn_header_footer_user_set', 'no') === 'yes') {
5055 return;
5056 }
5057
5058 if (get_option('presslearn_header_footer_enabled', 'no') === 'yes') {
5059 return;
5060 }
5061
5062 update_option('presslearn_header_footer_enabled', 'yes');
5063 }
5064
5065 function presslearn_maybe_auto_enable_header_footer() {
5066 if (!function_exists('presslearn_plugin') || !presslearn_plugin()->is_plugin_activated()) {
5067 return;
5068 }
5069
5070 presslearn_auto_enable_header_footer();
5071 }
5072 add_action('init', 'presslearn_maybe_auto_enable_header_footer');
5073