PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / trunk
Search Atlas SEO – OTTO AI SEO Automation for WordPress vtrunk
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / includes / class-metasync-admin-pages.php

class-metasync-admin-pages.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress trunk, at includes/class-metasync-admin-pages.php

1,937 lines 101.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'])) {
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 <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'):
916 $indefinite = isset($_GET['indefinite']) && $_GET['indefinite'] == '1';
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'): ?>
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'): ?>
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'): ?>
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 + ' ' + 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> ' + result.response_time + '</p>';
1133 html += '<p><strong>Status:</strong> ' + result.status + '</p>';
1134
1135 if (result.status_code) {
1136 html += '<p><strong>HTTP Status Code:</strong> <span class="status-code">' + result.status_code + '</span></p>';
1137 }
1138
1139 if (result.error) {
1140 html += '<p><strong>Error:</strong> <span class="error-message">' + 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 += key + ': ' + 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> ' + 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 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'): ?>
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'): ?>
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'): ?>
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'): ?>
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'): ?>
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'): ?>
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.'; ?></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 ?>
1391 <?php $this->admin->render_layout_open('Robots.txt', 'robots_txt', 'Control which pages search engines can crawl.'); ?>
1392
1393 <?php
1394 require_once plugin_dir_path(dirname(__FILE__)) . 'robots-txt/class-metasync-robots-txt.php';
1395
1396 $robots_txt = Metasync_Robots_Txt::get_instance();
1397
1398 if (isset($_POST['metasync_robots_txt_nonce'])) {
1399 check_admin_referer('metasync_save_robots_txt', 'metasync_robots_txt_nonce');
1400
1401 if (isset($_POST['robots_content'])) {
1402 $content = wp_unslash($_POST['robots_content']);
1403
1404 $validation = $robots_txt->validate_content($content);
1405
1406 if ($validation['valid']) {
1407 $result = $robots_txt->write_robots_file($content);
1408
1409 if (is_wp_error($result)) {
1410 echo '<div class="notice notice-error"><p>' . esc_html($result->get_error_message()) . '</p></div>';
1411 } else {
1412 echo '<div class="notice notice-success"><p>' . esc_html__('robots.txt file saved successfully!', 'metasync') . '</p></div>';
1413
1414 if (!empty($validation['warnings'])) {
1415 foreach ($validation['warnings'] as $warning) {
1416 echo '<div class="notice notice-warning"><p>' . esc_html($warning) . '</p></div>';
1417 }
1418 }
1419 }
1420 } else {
1421 echo '<div class="notice notice-error"><p>' . esc_html__('Validation failed. Please fix the errors below:', 'metasync') . '</p>';
1422 foreach ($validation['errors'] as $error) {
1423 echo '<p>- ' . esc_html($error) . '</p>';
1424 }
1425 echo '</div>';
1426 }
1427 }
1428 }
1429
1430 $current_content = $robots_txt->read_robots_file();
1431 if (is_wp_error($current_content)) {
1432 echo '<div class="notice notice-error"><p>' . esc_html($current_content->get_error_message()) . '</p></div>';
1433 $current_content = '';
1434 }
1435
1436 // First page of backups; the Backup History panel is paginated so the
1437 // list always reflects the full stored state and its true total.
1438 $backups = $robots_txt->get_backup_history(Metasync_Robots_Txt_Database::BACKUPS_PER_PAGE, 0);
1439
1440 $file_exists = $robots_txt->file_exists();
1441 $is_writable = $robots_txt->is_writable();
1442
1443 $robots_txt->render($this->admin, $current_content, $backups, $file_exists, $is_writable);
1444 ?>
1445 <?php $this->admin->render_layout_close(); ?>
1446 <?php
1447 }
1448
1449 public function create_admin_report_issue_page()
1450 {
1451 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-report-issue.php';
1452 }
1453
1454 public function create_admin_xml_sitemap_page()
1455 {
1456 require_once plugin_dir_path(dirname(__FILE__)) . 'sitemap/class-metasync-sitemap-generator.php';
1457
1458 $sitemap_generator = new Metasync_Sitemap_Generator();
1459
1460 if (isset($_POST['metasync_sitemap_nonce'])) {
1461 check_admin_referer('metasync_sitemap_action', 'metasync_sitemap_nonce');
1462
1463 if (isset($_POST['generate_sitemap'])) {
1464 $disabled_plugins = $sitemap_generator->disable_other_sitemap_generators();
1465
1466 $result = $sitemap_generator->generate_sitemap();
1467
1468 if (is_wp_error($result)) {
1469 error_log('[MetaSync] Sitemap generation failed: ' . $result->get_error_message());
1470 echo '<div class="notice notice-error"><p>' . esc_html__('Sitemap generation failed. Please try again or check the error logs.', 'metasync') . '</p></div>';
1471 } elseif (false === $result) {
1472 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>';
1473 } else {
1474 $message = esc_html__('Sitemap generated successfully!', 'metasync');
1475 if ($disabled_plugins) {
1476 $message .= ' ' . esc_html__('Conflicting sitemap generators have been automatically disabled.', 'metasync');
1477 }
1478
1479 $robots_result = get_transient('metasync_sitemap_robots_updated');
1480 if ($robots_result && $robots_result['success']) {
1481 if ($robots_result['action'] === 'added') {
1482 $message .= ' ' . esc_html__('Sitemap URL has been added to robots.txt.', 'metasync');
1483 } elseif ($robots_result['action'] === 'updated') {
1484 $message .= ' ' . esc_html__('Sitemap URL has been updated in robots.txt.', 'metasync');
1485 } elseif ($robots_result['action'] === 'created') {
1486 $message .= ' ' . esc_html__('robots.txt file has been created with sitemap URL.', 'metasync');
1487 }
1488 delete_transient('metasync_sitemap_robots_updated');
1489 }
1490
1491 echo '<div class="notice notice-success"><p>' . esc_html($message) . '</p></div>';
1492 }
1493 } elseif (isset($_POST['enable_auto_update'])) {
1494 update_option('metasync_sitemap_auto_update', true);
1495 $sitemap_generator->setup_auto_update_hooks();
1496 echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update enabled!', 'metasync') . '</p></div>';
1497 } elseif (isset($_POST['disable_auto_update'])) {
1498 update_option('metasync_sitemap_auto_update', false);
1499 echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update disabled!', 'metasync') . '</p></div>';
1500 } elseif (isset($_POST['delete_sitemap'])) {
1501 $deleted = $sitemap_generator->delete_sitemap();
1502 if ($deleted) {
1503 update_option('metasync_sitemap_auto_update', false);
1504 // Re-enable WP core sitemap so the site isn't left with zero sitemaps
1505 delete_option('metasync_disable_wp_sitemap');
1506 echo '<div class="notice notice-success"><p>' . esc_html__('Sitemap deleted successfully!', 'metasync') . '</p></div>';
1507 } else {
1508 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>';
1509 }
1510 } elseif (isset($_POST['enable_other_sitemaps'])) {
1511 $enabled_plugins = $sitemap_generator->enable_other_sitemap_generators();
1512 if ($enabled_plugins) {
1513 echo '<div class="notice notice-success"><p>' . esc_html__('Other sitemap plugins have been re-enabled successfully!', 'metasync') . '</p></div>';
1514 } else {
1515 echo '<div class="notice notice-info"><p>' . esc_html__('No sitemap plugins were found to re-enable.', 'metasync') . '</p></div>';
1516 }
1517 }
1518 }
1519
1520 $sitemap_exists = $sitemap_generator->sitemap_exists();
1521 $sitemap_url = $sitemap_generator->get_sitemap_url();
1522 $url_count = $sitemap_generator->count_urls();
1523 $last_generated = $sitemap_generator->get_last_generated_time();
1524 $auto_update_enabled = get_option('metasync_sitemap_auto_update', false);
1525 $active_sitemap_plugins = $sitemap_generator->check_active_sitemap_plugins();
1526
1527 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-xml-sitemap.php';
1528 }
1529
1530 public function create_admin_custom_pages_page()
1531 {
1532 ?>
1533 <?php $this->admin->render_layout_open('Custom Pages', 'custom_pages', 'Create blank canvas pages for Elementor, Divi, or Gutenberg.'); ?>
1534
1535 <div class="metasync-page-content">
1536 <?php
1537 require_once plugin_dir_path(dirname(__FILE__)) . 'custom-pages/class-metasync-custom-pages-admin.php';
1538 Metasync_Custom_Pages_Admin::render_admin_page();
1539 ?>
1540 </div>
1541
1542 <?php $this->admin->render_layout_close(); ?>
1543 <?php
1544 }
1545
1546 public function create_admin_404_monitor_page()
1547 {
1548 require_once plugin_dir_path(dirname(__FILE__)) . '404-monitor/class-metasync-404-monitor-database.php';
1549 require_once plugin_dir_path(dirname(__FILE__)) . '404-monitor/class-metasync-404-monitor.php';
1550
1551 $db_404 = new Metasync_Error_Monitor_Database();
1552 $ErrorMonitor = new Metasync_Error_Monitor($db_404);
1553
1554 $this->admin->render_layout_open('404 Monitor', 'monitor_404', 'Track and manage 404 errors detected on your site.');
1555 $ErrorMonitor->create_admin_plugin_interface();
1556 $this->admin->render_layout_close();
1557 }
1558
1559 public function create_admin_search_engine_verification_page()
1560 {
1561 $page_slug = Metasync_Admin::$page_slug . '_searchengines-verification';
1562 ?>
1563 <?php $this->admin->render_layout_open('Site Verification', 'site_verification', 'Verify your site ownership with search engines and other platforms.'); ?>
1564
1565 <form method="post" action="options.php">
1566 <div class="dashboard-card">
1567 <h2>Search Engine Verification</h2>
1568 <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>
1569 <?php
1570 settings_fields(Metasync_Admin::option_group);
1571 do_settings_sections($page_slug);
1572 submit_button();
1573 ?>
1574 </div>
1575 </form>
1576 <?php $this->admin->render_layout_close(); ?>
1577 <?php
1578 }
1579
1580 public function create_admin_breadcrumbs_page()
1581 {
1582 ?>
1583 <?php $this->admin->render_layout_open('Breadcrumbs', 'breadcrumbs', 'Configure breadcrumb trail settings and placement.'); ?>
1584
1585 <?php /* options.php redirects back here with ?settings-updated=true; without this call the save confirmation never prints */ ?>
1586 <?php settings_errors(); ?>
1587
1588 <form method="post" action="options.php">
1589 <div class="dashboard-card">
1590 <h2>Breadcrumbs Settings</h2>
1591 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p>
1592 <?php
1593 settings_fields(Metasync_Admin::option_group);
1594 Metasync_Settings_Fields::instance()->render_breadcrumbs_section();
1595 submit_button();
1596 ?>
1597 </div>
1598 </form>
1599
1600 <div class="dashboard-card" style="margin-top: 20px;">
1601 <h2>📖 How to Display Breadcrumbs</h2>
1602 <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>
1603
1604 <h3 style="margin-top: 0;">Option 1 — Shortcode</h3>
1605 <p style="color: var(--dashboard-text-secondary);">Paste the shortcode inside any page, post, or widget:</p>
1606 <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>
1607
1608 <h3>Option 2 PHP Template Function</h3>
1609 <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>
1610 <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>
1611
1612 <h3>Option 3 PHP Return Value</h3>
1613 <p style="color: var(--dashboard-text-secondary);">Get the breadcrumb HTML as a string to manipulate before output:</p>
1614 <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();
1615 echo $breadcrumbs-&gt;render_breadcrumb_html();</pre><span class="metasync-copy-badge">Copy</span></div>
1616
1617 <p style="color: var(--dashboard-text-secondary); margin-top: 16px; font-size: 12px;">
1618 <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>
1619 <code>echo $breadcrumbs-&gt;render_breadcrumb_html(['post_id' =&gt; 123]);</code>
1620 </p>
1621 <?php /* click-to-copy for the code snippets above */ ?>
1622 <style>
1623 .metasync-copy-wrap { position: relative; }
1624 .metasync-copy-snippet { cursor: pointer; outline: 1px solid transparent; transition: outline-color .15s ease; }
1625 .metasync-copy-wrap:hover .metasync-copy-snippet { outline-color: var(--dashboard-border, #374151); }
1626 .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); }
1627 .metasync-copy-wrap:hover .metasync-copy-badge { opacity: .85; }
1628 .metasync-copy-badge.metasync-copy-done { opacity: 1; background: var(--dashboard-gradient-primary, linear-gradient(135deg, #667eea 0%, #764ba2 100%)); color: #fff; }
1629 </style>
1630 <script>
1631 (function(){
1632 if (window.__metasyncCopyInit) return; window.__metasyncCopyInit = true;
1633 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); }
1634 function attach(){
1635 document.querySelectorAll('.metasync-copy-snippet').forEach(function(pre){
1636 if (pre.dataset.copyBound) return; pre.dataset.copyBound='1';
1637 pre.setAttribute('title','Click to copy');
1638 pre.addEventListener('click', function(){
1639 var text = pre.textContent.trim();
1640 var wrap = pre.closest('.metasync-copy-wrap') || pre.parentNode;
1641 var badge = wrap.querySelector('.metasync-copy-badge');
1642 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); };
1643 if (navigator.clipboard && navigator.clipboard.writeText){ navigator.clipboard.writeText(text).then(done).catch(function(){ fallback(text); done(); }); }
1644 else { fallback(text); done(); }
1645 });
1646 });
1647 }
1648 if (document.readyState==='loading'){ document.addEventListener('DOMContentLoaded', attach); } else { attach(); }
1649 })();
1650 </script>
1651 </div>
1652 <?php $this->admin->render_layout_close(); ?>
1653 <?php
1654 }
1655
1656 public function create_admin_schema_markup_page()
1657 {
1658 $page_slug = Metasync_Admin::$page_slug . '_schema-markup';
1659 $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.');
1660 ?>
1661 <form method="post" action="options.php">
1662 <div class="dashboard-card">
1663 <h2>Schema Markup Settings</h2>
1664 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure the site-wide Organization and WebSite structured data injected into every page.</p>
1665 <?php
1666 settings_fields(Metasync_Admin::option_group);
1667 do_settings_sections($page_slug);
1668 submit_button('Save Changes');
1669 ?>
1670 </div>
1671 </form>
1672 <?php
1673 $this->admin->render_layout_close();
1674 }
1675
1676 public function create_admin_local_business_page()
1677 {
1678 $page_slug = Metasync_Admin::$page_slug . '_local-seo';
1679 ?>
1680 <?php $this->admin->render_layout_open('Local Business', 'local_business', 'Configure local business information for better local SEO.'); ?>
1681
1682 <form method="post" action="options.php">
1683 <div class="dashboard-card">
1684 <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>
1685 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure your local business information for better local search engine optimization.</p>
1686 <?php
1687 settings_fields(Metasync_Admin::option_group);
1688 do_settings_sections($page_slug);
1689 submit_button();
1690 ?>
1691 </div>
1692 </form>
1693 <?php $this->admin->render_layout_close(); ?>
1694 <?php
1695 }
1696
1697 public function create_admin_code_snippets_page()
1698 {
1699 $page_slug = Metasync_Admin::$page_slug . '_code-snippets';
1700 ?>
1701 <?php $this->admin->render_layout_open('Code Snippets', 'code_snippets', 'Add custom code to your site header and footer.'); ?>
1702
1703 <form method="post" action="options.php">
1704 <div class="dashboard-card">
1705 <h2>Code Snippets Management</h2>
1706 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Add custom code snippets to enhance your site functionality and tracking capabilities.</p>
1707 <?php
1708 settings_fields(Metasync_Admin::option_group);
1709 do_settings_sections($page_slug);
1710 submit_button();
1711 ?>
1712 </div>
1713 </form>
1714 <?php $this->admin->render_layout_close(); ?>
1715 <?php
1716 }
1717
1718 public function create_admin_google_instant_index_page()
1719 {
1720 // Delegates to the admin class which uses the shared credentials view
1721 $this->admin->create_admin_google_instant_index_page();
1722 }
1723
1724 public function create_admin_google_console_page()
1725 {
1726 $this->admin->render_layout_open('Google Console', 'google_console', 'View Google Search Console data and manage indexing requests.');
1727 $google = function_exists('google_index_direct') ? google_index_direct() : null;
1728 if (is_null($google)) {
1729 $service_info = ['error' => 'Module not loaded'];
1730 } else {
1731 $service_info = $google->get_service_account_info();
1732 }
1733 $is_configured = !isset($service_info['error']);
1734 include_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-google-console.php';
1735 $this->admin->render_layout_close();
1736 }
1737
1738 public function create_admin_bing_console_page()
1739 {
1740 ?>
1741 <?php $this->admin->render_layout_open('Bing Console', 'bing_console', ''); ?>
1742
1743 <?php
1744 require_once plugin_dir_path(dirname(__FILE__)) . 'bing-index/class-metasync-bing-instant-index.php';
1745 $bing_instant_index = new Metasync_Bing_Instant_Index();
1746 $bing_instant_index->show_bing_instant_indexing_console();
1747 ?>
1748 <?php $this->admin->render_layout_close(); ?>
1749 <?php
1750 }
1751
1752 public function create_admin_global_settings_page()
1753 {
1754 $page_slug = Metasync_Admin::$page_slug . '_common-settings';
1755 ?>
1756 <?php $this->admin->render_layout_open('Global Settings', 'general', ''); ?>
1757
1758 <form method="post" action="options.php">
1759 <div class="dashboard-card">
1760 <h2>Global Configuration</h2>
1761 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure global settings that apply across your entire site.</p>
1762 <?php
1763 settings_fields(Metasync_Admin::option_group);
1764 do_settings_sections($page_slug);
1765 ?>
1766 </div>
1767
1768 <!-- Save button removed - using floating notification system instead -->
1769 </form>
1770 <?php $this->admin->render_layout_close(); ?>
1771 <?php
1772 }
1773
1774 public function create_admin_common_meta_settings_page()
1775 {
1776 $page_slug = Metasync_Admin::$page_slug . '_common-meta-settings';
1777 ?>
1778 <?php $this->admin->render_layout_open('Common Meta Settings', 'general', ''); ?>
1779
1780 <form method="post" action="options.php">
1781 <div class="dashboard-card">
1782 <h2>Meta Configuration</h2>
1783 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure common meta tags and SEO settings for your site.</p>
1784 <?php
1785 settings_fields(Metasync_Admin::option_group);
1786 do_settings_sections($page_slug);
1787 ?>
1788 </div>
1789
1790 <!-- Save button removed - using floating notification system instead -->
1791 </form>
1792 <?php $this->admin->render_layout_close(); ?>
1793 <?php
1794 }
1795
1796 public function create_admin_social_meta_page()
1797 {
1798 $page_slug = Metasync_Admin::$page_slug . '_social-meta';
1799 ?>
1800 <?php $this->admin->render_layout_open('Social Meta', 'general', ''); ?>
1801
1802 <form method="post" action="options.php">
1803 <div class="dashboard-card">
1804 <h2>📲 Social Meta Tags</h2>
1805 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how your content appears when shared on social media platforms.</p>
1806 <?php
1807 settings_fields(Metasync_Admin::option_group);
1808 do_settings_sections($page_slug);
1809 ?>
1810 </div>
1811
1812 <!-- Save button removed - using floating notification system instead -->
1813 </form>
1814 <?php $this->admin->render_layout_close(); ?>
1815 <?php
1816 }
1817
1818 public function create_admin_seo_controls_page()
1819 {
1820 $page_slug = Metasync_Admin::$page_slug . '_seo-controls';
1821 ?>
1822 <?php $this->admin->render_layout_open('Indexation Control', 'seo_controls', 'Control how search engines index your content.'); ?>
1823
1824 <!-- Status Messages Container -->
1825 <div id="seo-controls-messages"></div>
1826
1827 <form method="post" action="options.php" id="metaSyncSeoControlsForm">
1828 <div class="dashboard-card">
1829 <div style="display: flex; justify-content: flex-end; margin-bottom: 20px;">
1830 <a href="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-import-external&tab=indexation')); ?>" class="button button-secondary">
1831 <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
1832 </a>
1833 </div>
1834 <?php
1835 settings_fields(Metasync_Admin::option_group);
1836 do_settings_sections($page_slug);
1837
1838 wp_nonce_field('meta_sync_seo_controls_nonce', 'meta_sync_seo_controls_nonce');
1839 ?>
1840 </div>
1841
1842 <!-- Save button removed - using floating notification system instead -->
1843 </form>
1844 <?php $this->admin->render_layout_close(); ?>
1845 <?php
1846 }
1847
1848
1849 public function create_admin_error_logs_page()
1850 {
1851 ?>
1852 <?php $this->admin->render_layout_open('Error Logs', 'general', ''); ?>
1853
1854 <div class="dashboard-card">
1855 <h2>Error Logs Management</h2>
1856 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">View and manage system error logs to troubleshoot issues and monitor plugin performance.</p>
1857 <?php
1858 $error_logs = new Metasync_Error_Logs();
1859
1860 if ($error_logs->can_show_error_logs()) {
1861 $log_content = $error_logs->get_error_logs(50);
1862
1863 if (!empty(trim($log_content))) {
1864 $error_logs->show_copy_button();
1865 $error_logs->show_logs();
1866 $error_logs->show_info();
1867 } else {
1868 echo '<div class="dashboard-empty-state">';
1869 echo '<p style="color: var(--dashboard-text-secondary); font-style: italic; text-align: center; padding: 40px 20px;">�
1870 Log file is empty - no errors recorded.</p>';
1871 echo '</div>';
1872 }
1873 } else {
1874 $error_message = $error_logs->get_error_message();
1875 if (!empty($error_message)) {
1876 echo '<div class="dashboard-empty-state">';
1877 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;">';
1878 echo esc_html($error_message);
1879 echo '</p>';
1880 echo '</div>';
1881 } else {
1882 echo '<div class="dashboard-empty-state">';
1883 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>';
1884 echo '</div>';
1885 }
1886 }
1887 ?>
1888 </div>
1889 <?php $this->admin->render_layout_close(); ?>
1890 <?php
1891 }
1892
1893 // NOTE: The Changes Log (sync log) page is rendered by
1894 // Metasync_Admin::create_admin_sync_log_page() in
1895 // admin/class-metasync-admin.php, which is the callback registered in
1896 // class-metasync-admin-navigation.php. A former duplicate of that method
1897 // lived here but was never wired to a menu and has been removed to avoid
1898 // drift.
1899
1900 public function creat_error_Logs_List()
1901 {
1902 // printf('<h1> Error Logs </h1>');
1903 // $error_log = new Metasync_Error_Logs_Table($this->data_error_log_list);
1904 // $error_log->create_admin_error_log_list_interface();
1905 }
1906
1907 public function create_admin_heartbeat_error_logs_page()
1908 {
1909 ?>
1910 <?php $this->admin->render_layout_open('Heartbeat Error Logs', 'general', ''); ?>
1911
1912 <div class="dashboard-card">
1913 <h2>💓 HeartBeat Error Logs</h2>
1914 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Monitor WordPress heartbeat errors to identify connectivity issues and system problems.</p>
1915 <?php
1916 $heartbeat_errors = new Metasync_HeartBeat_Error_Monitor($this->admin->db_heartbeat_errors);
1917 $heartbeat_errors->create_admin_heartbeat_errors_interface();
1918 ?>
1919 </div>
1920 <?php $this->admin->render_layout_close(); ?>
1921 <?php
1922 }
1923
1924 public function create_admin_bot_statistics_page()
1925 {
1926 require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-otto-bot-statistics.php';
1927 }
1928
1929 // ------------------------------------------------------------------
1930 // Private helpers
1931 // ------------------------------------------------------------------
1932 // The former time_elapsed_string / build_filter_query_string /
1933 // handle_sync_log_ajax helpers here were only used by the dead
1934 // create_admin_sync_log_page() duplicate that has since been removed. The
1935 // live equivalents live on Metasync_Admin.
1936 }
1937