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

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