PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.5.23
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.5.23
2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 All 139 releases
metasync / includes / class-metasync-admin-pages.php

class-metasync-admin-pages.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.5.23, at includes/class-metasync-admin-pages.php

1,993 lines 93.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit;
4 }
5
6 /**
7 * Admin page renderer callbacks extracted from Metasync_Admin.
8 *
9 * Each public method renders a specific admin page. The class receives the
10 * Metasync_Admin instance so it can call back into shared helpers such as
11 * render_plugin_header() and render_navigation_menu().
12 *
13 * @package Metasync
14 * @subpackage Metasync/includes
15 */
16 class Metasync_Admin_Pages
17 {
18 /** @var Metasync_Admin */
19 private $admin;
20
21 /** @var self|null */
22 private static $instance = null;
23
24 private function __construct(Metasync_Admin $admin)
25 {
26 $this->admin = $admin;
27 }
28
29 /**
30 * @param Metasync_Admin $admin
31 * @return self
32 */
33 public static function get_instance(Metasync_Admin $admin): self
34 {
35 if (null === self::$instance || self::$instance->admin !== $admin) {
36 self::$instance = new self($admin);
37 }
38 return self::$instance;
39 }
40
41 // ------------------------------------------------------------------
42 // Dashboard iframe (~200 lines)
43 // ------------------------------------------------------------------
44
45 public function create_admin_dashboard_iframe()
46 {
47 $general_options = Metasync::get_option('general');
48 $otto_pixel_uuid = isset($general_options['otto_pixel_uuid']) ? $general_options['otto_pixel_uuid'] : '';
49 $api_key = isset($general_options['searchatlas_api_key']) ? $general_options['searchatlas_api_key'] : '';
50
51 $hide_dashboard = $general_options['hide_dashboard_framework'] ?? false;
52
53 if ($hide_dashboard) {
54 ?>
55 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
56 <?php $this->admin->render_plugin_header('Dashboard'); ?>
57
58 <?php $this->admin->render_navigation_menu('dashboard'); ?>
59
60 <div class="dashboard-card">
61 <h2>📊 Dashboard Disabled</h2>
62 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">
63 The dashboard is currently Disabled.
64 </p>
65 </div>
66 </div>
67 <?php
68 return;
69 }
70
71 if (!$this->admin->is_heartbeat_connected()) {
72 ?>
73 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
74 <?php $this->admin->render_plugin_header('Dashboard'); ?>
75
76 <?php $this->admin->render_navigation_menu('dashboard'); ?>
77
78 <div class="dashboard-card">
79 <h2>🚀 Setup Wizard</h2>
80 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Run the setup wizard to configure your plugin, import from other SEO plugins, and optimize your settings in just a few minutes.</p>
81 <a href="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-setup-wizard')); ?>" class="button button-primary" style="text-decoration: none;">
82 Start Setup Wizard
83 </a>
84 </div>
85
86 <div class="dashboard-card">
87 <h2>⚠️ Authentication Required</h2>
88 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">
89 You need to authenticate with <?php echo esc_html(Metasync::get_effective_plugin_name()); ?> to access the dashboard.
90 </p>
91 <a href="<?php echo admin_url('admin.php?page=' . Metasync_Admin::$page_slug); ?>" class="button button-primary">
92 🔗 Go to Settings & Connect
93 </a>
94 </div>
95 </div>
96 <?php
97 return;
98 }
99
100 $jwt_token = $this->admin->get_fresh_jwt_token();
101
102 if ($jwt_token) {
103 $jwt_parts = explode('.', $jwt_token);
104 $jwt_header_info = count($jwt_parts) >= 2 ? 'Valid format (' . count($jwt_parts) . ' parts)' : 'Invalid format';
105 } else {
106 }
107
108 $public_hash = false;
109 if ($jwt_token && $otto_pixel_uuid) {
110 $public_hash = $this->admin->fetch_public_hash($otto_pixel_uuid, $jwt_token);
111
112 if ($public_hash) {
113 } else {
114 }
115 } else {
116 $missing_params = [];
117 if (empty($jwt_token)) $missing_params[] = 'JWT_TOKEN';
118 if (empty($otto_pixel_uuid)) $missing_params[] = 'OTTO_UUID';
119 }
120
121 $dashboard_domain = Metasync_Admin::get_effective_dashboard_domain();
122
123 if ($public_hash) {
124 $iframe_url = $dashboard_domain . '/seo-automation-v3/public?uuid=' . urlencode($otto_pixel_uuid)
125 . '&category=onpage_optimizations&subGroup=page_title&public_hash=' . urlencode($public_hash);
126 } else {
127 $iframe_url = $dashboard_domain . '/seo-automation-v3/tasks?uuid=' . urlencode($otto_pixel_uuid) . '&category=All&Embed=True';
128 if ($jwt_token) {
129 $iframe_url .= '&jwtToken=' . urlencode($jwt_token) . '&impersonate=1';
130 } else {
131 }
132 }
133
134 $iframe_url .= '&source=wordpress-plugin-iframe';
135
136 $whitelabel_company_name = Metasync::get_whitelabel_company_name();
137 if ($whitelabel_company_name) {
138 $iframe_url .= '&whitelabel=' . urlencode($whitelabel_company_name);
139 }
140
141 $whitelabel_settings = Metasync::get_whitelabel_settings();
142 $is_whitelabel_domain = !empty($whitelabel_settings['domain']);
143
144 ?>
145 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
146 <?php $this->admin->render_plugin_header('Dashboard'); ?>
147
148 <?php $this->admin->render_navigation_menu('dashboard'); ?>
149
150 <iframe id="metasync-dashboard-iframe"
151 src="<?php echo esc_url($iframe_url); ?>"
152 width="100%"
153 height="100vh"
154 frameborder="0"
155 <?php if (!$is_whitelabel_domain): ?>
156 allow="cookies"
157 referrerpolicy="strict-origin-when-cross-origin"
158 <?php endif; ?>
159 style="border: none; margin: 0; padding: 0; min-height: 800px;"
160 onload="adjustIframeHeight(this)">
161 </iframe>
162
163 <script>
164 function adjustIframeHeight(iframe) {
165 var attempts = 0;
166 var maxAttempts = 20;
167
168 function tryAdjustHeight() {
169 try {
170 attempts++;
171
172 var iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
173 if (iframeDocument) {
174 var body = iframeDocument.body;
175 var hasContent = body && (body.children.length > 1 || body.innerText.trim().length > 100);
176
177 if (!hasContent && attempts < maxAttempts) {
178 setTimeout(tryAdjustHeight, 500);
179 return;
180 }
181
182 var height = Math.max(
183 body ? body.scrollHeight : 0,
184 body ? body.offsetHeight : 0,
185 iframeDocument.documentElement.clientHeight,
186 iframeDocument.documentElement.scrollHeight,
187 iframeDocument.documentElement.offsetHeight
188 );
189
190 if (height > 600) {
191 iframe.style.height = height + 'px';
192 } else if (attempts < maxAttempts) {
193 setTimeout(tryAdjustHeight, 500);
194 return;
195 }
196 } else {
197 if (attempts < maxAttempts) {
198 setTimeout(tryAdjustHeight, 500);
199 return;
200 }
201 }
202 } catch (e) {
203 iframe.style.height = '100vh';
204 }
205 }
206
207 tryAdjustHeight();
208 }
209
210 window.addEventListener('resize', function() {
211 var iframe = document.getElementById('metasync-dashboard-iframe');
212 if (iframe) {
213 adjustIframeHeight(iframe);
214 }
215 });
216
217 setTimeout(function() {
218 var iframe = document.getElementById('metasync-dashboard-iframe');
219 if (iframe) {
220 adjustIframeHeight(iframe);
221 }
222 }, 3000);
223 </script>
224 </div>
225 <?php
226 }
227
228 // ------------------------------------------------------------------
229 // Settings page (~1000 lines)
230 // ------------------------------------------------------------------
231
232 public function create_admin_settings_page()
233 {
234 $active_tab = isset($_GET['tab']) ? $_GET['tab'] : 'general';
235
236 $whitelabel_settings = Metasync::get_whitelabel_settings();
237
238 $user_password = $whitelabel_settings['settings_password'] ?? '';
239 $hide_settings_enabled = !empty($whitelabel_settings['hide_settings']);
240
241 $protected_tabs = [];
242
243 if (!empty($user_password)) {
244 $protected_tabs[] = 'whitelabel';
245 }
246
247 if ($hide_settings_enabled && !empty($user_password)) {
248 $protected_tabs = ['general', 'whitelabel', 'advanced'];
249 }
250
251 $password_protection_enabled = false;
252 $password_validated = false;
253 $password_error = '';
254
255 if (in_array($active_tab, $protected_tabs)) {
256
257 $password_protection_enabled = !empty($user_password);
258
259 $auth = new Metasync_Auth_Manager('whitelabel', 1800);
260 $password_validated = $auth->has_access();
261
262 if (isset($_POST['whitelabel_password_submit']) && !$password_validated) {
263 $password_error = 'Incorrect password. Please try again.';
264 }
265 }
266
267 $is_authenticated = $password_validated;
268
269 $whitelabel_otto_name = Metasync::get_whitelabel_otto_name();
270
271 $page_slug = Metasync_Admin::$page_slug;
272 ?>
273 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
274
275 <?php $this->admin->render_plugin_header('Settings'); ?>
276
277 <?php
278 /*
279 # Temporarily commented out: Clear Cache notice and button (can be re-enabled later)
280
281 * <div class="notice notice-success">
282 * <p>
283 * <b>Clear all caches at once</b><br/>
284 * This will slow down your site until caches are rebuilt
285 * <button style="margin-left: 15px;" type ="button" class="button" id="clear_otto_caches" data-toggle="tooltip" data-placement="top" title="Clear all <?php echo $whitelabel_otto_name;?> Caches">Clear <?php echo $whitelabel_otto_name;?> Cache</button>
286 * </p>
287 * </div>
288 */
289 ?>
290
291 <?php $this->admin->render_navigation_menu('general'); ?>
292
293 <?php
294 if (in_array($active_tab, $protected_tabs)) {
295
296 if ($password_protection_enabled && !$password_validated) {
297 ?>
298 <div class="dashboard-card" style="max-width: 500px; margin: 0 auto;">
299 <h2 style="text-align: center;">🔐 Protected Section</h2>
300 <p style="color: var(--dashboard-text-secondary); margin-bottom: 30px; text-align: center;">
301 <?php
302 $plugin_name = Metasync::get_effective_plugin_name('');
303 if ($hide_settings_enabled && !empty($user_password)) {
304 printf('Please enter the password to access the %s Settings section.', esc_html($plugin_name));
305 } else {
306 echo 'Please enter the password to access the Branding section.';
307 }
308 ?>
309 </p>
310
311 <?php if (!empty($password_error)): ?>
312 <div style="background: #f8d7da; color: #721c24; padding: 12px; border-radius: 6px; margin-bottom: 20px; border: 1px solid #f5c6cb;">
313 <strong> Access Denied:</strong> <?php echo esc_html($password_error); ?>
314 </div>
315 <?php endif; ?>
316
317 <form method="post" action="" style="max-width: 400px; margin: 0 auto;">
318 <?php wp_nonce_field('whitelabel_password_nonce', 'whitelabel_nonce'); ?>
319
320 <div style="margin-bottom: 20px;">
321 <label for="whitelabel_password" style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--dashboard-text-primary);">
322 🔑 Validate Password
323 </label>
324 <input
325 type="password"
326 id="whitelabel_password"
327 name="whitelabel_password"
328 placeholder="Enter password to access protected settings"
329 style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; box-sizing: border-box;"
330 required
331 autocomplete="off"
332 />
333 </div>
334
335 <div style="text-align: center;">
336 <button
337 type="submit"
338 name="whitelabel_password_submit"
339 value="1"
340 class="button button-primary"
341 style="padding: 12px 24px; font-size: 14px; font-weight: 600;"
342 >
343 🚀 Submit Password
344 </button>
345 </div>
346 </form>
347
348 <div style="text-align: center; margin-top: 20px;">
349 <a href="#" id="metasync-forgot-password-link" style="color: #2271b1; text-decoration: none; font-size: 14px;">
350 🔓 Forgot Password?
351 </a>
352 <div id="metasync-recovery-message" style="margin-top: 15px; padding: 12px; border-radius: 6px; display: none;"></div>
353 </div>
354
355 </div>
356
357 <script>
358 jQuery(document).ready(function($) {
359 $('#whitelabel_password').focus();
360
361 $('#whitelabel_password').on('keypress', function(e) {
362 if (e.which === 13) {
363 $(this).closest('form').submit();
364 }
365 });
366
367 $('#metasync-forgot-password-link').on('click', function(e) {
368 e.preventDefault();
369
370 var $link = $(this);
371 var $message = $('#metasync-recovery-message');
372
373 $link.css('pointer-events', 'none').css('opacity', '0.6');
374 $message.removeClass('success error').hide();
375 $message.html('⏳ Sending recovery email...').css('background', '#f0f6fc').css('color', '#0c5ba5').css('border', '1px solid #cfe2f3').fadeIn(200);
376
377 $.ajax({
378 url: ajaxurl,
379 type: 'POST',
380 data: {
381 action: 'metasync_recover_password',
382 nonce: '<?php echo wp_create_nonce('metasync_recover_password_nonce'); ?>'
383 },
384 success: function(response) {
385 $link.css('pointer-events', 'auto').css('opacity', '1');
386
387 if (response.success) {
388 $message.addClass('success').html('�
389 ' + response.data.message)
390 .css('background', '#d4edda')
391 .css('color', '#155724')
392 .css('border', '1px solid #c3e6cb');
393 } else {
394 $message.addClass('error').html('' + response.data.message)
395 .css('background', '#f8d7da')
396 .css('color', '#721c24')
397 .css('border', '1px solid #f5c6cb');
398 }
399 },
400 error: function() {
401 $link.css('pointer-events', 'auto').css('opacity', '1');
402 $message.addClass('error').html('❌ An error occurred. Please try again.')
403 .css('background', '#f8d7da')
404 .css('color', '#721c24')
405 .css('border', '1px solid #f5c6cb');
406 }
407 });
408 });
409 });
410 </script>
411 <?php
412 return;
413 } elseif (!$password_protection_enabled) {
414 ?>
415 <div class="notice notice-info" style="margin: 15px 0;">
416 <p>
417 <strong>💡 Security Tip:</strong> You can set a custom password in the White Label Settings to protect this section.
418 </p>
419 </div>
420 <?php
421 }
422 }
423 ?>
424
425 <form method="post" action="options.php?tab=<?php echo $active_tab?>" id="metaSyncGeneralSetting">
426 <?php
427 settings_fields(Metasync_Admin::option_group);
428
429 wp_nonce_field('meta_sync_general_setting_nonce', 'meta_sync_nonce');
430
431 if ($active_tab == 'general') {
432 ?>
433 <div class="dashboard-card">
434 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
435 <div style="flex: 1;">
436 <h2 style="margin: 0;">🔧 General Configuration</h2>
437 <p style="color: var(--dashboard-text-secondary); margin: 5px 0 0 0;">Configure your <?php echo esc_html(Metasync::get_effective_plugin_name()); ?> API, plugin features, caching, and general settings.</p>
438 </div>
439 <?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?>
440 <div>
441 <?php Metasync_Compatibility_Checker::instance()->render_lock_button('general'); ?>
442 </div>
443 <?php endif; ?>
444 </div>
445
446 <?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?>
447 <div style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 12px; border-radius: 4px; margin-bottom: 20px;">
448 <strong>�
449 Access Granted:</strong> You have successfully authenticated and can now modify settings.
450 </div>
451 <?php endif; ?>
452
453 <?php
454 $this->admin->render_accordion_sections(Metasync_Admin::$page_slug . '_general');
455 ?>
456 </div>
457
458 <div class="dashboard-card">
459 <h2>🔄 Content Genius Synchronization</h2>
460 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Sync your categories and user/author data with <?php echo esc_html(Metasync::get_effective_plugin_name()); ?>.</p>
461 <button type="button" class="button button-primary" id="sendAuthToken" data-toggle="tooltip" data-placement="top" title="Sync Categories and User">
462 🔄 Sync Now
463 </button>
464 </div>
465
466 <div class="dashboard-card">
467 <h2>🚀 Setup Wizard</h2>
468 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Run the setup wizard to configure your plugin, import from other SEO plugins, and optimize your settings in just a few minutes.</p>
469 <a href="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-setup-wizard')); ?>" class="button button-primary" style="text-decoration: none;">
470 Start Setup Wizard
471 </a>
472 </div>
473 <?php
474 } elseif ($active_tab == 'whitelabel') {
475 $whitelabel_settings = Metasync::get_whitelabel_settings();
476 $user_password = $whitelabel_settings['settings_password'] ?? '';
477 $password_protection_enabled = !empty($user_password);
478 ?>
479 <div class="dashboard-card">
480 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
481 <div>
482 <h2>🎨 White Label Branding</h2>
483 <p style="color: var(--dashboard-text-secondary); margin: 5px 0 0 0;">Customize the plugin appearance with your own branding and logo.</p>
484 </div>
485 <?php if ($password_protection_enabled && $is_authenticated): ?>
486 <div>
487 <?php Metasync_Compatibility_Checker::instance()->render_lock_button('whitelabel'); ?>
488 </div>
489 <?php endif; ?>
490 </div>
491
492 <?php if ($password_protection_enabled && $is_authenticated): ?>
493 <div style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 12px; border-radius: 4px; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
494 <div>
495 <strong>�
496 Access Granted:</strong> You have successfully authenticated and can now modify white label settings.
497 </div>
498 </div>
499
500 <?php endif; ?>
501
502 <?php
503 do_settings_sections(Metasync_Admin::$page_slug . '_branding');
504 ?>
505 </div>
506
507 <!-- Export Whitelabel Settings section -->
508 <div class="dashboard-card">
509 <h2>📦 Export Whitelabel Plugin</h2>
510 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Export the entire plugin with all whitelabel settings pre-configured. This creates a complete plugin zip file ready for installation on another WordPress site.</p>
511 <button type="button" class="button button-primary" id="metasync-export-whitelabel-btn" style="padding: 10px 20px; font-size: 14px; font-weight: 600;">
512 📥 Export Plugin with Whitelabel Settings
513 </button>
514 <p class="description" style="margin-top: 10px;">This will create a zip file containing the complete plugin with all your whitelabel settings included. Upload and install this zip file on another WordPress site via Plugins Add New Upload Plugin. All whitelabel configurations will be automatically applied upon activation.</p>
515 </div>
516
517 <!-- Advanced Access Control section -->
518 <?php Metasync_Access_Control_UI::render_access_control_table(); ?>
519
520 <!-- Custom Modal for Alerts -->
521 <div id="metasync-custom-modal" style="display: none; position: fixed; z-index: 999999; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.6);">
522 <div style="position: relative; margin: 10% auto; max-width: 500px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 2px; border-radius: 12px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); animation: modalSlideIn 0.3s ease-out;">
523 <div style="background: white; border-radius: 10px; padding: 0; overflow: hidden;">
524 <!-- Header -->
525 <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 24px; text-align: center;">
526 <div id="modal-icon" style="font-size: 48px; margin-bottom: 12px;">⚠️</div>
527 <h2 id="modal-title" style="color: white; margin: 0; font-size: 24px; font-weight: 600;">Password Required</h2>
528 </div>
529
530 <!-- Body -->
531 <div style="padding: 32px 24px;">
532 <p id="modal-message" style="color: #4a5568; font-size: 15px; line-height: 1.6; text-align: center; margin: 0;">
533 You must set a White Label Settings Password before enabling "Hide Settings".
534 </p>
535 </div>
536
537 <!-- Footer -->
538 <div style="padding: 0 24px 24px; text-align: center;">
539 <button type="button" id="modal-close-btn" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 12px 32px; border-radius: 6px; font-size: 15px; font-weight: 600; cursor: pointer; box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); transition: all 0.3s ease;">
540 Got It
541 </button>
542 </div>
543 </div>
544 </div>
545 </div>
546
547 <style>
548 @keyframes modalSlideIn {
549 from {
550 opacity: 0;
551 transform: translateY(-50px);
552 }
553 to {
554 opacity: 1;
555 transform: translateY(0);
556 }
557 }
558
559 #modal-close-btn:hover {
560 transform: translateY(-2px);
561 box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
562 }
563 </style>
564
565 <script>
566 jQuery(document).ready(function($) {
567 var isShowingModal = false;
568
569 function showModal(icon, title, message) {
570 isShowingModal = true;
571 $('#modal-icon').text(icon);
572 $('#modal-title').text(title);
573 $('#modal-message').html(message);
574 $('#metasync-custom-modal').fadeIn(200);
575
576 setTimeout(function() {
577 isShowingModal = false;
578 }, 300);
579 }
580
581 $('#modal-close-btn').on('click', function(e) {
582 e.preventDefault();
583 e.stopPropagation();
584 $('#metasync-custom-modal').fadeOut(200);
585 return false;
586 });
587
588 $('#metasync-custom-modal').on('click', function(e) {
589 if (e.target === this) {
590 $(this).fadeOut(200);
591 }
592 });
593
594 function hasPassword() {
595 var passwordField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]');
596 var passwordValue = passwordField.val();
597 return passwordValue && passwordValue.length > 0;
598 }
599
600 function hasRecoveryEmail() {
601 var recoveryEmailField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][recovery_email]"]');
602 var emailValue = recoveryEmailField.val();
603 return emailValue && emailValue.length > 0 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailValue);
604 }
605
606 var passwordField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]');
607 var recoveryEmailField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][recovery_email]"]');
608
609 passwordField.on('blur', function() {
610 if (hasPassword() && !hasRecoveryEmail()) {
611 showModal(
612 '📧',
613 'Recovery Email Required',
614 'You must set a <strong>Recovery Email</strong> when setting a password.<br><br>Please enter a valid email address in the <strong>Recovery Email</strong> field.'
615 );
616 recoveryEmailField.focus();
617 }
618 });
619
620 passwordField.on('input', function() {
621 if (hasPassword()) {
622 recoveryEmailField.attr('required', true);
623 recoveryEmailField.closest('tr').find('th').addClass('required-field');
624 } else {
625 recoveryEmailField.removeAttr('required');
626 recoveryEmailField.closest('tr').find('th').removeClass('required-field');
627 }
628 });
629
630 passwordField.trigger('input');
631
632 var hideSettingsCheckbox = $('#checkbox_hide_settings');
633 var originalCheckboxState = hideSettingsCheckbox.is(':checked');
634
635 hideSettingsCheckbox.on('click', function(e) {
636 if (!originalCheckboxState && !hasPassword()) {
637 e.preventDefault();
638 e.stopImmediatePropagation();
639
640 showModal(
641 '🔐',
642 'Password Required',
643 'You must set a <strong>Settings Password</strong> before enabling "Hide Settings".<br><br>Please scroll up to the <strong>Branding</strong> section and set a password first.'
644 );
645
646 setTimeout(function() {
647 $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]').focus();
648 }, 300);
649
650 return false;
651 }
652
653 setTimeout(function() {
654 originalCheckboxState = hideSettingsCheckbox.is(':checked');
655 }, 0);
656 });
657
658 var storedPassword = '<?php echo esc_js($whitelabel_settings['settings_password'] ?? ''); ?>';
659
660 passwordField.on('keydown', function(e) {
661 var currentValue = $(this).val();
662
663 if (hideSettingsCheckbox.is(':checked') &&
664 (currentValue.length <= 1 || !currentValue) &&
665 (e.keyCode === 8 || e.keyCode === 46)) {
666
667 e.preventDefault();
668 e.stopImmediatePropagation();
669
670 if (!isShowingModal) {
671 showModal(
672 '🚫',
673 'Cannot Remove Password',
674 'You cannot remove the <strong>White Label Settings Password</strong> while "Hide Settings" is enabled.<br><br>Please uncheck <strong>"Hide Settings"</strong> first if you want to remove the password.'
675 );
676 }
677
678 return false;
679 }
680 });
681
682 $(document).on('metasync_settings_saved', function() {
683 });
684
685 $(document).on('keydown', function(e) {
686 if (e.key === 'Escape') {
687 $('#metasync-custom-modal').fadeOut(200);
688 }
689 });
690
691 $('#metasync-export-whitelabel-btn').on('click', function(e) {
692 e.preventDefault();
693
694 var $button = $(this);
695 var originalText = $button.html();
696
697 $button.prop('disabled', true).html('⏳ Exporting...');
698
699 var form = $('<form>', {
700 'method': 'POST',
701 'action': '<?php echo esc_js(admin_url('admin-post.php')); ?>',
702 'target': '_blank'
703 });
704
705 form.append($('<input>', {
706 'type': 'hidden',
707 'name': 'action',
708 'value': 'metasync_export_whitelabel_settings'
709 }));
710
711 form.append($('<input>', {
712 'type': 'hidden',
713 'name': '_wpnonce',
714 'value': '<?php echo wp_create_nonce('metasync_export_whitelabel'); ?>'
715 }));
716
717 $('body').append(form);
718 form.submit();
719
720 setTimeout(function() {
721 form.remove();
722 $button.prop('disabled', false).html(originalText);
723 }, 2000);
724 });
725 });
726 </script>
727 <?php
728 } elseif ($active_tab == 'advanced') {
729 if (!Metasync_Access_Control::user_can_access('hide_advanced')) {
730 echo '<div class="dashboard-card" style="background: var(--dashboard-card-bg); padding: 25px; border-radius: 8px; text-align: center;">';
731 echo '<h2 style="color: var(--dashboard-text-primary);">🔒 Access Denied</h2>';
732 echo '<p style="color: var(--dashboard-text-secondary);">You do not have permission to access this page.</p>';
733 echo '</div>';
734 } else {
735 ?>
736 <div class="dashboard-card">
737 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
738 <div style="flex: 1;">
739 <h2 style="margin: 0;">🧰 Advanced Settings</h2>
740 <p style="color: var(--dashboard-text-secondary); margin: 5px 0 0 0;">Technical utilities for troubleshooting and connectivity checks.</p>
741 </div>
742 <?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?>
743 <div>
744 <?php Metasync_Compatibility_Checker::instance()->render_lock_button('advanced'); ?>
745 </div>
746 <?php endif; ?>
747 </div>
748
749 <?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?>
750 <div style="background: rgba(212, 237, 218, 0.2); border: 1px solid rgba(195, 230, 203, 0.5); color: var(--dashboard-success, #10b981); padding: 12px; border-radius: 8px; margin-bottom: 20px;">
751 <strong>�
752 Access Granted:</strong> You have successfully authenticated and can now modify settings.
753 </div>
754 <?php endif; ?>
755
756 <?php
757 if (isset($_GET['debug_mode_enabled']) && $_GET['debug_mode_enabled'] == '1'):
758 $indefinite = isset($_GET['indefinite']) && $_GET['indefinite'] == '1';
759 ?>
760 <div class="notice notice-success inline" style="margin-bottom: 20px;">
761 <p>
762 <strong>�
763 Success!</strong> Debug mode has been enabled<?php echo $indefinite ? ' indefinitely' : ' for 24 hours'; ?>.
764 </p>
765 </div>
766 <?php endif; ?>
767
768 <?php if (isset($_GET['debug_mode_disabled']) && $_GET['debug_mode_disabled'] == '1'): ?>
769 <div class="notice notice-info inline" style="margin-bottom: 20px;">
770 <p>
771 <strong>ℹ️ Debug Mode Disabled:</strong> Debug mode has been successfully disabled.
772 </p>
773 </div>
774 <?php endif; ?>
775
776 <?php if (isset($_GET['debug_mode_extended']) && $_GET['debug_mode_extended'] == '1'): ?>
777 <div class="notice notice-success inline" style="margin-bottom: 20px;">
778 <p>
779 <strong>�
780 Extended!</strong> Debug mode has been extended for another 24 hours.
781 </p>
782 </div>
783 <?php endif; ?>
784
785 <?php if (isset($_GET['debug_error']) && $_GET['debug_error'] == '1'): ?>
786 <div class="notice notice-error inline" style="margin-bottom: 20px;">
787 <p>
788 <strong> Error:</strong> Unable to perform the debug mode operation. Please try again.
789 </p>
790 </div>
791 <?php endif; ?>
792
793 <?php
794 $this->admin->render_advanced_accordion();
795 ?>
796 </div>
797
798 <?php
799 }
800 }
801 ?>
802
803 <!-- Save button removed - using floating notification system instead -->
804
805 </form>
806
807 <!-- Lock Section Button Handler (runs on all tabs) -->
808 <script>
809 jQuery(document).ready(function($) {
810 var ajaxUrl = (typeof window.ajaxurl !== 'undefined' && window.ajaxurl)
811 ? window.ajaxurl
812 : '<?php echo esc_js(admin_url('admin-ajax.php')); ?>';
813 $('.metasync-lock-btn').on('click', function(e) {
814 e.preventDefault();
815 e.stopPropagation();
816
817 var currentTab = $(this).data('tab');
818
819 var logoutForm = $('<form>', {
820 'method': 'post',
821 'action': ''
822 });
823
824 logoutForm.append('<?php echo wp_nonce_field("whitelabel_logout_nonce", "whitelabel_logout_nonce", true, false); ?>');
825
826 logoutForm.append($('<input>', {
827 'type': 'hidden',
828 'name': 'whitelabel_logout',
829 'value': '1'
830 }));
831
832 $('body').append(logoutForm);
833 logoutForm.submit();
834
835 return false;
836 });
837 });
838 </script>
839
840 <!-- Host Blocking-generated JavaScript -->
841 <script>
842 jQuery(document).ready(function($) {
843 var ajaxUrl = (typeof window.ajaxurl !== 'undefined' && window.ajaxurl)
844 ? window.ajaxurl
845 : '<?php echo esc_js(admin_url('admin-ajax.php')); ?>';
846
847 $(document).on('click', '#test-get-request', function(e) {
848 e.preventDefault();
849 e.stopPropagation();
850 runHostTest('GET');
851 return false;
852 });
853
854 $(document).on('click', '#test-post-request', function(e) {
855 e.preventDefault();
856 e.stopPropagation();
857 runHostTest('POST');
858 return false;
859 });
860
861 $(document).on('click', '#test-both-requests', function(e) {
862 e.preventDefault();
863 e.stopPropagation();
864 runHostTest('BOTH');
865 return false;
866 });
867
868 function runHostTest(method) {
869 var buttonId = (method === 'BOTH' ? 'test-both-requests' : 'test-' + method.toLowerCase() + '-request');
870 var $button = $('#' + buttonId);
871
872 if ($button.length === 0) {
873 alert('Error: Test button not found. Please refresh the page.');
874 return;
875 }
876
877 var originalText = $button.text();
878
879 $button.prop('disabled', true);
880 $button.text('🔄 Testing...');
881
882 var $resultsDiv = $('#host-test-results');
883 var $resultsContent = $('#test-results-content');
884 $resultsDiv.show();
885 $resultsContent.html('<div class="notice notice-info"><p>Running ' + (method === 'BOTH' ? 'GET and POST' : method) + ' test(s)...</p></div>');
886
887 var testsToRun = (method === 'BOTH') ? ['GET', 'POST'] : [method];
888 var completedTests = 0;
889 var allResults = [];
890
891 testsToRun.forEach(function(testMethod) {
892 var action = 'metasync_test_host_blocking_' + testMethod.toLowerCase();
893
894 $.ajax({
895 url: ajaxUrl,
896 type: 'POST',
897 dataType: 'json',
898 data: { action: action },
899 timeout: 35000,
900 success: function(response, textStatus, xhr) {
901 try {
902 if (response && response.success && response.data) {
903 allResults.push(response.data);
904 } else {
905 allResults.push({
906 method: testMethod,
907 status: 'error',
908 error: (response && response.data) ? response.data : 'Unexpected response',
909 blocked: true,
910 details: 'Received non-success response from server.'
911 });
912 }
913 } catch (e) {
914 allResults.push({
915 method: testMethod,
916 status: 'error',
917 error: 'Response parse error: ' + (e && e.message ? e.message : e),
918 blocked: true
919 });
920 }
921 finalizeOne();
922 },
923 error: function(xhr, status, error) {
924 var payload = (xhr && xhr.responseText) ? xhr.responseText.substring(0, 500) : '';
925 allResults.push({
926 method: testMethod,
927 status: 'error',
928 error: 'AJAX failed: ' + error + (payload ? '' + payload : ''),
929 blocked: true,
930 details: 'Request did not complete successfully. Status: ' + status
931 });
932 finalizeOne();
933 }
934 });
935 });
936
937 function finalizeOne() {
938 completedTests++;
939 if (completedTests === testsToRun.length) {
940 displayResults(allResults);
941 resetButtons();
942 var $container = $('#host-test-results');
943 if ($container && $container[0] && $container[0].scrollIntoView) {
944 $container[0].scrollIntoView({ behavior: 'smooth', block: 'start' });
945 }
946 }
947 }
948 }
949
950 function resetButtons() {
951 $('#test-get-request, #test-post-request, #test-both-requests').prop('disabled', false);
952 $('#test-get-request').text('🔍 Test GET Request');
953 $('#test-post-request').text('📤 Test POST Request');
954 $('#test-both-requests').text('🔄 Test Both Requests');
955 }
956
957 function displayResults(results) {
958 var html = '';
959
960 results.forEach(function(result) {
961 var statusClass = result.status === 'success' ? 'success' : 'error';
962 var statusIcon = result.status === 'success' ? '�
963 ' : '';
964 var blockedStatus = result.blocked ? 'BLOCKED' : 'ALLOWED';
965 var blockedClass = result.blocked ? 'blocked' : 'allowed';
966
967 html += '<div class="test-result-item ' + statusClass + '">';
968 html += '<div class="test-result-header">';
969 html += '<h4>' + statusIcon + ' ' + result.method + ' Request - ' + blockedStatus + '</h4>';
970 html += '<span class="test-status ' + blockedClass + '">' + blockedStatus + '</span>';
971 html += '</div>';
972
973 html += '<div class="test-result-details">';
974 html += '<p><strong>Response Time:</strong> ' + result.response_time + '</p>';
975 html += '<p><strong>Status:</strong> ' + result.status + '</p>';
976
977 if (result.status_code) {
978 html += '<p><strong>HTTP Status Code:</strong> <span class="status-code">' + result.status_code + '</span></p>';
979 }
980
981 if (result.error) {
982 html += '<p><strong>Error:</strong> <span class="error-message">' + result.error + '</span></p>';
983 }
984
985 if (result.body) {
986 html += '<p><strong>Response Body:</strong></p>';
987 html += '<pre class="response-body">' + escapeHtml(result.body) + '</pre>';
988 }
989
990 if (result.headers && Object.keys(result.headers).length > 0) {
991 html += '<p><strong>Response Headers:</strong></p>';
992 html += '<pre class="response-headers">';
993 Object.keys(result.headers).forEach(function(key) {
994 html += key + ': ' + result.headers[key] + '\n';
995 });
996 html += '</pre>';
997 }
998
999 if (result.sent_data) {
1000 html += '<p><strong>Sent Data:</strong></p>';
1001 html += '<pre class="sent-data">' + escapeHtml(JSON.stringify(result.sent_data, null, 2)) + '</pre>';
1002 }
1003
1004 if (result.parsed_response) {
1005 html += '<p><strong>Parsed Response:</strong></p>';
1006 html += '<pre class="parsed-response">' + escapeHtml(JSON.stringify(result.parsed_response, null, 2)) + '</pre>';
1007 }
1008
1009 html += '<p><strong>Details:</strong> ' + result.details + '</p>';
1010 html += '</div>';
1011 html += '</div>';
1012 });
1013
1014 $('#test-results-content').html(html);
1015 $('#host-test-results').show();
1016 }
1017
1018 function escapeHtml(text) {
1019 var map = {
1020 '&': '&amp;',
1021 '<': '&lt;',
1022 '>': '&gt;',
1023 '"': '&quot;',
1024 '\'': '&#039;'
1025 };
1026 return text.replace(/[&<>"']/g, function(m) { return map[m]; });
1027 }
1028 });
1029 </script>
1030
1031 <!-- Host Blocking Test CSS -->
1032 <style>
1033 .test-result-item {
1034 background: #fff;
1035 border: 1px solid #ddd;
1036 border-radius: 8px;
1037 margin-bottom: 20px;
1038 overflow: hidden;
1039 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1040 }
1041
1042 .test-result-item.success {
1043 border-left: 4px solid #28a745;
1044 }
1045
1046 .test-result-item.error {
1047 border-left: 4px solid #dc3545;
1048 }
1049
1050 .test-result-header {
1051 background: #f8f9fa;
1052 padding: 15px 20px;
1053 border-bottom: 1px solid #dee2e6;
1054 display: flex;
1055 justify-content: space-between;
1056 align-items: center;
1057 }
1058
1059 .test-result-header h4 {
1060 margin: 0;
1061 color: #495057;
1062 font-size: 16px;
1063 }
1064
1065 .test-status {
1066 padding: 4px 12px;
1067 border-radius: 20px;
1068 font-size: 12px;
1069 font-weight: bold;
1070 text-transform: uppercase;
1071 }
1072
1073 .test-status.allowed {
1074 background: #d4edda;
1075 color: #155724;
1076 }
1077
1078 .test-status.blocked {
1079 background: #f8d7da;
1080 color: #721c24;
1081 }
1082
1083 .test-result-details {
1084 padding: 20px;
1085 }
1086
1087 .test-result-details p {
1088 margin: 8px 0;
1089 color: #495057;
1090 }
1091
1092 .test-result-details code {
1093 background: #f8f9fa;
1094 padding: 2px 6px;
1095 border-radius: 3px;
1096 font-family: 'Courier New', monospace;
1097 color: #e83e8c;
1098 }
1099
1100 .status-code {
1101 font-weight: bold;
1102 color: #007cba;
1103 }
1104
1105 .error-message {
1106 color: #dc3545;
1107 font-weight: bold;
1108 }
1109
1110 .response-body, .response-headers, .sent-data, .parsed-response {
1111 background: #f8f9fa;
1112 border: 1px solid #dee2e6;
1113 border-radius: 4px;
1114 padding: 12px;
1115 margin: 8px 0;
1116 font-family: 'Courier New', monospace;
1117 font-size: 12px;
1118 line-height: 1.4;
1119 max-height: 200px;
1120 overflow-y: auto;
1121 white-space: pre-wrap;
1122 word-break: break-all;
1123 }
1124
1125 .response-body {
1126 color: #495057;
1127 }
1128
1129 .response-headers {
1130 color: #6c757d;
1131 }
1132
1133 .sent-data {
1134 color: #007cba;
1135 }
1136
1137 .parsed-response {
1138 color: #28a745;
1139 background: #f8fff9;
1140 border-color: #c3e6cb;
1141 }
1142
1143 #host-test-results {
1144 margin-top: 20px;
1145 }
1146
1147 #host-test-results h3 {
1148 color: #495057;
1149 margin-bottom: 15px;
1150 padding-bottom: 10px;
1151 border-bottom: 2px solid #dee2e6;
1152 }
1153
1154 .button:disabled {
1155 opacity: 0.6;
1156 cursor: not-allowed;
1157 }
1158 </style>
1159
1160 <?php if ($active_tab === 'advanced'): ?>
1161 <?php if (isset($_GET['settings_cleared']) && $_GET['settings_cleared'] == '1'): ?>
1162 <div class="notice notice-success is-dismissible">
1163 <p><strong>�
1164 Success!</strong> All plugin settings have been cleared successfully and a new Plugin Auth Token has been generated. Please reconfigure the plugin as needed.</p>
1165 </div>
1166 <?php endif; ?>
1167
1168 <?php if (isset($_GET['clear_settings_error']) && $_GET['clear_settings_error'] == '1'): ?>
1169 <div class="notice notice-error is-dismissible">
1170 <p><strong>❌ Error!</strong> Failed to clear settings due to a security check failure. Please try again.</p>
1171 </div>
1172 <?php endif; ?>
1173
1174 <?php if (isset($_GET['log_cleared']) && $_GET['log_cleared'] == '1'): ?>
1175 <div class="notice notice-success is-dismissible">
1176 <p><strong>�
1177 Success!</strong> Error logs have been cleared successfully.</p>
1178 </div>
1179 <?php endif; ?>
1180
1181 <?php if (isset($_GET['clear_error']) && $_GET['clear_error'] == '1'): ?>
1182 <div class="notice notice-error is-dismissible">
1183 <p><strong>❌ Error!</strong> Failed to clear error logs due to a security check failure. Please try again.</p>
1184 </div>
1185 <?php endif; ?>
1186
1187 <?php if (isset($_GET['access_roles_saved']) && $_GET['access_roles_saved'] == '1'): ?>
1188 <div class="notice notice-success is-dismissible">
1189 <p><strong>�
1190 Success!</strong> Plugin access roles have been saved successfully.</p>
1191 </div>
1192 <?php endif; ?>
1193
1194 <?php if (isset($_GET['access_roles_error']) && $_GET['access_roles_error'] == '1'): ?>
1195 <div class="notice notice-error is-dismissible">
1196 <p><strong>❌ Error!</strong> Failed to save plugin access roles. <?php echo isset($_GET['message']) ? esc_html(urldecode($_GET['message'])) : 'Please try again.'; ?></p>
1197 </div>
1198 <?php endif; ?>
1199
1200 <?php endif; ?>
1201 </div>
1202 <?php
1203 }
1204
1205 // ------------------------------------------------------------------
1206 // Simple page renderers
1207 // ------------------------------------------------------------------
1208
1209 public function create_admin_dashboard_page()
1210 {
1211 ?>
1212 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1213
1214 <?php $this->admin->render_plugin_header('Dashboard'); ?>
1215
1216 <?php $this->admin->render_navigation_menu('dashboard'); ?>
1217
1218 <div class="dashboard-card">
1219 <h2>📊 <?php echo esc_html($this->admin->get_effective_menu_title()); ?> Dashboard</h2>
1220 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Access your <?php echo esc_html(Metasync::get_effective_plugin_name()); ?> dashboard to view analytics, manage SEO settings, and monitor your site performance.</p>
1221 <?php
1222 if (!isset(Metasync::get_option('general')['linkgraph_token']) || Metasync::get_option('general')['linkgraph_token'] == '') {
1223 echo '<p style="color: #d54e21; margin-bottom: 15px;">⚠️ Authentication required: Please authenticate with your ' . esc_html(Metasync::get_effective_plugin_name()) . ' account and save your auth token in general settings.</p>';
1224 echo '<a href="' . admin_url('admin.php?page=' . Metasync_Admin::$page_slug) . '" class="button button-secondary">Go to Settings</a>';
1225 } else {
1226 echo '<a href="' . esc_url($this->admin->get_dashboard_url()) . '" target="_blank" class="button button-primary">🌐 Open Dashboard</a>';
1227 }
1228 ?>
1229 </div>
1230 </div>
1231 <?php
1232 }
1233
1234 public function create_admin_robots_txt_page()
1235 {
1236 ?>
1237 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1238
1239 <?php $this->admin->render_plugin_header('Robots.txt'); ?>
1240
1241 <?php $this->admin->render_navigation_menu('robots_txt'); ?>
1242
1243 <?php
1244 require_once plugin_dir_path(dirname(__FILE__)) . 'robots-txt/class-metasync-robots-txt.php';
1245
1246 $robots_txt = Metasync_Robots_Txt::get_instance();
1247
1248 if (isset($_POST['metasync_robots_txt_nonce'])) {
1249 check_admin_referer('metasync_save_robots_txt', 'metasync_robots_txt_nonce');
1250
1251 if (isset($_POST['robots_content'])) {
1252 $content = wp_unslash($_POST['robots_content']);
1253
1254 $validation = $robots_txt->validate_content($content);
1255
1256 if ($validation['valid']) {
1257 $result = $robots_txt->write_robots_file($content);
1258
1259 if (is_wp_error($result)) {
1260 echo '<div class="notice notice-error"><p>' . esc_html($result->get_error_message()) . '</p></div>';
1261 } else {
1262 echo '<div class="notice notice-success"><p>' . esc_html__('robots.txt file saved successfully!', 'metasync') . '</p></div>';
1263
1264 if (!empty($validation['warnings'])) {
1265 foreach ($validation['warnings'] as $warning) {
1266 echo '<div class="notice notice-warning"><p>' . esc_html($warning) . '</p></div>';
1267 }
1268 }
1269 }
1270 } else {
1271 echo '<div class="notice notice-error"><p>' . esc_html__('Validation failed. Please fix the errors below:', 'metasync') . '</p>';
1272 foreach ($validation['errors'] as $error) {
1273 echo '<p>- ' . esc_html($error) . '</p>';
1274 }
1275 echo '</div>';
1276 }
1277 }
1278 }
1279
1280 $current_content = $robots_txt->read_robots_file();
1281 if (is_wp_error($current_content)) {
1282 echo '<div class="notice notice-error"><p>' . esc_html($current_content->get_error_message()) . '</p></div>';
1283 $current_content = '';
1284 }
1285
1286 $backups = $robots_txt->get_backup_history(10);
1287
1288 $file_exists = $robots_txt->file_exists();
1289 $is_writable = $robots_txt->is_writable();
1290
1291 $robots_txt->render($this->admin, $current_content, $backups, $file_exists, $is_writable);
1292 ?>
1293 </div>
1294 <?php
1295 }
1296
1297 public function create_admin_report_issue_page()
1298 {
1299 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-report-issue.php';
1300 }
1301
1302 public function create_admin_xml_sitemap_page()
1303 {
1304 require_once plugin_dir_path(dirname(__FILE__)) . 'sitemap/class-metasync-sitemap-generator.php';
1305
1306 $sitemap_generator = new Metasync_Sitemap_Generator();
1307
1308 if (isset($_POST['metasync_sitemap_nonce'])) {
1309 check_admin_referer('metasync_sitemap_action', 'metasync_sitemap_nonce');
1310
1311 if (isset($_POST['generate_sitemap'])) {
1312 $disabled_plugins = $sitemap_generator->disable_other_sitemap_generators();
1313
1314 $result = $sitemap_generator->generate_sitemap();
1315
1316 if (is_wp_error($result)) {
1317 echo '<div class="notice notice-error"><p>' . esc_html($result->get_error_message()) . '</p></div>';
1318 } else {
1319 $message = esc_html__('Sitemap generated successfully!', 'metasync');
1320 if ($disabled_plugins) {
1321 $message .= ' ' . esc_html__('Conflicting sitemap generators have been automatically disabled.', 'metasync');
1322 }
1323
1324 $robots_result = get_transient('metasync_sitemap_robots_updated');
1325 if ($robots_result && $robots_result['success']) {
1326 if ($robots_result['action'] === 'added') {
1327 $message .= ' ' . esc_html__('Sitemap URL has been added to robots.txt.', 'metasync');
1328 } elseif ($robots_result['action'] === 'updated') {
1329 $message .= ' ' . esc_html__('Sitemap URL has been updated in robots.txt.', 'metasync');
1330 } elseif ($robots_result['action'] === 'created') {
1331 $message .= ' ' . esc_html__('robots.txt file has been created with sitemap URL.', 'metasync');
1332 }
1333 delete_transient('metasync_sitemap_robots_updated');
1334 }
1335
1336 echo '<div class="notice notice-success"><p>' . esc_html($message) . '</p></div>';
1337 }
1338 } elseif (isset($_POST['enable_auto_update'])) {
1339 update_option('metasync_sitemap_auto_update', true);
1340 $sitemap_generator->setup_auto_update_hooks();
1341 echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update enabled!', 'metasync') . '</p></div>';
1342 } elseif (isset($_POST['disable_auto_update'])) {
1343 update_option('metasync_sitemap_auto_update', false);
1344 echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update disabled!', 'metasync') . '</p></div>';
1345 } elseif (isset($_POST['delete_sitemap'])) {
1346 $deleted = $sitemap_generator->delete_sitemap();
1347 if ($deleted) {
1348 update_option('metasync_sitemap_auto_update', false);
1349 echo '<div class="notice notice-success"><p>' . esc_html__('Sitemap deleted successfully!', 'metasync') . '</p></div>';
1350 } else {
1351 echo '<div class="notice notice-error"><p>' . esc_html__('Failed to delete sitemap. The file may not exist or is not writable.', 'metasync') . '</p></div>';
1352 }
1353 } elseif (isset($_POST['enable_other_sitemaps'])) {
1354 $enabled_plugins = $sitemap_generator->enable_other_sitemap_generators();
1355 if ($enabled_plugins) {
1356 echo '<div class="notice notice-success"><p>' . esc_html__('Other sitemap plugins have been re-enabled successfully!', 'metasync') . '</p></div>';
1357 } else {
1358 echo '<div class="notice notice-info"><p>' . esc_html__('No sitemap plugins were found to re-enable.', 'metasync') . '</p></div>';
1359 }
1360 }
1361 }
1362
1363 $sitemap_exists = $sitemap_generator->sitemap_exists();
1364 $sitemap_url = $sitemap_generator->get_sitemap_url();
1365 $url_count = $sitemap_generator->count_urls();
1366 $last_generated = $sitemap_generator->get_last_generated_time();
1367 $auto_update_enabled = get_option('metasync_sitemap_auto_update', false);
1368 $active_sitemap_plugins = $sitemap_generator->check_active_sitemap_plugins();
1369
1370 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-xml-sitemap.php';
1371 }
1372
1373 public function create_admin_custom_pages_page()
1374 {
1375 ?>
1376 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1377
1378 <?php $this->admin->render_plugin_header('Custom HTML Pages'); ?>
1379
1380 <?php $this->admin->render_navigation_menu('custom_pages'); ?>
1381
1382 <div class="metasync-page-content">
1383 <?php
1384 require_once plugin_dir_path(dirname(__FILE__)) . 'custom-pages/class-metasync-custom-pages-admin.php';
1385 Metasync_Custom_Pages_Admin::render_admin_page();
1386 ?>
1387 </div>
1388
1389 </div>
1390 <?php
1391 }
1392
1393 public function create_admin_404_monitor_page()
1394 {
1395 require_once plugin_dir_path(dirname(__FILE__)) . '404-monitor/class-metasync-404-monitor-database.php';
1396 require_once plugin_dir_path(dirname(__FILE__)) . '404-monitor/class-metasync-404-monitor.php';
1397
1398 $db_404 = new Metasync_Error_Monitor_Database();
1399 $ErrorMonitor = new Metasync_Error_Monitor($db_404);
1400
1401 $ErrorMonitor->create_admin_plugin_interface();
1402 }
1403
1404 public function create_admin_search_engine_verification_page()
1405 {
1406 $page_slug = Metasync_Admin::$page_slug . '_searchengines-verification';
1407 ?>
1408 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1409
1410 <?php $this->admin->render_plugin_header('Site Verification'); ?>
1411
1412 <?php $this->admin->render_navigation_menu('site-verification'); ?>
1413
1414 <form method="post" action="options.php">
1415 <div class="dashboard-card">
1416 <h2>🔍 Search Engine Verification</h2>
1417 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Verify your site ownership with major search engines to access their tools and analytics.</p>
1418 <?php
1419 settings_fields(Metasync_Admin::option_group);
1420 do_settings_sections($page_slug);
1421 ?>
1422 </div>
1423
1424 <!-- Save button removed - using floating notification system instead -->
1425 </form>
1426 </div>
1427 <?php
1428 }
1429
1430 public function create_admin_local_business_page()
1431 {
1432 $page_slug = Metasync_Admin::$page_slug . '_local-seo';
1433 ?>
1434 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1435
1436 <?php $this->admin->render_plugin_header('Local Business'); ?>
1437
1438 <?php $this->admin->render_navigation_menu('local-business'); ?>
1439
1440 <form method="post" action="options.php">
1441 <div class="dashboard-card">
1442 <h2>🏢 Local Business Configuration</h2>
1443 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure your local business information for better local search engine optimization.</p>
1444 <?php
1445 settings_fields(Metasync_Admin::option_group);
1446 do_settings_sections($page_slug);
1447 ?>
1448 </div>
1449
1450 <!-- Save button removed - using floating notification system instead -->
1451 </form>
1452 </div>
1453 <?php
1454 }
1455
1456 public function create_admin_code_snippets_page()
1457 {
1458 $page_slug = Metasync_Admin::$page_slug . '_code-snippets';
1459 ?>
1460 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1461
1462 <?php $this->admin->render_plugin_header('Code Snippets'); ?>
1463
1464 <?php $this->admin->render_navigation_menu('code-snippets'); ?>
1465
1466 <form method="post" action="options.php">
1467 <div class="dashboard-card">
1468 <h2>📝 Code Snippets Management</h2>
1469 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Add custom code snippets to enhance your site functionality and tracking capabilities.</p>
1470 <?php
1471 settings_fields(Metasync_Admin::option_group);
1472 do_settings_sections($page_slug);
1473 ?>
1474 </div>
1475
1476 <!-- Save button removed - using floating notification system instead -->
1477 </form>
1478 </div>
1479 <?php
1480 }
1481
1482 public function create_admin_google_instant_index_page()
1483 {
1484 ?>
1485 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1486
1487 <?php $this->admin->render_plugin_header('Instant Indexing'); ?>
1488
1489 <?php $this->admin->render_navigation_menu('instant_index'); ?>
1490
1491 <?php
1492 google_index_direct()->show_google_instant_indexing_settings();
1493 ?>
1494 </div>
1495 <?php
1496 }
1497
1498 public function create_admin_google_console_page()
1499 {
1500 ?>
1501 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1502
1503 <?php $this->admin->render_plugin_header('Google Console'); ?>
1504
1505 <?php $this->admin->render_navigation_menu('google_console'); ?>
1506
1507 <?php
1508 google_index_direct()->show_google_instant_indexing_console();
1509 ?>
1510 </div>
1511 <?php
1512 }
1513
1514 public function create_admin_bing_console_page()
1515 {
1516 ?>
1517 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1518
1519 <?php $this->admin->render_plugin_header('Bing Console'); ?>
1520
1521 <?php $this->admin->render_navigation_menu('bing_console'); ?>
1522
1523 <?php
1524 require_once plugin_dir_path(dirname(__FILE__)) . 'bing-index/class-metasync-bing-instant-index.php';
1525 $bing_instant_index = new Metasync_Bing_Instant_Index();
1526 $bing_instant_index->show_bing_instant_indexing_console();
1527 ?>
1528 </div>
1529 <?php
1530 }
1531
1532 public function create_admin_optimal_settings_page()
1533 {
1534 ?>
1535 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1536
1537 <?php $this->admin->render_plugin_header('Settings'); ?>
1538
1539 <?php $this->admin->render_navigation_menu('optimal-settings'); ?>
1540
1541 <div class="dashboard-card">
1542 <h2>🚀 Site Compatibility Status</h2>
1543 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Check your site's compatibility with optimal <?php echo esc_html(Metasync::get_effective_plugin_name()); ?> settings.</p>
1544 <?php
1545 $optimal_settings = new Metasync_Optimal_Settings();
1546 $optimal_settings->site_compatible_status_view();
1547 ?>
1548 </div>
1549
1550 <div class="dashboard-card">
1551 <h2>⚙️ Optimization Settings</h2>
1552 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure optimization settings for best performance.</p>
1553 <?php
1554 $this->optimization_settings_options();
1555 ?>
1556 </div>
1557 </div>
1558 <?php
1559 }
1560
1561 public function create_admin_global_settings_page()
1562 {
1563 $page_slug = Metasync_Admin::$page_slug . '_common-settings';
1564 ?>
1565 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1566
1567 <?php $this->admin->render_plugin_header('Settings'); ?>
1568
1569 <?php $this->admin->render_navigation_menu('global-settings'); ?>
1570
1571 <form method="post" action="options.php">
1572 <div class="dashboard-card">
1573 <h2>🌐 Global Configuration</h2>
1574 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure global settings that apply across your entire site.</p>
1575 <?php
1576 settings_fields(Metasync_Admin::option_group);
1577 do_settings_sections($page_slug);
1578 ?>
1579 </div>
1580
1581 <!-- Save button removed - using floating notification system instead -->
1582 </form>
1583 </div>
1584 <?php
1585 }
1586
1587 public function create_admin_common_meta_settings_page()
1588 {
1589 $page_slug = Metasync_Admin::$page_slug . '_common-meta-settings';
1590 ?>
1591 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1592
1593 <?php $this->admin->render_plugin_header('Settings'); ?>
1594
1595 <?php $this->admin->render_navigation_menu('common-meta-settings'); ?>
1596
1597 <form method="post" action="options.php">
1598 <div class="dashboard-card">
1599 <h2>🏷️ Meta Configuration</h2>
1600 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure common meta tags and SEO settings for your site.</p>
1601 <?php
1602 settings_fields(Metasync_Admin::option_group);
1603 do_settings_sections($page_slug);
1604 ?>
1605 </div>
1606
1607 <!-- Save button removed - using floating notification system instead -->
1608 </form>
1609 </div>
1610 <?php
1611 }
1612
1613 public function create_admin_social_meta_page()
1614 {
1615 $page_slug = Metasync_Admin::$page_slug . '_social-meta';
1616 ?>
1617 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1618
1619 <?php $this->admin->render_plugin_header('Settings'); ?>
1620
1621 <?php $this->admin->render_navigation_menu('social-meta'); ?>
1622
1623 <form method="post" action="options.php">
1624 <div class="dashboard-card">
1625 <h2>📲 Social Meta Tags</h2>
1626 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how your content appears when shared on social media platforms.</p>
1627 <?php
1628 settings_fields(Metasync_Admin::option_group);
1629 do_settings_sections($page_slug);
1630 ?>
1631 </div>
1632
1633 <!-- Save button removed - using floating notification system instead -->
1634 </form>
1635 </div>
1636 <?php
1637 }
1638
1639 public function create_admin_seo_controls_page()
1640 {
1641 $page_slug = Metasync_Admin::$page_slug . '_seo-controls';
1642 ?>
1643 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1644
1645 <?php $this->admin->render_plugin_header('Indexation Control'); ?>
1646
1647 <?php $this->admin->render_navigation_menu('seo_controls'); ?>
1648
1649 <!-- Status Messages Container -->
1650 <div id="seo-controls-messages"></div>
1651
1652 <form method="post" action="options.php" id="metaSyncSeoControlsForm">
1653 <div class="dashboard-card">
1654 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
1655 <h2 style="margin: 0;">🚫 Indexation Control</h2>
1656 <a href="<?php echo esc_url(admin_url('admin.php?page=metasync-import-external&tab=indexation')); ?>" class="button button-secondary">
1657 <span>📥</span> Import from SEO Plugins
1658 </a>
1659 </div>
1660 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Control which archive pages should be disallowed from search engine indexing to improve your site's SEO health and conserve crawl budget.</p>
1661 <?php
1662 settings_fields(Metasync_Admin::option_group);
1663 do_settings_sections($page_slug);
1664
1665 wp_nonce_field('meta_sync_seo_controls_nonce', 'meta_sync_seo_controls_nonce');
1666 ?>
1667 </div>
1668
1669 <!-- Save button removed - using floating notification system instead -->
1670 </form>
1671 </div>
1672 <?php
1673 }
1674
1675 public function optimization_settings_options()
1676 {
1677 $page_slug = Metasync_Admin::$page_slug . '_optimal-settings';
1678 $site_info_slug = Metasync_Admin::$page_slug . '_site-info-settings';
1679
1680 printf('<form method="post" action="options.php">');
1681 settings_fields(Metasync_Admin::option_group);
1682 do_settings_sections($page_slug);
1683 do_settings_sections($site_info_slug);
1684 submit_button();
1685 printf('</form>');
1686 }
1687
1688 public function create_admin_error_logs_page()
1689 {
1690 ?>
1691 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1692
1693 <?php $this->admin->render_plugin_header('Error Logs'); ?>
1694
1695 <?php $this->admin->render_navigation_menu('error-log'); ?>
1696
1697 <div class="dashboard-card">
1698 <h2>⚠️ Error Logs Management</h2>
1699 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">View and manage system error logs to troubleshoot issues and monitor plugin performance.</p>
1700 <?php
1701 $error_logs = new Metasync_Error_Logs();
1702
1703 if ($error_logs->can_show_error_logs()) {
1704 $log_content = $error_logs->get_error_logs(50);
1705
1706 if (!empty(trim($log_content))) {
1707 $error_logs->show_copy_button();
1708 $error_logs->show_logs();
1709 $error_logs->show_info();
1710 } else {
1711 echo '<div class="dashboard-empty-state">';
1712 echo '<p style="color: var(--dashboard-text-secondary); font-style: italic; text-align: center; padding: 40px 20px;">�
1713 Log file is empty - no errors recorded.</p>';
1714 echo '</div>';
1715 }
1716 } else {
1717 $error_message = $error_logs->get_error_message();
1718 if (!empty($error_message)) {
1719 echo '<div class="dashboard-empty-state">';
1720 echo '<p style="color: #d54e21; font-weight: bold; text-align: center; padding: 20px; background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; margin: 20px 0;">';
1721 echo '⚠️ ' . esc_html($error_message);
1722 echo '</p>';
1723 echo '</div>';
1724 } else {
1725 echo '<div class="dashboard-empty-state">';
1726 echo '<p style="color: var(--dashboard-text-secondary); font-style: italic; text-align: center; padding: 40px 20px;">⚠️ Unable to access error log file. Please check permissions.</p>';
1727 echo '</div>';
1728 }
1729 }
1730 ?>
1731 </div>
1732 </div>
1733 <?php
1734 }
1735
1736 public function create_admin_sync_log_page()
1737 {
1738 $sync_db = new Metasync_Sync_History_Database();
1739
1740 if (wp_doing_ajax()) {
1741 $this->handle_sync_log_ajax();
1742 return;
1743 }
1744
1745 $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
1746 $per_page = 10;
1747 $offset = ($page - 1) * $per_page;
1748
1749 $filters = [
1750 'date_range' => isset($_GET['date_range']) ? sanitize_text_field($_GET['date_range']) : '',
1751 'status' => isset($_GET['status']) ? sanitize_text_field($_GET['status']) : '',
1752 ];
1753
1754 $date_range = $filters['date_range'];
1755 $wp_now_ts = current_time('timestamp');
1756 $date_from = '';
1757 $date_to = '';
1758
1759 if (!empty($date_range)) {
1760 $date_to = date('Y-m-d H:i:s', $wp_now_ts);
1761
1762 if ($date_range === 'today') {
1763 $start_ts = strtotime('today', $wp_now_ts);
1764 $date_from = date('Y-m-d H:i:s', $start_ts);
1765 } elseif ($date_range === 'yesterday') {
1766 $start_ts = strtotime('yesterday', $wp_now_ts);
1767 $end_ts = strtotime('today', $wp_now_ts) - 1;
1768 $date_from = date('Y-m-d H:i:s', $start_ts);
1769 $date_to = date('Y-m-d H:i:s', $end_ts);
1770 } elseif ($date_range === 'this_week') {
1771 $start_of_week = (int) get_option('start_of_week', 1);
1772 $day_of_week = (int) date('w', $wp_now_ts);
1773 $delta_days = ($day_of_week - $start_of_week + 7) % 7;
1774 $start_ts = strtotime('-' . $delta_days . ' days', strtotime('today', $wp_now_ts));
1775 $date_from = date('Y-m-d H:i:s', $start_ts);
1776 } elseif ($date_range === 'this_month') {
1777 $start_ts = strtotime(date('Y-m-01 00:00:00', $wp_now_ts));
1778 $date_from = date('Y-m-d H:i:s', $start_ts);
1779 } elseif ($date_range === 'all') {
1780 // no bounds
1781 }
1782 }
1783
1784 if (!empty($date_from)) {
1785 $filters['date_from'] = $date_from;
1786 }
1787 if (!empty($date_to)) {
1788 $filters['date_to'] = $date_to;
1789 }
1790
1791 $filters = array_filter($filters);
1792
1793 $sync_records = $sync_db->getAllRecords($per_page, $offset, $filters);
1794 $total_records = $sync_db->get_count($filters);
1795 $total_pages = ceil($total_records / $per_page);
1796
1797 $stats = $sync_db->get_statistics();
1798
1799 ?>
1800 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1801
1802 <?php $this->admin->render_plugin_header('Sync History'); ?>
1803
1804 <?php $this->admin->render_navigation_menu('sync_log'); ?>
1805
1806 <div class="dashboard-card">
1807 <div class="sync-log-header">
1808 <div class="sync-log-title-section">
1809 <h2>📋 Sync History</h2>
1810 <p style="color: var(--dashboard-text-secondary); margin-bottom: 0;">Recent content synchronizations from external tools.</p>
1811 </div>
1812
1813 <!-- Filters - Right aligned -->
1814 <div class="sync-log-filters">
1815 <form method="get" class="sync-filters-form" onchange="this.submit()">
1816 <input type="hidden" name="page" value="<?php echo esc_attr($_GET['page']); ?>">
1817
1818 <select name="date_range" class="sync-filter-select">
1819 <option value="all" <?php selected($filters['date_range'] ?? 'all', 'all'); ?>> All Time</option>
1820 <option value="today" <?php selected($filters['date_range'] ?? '', 'today'); ?>>Today</option>
1821 <option value="yesterday" <?php selected($filters['date_range'] ?? '', 'yesterday'); ?>>Yesterday</option>
1822 <option value="this_week" <?php selected($filters['date_range'] ?? '', 'this_week'); ?>>This week</option>
1823 <option value="this_month" <?php selected($filters['date_range'] ?? '', 'this_month'); ?>>This month</option>
1824 </select>
1825
1826 <select name="status" class="sync-filter-select">
1827 <option value="" <?php selected($filters['status'] ?? '', ''); ?>>Status Filter</option>
1828 <option value="published" <?php selected($filters['status'] ?? '', 'published'); ?>>Published</option>
1829 <option value="draft" <?php selected($filters['status'] ?? '', 'draft'); ?>>Draft</option>
1830 </select>
1831 </form>
1832 </div>
1833 </div>
1834
1835 <!-- Sync History List -->
1836 <div class="sync-log-list">
1837 <?php if (empty($sync_records)): ?>
1838 <div class="sync-log-empty">
1839 <div class="sync-log-empty-icon">📄</div>
1840 <h3>No sync records found</h3>
1841 <p>Sync records will appear here when content/pages receive new updates.</p>
1842 </div>
1843 <?php else: ?>
1844 <?php foreach ($sync_records as $record): ?>
1845 <div class="sync-log-item">
1846 <div class="sync-log-icon">
1847 <div class="sync-icon-circle">
1848 <span class="sync-icon">📄</span>
1849 </div>
1850 </div>
1851
1852 <div class="sync-log-content">
1853 <div class="sync-log-title"><?php echo esc_html($record->title); ?>
1854 <?php if (!empty($record->url)): ?>
1855 <a href="<?php echo esc_url($record->url); ?>" target="_blank" rel="noopener" title="Open URL" style="margin-left:8px; text-decoration:none;">🔗</a>
1856 <?php endif; ?>
1857 </div>
1858 <div class="sync-log-meta">
1859 <?php echo $this->time_elapsed_string($record->created_at); ?>
1860 </div>
1861 </div>
1862
1863 <div class="sync-log-status">
1864 <?php if ($record->status === 'published' || $record->status === 'publish'): ?>
1865 <span class="sync-status-badge sync-status-published">
1866 <span class="sync-status-icon">✓</span>
1867 Published
1868 </span>
1869 <?php else: ?>
1870 <span class="sync-status-badge sync-status-draft">
1871 <span class="sync-status-icon">i</span>
1872 Draft
1873 </span>
1874 <?php endif; ?>
1875 </div>
1876 </div>
1877 <?php endforeach; ?>
1878 <?php endif; ?>
1879 </div>
1880
1881 <!-- Pagination -->
1882 <?php if ($total_pages > 1): ?>
1883 <div class="sync-log-pagination">
1884 <div class="sync-log-pagination-info">
1885 Total records: <?php echo $total_records; ?> | Showing <?php echo $offset + 1; ?>-<?php echo min($offset + $per_page, $total_records); ?>
1886 </div>
1887
1888 <div class="sync-log-pagination-controls">
1889 <?php if ($page > 1): ?>
1890 <a href="?page=<?php echo esc_attr($_GET['page']); ?>&paged=<?php echo $page - 1; ?><?php echo $this->build_filter_query_string($filters); ?>" class="sync-pagination-btn">‹</a>
1891 <?php endif; ?>
1892
1893 <?php for ($i = max(1, $page - 2); $i <= min($total_pages, $page + 2); $i++): ?>
1894 <a href="?page=<?php echo esc_attr($_GET['page']); ?>&paged=<?php echo $i; ?><?php echo $this->build_filter_query_string($filters); ?>"
1895 class="sync-pagination-btn <?php echo $i === $page ? 'active' : ''; ?>"><?php echo $i; ?></a>
1896 <?php endfor; ?>
1897
1898 <?php if ($page < $total_pages): ?>
1899 <a href="?page=<?php echo esc_attr($_GET['page']); ?>&paged=<?php echo $page + 1; ?><?php echo $this->build_filter_query_string($filters); ?>" class="sync-pagination-btn">›</a>
1900 <?php endif; ?>
1901 </div>
1902 </div>
1903 <?php endif; ?>
1904 </div>
1905 </div>
1906 <?php
1907 }
1908
1909 public function creat_error_Logs_List()
1910 {
1911 // printf('<h1> Error Logs </h1>');
1912 // $error_log = new Metasync_Error_Logs_Table($this->data_error_log_list);
1913 // $error_log->create_admin_error_log_list_interface();
1914 }
1915
1916 public function create_admin_heartbeat_error_logs_page()
1917 {
1918 ?>
1919 <div class="wrap metasync-dashboard-wrap" data-theme="<?php echo esc_attr(get_option('metasync_theme', 'dark')); ?>">
1920
1921 <?php $this->admin->render_plugin_header('HeartBeat Error Logs'); ?>
1922
1923 <?php $this->admin->render_navigation_menu('heartbeat-error-logs'); ?>
1924
1925 <div class="dashboard-card">
1926 <h2>💓 HeartBeat Error Logs</h2>
1927 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Monitor WordPress heartbeat errors to identify connectivity issues and system problems.</p>
1928 <?php
1929 $heartbeat_errors = new Metasync_HeartBeat_Error_Monitor($this->admin->db_heartbeat_errors);
1930 $heartbeat_errors->create_admin_heartbeat_errors_interface();
1931 ?>
1932 </div>
1933 </div>
1934 <?php
1935 }
1936
1937 public function create_admin_bot_statistics_page()
1938 {
1939 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-otto-bot-statistics.php';
1940 }
1941
1942 // ------------------------------------------------------------------
1943 // Private helpers (copied from Metasync_Admin, used only by pages above)
1944 // ------------------------------------------------------------------
1945
1946 private function time_elapsed_string($datetime, $full = false)
1947 {
1948 if(empty($datetime)){
1949 return "";
1950 }
1951 $now = new DateTime;
1952 $ago = new DateTime($datetime);
1953
1954 $diff = $now->diff($ago);
1955
1956 $string = [
1957 'y' => 'year',
1958 'm' => 'month',
1959 'w' => 'week',
1960 'd' => 'day',
1961 'h' => 'hour',
1962 'i' => 'minute',
1963 's' => 'second',
1964 ];
1965
1966 foreach ($string as $k => &$v) {
1967 if (isset($diff->$k) && $diff->$k) {
1968 $v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : '');
1969 } else {
1970 unset($string[$k]);
1971 }
1972 }
1973 if (!$full) $string = array_slice($string, 0, 1);
1974 return $string ? implode(', ', $string) . ' ago' : 'just now';
1975 }
1976
1977 private function build_filter_query_string($filters)
1978 {
1979 $query_parts = [];
1980 foreach ($filters as $key => $value) {
1981 if (!empty($value)) {
1982 $query_parts[] = $key . '=' . urlencode($value);
1983 }
1984 }
1985 return !empty($query_parts) ? '&' . implode('&', $query_parts) : '';
1986 }
1987
1988 private function handle_sync_log_ajax()
1989 {
1990 wp_die();
1991 }
1992 }
1993