PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.13
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.13
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 2.5.23 All 138 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.13, at includes/class-metasync-admin-pages.php

2,252 lines 114.6 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 var storedPassword = '<?php echo esc_js($whitelabel_settings['settings_password'] ?? ''); ?>';
895
896 passwordField.on('keydown', function(e) {
897 var currentValue = $(this).val();
898
899 if (hideSettingsCheckbox.is(':checked') &&
900 (currentValue.length <= 1 || !currentValue) &&
901 (e.keyCode === 8 || e.keyCode === 46)) {
902
903 e.preventDefault();
904 e.stopImmediatePropagation();
905
906 if (!isShowingModal) {
907 showModal(
908 '&#9888;',
909 'Cannot Remove Password',
910 '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.'
911 );
912 }
913
914 return false;
915 }
916 });
917
918 $(document).on('metasync_settings_saved', function() {
919 });
920
921 $(document).on('keydown', function(e) {
922 if (e.key === 'Escape') {
923 $('#metasync-custom-modal').fadeOut(200);
924 }
925 });
926
927 $('#metasync-export-whitelabel-btn').on('click', function(e) {
928 e.preventDefault();
929
930 var $button = $(this);
931 var originalText = $button.html();
932
933 $button.prop('disabled', true).html('⏳ Exporting...');
934
935 var form = $('<form>', {
936 'method': 'POST',
937 'action': '<?php echo esc_js(admin_url('admin-post.php')); ?>',
938 'target': '_blank'
939 });
940
941 form.append($('<input>', {
942 'type': 'hidden',
943 'name': 'action',
944 'value': 'metasync_export_whitelabel_settings'
945 }));
946
947 form.append($('<input>', {
948 'type': 'hidden',
949 'name': '_wpnonce',
950 'value': '<?php echo wp_create_nonce('metasync_export_whitelabel'); ?>'
951 }));
952
953 $('body').append(form);
954 form.submit();
955
956 setTimeout(function() {
957 form.remove();
958 $button.prop('disabled', false).html(originalText);
959 }, 2000);
960 });
961 });
962 </script>
963 <?php
964 } elseif ($active_tab == 'advanced') {
965 if (!Metasync_Access_Control::user_can_access('hide_advanced')) {
966 echo '<div class="dashboard-card" style="background: var(--dashboard-card-bg); padding: 25px; border-radius: 8px; text-align: center;">';
967 echo '<h2 style="color: var(--dashboard-text-primary);">Access Denied</h2>';
968 echo '<p style="color: var(--dashboard-text-secondary);">You do not have permission to access this page.</p>';
969 echo '</div>';
970 } else {
971 ?>
972 <div class="dashboard-card">
973 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
974 <div style="flex: 1;">
975 <h2 style="margin: 0;">🧰 Advanced Settings</h2>
976 <p style="color: var(--dashboard-text-secondary); margin: 5px 0 0 0;">Technical utilities for troubleshooting and connectivity checks.</p>
977 </div>
978 <?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?>
979 <div>
980 <?php Metasync_Compatibility_Checker::instance()->render_lock_button('advanced'); ?>
981 </div>
982 <?php endif; ?>
983 </div>
984
985 <?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?>
986 <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;">
987 <strong>�
988 Access Granted:</strong> You have successfully authenticated and can now modify settings.
989 </div>
990 <?php endif; ?>
991
992 <?php
993 if (isset($_GET['debug_mode_enabled']) && $_GET['debug_mode_enabled'] == '1'):
994 $indefinite = isset($_GET['indefinite']) && $_GET['indefinite'] == '1';
995 ?>
996 <div class="notice notice-success inline" style="margin-bottom: 20px;">
997 <p>
998 <strong>�
999 Success!</strong> Debug mode has been enabled<?php echo $indefinite ? ' indefinitely' : ' for 24 hours'; ?>.
1000 </p>
1001 </div>
1002 <?php endif; ?>
1003
1004 <?php if (isset($_GET['debug_mode_disabled']) && $_GET['debug_mode_disabled'] == '1'): ?>
1005 <div class="notice notice-info inline" style="margin-bottom: 20px;">
1006 <p>
1007 <strong>ℹ️ Debug Mode Disabled:</strong> Debug mode has been successfully disabled.
1008 </p>
1009 </div>
1010 <?php endif; ?>
1011
1012 <?php if (isset($_GET['debug_mode_extended']) && $_GET['debug_mode_extended'] == '1'): ?>
1013 <div class="notice notice-success inline" style="margin-bottom: 20px;">
1014 <p>
1015 <strong>�
1016 Extended!</strong> Debug mode has been extended for another 24 hours.
1017 </p>
1018 </div>
1019 <?php endif; ?>
1020
1021 <?php if (isset($_GET['debug_error']) && $_GET['debug_error'] == '1'): ?>
1022 <div class="notice notice-error inline" style="margin-bottom: 20px;">
1023 <p>
1024 <strong> Error:</strong> Unable to perform the debug mode operation. Please try again.
1025 </p>
1026 </div>
1027 <?php endif; ?>
1028
1029 <?php
1030 $this->admin->render_advanced_accordion();
1031 ?>
1032 </div>
1033
1034 <?php
1035 }
1036 }
1037 ?>
1038
1039 <!-- Save button removed - using floating notification system instead -->
1040
1041 </form>
1042
1043 <!-- Lock Section Button Handler (runs on all tabs) -->
1044 <script>
1045 jQuery(document).ready(function($) {
1046 var ajaxUrl = (typeof window.ajaxurl !== 'undefined' && window.ajaxurl)
1047 ? window.ajaxurl
1048 : '<?php echo esc_js(admin_url('admin-ajax.php')); ?>';
1049 $('.metasync-lock-btn').on('click', function(e) {
1050 e.preventDefault();
1051 e.stopPropagation();
1052
1053 var currentTab = $(this).data('tab');
1054
1055 var logoutForm = $('<form>', {
1056 'method': 'post',
1057 'action': ''
1058 });
1059
1060 logoutForm.append('<?php echo wp_nonce_field("whitelabel_logout_nonce", "whitelabel_logout_nonce", true, false); ?>');
1061
1062 logoutForm.append($('<input>', {
1063 'type': 'hidden',
1064 'name': 'whitelabel_logout',
1065 'value': '1'
1066 }));
1067
1068 $('body').append(logoutForm);
1069 logoutForm.submit();
1070
1071 return false;
1072 });
1073 });
1074 </script>
1075
1076 <!-- Host Blocking-generated JavaScript -->
1077 <script>
1078 jQuery(document).ready(function($) {
1079 var ajaxUrl = (typeof window.ajaxurl !== 'undefined' && window.ajaxurl)
1080 ? window.ajaxurl
1081 : '<?php echo esc_js(admin_url('admin-ajax.php')); ?>';
1082
1083 $(document).on('click', '#test-get-request', function(e) {
1084 e.preventDefault();
1085 e.stopPropagation();
1086 runHostTest('GET');
1087 return false;
1088 });
1089
1090 $(document).on('click', '#test-post-request', function(e) {
1091 e.preventDefault();
1092 e.stopPropagation();
1093 runHostTest('POST');
1094 return false;
1095 });
1096
1097 $(document).on('click', '#test-both-requests', function(e) {
1098 e.preventDefault();
1099 e.stopPropagation();
1100 runHostTest('BOTH');
1101 return false;
1102 });
1103
1104 function runHostTest(method) {
1105 var buttonId = (method === 'BOTH' ? 'test-both-requests' : 'test-' + method.toLowerCase() + '-request');
1106 var $button = $('#' + buttonId);
1107
1108 if ($button.length === 0) {
1109 alert('Error: Test button not found. Please refresh the page.');
1110 return;
1111 }
1112
1113 var originalText = $button.text();
1114
1115 $button.prop('disabled', true);
1116 $button.text('Testing...');
1117
1118 var $resultsDiv = $('#host-test-results');
1119 var $resultsContent = $('#test-results-content');
1120 $resultsDiv.show();
1121 $resultsContent.html('<div class="notice notice-info"><p>Running ' + (method === 'BOTH' ? 'GET and POST' : method) + ' test(s)...</p></div>');
1122
1123 var testsToRun = (method === 'BOTH') ? ['GET', 'POST'] : [method];
1124 var completedTests = 0;
1125 var allResults = [];
1126
1127 testsToRun.forEach(function(testMethod) {
1128 var action = 'metasync_test_host_blocking_' + testMethod.toLowerCase();
1129
1130 $.ajax({
1131 url: ajaxUrl,
1132 type: 'POST',
1133 dataType: 'json',
1134 data: { action: action, nonce: '<?php echo wp_create_nonce('metasync_nonce'); ?>' },
1135 timeout: 35000,
1136 success: function(response, textStatus, xhr) {
1137 try {
1138 if (response && response.success && response.data) {
1139 allResults.push(response.data);
1140 } else {
1141 allResults.push({
1142 method: testMethod,
1143 status: 'error',
1144 error: (response && response.data) ? response.data : 'Unexpected response',
1145 blocked: true,
1146 details: 'Received non-success response from server.'
1147 });
1148 }
1149 } catch (e) {
1150 allResults.push({
1151 method: testMethod,
1152 status: 'error',
1153 error: 'Response parse error: ' + (e && e.message ? e.message : e),
1154 blocked: true
1155 });
1156 }
1157 finalizeOne();
1158 },
1159 error: function(xhr, status, error) {
1160 var payload = (xhr && xhr.responseText) ? xhr.responseText.substring(0, 500) : '';
1161 allResults.push({
1162 method: testMethod,
1163 status: 'error',
1164 error: 'AJAX failed: ' + error + (payload ? '' + payload : ''),
1165 blocked: true,
1166 details: 'Request did not complete successfully. Status: ' + status
1167 });
1168 finalizeOne();
1169 }
1170 });
1171 });
1172
1173 function finalizeOne() {
1174 completedTests++;
1175 if (completedTests === testsToRun.length) {
1176 displayResults(allResults);
1177 resetButtons();
1178 var $container = $('#host-test-results');
1179 if ($container && $container[0] && $container[0].scrollIntoView) {
1180 $container[0].scrollIntoView({ behavior: 'smooth', block: 'start' });
1181 }
1182 }
1183 }
1184 }
1185
1186 function resetButtons() {
1187 $('#test-get-request, #test-post-request, #test-both-requests').prop('disabled', false);
1188 $('#test-get-request').text('Test GET Request');
1189 $('#test-post-request').text('Test POST Request');
1190 $('#test-both-requests').text('Test Both Requests');
1191 }
1192
1193 function displayResults(results) {
1194 var html = '';
1195
1196 results.forEach(function(result) {
1197 var statusClass = result.status === 'success' ? 'success' : 'error';
1198 var statusIcon = result.status === 'success' ? '�
1199 ' : '';
1200 var blockedStatus = result.blocked ? 'BLOCKED' : 'ALLOWED';
1201 var blockedClass = result.blocked ? 'blocked' : 'allowed';
1202
1203 html += '<div class="test-result-item ' + statusClass + '">';
1204 html += '<div class="test-result-header">';
1205 html += '<h4>' + statusIcon + ' ' + result.method + ' Request - ' + blockedStatus + '</h4>';
1206 html += '<span class="test-status ' + blockedClass + '">' + blockedStatus + '</span>';
1207 html += '</div>';
1208
1209 html += '<div class="test-result-details">';
1210 html += '<p><strong>Response Time:</strong> ' + result.response_time + '</p>';
1211 html += '<p><strong>Status:</strong> ' + result.status + '</p>';
1212
1213 if (result.status_code) {
1214 html += '<p><strong>HTTP Status Code:</strong> <span class="status-code">' + result.status_code + '</span></p>';
1215 }
1216
1217 if (result.error) {
1218 html += '<p><strong>Error:</strong> <span class="error-message">' + result.error + '</span></p>';
1219 }
1220
1221 if (result.body) {
1222 html += '<p><strong>Response Body:</strong></p>';
1223 html += '<pre class="response-body">' + escapeHtml(result.body) + '</pre>';
1224 }
1225
1226 if (result.headers && Object.keys(result.headers).length > 0) {
1227 html += '<p><strong>Response Headers:</strong></p>';
1228 html += '<pre class="response-headers">';
1229 Object.keys(result.headers).forEach(function(key) {
1230 html += key + ': ' + result.headers[key] + '\n';
1231 });
1232 html += '</pre>';
1233 }
1234
1235 if (result.sent_data) {
1236 html += '<p><strong>Sent Data:</strong></p>';
1237 html += '<pre class="sent-data">' + escapeHtml(JSON.stringify(result.sent_data, null, 2)) + '</pre>';
1238 }
1239
1240 if (result.parsed_response) {
1241 html += '<p><strong>Parsed Response:</strong></p>';
1242 html += '<pre class="parsed-response">' + escapeHtml(JSON.stringify(result.parsed_response, null, 2)) + '</pre>';
1243 }
1244
1245 html += '<p><strong>Details:</strong> ' + result.details + '</p>';
1246 html += '</div>';
1247 html += '</div>';
1248 });
1249
1250 $('#test-results-content').html(html);
1251 $('#host-test-results').show();
1252 }
1253
1254 function escapeHtml(text) {
1255 var map = {
1256 '&': '&amp;',
1257 '<': '&lt;',
1258 '>': '&gt;',
1259 '"': '&quot;',
1260 '\'': '&#039;'
1261 };
1262 return text.replace(/[&<>"']/g, function(m) { return map[m]; });
1263 }
1264 });
1265 </script>
1266
1267 <!-- Host Blocking Test CSS -->
1268 <style>
1269 .test-result-item {
1270 background: #fff;
1271 border: 1px solid #ddd;
1272 border-radius: 8px;
1273 margin-bottom: 20px;
1274 overflow: hidden;
1275 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1276 }
1277
1278 .test-result-item.success {
1279 border-left: 4px solid #28a745;
1280 }
1281
1282 .test-result-item.error {
1283 border-left: 4px solid #dc3545;
1284 }
1285
1286 .test-result-header {
1287 background: #f8f9fa;
1288 padding: 15px 20px;
1289 border-bottom: 1px solid #dee2e6;
1290 display: flex;
1291 justify-content: space-between;
1292 align-items: center;
1293 }
1294
1295 .test-result-header h4 {
1296 margin: 0;
1297 color: #495057;
1298 font-size: 16px;
1299 }
1300
1301 .test-status {
1302 padding: 4px 12px;
1303 border-radius: 20px;
1304 font-size: 12px;
1305 font-weight: bold;
1306 text-transform: uppercase;
1307 }
1308
1309 .test-status.allowed {
1310 background: #d4edda;
1311 color: #155724;
1312 }
1313
1314 .test-status.blocked {
1315 background: #f8d7da;
1316 color: #721c24;
1317 }
1318
1319 .test-result-details {
1320 padding: 20px;
1321 }
1322
1323 .test-result-details p {
1324 margin: 8px 0;
1325 color: #495057;
1326 }
1327
1328 .test-result-details code {
1329 background: #f8f9fa;
1330 padding: 2px 6px;
1331 border-radius: 3px;
1332 font-family: 'Courier New', monospace;
1333 color: #e83e8c;
1334 }
1335
1336 .status-code {
1337 font-weight: bold;
1338 color: #007cba;
1339 }
1340
1341 .error-message {
1342 color: #dc3545;
1343 font-weight: bold;
1344 }
1345
1346 .response-body, .response-headers, .sent-data, .parsed-response {
1347 background: #f8f9fa;
1348 border: 1px solid #dee2e6;
1349 border-radius: 4px;
1350 padding: 12px;
1351 margin: 8px 0;
1352 font-family: 'Courier New', monospace;
1353 font-size: 12px;
1354 line-height: 1.4;
1355 max-height: 200px;
1356 overflow-y: auto;
1357 white-space: pre-wrap;
1358 word-break: break-all;
1359 }
1360
1361 .response-body {
1362 color: #495057;
1363 }
1364
1365 .response-headers {
1366 color: #6c757d;
1367 }
1368
1369 .sent-data {
1370 color: #007cba;
1371 }
1372
1373 .parsed-response {
1374 color: #28a745;
1375 background: #f8fff9;
1376 border-color: #c3e6cb;
1377 }
1378
1379 #host-test-results {
1380 margin-top: 20px;
1381 }
1382
1383 #host-test-results h3 {
1384 color: #495057;
1385 margin-bottom: 15px;
1386 padding-bottom: 10px;
1387 border-bottom: 2px solid #dee2e6;
1388 }
1389
1390 .button:disabled {
1391 opacity: 0.6;
1392 cursor: not-allowed;
1393 }
1394 </style>
1395
1396 <?php if ($active_tab === 'advanced'): ?>
1397 <?php if (isset($_GET['settings_cleared']) && $_GET['settings_cleared'] == '1'): ?>
1398 <div class="notice notice-success is-dismissible">
1399 <p><strong>�
1400 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>
1401 </div>
1402 <?php endif; ?>
1403
1404 <?php if (isset($_GET['clear_settings_error']) && $_GET['clear_settings_error'] == '1'): ?>
1405 <div class="notice notice-error is-dismissible">
1406 <p><strong>❌ Error!</strong> Failed to clear settings due to a security check failure. Please try again.</p>
1407 </div>
1408 <?php endif; ?>
1409
1410 <?php if (isset($_GET['log_cleared']) && $_GET['log_cleared'] == '1'): ?>
1411 <div class="notice notice-success is-dismissible">
1412 <p><strong>�
1413 Success!</strong> Error logs have been cleared successfully.</p>
1414 </div>
1415 <?php endif; ?>
1416
1417 <?php if (isset($_GET['clear_error']) && $_GET['clear_error'] == '1'): ?>
1418 <div class="notice notice-error is-dismissible">
1419 <p><strong>❌ Error!</strong> Failed to clear error logs due to a security check failure. Please try again.</p>
1420 </div>
1421 <?php endif; ?>
1422
1423 <?php if (isset($_GET['access_roles_saved']) && $_GET['access_roles_saved'] == '1'): ?>
1424 <div class="notice notice-success is-dismissible">
1425 <p><strong>�
1426 Success!</strong> Plugin access roles have been saved successfully.</p>
1427 </div>
1428 <?php endif; ?>
1429
1430 <?php if (isset($_GET['access_roles_error']) && $_GET['access_roles_error'] == '1'): ?>
1431 <div class="notice notice-error is-dismissible">
1432 <p><strong>❌ Error!</strong> Failed to save plugin access roles. <?php echo isset($_GET['message']) ? esc_html(urldecode($_GET['message'])) : 'Please try again.'; ?></p>
1433 </div>
1434 <?php endif; ?>
1435
1436 <?php endif; ?>
1437 <?php $this->admin->render_layout_close(); ?>
1438 <?php
1439 }
1440
1441 // ------------------------------------------------------------------
1442 // Simple page renderers
1443 // ------------------------------------------------------------------
1444
1445 public function create_admin_dashboard_page()
1446 {
1447 ?>
1448 <?php $this->admin->render_layout_open('Dashboard', 'dashboard', ''); ?>
1449
1450 <div class="dashboard-card">
1451 <h2><?php echo esc_html($this->admin->get_effective_menu_title()); ?> Dashboard</h2>
1452 <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>
1453 <?php
1454 if (!isset(Metasync::get_option('general')['linkgraph_token']) || Metasync::get_option('general')['linkgraph_token'] == '') {
1455 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>';
1456 echo '<a href="' . admin_url('admin.php?page=' . Metasync_Admin::$page_slug) . '" class="button button-secondary">Go to Settings</a>';
1457 } else {
1458 echo '<a href="' . esc_url($this->admin->get_dashboard_url()) . '" target="_blank" class="button button-primary">Open Dashboard</a>';
1459 }
1460 ?>
1461 </div>
1462 <?php $this->admin->render_layout_close(); ?>
1463 <?php
1464 }
1465
1466 public function create_admin_robots_txt_page()
1467 {
1468 ?>
1469 <?php $this->admin->render_layout_open('Robots.txt', 'robots_txt', 'Control which pages search engines can crawl.'); ?>
1470
1471 <?php
1472 require_once plugin_dir_path(dirname(__FILE__)) . 'robots-txt/class-metasync-robots-txt.php';
1473
1474 $robots_txt = Metasync_Robots_Txt::get_instance();
1475
1476 if (isset($_POST['metasync_robots_txt_nonce'])) {
1477 check_admin_referer('metasync_save_robots_txt', 'metasync_robots_txt_nonce');
1478
1479 if (isset($_POST['robots_content'])) {
1480 $content = wp_unslash($_POST['robots_content']);
1481
1482 $validation = $robots_txt->validate_content($content);
1483
1484 if ($validation['valid']) {
1485 $result = $robots_txt->write_robots_file($content);
1486
1487 if (is_wp_error($result)) {
1488 echo '<div class="notice notice-error"><p>' . esc_html($result->get_error_message()) . '</p></div>';
1489 } else {
1490 echo '<div class="notice notice-success"><p>' . esc_html__('robots.txt file saved successfully!', 'metasync') . '</p></div>';
1491
1492 if (!empty($validation['warnings'])) {
1493 foreach ($validation['warnings'] as $warning) {
1494 echo '<div class="notice notice-warning"><p>' . esc_html($warning) . '</p></div>';
1495 }
1496 }
1497 }
1498 } else {
1499 echo '<div class="notice notice-error"><p>' . esc_html__('Validation failed. Please fix the errors below:', 'metasync') . '</p>';
1500 foreach ($validation['errors'] as $error) {
1501 echo '<p>- ' . esc_html($error) . '</p>';
1502 }
1503 echo '</div>';
1504 }
1505 }
1506 }
1507
1508 $current_content = $robots_txt->read_robots_file();
1509 if (is_wp_error($current_content)) {
1510 echo '<div class="notice notice-error"><p>' . esc_html($current_content->get_error_message()) . '</p></div>';
1511 $current_content = '';
1512 }
1513
1514 $backups = $robots_txt->get_backup_history(10);
1515
1516 $file_exists = $robots_txt->file_exists();
1517 $is_writable = $robots_txt->is_writable();
1518
1519 $robots_txt->render($this->admin, $current_content, $backups, $file_exists, $is_writable);
1520 ?>
1521 <?php $this->admin->render_layout_close(); ?>
1522 <?php
1523 }
1524
1525 public function create_admin_report_issue_page()
1526 {
1527 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-report-issue.php';
1528 }
1529
1530 public function create_admin_xml_sitemap_page()
1531 {
1532 require_once plugin_dir_path(dirname(__FILE__)) . 'sitemap/class-metasync-sitemap-generator.php';
1533
1534 $sitemap_generator = new Metasync_Sitemap_Generator();
1535
1536 if (isset($_POST['metasync_sitemap_nonce'])) {
1537 check_admin_referer('metasync_sitemap_action', 'metasync_sitemap_nonce');
1538
1539 if (isset($_POST['generate_sitemap'])) {
1540 $disabled_plugins = $sitemap_generator->disable_other_sitemap_generators();
1541
1542 $result = $sitemap_generator->generate_sitemap();
1543
1544 if (is_wp_error($result)) {
1545 error_log('[MetaSync] Sitemap generation failed: ' . $result->get_error_message());
1546 echo '<div class="notice notice-error"><p>' . esc_html__('Sitemap generation failed. Please try again or check the error logs.', 'metasync') . '</p></div>';
1547 } else {
1548 $message = esc_html__('Sitemap generated successfully!', 'metasync');
1549 if ($disabled_plugins) {
1550 $message .= ' ' . esc_html__('Conflicting sitemap generators have been automatically disabled.', 'metasync');
1551 }
1552
1553 $robots_result = get_transient('metasync_sitemap_robots_updated');
1554 if ($robots_result && $robots_result['success']) {
1555 if ($robots_result['action'] === 'added') {
1556 $message .= ' ' . esc_html__('Sitemap URL has been added to robots.txt.', 'metasync');
1557 } elseif ($robots_result['action'] === 'updated') {
1558 $message .= ' ' . esc_html__('Sitemap URL has been updated in robots.txt.', 'metasync');
1559 } elseif ($robots_result['action'] === 'created') {
1560 $message .= ' ' . esc_html__('robots.txt file has been created with sitemap URL.', 'metasync');
1561 }
1562 delete_transient('metasync_sitemap_robots_updated');
1563 }
1564
1565 echo '<div class="notice notice-success"><p>' . esc_html($message) . '</p></div>';
1566 }
1567 } elseif (isset($_POST['enable_auto_update'])) {
1568 update_option('metasync_sitemap_auto_update', true);
1569 $sitemap_generator->setup_auto_update_hooks();
1570 echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update enabled!', 'metasync') . '</p></div>';
1571 } elseif (isset($_POST['disable_auto_update'])) {
1572 update_option('metasync_sitemap_auto_update', false);
1573 echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update disabled!', 'metasync') . '</p></div>';
1574 } elseif (isset($_POST['delete_sitemap'])) {
1575 $deleted = $sitemap_generator->delete_sitemap();
1576 if ($deleted) {
1577 update_option('metasync_sitemap_auto_update', false);
1578 // Re-enable WP core sitemap so the site isn't left with zero sitemaps (WP-396)
1579 delete_option('metasync_disable_wp_sitemap');
1580 echo '<div class="notice notice-success"><p>' . esc_html__('Sitemap deleted successfully!', 'metasync') . '</p></div>';
1581 } else {
1582 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>';
1583 }
1584 } elseif (isset($_POST['enable_other_sitemaps'])) {
1585 $enabled_plugins = $sitemap_generator->enable_other_sitemap_generators();
1586 if ($enabled_plugins) {
1587 echo '<div class="notice notice-success"><p>' . esc_html__('Other sitemap plugins have been re-enabled successfully!', 'metasync') . '</p></div>';
1588 } else {
1589 echo '<div class="notice notice-info"><p>' . esc_html__('No sitemap plugins were found to re-enable.', 'metasync') . '</p></div>';
1590 }
1591 }
1592 }
1593
1594 $sitemap_exists = $sitemap_generator->sitemap_exists();
1595 $sitemap_url = $sitemap_generator->get_sitemap_url();
1596 $url_count = $sitemap_generator->count_urls();
1597 $last_generated = $sitemap_generator->get_last_generated_time();
1598 $auto_update_enabled = get_option('metasync_sitemap_auto_update', false);
1599 $active_sitemap_plugins = $sitemap_generator->check_active_sitemap_plugins();
1600
1601 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-xml-sitemap.php';
1602 }
1603
1604 public function create_admin_custom_pages_page()
1605 {
1606 ?>
1607 <?php $this->admin->render_layout_open('Custom Pages', 'custom_pages', 'Create blank canvas pages for Elementor, Divi, or Gutenberg.'); ?>
1608
1609 <div class="metasync-page-content">
1610 <?php
1611 require_once plugin_dir_path(dirname(__FILE__)) . 'custom-pages/class-metasync-custom-pages-admin.php';
1612 Metasync_Custom_Pages_Admin::render_admin_page();
1613 ?>
1614 </div>
1615
1616 <?php $this->admin->render_layout_close(); ?>
1617 <?php
1618 }
1619
1620 public function create_admin_404_monitor_page()
1621 {
1622 require_once plugin_dir_path(dirname(__FILE__)) . '404-monitor/class-metasync-404-monitor-database.php';
1623 require_once plugin_dir_path(dirname(__FILE__)) . '404-monitor/class-metasync-404-monitor.php';
1624
1625 $db_404 = new Metasync_Error_Monitor_Database();
1626 $ErrorMonitor = new Metasync_Error_Monitor($db_404);
1627
1628 $this->admin->render_layout_open('404 Monitor', 'monitor_404', 'Track and manage 404 errors detected on your site.');
1629 $ErrorMonitor->create_admin_plugin_interface();
1630 $this->admin->render_layout_close();
1631 }
1632
1633 public function create_admin_search_engine_verification_page()
1634 {
1635 $page_slug = Metasync_Admin::$page_slug . '_searchengines-verification';
1636 ?>
1637 <?php $this->admin->render_layout_open('Site Verification', 'site_verification', 'Verify your site ownership with search engines and other platforms.'); ?>
1638
1639 <form method="post" action="options.php">
1640 <div class="dashboard-card">
1641 <h2>Search Engine Verification</h2>
1642 <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>
1643 <?php
1644 settings_fields(Metasync_Admin::option_group);
1645 do_settings_sections($page_slug);
1646 submit_button();
1647 ?>
1648 </div>
1649 </form>
1650 <?php $this->admin->render_layout_close(); ?>
1651 <?php
1652 }
1653
1654 public function create_admin_breadcrumbs_page()
1655 {
1656 $page_slug = Metasync_Admin::$page_slug . '_breadcrumbs';
1657 ?>
1658 <?php $this->admin->render_layout_open('Breadcrumbs', 'breadcrumbs', 'Configure breadcrumb trail settings and placement.'); ?>
1659
1660 <form method="post" action="options.php">
1661 <div class="dashboard-card">
1662 <h2>Breadcrumbs Settings</h2>
1663 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p>
1664 <?php
1665 settings_fields(Metasync_Admin::option_group);
1666 do_settings_sections($page_slug);
1667 submit_button();
1668 ?>
1669 </div>
1670 </form>
1671
1672 <div class="dashboard-card" style="margin-top: 20px;">
1673 <h2>📖 How to Display Breadcrumbs</h2>
1674 <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>
1675
1676 <h3 style="margin-top: 0;">Option 1 — Shortcode</h3>
1677 <p style="color: var(--dashboard-text-secondary);">Paste the shortcode inside any page, post, or widget:</p>
1678 <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>
1679
1680 <h3>Option 2 PHP Template Function</h3>
1681 <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>
1682 <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>
1683
1684 <h3>Option 3 PHP Return Value</h3>
1685 <p style="color: var(--dashboard-text-secondary);">Get the breadcrumb HTML as a string to manipulate before output:</p>
1686 <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();
1687 echo $breadcrumbs-&gt;render_breadcrumb_html();</pre><span class="metasync-copy-badge">Copy</span></div>
1688
1689 <p style="color: var(--dashboard-text-secondary); margin-top: 16px; font-size: 12px;">
1690 <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>
1691 <code>echo $breadcrumbs-&gt;render_breadcrumb_html(['post_id' =&gt; 123]);</code>
1692 </p>
1693 <?php /* WP-492: click-to-copy for the code snippets above */ ?>
1694 <style>
1695 .metasync-copy-wrap { position: relative; }
1696 .metasync-copy-snippet { cursor: pointer; outline: 1px solid transparent; transition: outline-color .15s ease; }
1697 .metasync-copy-wrap:hover .metasync-copy-snippet { outline-color: var(--dashboard-border, #374151); }
1698 .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); }
1699 .metasync-copy-wrap:hover .metasync-copy-badge { opacity: .85; }
1700 .metasync-copy-badge.metasync-copy-done { opacity: 1; background: var(--dashboard-gradient-primary, linear-gradient(135deg, #667eea 0%, #764ba2 100%)); color: #fff; }
1701 </style>
1702 <script>
1703 (function(){
1704 if (window.__metasyncCopyInit) return; window.__metasyncCopyInit = true;
1705 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); }
1706 function attach(){
1707 document.querySelectorAll('.metasync-copy-snippet').forEach(function(pre){
1708 if (pre.dataset.copyBound) return; pre.dataset.copyBound='1';
1709 pre.setAttribute('title','Click to copy');
1710 pre.addEventListener('click', function(){
1711 var text = pre.textContent.trim();
1712 var wrap = pre.closest('.metasync-copy-wrap') || pre.parentNode;
1713 var badge = wrap.querySelector('.metasync-copy-badge');
1714 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); };
1715 if (navigator.clipboard && navigator.clipboard.writeText){ navigator.clipboard.writeText(text).then(done).catch(function(){ fallback(text); done(); }); }
1716 else { fallback(text); done(); }
1717 });
1718 });
1719 }
1720 if (document.readyState==='loading'){ document.addEventListener('DOMContentLoaded', attach); } else { attach(); }
1721 })();
1722 </script>
1723 </div>
1724 <?php $this->admin->render_layout_close(); ?>
1725 <?php
1726 }
1727
1728 public function create_admin_schema_markup_page()
1729 {
1730 $page_slug = Metasync_Admin::$page_slug . '_schema-markup';
1731 $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.');
1732 ?>
1733 <form method="post" action="options.php">
1734 <div class="dashboard-card">
1735 <?php
1736 settings_fields(Metasync_Admin::option_group);
1737 do_settings_sections($page_slug);
1738 submit_button('Save Changes');
1739 ?>
1740 </div>
1741 </form>
1742 <?php
1743 $this->admin->render_layout_close();
1744 }
1745
1746 public function create_admin_local_business_page()
1747 {
1748 $page_slug = Metasync_Admin::$page_slug . '_local-seo';
1749 ?>
1750 <?php $this->admin->render_layout_open('Local Business', 'local_business', 'Configure local business information for better local SEO.'); ?>
1751
1752 <form method="post" action="options.php">
1753 <div class="dashboard-card">
1754 <h2>🏢 Local Business Configuration</h2>
1755 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure your local business information for better local search engine optimization.</p>
1756 <?php
1757 settings_fields(Metasync_Admin::option_group);
1758 do_settings_sections($page_slug);
1759 submit_button();
1760 ?>
1761 </div>
1762 </form>
1763 <?php $this->admin->render_layout_close(); ?>
1764 <?php
1765 }
1766
1767 public function create_admin_code_snippets_page()
1768 {
1769 $page_slug = Metasync_Admin::$page_slug . '_code-snippets';
1770 ?>
1771 <?php $this->admin->render_layout_open('Code Snippets', 'code_snippets', 'Add custom code to your site header and footer.'); ?>
1772
1773 <form method="post" action="options.php">
1774 <div class="dashboard-card">
1775 <h2>Code Snippets Management</h2>
1776 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Add custom code snippets to enhance your site functionality and tracking capabilities.</p>
1777 <?php
1778 settings_fields(Metasync_Admin::option_group);
1779 do_settings_sections($page_slug);
1780 submit_button();
1781 ?>
1782 </div>
1783 </form>
1784 <?php $this->admin->render_layout_close(); ?>
1785 <?php
1786 }
1787
1788 public function create_admin_google_instant_index_page()
1789 {
1790 // Delegates to the admin class which uses the shared credentials view
1791 $this->admin->create_admin_google_instant_index_page();
1792 }
1793
1794 public function create_admin_google_console_page()
1795 {
1796 ?>
1797 <?php $this->admin->render_layout_open('Google Console', 'google_console', ''); ?>
1798
1799 <?php
1800 google_index_direct()->show_google_instant_indexing_console();
1801 ?>
1802 <?php $this->admin->render_layout_close(); ?>
1803 <?php
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 (WP-350 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