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

2,214 lines 110.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']) ? $_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 $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 },
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 echo '<div class="notice notice-error"><p>' . esc_html($result->get_error_message()) . '</p></div>';
1546 } else {
1547 $message = esc_html__('Sitemap generated successfully!', 'metasync');
1548 if ($disabled_plugins) {
1549 $message .= ' ' . esc_html__('Conflicting sitemap generators have been automatically disabled.', 'metasync');
1550 }
1551
1552 $robots_result = get_transient('metasync_sitemap_robots_updated');
1553 if ($robots_result && $robots_result['success']) {
1554 if ($robots_result['action'] === 'added') {
1555 $message .= ' ' . esc_html__('Sitemap URL has been added to robots.txt.', 'metasync');
1556 } elseif ($robots_result['action'] === 'updated') {
1557 $message .= ' ' . esc_html__('Sitemap URL has been updated in robots.txt.', 'metasync');
1558 } elseif ($robots_result['action'] === 'created') {
1559 $message .= ' ' . esc_html__('robots.txt file has been created with sitemap URL.', 'metasync');
1560 }
1561 delete_transient('metasync_sitemap_robots_updated');
1562 }
1563
1564 echo '<div class="notice notice-success"><p>' . esc_html($message) . '</p></div>';
1565 }
1566 } elseif (isset($_POST['enable_auto_update'])) {
1567 update_option('metasync_sitemap_auto_update', true);
1568 $sitemap_generator->setup_auto_update_hooks();
1569 echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update enabled!', 'metasync') . '</p></div>';
1570 } elseif (isset($_POST['disable_auto_update'])) {
1571 update_option('metasync_sitemap_auto_update', false);
1572 echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update disabled!', 'metasync') . '</p></div>';
1573 } elseif (isset($_POST['delete_sitemap'])) {
1574 $deleted = $sitemap_generator->delete_sitemap();
1575 if ($deleted) {
1576 update_option('metasync_sitemap_auto_update', false);
1577 echo '<div class="notice notice-success"><p>' . esc_html__('Sitemap deleted successfully!', 'metasync') . '</p></div>';
1578 } else {
1579 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>';
1580 }
1581 } elseif (isset($_POST['enable_other_sitemaps'])) {
1582 $enabled_plugins = $sitemap_generator->enable_other_sitemap_generators();
1583 if ($enabled_plugins) {
1584 echo '<div class="notice notice-success"><p>' . esc_html__('Other sitemap plugins have been re-enabled successfully!', 'metasync') . '</p></div>';
1585 } else {
1586 echo '<div class="notice notice-info"><p>' . esc_html__('No sitemap plugins were found to re-enable.', 'metasync') . '</p></div>';
1587 }
1588 }
1589 }
1590
1591 $sitemap_exists = $sitemap_generator->sitemap_exists();
1592 $sitemap_url = $sitemap_generator->get_sitemap_url();
1593 $url_count = $sitemap_generator->count_urls();
1594 $last_generated = $sitemap_generator->get_last_generated_time();
1595 $auto_update_enabled = get_option('metasync_sitemap_auto_update', false);
1596 $active_sitemap_plugins = $sitemap_generator->check_active_sitemap_plugins();
1597
1598 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-xml-sitemap.php';
1599 }
1600
1601 public function create_admin_custom_pages_page()
1602 {
1603 ?>
1604 <?php $this->admin->render_layout_open('Custom Pages', 'custom_pages', 'Create blank canvas pages for Elementor, Divi, or Gutenberg.'); ?>
1605
1606 <div class="metasync-page-content">
1607 <?php
1608 require_once plugin_dir_path(dirname(__FILE__)) . 'custom-pages/class-metasync-custom-pages-admin.php';
1609 Metasync_Custom_Pages_Admin::render_admin_page();
1610 ?>
1611 </div>
1612
1613 <?php $this->admin->render_layout_close(); ?>
1614 <?php
1615 }
1616
1617 public function create_admin_404_monitor_page()
1618 {
1619 require_once plugin_dir_path(dirname(__FILE__)) . '404-monitor/class-metasync-404-monitor-database.php';
1620 require_once plugin_dir_path(dirname(__FILE__)) . '404-monitor/class-metasync-404-monitor.php';
1621
1622 $db_404 = new Metasync_Error_Monitor_Database();
1623 $ErrorMonitor = new Metasync_Error_Monitor($db_404);
1624
1625 $this->admin->render_layout_open('404 Monitor', 'monitor_404', 'Track and manage 404 errors detected on your site.');
1626 $ErrorMonitor->create_admin_plugin_interface();
1627 $this->admin->render_layout_close();
1628 }
1629
1630 public function create_admin_search_engine_verification_page()
1631 {
1632 $page_slug = Metasync_Admin::$page_slug . '_searchengines-verification';
1633 ?>
1634 <?php $this->admin->render_layout_open('Site Verification', 'site_verification', 'Verify your site ownership with search engines and other platforms.'); ?>
1635
1636 <form method="post" action="options.php">
1637 <div class="dashboard-card">
1638 <h2>Search Engine Verification</h2>
1639 <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>
1640 <?php
1641 settings_fields(Metasync_Admin::option_group);
1642 do_settings_sections($page_slug);
1643 submit_button();
1644 ?>
1645 </div>
1646 </form>
1647 <?php $this->admin->render_layout_close(); ?>
1648 <?php
1649 }
1650
1651 public function create_admin_breadcrumbs_page()
1652 {
1653 $page_slug = Metasync_Admin::$page_slug . '_breadcrumbs';
1654 ?>
1655 <?php $this->admin->render_layout_open('Breadcrumbs', 'breadcrumbs', 'Configure breadcrumb trail settings and placement.'); ?>
1656
1657 <form method="post" action="options.php">
1658 <div class="dashboard-card">
1659 <h2>Breadcrumbs Settings</h2>
1660 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p>
1661 <?php
1662 settings_fields(Metasync_Admin::option_group);
1663 do_settings_sections($page_slug);
1664 submit_button();
1665 ?>
1666 </div>
1667 </form>
1668
1669 <div class="dashboard-card" style="margin-top: 20px;">
1670 <h2>📖 How to Display Breadcrumbs</h2>
1671 <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>
1672
1673 <h3 style="margin-top: 0;">Option 1 — Shortcode</h3>
1674 <p style="color: var(--dashboard-text-secondary);">Paste the shortcode inside any page, post, or widget:</p>
1675 <pre style="background: var(--dashboard-bg, #1e1e1e); color: var(--dashboard-text, #e0e0e0); padding: 12px 16px; border-radius: 6px; font-size: 13px; overflow-x: auto;">[metasync_breadcrumb]</pre>
1676
1677 <h3>Option 2 PHP Template Function</h3>
1678 <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>
1679 <pre style="background: var(--dashboard-bg, #1e1e1e); color: var(--dashboard-text, #e0e0e0); padding: 12px 16px; border-radius: 6px; font-size: 13px; overflow-x: auto;">&lt;?php metasync_breadcrumb(); ?&gt;</pre>
1680
1681 <h3>Option 3 PHP Return Value</h3>
1682 <p style="color: var(--dashboard-text-secondary);">Get the breadcrumb HTML as a string to manipulate before output:</p>
1683 <pre style="background: var(--dashboard-bg, #1e1e1e); color: var(--dashboard-text, #e0e0e0); padding: 12px 16px; border-radius: 6px; font-size: 13px; overflow-x: auto;">$breadcrumbs = new Metasync_Breadcrumbs();
1684 echo $breadcrumbs-&gt;render_breadcrumb_html();</pre>
1685
1686 <p style="color: var(--dashboard-text-secondary); margin-top: 16px; font-size: 12px;">
1687 <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>
1688 <code>echo $breadcrumbs-&gt;render_breadcrumb_html(['post_id' =&gt; 123]);</code>
1689 </p>
1690 </div>
1691 <?php $this->admin->render_layout_close(); ?>
1692 <?php
1693 }
1694
1695 public function create_admin_schema_markup_page()
1696 {
1697 $page_slug = Metasync_Admin::$page_slug . '_schema-markup';
1698 $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.');
1699 ?>
1700 <form method="post" action="options.php">
1701 <div class="dashboard-card">
1702 <?php
1703 settings_fields(Metasync_Admin::option_group);
1704 do_settings_sections($page_slug);
1705 submit_button('Save Changes');
1706 ?>
1707 </div>
1708 </form>
1709 <?php
1710 $this->admin->render_layout_close();
1711 }
1712
1713 public function create_admin_local_business_page()
1714 {
1715 $page_slug = Metasync_Admin::$page_slug . '_local-seo';
1716 ?>
1717 <?php $this->admin->render_layout_open('Local Business', 'local_business', 'Configure local business information for better local SEO.'); ?>
1718
1719 <form method="post" action="options.php">
1720 <div class="dashboard-card">
1721 <h2>🏢 Local Business Configuration</h2>
1722 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure your local business information for better local search engine optimization.</p>
1723 <?php
1724 settings_fields(Metasync_Admin::option_group);
1725 do_settings_sections($page_slug);
1726 submit_button();
1727 ?>
1728 </div>
1729 </form>
1730 <?php $this->admin->render_layout_close(); ?>
1731 <?php
1732 }
1733
1734 public function create_admin_code_snippets_page()
1735 {
1736 $page_slug = Metasync_Admin::$page_slug . '_code-snippets';
1737 ?>
1738 <?php $this->admin->render_layout_open('Code Snippets', 'code_snippets', 'Add custom code to your site header and footer.'); ?>
1739
1740 <form method="post" action="options.php">
1741 <div class="dashboard-card">
1742 <h2>Code Snippets Management</h2>
1743 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Add custom code snippets to enhance your site functionality and tracking capabilities.</p>
1744 <?php
1745 settings_fields(Metasync_Admin::option_group);
1746 do_settings_sections($page_slug);
1747 submit_button();
1748 ?>
1749 </div>
1750 </form>
1751 <?php $this->admin->render_layout_close(); ?>
1752 <?php
1753 }
1754
1755 public function create_admin_google_instant_index_page()
1756 {
1757 // Delegates to the admin class which uses the shared credentials view
1758 $this->admin->create_admin_google_instant_index_page();
1759 }
1760
1761 public function create_admin_google_console_page()
1762 {
1763 ?>
1764 <?php $this->admin->render_layout_open('Google Console', 'google_console', ''); ?>
1765
1766 <?php
1767 google_index_direct()->show_google_instant_indexing_console();
1768 ?>
1769 <?php $this->admin->render_layout_close(); ?>
1770 <?php
1771 }
1772
1773 public function create_admin_bing_console_page()
1774 {
1775 ?>
1776 <?php $this->admin->render_layout_open('Bing Console', 'bing_console', ''); ?>
1777
1778 <?php
1779 require_once plugin_dir_path(dirname(__FILE__)) . 'bing-index/class-metasync-bing-instant-index.php';
1780 $bing_instant_index = new Metasync_Bing_Instant_Index();
1781 $bing_instant_index->show_bing_instant_indexing_console();
1782 ?>
1783 <?php $this->admin->render_layout_close(); ?>
1784 <?php
1785 }
1786
1787 public function create_admin_optimal_settings_page()
1788 {
1789 ?>
1790 <?php $this->admin->render_layout_open('Optimal Settings', 'optimal_settings', 'Apply recommended SEO settings automatically.'); ?>
1791
1792 <div class="dashboard-card">
1793 <h2>Site Compatibility Status</h2>
1794 <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>
1795 <?php
1796 $optimal_settings = new Metasync_Optimal_Settings();
1797 $optimal_settings->site_compatible_status_view();
1798 ?>
1799 </div>
1800
1801 <div class="dashboard-card">
1802 <h2>Optimization Settings</h2>
1803 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure optimization settings for best performance.</p>
1804 <?php
1805 $this->optimization_settings_options();
1806 ?>
1807 </div>
1808 <?php $this->admin->render_layout_close(); ?>
1809 <?php
1810 }
1811
1812 public function create_admin_global_settings_page()
1813 {
1814 $page_slug = Metasync_Admin::$page_slug . '_common-settings';
1815 ?>
1816 <?php $this->admin->render_layout_open('Global Settings', 'general', ''); ?>
1817
1818 <form method="post" action="options.php">
1819 <div class="dashboard-card">
1820 <h2>Global Configuration</h2>
1821 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure global settings that apply across your entire site.</p>
1822 <?php
1823 settings_fields(Metasync_Admin::option_group);
1824 do_settings_sections($page_slug);
1825 ?>
1826 </div>
1827
1828 <!-- Save button removed - using floating notification system instead -->
1829 </form>
1830 <?php $this->admin->render_layout_close(); ?>
1831 <?php
1832 }
1833
1834 public function create_admin_common_meta_settings_page()
1835 {
1836 $page_slug = Metasync_Admin::$page_slug . '_common-meta-settings';
1837 ?>
1838 <?php $this->admin->render_layout_open('Common Meta Settings', 'general', ''); ?>
1839
1840 <form method="post" action="options.php">
1841 <div class="dashboard-card">
1842 <h2>Meta Configuration</h2>
1843 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure common meta tags and SEO settings for your site.</p>
1844 <?php
1845 settings_fields(Metasync_Admin::option_group);
1846 do_settings_sections($page_slug);
1847 ?>
1848 </div>
1849
1850 <!-- Save button removed - using floating notification system instead -->
1851 </form>
1852 <?php $this->admin->render_layout_close(); ?>
1853 <?php
1854 }
1855
1856 public function create_admin_social_meta_page()
1857 {
1858 $page_slug = Metasync_Admin::$page_slug . '_social-meta';
1859 ?>
1860 <?php $this->admin->render_layout_open('Social Meta', 'general', ''); ?>
1861
1862 <form method="post" action="options.php">
1863 <div class="dashboard-card">
1864 <h2>📲 Social Meta Tags</h2>
1865 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how your content appears when shared on social media platforms.</p>
1866 <?php
1867 settings_fields(Metasync_Admin::option_group);
1868 do_settings_sections($page_slug);
1869 ?>
1870 </div>
1871
1872 <!-- Save button removed - using floating notification system instead -->
1873 </form>
1874 <?php $this->admin->render_layout_close(); ?>
1875 <?php
1876 }
1877
1878 public function create_admin_seo_controls_page()
1879 {
1880 $page_slug = Metasync_Admin::$page_slug . '_seo-controls';
1881 ?>
1882 <?php $this->admin->render_layout_open('Indexation Control', 'seo_controls', 'Control how search engines index your content.'); ?>
1883
1884 <!-- Status Messages Container -->
1885 <div id="seo-controls-messages"></div>
1886
1887 <form method="post" action="options.php" id="metaSyncSeoControlsForm">
1888 <div class="dashboard-card">
1889 <div style="display: flex; justify-content: flex-end; margin-bottom: 20px;">
1890 <a href="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-import-external&tab=indexation')); ?>" class="button button-secondary">
1891 <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
1892 </a>
1893 </div>
1894 <?php
1895 settings_fields(Metasync_Admin::option_group);
1896 do_settings_sections($page_slug);
1897
1898 wp_nonce_field('meta_sync_seo_controls_nonce', 'meta_sync_seo_controls_nonce');
1899 ?>
1900 </div>
1901
1902 <!-- Save button removed - using floating notification system instead -->
1903 </form>
1904 <?php $this->admin->render_layout_close(); ?>
1905 <?php
1906 }
1907
1908 public function optimization_settings_options()
1909 {
1910 $page_slug = Metasync_Admin::$page_slug . '_optimal-settings';
1911 $site_info_slug = Metasync_Admin::$page_slug . '_site-info-settings';
1912
1913 printf('<form method="post" action="options.php">');
1914 settings_fields(Metasync_Admin::option_group);
1915 do_settings_sections($page_slug);
1916 do_settings_sections($site_info_slug);
1917 submit_button();
1918 printf('</form>');
1919 }
1920
1921 public function create_admin_error_logs_page()
1922 {
1923 ?>
1924 <?php $this->admin->render_layout_open('Error Logs', 'general', ''); ?>
1925
1926 <div class="dashboard-card">
1927 <h2>Error Logs Management</h2>
1928 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">View and manage system error logs to troubleshoot issues and monitor plugin performance.</p>
1929 <?php
1930 $error_logs = new Metasync_Error_Logs();
1931
1932 if ($error_logs->can_show_error_logs()) {
1933 $log_content = $error_logs->get_error_logs(50);
1934
1935 if (!empty(trim($log_content))) {
1936 $error_logs->show_copy_button();
1937 $error_logs->show_logs();
1938 $error_logs->show_info();
1939 } else {
1940 echo '<div class="dashboard-empty-state">';
1941 echo '<p style="color: var(--dashboard-text-secondary); font-style: italic; text-align: center; padding: 40px 20px;">�
1942 Log file is empty - no errors recorded.</p>';
1943 echo '</div>';
1944 }
1945 } else {
1946 $error_message = $error_logs->get_error_message();
1947 if (!empty($error_message)) {
1948 echo '<div class="dashboard-empty-state">';
1949 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;">';
1950 echo esc_html($error_message);
1951 echo '</p>';
1952 echo '</div>';
1953 } else {
1954 echo '<div class="dashboard-empty-state">';
1955 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>';
1956 echo '</div>';
1957 }
1958 }
1959 ?>
1960 </div>
1961 <?php $this->admin->render_layout_close(); ?>
1962 <?php
1963 }
1964
1965 public function create_admin_sync_log_page()
1966 {
1967 $sync_db = new Metasync_Sync_History_Database();
1968
1969 if (wp_doing_ajax()) {
1970 $this->handle_sync_log_ajax();
1971 return;
1972 }
1973
1974 $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
1975 $per_page = 10;
1976 $offset = ($page - 1) * $per_page;
1977
1978 $filters = [
1979 'date_range' => isset($_GET['date_range']) ? sanitize_text_field($_GET['date_range']) : '',
1980 'status' => isset($_GET['status']) ? sanitize_text_field($_GET['status']) : '',
1981 ];
1982
1983 $date_range = $filters['date_range'];
1984 $wp_now_ts = current_time('timestamp');
1985 $date_from = '';
1986 $date_to = '';
1987
1988 if (!empty($date_range)) {
1989 $date_to = date('Y-m-d H:i:s', $wp_now_ts);
1990
1991 if ($date_range === 'today') {
1992 $start_ts = strtotime('today', $wp_now_ts);
1993 $date_from = date('Y-m-d H:i:s', $start_ts);
1994 } elseif ($date_range === 'yesterday') {
1995 $start_ts = strtotime('yesterday', $wp_now_ts);
1996 $end_ts = strtotime('today', $wp_now_ts) - 1;
1997 $date_from = date('Y-m-d H:i:s', $start_ts);
1998 $date_to = date('Y-m-d H:i:s', $end_ts);
1999 } elseif ($date_range === 'this_week') {
2000 $start_of_week = (int) get_option('start_of_week', 1);
2001 $day_of_week = (int) date('w', $wp_now_ts);
2002 $delta_days = ($day_of_week - $start_of_week + 7) % 7;
2003 $start_ts = strtotime('-' . $delta_days . ' days', strtotime('today', $wp_now_ts));
2004 $date_from = date('Y-m-d H:i:s', $start_ts);
2005 } elseif ($date_range === 'this_month') {
2006 $start_ts = strtotime(date('Y-m-01 00:00:00', $wp_now_ts));
2007 $date_from = date('Y-m-d H:i:s', $start_ts);
2008 } elseif ($date_range === 'all') {
2009 // no bounds
2010 }
2011 }
2012
2013 if (!empty($date_from)) {
2014 $filters['date_from'] = $date_from;
2015 }
2016 if (!empty($date_to)) {
2017 $filters['date_to'] = $date_to;
2018 }
2019
2020 $filters = array_filter($filters);
2021
2022 $sync_records = $sync_db->getAllRecords($per_page, $offset, $filters);
2023 $total_records = $sync_db->get_count($filters);
2024 $total_pages = ceil($total_records / $per_page);
2025
2026 $stats = $sync_db->get_statistics();
2027
2028 ?>
2029 <?php $this->admin->render_layout_open('Changes Log', 'sync_log', 'Track all SEO changes made by the plugin.'); ?>
2030
2031 <div class="dashboard-card">
2032 <div class="sync-log-header">
2033 <div class="sync-log-title-section">
2034 <h2>Changes Log</h2>
2035 <p style="color: var(--dashboard-text-secondary); margin-bottom: 0;">Recent content synchronizations from external tools.</p>
2036 </div>
2037
2038 <!-- Filters - Right aligned -->
2039 <div class="sync-log-filters">
2040 <form method="get" class="sync-filters-form" onchange="this.submit()">
2041 <input type="hidden" name="page" value="<?php echo esc_attr($_GET['page']); ?>">
2042
2043 <select name="date_range" class="sync-filter-select">
2044 <option value="all" <?php selected($filters['date_range'] ?? 'all', 'all'); ?>> All Time</option>
2045 <option value="today" <?php selected($filters['date_range'] ?? '', 'today'); ?>>Today</option>
2046 <option value="yesterday" <?php selected($filters['date_range'] ?? '', 'yesterday'); ?>>Yesterday</option>
2047 <option value="this_week" <?php selected($filters['date_range'] ?? '', 'this_week'); ?>>This week</option>
2048 <option value="this_month" <?php selected($filters['date_range'] ?? '', 'this_month'); ?>>This month</option>
2049 </select>
2050
2051 <select name="status" class="sync-filter-select">
2052 <option value="" <?php selected($filters['status'] ?? '', ''); ?>>Status Filter</option>
2053 <option value="published" <?php selected($filters['status'] ?? '', 'published'); ?>>Published</option>
2054 <option value="draft" <?php selected($filters['status'] ?? '', 'draft'); ?>>Draft</option>
2055 </select>
2056 </form>
2057 </div>
2058 </div>
2059
2060 <!-- Sync History List -->
2061 <div class="sync-log-list">
2062 <?php if (empty($sync_records)): ?>
2063 <div class="sync-log-empty">
2064 <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>
2065 <h3>No sync records found</h3>
2066 <p>Sync records will appear here when content/pages receive new updates.</p>
2067 </div>
2068 <?php else: ?>
2069 <?php foreach ($sync_records as $record): ?>
2070 <div class="sync-log-item">
2071 <div class="sync-log-icon">
2072 <div class="sync-icon-circle">
2073 <span class="dashicons dashicons-media-default sync-icon"></span>
2074 </div>
2075 </div>
2076
2077 <div class="sync-log-content">
2078 <div class="sync-log-title"><?php echo esc_html($record->title); ?>
2079 <?php if (!empty($record->url)): ?>
2080 <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>
2081 <?php endif; ?>
2082 </div>
2083 <div class="sync-log-meta">
2084 <?php echo $this->time_elapsed_string($record->created_at); ?>
2085 </div>
2086 </div>
2087
2088 <div class="sync-log-status">
2089 <?php if ($record->status === 'published' || $record->status === 'publish'): ?>
2090 <span class="sync-status-badge sync-status-published">
2091 <span class="sync-status-icon"></span>
2092 Published
2093 </span>
2094 <?php else: ?>
2095 <span class="sync-status-badge sync-status-draft">
2096 <span class="sync-status-icon">i</span>
2097 Draft
2098 </span>
2099 <?php endif; ?>
2100 </div>
2101 </div>
2102 <?php endforeach; ?>
2103 <?php endif; ?>
2104 </div>
2105
2106 <!-- Pagination -->
2107 <?php if ($total_pages > 1): ?>
2108 <div class="sync-log-pagination">
2109 <div class="sync-log-pagination-info">
2110 Total records: <?php echo $total_records; ?> | Showing <?php echo $offset + 1; ?>-<?php echo min($offset + $per_page, $total_records); ?>
2111 </div>
2112
2113 <div class="sync-log-pagination-controls">
2114 <?php if ($page > 1): ?>
2115 <a href="?page=<?php echo esc_attr($_GET['page']); ?>&paged=<?php echo $page - 1; ?><?php echo $this->build_filter_query_string($filters); ?>" class="sync-pagination-btn"></a>
2116 <?php endif; ?>
2117
2118 <?php for ($i = max(1, $page - 2); $i <= min($total_pages, $page + 2); $i++): ?>
2119 <a href="?page=<?php echo esc_attr($_GET['page']); ?>&paged=<?php echo $i; ?><?php echo $this->build_filter_query_string($filters); ?>"
2120 class="sync-pagination-btn <?php echo $i === $page ? 'active' : ''; ?>"><?php echo $i; ?></a>
2121 <?php endfor; ?>
2122
2123 <?php if ($page < $total_pages): ?>
2124 <a href="?page=<?php echo esc_attr($_GET['page']); ?>&paged=<?php echo $page + 1; ?><?php echo $this->build_filter_query_string($filters); ?>" class="sync-pagination-btn"></a>
2125 <?php endif; ?>
2126 </div>
2127 </div>
2128 <?php endif; ?>
2129 </div>
2130 <?php $this->admin->render_layout_close(); ?>
2131 <?php
2132 }
2133
2134 public function creat_error_Logs_List()
2135 {
2136 // printf('<h1> Error Logs </h1>');
2137 // $error_log = new Metasync_Error_Logs_Table($this->data_error_log_list);
2138 // $error_log->create_admin_error_log_list_interface();
2139 }
2140
2141 public function create_admin_heartbeat_error_logs_page()
2142 {
2143 ?>
2144 <?php $this->admin->render_layout_open('Heartbeat Error Logs', 'general', ''); ?>
2145
2146 <div class="dashboard-card">
2147 <h2>💓 HeartBeat Error Logs</h2>
2148 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Monitor WordPress heartbeat errors to identify connectivity issues and system problems.</p>
2149 <?php
2150 $heartbeat_errors = new Metasync_HeartBeat_Error_Monitor($this->admin->db_heartbeat_errors);
2151 $heartbeat_errors->create_admin_heartbeat_errors_interface();
2152 ?>
2153 </div>
2154 <?php $this->admin->render_layout_close(); ?>
2155 <?php
2156 }
2157
2158 public function create_admin_bot_statistics_page()
2159 {
2160 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-otto-bot-statistics.php';
2161 }
2162
2163 // ------------------------------------------------------------------
2164 // Private helpers (copied from Metasync_Admin, used only by pages above)
2165 // ------------------------------------------------------------------
2166
2167 private function time_elapsed_string($datetime, $full = false)
2168 {
2169 if(empty($datetime)){
2170 return "";
2171 }
2172 $now = new DateTime;
2173 $ago = new DateTime($datetime);
2174
2175 $diff = $now->diff($ago);
2176
2177 $string = [
2178 'y' => 'year',
2179 'm' => 'month',
2180 'w' => 'week',
2181 'd' => 'day',
2182 'h' => 'hour',
2183 'i' => 'minute',
2184 's' => 'second',
2185 ];
2186
2187 foreach ($string as $k => &$v) {
2188 if (isset($diff->$k) && $diff->$k) {
2189 $v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : '');
2190 } else {
2191 unset($string[$k]);
2192 }
2193 }
2194 if (!$full) $string = array_slice($string, 0, 1);
2195 return $string ? implode(', ', $string) . ' ago' : 'just now';
2196 }
2197
2198 private function build_filter_query_string($filters)
2199 {
2200 $query_parts = [];
2201 foreach ($filters as $key => $value) {
2202 if (!empty($value)) {
2203 $query_parts[] = $key . '=' . urlencode($value);
2204 }
2205 }
2206 return !empty($query_parts) ? '&' . implode('&', $query_parts) : '';
2207 }
2208
2209 private function handle_sync_log_ajax()
2210 {
2211 wp_die();
2212 }
2213 }
2214