PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.25
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.25
2.7.1 2.7.2 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 All 141 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.25, at includes/class-metasync-admin-pages.php

1,932 lines 100.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 if (isset($_REQUEST['metasync_password_reset'])) {
222 Metasync_Admin_Ajax::instance()->render_password_reset_page();
223 return;
224 }
225
226 $active_tab = isset($_GET['tab']) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general';
227
228 $whitelabel_settings = Metasync::get_whitelabel_settings();
229
230 $user_password = $whitelabel_settings['settings_password'] ?? '';
231 $hide_settings_enabled = !empty($whitelabel_settings['hide_settings']);
232
233 $protected_tabs = [];
234
235 if (!empty($user_password)) {
236 $protected_tabs[] = 'whitelabel';
237 }
238
239 if ($hide_settings_enabled && !empty($user_password)) {
240 $protected_tabs = ['general', 'whitelabel', 'advanced'];
241 }
242
243 $password_protection_enabled = false;
244 $password_validated = false;
245 $password_error = '';
246
247 if (in_array($active_tab, $protected_tabs)) {
248
249 $password_protection_enabled = !empty($user_password);
250
251 $auth = new Metasync_Auth_Manager('whitelabel', 1800);
252 $password_validated = $auth->has_access();
253
254 if (isset($_POST['whitelabel_password_submit']) && !$password_validated) {
255 $password_error = 'Incorrect password. Please try again.';
256 }
257 }
258
259 $is_authenticated = $password_validated;
260
261 $whitelabel_otto_name = Metasync::get_whitelabel_otto_name();
262
263 $page_slug = Metasync_Admin::$page_slug;
264 ?>
265 <?php
266 $settings_current_page = 'general';
267 if ($active_tab === 'advanced') $settings_current_page = 'advanced_settings';
268 elseif ($active_tab === 'whitelabel') $settings_current_page = 'whitelabel';
269 $this->admin->render_layout_open('Settings', $settings_current_page, 'Manage all plugin settings and configuration.');
270 ?>
271 <?php
272 /*
273 # Temporarily commented out: Clear Cache notice and button (can be re-enabled later)
274
275 * <div class="notice notice-success">
276 * <p>
277 * <b>Clear all caches at once</b><br/>
278 * This will slow down your site until caches are rebuilt
279 * <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>
280 * </p>
281 * </div>
282 */
283 ?>
284
285 <?php /* Navigation now rendered in left sidenav via render_layout_open */ ?>
286
287 <?php
288 if (in_array($active_tab, $protected_tabs)) {
289
290 if ($password_protection_enabled && !$password_validated) {
291 ?>
292 <div class="dashboard-card" style="max-width: 500px; margin: 0 auto;">
293 <h2 style="text-align: center;">Protected Section</h2>
294 <p style="color: var(--dashboard-text-secondary); margin-bottom: 30px; text-align: center;">
295 <?php
296 $plugin_name = Metasync::get_effective_plugin_name('');
297 if ($hide_settings_enabled && !empty($user_password)) {
298 printf('Please enter the password to access the %s Settings section.', esc_html($plugin_name));
299 } else {
300 echo 'Please enter the password to access the Branding section.';
301 }
302 ?>
303 </p>
304
305 <?php if (!empty($password_error)): ?>
306 <div style="background: #f8d7da; color: #721c24; padding: 12px; border-radius: 6px; margin-bottom: 20px; border: 1px solid #f5c6cb;">
307 <strong>❌ Access Denied:</strong> <?php echo esc_html($password_error); ?>
308 </div>
309 <?php endif; ?>
310
311 <form method="post" action="" style="max-width: 400px; margin: 0 auto;">
312 <?php wp_nonce_field('whitelabel_password_nonce', 'whitelabel_nonce'); ?>
313
314 <div style="margin-bottom: 20px;">
315 <label for="whitelabel_password" style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--dashboard-text-primary);">
316 Validate Password
317 </label>
318 <input
319 type="password"
320 id="whitelabel_password"
321 name="whitelabel_password"
322 placeholder="Enter password to access protected settings"
323 style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; box-sizing: border-box;"
324 required
325 autocomplete="off"
326 />
327 </div>
328
329 <div style="text-align: center;">
330 <button
331 type="submit"
332 name="whitelabel_password_submit"
333 value="1"
334 class="button button-primary"
335 style="padding: 12px 24px; font-size: 14px; font-weight: 600;"
336 >
337 Submit Password
338 </button>
339 </div>
340 </form>
341
342 <?php if (current_user_can('manage_options')): ?>
343 <div style="text-align: center; margin-top: 20px;">
344 <a href="#" id="metasync-forgot-password-link" style="color: #2271b1; text-decoration: none; font-size: 14px;">
345 Forgot Password?
346 </a>
347 <div id="metasync-recovery-message" style="margin-top: 15px; padding: 12px; border-radius: 6px; display: none;"></div>
348 </div>
349 <?php endif; ?>
350
351 </div>
352
353 <?php
354 return;
355 } elseif (!$password_protection_enabled) {
356 ?>
357 <div class="notice notice-info" style="margin: 15px 0;">
358 <p>
359 <strong>Security Tip:</strong> You can set a custom password in the White Label Settings to protect this section.
360 </p>
361 </div>
362 <?php
363 }
364 }
365 ?>
366
367 <form method="post" action="options.php?tab=<?php echo esc_attr( $active_tab ); ?>" id="metaSyncGeneralSetting">
368 <input type="hidden" name="metasync_pw_reset_generation" value="<?php echo esc_attr((string) (int) get_option('metasync_pw_reset_generation', 0)); ?>" />
369 <?php
370 settings_fields(Metasync_Admin::option_group);
371
372 wp_nonce_field('meta_sync_general_setting_nonce', 'meta_sync_nonce');
373
374 if ($active_tab == 'general') {
375 ?>
376 <div class="dashboard-card">
377 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
378 <div style="flex: 1;">
379 <h2 style="margin: 0;">General Configuration</h2>
380 <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>
381 </div>
382 <?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?>
383 <div>
384 <?php Metasync_Compatibility_Checker::instance()->render_lock_button('general'); ?>
385 </div>
386 <?php endif; ?>
387 </div>
388
389 <?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?>
390 <div style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 12px; border-radius: 4px; margin-bottom: 20px;">
391 <strong>�
392 Access Granted:</strong> You have successfully authenticated and can now modify settings.
393 </div>
394 <?php endif; ?>
395
396 <?php
397 $this->admin->render_accordion_sections(Metasync_Admin::$page_slug . '_general');
398 ?>
399 </div>
400
401 <div class="dashboard-card">
402 <h2>Content Genius Synchronization<?php Metasync::render_tooltip_icon('cg_sync_section', sprintf('Syncing sends this site\'s categories and authors to %s, so Content Genius can assign the right category and author when publishing articles to this site.', Metasync::get_effective_plugin_name())); ?></h2>
403 <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>
404 <button type="button" class="button button-primary" id="sendAuthToken">
405 Sync Now
406 </button>
407 </div>
408
409 <div class="dashboard-card">
410 <h2>Setup Wizard</h2>
411 <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>
412 <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;">
413 Start Setup Wizard
414 </a>
415 </div>
416 <?php
417 } elseif ($active_tab == 'whitelabel') {
418 $whitelabel_settings = Metasync::get_whitelabel_settings();
419 $user_password = $whitelabel_settings['settings_password'] ?? '';
420 $password_protection_enabled = !empty($user_password);
421 ?>
422 <div class="dashboard-card">
423 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
424 <div>
425 <h2>White Label Branding<?php Metasync::render_tooltip_icon('wl_branding_section', 'Replace this plugin\'s name, logo, and links with your own so clients see your brand instead of Search Atlas. Set a password below to stop clients from changing it back.'); ?></h2>
426 <p style="color: var(--dashboard-text-secondary); margin: 5px 0 0 0;">Customize the plugin appearance with your own branding and logo.</p>
427 </div>
428 <?php if ($password_protection_enabled && $is_authenticated): ?>
429 <div>
430 <?php Metasync_Compatibility_Checker::instance()->render_lock_button('whitelabel'); ?>
431 <?php Metasync::render_tooltip_icon('wl_lock_section', 'Re-lock this section now. You will need the password to open it again.'); ?>
432 </div>
433 <?php endif; ?>
434 </div>
435
436 <?php if ($password_protection_enabled && $is_authenticated): ?>
437 <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;">
438 <div>
439 <strong>�
440 Access Granted:</strong> You have successfully authenticated and can now modify white label settings.
441 </div>
442 </div>
443
444 <?php endif; ?>
445
446 <?php
447 do_settings_sections(Metasync_Admin::$page_slug . '_branding');
448 ?>
449 </div>
450
451 <!-- Color Palette Customization section -->
452 <div class="dashboard-card" id="metasync-color-palette-section">
453 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
454 <div>
455 <h2>Color Palette<?php Metasync::render_tooltip_icon('wl_color_palette', 'These colors control this plugin\'s admin dashboard theme only. Changes apply immediately here and never affect what your site\'s visitors see on the frontend.'); ?></h2>
456 <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>
457 </div>
458 </div>
459
460 <?php
461 $color_palette = isset($whitelabel_settings['color_palette']) && is_array($whitelabel_settings['color_palette'])
462 ? $whitelabel_settings['color_palette']
463 : array();
464 $dark_colors = isset($color_palette['dark']) && is_array($color_palette['dark']) ? $color_palette['dark'] : array();
465 $light_colors = isset($color_palette['light']) && is_array($color_palette['light']) ? $color_palette['light'] : array();
466
467 $palette_fields = array(
468 'dashboard-bg' => array(
469 'label' => 'Background',
470 'hint' => 'Main page background behind all content',
471 'dark' => '#0f1419',
472 'light' => '#f8f9fa',
473 ),
474 'dashboard-card-bg' => array(
475 'label' => 'Card Background',
476 'hint' => 'Background of cards, panels, and content boxes',
477 'dark' => '#1a1f26',
478 'light' => '#ffffff',
479 ),
480 'dashboard-card-hover' => array(
481 'label' => 'Card Hover',
482 'hint' => 'Card background when you hover over it',
483 'dark' => '#222831',
484 'light' => '#f1f3f5',
485 ),
486 'dashboard-text-primary' => array(
487 'label' => 'Primary Text',
488 'hint' => 'Headings, body text, and main content',
489 'dark' => '#ffffff',
490 'light' => '#1a1f26',
491 ),
492 'dashboard-text-secondary' => array(
493 'label' => 'Secondary Text',
494 'hint' => 'Descriptions, hints, and less important text',
495 'dark' => '#9ca3af',
496 'light' => '#6b7280',
497 ),
498 'dashboard-accent' => array(
499 'label' => 'Accent Color',
500 'hint' => 'Buttons, links, and highlighted elements',
501 'dark' => '#3b82f6',
502 'light' => '#3b82f6',
503 ),
504 'dashboard-accent-hover' => array(
505 'label' => 'Accent Hover',
506 'hint' => 'Buttons and links when hovered',
507 'dark' => '#2563eb',
508 'light' => '#2563eb',
509 ),
510 'dashboard-success' => array(
511 'label' => 'Success',
512 'hint' => 'Success messages, connected status, positive indicators',
513 'dark' => '#10b981',
514 'light' => '#10b981',
515 ),
516 'dashboard-warning' => array(
517 'label' => 'Warning',
518 'hint' => 'Warning alerts and caution indicators',
519 'dark' => '#f59e0b',
520 'light' => '#f59e0b',
521 ),
522 'dashboard-error' => array(
523 'label' => 'Error',
524 'hint' => 'Error messages and critical alerts',
525 'dark' => '#ef4444',
526 'light' => '#ef4444',
527 ),
528 'dashboard-border' => array(
529 'label' => 'Border',
530 'hint' => 'Card borders, dividers, and separators',
531 'dark' => '#374151',
532 'light' => '#e5e7eb',
533 ),
534 'dashboard-gradient-primary-from' => array(
535 'label' => 'Button Gradient Start',
536 'hint' => 'Left/top color of buttons, save bars, and active tabs',
537 'dark' => '#667eea',
538 'light' => '#667eea',
539 ),
540 'dashboard-gradient-primary-to' => array(
541 'label' => 'Button Gradient End',
542 'hint' => 'Right/bottom color of buttons, save bars, and active tabs',
543 'dark' => '#764ba2',
544 'light' => '#764ba2',
545 ),
546 'dashboard-gradient-accent-from' => array(
547 'label' => 'Accent Gradient Start',
548 'hint' => 'Left/top color of secondary badges and highlight bars',
549 'dark' => '#f093fb',
550 'light' => '#f093fb',
551 ),
552 'dashboard-gradient-accent-to' => array(
553 'label' => 'Accent Gradient End',
554 'hint' => 'Right/bottom color of secondary badges and highlight bars',
555 'dark' => '#f5576c',
556 'light' => '#f5576c',
557 ),
558 );
559 ?>
560
561 <!-- Theme tabs -->
562 <div style="display: flex; gap: 8px; margin-bottom: 20px;">
563 <button type="button" class="metasync-palette-tab active" data-palette="dark"
564 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;">
565 &#9790; Dark Theme
566 </button>
567 <button type="button" class="metasync-palette-tab" data-palette="light"
568 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;">
569 &#9728; Light Theme
570 </button>
571 </div>
572
573 <!-- Dark theme palette -->
574 <div class="metasync-palette-panel active" id="metasync-palette-dark">
575 <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px;">
576 <?php foreach ($palette_fields as $var_name => $field): ?>
577 <div style="background: var(--dashboard-card-hover); border: 1px solid var(--dashboard-border); border-radius: 10px; padding: 14px;">
578 <label style="display: block; font-weight: 600; font-size: 13px; color: var(--dashboard-text-primary); margin-bottom: 2px;">
579 <?php echo esc_html($field['label']); ?>
580 </label>
581 <p style="font-size: 12px; color: var(--dashboard-text-secondary); margin: 0 0 10px 0;"><?php echo esc_html($field['hint']); ?></p>
582 <div style="display: flex; align-items: center; gap: 10px;">
583 <input type="color"
584 class="metasync-color-field"
585 name="<?php echo esc_attr(Metasync_Admin::option_key); ?>[whitelabel][color_palette][dark][<?php echo esc_attr($var_name); ?>]"
586 value="<?php echo esc_attr(!empty($dark_colors[$var_name]) ? $dark_colors[$var_name] : $field['dark']); ?>"
587 data-default-color="<?php echo esc_attr($field['dark']); ?>"
588 data-css-var="<?php echo esc_attr($var_name); ?>"
589 data-color-theme="dark"
590 style="width: 44px; height: 36px; padding: 2px; border: 1px solid var(--dashboard-border); border-radius: 8px; cursor: pointer; background: transparent;"
591 />
592 <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>
593 </div>
594 </div>
595 <?php endforeach; ?>
596 </div>
597 <div style="margin-top: 16px; text-align: right;">
598 <button type="button" id="metasync-reset-dark-palette" data-reset-theme="dark"
599 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;">
600 Reset Dark Theme to Defaults
601 </button>
602 </div>
603 </div>
604
605 <!-- Light theme palette -->
606 <div class="metasync-palette-panel" id="metasync-palette-light">
607 <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px;">
608 <?php foreach ($palette_fields as $var_name => $field): ?>
609 <div style="background: var(--dashboard-card-hover); border: 1px solid var(--dashboard-border); border-radius: 10px; padding: 14px;">
610 <label style="display: block; font-weight: 600; font-size: 13px; color: var(--dashboard-text-primary); margin-bottom: 2px;">
611 <?php echo esc_html($field['label']); ?>
612 </label>
613 <p style="font-size: 12px; color: var(--dashboard-text-secondary); margin: 0 0 10px 0;"><?php echo esc_html($field['hint']); ?></p>
614 <div style="display: flex; align-items: center; gap: 10px;">
615 <input type="color"
616 class="metasync-color-field"
617 name="<?php echo esc_attr(Metasync_Admin::option_key); ?>[whitelabel][color_palette][light][<?php echo esc_attr($var_name); ?>]"
618 value="<?php echo esc_attr(!empty($light_colors[$var_name]) ? $light_colors[$var_name] : $field['light']); ?>"
619 data-default-color="<?php echo esc_attr($field['light']); ?>"
620 data-css-var="<?php echo esc_attr($var_name); ?>"
621 data-color-theme="light"
622 style="width: 44px; height: 36px; padding: 2px; border: 1px solid var(--dashboard-border); border-radius: 8px; cursor: pointer; background: transparent;"
623 />
624 <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>
625 </div>
626 </div>
627 <?php endforeach; ?>
628 </div>
629 <div style="margin-top: 16px; text-align: right;">
630 <button type="button" id="metasync-reset-light-palette" data-reset-theme="light"
631 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;">
632 Reset Light Theme to Defaults
633 </button>
634 </div>
635 </div>
636 </div>
637
638 <style>
639 .metasync-palette-tab.active {
640 background: var(--dashboard-gradient-primary) !important;
641 color: #fff !important;
642 }
643 .metasync-palette-tab:not(.active) {
644 background: transparent !important;
645 color: var(--dashboard-text-secondary) !important;
646 }
647 .metasync-palette-panel { display: none !important; }
648 .metasync-palette-panel.active { display: block !important; }
649 </style>
650
651 <script>
652 jQuery(function($) {
653 // Tab switching
654 $(document).on('click', '.metasync-palette-tab', function(e) {
655 e.preventDefault();
656 e.stopPropagation();
657 var target = $(this).data('palette');
658 $('.metasync-palette-tab').removeClass('active');
659 $(this).addClass('active');
660 $('.metasync-palette-panel').removeClass('active');
661 $('#metasync-palette-' + target).addClass('active');
662 });
663
664 // Reset to defaults
665 var paletteDefaults = <?php echo wp_json_encode(array(
666 'dark' => array_combine(
667 array_keys($palette_fields),
668 array_column($palette_fields, 'dark')
669 ),
670 'light' => array_combine(
671 array_keys($palette_fields),
672 array_column($palette_fields, 'light')
673 )
674 )); ?>;
675
676 $(document).on('click', '#metasync-reset-dark-palette, #metasync-reset-light-palette', function(e) {
677 e.preventDefault();
678 e.stopPropagation();
679 var theme = $(this).data('reset-theme');
680 var defs = paletteDefaults[theme];
681 if (!defs) return;
682 $('#metasync-color-palette-section .metasync-color-field[data-color-theme="' + theme + '"]').each(function() {
683 var $input = $(this);
684 var varName = $input.data('css-var');
685 var defVal = defs[varName] || '';
686 if (defVal) {
687 $input.val(defVal);
688 $input.siblings('.metasync-color-hex-display').text(defVal);
689 }
690 });
691 // Trigger live preview if external JS is loaded
692 $input = $('#metasync-color-palette-section .metasync-color-field').first();
693 if ($input.length) $input.trigger('input');
694 });
695 });
696 </script>
697
698 <!-- Export Whitelabel Settings section -->
699 <div class="dashboard-card">
700 <h2>Export Whitelabel Plugin<?php Metasync::render_tooltip_icon('wl_export_plugin', 'Downloads a copy of this plugin with your branding already baked in, ready to upload and install on a client\'s site. All your white-label settings are applied automatically on activation.'); ?></h2>
701 <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>
702 <button type="button" class="button button-primary" id="metasync-export-whitelabel-btn" style="padding: 10px 20px; font-size: 14px; font-weight: 600;">
703 Export Plugin with Whitelabel Settings
704 </button>
705 <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>
706 </div>
707
708 <!-- Advanced Access Control section -->
709 <?php Metasync_Access_Control_UI::render_access_control_table(); ?>
710
711 <!-- Custom Modal for Alerts -->
712 <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);">
713 <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;">
714 <div style="background: white; border-radius: 10px; padding: 0; overflow: hidden;">
715 <!-- Header -->
716 <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 24px; text-align: center;">
717 <div id="modal-icon" style="font-size: 48px; margin-bottom: 12px; color: #fff;">&#9888;</div>
718 <h2 id="modal-title" style="color: white; margin: 0; font-size: 24px; font-weight: 600;">Password Required</h2>
719 </div>
720
721 <!-- Body -->
722 <div style="padding: 32px 24px;">
723 <p id="modal-message" style="color: #4a5568; font-size: 15px; line-height: 1.6; text-align: center; margin: 0;">
724 You must set a White Label Settings Password before enabling "Hide Settings".
725 </p>
726 </div>
727
728 <!-- Footer -->
729 <div style="padding: 0 24px 24px; text-align: center;">
730 <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;">
731 Got It
732 </button>
733 </div>
734 </div>
735 </div>
736 </div>
737
738 <style>
739 @keyframes modalSlideIn {
740 from {
741 opacity: 0;
742 transform: translateY(-50px);
743 }
744 to {
745 opacity: 1;
746 transform: translateY(0);
747 }
748 }
749
750 #modal-close-btn:hover {
751 transform: translateY(-2px);
752 box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
753 }
754 </style>
755
756 <script>
757 jQuery(document).ready(function($) {
758 var isShowingModal = false;
759
760 function showModal(icon, title, message) {
761 isShowingModal = true;
762 $('#modal-icon').text(icon);
763 $('#modal-title').text(title);
764 $('#modal-message').html(message);
765 $('#metasync-custom-modal').fadeIn(200);
766
767 setTimeout(function() {
768 isShowingModal = false;
769 }, 300);
770 }
771
772 $('#modal-close-btn').on('click', function(e) {
773 e.preventDefault();
774 e.stopPropagation();
775 $('#metasync-custom-modal').fadeOut(200);
776 return false;
777 });
778
779 $('#metasync-custom-modal').on('click', function(e) {
780 if (e.target === this) {
781 $(this).fadeOut(200);
782 }
783 });
784
785 function hasPassword() {
786 var passwordField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]');
787 var passwordValue = passwordField.val();
788 return passwordValue && passwordValue.length > 0;
789 }
790
791 function hasRecoveryEmail() {
792 var recoveryEmailField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][recovery_email]"]');
793 var emailValue = recoveryEmailField.val();
794 return emailValue && emailValue.length > 0 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailValue);
795 }
796
797 var passwordField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]');
798 var recoveryEmailField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][recovery_email]"]');
799
800 passwordField.on('blur', function() {
801 if (hasPassword() && !hasRecoveryEmail()) {
802 showModal(
803 '📧',
804 'Recovery Email Required',
805 '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.'
806 );
807 recoveryEmailField.focus();
808 }
809 });
810
811 passwordField.on('input', function() {
812 if (hasPassword()) {
813 recoveryEmailField.attr('required', true);
814 recoveryEmailField.closest('tr').find('th').addClass('required-field');
815 } else {
816 recoveryEmailField.removeAttr('required');
817 recoveryEmailField.closest('tr').find('th').removeClass('required-field');
818 }
819 });
820
821 passwordField.trigger('input');
822
823 var hideSettingsCheckbox = $('#checkbox_hide_settings');
824 var originalCheckboxState = hideSettingsCheckbox.is(':checked');
825
826 hideSettingsCheckbox.on('click', function(e) {
827 if (!originalCheckboxState && !hasPassword()) {
828 e.preventDefault();
829 e.stopImmediatePropagation();
830
831 showModal(
832 '&#9888;',
833 'Password Required',
834 '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.'
835 );
836
837 setTimeout(function() {
838 $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]').focus();
839 }, 300);
840
841 return false;
842 }
843
844 setTimeout(function() {
845 originalCheckboxState = hideSettingsCheckbox.is(':checked');
846 }, 0);
847 });
848
849 passwordField.on('keydown', function(e) {
850 var currentValue = $(this).val();
851
852 if (hideSettingsCheckbox.is(':checked') &&
853 (currentValue.length <= 1 || !currentValue) &&
854 (e.keyCode === 8 || e.keyCode === 46)) {
855
856 e.preventDefault();
857 e.stopImmediatePropagation();
858
859 if (!isShowingModal) {
860 showModal(
861 '&#9888;',
862 'Cannot Remove Password',
863 '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.'
864 );
865 }
866
867 return false;
868 }
869 });
870
871 $(document).on('metasync_settings_saved', function() {
872 });
873
874 $(document).on('keydown', function(e) {
875 if (e.key === 'Escape') {
876 $('#metasync-custom-modal').fadeOut(200);
877 }
878 });
879
880 });
881 </script>
882 <?php
883 } elseif ($active_tab == 'advanced') {
884 if (!Metasync_Access_Control::user_can_access('hide_advanced')) {
885 echo '<div class="dashboard-card" style="background: var(--dashboard-card-bg); padding: 25px; border-radius: 8px; text-align: center;">';
886 echo '<h2 style="color: var(--dashboard-text-primary);">Access Denied</h2>';
887 echo '<p style="color: var(--dashboard-text-secondary);">You do not have permission to access this page.</p>';
888 echo '</div>';
889 } else {
890 ?>
891 <div class="dashboard-card">
892 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
893 <div style="flex: 1;">
894 <h2 style="margin: 0;">🧰 Advanced Settings</h2>
895 <p style="color: var(--dashboard-text-secondary); margin: 5px 0 0 0;">Technical utilities for troubleshooting and connectivity checks.</p>
896 </div>
897 <?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?>
898 <div>
899 <?php Metasync_Compatibility_Checker::instance()->render_lock_button('advanced'); ?>
900 </div>
901 <?php endif; ?>
902 </div>
903
904 <?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?>
905 <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;">
906 <strong>�
907 Access Granted:</strong> You have successfully authenticated and can now modify settings.
908 </div>
909 <?php endif; ?>
910
911 <?php
912 if (isset($_GET['debug_mode_enabled']) && $_GET['debug_mode_enabled'] == '1'):
913 $indefinite = isset($_GET['indefinite']) && $_GET['indefinite'] == '1';
914 ?>
915 <div class="notice notice-success inline" style="margin-bottom: 20px;">
916 <p>
917 <strong>�
918 Success!</strong> Debug mode has been enabled<?php echo $indefinite ? ' indefinitely' : ' for 24 hours'; ?>.
919 </p>
920 </div>
921 <?php endif; ?>
922
923 <?php if (isset($_GET['debug_mode_disabled']) && $_GET['debug_mode_disabled'] == '1'): ?>
924 <div class="notice notice-info inline" style="margin-bottom: 20px;">
925 <p>
926 <strong>ℹ️ Debug Mode Disabled:</strong> Debug mode has been successfully disabled.
927 </p>
928 </div>
929 <?php endif; ?>
930
931 <?php if (isset($_GET['debug_mode_extended']) && $_GET['debug_mode_extended'] == '1'): ?>
932 <div class="notice notice-success inline" style="margin-bottom: 20px;">
933 <p>
934 <strong>�
935 Extended!</strong> Debug mode has been extended for another 24 hours.
936 </p>
937 </div>
938 <?php endif; ?>
939
940 <?php if (isset($_GET['debug_error']) && $_GET['debug_error'] == '1'): ?>
941 <div class="notice notice-error inline" style="margin-bottom: 20px;">
942 <p>
943 <strong>❌ Error:</strong> Unable to perform the debug mode operation. Please try again.
944 </p>
945 </div>
946 <?php endif; ?>
947
948 <?php
949 $this->admin->render_advanced_accordion();
950 ?>
951 </div>
952
953 <?php
954 }
955 }
956 ?>
957
958 <!-- Save button removed - using floating notification system instead -->
959
960 </form>
961
962 <!-- Lock Section Button Handler (runs on all tabs) -->
963 <script>
964 jQuery(document).ready(function($) {
965 var ajaxUrl = (typeof window.ajaxurl !== 'undefined' && window.ajaxurl)
966 ? window.ajaxurl
967 : '<?php echo esc_js(admin_url('admin-ajax.php')); ?>';
968 $('.metasync-lock-btn').on('click', function(e) {
969 e.preventDefault();
970 e.stopPropagation();
971
972 var currentTab = $(this).data('tab');
973
974 var logoutForm = $('<form>', {
975 'method': 'post',
976 'action': ''
977 });
978
979 logoutForm.append('<?php echo wp_nonce_field("whitelabel_logout_nonce", "whitelabel_logout_nonce", true, false); ?>');
980
981 logoutForm.append($('<input>', {
982 'type': 'hidden',
983 'name': 'whitelabel_logout',
984 'value': '1'
985 }));
986
987 $('body').append(logoutForm);
988 logoutForm.submit();
989
990 return false;
991 });
992 });
993 </script>
994
995 <!-- Host Blocking-generated JavaScript -->
996 <script>
997 jQuery(document).ready(function($) {
998 var ajaxUrl = (typeof window.ajaxurl !== 'undefined' && window.ajaxurl)
999 ? window.ajaxurl
1000 : '<?php echo esc_js(admin_url('admin-ajax.php')); ?>';
1001
1002 $(document).on('click', '#test-get-request', function(e) {
1003 e.preventDefault();
1004 e.stopPropagation();
1005 runHostTest('GET');
1006 return false;
1007 });
1008
1009 $(document).on('click', '#test-post-request', function(e) {
1010 e.preventDefault();
1011 e.stopPropagation();
1012 runHostTest('POST');
1013 return false;
1014 });
1015
1016 $(document).on('click', '#test-both-requests', function(e) {
1017 e.preventDefault();
1018 e.stopPropagation();
1019 runHostTest('BOTH');
1020 return false;
1021 });
1022
1023 function runHostTest(method) {
1024 var buttonId = (method === 'BOTH' ? 'test-both-requests' : 'test-' + method.toLowerCase() + '-request');
1025 var $button = $('#' + buttonId);
1026
1027 if ($button.length === 0) {
1028 alert('Error: Test button not found. Please refresh the page.');
1029 return;
1030 }
1031
1032 var originalText = $button.text();
1033
1034 $button.prop('disabled', true);
1035 $button.text('Testing...');
1036
1037 var $resultsDiv = $('#host-test-results');
1038 var $resultsContent = $('#test-results-content');
1039 $resultsDiv.show();
1040 $resultsContent.html('<div class="notice notice-info"><p>Running ' + (method === 'BOTH' ? 'GET and POST' : method) + ' test(s)...</p></div>');
1041
1042 var testsToRun = (method === 'BOTH') ? ['GET', 'POST'] : [method];
1043 var completedTests = 0;
1044 var allResults = [];
1045
1046 testsToRun.forEach(function(testMethod) {
1047 var action = 'metasync_test_host_blocking_' + testMethod.toLowerCase();
1048
1049 $.ajax({
1050 url: ajaxUrl,
1051 type: 'POST',
1052 dataType: 'json',
1053 data: { action: action, nonce: '<?php echo wp_create_nonce('metasync_nonce'); ?>' },
1054 timeout: 35000,
1055 success: function(response, textStatus, xhr) {
1056 try {
1057 if (response && response.success && response.data) {
1058 allResults.push(response.data);
1059 } else {
1060 allResults.push({
1061 method: testMethod,
1062 status: 'error',
1063 error: (response && response.data) ? response.data : 'Unexpected response',
1064 blocked: true,
1065 details: 'Received non-success response from server.'
1066 });
1067 }
1068 } catch (e) {
1069 allResults.push({
1070 method: testMethod,
1071 status: 'error',
1072 error: 'Response parse error: ' + (e && e.message ? e.message : e),
1073 blocked: true
1074 });
1075 }
1076 finalizeOne();
1077 },
1078 error: function(xhr, status, error) {
1079 var payload = (xhr && xhr.responseText) ? xhr.responseText.substring(0, 500) : '';
1080 allResults.push({
1081 method: testMethod,
1082 status: 'error',
1083 error: 'AJAX failed: ' + error + (payload ? ' — ' + payload : ''),
1084 blocked: true,
1085 details: 'Request did not complete successfully. Status: ' + status
1086 });
1087 finalizeOne();
1088 }
1089 });
1090 });
1091
1092 function finalizeOne() {
1093 completedTests++;
1094 if (completedTests === testsToRun.length) {
1095 displayResults(allResults);
1096 resetButtons();
1097 var $container = $('#host-test-results');
1098 if ($container && $container[0] && $container[0].scrollIntoView) {
1099 $container[0].scrollIntoView({ behavior: 'smooth', block: 'start' });
1100 }
1101 }
1102 }
1103 }
1104
1105 function resetButtons() {
1106 $('#test-get-request, #test-post-request, #test-both-requests').prop('disabled', false);
1107 $('#test-get-request').text('Test GET Request');
1108 $('#test-post-request').text('Test POST Request');
1109 $('#test-both-requests').text('Test Both Requests');
1110 }
1111
1112 function displayResults(results) {
1113 var html = '';
1114
1115 results.forEach(function(result) {
1116 var statusClass = result.status === 'success' ? 'success' : 'error';
1117 var statusIcon = result.status === 'success' ? '�
1118 ' : '❌';
1119 var blockedStatus = result.blocked ? 'BLOCKED' : 'ALLOWED';
1120 var blockedClass = result.blocked ? 'blocked' : 'allowed';
1121
1122 html += '<div class="test-result-item ' + statusClass + '">';
1123 html += '<div class="test-result-header">';
1124 html += '<h4>' + statusIcon + ' ' + result.method + ' Request - ' + blockedStatus + '</h4>';
1125 html += '<span class="test-status ' + blockedClass + '">' + blockedStatus + '</span>';
1126 html += '</div>';
1127
1128 html += '<div class="test-result-details">';
1129 html += '<p><strong>Response Time:</strong> ' + result.response_time + '</p>';
1130 html += '<p><strong>Status:</strong> ' + result.status + '</p>';
1131
1132 if (result.status_code) {
1133 html += '<p><strong>HTTP Status Code:</strong> <span class="status-code">' + result.status_code + '</span></p>';
1134 }
1135
1136 if (result.error) {
1137 html += '<p><strong>Error:</strong> <span class="error-message">' + result.error + '</span></p>';
1138 }
1139
1140 if (result.body) {
1141 html += '<p><strong>Response Body:</strong></p>';
1142 html += '<pre class="response-body">' + escapeHtml(result.body) + '</pre>';
1143 }
1144
1145 if (result.headers && Object.keys(result.headers).length > 0) {
1146 html += '<p><strong>Response Headers:</strong></p>';
1147 html += '<pre class="response-headers">';
1148 Object.keys(result.headers).forEach(function(key) {
1149 html += key + ': ' + result.headers[key] + '\n';
1150 });
1151 html += '</pre>';
1152 }
1153
1154 if (result.sent_data) {
1155 html += '<p><strong>Sent Data:</strong></p>';
1156 html += '<pre class="sent-data">' + escapeHtml(JSON.stringify(result.sent_data, null, 2)) + '</pre>';
1157 }
1158
1159 if (result.parsed_response) {
1160 html += '<p><strong>Parsed Response:</strong></p>';
1161 html += '<pre class="parsed-response">' + escapeHtml(JSON.stringify(result.parsed_response, null, 2)) + '</pre>';
1162 }
1163
1164 html += '<p><strong>Details:</strong> ' + result.details + '</p>';
1165 html += '</div>';
1166 html += '</div>';
1167 });
1168
1169 $('#test-results-content').html(html);
1170 $('#host-test-results').show();
1171 }
1172
1173 function escapeHtml(text) {
1174 var map = {
1175 '&': '&amp;',
1176 '<': '&lt;',
1177 '>': '&gt;',
1178 '"': '&quot;',
1179 '\'': '&#039;'
1180 };
1181 return text.replace(/[&<>"']/g, function(m) { return map[m]; });
1182 }
1183 });
1184 </script>
1185
1186 <!-- Host Blocking Test CSS -->
1187 <style>
1188 .test-result-item {
1189 background: #fff;
1190 border: 1px solid #ddd;
1191 border-radius: 8px;
1192 margin-bottom: 20px;
1193 overflow: hidden;
1194 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1195 }
1196
1197 .test-result-item.success {
1198 border-left: 4px solid #28a745;
1199 }
1200
1201 .test-result-item.error {
1202 border-left: 4px solid #dc3545;
1203 }
1204
1205 .test-result-header {
1206 background: #f8f9fa;
1207 padding: 15px 20px;
1208 border-bottom: 1px solid #dee2e6;
1209 display: flex;
1210 justify-content: space-between;
1211 align-items: center;
1212 }
1213
1214 .test-result-header h4 {
1215 margin: 0;
1216 color: #495057;
1217 font-size: 16px;
1218 }
1219
1220 .test-status {
1221 padding: 4px 12px;
1222 border-radius: 20px;
1223 font-size: 12px;
1224 font-weight: bold;
1225 text-transform: uppercase;
1226 }
1227
1228 .test-status.allowed {
1229 background: #d4edda;
1230 color: #155724;
1231 }
1232
1233 .test-status.blocked {
1234 background: #f8d7da;
1235 color: #721c24;
1236 }
1237
1238 .test-result-details {
1239 padding: 20px;
1240 }
1241
1242 .test-result-details p {
1243 margin: 8px 0;
1244 color: #495057;
1245 }
1246
1247 .test-result-details code {
1248 background: #f8f9fa;
1249 padding: 2px 6px;
1250 border-radius: 3px;
1251 font-family: 'Courier New', monospace;
1252 color: #e83e8c;
1253 }
1254
1255 .status-code {
1256 font-weight: bold;
1257 color: #007cba;
1258 }
1259
1260 .error-message {
1261 color: #dc3545;
1262 font-weight: bold;
1263 }
1264
1265 .response-body, .response-headers, .sent-data, .parsed-response {
1266 background: #f8f9fa;
1267 border: 1px solid #dee2e6;
1268 border-radius: 4px;
1269 padding: 12px;
1270 margin: 8px 0;
1271 font-family: 'Courier New', monospace;
1272 font-size: 12px;
1273 line-height: 1.4;
1274 max-height: 200px;
1275 overflow-y: auto;
1276 white-space: pre-wrap;
1277 word-break: break-all;
1278 }
1279
1280 .response-body {
1281 color: #495057;
1282 }
1283
1284 .response-headers {
1285 color: #6c757d;
1286 }
1287
1288 .sent-data {
1289 color: #007cba;
1290 }
1291
1292 .parsed-response {
1293 color: #28a745;
1294 background: #f8fff9;
1295 border-color: #c3e6cb;
1296 }
1297
1298 #host-test-results {
1299 margin-top: 20px;
1300 }
1301
1302 #host-test-results h3 {
1303 color: #495057;
1304 margin-bottom: 15px;
1305 padding-bottom: 10px;
1306 border-bottom: 2px solid #dee2e6;
1307 }
1308
1309 .button:disabled {
1310 opacity: 0.6;
1311 cursor: not-allowed;
1312 }
1313 </style>
1314
1315 <?php if ($active_tab === 'advanced'): ?>
1316 <?php if (isset($_GET['settings_cleared']) && $_GET['settings_cleared'] == '1'): ?>
1317 <div class="notice notice-success is-dismissible">
1318 <p><strong>�
1319 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>
1320 </div>
1321 <?php endif; ?>
1322
1323 <?php if (isset($_GET['clear_settings_error']) && $_GET['clear_settings_error'] == '1'): ?>
1324 <div class="notice notice-error is-dismissible">
1325 <p><strong>❌ Error!</strong> Failed to clear settings due to a security check failure. Please try again.</p>
1326 </div>
1327 <?php endif; ?>
1328
1329 <?php if (isset($_GET['log_cleared']) && $_GET['log_cleared'] == '1'): ?>
1330 <div class="notice notice-success is-dismissible">
1331 <p><strong>�
1332 Success!</strong> Error logs have been cleared successfully.</p>
1333 </div>
1334 <?php endif; ?>
1335
1336 <?php if (isset($_GET['clear_error']) && $_GET['clear_error'] == '1'): ?>
1337 <div class="notice notice-error is-dismissible">
1338 <p><strong>❌ Error!</strong> Failed to clear error logs due to a security check failure. Please try again.</p>
1339 </div>
1340 <?php endif; ?>
1341
1342 <?php if (isset($_GET['access_roles_saved']) && $_GET['access_roles_saved'] == '1'): ?>
1343 <div class="notice notice-success is-dismissible">
1344 <p><strong>�
1345 Success!</strong> Plugin access roles have been saved successfully.</p>
1346 </div>
1347 <?php endif; ?>
1348
1349 <?php if (isset($_GET['access_roles_error']) && $_GET['access_roles_error'] == '1'): ?>
1350 <div class="notice notice-error is-dismissible">
1351 <p><strong>❌ Error!</strong> Failed to save plugin access roles. <?php echo isset($_GET['message']) ? esc_html(urldecode($_GET['message'])) : 'Please try again.'; ?></p>
1352 </div>
1353 <?php endif; ?>
1354
1355 <?php endif; ?>
1356 <?php $this->admin->render_layout_close(); ?>
1357 <?php
1358 }
1359
1360 // ------------------------------------------------------------------
1361 // Simple page renderers
1362 // ------------------------------------------------------------------
1363
1364 public function create_admin_dashboard_page()
1365 {
1366 ?>
1367 <?php $this->admin->render_layout_open('Dashboard', 'dashboard', ''); ?>
1368
1369 <div class="dashboard-card">
1370 <h2><?php echo esc_html($this->admin->get_effective_menu_title()); ?> Dashboard</h2>
1371 <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>
1372 <?php
1373 if (!isset(Metasync::get_option('general')['linkgraph_token']) || Metasync::get_option('general')['linkgraph_token'] == '') {
1374 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>';
1375 echo '<a href="' . admin_url('admin.php?page=' . Metasync_Admin::$page_slug) . '" class="button button-secondary">Go to Settings</a>';
1376 } else {
1377 echo '<a href="' . esc_url($this->admin->get_dashboard_url()) . '" target="_blank" class="button button-primary">Open Dashboard</a>';
1378 }
1379 ?>
1380 </div>
1381 <?php $this->admin->render_layout_close(); ?>
1382 <?php
1383 }
1384
1385 public function create_admin_robots_txt_page()
1386 {
1387 ?>
1388 <?php $this->admin->render_layout_open('Robots.txt', 'robots_txt', 'Control which pages search engines can crawl.'); ?>
1389
1390 <?php
1391 require_once plugin_dir_path(dirname(__FILE__)) . 'robots-txt/class-metasync-robots-txt.php';
1392
1393 $robots_txt = Metasync_Robots_Txt::get_instance();
1394
1395 if (isset($_POST['metasync_robots_txt_nonce'])) {
1396 check_admin_referer('metasync_save_robots_txt', 'metasync_robots_txt_nonce');
1397
1398 if (isset($_POST['robots_content'])) {
1399 $content = wp_unslash($_POST['robots_content']);
1400
1401 $validation = $robots_txt->validate_content($content);
1402
1403 if ($validation['valid']) {
1404 $result = $robots_txt->write_robots_file($content);
1405
1406 if (is_wp_error($result)) {
1407 echo '<div class="notice notice-error"><p>' . esc_html($result->get_error_message()) . '</p></div>';
1408 } else {
1409 echo '<div class="notice notice-success"><p>' . esc_html__('robots.txt file saved successfully!', 'metasync') . '</p></div>';
1410
1411 if (!empty($validation['warnings'])) {
1412 foreach ($validation['warnings'] as $warning) {
1413 echo '<div class="notice notice-warning"><p>' . esc_html($warning) . '</p></div>';
1414 }
1415 }
1416 }
1417 } else {
1418 echo '<div class="notice notice-error"><p>' . esc_html__('Validation failed. Please fix the errors below:', 'metasync') . '</p>';
1419 foreach ($validation['errors'] as $error) {
1420 echo '<p>- ' . esc_html($error) . '</p>';
1421 }
1422 echo '</div>';
1423 }
1424 }
1425 }
1426
1427 $current_content = $robots_txt->read_robots_file();
1428 if (is_wp_error($current_content)) {
1429 echo '<div class="notice notice-error"><p>' . esc_html($current_content->get_error_message()) . '</p></div>';
1430 $current_content = '';
1431 }
1432
1433 // First page of backups; the Backup History panel is paginated so the
1434 // list always reflects the full stored state and its true total.
1435 $backups = $robots_txt->get_backup_history(Metasync_Robots_Txt_Database::BACKUPS_PER_PAGE, 0);
1436
1437 $file_exists = $robots_txt->file_exists();
1438 $is_writable = $robots_txt->is_writable();
1439
1440 $robots_txt->render($this->admin, $current_content, $backups, $file_exists, $is_writable);
1441 ?>
1442 <?php $this->admin->render_layout_close(); ?>
1443 <?php
1444 }
1445
1446 public function create_admin_report_issue_page()
1447 {
1448 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-report-issue.php';
1449 }
1450
1451 public function create_admin_xml_sitemap_page()
1452 {
1453 require_once plugin_dir_path(dirname(__FILE__)) . 'sitemap/class-metasync-sitemap-generator.php';
1454
1455 $sitemap_generator = new Metasync_Sitemap_Generator();
1456
1457 if (isset($_POST['metasync_sitemap_nonce'])) {
1458 check_admin_referer('metasync_sitemap_action', 'metasync_sitemap_nonce');
1459
1460 if (isset($_POST['generate_sitemap'])) {
1461 $disabled_plugins = $sitemap_generator->disable_other_sitemap_generators();
1462
1463 $result = $sitemap_generator->generate_sitemap();
1464
1465 if (is_wp_error($result)) {
1466 error_log('[MetaSync] Sitemap generation failed: ' . $result->get_error_message());
1467 echo '<div class="notice notice-error"><p>' . esc_html__('Sitemap generation failed. Please try again or check the error logs.', 'metasync') . '</p></div>';
1468 } elseif (false === $result) {
1469 echo '<div class="notice notice-error"><p>' . esc_html__('Sitemap generation failed: the sitemap data could not be stored, so no sitemap is being served. Check your object cache and database write settings, then try again.', 'metasync') . '</p></div>';
1470 } else {
1471 $message = esc_html__('Sitemap generated successfully!', 'metasync');
1472 if ($disabled_plugins) {
1473 $message .= ' ' . esc_html__('Conflicting sitemap generators have been automatically disabled.', 'metasync');
1474 }
1475
1476 $robots_result = get_transient('metasync_sitemap_robots_updated');
1477 if ($robots_result && $robots_result['success']) {
1478 if ($robots_result['action'] === 'added') {
1479 $message .= ' ' . esc_html__('Sitemap URL has been added to robots.txt.', 'metasync');
1480 } elseif ($robots_result['action'] === 'updated') {
1481 $message .= ' ' . esc_html__('Sitemap URL has been updated in robots.txt.', 'metasync');
1482 } elseif ($robots_result['action'] === 'created') {
1483 $message .= ' ' . esc_html__('robots.txt file has been created with sitemap URL.', 'metasync');
1484 }
1485 delete_transient('metasync_sitemap_robots_updated');
1486 }
1487
1488 echo '<div class="notice notice-success"><p>' . esc_html($message) . '</p></div>';
1489 }
1490 } elseif (isset($_POST['enable_auto_update'])) {
1491 update_option('metasync_sitemap_auto_update', true);
1492 $sitemap_generator->setup_auto_update_hooks();
1493 echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update enabled!', 'metasync') . '</p></div>';
1494 } elseif (isset($_POST['disable_auto_update'])) {
1495 update_option('metasync_sitemap_auto_update', false);
1496 echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update disabled!', 'metasync') . '</p></div>';
1497 } elseif (isset($_POST['delete_sitemap'])) {
1498 $deleted = $sitemap_generator->delete_sitemap();
1499 if ($deleted) {
1500 update_option('metasync_sitemap_auto_update', false);
1501 // Re-enable WP core sitemap so the site isn't left with zero sitemaps
1502 delete_option('metasync_disable_wp_sitemap');
1503 echo '<div class="notice notice-success"><p>' . esc_html__('Sitemap deleted successfully!', 'metasync') . '</p></div>';
1504 } else {
1505 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>';
1506 }
1507 } elseif (isset($_POST['enable_other_sitemaps'])) {
1508 $enabled_plugins = $sitemap_generator->enable_other_sitemap_generators();
1509 if ($enabled_plugins) {
1510 echo '<div class="notice notice-success"><p>' . esc_html__('Other sitemap plugins have been re-enabled successfully!', 'metasync') . '</p></div>';
1511 } else {
1512 echo '<div class="notice notice-info"><p>' . esc_html__('No sitemap plugins were found to re-enable.', 'metasync') . '</p></div>';
1513 }
1514 }
1515 }
1516
1517 $sitemap_exists = $sitemap_generator->sitemap_exists();
1518 $sitemap_url = $sitemap_generator->get_sitemap_url();
1519 $url_count = $sitemap_generator->count_urls();
1520 $last_generated = $sitemap_generator->get_last_generated_time();
1521 $auto_update_enabled = get_option('metasync_sitemap_auto_update', false);
1522 $active_sitemap_plugins = $sitemap_generator->check_active_sitemap_plugins();
1523
1524 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-xml-sitemap.php';
1525 }
1526
1527 public function create_admin_custom_pages_page()
1528 {
1529 ?>
1530 <?php $this->admin->render_layout_open('Custom Pages', 'custom_pages', 'Create blank canvas pages for Elementor, Divi, or Gutenberg.'); ?>
1531
1532 <div class="metasync-page-content">
1533 <?php
1534 require_once plugin_dir_path(dirname(__FILE__)) . 'custom-pages/class-metasync-custom-pages-admin.php';
1535 Metasync_Custom_Pages_Admin::render_admin_page();
1536 ?>
1537 </div>
1538
1539 <?php $this->admin->render_layout_close(); ?>
1540 <?php
1541 }
1542
1543 public function create_admin_404_monitor_page()
1544 {
1545 require_once plugin_dir_path(dirname(__FILE__)) . '404-monitor/class-metasync-404-monitor-database.php';
1546 require_once plugin_dir_path(dirname(__FILE__)) . '404-monitor/class-metasync-404-monitor.php';
1547
1548 $db_404 = new Metasync_Error_Monitor_Database();
1549 $ErrorMonitor = new Metasync_Error_Monitor($db_404);
1550
1551 $this->admin->render_layout_open('404 Monitor', 'monitor_404', 'Track and manage 404 errors detected on your site.');
1552 $ErrorMonitor->create_admin_plugin_interface();
1553 $this->admin->render_layout_close();
1554 }
1555
1556 public function create_admin_search_engine_verification_page()
1557 {
1558 $page_slug = Metasync_Admin::$page_slug . '_searchengines-verification';
1559 ?>
1560 <?php $this->admin->render_layout_open('Site Verification', 'site_verification', 'Verify your site ownership with search engines and other platforms.'); ?>
1561
1562 <form method="post" action="options.php">
1563 <div class="dashboard-card">
1564 <h2>Search Engine Verification</h2>
1565 <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>
1566 <?php
1567 settings_fields(Metasync_Admin::option_group);
1568 do_settings_sections($page_slug);
1569 submit_button();
1570 ?>
1571 </div>
1572 </form>
1573 <?php $this->admin->render_layout_close(); ?>
1574 <?php
1575 }
1576
1577 public function create_admin_breadcrumbs_page()
1578 {
1579 $page_slug = Metasync_Admin::$page_slug . '_breadcrumbs';
1580 ?>
1581 <?php $this->admin->render_layout_open('Breadcrumbs', 'breadcrumbs', 'Configure breadcrumb trail settings and placement.'); ?>
1582
1583 <form method="post" action="options.php">
1584 <div class="dashboard-card">
1585 <h2>Breadcrumbs Settings</h2>
1586 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p>
1587 <?php
1588 settings_fields(Metasync_Admin::option_group);
1589 do_settings_sections($page_slug);
1590 submit_button();
1591 ?>
1592 </div>
1593 </form>
1594
1595 <div class="dashboard-card" style="margin-top: 20px;">
1596 <h2>📖 How to Display Breadcrumbs</h2>
1597 <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>
1598
1599 <h3 style="margin-top: 0;">Option 1 — Shortcode</h3>
1600 <p style="color: var(--dashboard-text-secondary);">Paste the shortcode inside any page, post, or widget:</p>
1601 <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>
1602
1603 <h3>Option 2 — PHP Template Function</h3>
1604 <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>
1605 <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>
1606
1607 <h3>Option 3 — PHP Return Value</h3>
1608 <p style="color: var(--dashboard-text-secondary);">Get the breadcrumb HTML as a string to manipulate before output:</p>
1609 <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();
1610 echo $breadcrumbs-&gt;render_breadcrumb_html();</pre><span class="metasync-copy-badge">Copy</span></div>
1611
1612 <p style="color: var(--dashboard-text-secondary); margin-top: 16px; font-size: 12px;">
1613 <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>
1614 <code>echo $breadcrumbs-&gt;render_breadcrumb_html(['post_id' =&gt; 123]);</code>
1615 </p>
1616 <?php /* click-to-copy for the code snippets above */ ?>
1617 <style>
1618 .metasync-copy-wrap { position: relative; }
1619 .metasync-copy-snippet { cursor: pointer; outline: 1px solid transparent; transition: outline-color .15s ease; }
1620 .metasync-copy-wrap:hover .metasync-copy-snippet { outline-color: var(--dashboard-border, #374151); }
1621 .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); }
1622 .metasync-copy-wrap:hover .metasync-copy-badge { opacity: .85; }
1623 .metasync-copy-badge.metasync-copy-done { opacity: 1; background: var(--dashboard-gradient-primary, linear-gradient(135deg, #667eea 0%, #764ba2 100%)); color: #fff; }
1624 </style>
1625 <script>
1626 (function(){
1627 if (window.__metasyncCopyInit) return; window.__metasyncCopyInit = true;
1628 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); }
1629 function attach(){
1630 document.querySelectorAll('.metasync-copy-snippet').forEach(function(pre){
1631 if (pre.dataset.copyBound) return; pre.dataset.copyBound='1';
1632 pre.setAttribute('title','Click to copy');
1633 pre.addEventListener('click', function(){
1634 var text = pre.textContent.trim();
1635 var wrap = pre.closest('.metasync-copy-wrap') || pre.parentNode;
1636 var badge = wrap.querySelector('.metasync-copy-badge');
1637 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); };
1638 if (navigator.clipboard && navigator.clipboard.writeText){ navigator.clipboard.writeText(text).then(done).catch(function(){ fallback(text); done(); }); }
1639 else { fallback(text); done(); }
1640 });
1641 });
1642 }
1643 if (document.readyState==='loading'){ document.addEventListener('DOMContentLoaded', attach); } else { attach(); }
1644 })();
1645 </script>
1646 </div>
1647 <?php $this->admin->render_layout_close(); ?>
1648 <?php
1649 }
1650
1651 public function create_admin_schema_markup_page()
1652 {
1653 $page_slug = Metasync_Admin::$page_slug . '_schema-markup';
1654 $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.');
1655 ?>
1656 <form method="post" action="options.php">
1657 <div class="dashboard-card">
1658 <h2>Schema Markup Settings</h2>
1659 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure the site-wide Organization and WebSite structured data injected into every page.</p>
1660 <?php
1661 settings_fields(Metasync_Admin::option_group);
1662 do_settings_sections($page_slug);
1663 submit_button('Save Changes');
1664 ?>
1665 </div>
1666 </form>
1667 <?php
1668 $this->admin->render_layout_close();
1669 }
1670
1671 public function create_admin_local_business_page()
1672 {
1673 $page_slug = Metasync_Admin::$page_slug . '_local-seo';
1674 ?>
1675 <?php $this->admin->render_layout_open('Local Business', 'local_business', 'Configure local business information for better local SEO.'); ?>
1676
1677 <form method="post" action="options.php">
1678 <div class="dashboard-card">
1679 <h2>🏢 Local Business Configuration<?php Metasync::render_tooltip_icon('local_business_section', 'Fill in your business name, address, phone, hours, and category so search engines can show your business in local results and map listings. This generates LocalBusiness schema behind the scenes. Use your exact, consistent NAP (name/address/phone) details.'); ?></h2>
1680 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure your local business information for better local search engine optimization.</p>
1681 <?php
1682 settings_fields(Metasync_Admin::option_group);
1683 do_settings_sections($page_slug);
1684 submit_button();
1685 ?>
1686 </div>
1687 </form>
1688 <?php $this->admin->render_layout_close(); ?>
1689 <?php
1690 }
1691
1692 public function create_admin_code_snippets_page()
1693 {
1694 $page_slug = Metasync_Admin::$page_slug . '_code-snippets';
1695 ?>
1696 <?php $this->admin->render_layout_open('Code Snippets', 'code_snippets', 'Add custom code to your site header and footer.'); ?>
1697
1698 <form method="post" action="options.php">
1699 <div class="dashboard-card">
1700 <h2>Code Snippets Management</h2>
1701 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Add custom code snippets to enhance your site functionality and tracking capabilities.</p>
1702 <?php
1703 settings_fields(Metasync_Admin::option_group);
1704 do_settings_sections($page_slug);
1705 submit_button();
1706 ?>
1707 </div>
1708 </form>
1709 <?php $this->admin->render_layout_close(); ?>
1710 <?php
1711 }
1712
1713 public function create_admin_google_instant_index_page()
1714 {
1715 // Delegates to the admin class which uses the shared credentials view
1716 $this->admin->create_admin_google_instant_index_page();
1717 }
1718
1719 public function create_admin_google_console_page()
1720 {
1721 $this->admin->render_layout_open('Google Console', 'google_console', 'View Google Search Console data and manage indexing requests.');
1722 $google = function_exists('google_index_direct') ? google_index_direct() : null;
1723 if (is_null($google)) {
1724 $service_info = ['error' => 'Module not loaded'];
1725 } else {
1726 $service_info = $google->get_service_account_info();
1727 }
1728 $is_configured = !isset($service_info['error']);
1729 include_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-google-console.php';
1730 $this->admin->render_layout_close();
1731 }
1732
1733 public function create_admin_bing_console_page()
1734 {
1735 ?>
1736 <?php $this->admin->render_layout_open('Bing Console', 'bing_console', ''); ?>
1737
1738 <?php
1739 require_once plugin_dir_path(dirname(__FILE__)) . 'bing-index/class-metasync-bing-instant-index.php';
1740 $bing_instant_index = new Metasync_Bing_Instant_Index();
1741 $bing_instant_index->show_bing_instant_indexing_console();
1742 ?>
1743 <?php $this->admin->render_layout_close(); ?>
1744 <?php
1745 }
1746
1747 public function create_admin_global_settings_page()
1748 {
1749 $page_slug = Metasync_Admin::$page_slug . '_common-settings';
1750 ?>
1751 <?php $this->admin->render_layout_open('Global Settings', 'general', ''); ?>
1752
1753 <form method="post" action="options.php">
1754 <div class="dashboard-card">
1755 <h2>Global Configuration</h2>
1756 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure global settings that apply across your entire site.</p>
1757 <?php
1758 settings_fields(Metasync_Admin::option_group);
1759 do_settings_sections($page_slug);
1760 ?>
1761 </div>
1762
1763 <!-- Save button removed - using floating notification system instead -->
1764 </form>
1765 <?php $this->admin->render_layout_close(); ?>
1766 <?php
1767 }
1768
1769 public function create_admin_common_meta_settings_page()
1770 {
1771 $page_slug = Metasync_Admin::$page_slug . '_common-meta-settings';
1772 ?>
1773 <?php $this->admin->render_layout_open('Common Meta Settings', 'general', ''); ?>
1774
1775 <form method="post" action="options.php">
1776 <div class="dashboard-card">
1777 <h2>Meta Configuration</h2>
1778 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure common meta tags and SEO settings for your site.</p>
1779 <?php
1780 settings_fields(Metasync_Admin::option_group);
1781 do_settings_sections($page_slug);
1782 ?>
1783 </div>
1784
1785 <!-- Save button removed - using floating notification system instead -->
1786 </form>
1787 <?php $this->admin->render_layout_close(); ?>
1788 <?php
1789 }
1790
1791 public function create_admin_social_meta_page()
1792 {
1793 $page_slug = Metasync_Admin::$page_slug . '_social-meta';
1794 ?>
1795 <?php $this->admin->render_layout_open('Social Meta', 'general', ''); ?>
1796
1797 <form method="post" action="options.php">
1798 <div class="dashboard-card">
1799 <h2>📲 Social Meta Tags</h2>
1800 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how your content appears when shared on social media platforms.</p>
1801 <?php
1802 settings_fields(Metasync_Admin::option_group);
1803 do_settings_sections($page_slug);
1804 ?>
1805 </div>
1806
1807 <!-- Save button removed - using floating notification system instead -->
1808 </form>
1809 <?php $this->admin->render_layout_close(); ?>
1810 <?php
1811 }
1812
1813 public function create_admin_seo_controls_page()
1814 {
1815 $page_slug = Metasync_Admin::$page_slug . '_seo-controls';
1816 ?>
1817 <?php $this->admin->render_layout_open('Indexation Control', 'seo_controls', 'Control how search engines index your content.'); ?>
1818
1819 <!-- Status Messages Container -->
1820 <div id="seo-controls-messages"></div>
1821
1822 <form method="post" action="options.php" id="metaSyncSeoControlsForm">
1823 <div class="dashboard-card">
1824 <div style="display: flex; justify-content: flex-end; margin-bottom: 20px;">
1825 <a href="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-import-external&tab=indexation')); ?>" class="button button-secondary">
1826 <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
1827 </a>
1828 </div>
1829 <?php
1830 settings_fields(Metasync_Admin::option_group);
1831 do_settings_sections($page_slug);
1832
1833 wp_nonce_field('meta_sync_seo_controls_nonce', 'meta_sync_seo_controls_nonce');
1834 ?>
1835 </div>
1836
1837 <!-- Save button removed - using floating notification system instead -->
1838 </form>
1839 <?php $this->admin->render_layout_close(); ?>
1840 <?php
1841 }
1842
1843
1844 public function create_admin_error_logs_page()
1845 {
1846 ?>
1847 <?php $this->admin->render_layout_open('Error Logs', 'general', ''); ?>
1848
1849 <div class="dashboard-card">
1850 <h2>Error Logs Management</h2>
1851 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">View and manage system error logs to troubleshoot issues and monitor plugin performance.</p>
1852 <?php
1853 $error_logs = new Metasync_Error_Logs();
1854
1855 if ($error_logs->can_show_error_logs()) {
1856 $log_content = $error_logs->get_error_logs(50);
1857
1858 if (!empty(trim($log_content))) {
1859 $error_logs->show_copy_button();
1860 $error_logs->show_logs();
1861 $error_logs->show_info();
1862 } else {
1863 echo '<div class="dashboard-empty-state">';
1864 echo '<p style="color: var(--dashboard-text-secondary); font-style: italic; text-align: center; padding: 40px 20px;">�
1865 Log file is empty - no errors recorded.</p>';
1866 echo '</div>';
1867 }
1868 } else {
1869 $error_message = $error_logs->get_error_message();
1870 if (!empty($error_message)) {
1871 echo '<div class="dashboard-empty-state">';
1872 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;">';
1873 echo esc_html($error_message);
1874 echo '</p>';
1875 echo '</div>';
1876 } else {
1877 echo '<div class="dashboard-empty-state">';
1878 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>';
1879 echo '</div>';
1880 }
1881 }
1882 ?>
1883 </div>
1884 <?php $this->admin->render_layout_close(); ?>
1885 <?php
1886 }
1887
1888 // NOTE: The Changes Log (sync log) page is rendered by
1889 // Metasync_Admin::create_admin_sync_log_page() in
1890 // admin/class-metasync-admin.php, which is the callback registered in
1891 // class-metasync-admin-navigation.php. A former duplicate of that method
1892 // lived here but was never wired to a menu and has been removed to avoid
1893 // drift.
1894
1895 public function creat_error_Logs_List()
1896 {
1897 // printf('<h1> Error Logs </h1>');
1898 // $error_log = new Metasync_Error_Logs_Table($this->data_error_log_list);
1899 // $error_log->create_admin_error_log_list_interface();
1900 }
1901
1902 public function create_admin_heartbeat_error_logs_page()
1903 {
1904 ?>
1905 <?php $this->admin->render_layout_open('Heartbeat Error Logs', 'general', ''); ?>
1906
1907 <div class="dashboard-card">
1908 <h2>💓 HeartBeat Error Logs</h2>
1909 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Monitor WordPress heartbeat errors to identify connectivity issues and system problems.</p>
1910 <?php
1911 $heartbeat_errors = new Metasync_HeartBeat_Error_Monitor($this->admin->db_heartbeat_errors);
1912 $heartbeat_errors->create_admin_heartbeat_errors_interface();
1913 ?>
1914 </div>
1915 <?php $this->admin->render_layout_close(); ?>
1916 <?php
1917 }
1918
1919 public function create_admin_bot_statistics_page()
1920 {
1921 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-otto-bot-statistics.php';
1922 }
1923
1924 // ------------------------------------------------------------------
1925 // Private helpers
1926 // ------------------------------------------------------------------
1927 // The former time_elapsed_string / build_filter_query_string /
1928 // handle_sync_log_ajax helpers here were only used by the dead
1929 // create_admin_sync_log_page() duplicate that has since been removed. The
1930 // live equivalents live on Metasync_Admin.
1931 }
1932