| 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 & Connect |
| 85 |
</a> |
| 86 |
</div> |
| 87 |
<?php |
| 88 |
$this->admin->render_layout_close(false); |
| 89 |
return; |
| 90 |
} |
| 91 |
|
| 92 |
$jwt_token = $this->admin->get_fresh_jwt_token(); |
| 93 |
|
| 94 |
if ($jwt_token) { |
| 95 |
$jwt_parts = explode('.', $jwt_token); |
| 96 |
$jwt_header_info = count($jwt_parts) >= 2 ? 'Valid format (' . count($jwt_parts) . ' parts)' : 'Invalid format'; |
| 97 |
} else { |
| 98 |
} |
| 99 |
|
| 100 |
$public_hash = false; |
| 101 |
if ($jwt_token && $otto_pixel_uuid) { |
| 102 |
$public_hash = $this->admin->fetch_public_hash($otto_pixel_uuid, $jwt_token); |
| 103 |
|
| 104 |
if ($public_hash) { |
| 105 |
} else { |
| 106 |
} |
| 107 |
} else { |
| 108 |
$missing_params = []; |
| 109 |
if (empty($jwt_token)) $missing_params[] = 'JWT_TOKEN'; |
| 110 |
if (empty($otto_pixel_uuid)) $missing_params[] = 'OTTO_UUID'; |
| 111 |
} |
| 112 |
|
| 113 |
$dashboard_domain = Metasync_Admin::get_effective_dashboard_domain(); |
| 114 |
|
| 115 |
if ($public_hash) { |
| 116 |
$iframe_url = $dashboard_domain . '/seo-automation-v3/public?uuid=' . urlencode($otto_pixel_uuid) |
| 117 |
. '&category=onpage_optimizations&subGroup=page_title&public_hash=' . urlencode($public_hash); |
| 118 |
} else { |
| 119 |
$iframe_url = $dashboard_domain . '/seo-automation-v3/tasks?uuid=' . urlencode($otto_pixel_uuid) . '&category=All&Embed=True'; |
| 120 |
if ($jwt_token) { |
| 121 |
$iframe_url .= '&jwtToken=' . urlencode($jwt_token) . '&impersonate=1'; |
| 122 |
} else { |
| 123 |
} |
| 124 |
} |
| 125 |
|
| 126 |
$iframe_url .= '&source=wordpress-plugin-iframe'; |
| 127 |
|
| 128 |
$whitelabel_company_name = Metasync::get_whitelabel_company_name(); |
| 129 |
if ($whitelabel_company_name) { |
| 130 |
$iframe_url .= '&whitelabel=' . urlencode($whitelabel_company_name); |
| 131 |
} |
| 132 |
|
| 133 |
$whitelabel_settings = Metasync::get_whitelabel_settings(); |
| 134 |
$is_whitelabel_domain = !empty($whitelabel_settings['domain']); |
| 135 |
|
| 136 |
$this->admin->render_layout_open('Dashboard', 'dashboard', 'Overview of your SEO performance and plugin status.'); |
| 137 |
?> |
| 138 |
<iframe id="metasync-dashboard-iframe" |
| 139 |
src="<?php echo esc_url($iframe_url); ?>" |
| 140 |
width="100%" |
| 141 |
frameborder="0" |
| 142 |
<?php if (!$is_whitelabel_domain): ?> |
| 143 |
allow="cookies" |
| 144 |
referrerpolicy="strict-origin-when-cross-origin" |
| 145 |
<?php endif; ?> |
| 146 |
style="border: none; margin: 0; padding: 0; min-height: 800px; border-radius: 12px;" |
| 147 |
onload="adjustIframeHeight(this)"> |
| 148 |
</iframe> |
| 149 |
|
| 150 |
<script> |
| 151 |
function adjustIframeHeight(iframe) { |
| 152 |
var attempts = 0; |
| 153 |
var maxAttempts = 20; |
| 154 |
|
| 155 |
function tryAdjustHeight() { |
| 156 |
try { |
| 157 |
attempts++; |
| 158 |
|
| 159 |
var iframeDocument = iframe.contentDocument || iframe.contentWindow.document; |
| 160 |
if (iframeDocument) { |
| 161 |
var body = iframeDocument.body; |
| 162 |
var hasContent = body && (body.children.length > 1 || body.innerText.trim().length > 100); |
| 163 |
|
| 164 |
if (!hasContent && attempts < maxAttempts) { |
| 165 |
setTimeout(tryAdjustHeight, 500); |
| 166 |
return; |
| 167 |
} |
| 168 |
|
| 169 |
var height = Math.max( |
| 170 |
body ? body.scrollHeight : 0, |
| 171 |
body ? body.offsetHeight : 0, |
| 172 |
iframeDocument.documentElement.clientHeight, |
| 173 |
iframeDocument.documentElement.scrollHeight, |
| 174 |
iframeDocument.documentElement.offsetHeight |
| 175 |
); |
| 176 |
|
| 177 |
if (height > 600) { |
| 178 |
iframe.style.height = height + 'px'; |
| 179 |
} else if (attempts < maxAttempts) { |
| 180 |
setTimeout(tryAdjustHeight, 500); |
| 181 |
return; |
| 182 |
} |
| 183 |
} else { |
| 184 |
if (attempts < maxAttempts) { |
| 185 |
setTimeout(tryAdjustHeight, 500); |
| 186 |
return; |
| 187 |
} |
| 188 |
} |
| 189 |
} catch (e) { |
| 190 |
iframe.style.height = '100vh'; |
| 191 |
} |
| 192 |
} |
| 193 |
|
| 194 |
tryAdjustHeight(); |
| 195 |
} |
| 196 |
|
| 197 |
window.addEventListener('resize', function() { |
| 198 |
var iframe = document.getElementById('metasync-dashboard-iframe'); |
| 199 |
if (iframe) { |
| 200 |
adjustIframeHeight(iframe); |
| 201 |
} |
| 202 |
}); |
| 203 |
|
| 204 |
setTimeout(function() { |
| 205 |
var iframe = document.getElementById('metasync-dashboard-iframe'); |
| 206 |
if (iframe) { |
| 207 |
adjustIframeHeight(iframe); |
| 208 |
} |
| 209 |
}, 3000); |
| 210 |
</script> |
| 211 |
<?php |
| 212 |
$this->admin->render_layout_close(false); |
| 213 |
} |
| 214 |
|
| 215 |
// ------------------------------------------------------------------ |
| 216 |
// Settings page (~1000 lines) |
| 217 |
// ------------------------------------------------------------------ |
| 218 |
|
| 219 |
public function create_admin_settings_page() |
| 220 |
{ |
| 221 |
$active_tab = isset($_GET['tab']) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general'; |
| 222 |
|
| 223 |
$whitelabel_settings = Metasync::get_whitelabel_settings(); |
| 224 |
|
| 225 |
$user_password = $whitelabel_settings['settings_password'] ?? ''; |
| 226 |
$hide_settings_enabled = !empty($whitelabel_settings['hide_settings']); |
| 227 |
|
| 228 |
$protected_tabs = []; |
| 229 |
|
| 230 |
if (!empty($user_password)) { |
| 231 |
$protected_tabs[] = 'whitelabel'; |
| 232 |
} |
| 233 |
|
| 234 |
if ($hide_settings_enabled && !empty($user_password)) { |
| 235 |
$protected_tabs = ['general', 'whitelabel', 'advanced']; |
| 236 |
} |
| 237 |
|
| 238 |
$password_protection_enabled = false; |
| 239 |
$password_validated = false; |
| 240 |
$password_error = ''; |
| 241 |
|
| 242 |
if (in_array($active_tab, $protected_tabs)) { |
| 243 |
|
| 244 |
$password_protection_enabled = !empty($user_password); |
| 245 |
|
| 246 |
$auth = new Metasync_Auth_Manager('whitelabel', 1800); |
| 247 |
$password_validated = $auth->has_access(); |
| 248 |
|
| 249 |
if (isset($_POST['whitelabel_password_submit']) && !$password_validated) { |
| 250 |
$password_error = 'Incorrect password. Please try again.'; |
| 251 |
} |
| 252 |
} |
| 253 |
|
| 254 |
$is_authenticated = $password_validated; |
| 255 |
|
| 256 |
$whitelabel_otto_name = Metasync::get_whitelabel_otto_name(); |
| 257 |
|
| 258 |
$page_slug = Metasync_Admin::$page_slug; |
| 259 |
?> |
| 260 |
<?php |
| 261 |
$settings_current_page = 'general'; |
| 262 |
if ($active_tab === 'advanced') $settings_current_page = 'advanced_settings'; |
| 263 |
elseif ($active_tab === 'whitelabel') $settings_current_page = 'whitelabel'; |
| 264 |
$this->admin->render_layout_open('Settings', $settings_current_page, 'Manage all plugin settings and configuration.'); |
| 265 |
?> |
| 266 |
<?php |
| 267 |
/* |
| 268 |
# Temporarily commented out: Clear Cache notice and button (can be re-enabled later) |
| 269 |
|
| 270 |
* <div class="notice notice-success"> |
| 271 |
* <p> |
| 272 |
* <b>Clear all caches at once</b><br/> |
| 273 |
* This will slow down your site until caches are rebuilt |
| 274 |
* <button style="margin-left: 15px;" type ="button" class="button" id="clear_otto_caches" data-toggle="tooltip" data-placement="top" title="Clear all <?php echo $whitelabel_otto_name;?> Caches">Clear <?php echo $whitelabel_otto_name;?> Cache</button> |
| 275 |
* </p> |
| 276 |
* </div> |
| 277 |
*/ |
| 278 |
?> |
| 279 |
|
| 280 |
<?php /* Navigation now rendered in left sidenav via render_layout_open */ ?> |
| 281 |
|
| 282 |
<?php |
| 283 |
if (in_array($active_tab, $protected_tabs)) { |
| 284 |
|
| 285 |
if ($password_protection_enabled && !$password_validated) { |
| 286 |
?> |
| 287 |
<div class="dashboard-card" style="max-width: 500px; margin: 0 auto;"> |
| 288 |
<h2 style="text-align: center;">Protected Section</h2> |
| 289 |
<p style="color: var(--dashboard-text-secondary); margin-bottom: 30px; text-align: center;"> |
| 290 |
<?php |
| 291 |
$plugin_name = Metasync::get_effective_plugin_name(''); |
| 292 |
if ($hide_settings_enabled && !empty($user_password)) { |
| 293 |
printf('Please enter the password to access the %s Settings section.', esc_html($plugin_name)); |
| 294 |
} else { |
| 295 |
echo 'Please enter the password to access the Branding section.'; |
| 296 |
} |
| 297 |
?> |
| 298 |
</p> |
| 299 |
|
| 300 |
<?php if (!empty($password_error)): ?> |
| 301 |
<div style="background: #f8d7da; color: #721c24; padding: 12px; border-radius: 6px; margin-bottom: 20px; border: 1px solid #f5c6cb;"> |
| 302 |
<strong>❌ Access Denied:</strong> <?php echo esc_html($password_error); ?> |
| 303 |
</div> |
| 304 |
<?php endif; ?> |
| 305 |
|
| 306 |
<form method="post" action="" style="max-width: 400px; margin: 0 auto;"> |
| 307 |
<?php wp_nonce_field('whitelabel_password_nonce', 'whitelabel_nonce'); ?> |
| 308 |
|
| 309 |
<div style="margin-bottom: 20px;"> |
| 310 |
<label for="whitelabel_password" style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--dashboard-text-primary);"> |
| 311 |
Validate Password |
| 312 |
</label> |
| 313 |
<input |
| 314 |
type="password" |
| 315 |
id="whitelabel_password" |
| 316 |
name="whitelabel_password" |
| 317 |
placeholder="Enter password to access protected settings" |
| 318 |
style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; box-sizing: border-box;" |
| 319 |
required |
| 320 |
autocomplete="off" |
| 321 |
/> |
| 322 |
</div> |
| 323 |
|
| 324 |
<div style="text-align: center;"> |
| 325 |
<button |
| 326 |
type="submit" |
| 327 |
name="whitelabel_password_submit" |
| 328 |
value="1" |
| 329 |
class="button button-primary" |
| 330 |
style="padding: 12px 24px; font-size: 14px; font-weight: 600;" |
| 331 |
> |
| 332 |
Submit Password |
| 333 |
</button> |
| 334 |
</div> |
| 335 |
</form> |
| 336 |
|
| 337 |
<div style="text-align: center; margin-top: 20px;"> |
| 338 |
<a href="#" id="metasync-forgot-password-link" style="color: #2271b1; text-decoration: none; font-size: 14px;"> |
| 339 |
Forgot Password? |
| 340 |
</a> |
| 341 |
<div id="metasync-recovery-message" style="margin-top: 15px; padding: 12px; border-radius: 6px; display: none;"></div> |
| 342 |
</div> |
| 343 |
|
| 344 |
</div> |
| 345 |
|
| 346 |
<script> |
| 347 |
jQuery(document).ready(function($) { |
| 348 |
$('#whitelabel_password').focus(); |
| 349 |
|
| 350 |
$('#whitelabel_password').on('keypress', function(e) { |
| 351 |
if (e.which === 13) { |
| 352 |
$(this).closest('form').submit(); |
| 353 |
} |
| 354 |
}); |
| 355 |
|
| 356 |
$('#metasync-forgot-password-link').on('click', function(e) { |
| 357 |
e.preventDefault(); |
| 358 |
|
| 359 |
var $link = $(this); |
| 360 |
var $message = $('#metasync-recovery-message'); |
| 361 |
|
| 362 |
$link.css('pointer-events', 'none').css('opacity', '0.6'); |
| 363 |
$message.removeClass('success error').hide(); |
| 364 |
$message.html('⏳ Sending recovery email...').css('background', '#f0f6fc').css('color', '#0c5ba5').css('border', '1px solid #cfe2f3').fadeIn(200); |
| 365 |
|
| 366 |
$.ajax({ |
| 367 |
url: ajaxurl, |
| 368 |
type: 'POST', |
| 369 |
data: { |
| 370 |
action: 'metasync_recover_password', |
| 371 |
nonce: '<?php echo wp_create_nonce('metasync_recover_password_nonce'); ?>' |
| 372 |
}, |
| 373 |
success: function(response) { |
| 374 |
$link.css('pointer-events', 'auto').css('opacity', '1'); |
| 375 |
|
| 376 |
if (response.success) { |
| 377 |
$message.addClass('success').html('� |
| 378 |
' + response.data.message) |
| 379 |
.css('background', '#d4edda') |
| 380 |
.css('color', '#155724') |
| 381 |
.css('border', '1px solid #c3e6cb'); |
| 382 |
} else { |
| 383 |
$message.addClass('error').html('❌ ' + response.data.message) |
| 384 |
.css('background', '#f8d7da') |
| 385 |
.css('color', '#721c24') |
| 386 |
.css('border', '1px solid #f5c6cb'); |
| 387 |
} |
| 388 |
}, |
| 389 |
error: function() { |
| 390 |
$link.css('pointer-events', 'auto').css('opacity', '1'); |
| 391 |
$message.addClass('error').html('❌ An error occurred. Please try again.') |
| 392 |
.css('background', '#f8d7da') |
| 393 |
.css('color', '#721c24') |
| 394 |
.css('border', '1px solid #f5c6cb'); |
| 395 |
} |
| 396 |
}); |
| 397 |
}); |
| 398 |
}); |
| 399 |
</script> |
| 400 |
<?php |
| 401 |
return; |
| 402 |
} elseif (!$password_protection_enabled) { |
| 403 |
?> |
| 404 |
<div class="notice notice-info" style="margin: 15px 0;"> |
| 405 |
<p> |
| 406 |
<strong>Security Tip:</strong> You can set a custom password in the White Label Settings to protect this section. |
| 407 |
</p> |
| 408 |
</div> |
| 409 |
<?php |
| 410 |
} |
| 411 |
} |
| 412 |
?> |
| 413 |
|
| 414 |
<form method="post" action="options.php?tab=<?php echo esc_attr( $active_tab ); ?>" id="metaSyncGeneralSetting"> |
| 415 |
<?php |
| 416 |
settings_fields(Metasync_Admin::option_group); |
| 417 |
|
| 418 |
wp_nonce_field('meta_sync_general_setting_nonce', 'meta_sync_nonce'); |
| 419 |
|
| 420 |
if ($active_tab == 'general') { |
| 421 |
?> |
| 422 |
<div class="dashboard-card"> |
| 423 |
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> |
| 424 |
<div style="flex: 1;"> |
| 425 |
<h2 style="margin: 0;">General Configuration</h2> |
| 426 |
<p style="color: var(--dashboard-text-secondary); margin: 5px 0 0 0;">Configure your <?php echo esc_html(Metasync::get_effective_plugin_name()); ?> API, plugin features, caching, and general settings.</p> |
| 427 |
</div> |
| 428 |
<?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?> |
| 429 |
<div> |
| 430 |
<?php Metasync_Compatibility_Checker::instance()->render_lock_button('general'); ?> |
| 431 |
</div> |
| 432 |
<?php endif; ?> |
| 433 |
</div> |
| 434 |
|
| 435 |
<?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?> |
| 436 |
<div style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 12px; border-radius: 4px; margin-bottom: 20px;"> |
| 437 |
<strong>� |
| 438 |
Access Granted:</strong> You have successfully authenticated and can now modify settings. |
| 439 |
</div> |
| 440 |
<?php endif; ?> |
| 441 |
|
| 442 |
<?php |
| 443 |
$this->admin->render_accordion_sections(Metasync_Admin::$page_slug . '_general'); |
| 444 |
?> |
| 445 |
</div> |
| 446 |
|
| 447 |
<div class="dashboard-card"> |
| 448 |
<h2>Content Genius Synchronization<?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> |
| 449 |
<p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Sync your categories and user/author data with <?php echo esc_html(Metasync::get_effective_plugin_name()); ?>.</p> |
| 450 |
<button type="button" class="button button-primary" id="sendAuthToken"> |
| 451 |
Sync Now |
| 452 |
</button> |
| 453 |
</div> |
| 454 |
|
| 455 |
<div class="dashboard-card"> |
| 456 |
<h2>Setup Wizard</h2> |
| 457 |
<p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Run the setup wizard to configure your plugin, import from other SEO plugins, and optimize your settings in just a few minutes.</p> |
| 458 |
<a href="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-setup-wizard')); ?>" class="button button-primary" style="text-decoration: none;"> |
| 459 |
Start Setup Wizard |
| 460 |
</a> |
| 461 |
</div> |
| 462 |
<?php |
| 463 |
} elseif ($active_tab == 'whitelabel') { |
| 464 |
$whitelabel_settings = Metasync::get_whitelabel_settings(); |
| 465 |
$user_password = $whitelabel_settings['settings_password'] ?? ''; |
| 466 |
$password_protection_enabled = !empty($user_password); |
| 467 |
?> |
| 468 |
<div class="dashboard-card"> |
| 469 |
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> |
| 470 |
<div> |
| 471 |
<h2>White Label Branding<?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> |
| 472 |
<p style="color: var(--dashboard-text-secondary); margin: 5px 0 0 0;">Customize the plugin appearance with your own branding and logo.</p> |
| 473 |
</div> |
| 474 |
<?php if ($password_protection_enabled && $is_authenticated): ?> |
| 475 |
<div> |
| 476 |
<?php Metasync_Compatibility_Checker::instance()->render_lock_button('whitelabel'); ?> |
| 477 |
<?php Metasync::render_tooltip_icon('wl_lock_section', 'Re-lock this section now. You will need the password to open it again.'); ?> |
| 478 |
</div> |
| 479 |
<?php endif; ?> |
| 480 |
</div> |
| 481 |
|
| 482 |
<?php if ($password_protection_enabled && $is_authenticated): ?> |
| 483 |
<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;"> |
| 484 |
<div> |
| 485 |
<strong>� |
| 486 |
Access Granted:</strong> You have successfully authenticated and can now modify white label settings. |
| 487 |
</div> |
| 488 |
</div> |
| 489 |
|
| 490 |
<?php endif; ?> |
| 491 |
|
| 492 |
<?php |
| 493 |
do_settings_sections(Metasync_Admin::$page_slug . '_branding'); |
| 494 |
?> |
| 495 |
</div> |
| 496 |
|
| 497 |
<!-- Color Palette Customization section --> |
| 498 |
<div class="dashboard-card" id="metasync-color-palette-section"> |
| 499 |
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> |
| 500 |
<div> |
| 501 |
<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> |
| 502 |
<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> |
| 503 |
</div> |
| 504 |
</div> |
| 505 |
|
| 506 |
<?php |
| 507 |
$color_palette = isset($whitelabel_settings['color_palette']) && is_array($whitelabel_settings['color_palette']) |
| 508 |
? $whitelabel_settings['color_palette'] |
| 509 |
: array(); |
| 510 |
$dark_colors = isset($color_palette['dark']) && is_array($color_palette['dark']) ? $color_palette['dark'] : array(); |
| 511 |
$light_colors = isset($color_palette['light']) && is_array($color_palette['light']) ? $color_palette['light'] : array(); |
| 512 |
|
| 513 |
$palette_fields = array( |
| 514 |
'dashboard-bg' => array( |
| 515 |
'label' => 'Background', |
| 516 |
'hint' => 'Main page background behind all content', |
| 517 |
'dark' => '#0f1419', |
| 518 |
'light' => '#f8f9fa', |
| 519 |
), |
| 520 |
'dashboard-card-bg' => array( |
| 521 |
'label' => 'Card Background', |
| 522 |
'hint' => 'Background of cards, panels, and content boxes', |
| 523 |
'dark' => '#1a1f26', |
| 524 |
'light' => '#ffffff', |
| 525 |
), |
| 526 |
'dashboard-card-hover' => array( |
| 527 |
'label' => 'Card Hover', |
| 528 |
'hint' => 'Card background when you hover over it', |
| 529 |
'dark' => '#222831', |
| 530 |
'light' => '#f1f3f5', |
| 531 |
), |
| 532 |
'dashboard-text-primary' => array( |
| 533 |
'label' => 'Primary Text', |
| 534 |
'hint' => 'Headings, body text, and main content', |
| 535 |
'dark' => '#ffffff', |
| 536 |
'light' => '#1a1f26', |
| 537 |
), |
| 538 |
'dashboard-text-secondary' => array( |
| 539 |
'label' => 'Secondary Text', |
| 540 |
'hint' => 'Descriptions, hints, and less important text', |
| 541 |
'dark' => '#9ca3af', |
| 542 |
'light' => '#6b7280', |
| 543 |
), |
| 544 |
'dashboard-accent' => array( |
| 545 |
'label' => 'Accent Color', |
| 546 |
'hint' => 'Buttons, links, and highlighted elements', |
| 547 |
'dark' => '#3b82f6', |
| 548 |
'light' => '#3b82f6', |
| 549 |
), |
| 550 |
'dashboard-accent-hover' => array( |
| 551 |
'label' => 'Accent Hover', |
| 552 |
'hint' => 'Buttons and links when hovered', |
| 553 |
'dark' => '#2563eb', |
| 554 |
'light' => '#2563eb', |
| 555 |
), |
| 556 |
'dashboard-success' => array( |
| 557 |
'label' => 'Success', |
| 558 |
'hint' => 'Success messages, connected status, positive indicators', |
| 559 |
'dark' => '#10b981', |
| 560 |
'light' => '#10b981', |
| 561 |
), |
| 562 |
'dashboard-warning' => array( |
| 563 |
'label' => 'Warning', |
| 564 |
'hint' => 'Warning alerts and caution indicators', |
| 565 |
'dark' => '#f59e0b', |
| 566 |
'light' => '#f59e0b', |
| 567 |
), |
| 568 |
'dashboard-error' => array( |
| 569 |
'label' => 'Error', |
| 570 |
'hint' => 'Error messages and critical alerts', |
| 571 |
'dark' => '#ef4444', |
| 572 |
'light' => '#ef4444', |
| 573 |
), |
| 574 |
'dashboard-border' => array( |
| 575 |
'label' => 'Border', |
| 576 |
'hint' => 'Card borders, dividers, and separators', |
| 577 |
'dark' => '#374151', |
| 578 |
'light' => '#e5e7eb', |
| 579 |
), |
| 580 |
'dashboard-gradient-primary-from' => array( |
| 581 |
'label' => 'Button Gradient Start', |
| 582 |
'hint' => 'Left/top color of buttons, save bars, and active tabs', |
| 583 |
'dark' => '#667eea', |
| 584 |
'light' => '#667eea', |
| 585 |
), |
| 586 |
'dashboard-gradient-primary-to' => array( |
| 587 |
'label' => 'Button Gradient End', |
| 588 |
'hint' => 'Right/bottom color of buttons, save bars, and active tabs', |
| 589 |
'dark' => '#764ba2', |
| 590 |
'light' => '#764ba2', |
| 591 |
), |
| 592 |
'dashboard-gradient-accent-from' => array( |
| 593 |
'label' => 'Accent Gradient Start', |
| 594 |
'hint' => 'Left/top color of secondary badges and highlight bars', |
| 595 |
'dark' => '#f093fb', |
| 596 |
'light' => '#f093fb', |
| 597 |
), |
| 598 |
'dashboard-gradient-accent-to' => array( |
| 599 |
'label' => 'Accent Gradient End', |
| 600 |
'hint' => 'Right/bottom color of secondary badges and highlight bars', |
| 601 |
'dark' => '#f5576c', |
| 602 |
'light' => '#f5576c', |
| 603 |
), |
| 604 |
); |
| 605 |
?> |
| 606 |
|
| 607 |
<!-- Theme tabs --> |
| 608 |
<div style="display: flex; gap: 8px; margin-bottom: 20px;"> |
| 609 |
<button type="button" class="metasync-palette-tab active" data-palette="dark" |
| 610 |
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;"> |
| 611 |
☾ Dark Theme |
| 612 |
</button> |
| 613 |
<button type="button" class="metasync-palette-tab" data-palette="light" |
| 614 |
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;"> |
| 615 |
☀ Light Theme |
| 616 |
</button> |
| 617 |
</div> |
| 618 |
|
| 619 |
<!-- Dark theme palette --> |
| 620 |
<div class="metasync-palette-panel active" id="metasync-palette-dark"> |
| 621 |
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px;"> |
| 622 |
<?php foreach ($palette_fields as $var_name => $field): ?> |
| 623 |
<div style="background: var(--dashboard-card-hover); border: 1px solid var(--dashboard-border); border-radius: 10px; padding: 14px;"> |
| 624 |
<label style="display: block; font-weight: 600; font-size: 13px; color: var(--dashboard-text-primary); margin-bottom: 2px;"> |
| 625 |
<?php echo esc_html($field['label']); ?> |
| 626 |
</label> |
| 627 |
<p style="font-size: 12px; color: var(--dashboard-text-secondary); margin: 0 0 10px 0;"><?php echo esc_html($field['hint']); ?></p> |
| 628 |
<div style="display: flex; align-items: center; gap: 10px;"> |
| 629 |
<input type="color" |
| 630 |
class="metasync-color-field" |
| 631 |
name="<?php echo esc_attr(Metasync_Admin::option_key); ?>[whitelabel][color_palette][dark][<?php echo esc_attr($var_name); ?>]" |
| 632 |
value="<?php echo esc_attr(!empty($dark_colors[$var_name]) ? $dark_colors[$var_name] : $field['dark']); ?>" |
| 633 |
data-default-color="<?php echo esc_attr($field['dark']); ?>" |
| 634 |
data-css-var="<?php echo esc_attr($var_name); ?>" |
| 635 |
data-color-theme="dark" |
| 636 |
style="width: 44px; height: 36px; padding: 2px; border: 1px solid var(--dashboard-border); border-radius: 8px; cursor: pointer; background: transparent;" |
| 637 |
/> |
| 638 |
<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> |
| 639 |
</div> |
| 640 |
</div> |
| 641 |
<?php endforeach; ?> |
| 642 |
</div> |
| 643 |
<div style="margin-top: 16px; text-align: right;"> |
| 644 |
<button type="button" id="metasync-reset-dark-palette" data-reset-theme="dark" |
| 645 |
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;"> |
| 646 |
Reset Dark Theme to Defaults |
| 647 |
</button> |
| 648 |
</div> |
| 649 |
</div> |
| 650 |
|
| 651 |
<!-- Light theme palette --> |
| 652 |
<div class="metasync-palette-panel" id="metasync-palette-light"> |
| 653 |
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px;"> |
| 654 |
<?php foreach ($palette_fields as $var_name => $field): ?> |
| 655 |
<div style="background: var(--dashboard-card-hover); border: 1px solid var(--dashboard-border); border-radius: 10px; padding: 14px;"> |
| 656 |
<label style="display: block; font-weight: 600; font-size: 13px; color: var(--dashboard-text-primary); margin-bottom: 2px;"> |
| 657 |
<?php echo esc_html($field['label']); ?> |
| 658 |
</label> |
| 659 |
<p style="font-size: 12px; color: var(--dashboard-text-secondary); margin: 0 0 10px 0;"><?php echo esc_html($field['hint']); ?></p> |
| 660 |
<div style="display: flex; align-items: center; gap: 10px;"> |
| 661 |
<input type="color" |
| 662 |
class="metasync-color-field" |
| 663 |
name="<?php echo esc_attr(Metasync_Admin::option_key); ?>[whitelabel][color_palette][light][<?php echo esc_attr($var_name); ?>]" |
| 664 |
value="<?php echo esc_attr(!empty($light_colors[$var_name]) ? $light_colors[$var_name] : $field['light']); ?>" |
| 665 |
data-default-color="<?php echo esc_attr($field['light']); ?>" |
| 666 |
data-css-var="<?php echo esc_attr($var_name); ?>" |
| 667 |
data-color-theme="light" |
| 668 |
style="width: 44px; height: 36px; padding: 2px; border: 1px solid var(--dashboard-border); border-radius: 8px; cursor: pointer; background: transparent;" |
| 669 |
/> |
| 670 |
<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> |
| 671 |
</div> |
| 672 |
</div> |
| 673 |
<?php endforeach; ?> |
| 674 |
</div> |
| 675 |
<div style="margin-top: 16px; text-align: right;"> |
| 676 |
<button type="button" id="metasync-reset-light-palette" data-reset-theme="light" |
| 677 |
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;"> |
| 678 |
Reset Light Theme to Defaults |
| 679 |
</button> |
| 680 |
</div> |
| 681 |
</div> |
| 682 |
</div> |
| 683 |
|
| 684 |
<style> |
| 685 |
.metasync-palette-tab.active { |
| 686 |
background: var(--dashboard-gradient-primary) !important; |
| 687 |
color: #fff !important; |
| 688 |
} |
| 689 |
.metasync-palette-tab:not(.active) { |
| 690 |
background: transparent !important; |
| 691 |
color: var(--dashboard-text-secondary) !important; |
| 692 |
} |
| 693 |
.metasync-palette-panel { display: none !important; } |
| 694 |
.metasync-palette-panel.active { display: block !important; } |
| 695 |
</style> |
| 696 |
|
| 697 |
<script> |
| 698 |
jQuery(function($) { |
| 699 |
// Tab switching |
| 700 |
$(document).on('click', '.metasync-palette-tab', function(e) { |
| 701 |
e.preventDefault(); |
| 702 |
e.stopPropagation(); |
| 703 |
var target = $(this).data('palette'); |
| 704 |
$('.metasync-palette-tab').removeClass('active'); |
| 705 |
$(this).addClass('active'); |
| 706 |
$('.metasync-palette-panel').removeClass('active'); |
| 707 |
$('#metasync-palette-' + target).addClass('active'); |
| 708 |
}); |
| 709 |
|
| 710 |
// Reset to defaults |
| 711 |
var paletteDefaults = <?php echo wp_json_encode(array( |
| 712 |
'dark' => array_combine( |
| 713 |
array_keys($palette_fields), |
| 714 |
array_column($palette_fields, 'dark') |
| 715 |
), |
| 716 |
'light' => array_combine( |
| 717 |
array_keys($palette_fields), |
| 718 |
array_column($palette_fields, 'light') |
| 719 |
) |
| 720 |
)); ?>; |
| 721 |
|
| 722 |
$(document).on('click', '#metasync-reset-dark-palette, #metasync-reset-light-palette', function(e) { |
| 723 |
e.preventDefault(); |
| 724 |
e.stopPropagation(); |
| 725 |
var theme = $(this).data('reset-theme'); |
| 726 |
var defs = paletteDefaults[theme]; |
| 727 |
if (!defs) return; |
| 728 |
$('#metasync-color-palette-section .metasync-color-field[data-color-theme="' + theme + '"]').each(function() { |
| 729 |
var $input = $(this); |
| 730 |
var varName = $input.data('css-var'); |
| 731 |
var defVal = defs[varName] || ''; |
| 732 |
if (defVal) { |
| 733 |
$input.val(defVal); |
| 734 |
$input.siblings('.metasync-color-hex-display').text(defVal); |
| 735 |
} |
| 736 |
}); |
| 737 |
// Trigger live preview if external JS is loaded |
| 738 |
$input = $('#metasync-color-palette-section .metasync-color-field').first(); |
| 739 |
if ($input.length) $input.trigger('input'); |
| 740 |
}); |
| 741 |
}); |
| 742 |
</script> |
| 743 |
|
| 744 |
<!-- Export Whitelabel Settings section --> |
| 745 |
<div class="dashboard-card"> |
| 746 |
<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> |
| 747 |
<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> |
| 748 |
<button type="button" class="button button-primary" id="metasync-export-whitelabel-btn" style="padding: 10px 20px; font-size: 14px; font-weight: 600;"> |
| 749 |
Export Plugin with Whitelabel Settings |
| 750 |
</button> |
| 751 |
<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> |
| 752 |
</div> |
| 753 |
|
| 754 |
<!-- Advanced Access Control section --> |
| 755 |
<?php Metasync_Access_Control_UI::render_access_control_table(); ?> |
| 756 |
|
| 757 |
<!-- Custom Modal for Alerts --> |
| 758 |
<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);"> |
| 759 |
<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;"> |
| 760 |
<div style="background: white; border-radius: 10px; padding: 0; overflow: hidden;"> |
| 761 |
<!-- Header --> |
| 762 |
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 24px; text-align: center;"> |
| 763 |
<div id="modal-icon" style="font-size: 48px; margin-bottom: 12px; color: #fff;">⚠</div> |
| 764 |
<h2 id="modal-title" style="color: white; margin: 0; font-size: 24px; font-weight: 600;">Password Required</h2> |
| 765 |
</div> |
| 766 |
|
| 767 |
<!-- Body --> |
| 768 |
<div style="padding: 32px 24px;"> |
| 769 |
<p id="modal-message" style="color: #4a5568; font-size: 15px; line-height: 1.6; text-align: center; margin: 0;"> |
| 770 |
You must set a White Label Settings Password before enabling "Hide Settings". |
| 771 |
</p> |
| 772 |
</div> |
| 773 |
|
| 774 |
<!-- Footer --> |
| 775 |
<div style="padding: 0 24px 24px; text-align: center;"> |
| 776 |
<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;"> |
| 777 |
Got It |
| 778 |
</button> |
| 779 |
</div> |
| 780 |
</div> |
| 781 |
</div> |
| 782 |
</div> |
| 783 |
|
| 784 |
<style> |
| 785 |
@keyframes modalSlideIn { |
| 786 |
from { |
| 787 |
opacity: 0; |
| 788 |
transform: translateY(-50px); |
| 789 |
} |
| 790 |
to { |
| 791 |
opacity: 1; |
| 792 |
transform: translateY(0); |
| 793 |
} |
| 794 |
} |
| 795 |
|
| 796 |
#modal-close-btn:hover { |
| 797 |
transform: translateY(-2px); |
| 798 |
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5); |
| 799 |
} |
| 800 |
</style> |
| 801 |
|
| 802 |
<script> |
| 803 |
jQuery(document).ready(function($) { |
| 804 |
var isShowingModal = false; |
| 805 |
|
| 806 |
function showModal(icon, title, message) { |
| 807 |
isShowingModal = true; |
| 808 |
$('#modal-icon').text(icon); |
| 809 |
$('#modal-title').text(title); |
| 810 |
$('#modal-message').html(message); |
| 811 |
$('#metasync-custom-modal').fadeIn(200); |
| 812 |
|
| 813 |
setTimeout(function() { |
| 814 |
isShowingModal = false; |
| 815 |
}, 300); |
| 816 |
} |
| 817 |
|
| 818 |
$('#modal-close-btn').on('click', function(e) { |
| 819 |
e.preventDefault(); |
| 820 |
e.stopPropagation(); |
| 821 |
$('#metasync-custom-modal').fadeOut(200); |
| 822 |
return false; |
| 823 |
}); |
| 824 |
|
| 825 |
$('#metasync-custom-modal').on('click', function(e) { |
| 826 |
if (e.target === this) { |
| 827 |
$(this).fadeOut(200); |
| 828 |
} |
| 829 |
}); |
| 830 |
|
| 831 |
function hasPassword() { |
| 832 |
var passwordField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]'); |
| 833 |
var passwordValue = passwordField.val(); |
| 834 |
return passwordValue && passwordValue.length > 0; |
| 835 |
} |
| 836 |
|
| 837 |
function hasRecoveryEmail() { |
| 838 |
var recoveryEmailField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][recovery_email]"]'); |
| 839 |
var emailValue = recoveryEmailField.val(); |
| 840 |
return emailValue && emailValue.length > 0 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailValue); |
| 841 |
} |
| 842 |
|
| 843 |
var passwordField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]'); |
| 844 |
var recoveryEmailField = $('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][recovery_email]"]'); |
| 845 |
|
| 846 |
passwordField.on('blur', function() { |
| 847 |
if (hasPassword() && !hasRecoveryEmail()) { |
| 848 |
showModal( |
| 849 |
'📧', |
| 850 |
'Recovery Email Required', |
| 851 |
'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.' |
| 852 |
); |
| 853 |
recoveryEmailField.focus(); |
| 854 |
} |
| 855 |
}); |
| 856 |
|
| 857 |
passwordField.on('input', function() { |
| 858 |
if (hasPassword()) { |
| 859 |
recoveryEmailField.attr('required', true); |
| 860 |
recoveryEmailField.closest('tr').find('th').addClass('required-field'); |
| 861 |
} else { |
| 862 |
recoveryEmailField.removeAttr('required'); |
| 863 |
recoveryEmailField.closest('tr').find('th').removeClass('required-field'); |
| 864 |
} |
| 865 |
}); |
| 866 |
|
| 867 |
passwordField.trigger('input'); |
| 868 |
|
| 869 |
var hideSettingsCheckbox = $('#checkbox_hide_settings'); |
| 870 |
var originalCheckboxState = hideSettingsCheckbox.is(':checked'); |
| 871 |
|
| 872 |
hideSettingsCheckbox.on('click', function(e) { |
| 873 |
if (!originalCheckboxState && !hasPassword()) { |
| 874 |
e.preventDefault(); |
| 875 |
e.stopImmediatePropagation(); |
| 876 |
|
| 877 |
showModal( |
| 878 |
'⚠', |
| 879 |
'Password Required', |
| 880 |
'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.' |
| 881 |
); |
| 882 |
|
| 883 |
setTimeout(function() { |
| 884 |
$('input[name="<?php echo Metasync_Admin::option_key; ?>[whitelabel][settings_password]"]').focus(); |
| 885 |
}, 300); |
| 886 |
|
| 887 |
return false; |
| 888 |
} |
| 889 |
|
| 890 |
setTimeout(function() { |
| 891 |
originalCheckboxState = hideSettingsCheckbox.is(':checked'); |
| 892 |
}, 0); |
| 893 |
}); |
| 894 |
|
| 895 |
passwordField.on('keydown', function(e) { |
| 896 |
var currentValue = $(this).val(); |
| 897 |
|
| 898 |
if (hideSettingsCheckbox.is(':checked') && |
| 899 |
(currentValue.length <= 1 || !currentValue) && |
| 900 |
(e.keyCode === 8 || e.keyCode === 46)) { |
| 901 |
|
| 902 |
e.preventDefault(); |
| 903 |
e.stopImmediatePropagation(); |
| 904 |
|
| 905 |
if (!isShowingModal) { |
| 906 |
showModal( |
| 907 |
'⚠', |
| 908 |
'Cannot Remove Password', |
| 909 |
'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.' |
| 910 |
); |
| 911 |
} |
| 912 |
|
| 913 |
return false; |
| 914 |
} |
| 915 |
}); |
| 916 |
|
| 917 |
$(document).on('metasync_settings_saved', function() { |
| 918 |
}); |
| 919 |
|
| 920 |
$(document).on('keydown', function(e) { |
| 921 |
if (e.key === 'Escape') { |
| 922 |
$('#metasync-custom-modal').fadeOut(200); |
| 923 |
} |
| 924 |
}); |
| 925 |
|
| 926 |
$('#metasync-export-whitelabel-btn').on('click', function(e) { |
| 927 |
e.preventDefault(); |
| 928 |
|
| 929 |
var $button = $(this); |
| 930 |
var originalText = $button.html(); |
| 931 |
|
| 932 |
$button.prop('disabled', true).html('⏳ Exporting...'); |
| 933 |
|
| 934 |
var form = $('<form>', { |
| 935 |
'method': 'POST', |
| 936 |
'action': '<?php echo esc_js(admin_url('admin-post.php')); ?>', |
| 937 |
'target': '_blank' |
| 938 |
}); |
| 939 |
|
| 940 |
form.append($('<input>', { |
| 941 |
'type': 'hidden', |
| 942 |
'name': 'action', |
| 943 |
'value': 'metasync_export_whitelabel_settings' |
| 944 |
})); |
| 945 |
|
| 946 |
form.append($('<input>', { |
| 947 |
'type': 'hidden', |
| 948 |
'name': '_wpnonce', |
| 949 |
'value': '<?php echo wp_create_nonce('metasync_export_whitelabel'); ?>' |
| 950 |
})); |
| 951 |
|
| 952 |
$('body').append(form); |
| 953 |
form.submit(); |
| 954 |
|
| 955 |
setTimeout(function() { |
| 956 |
form.remove(); |
| 957 |
$button.prop('disabled', false).html(originalText); |
| 958 |
}, 2000); |
| 959 |
}); |
| 960 |
}); |
| 961 |
</script> |
| 962 |
<?php |
| 963 |
} elseif ($active_tab == 'advanced') { |
| 964 |
if (!Metasync_Access_Control::user_can_access('hide_advanced')) { |
| 965 |
echo '<div class="dashboard-card" style="background: var(--dashboard-card-bg); padding: 25px; border-radius: 8px; text-align: center;">'; |
| 966 |
echo '<h2 style="color: var(--dashboard-text-primary);">Access Denied</h2>'; |
| 967 |
echo '<p style="color: var(--dashboard-text-secondary);">You do not have permission to access this page.</p>'; |
| 968 |
echo '</div>'; |
| 969 |
} else { |
| 970 |
?> |
| 971 |
<div class="dashboard-card"> |
| 972 |
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> |
| 973 |
<div style="flex: 1;"> |
| 974 |
<h2 style="margin: 0;">🧰 Advanced Settings</h2> |
| 975 |
<p style="color: var(--dashboard-text-secondary); margin: 5px 0 0 0;">Technical utilities for troubleshooting and connectivity checks.</p> |
| 976 |
</div> |
| 977 |
<?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?> |
| 978 |
<div> |
| 979 |
<?php Metasync_Compatibility_Checker::instance()->render_lock_button('advanced'); ?> |
| 980 |
</div> |
| 981 |
<?php endif; ?> |
| 982 |
</div> |
| 983 |
|
| 984 |
<?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?> |
| 985 |
<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;"> |
| 986 |
<strong>� |
| 987 |
Access Granted:</strong> You have successfully authenticated and can now modify settings. |
| 988 |
</div> |
| 989 |
<?php endif; ?> |
| 990 |
|
| 991 |
<?php |
| 992 |
if (isset($_GET['debug_mode_enabled']) && $_GET['debug_mode_enabled'] == '1'): |
| 993 |
$indefinite = isset($_GET['indefinite']) && $_GET['indefinite'] == '1'; |
| 994 |
?> |
| 995 |
<div class="notice notice-success inline" style="margin-bottom: 20px;"> |
| 996 |
<p> |
| 997 |
<strong>� |
| 998 |
Success!</strong> Debug mode has been enabled<?php echo $indefinite ? ' indefinitely' : ' for 24 hours'; ?>. |
| 999 |
</p> |
| 1000 |
</div> |
| 1001 |
<?php endif; ?> |
| 1002 |
|
| 1003 |
<?php if (isset($_GET['debug_mode_disabled']) && $_GET['debug_mode_disabled'] == '1'): ?> |
| 1004 |
<div class="notice notice-info inline" style="margin-bottom: 20px;"> |
| 1005 |
<p> |
| 1006 |
<strong>ℹ️ Debug Mode Disabled:</strong> Debug mode has been successfully disabled. |
| 1007 |
</p> |
| 1008 |
</div> |
| 1009 |
<?php endif; ?> |
| 1010 |
|
| 1011 |
<?php if (isset($_GET['debug_mode_extended']) && $_GET['debug_mode_extended'] == '1'): ?> |
| 1012 |
<div class="notice notice-success inline" style="margin-bottom: 20px;"> |
| 1013 |
<p> |
| 1014 |
<strong>� |
| 1015 |
Extended!</strong> Debug mode has been extended for another 24 hours. |
| 1016 |
</p> |
| 1017 |
</div> |
| 1018 |
<?php endif; ?> |
| 1019 |
|
| 1020 |
<?php if (isset($_GET['debug_error']) && $_GET['debug_error'] == '1'): ?> |
| 1021 |
<div class="notice notice-error inline" style="margin-bottom: 20px;"> |
| 1022 |
<p> |
| 1023 |
<strong>❌ Error:</strong> Unable to perform the debug mode operation. Please try again. |
| 1024 |
</p> |
| 1025 |
</div> |
| 1026 |
<?php endif; ?> |
| 1027 |
|
| 1028 |
<?php |
| 1029 |
$this->admin->render_advanced_accordion(); |
| 1030 |
?> |
| 1031 |
</div> |
| 1032 |
|
| 1033 |
<?php |
| 1034 |
} |
| 1035 |
} |
| 1036 |
?> |
| 1037 |
|
| 1038 |
<!-- Save button removed - using floating notification system instead --> |
| 1039 |
|
| 1040 |
</form> |
| 1041 |
|
| 1042 |
<!-- Lock Section Button Handler (runs on all tabs) --> |
| 1043 |
<script> |
| 1044 |
jQuery(document).ready(function($) { |
| 1045 |
var ajaxUrl = (typeof window.ajaxurl !== 'undefined' && window.ajaxurl) |
| 1046 |
? window.ajaxurl |
| 1047 |
: '<?php echo esc_js(admin_url('admin-ajax.php')); ?>'; |
| 1048 |
$('.metasync-lock-btn').on('click', function(e) { |
| 1049 |
e.preventDefault(); |
| 1050 |
e.stopPropagation(); |
| 1051 |
|
| 1052 |
var currentTab = $(this).data('tab'); |
| 1053 |
|
| 1054 |
var logoutForm = $('<form>', { |
| 1055 |
'method': 'post', |
| 1056 |
'action': '' |
| 1057 |
}); |
| 1058 |
|
| 1059 |
logoutForm.append('<?php echo wp_nonce_field("whitelabel_logout_nonce", "whitelabel_logout_nonce", true, false); ?>'); |
| 1060 |
|
| 1061 |
logoutForm.append($('<input>', { |
| 1062 |
'type': 'hidden', |
| 1063 |
'name': 'whitelabel_logout', |
| 1064 |
'value': '1' |
| 1065 |
})); |
| 1066 |
|
| 1067 |
$('body').append(logoutForm); |
| 1068 |
logoutForm.submit(); |
| 1069 |
|
| 1070 |
return false; |
| 1071 |
}); |
| 1072 |
}); |
| 1073 |
</script> |
| 1074 |
|
| 1075 |
<!-- Host Blocking-generated JavaScript --> |
| 1076 |
<script> |
| 1077 |
jQuery(document).ready(function($) { |
| 1078 |
var ajaxUrl = (typeof window.ajaxurl !== 'undefined' && window.ajaxurl) |
| 1079 |
? window.ajaxurl |
| 1080 |
: '<?php echo esc_js(admin_url('admin-ajax.php')); ?>'; |
| 1081 |
|
| 1082 |
$(document).on('click', '#test-get-request', function(e) { |
| 1083 |
e.preventDefault(); |
| 1084 |
e.stopPropagation(); |
| 1085 |
runHostTest('GET'); |
| 1086 |
return false; |
| 1087 |
}); |
| 1088 |
|
| 1089 |
$(document).on('click', '#test-post-request', function(e) { |
| 1090 |
e.preventDefault(); |
| 1091 |
e.stopPropagation(); |
| 1092 |
runHostTest('POST'); |
| 1093 |
return false; |
| 1094 |
}); |
| 1095 |
|
| 1096 |
$(document).on('click', '#test-both-requests', function(e) { |
| 1097 |
e.preventDefault(); |
| 1098 |
e.stopPropagation(); |
| 1099 |
runHostTest('BOTH'); |
| 1100 |
return false; |
| 1101 |
}); |
| 1102 |
|
| 1103 |
function runHostTest(method) { |
| 1104 |
var buttonId = (method === 'BOTH' ? 'test-both-requests' : 'test-' + method.toLowerCase() + '-request'); |
| 1105 |
var $button = $('#' + buttonId); |
| 1106 |
|
| 1107 |
if ($button.length === 0) { |
| 1108 |
alert('Error: Test button not found. Please refresh the page.'); |
| 1109 |
return; |
| 1110 |
} |
| 1111 |
|
| 1112 |
var originalText = $button.text(); |
| 1113 |
|
| 1114 |
$button.prop('disabled', true); |
| 1115 |
$button.text('Testing...'); |
| 1116 |
|
| 1117 |
var $resultsDiv = $('#host-test-results'); |
| 1118 |
var $resultsContent = $('#test-results-content'); |
| 1119 |
$resultsDiv.show(); |
| 1120 |
$resultsContent.html('<div class="notice notice-info"><p>Running ' + (method === 'BOTH' ? 'GET and POST' : method) + ' test(s)...</p></div>'); |
| 1121 |
|
| 1122 |
var testsToRun = (method === 'BOTH') ? ['GET', 'POST'] : [method]; |
| 1123 |
var completedTests = 0; |
| 1124 |
var allResults = []; |
| 1125 |
|
| 1126 |
testsToRun.forEach(function(testMethod) { |
| 1127 |
var action = 'metasync_test_host_blocking_' + testMethod.toLowerCase(); |
| 1128 |
|
| 1129 |
$.ajax({ |
| 1130 |
url: ajaxUrl, |
| 1131 |
type: 'POST', |
| 1132 |
dataType: 'json', |
| 1133 |
data: { action: action, nonce: '<?php echo wp_create_nonce('metasync_nonce'); ?>' }, |
| 1134 |
timeout: 35000, |
| 1135 |
success: function(response, textStatus, xhr) { |
| 1136 |
try { |
| 1137 |
if (response && response.success && response.data) { |
| 1138 |
allResults.push(response.data); |
| 1139 |
} else { |
| 1140 |
allResults.push({ |
| 1141 |
method: testMethod, |
| 1142 |
status: 'error', |
| 1143 |
error: (response && response.data) ? response.data : 'Unexpected response', |
| 1144 |
blocked: true, |
| 1145 |
details: 'Received non-success response from server.' |
| 1146 |
}); |
| 1147 |
} |
| 1148 |
} catch (e) { |
| 1149 |
allResults.push({ |
| 1150 |
method: testMethod, |
| 1151 |
status: 'error', |
| 1152 |
error: 'Response parse error: ' + (e && e.message ? e.message : e), |
| 1153 |
blocked: true |
| 1154 |
}); |
| 1155 |
} |
| 1156 |
finalizeOne(); |
| 1157 |
}, |
| 1158 |
error: function(xhr, status, error) { |
| 1159 |
var payload = (xhr && xhr.responseText) ? xhr.responseText.substring(0, 500) : ''; |
| 1160 |
allResults.push({ |
| 1161 |
method: testMethod, |
| 1162 |
status: 'error', |
| 1163 |
error: 'AJAX failed: ' + error + (payload ? ' — ' + payload : ''), |
| 1164 |
blocked: true, |
| 1165 |
details: 'Request did not complete successfully. Status: ' + status |
| 1166 |
}); |
| 1167 |
finalizeOne(); |
| 1168 |
} |
| 1169 |
}); |
| 1170 |
}); |
| 1171 |
|
| 1172 |
function finalizeOne() { |
| 1173 |
completedTests++; |
| 1174 |
if (completedTests === testsToRun.length) { |
| 1175 |
displayResults(allResults); |
| 1176 |
resetButtons(); |
| 1177 |
var $container = $('#host-test-results'); |
| 1178 |
if ($container && $container[0] && $container[0].scrollIntoView) { |
| 1179 |
$container[0].scrollIntoView({ behavior: 'smooth', block: 'start' }); |
| 1180 |
} |
| 1181 |
} |
| 1182 |
} |
| 1183 |
} |
| 1184 |
|
| 1185 |
function resetButtons() { |
| 1186 |
$('#test-get-request, #test-post-request, #test-both-requests').prop('disabled', false); |
| 1187 |
$('#test-get-request').text('Test GET Request'); |
| 1188 |
$('#test-post-request').text('Test POST Request'); |
| 1189 |
$('#test-both-requests').text('Test Both Requests'); |
| 1190 |
} |
| 1191 |
|
| 1192 |
function displayResults(results) { |
| 1193 |
var html = ''; |
| 1194 |
|
| 1195 |
results.forEach(function(result) { |
| 1196 |
var statusClass = result.status === 'success' ? 'success' : 'error'; |
| 1197 |
var statusIcon = result.status === 'success' ? '� |
| 1198 |
' : '❌'; |
| 1199 |
var blockedStatus = result.blocked ? 'BLOCKED' : 'ALLOWED'; |
| 1200 |
var blockedClass = result.blocked ? 'blocked' : 'allowed'; |
| 1201 |
|
| 1202 |
html += '<div class="test-result-item ' + statusClass + '">'; |
| 1203 |
html += '<div class="test-result-header">'; |
| 1204 |
html += '<h4>' + statusIcon + ' ' + result.method + ' Request - ' + blockedStatus + '</h4>'; |
| 1205 |
html += '<span class="test-status ' + blockedClass + '">' + blockedStatus + '</span>'; |
| 1206 |
html += '</div>'; |
| 1207 |
|
| 1208 |
html += '<div class="test-result-details">'; |
| 1209 |
html += '<p><strong>Response Time:</strong> ' + result.response_time + '</p>'; |
| 1210 |
html += '<p><strong>Status:</strong> ' + result.status + '</p>'; |
| 1211 |
|
| 1212 |
if (result.status_code) { |
| 1213 |
html += '<p><strong>HTTP Status Code:</strong> <span class="status-code">' + result.status_code + '</span></p>'; |
| 1214 |
} |
| 1215 |
|
| 1216 |
if (result.error) { |
| 1217 |
html += '<p><strong>Error:</strong> <span class="error-message">' + result.error + '</span></p>'; |
| 1218 |
} |
| 1219 |
|
| 1220 |
if (result.body) { |
| 1221 |
html += '<p><strong>Response Body:</strong></p>'; |
| 1222 |
html += '<pre class="response-body">' + escapeHtml(result.body) + '</pre>'; |
| 1223 |
} |
| 1224 |
|
| 1225 |
if (result.headers && Object.keys(result.headers).length > 0) { |
| 1226 |
html += '<p><strong>Response Headers:</strong></p>'; |
| 1227 |
html += '<pre class="response-headers">'; |
| 1228 |
Object.keys(result.headers).forEach(function(key) { |
| 1229 |
html += key + ': ' + result.headers[key] + '\n'; |
| 1230 |
}); |
| 1231 |
html += '</pre>'; |
| 1232 |
} |
| 1233 |
|
| 1234 |
if (result.sent_data) { |
| 1235 |
html += '<p><strong>Sent Data:</strong></p>'; |
| 1236 |
html += '<pre class="sent-data">' + escapeHtml(JSON.stringify(result.sent_data, null, 2)) + '</pre>'; |
| 1237 |
} |
| 1238 |
|
| 1239 |
if (result.parsed_response) { |
| 1240 |
html += '<p><strong>Parsed Response:</strong></p>'; |
| 1241 |
html += '<pre class="parsed-response">' + escapeHtml(JSON.stringify(result.parsed_response, null, 2)) + '</pre>'; |
| 1242 |
} |
| 1243 |
|
| 1244 |
html += '<p><strong>Details:</strong> ' + result.details + '</p>'; |
| 1245 |
html += '</div>'; |
| 1246 |
html += '</div>'; |
| 1247 |
}); |
| 1248 |
|
| 1249 |
$('#test-results-content').html(html); |
| 1250 |
$('#host-test-results').show(); |
| 1251 |
} |
| 1252 |
|
| 1253 |
function escapeHtml(text) { |
| 1254 |
var map = { |
| 1255 |
'&': '&', |
| 1256 |
'<': '<', |
| 1257 |
'>': '>', |
| 1258 |
'"': '"', |
| 1259 |
'\'': ''' |
| 1260 |
}; |
| 1261 |
return text.replace(/[&<>"']/g, function(m) { return map[m]; }); |
| 1262 |
} |
| 1263 |
}); |
| 1264 |
</script> |
| 1265 |
|
| 1266 |
<!-- Host Blocking Test CSS --> |
| 1267 |
<style> |
| 1268 |
.test-result-item { |
| 1269 |
background: #fff; |
| 1270 |
border: 1px solid #ddd; |
| 1271 |
border-radius: 8px; |
| 1272 |
margin-bottom: 20px; |
| 1273 |
overflow: hidden; |
| 1274 |
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| 1275 |
} |
| 1276 |
|
| 1277 |
.test-result-item.success { |
| 1278 |
border-left: 4px solid #28a745; |
| 1279 |
} |
| 1280 |
|
| 1281 |
.test-result-item.error { |
| 1282 |
border-left: 4px solid #dc3545; |
| 1283 |
} |
| 1284 |
|
| 1285 |
.test-result-header { |
| 1286 |
background: #f8f9fa; |
| 1287 |
padding: 15px 20px; |
| 1288 |
border-bottom: 1px solid #dee2e6; |
| 1289 |
display: flex; |
| 1290 |
justify-content: space-between; |
| 1291 |
align-items: center; |
| 1292 |
} |
| 1293 |
|
| 1294 |
.test-result-header h4 { |
| 1295 |
margin: 0; |
| 1296 |
color: #495057; |
| 1297 |
font-size: 16px; |
| 1298 |
} |
| 1299 |
|
| 1300 |
.test-status { |
| 1301 |
padding: 4px 12px; |
| 1302 |
border-radius: 20px; |
| 1303 |
font-size: 12px; |
| 1304 |
font-weight: bold; |
| 1305 |
text-transform: uppercase; |
| 1306 |
} |
| 1307 |
|
| 1308 |
.test-status.allowed { |
| 1309 |
background: #d4edda; |
| 1310 |
color: #155724; |
| 1311 |
} |
| 1312 |
|
| 1313 |
.test-status.blocked { |
| 1314 |
background: #f8d7da; |
| 1315 |
color: #721c24; |
| 1316 |
} |
| 1317 |
|
| 1318 |
.test-result-details { |
| 1319 |
padding: 20px; |
| 1320 |
} |
| 1321 |
|
| 1322 |
.test-result-details p { |
| 1323 |
margin: 8px 0; |
| 1324 |
color: #495057; |
| 1325 |
} |
| 1326 |
|
| 1327 |
.test-result-details code { |
| 1328 |
background: #f8f9fa; |
| 1329 |
padding: 2px 6px; |
| 1330 |
border-radius: 3px; |
| 1331 |
font-family: 'Courier New', monospace; |
| 1332 |
color: #e83e8c; |
| 1333 |
} |
| 1334 |
|
| 1335 |
.status-code { |
| 1336 |
font-weight: bold; |
| 1337 |
color: #007cba; |
| 1338 |
} |
| 1339 |
|
| 1340 |
.error-message { |
| 1341 |
color: #dc3545; |
| 1342 |
font-weight: bold; |
| 1343 |
} |
| 1344 |
|
| 1345 |
.response-body, .response-headers, .sent-data, .parsed-response { |
| 1346 |
background: #f8f9fa; |
| 1347 |
border: 1px solid #dee2e6; |
| 1348 |
border-radius: 4px; |
| 1349 |
padding: 12px; |
| 1350 |
margin: 8px 0; |
| 1351 |
font-family: 'Courier New', monospace; |
| 1352 |
font-size: 12px; |
| 1353 |
line-height: 1.4; |
| 1354 |
max-height: 200px; |
| 1355 |
overflow-y: auto; |
| 1356 |
white-space: pre-wrap; |
| 1357 |
word-break: break-all; |
| 1358 |
} |
| 1359 |
|
| 1360 |
.response-body { |
| 1361 |
color: #495057; |
| 1362 |
} |
| 1363 |
|
| 1364 |
.response-headers { |
| 1365 |
color: #6c757d; |
| 1366 |
} |
| 1367 |
|
| 1368 |
.sent-data { |
| 1369 |
color: #007cba; |
| 1370 |
} |
| 1371 |
|
| 1372 |
.parsed-response { |
| 1373 |
color: #28a745; |
| 1374 |
background: #f8fff9; |
| 1375 |
border-color: #c3e6cb; |
| 1376 |
} |
| 1377 |
|
| 1378 |
#host-test-results { |
| 1379 |
margin-top: 20px; |
| 1380 |
} |
| 1381 |
|
| 1382 |
#host-test-results h3 { |
| 1383 |
color: #495057; |
| 1384 |
margin-bottom: 15px; |
| 1385 |
padding-bottom: 10px; |
| 1386 |
border-bottom: 2px solid #dee2e6; |
| 1387 |
} |
| 1388 |
|
| 1389 |
.button:disabled { |
| 1390 |
opacity: 0.6; |
| 1391 |
cursor: not-allowed; |
| 1392 |
} |
| 1393 |
</style> |
| 1394 |
|
| 1395 |
<?php if ($active_tab === 'advanced'): ?> |
| 1396 |
<?php if (isset($_GET['settings_cleared']) && $_GET['settings_cleared'] == '1'): ?> |
| 1397 |
<div class="notice notice-success is-dismissible"> |
| 1398 |
<p><strong>� |
| 1399 |
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> |
| 1400 |
</div> |
| 1401 |
<?php endif; ?> |
| 1402 |
|
| 1403 |
<?php if (isset($_GET['clear_settings_error']) && $_GET['clear_settings_error'] == '1'): ?> |
| 1404 |
<div class="notice notice-error is-dismissible"> |
| 1405 |
<p><strong>❌ Error!</strong> Failed to clear settings due to a security check failure. Please try again.</p> |
| 1406 |
</div> |
| 1407 |
<?php endif; ?> |
| 1408 |
|
| 1409 |
<?php if (isset($_GET['log_cleared']) && $_GET['log_cleared'] == '1'): ?> |
| 1410 |
<div class="notice notice-success is-dismissible"> |
| 1411 |
<p><strong>� |
| 1412 |
Success!</strong> Error logs have been cleared successfully.</p> |
| 1413 |
</div> |
| 1414 |
<?php endif; ?> |
| 1415 |
|
| 1416 |
<?php if (isset($_GET['clear_error']) && $_GET['clear_error'] == '1'): ?> |
| 1417 |
<div class="notice notice-error is-dismissible"> |
| 1418 |
<p><strong>❌ Error!</strong> Failed to clear error logs due to a security check failure. Please try again.</p> |
| 1419 |
</div> |
| 1420 |
<?php endif; ?> |
| 1421 |
|
| 1422 |
<?php if (isset($_GET['access_roles_saved']) && $_GET['access_roles_saved'] == '1'): ?> |
| 1423 |
<div class="notice notice-success is-dismissible"> |
| 1424 |
<p><strong>� |
| 1425 |
Success!</strong> Plugin access roles have been saved successfully.</p> |
| 1426 |
</div> |
| 1427 |
<?php endif; ?> |
| 1428 |
|
| 1429 |
<?php if (isset($_GET['access_roles_error']) && $_GET['access_roles_error'] == '1'): ?> |
| 1430 |
<div class="notice notice-error is-dismissible"> |
| 1431 |
<p><strong>❌ Error!</strong> Failed to save plugin access roles. <?php echo isset($_GET['message']) ? esc_html(urldecode($_GET['message'])) : 'Please try again.'; ?></p> |
| 1432 |
</div> |
| 1433 |
<?php endif; ?> |
| 1434 |
|
| 1435 |
<?php endif; ?> |
| 1436 |
<?php $this->admin->render_layout_close(); ?> |
| 1437 |
<?php |
| 1438 |
} |
| 1439 |
|
| 1440 |
// ------------------------------------------------------------------ |
| 1441 |
// Simple page renderers |
| 1442 |
// ------------------------------------------------------------------ |
| 1443 |
|
| 1444 |
public function create_admin_dashboard_page() |
| 1445 |
{ |
| 1446 |
?> |
| 1447 |
<?php $this->admin->render_layout_open('Dashboard', 'dashboard', ''); ?> |
| 1448 |
|
| 1449 |
<div class="dashboard-card"> |
| 1450 |
<h2><?php echo esc_html($this->admin->get_effective_menu_title()); ?> Dashboard</h2> |
| 1451 |
<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> |
| 1452 |
<?php |
| 1453 |
if (!isset(Metasync::get_option('general')['linkgraph_token']) || Metasync::get_option('general')['linkgraph_token'] == '') { |
| 1454 |
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>'; |
| 1455 |
echo '<a href="' . admin_url('admin.php?page=' . Metasync_Admin::$page_slug) . '" class="button button-secondary">Go to Settings</a>'; |
| 1456 |
} else { |
| 1457 |
echo '<a href="' . esc_url($this->admin->get_dashboard_url()) . '" target="_blank" class="button button-primary">Open Dashboard</a>'; |
| 1458 |
} |
| 1459 |
?> |
| 1460 |
</div> |
| 1461 |
<?php $this->admin->render_layout_close(); ?> |
| 1462 |
<?php |
| 1463 |
} |
| 1464 |
|
| 1465 |
public function create_admin_robots_txt_page() |
| 1466 |
{ |
| 1467 |
?> |
| 1468 |
<?php $this->admin->render_layout_open('Robots.txt', 'robots_txt', 'Control which pages search engines can crawl.'); ?> |
| 1469 |
|
| 1470 |
<?php |
| 1471 |
require_once plugin_dir_path(dirname(__FILE__)) . 'robots-txt/class-metasync-robots-txt.php'; |
| 1472 |
|
| 1473 |
$robots_txt = Metasync_Robots_Txt::get_instance(); |
| 1474 |
|
| 1475 |
if (isset($_POST['metasync_robots_txt_nonce'])) { |
| 1476 |
check_admin_referer('metasync_save_robots_txt', 'metasync_robots_txt_nonce'); |
| 1477 |
|
| 1478 |
if (isset($_POST['robots_content'])) { |
| 1479 |
$content = wp_unslash($_POST['robots_content']); |
| 1480 |
|
| 1481 |
$validation = $robots_txt->validate_content($content); |
| 1482 |
|
| 1483 |
if ($validation['valid']) { |
| 1484 |
$result = $robots_txt->write_robots_file($content); |
| 1485 |
|
| 1486 |
if (is_wp_error($result)) { |
| 1487 |
echo '<div class="notice notice-error"><p>' . esc_html($result->get_error_message()) . '</p></div>'; |
| 1488 |
} else { |
| 1489 |
echo '<div class="notice notice-success"><p>' . esc_html__('robots.txt file saved successfully!', 'metasync') . '</p></div>'; |
| 1490 |
|
| 1491 |
if (!empty($validation['warnings'])) { |
| 1492 |
foreach ($validation['warnings'] as $warning) { |
| 1493 |
echo '<div class="notice notice-warning"><p>' . esc_html($warning) . '</p></div>'; |
| 1494 |
} |
| 1495 |
} |
| 1496 |
} |
| 1497 |
} else { |
| 1498 |
echo '<div class="notice notice-error"><p>' . esc_html__('Validation failed. Please fix the errors below:', 'metasync') . '</p>'; |
| 1499 |
foreach ($validation['errors'] as $error) { |
| 1500 |
echo '<p>- ' . esc_html($error) . '</p>'; |
| 1501 |
} |
| 1502 |
echo '</div>'; |
| 1503 |
} |
| 1504 |
} |
| 1505 |
} |
| 1506 |
|
| 1507 |
$current_content = $robots_txt->read_robots_file(); |
| 1508 |
if (is_wp_error($current_content)) { |
| 1509 |
echo '<div class="notice notice-error"><p>' . esc_html($current_content->get_error_message()) . '</p></div>'; |
| 1510 |
$current_content = ''; |
| 1511 |
} |
| 1512 |
|
| 1513 |
// First page of backups; the Backup History panel is paginated so the |
| 1514 |
// list always reflects the full stored state and its true total. |
| 1515 |
$backups = $robots_txt->get_backup_history(Metasync_Robots_Txt_Database::BACKUPS_PER_PAGE, 0); |
| 1516 |
|
| 1517 |
$file_exists = $robots_txt->file_exists(); |
| 1518 |
$is_writable = $robots_txt->is_writable(); |
| 1519 |
|
| 1520 |
$robots_txt->render($this->admin, $current_content, $backups, $file_exists, $is_writable); |
| 1521 |
?> |
| 1522 |
<?php $this->admin->render_layout_close(); ?> |
| 1523 |
<?php |
| 1524 |
} |
| 1525 |
|
| 1526 |
public function create_admin_report_issue_page() |
| 1527 |
{ |
| 1528 |
require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-report-issue.php'; |
| 1529 |
} |
| 1530 |
|
| 1531 |
public function create_admin_xml_sitemap_page() |
| 1532 |
{ |
| 1533 |
require_once plugin_dir_path(dirname(__FILE__)) . 'sitemap/class-metasync-sitemap-generator.php'; |
| 1534 |
|
| 1535 |
$sitemap_generator = new Metasync_Sitemap_Generator(); |
| 1536 |
|
| 1537 |
if (isset($_POST['metasync_sitemap_nonce'])) { |
| 1538 |
check_admin_referer('metasync_sitemap_action', 'metasync_sitemap_nonce'); |
| 1539 |
|
| 1540 |
if (isset($_POST['generate_sitemap'])) { |
| 1541 |
$disabled_plugins = $sitemap_generator->disable_other_sitemap_generators(); |
| 1542 |
|
| 1543 |
$result = $sitemap_generator->generate_sitemap(); |
| 1544 |
|
| 1545 |
if (is_wp_error($result)) { |
| 1546 |
error_log('[MetaSync] Sitemap generation failed: ' . $result->get_error_message()); |
| 1547 |
echo '<div class="notice notice-error"><p>' . esc_html__('Sitemap generation failed. Please try again or check the error logs.', 'metasync') . '</p></div>'; |
| 1548 |
} else { |
| 1549 |
$message = esc_html__('Sitemap generated successfully!', 'metasync'); |
| 1550 |
if ($disabled_plugins) { |
| 1551 |
$message .= ' ' . esc_html__('Conflicting sitemap generators have been automatically disabled.', 'metasync'); |
| 1552 |
} |
| 1553 |
|
| 1554 |
$robots_result = get_transient('metasync_sitemap_robots_updated'); |
| 1555 |
if ($robots_result && $robots_result['success']) { |
| 1556 |
if ($robots_result['action'] === 'added') { |
| 1557 |
$message .= ' ' . esc_html__('Sitemap URL has been added to robots.txt.', 'metasync'); |
| 1558 |
} elseif ($robots_result['action'] === 'updated') { |
| 1559 |
$message .= ' ' . esc_html__('Sitemap URL has been updated in robots.txt.', 'metasync'); |
| 1560 |
} elseif ($robots_result['action'] === 'created') { |
| 1561 |
$message .= ' ' . esc_html__('robots.txt file has been created with sitemap URL.', 'metasync'); |
| 1562 |
} |
| 1563 |
delete_transient('metasync_sitemap_robots_updated'); |
| 1564 |
} |
| 1565 |
|
| 1566 |
echo '<div class="notice notice-success"><p>' . esc_html($message) . '</p></div>'; |
| 1567 |
} |
| 1568 |
} elseif (isset($_POST['enable_auto_update'])) { |
| 1569 |
update_option('metasync_sitemap_auto_update', true); |
| 1570 |
$sitemap_generator->setup_auto_update_hooks(); |
| 1571 |
echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update enabled!', 'metasync') . '</p></div>'; |
| 1572 |
} elseif (isset($_POST['disable_auto_update'])) { |
| 1573 |
update_option('metasync_sitemap_auto_update', false); |
| 1574 |
echo '<div class="notice notice-success"><p>' . esc_html__('Auto-update disabled!', 'metasync') . '</p></div>'; |
| 1575 |
} elseif (isset($_POST['delete_sitemap'])) { |
| 1576 |
$deleted = $sitemap_generator->delete_sitemap(); |
| 1577 |
if ($deleted) { |
| 1578 |
update_option('metasync_sitemap_auto_update', false); |
| 1579 |
// Re-enable WP core sitemap so the site isn't left with zero sitemaps |
| 1580 |
delete_option('metasync_disable_wp_sitemap'); |
| 1581 |
echo '<div class="notice notice-success"><p>' . esc_html__('Sitemap deleted successfully!', 'metasync') . '</p></div>'; |
| 1582 |
} else { |
| 1583 |
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>'; |
| 1584 |
} |
| 1585 |
} elseif (isset($_POST['enable_other_sitemaps'])) { |
| 1586 |
$enabled_plugins = $sitemap_generator->enable_other_sitemap_generators(); |
| 1587 |
if ($enabled_plugins) { |
| 1588 |
echo '<div class="notice notice-success"><p>' . esc_html__('Other sitemap plugins have been re-enabled successfully!', 'metasync') . '</p></div>'; |
| 1589 |
} else { |
| 1590 |
echo '<div class="notice notice-info"><p>' . esc_html__('No sitemap plugins were found to re-enable.', 'metasync') . '</p></div>'; |
| 1591 |
} |
| 1592 |
} |
| 1593 |
} |
| 1594 |
|
| 1595 |
$sitemap_exists = $sitemap_generator->sitemap_exists(); |
| 1596 |
$sitemap_url = $sitemap_generator->get_sitemap_url(); |
| 1597 |
$url_count = $sitemap_generator->count_urls(); |
| 1598 |
$last_generated = $sitemap_generator->get_last_generated_time(); |
| 1599 |
$auto_update_enabled = get_option('metasync_sitemap_auto_update', false); |
| 1600 |
$active_sitemap_plugins = $sitemap_generator->check_active_sitemap_plugins(); |
| 1601 |
|
| 1602 |
require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-xml-sitemap.php'; |
| 1603 |
} |
| 1604 |
|
| 1605 |
public function create_admin_custom_pages_page() |
| 1606 |
{ |
| 1607 |
?> |
| 1608 |
<?php $this->admin->render_layout_open('Custom Pages', 'custom_pages', 'Create blank canvas pages for Elementor, Divi, or Gutenberg.'); ?> |
| 1609 |
|
| 1610 |
<div class="metasync-page-content"> |
| 1611 |
<?php |
| 1612 |
require_once plugin_dir_path(dirname(__FILE__)) . 'custom-pages/class-metasync-custom-pages-admin.php'; |
| 1613 |
Metasync_Custom_Pages_Admin::render_admin_page(); |
| 1614 |
?> |
| 1615 |
</div> |
| 1616 |
|
| 1617 |
<?php $this->admin->render_layout_close(); ?> |
| 1618 |
<?php |
| 1619 |
} |
| 1620 |
|
| 1621 |
public function create_admin_404_monitor_page() |
| 1622 |
{ |
| 1623 |
require_once plugin_dir_path(dirname(__FILE__)) . '404-monitor/class-metasync-404-monitor-database.php'; |
| 1624 |
require_once plugin_dir_path(dirname(__FILE__)) . '404-monitor/class-metasync-404-monitor.php'; |
| 1625 |
|
| 1626 |
$db_404 = new Metasync_Error_Monitor_Database(); |
| 1627 |
$ErrorMonitor = new Metasync_Error_Monitor($db_404); |
| 1628 |
|
| 1629 |
$this->admin->render_layout_open('404 Monitor', 'monitor_404', 'Track and manage 404 errors detected on your site.'); |
| 1630 |
$ErrorMonitor->create_admin_plugin_interface(); |
| 1631 |
$this->admin->render_layout_close(); |
| 1632 |
} |
| 1633 |
|
| 1634 |
public function create_admin_search_engine_verification_page() |
| 1635 |
{ |
| 1636 |
$page_slug = Metasync_Admin::$page_slug . '_searchengines-verification'; |
| 1637 |
?> |
| 1638 |
<?php $this->admin->render_layout_open('Site Verification', 'site_verification', 'Verify your site ownership with search engines and other platforms.'); ?> |
| 1639 |
|
| 1640 |
<form method="post" action="options.php"> |
| 1641 |
<div class="dashboard-card"> |
| 1642 |
<h2>Search Engine Verification</h2> |
| 1643 |
<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> |
| 1644 |
<?php |
| 1645 |
settings_fields(Metasync_Admin::option_group); |
| 1646 |
do_settings_sections($page_slug); |
| 1647 |
submit_button(); |
| 1648 |
?> |
| 1649 |
</div> |
| 1650 |
</form> |
| 1651 |
<?php $this->admin->render_layout_close(); ?> |
| 1652 |
<?php |
| 1653 |
} |
| 1654 |
|
| 1655 |
public function create_admin_breadcrumbs_page() |
| 1656 |
{ |
| 1657 |
$page_slug = Metasync_Admin::$page_slug . '_breadcrumbs'; |
| 1658 |
?> |
| 1659 |
<?php $this->admin->render_layout_open('Breadcrumbs', 'breadcrumbs', 'Configure breadcrumb trail settings and placement.'); ?> |
| 1660 |
|
| 1661 |
<form method="post" action="options.php"> |
| 1662 |
<div class="dashboard-card"> |
| 1663 |
<h2>Breadcrumbs Settings</h2> |
| 1664 |
<p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how breadcrumb trails are displayed across your site.</p> |
| 1665 |
<?php |
| 1666 |
settings_fields(Metasync_Admin::option_group); |
| 1667 |
do_settings_sections($page_slug); |
| 1668 |
submit_button(); |
| 1669 |
?> |
| 1670 |
</div> |
| 1671 |
</form> |
| 1672 |
|
| 1673 |
<div class="dashboard-card" style="margin-top: 20px;"> |
| 1674 |
<h2>📖 How to Display Breadcrumbs</h2> |
| 1675 |
<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> |
| 1676 |
|
| 1677 |
<h3 style="margin-top: 0;">Option 1 — Shortcode</h3> |
| 1678 |
<p style="color: var(--dashboard-text-secondary);">Paste the shortcode inside any page, post, or widget:</p> |
| 1679 |
<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> |
| 1680 |
|
| 1681 |
<h3>Option 2 — PHP Template Function</h3> |
| 1682 |
<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> |
| 1683 |
<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;"><?php metasync_breadcrumb(); ?></pre><span class="metasync-copy-badge">Copy</span></div> |
| 1684 |
|
| 1685 |
<h3>Option 3 — PHP Return Value</h3> |
| 1686 |
<p style="color: var(--dashboard-text-secondary);">Get the breadcrumb HTML as a string to manipulate before output:</p> |
| 1687 |
<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(); |
| 1688 |
echo $breadcrumbs->render_breadcrumb_html();</pre><span class="metasync-copy-badge">Copy</span></div> |
| 1689 |
|
| 1690 |
<p style="color: var(--dashboard-text-secondary); margin-top: 16px; font-size: 12px;"> |
| 1691 |
<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> |
| 1692 |
<code>echo $breadcrumbs->render_breadcrumb_html(['post_id' => 123]);</code> |
| 1693 |
</p> |
| 1694 |
<?php /* click-to-copy for the code snippets above */ ?> |
| 1695 |
<style> |
| 1696 |
.metasync-copy-wrap { position: relative; } |
| 1697 |
.metasync-copy-snippet { cursor: pointer; outline: 1px solid transparent; transition: outline-color .15s ease; } |
| 1698 |
.metasync-copy-wrap:hover .metasync-copy-snippet { outline-color: var(--dashboard-border, #374151); } |
| 1699 |
.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); } |
| 1700 |
.metasync-copy-wrap:hover .metasync-copy-badge { opacity: .85; } |
| 1701 |
.metasync-copy-badge.metasync-copy-done { opacity: 1; background: var(--dashboard-gradient-primary, linear-gradient(135deg, #667eea 0%, #764ba2 100%)); color: #fff; } |
| 1702 |
</style> |
| 1703 |
<script> |
| 1704 |
(function(){ |
| 1705 |
if (window.__metasyncCopyInit) return; window.__metasyncCopyInit = true; |
| 1706 |
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); } |
| 1707 |
function attach(){ |
| 1708 |
document.querySelectorAll('.metasync-copy-snippet').forEach(function(pre){ |
| 1709 |
if (pre.dataset.copyBound) return; pre.dataset.copyBound='1'; |
| 1710 |
pre.setAttribute('title','Click to copy'); |
| 1711 |
pre.addEventListener('click', function(){ |
| 1712 |
var text = pre.textContent.trim(); |
| 1713 |
var wrap = pre.closest('.metasync-copy-wrap') || pre.parentNode; |
| 1714 |
var badge = wrap.querySelector('.metasync-copy-badge'); |
| 1715 |
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); }; |
| 1716 |
if (navigator.clipboard && navigator.clipboard.writeText){ navigator.clipboard.writeText(text).then(done).catch(function(){ fallback(text); done(); }); } |
| 1717 |
else { fallback(text); done(); } |
| 1718 |
}); |
| 1719 |
}); |
| 1720 |
} |
| 1721 |
if (document.readyState==='loading'){ document.addEventListener('DOMContentLoaded', attach); } else { attach(); } |
| 1722 |
})(); |
| 1723 |
</script> |
| 1724 |
</div> |
| 1725 |
<?php $this->admin->render_layout_close(); ?> |
| 1726 |
<?php |
| 1727 |
} |
| 1728 |
|
| 1729 |
public function create_admin_schema_markup_page() |
| 1730 |
{ |
| 1731 |
$page_slug = Metasync_Admin::$page_slug . '_schema-markup'; |
| 1732 |
$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.'); |
| 1733 |
?> |
| 1734 |
<form method="post" action="options.php"> |
| 1735 |
<div class="dashboard-card"> |
| 1736 |
<h2>Schema Markup Settings</h2> |
| 1737 |
<p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure the site-wide Organization and WebSite structured data injected into every page.</p> |
| 1738 |
<?php |
| 1739 |
settings_fields(Metasync_Admin::option_group); |
| 1740 |
do_settings_sections($page_slug); |
| 1741 |
submit_button('Save Changes'); |
| 1742 |
?> |
| 1743 |
</div> |
| 1744 |
</form> |
| 1745 |
<?php |
| 1746 |
$this->admin->render_layout_close(); |
| 1747 |
} |
| 1748 |
|
| 1749 |
public function create_admin_local_business_page() |
| 1750 |
{ |
| 1751 |
$page_slug = Metasync_Admin::$page_slug . '_local-seo'; |
| 1752 |
?> |
| 1753 |
<?php $this->admin->render_layout_open('Local Business', 'local_business', 'Configure local business information for better local SEO.'); ?> |
| 1754 |
|
| 1755 |
<form method="post" action="options.php"> |
| 1756 |
<div class="dashboard-card"> |
| 1757 |
<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> |
| 1758 |
<p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure your local business information for better local search engine optimization.</p> |
| 1759 |
<?php |
| 1760 |
settings_fields(Metasync_Admin::option_group); |
| 1761 |
do_settings_sections($page_slug); |
| 1762 |
submit_button(); |
| 1763 |
?> |
| 1764 |
</div> |
| 1765 |
</form> |
| 1766 |
<?php $this->admin->render_layout_close(); ?> |
| 1767 |
<?php |
| 1768 |
} |
| 1769 |
|
| 1770 |
public function create_admin_code_snippets_page() |
| 1771 |
{ |
| 1772 |
$page_slug = Metasync_Admin::$page_slug . '_code-snippets'; |
| 1773 |
?> |
| 1774 |
<?php $this->admin->render_layout_open('Code Snippets', 'code_snippets', 'Add custom code to your site header and footer.'); ?> |
| 1775 |
|
| 1776 |
<form method="post" action="options.php"> |
| 1777 |
<div class="dashboard-card"> |
| 1778 |
<h2>Code Snippets Management</h2> |
| 1779 |
<p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Add custom code snippets to enhance your site functionality and tracking capabilities.</p> |
| 1780 |
<?php |
| 1781 |
settings_fields(Metasync_Admin::option_group); |
| 1782 |
do_settings_sections($page_slug); |
| 1783 |
submit_button(); |
| 1784 |
?> |
| 1785 |
</div> |
| 1786 |
</form> |
| 1787 |
<?php $this->admin->render_layout_close(); ?> |
| 1788 |
<?php |
| 1789 |
} |
| 1790 |
|
| 1791 |
public function create_admin_google_instant_index_page() |
| 1792 |
{ |
| 1793 |
// Delegates to the admin class which uses the shared credentials view |
| 1794 |
$this->admin->create_admin_google_instant_index_page(); |
| 1795 |
} |
| 1796 |
|
| 1797 |
public function create_admin_google_console_page() |
| 1798 |
{ |
| 1799 |
$this->admin->render_layout_open('Google Console', 'google_console', 'View Google Search Console data and manage indexing requests.'); |
| 1800 |
$google = function_exists('google_index_direct') ? google_index_direct() : null; |
| 1801 |
if (is_null($google)) { |
| 1802 |
$service_info = ['error' => 'Module not loaded']; |
| 1803 |
} else { |
| 1804 |
$service_info = $google->get_service_account_info(); |
| 1805 |
} |
| 1806 |
$is_configured = !isset($service_info['error']); |
| 1807 |
include_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-google-console.php'; |
| 1808 |
$this->admin->render_layout_close(); |
| 1809 |
} |
| 1810 |
|
| 1811 |
public function create_admin_bing_console_page() |
| 1812 |
{ |
| 1813 |
?> |
| 1814 |
<?php $this->admin->render_layout_open('Bing Console', 'bing_console', ''); ?> |
| 1815 |
|
| 1816 |
<?php |
| 1817 |
require_once plugin_dir_path(dirname(__FILE__)) . 'bing-index/class-metasync-bing-instant-index.php'; |
| 1818 |
$bing_instant_index = new Metasync_Bing_Instant_Index(); |
| 1819 |
$bing_instant_index->show_bing_instant_indexing_console(); |
| 1820 |
?> |
| 1821 |
<?php $this->admin->render_layout_close(); ?> |
| 1822 |
<?php |
| 1823 |
} |
| 1824 |
|
| 1825 |
public function create_admin_global_settings_page() |
| 1826 |
{ |
| 1827 |
$page_slug = Metasync_Admin::$page_slug . '_common-settings'; |
| 1828 |
?> |
| 1829 |
<?php $this->admin->render_layout_open('Global Settings', 'general', ''); ?> |
| 1830 |
|
| 1831 |
<form method="post" action="options.php"> |
| 1832 |
<div class="dashboard-card"> |
| 1833 |
<h2>Global Configuration</h2> |
| 1834 |
<p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure global settings that apply across your entire site.</p> |
| 1835 |
<?php |
| 1836 |
settings_fields(Metasync_Admin::option_group); |
| 1837 |
do_settings_sections($page_slug); |
| 1838 |
?> |
| 1839 |
</div> |
| 1840 |
|
| 1841 |
<!-- Save button removed - using floating notification system instead --> |
| 1842 |
</form> |
| 1843 |
<?php $this->admin->render_layout_close(); ?> |
| 1844 |
<?php |
| 1845 |
} |
| 1846 |
|
| 1847 |
public function create_admin_common_meta_settings_page() |
| 1848 |
{ |
| 1849 |
$page_slug = Metasync_Admin::$page_slug . '_common-meta-settings'; |
| 1850 |
?> |
| 1851 |
<?php $this->admin->render_layout_open('Common Meta Settings', 'general', ''); ?> |
| 1852 |
|
| 1853 |
<form method="post" action="options.php"> |
| 1854 |
<div class="dashboard-card"> |
| 1855 |
<h2>Meta Configuration</h2> |
| 1856 |
<p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure common meta tags and SEO settings for your site.</p> |
| 1857 |
<?php |
| 1858 |
settings_fields(Metasync_Admin::option_group); |
| 1859 |
do_settings_sections($page_slug); |
| 1860 |
?> |
| 1861 |
</div> |
| 1862 |
|
| 1863 |
<!-- Save button removed - using floating notification system instead --> |
| 1864 |
</form> |
| 1865 |
<?php $this->admin->render_layout_close(); ?> |
| 1866 |
<?php |
| 1867 |
} |
| 1868 |
|
| 1869 |
public function create_admin_social_meta_page() |
| 1870 |
{ |
| 1871 |
$page_slug = Metasync_Admin::$page_slug . '_social-meta'; |
| 1872 |
?> |
| 1873 |
<?php $this->admin->render_layout_open('Social Meta', 'general', ''); ?> |
| 1874 |
|
| 1875 |
<form method="post" action="options.php"> |
| 1876 |
<div class="dashboard-card"> |
| 1877 |
<h2>📲 Social Meta Tags</h2> |
| 1878 |
<p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Configure how your content appears when shared on social media platforms.</p> |
| 1879 |
<?php |
| 1880 |
settings_fields(Metasync_Admin::option_group); |
| 1881 |
do_settings_sections($page_slug); |
| 1882 |
?> |
| 1883 |
</div> |
| 1884 |
|
| 1885 |
<!-- Save button removed - using floating notification system instead --> |
| 1886 |
</form> |
| 1887 |
<?php $this->admin->render_layout_close(); ?> |
| 1888 |
<?php |
| 1889 |
} |
| 1890 |
|
| 1891 |
public function create_admin_seo_controls_page() |
| 1892 |
{ |
| 1893 |
$page_slug = Metasync_Admin::$page_slug . '_seo-controls'; |
| 1894 |
?> |
| 1895 |
<?php $this->admin->render_layout_open('Indexation Control', 'seo_controls', 'Control how search engines index your content.'); ?> |
| 1896 |
|
| 1897 |
<!-- Status Messages Container --> |
| 1898 |
<div id="seo-controls-messages"></div> |
| 1899 |
|
| 1900 |
<form method="post" action="options.php" id="metaSyncSeoControlsForm"> |
| 1901 |
<div class="dashboard-card"> |
| 1902 |
<div style="display: flex; justify-content: flex-end; margin-bottom: 20px;"> |
| 1903 |
<a href="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-import-external&tab=indexation')); ?>" class="button button-secondary"> |
| 1904 |
<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 |
| 1905 |
</a> |
| 1906 |
</div> |
| 1907 |
<?php |
| 1908 |
settings_fields(Metasync_Admin::option_group); |
| 1909 |
do_settings_sections($page_slug); |
| 1910 |
|
| 1911 |
wp_nonce_field('meta_sync_seo_controls_nonce', 'meta_sync_seo_controls_nonce'); |
| 1912 |
?> |
| 1913 |
</div> |
| 1914 |
|
| 1915 |
<!-- Save button removed - using floating notification system instead --> |
| 1916 |
</form> |
| 1917 |
<?php $this->admin->render_layout_close(); ?> |
| 1918 |
<?php |
| 1919 |
} |
| 1920 |
|
| 1921 |
|
| 1922 |
public function create_admin_error_logs_page() |
| 1923 |
{ |
| 1924 |
?> |
| 1925 |
<?php $this->admin->render_layout_open('Error Logs', 'general', ''); ?> |
| 1926 |
|
| 1927 |
<div class="dashboard-card"> |
| 1928 |
<h2>Error Logs Management</h2> |
| 1929 |
<p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">View and manage system error logs to troubleshoot issues and monitor plugin performance.</p> |
| 1930 |
<?php |
| 1931 |
$error_logs = new Metasync_Error_Logs(); |
| 1932 |
|
| 1933 |
if ($error_logs->can_show_error_logs()) { |
| 1934 |
$log_content = $error_logs->get_error_logs(50); |
| 1935 |
|
| 1936 |
if (!empty(trim($log_content))) { |
| 1937 |
$error_logs->show_copy_button(); |
| 1938 |
$error_logs->show_logs(); |
| 1939 |
$error_logs->show_info(); |
| 1940 |
} else { |
| 1941 |
echo '<div class="dashboard-empty-state">'; |
| 1942 |
echo '<p style="color: var(--dashboard-text-secondary); font-style: italic; text-align: center; padding: 40px 20px;">� |
| 1943 |
Log file is empty - no errors recorded.</p>'; |
| 1944 |
echo '</div>'; |
| 1945 |
} |
| 1946 |
} else { |
| 1947 |
$error_message = $error_logs->get_error_message(); |
| 1948 |
if (!empty($error_message)) { |
| 1949 |
echo '<div class="dashboard-empty-state">'; |
| 1950 |
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;">'; |
| 1951 |
echo esc_html($error_message); |
| 1952 |
echo '</p>'; |
| 1953 |
echo '</div>'; |
| 1954 |
} else { |
| 1955 |
echo '<div class="dashboard-empty-state">'; |
| 1956 |
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>'; |
| 1957 |
echo '</div>'; |
| 1958 |
} |
| 1959 |
} |
| 1960 |
?> |
| 1961 |
</div> |
| 1962 |
<?php $this->admin->render_layout_close(); ?> |
| 1963 |
<?php |
| 1964 |
} |
| 1965 |
|
| 1966 |
public function create_admin_sync_log_page() |
| 1967 |
{ |
| 1968 |
$sync_db = new Metasync_Sync_History_Database(); |
| 1969 |
|
| 1970 |
if (wp_doing_ajax()) { |
| 1971 |
$this->handle_sync_log_ajax(); |
| 1972 |
return; |
| 1973 |
} |
| 1974 |
|
| 1975 |
$page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1; |
| 1976 |
$per_page = 10; |
| 1977 |
$offset = ($page - 1) * $per_page; |
| 1978 |
|
| 1979 |
$filters = [ |
| 1980 |
'date_range' => isset($_GET['date_range']) ? sanitize_text_field($_GET['date_range']) : '', |
| 1981 |
'status' => isset($_GET['status']) ? sanitize_text_field($_GET['status']) : '', |
| 1982 |
]; |
| 1983 |
|
| 1984 |
$date_range = $filters['date_range']; |
| 1985 |
$wp_now_ts = current_time('timestamp'); |
| 1986 |
$date_from = ''; |
| 1987 |
$date_to = ''; |
| 1988 |
|
| 1989 |
if (!empty($date_range)) { |
| 1990 |
$date_to = date('Y-m-d H:i:s', $wp_now_ts); |
| 1991 |
|
| 1992 |
if ($date_range === 'today') { |
| 1993 |
$start_ts = strtotime('today', $wp_now_ts); |
| 1994 |
$date_from = date('Y-m-d H:i:s', $start_ts); |
| 1995 |
} elseif ($date_range === 'yesterday') { |
| 1996 |
$start_ts = strtotime('yesterday', $wp_now_ts); |
| 1997 |
$end_ts = strtotime('today', $wp_now_ts) - 1; |
| 1998 |
$date_from = date('Y-m-d H:i:s', $start_ts); |
| 1999 |
$date_to = date('Y-m-d H:i:s', $end_ts); |
| 2000 |
} elseif ($date_range === 'this_week') { |
| 2001 |
$start_of_week = (int) get_option('start_of_week', 1); |
| 2002 |
$day_of_week = (int) date('w', $wp_now_ts); |
| 2003 |
$delta_days = ($day_of_week - $start_of_week + 7) % 7; |
| 2004 |
$start_ts = strtotime('-' . $delta_days . ' days', strtotime('today', $wp_now_ts)); |
| 2005 |
$date_from = date('Y-m-d H:i:s', $start_ts); |
| 2006 |
} elseif ($date_range === 'this_month') { |
| 2007 |
$start_ts = strtotime(date('Y-m-01 00:00:00', $wp_now_ts)); |
| 2008 |
$date_from = date('Y-m-d H:i:s', $start_ts); |
| 2009 |
} elseif ($date_range === 'all') { |
| 2010 |
// no bounds |
| 2011 |
} |
| 2012 |
} |
| 2013 |
|
| 2014 |
if (!empty($date_from)) { |
| 2015 |
$filters['date_from'] = $date_from; |
| 2016 |
} |
| 2017 |
if (!empty($date_to)) { |
| 2018 |
$filters['date_to'] = $date_to; |
| 2019 |
} |
| 2020 |
|
| 2021 |
$filters = array_filter($filters); |
| 2022 |
|
| 2023 |
$sync_records = $sync_db->getAllRecords($per_page, $offset, $filters); |
| 2024 |
$total_records = $sync_db->get_count($filters); |
| 2025 |
$total_pages = ceil($total_records / $per_page); |
| 2026 |
|
| 2027 |
$stats = $sync_db->get_statistics(); |
| 2028 |
|
| 2029 |
?> |
| 2030 |
<?php $this->admin->render_layout_open('Changes Log', 'sync_log', 'Track all SEO changes made by the plugin.'); ?> |
| 2031 |
|
| 2032 |
<div class="dashboard-card"> |
| 2033 |
<div class="sync-log-header"> |
| 2034 |
<div class="sync-log-title-section"> |
| 2035 |
<h2>Changes Log</h2> |
| 2036 |
<p style="color: var(--dashboard-text-secondary); margin-bottom: 0;">Recent content synchronizations from external tools.</p> |
| 2037 |
</div> |
| 2038 |
|
| 2039 |
<!-- Filters - Right aligned --> |
| 2040 |
<div class="sync-log-filters"> |
| 2041 |
<form method="get" class="sync-filters-form" onchange="this.submit()" style="display:flex;flex-direction:row;align-items:center;gap:12px;flex-wrap:nowrap;"> |
| 2042 |
<input type="hidden" name="page" value="<?php echo esc_attr($_GET['page']); ?>"> |
| 2043 |
|
| 2044 |
<select name="date_range" class="sync-filter-select"> |
| 2045 |
<option value="all" <?php selected($filters['date_range'] ?? 'all', 'all'); ?>> All Time</option> |
| 2046 |
<option value="today" <?php selected($filters['date_range'] ?? '', 'today'); ?>>Today</option> |
| 2047 |
<option value="yesterday" <?php selected($filters['date_range'] ?? '', 'yesterday'); ?>>Yesterday</option> |
| 2048 |
<option value="this_week" <?php selected($filters['date_range'] ?? '', 'this_week'); ?>>This week</option> |
| 2049 |
<option value="this_month" <?php selected($filters['date_range'] ?? '', 'this_month'); ?>>This month</option> |
| 2050 |
</select> |
| 2051 |
|
| 2052 |
<select name="status" class="sync-filter-select"> |
| 2053 |
<option value="" <?php selected($filters['status'] ?? '', ''); ?>>Status Filter</option> |
| 2054 |
<option value="published" <?php selected($filters['status'] ?? '', 'published'); ?>>Published</option> |
| 2055 |
<option value="draft" <?php selected($filters['status'] ?? '', 'draft'); ?>>Draft</option> |
| 2056 |
</select> |
| 2057 |
</form> |
| 2058 |
</div> |
| 2059 |
</div> |
| 2060 |
|
| 2061 |
<!-- Sync History List --> |
| 2062 |
<div class="sync-log-list"> |
| 2063 |
<?php if (empty($sync_records)): ?> |
| 2064 |
<div class="sync-log-empty"> |
| 2065 |
<div class="sync-log-empty-icon"><span class="dashicons dashicons-list-view" style="font-size:48px;width:48px;height:48px;color:var(--dashboard-text-secondary);"></span></div> |
| 2066 |
<h3>No sync records found</h3> |
| 2067 |
<p>Sync records will appear here when content/pages receive new updates.</p> |
| 2068 |
</div> |
| 2069 |
<?php else: ?> |
| 2070 |
<?php foreach ($sync_records as $record): ?> |
| 2071 |
<div class="sync-log-item"> |
| 2072 |
<div class="sync-log-icon"> |
| 2073 |
<div class="sync-icon-circle"> |
| 2074 |
<span class="dashicons dashicons-media-default sync-icon"></span> |
| 2075 |
</div> |
| 2076 |
</div> |
| 2077 |
|
| 2078 |
<div class="sync-log-content"> |
| 2079 |
<div class="sync-log-title"><?php echo esc_html($record->title); ?> |
| 2080 |
<?php if (!empty($record->url)): ?> |
| 2081 |
<a href="<?php echo esc_url($record->url); ?>" target="_blank" rel="noopener" title="Open URL" style="margin-left:8px; text-decoration:none;"><span class="dashicons dashicons-external" style="font-size:14px;width:14px;height:14px;vertical-align:middle;"></span></a> |
| 2082 |
<?php endif; ?> |
| 2083 |
</div> |
| 2084 |
<div class="sync-log-meta"> |
| 2085 |
<?php echo esc_html( $this->time_elapsed_string($record->created_at) ); ?> |
| 2086 |
</div> |
| 2087 |
</div> |
| 2088 |
|
| 2089 |
<div class="sync-log-status"> |
| 2090 |
<?php if ($record->status === 'published' || $record->status === 'publish'): ?> |
| 2091 |
<span class="sync-status-badge sync-status-published"> |
| 2092 |
<span class="sync-status-icon">✓</span> |
| 2093 |
Published |
| 2094 |
</span> |
| 2095 |
<?php else: ?> |
| 2096 |
<span class="sync-status-badge sync-status-draft"> |
| 2097 |
<span class="sync-status-icon">i</span> |
| 2098 |
Draft |
| 2099 |
</span> |
| 2100 |
<?php endif; ?> |
| 2101 |
</div> |
| 2102 |
</div> |
| 2103 |
<?php endforeach; ?> |
| 2104 |
<?php endif; ?> |
| 2105 |
</div> |
| 2106 |
|
| 2107 |
<!-- Pagination --> |
| 2108 |
<?php if ($total_pages > 1): ?> |
| 2109 |
<div class="sync-log-pagination"> |
| 2110 |
<div class="sync-log-pagination-info"> |
| 2111 |
Total records: <?php echo intval( $total_records ); ?> | Showing <?php echo intval( $offset ) + 1; ?>-<?php echo intval( min($offset + $per_page, $total_records) ); ?> |
| 2112 |
</div> |
| 2113 |
|
| 2114 |
<div class="sync-log-pagination-controls"> |
| 2115 |
<?php if ($page > 1): ?> |
| 2116 |
<a href="?page=<?php echo esc_attr($_GET['page']); ?>&paged=<?php echo intval( $page ) - 1; ?><?php echo esc_html( $this->build_filter_query_string($filters) ); ?>" class="sync-pagination-btn">‹</a> |
| 2117 |
<?php endif; ?> |
| 2118 |
|
| 2119 |
<?php for ($i = max(1, $page - 2); $i <= min($total_pages, $page + 2); $i++): ?> |
| 2120 |
<a href="?page=<?php echo esc_attr($_GET['page']); ?>&paged=<?php echo intval( $i ); ?><?php echo esc_html( $this->build_filter_query_string($filters) ); ?>" |
| 2121 |
class="sync-pagination-btn <?php echo $i === $page ? 'active' : ''; ?>"><?php echo intval( $i ); ?></a> |
| 2122 |
<?php endfor; ?> |
| 2123 |
|
| 2124 |
<?php if ($page < $total_pages): ?> |
| 2125 |
<a href="?page=<?php echo esc_attr($_GET['page']); ?>&paged=<?php echo intval( $page ) + 1; ?><?php echo esc_html( $this->build_filter_query_string($filters) ); ?>" class="sync-pagination-btn">›</a> |
| 2126 |
<?php endif; ?> |
| 2127 |
</div> |
| 2128 |
</div> |
| 2129 |
<?php endif; ?> |
| 2130 |
</div> |
| 2131 |
<?php $this->admin->render_layout_close(); ?> |
| 2132 |
<?php |
| 2133 |
} |
| 2134 |
|
| 2135 |
public function creat_error_Logs_List() |
| 2136 |
{ |
| 2137 |
// printf('<h1> Error Logs </h1>'); |
| 2138 |
// $error_log = new Metasync_Error_Logs_Table($this->data_error_log_list); |
| 2139 |
// $error_log->create_admin_error_log_list_interface(); |
| 2140 |
} |
| 2141 |
|
| 2142 |
public function create_admin_heartbeat_error_logs_page() |
| 2143 |
{ |
| 2144 |
?> |
| 2145 |
<?php $this->admin->render_layout_open('Heartbeat Error Logs', 'general', ''); ?> |
| 2146 |
|
| 2147 |
<div class="dashboard-card"> |
| 2148 |
<h2>💓 HeartBeat Error Logs</h2> |
| 2149 |
<p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Monitor WordPress heartbeat errors to identify connectivity issues and system problems.</p> |
| 2150 |
<?php |
| 2151 |
$heartbeat_errors = new Metasync_HeartBeat_Error_Monitor($this->admin->db_heartbeat_errors); |
| 2152 |
$heartbeat_errors->create_admin_heartbeat_errors_interface(); |
| 2153 |
?> |
| 2154 |
</div> |
| 2155 |
<?php $this->admin->render_layout_close(); ?> |
| 2156 |
<?php |
| 2157 |
} |
| 2158 |
|
| 2159 |
public function create_admin_bot_statistics_page() |
| 2160 |
{ |
| 2161 |
require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-otto-bot-statistics.php'; |
| 2162 |
} |
| 2163 |
|
| 2164 |
// ------------------------------------------------------------------ |
| 2165 |
// Private helpers (copied from Metasync_Admin, used only by pages above) |
| 2166 |
// ------------------------------------------------------------------ |
| 2167 |
|
| 2168 |
private function time_elapsed_string($datetime, $full = false) |
| 2169 |
{ |
| 2170 |
if(empty($datetime)){ |
| 2171 |
return ""; |
| 2172 |
} |
| 2173 |
# Stored timestamps use current_time('mysql') which is WP local time |
| 2174 |
# with no timezone marker. Defaulting DateTime to UTC produces a diff |
| 2175 |
# equal to the WP timezone offset (QA: "4 hours ago" after a |
| 2176 |
# 4-minute sync on non-UTC sites). |
| 2177 |
$wp_tz = function_exists('wp_timezone') ? wp_timezone() : new DateTimeZone('UTC'); |
| 2178 |
$now = new DateTime('now', $wp_tz); |
| 2179 |
$ago = new DateTime($datetime, $wp_tz); |
| 2180 |
|
| 2181 |
$diff = $now->diff($ago); |
| 2182 |
|
| 2183 |
$string = [ |
| 2184 |
'y' => 'year', |
| 2185 |
'm' => 'month', |
| 2186 |
'w' => 'week', |
| 2187 |
'd' => 'day', |
| 2188 |
'h' => 'hour', |
| 2189 |
'i' => 'minute', |
| 2190 |
's' => 'second', |
| 2191 |
]; |
| 2192 |
|
| 2193 |
foreach ($string as $k => &$v) { |
| 2194 |
if (isset($diff->$k) && $diff->$k) { |
| 2195 |
$v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : ''); |
| 2196 |
} else { |
| 2197 |
unset($string[$k]); |
| 2198 |
} |
| 2199 |
} |
| 2200 |
if (!$full) $string = array_slice($string, 0, 1); |
| 2201 |
return $string ? implode(', ', $string) . ' ago' : 'just now'; |
| 2202 |
} |
| 2203 |
|
| 2204 |
private function build_filter_query_string($filters) |
| 2205 |
{ |
| 2206 |
$query_parts = []; |
| 2207 |
foreach ($filters as $key => $value) { |
| 2208 |
if (!empty($value)) { |
| 2209 |
$query_parts[] = $key . '=' . urlencode($value); |
| 2210 |
} |
| 2211 |
} |
| 2212 |
return !empty($query_parts) ? '&' . implode('&', $query_parts) : ''; |
| 2213 |
} |
| 2214 |
|
| 2215 |
private function handle_sync_log_ajax() |
| 2216 |
{ |
| 2217 |
wp_die(); |
| 2218 |
} |
| 2219 |
} |
| 2220 |
|