PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.17
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.17
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.6.17, at includes/class-metasync-admin-pages.php

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