| 1 |
<?php |
| 2 |
// If this file is called directly, abort. |
| 3 |
if (!defined('ABSPATH')) { |
| 4 |
exit; |
| 5 |
} |
| 6 |
|
| 7 |
/** |
| 8 |
* Settings field callbacks and accordion/section helpers extracted from Metasync_Admin. |
| 9 |
* |
| 10 |
* @package Metasync |
| 11 |
* @subpackage Metasync/includes |
| 12 |
*/ |
| 13 |
class Metasync_Settings_Fields { |
| 14 |
|
| 15 |
private static $instance = null; |
| 16 |
|
| 17 |
/** |
| 18 |
* Reference to the admin instance for callbacks to non-extracted admin methods. |
| 19 |
* |
| 20 |
* @var Metasync_Admin|null |
| 21 |
*/ |
| 22 |
private $admin_instance = null; |
| 23 |
|
| 24 |
private function __construct() {} |
| 25 |
|
| 26 |
/** |
| 27 |
* @return self |
| 28 |
*/ |
| 29 |
public static function instance() { |
| 30 |
if (null === self::$instance) { |
| 31 |
self::$instance = new self(); |
| 32 |
} |
| 33 |
return self::$instance; |
| 34 |
} |
| 35 |
|
| 36 |
/** |
| 37 |
* Inject the admin instance so callbacks to non-extracted methods still work. |
| 38 |
* |
| 39 |
* @param Metasync_Admin $admin |
| 40 |
*/ |
| 41 |
public function set_admin_instance($admin) { |
| 42 |
$this->admin_instance = $admin; |
| 43 |
} |
| 44 |
|
| 45 |
// ──────────────────────────────────────────────────────────────── |
| 46 |
// Accordion / Section helpers |
| 47 |
// ──────────────────────────────────────────────────────────────── |
| 48 |
|
| 49 |
public function get_accordion_sections_config() { |
| 50 |
return array( |
| 51 |
'connection' => array( |
| 52 |
'title' => 'Connection & Authentication', |
| 53 |
'description' => 'Manage your API connection and authentication settings', |
| 54 |
'icon' => 'admin-network', |
| 55 |
'priority' => 10, |
| 56 |
'default_open' => true, |
| 57 |
'fields' => array( |
| 58 |
'searchatlas_api_key', |
| 59 |
'apikey' |
| 60 |
) |
| 61 |
), |
| 62 |
'otto_ssr' => array( |
| 63 |
'title' => Metasync::get_whitelabel_otto_name() . ' Server-Side Rendering', |
| 64 |
'description' => 'Configure ' . Metasync::get_whitelabel_otto_name() . ' rendering and display options', |
| 65 |
'icon' => 'performance', |
| 66 |
'priority' => 20, |
| 67 |
'default_open' => false, |
| 68 |
'fields' => array( |
| 69 |
'otto_pixel_uuid', |
| 70 |
'otto_disable_on_loggedin', |
| 71 |
'otto_disable_preview_button', |
| 72 |
'otto_wp_rocket_compat', |
| 73 |
'otto_sg_optimizer_compat', |
| 74 |
'bookingpress_session_compat', |
| 75 |
) |
| 76 |
), |
| 77 |
'edge_cache' => array( |
| 78 |
'title' => 'Edge Cache / CDN', |
| 79 |
'description' => 'Configure CDN credentials for cache-tag purging', |
| 80 |
'icon' => 'admin-site-alt3', |
| 81 |
'priority' => 22, |
| 82 |
'default_open' => false, |
| 83 |
'render_callback' => array('Metasync_Edge_Cache_Settings', 'render'), |
| 84 |
), |
| 85 |
'bot_detection' => array( |
| 86 |
'title' => 'Bot Detection & Filtering', |
| 87 |
'description' => 'Manage bot traffic and reduce unnecessary API calls', |
| 88 |
'icon' => 'filter', |
| 89 |
'priority' => 25, |
| 90 |
'default_open' => false, |
| 91 |
'fields' => array( |
| 92 |
'otto_disable_for_bots', |
| 93 |
'otto_bot_whitelist', |
| 94 |
'otto_bot_blacklist', |
| 95 |
'otto_bot_statistics_link' |
| 96 |
) |
| 97 |
), |
| 98 |
'open_graph' => array( |
| 99 |
'title' => 'Open Graph', |
| 100 |
'description' => 'Control which Open Graph and article meta tags are output', |
| 101 |
'icon' => 'share', |
| 102 |
'priority' => 38, |
| 103 |
'default_open' => false, |
| 104 |
'fields' => array( |
| 105 |
'og_image_dimensions', |
| 106 |
'article_timestamps', |
| 107 |
'article_author', |
| 108 |
'article_section', |
| 109 |
'article_tags', |
| 110 |
'twitter_image_alt' |
| 111 |
) |
| 112 |
), |
| 113 |
'editor_settings' => array( |
| 114 |
'title' => 'Post/Page Editor Settings', |
| 115 |
'description' => 'Customize meta boxes and editor functionality', |
| 116 |
'icon' => 'edit', |
| 117 |
'priority' => 40, |
| 118 |
'default_open' => false, |
| 119 |
'fields' => array( |
| 120 |
'disable_common_robots_metabox', |
| 121 |
'disable_advance_robots_metabox', |
| 122 |
'disable_redirection_metabox', |
| 123 |
'disable_canonical_metabox', |
| 124 |
'disable_social_opengraph_metabox', |
| 125 |
'disable_schema_markup_metabox', |
| 126 |
'disable_seo_metabox', |
| 127 |
'open_external_links' |
| 128 |
) |
| 129 |
), |
| 130 |
'user_management' => array( |
| 131 |
'title' => 'User Management for Content', |
| 132 |
'description' => 'Configure which users are allowed to be authors of content synced.', |
| 133 |
'icon' => 'groups', |
| 134 |
'priority' => 50, |
| 135 |
'default_open' => false, |
| 136 |
'fields' => array( |
| 137 |
'content_genius_sync_roles' |
| 138 |
) |
| 139 |
), |
| 140 |
'content_rendering' => array( |
| 141 |
'title' => 'Content Rendering', |
| 142 |
'description' => 'Configure how synced content is stored and rendered by your page builder', |
| 143 |
'icon' => 'admin-appearance', |
| 144 |
'priority' => 55, |
| 145 |
'default_open' => false, |
| 146 |
'fields' => array( |
| 147 |
'default_page_builder' |
| 148 |
) |
| 149 |
), |
| 150 |
'llms_txt' => array( |
| 151 |
'title' => 'LLMs.txt', |
| 152 |
'description' => 'Publish a standards-compliant /llms.txt so AI search engines and assistants can discover your content', |
| 153 |
'icon' => 'media-text', |
| 154 |
'priority' => 56, |
| 155 |
'default_open' => false, |
| 156 |
'render_callback' => array($this, 'render_llms_txt_section'), |
| 157 |
), |
| 158 |
'advanced' => array( |
| 159 |
'title' => 'Plugin Settings', |
| 160 |
'description' => 'System configuration and maintenance options', |
| 161 |
'icon' => 'admin-settings', |
| 162 |
'priority' => 60, |
| 163 |
'default_open' => false, |
| 164 |
'fields' => array( |
| 165 |
'permalink_structure', |
| 166 |
'hide_dashboard_framework', |
| 167 |
'show_admin_bar_status', |
| 168 |
'enable_auto_updates', |
| 169 |
'import_external_data' |
| 170 |
) |
| 171 |
) |
| 172 |
); |
| 173 |
} |
| 174 |
|
| 175 |
public function get_advanced_accordion_config() { |
| 176 |
$config = array(); |
| 177 |
|
| 178 |
$user = wp_get_current_user(); |
| 179 |
if (in_array('administrator', (array) $user->roles)) { |
| 180 |
$config['plugin_access'] = array( |
| 181 |
'title' => 'User Roles with Plugin Access', |
| 182 |
'description' => 'Control which user roles can see and access this plugin', |
| 183 |
'icon' => 'admin-users', |
| 184 |
'priority' => 5, |
| 185 |
'default_open' => false, |
| 186 |
'render_callback' => array($this, 'render_plugin_access_roles_section') |
| 187 |
); |
| 188 |
} |
| 189 |
|
| 190 |
$config['debug_mode'] = array( |
| 191 |
'title' => 'Debug Mode', |
| 192 |
'description' => 'Manage debug mode with automatic disable and safety limits', |
| 193 |
'icon' => 'info', |
| 194 |
'priority' => 8, |
| 195 |
'default_open' => false, |
| 196 |
'render_callback' => array($this->admin_instance, 'render_debug_mode_section') |
| 197 |
); |
| 198 |
|
| 199 |
$config['error_logs'] = array( |
| 200 |
'title' => 'Error Logs', |
| 201 |
'description' => 'View and manage error logs to troubleshoot issues', |
| 202 |
'icon' => 'warning', |
| 203 |
'priority' => 10, |
| 204 |
'default_open' => true, |
| 205 |
'render_callback' => array($this->admin_instance, 'render_error_log_content') |
| 206 |
); |
| 207 |
|
| 208 |
$config['execution_settings'] = array( |
| 209 |
'title' => 'Execution Settings', |
| 210 |
'description' => 'Configure resource limits and execution parameters', |
| 211 |
'icon' => 'controls-play', |
| 212 |
'priority' => 15, |
| 213 |
'default_open' => false, |
| 214 |
'render_callback' => array($this, 'render_execution_settings_section') |
| 215 |
); |
| 216 |
|
| 217 |
$config['otto_cache'] = array( |
| 218 |
'title' => 'Cache Management', |
| 219 |
'description' => 'Manage ' . Metasync::get_whitelabel_otto_name() . ' cache and clear all cache plugins', |
| 220 |
'icon' => 'database', |
| 221 |
'priority' => 20, |
| 222 |
'default_open' => false, |
| 223 |
'render_callback' => array($this->admin_instance, 'render_otto_cache_management') |
| 224 |
); |
| 225 |
|
| 226 |
$config['db_cleanup'] = array( |
| 227 |
'title' => 'Database Cleanup', |
| 228 |
'description' => 'Remove orphaned data and schedule automated weekly cleanup', |
| 229 |
'icon' => 'trash', |
| 230 |
'priority' => 25, |
| 231 |
'default_open' => false, |
| 232 |
'render_callback' => array($this->admin_instance, 'render_db_cleanup_section') |
| 233 |
); |
| 234 |
|
| 235 |
$config['performance'] = array( |
| 236 |
'title' => 'Performance & CPU Load', |
| 237 |
'description' => 'Configure CPU load thresholds to prevent processing during peak server load', |
| 238 |
'icon' => 'chart-line', |
| 239 |
'priority' => 27, |
| 240 |
'default_open' => false, |
| 241 |
'render_callback' => array($this->admin_instance, 'render_cpu_monitor_section') |
| 242 |
); |
| 243 |
|
| 244 |
$config['reset_settings'] = array( |
| 245 |
'title' => 'Reset Plugin Settings', |
| 246 |
'description' => 'Reset all plugin settings to default values', |
| 247 |
'icon' => 'update', |
| 248 |
'priority' => 30, |
| 249 |
'default_open' => false, |
| 250 |
'render_callback' => array($this, 'render_reset_settings_section') |
| 251 |
); |
| 252 |
|
| 253 |
return $config; |
| 254 |
} |
| 255 |
|
| 256 |
public function render_advanced_accordion() { |
| 257 |
$sections_config = $this->get_advanced_accordion_config(); |
| 258 |
|
| 259 |
uasort($sections_config, function($a, $b) { |
| 260 |
return $a['priority'] - $b['priority']; |
| 261 |
}); |
| 262 |
|
| 263 |
echo '<div class="metasync-settings-accordion">'; |
| 264 |
|
| 265 |
foreach ($sections_config as $section_key => $section_data) { |
| 266 |
$section_id = 'metasync-advanced-section-' . $section_key; |
| 267 |
$is_open = $section_data['default_open']; |
| 268 |
$aria_expanded = $is_open ? 'true' : 'false'; |
| 269 |
$content_state = $is_open ? 'open' : 'closed'; |
| 270 |
|
| 271 |
echo '<div class="metasync-accordion-section" data-section="' . esc_attr($section_key) . '">'; |
| 272 |
|
| 273 |
echo '<div class="metasync-accordion-header" role="button" tabindex="0" aria-expanded="' . $aria_expanded . '" aria-controls="' . $section_id . '">'; |
| 274 |
echo '<div class="metasync-accordion-title">'; |
| 275 |
echo '<span class="metasync-accordion-icon"><span class="dashicons dashicons-' . esc_attr($section_data['icon']) . '"></span></span>'; |
| 276 |
echo '<div class="metasync-accordion-text">'; |
| 277 |
echo '<h3>' . esc_html($section_data['title']) . '</h3>'; |
| 278 |
echo '<p class="metasync-accordion-description">' . esc_html($section_data['description']) . '</p>'; |
| 279 |
echo '</div>'; |
| 280 |
echo '</div>'; |
| 281 |
echo '<button type="button" class="metasync-accordion-toggle" aria-label="Toggle section">'; |
| 282 |
echo '<span class="toggle-icon">▼</span>'; |
| 283 |
echo '</button>'; |
| 284 |
echo '</div>'; |
| 285 |
|
| 286 |
echo '<div class="metasync-accordion-content" id="' . $section_id . '" data-state="' . $content_state . '">'; |
| 287 |
|
| 288 |
if (isset($section_data['render_callback']) && is_callable($section_data['render_callback'])) { |
| 289 |
call_user_func($section_data['render_callback']); |
| 290 |
} |
| 291 |
|
| 292 |
echo '</div>'; |
| 293 |
echo '</div>'; |
| 294 |
} |
| 295 |
|
| 296 |
echo '</div>'; |
| 297 |
} |
| 298 |
|
| 299 |
public function render_reset_settings_section() { |
| 300 |
?> |
| 301 |
<div style="background: var(--dashboard-card-bg); padding: 20px; border-radius: 8px;"> |
| 302 |
<div style="background: rgba(255, 243, 205, 0.1); border: 1px solid rgba(255, 234, 167, 0.3); border-radius: 8px; padding: 20px; margin-bottom: 20px;"> |
| 303 |
<h4 style="color: var(--dashboard-warning, #f59e0b); margin: 0 0 12px 0; display: flex; align-items: center; gap: 8px;"> |
| 304 |
<span class="dashicons dashicons-warning" style="color:var(--dashboard-warning,#f59e0b);font-size:18px;width:18px;height:18px;"></span> |
| 305 |
<span>Important Warning</span> |
| 306 |
</h4> |
| 307 |
<p style="color: var(--dashboard-text-secondary); margin: 0 0 12px 0;">This action will permanently delete:</p> |
| 308 |
<ul style="color: var(--dashboard-text-secondary); margin: 0 0 12px 20px; line-height: 1.8;"> |
| 309 |
<li>All API keys and authentication tokens</li> |
| 310 |
<li>White label branding settings</li> |
| 311 |
<li>Plugin configuration and preferences</li> |
| 312 |
<li>Instant indexing settings</li> |
| 313 |
<li>Media optimization settings and cache</li> |
| 314 |
<li>Code minification settings</li> |
| 315 |
<li>All cached data and crawl information</li> |
| 316 |
</ul> |
| 317 |
<p style="color: var(--dashboard-warning, #f59e0b); margin: 0; font-weight: 600;">You will need to reconfigure the plugin completely after this reset.</p> |
| 318 |
</div> |
| 319 |
<form method="post" action="" onsubmit="return confirmClearSettings(event)"> |
| 320 |
<?php wp_nonce_field('metasync_clear_all_settings_nonce', 'clear_all_settings_nonce'); ?> |
| 321 |
<input type="hidden" name="clear_all_settings" value="yes" /> |
| 322 |
<button type="submit" class="metasync-btn-danger" style="background: var(--dashboard-error, #ef4444); color: #ffffff; border: none; padding: 10px 20px; border-radius: 8px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 2px 4px rgba(239, 68, 68, 0.2);" onmouseover="this.style.background='#dc2626'; this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 8px rgba(239, 68, 68, 0.3)';" onmouseout="this.style.background='var(--dashboard-error, #ef4444)'; this.style.transform='translateY(0)'; this.style.boxShadow='0 2px 4px rgba(239, 68, 68, 0.2)';"> |
| 323 |
<span class="dashicons dashicons-trash" style="margin-top:3px;font-size:15px;width:15px;height:15px;"></span> Clear All Settings |
| 324 |
</button> |
| 325 |
</form> |
| 326 |
</div> |
| 327 |
<div style="background: var(--dashboard-card-bg); padding: 20px; border-radius: 8px; margin-top: 20px;"> |
| 328 |
<h4 style="color: var(--dashboard-text-primary, #1e1e1e); margin: 0 0 12px 0; display: flex; align-items: center; gap: 8px;"> |
| 329 |
<span class="dashicons dashicons-universal-access-alt" style="font-size:18px;width:18px;height:18px;"></span> |
| 330 |
<span>Delete data on uninstall</span> |
| 331 |
</h4> |
| 332 |
<p style="color: var(--dashboard-text-secondary); margin: 0 0 12px 0;"> |
| 333 |
By default, deleting the plugin keeps your data: sitemap and redirection settings, per-post SEO values, and the plugin's database tables stay in place in case you reinstall. Enable this to also remove all of that when the plugin itself is deleted from the Plugins screen. |
| 334 |
</p> |
| 335 |
<p style="color: var(--dashboard-text-secondary); margin: 0 0 12px 0;"> |
| 336 |
The Google service account and the IndexNow API key are always deleted on uninstall, regardless of this setting. |
| 337 |
</p> |
| 338 |
<form method="post" action=""> |
| 339 |
<?php wp_nonce_field('metasync_uninstall_data_toggle_nonce', 'uninstall_data_toggle_nonce'); ?> |
| 340 |
<input type="hidden" name="metasync_uninstall_data_toggle_form" value="1" /> |
| 341 |
<label style="color: var(--dashboard-text-primary, #1e1e1e); display: flex; align-items: center; gap: 8px; margin: 0 0 12px 0; cursor: pointer;"> |
| 342 |
<input type="checkbox" name="metasync_delete_data_on_uninstall" value="yes" <?php checked(get_option('metasync_delete_data_on_uninstall', 'no'), 'yes'); ?> /> |
| 343 |
Delete all plugin data (tables, settings, per-post SEO values, logs) when the plugin is uninstalled |
| 344 |
</label> |
| 345 |
<button type="submit" class="button button-secondary"> |
| 346 |
Save Uninstall Setting |
| 347 |
</button> |
| 348 |
</form> |
| 349 |
</div> |
| 350 |
<script> |
| 351 |
function confirmClearSettings(event) { |
| 352 |
event.preventDefault(); |
| 353 |
|
| 354 |
var firstConfirm = confirm("⚠️ WARNING: This will permanently delete ALL plugin settings!\n\nThis action cannot be undone. Are you sure you want to continue?"); |
| 355 |
if (!firstConfirm) { |
| 356 |
return false; |
| 357 |
} |
| 358 |
|
| 359 |
var secondConfirm = confirm("🚨 FINAL WARNING 🚨\n\nThis will delete:\n• All API keys and authentication tokens\n• White label branding settings\n• Plugin configuration and preferences\n• Instant indexing settings\n• Media optimization settings and cache\n• Code minification settings\n• All cached data\n\nYou will need to reconfigure the entire plugin from scratch.\n\nType 'DELETE' in the next prompt to confirm."); |
| 360 |
if (!secondConfirm) { |
| 361 |
return false; |
| 362 |
} |
| 363 |
|
| 364 |
var typeConfirm = prompt("Type 'DELETE' (in capital letters) to confirm you want to permanently clear all settings:"); |
| 365 |
if (typeConfirm !== 'DELETE') { |
| 366 |
alert("Settings reset cancelled. Type 'DELETE' exactly to confirm."); |
| 367 |
return false; |
| 368 |
} |
| 369 |
|
| 370 |
event.target.submit(); |
| 371 |
return false; |
| 372 |
} |
| 373 |
</script> |
| 374 |
<?php |
| 375 |
} |
| 376 |
|
| 377 |
public function render_google_index_section() { |
| 378 |
if (!function_exists('google_index_direct')) { |
| 379 |
if (file_exists(plugin_dir_path(dirname(__FILE__)) . 'google-index/google-index-init.php')) { |
| 380 |
require_once plugin_dir_path(dirname(__FILE__)) . 'google-index/google-index-init.php'; |
| 381 |
} else { |
| 382 |
error_log('MetaSync Google Index: google-index-init.php not found at ' . plugin_dir_path(dirname(__FILE__)) . 'google-index/google-index-init.php'); |
| 383 |
return; |
| 384 |
} |
| 385 |
} |
| 386 |
|
| 387 |
$google_index = google_index_direct(); |
| 388 |
$service_info = $google_index->get_service_account_info(); |
| 389 |
$is_configured = !isset($service_info['error']); |
| 390 |
|
| 391 |
$saved_json_display = $is_configured ? $google_index->get_redacted_config_json() : ''; |
| 392 |
|
| 393 |
include plugin_dir_path(dirname(__FILE__)) . 'views/metasync-google-index-api-settings.php'; |
| 394 |
|
| 395 |
// Render post types selection |
| 396 |
$options = get_option('metasync_options_instant_indexing', ['post_types' => []]); |
| 397 |
$post_types_settings = isset($options['post_types']) && is_array($options['post_types']) ? $options['post_types'] : []; |
| 398 |
include plugin_dir_path(dirname(__FILE__)) . 'views/metasync-google-instant-post-types.php'; |
| 399 |
} |
| 400 |
|
| 401 |
public function render_bing_index_section() { |
| 402 |
require_once plugin_dir_path(dirname(__FILE__)) . 'bing-index/class-metasync-bing-instant-index.php'; |
| 403 |
$bing_index = new Metasync_Bing_Instant_Index(); |
| 404 |
$api_key = $bing_index->get_setting('api_key'); |
| 405 |
$endpoint = $bing_index->get_setting('endpoint', 'indexnow'); |
| 406 |
$post_types_settings = $bing_index->get_setting('post_types', []); |
| 407 |
$is_configured = !empty($api_key); |
| 408 |
|
| 409 |
$post_types = get_post_types(['public' => true], 'objects'); |
| 410 |
|
| 411 |
?> |
| 412 |
<div style="padding: 20px;"> |
| 413 |
<!-- About IndexNow (Collapsible) --> |
| 414 |
<details style="margin-bottom: 20px;"> |
| 415 |
<summary style="cursor: pointer; font-weight: 600; color: var(--dashboard-text-primary);"> |
| 416 |
About IndexNow Protocol |
| 417 |
</summary> |
| 418 |
<div style="margin-top: 10px; padding: 15px; background: rgba(255, 255, 255, 0.03); border-radius: 4px;"> |
| 419 |
<p style="color: var(--dashboard-text-secondary); margin: 0 0 10px 0;"> |
| 420 |
IndexNow is a simple protocol that allows websites to instantly notify search engines about URL changes. |
| 421 |
It's supported by Bing, Yandex, Naver, Seznam, and other search engines. |
| 422 |
</p> |
| 423 |
<ul style="color: var(--dashboard-text-secondary); margin: 0 0 10px 20px;"> |
| 424 |
<li>✓ Instant notification to multiple search engines</li> |
| 425 |
<li>✓ Simple API key authentication (no OAuth required)</li> |
| 426 |
<li>✓ Supports batch URL submissions (up to 10,000 URLs)</li> |
| 427 |
<li>✓ Free to use with no quotas</li> |
| 428 |
<li>✓ Fire-and-forget protocol (no status checking needed)</li> |
| 429 |
</ul> |
| 430 |
<p style="color: var(--dashboard-text-secondary); margin: 0;"> |
| 431 |
<strong>Resources:</strong> |
| 432 |
<a href="https://www.indexnow.org/documentation" target="_blank" style="color: var(--dashboard-accent);">IndexNow Documentation</a> | |
| 433 |
<a href="https://www.bing.com/webmasters" target="_blank" style="color: var(--dashboard-accent);">Bing Webmaster Tools</a> |
| 434 |
</p> |
| 435 |
</div> |
| 436 |
</details> |
| 437 |
|
| 438 |
<!-- Configuration Status --> |
| 439 |
<div style="margin-bottom: 20px;"> |
| 440 |
<?php if ($is_configured): ?> |
| 441 |
<div style="padding: 12px; background: rgba(76, 175, 80, 0.1); border-left: 4px solid #4caf50; border-radius: 4px;"> |
| 442 |
<p style="margin: 0; color: var(--dashboard-text-primary);"> |
| 443 |
<strong style="color: #4caf50;">IndexNow API Configured</strong><br> |
| 444 |
<span style="color: var(--dashboard-text-secondary); font-size: 0.95em;"> |
| 445 |
API Key: <code style="padding: 2px 6px; background: rgba(0,0,0,0.1); border-radius: 3px;"><?php echo esc_html(substr($api_key, 0, 8) . '...' . substr($api_key, -8)); ?></code> |
| 446 |
</span> |
| 447 |
</p> |
| 448 |
</div> |
| 449 |
<?php else: ?> |
| 450 |
<div style="padding: 12px; background: rgba(255, 152, 0, 0.1); border-left: 4px solid #ff9800; border-radius: 4px;"> |
| 451 |
<p style="margin: 0; color: var(--dashboard-text-primary);"> |
| 452 |
<strong style="color: #ff9800;">Configuration Required</strong><br> |
| 453 |
<span style="color: var(--dashboard-text-secondary); font-size: 0.95em;"> |
| 454 |
Configure your IndexNow API key below to enable instant indexing with Bing and other search engines. |
| 455 |
</span> |
| 456 |
</p> |
| 457 |
</div> |
| 458 |
<?php endif; ?> |
| 459 |
</div> |
| 460 |
|
| 461 |
<!-- Settings Fields --> |
| 462 |
<table class="form-table" style="margin-top: 0;"> |
| 463 |
<tr> |
| 464 |
<th scope="row" style="width: 200px; padding-top: 15px;"> |
| 465 |
<label for="metasync_bing_api_key_inline">IndexNow API Key <span style="color: #d63638;">*</span></label> |
| 466 |
<?php Metasync::render_tooltip_icon('metasync_bing_api_key_inline', 'This key lets Bing trust indexing requests from your site. Click \'Generate Random API Key\' if you don\'t have one — the plugin publishes the matching verification file automatically, no manual upload needed.'); ?> |
| 467 |
</th> |
| 468 |
<td style="padding-top: 15px;"> |
| 469 |
<input type="text" |
| 470 |
name="metasync_bing_api_key_inline" |
| 471 |
id="metasync_bing_api_key_inline" |
| 472 |
class="large-text" |
| 473 |
value="<?php echo esc_attr($api_key); ?>" |
| 474 |
placeholder="Enter your IndexNow API key (32+ character hexadecimal string)" /> |
| 475 |
<br> |
| 476 |
<button type="button" |
| 477 |
id="generate-bing-api-key-inline" |
| 478 |
class="button button-secondary" |
| 479 |
style="margin-top: 8px;"> |
| 480 |
Generate Random API Key |
| 481 |
</button> |
| 482 |
<p class="description" style="margin-top: 8px;"> |
| 483 |
Your IndexNow API key is required to submit URLs for instant indexing. You can generate a random key or use your own (32+ character hexadecimal string). |
| 484 |
After saving, a verification file will be automatically created at your site root. |
| 485 |
</p> |
| 486 |
</td> |
| 487 |
</tr> |
| 488 |
|
| 489 |
<tr> |
| 490 |
<th scope="row" style="padding-top: 15px;"> |
| 491 |
<label>API Endpoint</label> |
| 492 |
</th> |
| 493 |
<td style="padding-top: 15px;"> |
| 494 |
<fieldset> |
| 495 |
<label style="display: block; margin-bottom: 8px;"> |
| 496 |
<input type="radio" |
| 497 |
name="metasync_bing_endpoint_inline" |
| 498 |
value="indexnow" |
| 499 |
<?php checked($endpoint, 'indexnow'); ?>> |
| 500 |
<strong>IndexNow.org</strong> (Recommended) |
| 501 |
<span style="color: var(--dashboard-text-secondary); font-size: 0.9em; display: block; margin-left: 24px;"> |
| 502 |
Notifies Bing, Yandex, Naver, Seznam, and other participating search engines |
| 503 |
</span> |
| 504 |
</label> |
| 505 |
<label style="display: block;"> |
| 506 |
<input type="radio" |
| 507 |
name="metasync_bing_endpoint_inline" |
| 508 |
value="bing" |
| 509 |
<?php checked($endpoint, 'bing'); ?>> |
| 510 |
<strong>Bing.com</strong> (Bing-specific) |
| 511 |
<span style="color: var(--dashboard-text-secondary); font-size: 0.9em; display: block; margin-left: 24px;"> |
| 512 |
Direct submission to Bing only |
| 513 |
</span> |
| 514 |
</label> |
| 515 |
</fieldset> |
| 516 |
<p class="description"> |
| 517 |
Select which endpoint to use for submitting URLs. The IndexNow.org endpoint is recommended as it notifies multiple search engines at once. |
| 518 |
</p> |
| 519 |
</td> |
| 520 |
</tr> |
| 521 |
|
| 522 |
<tr> |
| 523 |
<th scope="row" style="padding-top: 15px;"> |
| 524 |
<label>Auto-Submit Post Types</label> |
| 525 |
</th> |
| 526 |
<td style="padding-top: 15px;"> |
| 527 |
<fieldset> |
| 528 |
<?php foreach ($post_types as $post_type): ?> |
| 529 |
<label style="display: inline-block; margin-right: 20px; margin-bottom: 8px;"> |
| 530 |
<input type="checkbox" |
| 531 |
name="metasync_bing_post_types_inline[<?php echo esc_attr($post_type->name); ?>]" |
| 532 |
value="<?php echo esc_attr($post_type->name); ?>" |
| 533 |
<?php checked(in_array($post_type->name, $post_types_settings, true)); ?>> |
| 534 |
<?php echo esc_html($post_type->label); ?> |
| 535 |
</label> |
| 536 |
<?php endforeach; ?> |
| 537 |
</fieldset> |
| 538 |
<p class="description"> |
| 539 |
Selected post types will be automatically submitted to IndexNow when published or updated. |
| 540 |
</p> |
| 541 |
</td> |
| 542 |
</tr> |
| 543 |
|
| 544 |
<tr> |
| 545 |
<th scope="row" style="padding-top: 15px;"> |
| 546 |
<label>Plugin Source Control</label> |
| 547 |
</th> |
| 548 |
<td style="padding-top: 15px;"> |
| 549 |
<fieldset> |
| 550 |
<label style="display: flex; align-items: flex-start; gap: 8px;"> |
| 551 |
<input type="checkbox" |
| 552 |
name="metasync_bing_disable_other_plugins_inline" |
| 553 |
value="1" |
| 554 |
<?php checked($bing_index->get_setting('disable_other_plugins', true), true); ?> |
| 555 |
style="margin-top: 2px;"> |
| 556 |
<span> |
| 557 |
<strong>Disable other IndexNow plugins</strong> |
| 558 |
<span style="display: block; color: var(--dashboard-text-secondary); font-size: 0.9em; margin-top: 4px; font-weight: normal;"> |
| 559 |
When enabled, Our plugin will be the exclusive source for IndexNow submissions in Bing Webmaster Tools. |
| 560 |
This disables IndexNow features in Yoast SEO, Rank Math, and other competing plugins to prevent duplicate submissions. |
| 561 |
</span> |
| 562 |
</span> |
| 563 |
</label> |
| 564 |
</fieldset> |
| 565 |
</td> |
| 566 |
</tr> |
| 567 |
</table> |
| 568 |
|
| 569 |
</div> |
| 570 |
|
| 571 |
<script> |
| 572 |
jQuery(document).ready(function($) { |
| 573 |
$('#generate-bing-api-key-inline').on('click', function() { |
| 574 |
const array = new Uint8Array(16); |
| 575 |
crypto.getRandomValues(array); |
| 576 |
const hexString = Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); |
| 577 |
$('#metasync_bing_api_key_inline').val(hexString); |
| 578 |
|
| 579 |
$('#enable_binginstantindex').prop('checked', true); |
| 580 |
|
| 581 |
$('#metasync_bing_api_key_inline').trigger('change'); |
| 582 |
}); |
| 583 |
|
| 584 |
$('#metasync_bing_api_key_inline').on('input', function() { |
| 585 |
const apiKey = $(this).val().trim(); |
| 586 |
if (apiKey.length >= 8) { |
| 587 |
$('#enable_binginstantindex').prop('checked', true); |
| 588 |
} |
| 589 |
}); |
| 590 |
|
| 591 |
function toggleBingConfig() { |
| 592 |
const isEnabled = $('#enable_binginstantindex').is(':checked'); |
| 593 |
const $apiConfig = $('#enable_binginstantindex').closest('tr').nextAll('tr').first(); |
| 594 |
if (isEnabled) { |
| 595 |
$apiConfig.show(); |
| 596 |
} else { |
| 597 |
$apiConfig.hide(); |
| 598 |
} |
| 599 |
} |
| 600 |
|
| 601 |
toggleBingConfig(); |
| 602 |
|
| 603 |
$('#enable_binginstantindex').on('change', toggleBingConfig); |
| 604 |
}); |
| 605 |
</script> |
| 606 |
|
| 607 |
<style> |
| 608 |
.form-table th { |
| 609 |
color: var(--dashboard-text-primary); |
| 610 |
} |
| 611 |
|
| 612 |
.form-table td { |
| 613 |
color: var(--dashboard-text-secondary); |
| 614 |
} |
| 615 |
|
| 616 |
.form-table code { |
| 617 |
padding: 2px 6px; |
| 618 |
background: rgba(0, 0, 0, 0.1); |
| 619 |
border-radius: 3px; |
| 620 |
font-size: 0.9em; |
| 621 |
} |
| 622 |
|
| 623 |
details summary { |
| 624 |
transition: color 0.2s; |
| 625 |
} |
| 626 |
|
| 627 |
details summary:hover { |
| 628 |
color: var(--dashboard-accent); |
| 629 |
} |
| 630 |
|
| 631 |
details[open] summary { |
| 632 |
margin-bottom: 10px; |
| 633 |
} |
| 634 |
</style> |
| 635 |
<?php |
| 636 |
} |
| 637 |
|
| 638 |
public function render_plugin_access_roles_section() { |
| 639 |
$general_options = Metasync::get_option('general'); |
| 640 |
|
| 641 |
$setting_exists = isset($general_options['plugin_access_roles']); |
| 642 |
$selected_roles = $general_options['plugin_access_roles'] ?? null; |
| 643 |
|
| 644 |
if (is_string($selected_roles)) { |
| 645 |
$selected_roles = array($selected_roles); |
| 646 |
} elseif (!is_array($selected_roles)) { |
| 647 |
$selected_roles = null; |
| 648 |
} |
| 649 |
|
| 650 |
if (!function_exists('wp_roles')) { |
| 651 |
require_once(ABSPATH . 'wp-admin/includes/user.php'); |
| 652 |
} |
| 653 |
global $wp_roles; |
| 654 |
|
| 655 |
if (!isset($wp_roles) || !is_object($wp_roles)) { |
| 656 |
$wp_roles = new WP_Roles(); |
| 657 |
} |
| 658 |
|
| 659 |
$all_roles = $wp_roles->roles; |
| 660 |
|
| 661 |
$all_roles_checked = is_array($selected_roles) && in_array('all', $selected_roles); |
| 662 |
?> |
| 663 |
<div style="background: var(--dashboard-card-bg); padding: 20px; border-radius: 8px;"> |
| 664 |
<p style="color: var(--dashboard-text-secondary); margin: 0 0 20px 0;"> |
| 665 |
Select which user roles can see and access this plugin's menu, settings, and options in the WordPress admin area. |
| 666 |
</p> |
| 667 |
|
| 668 |
<form method="post" action="<?php echo admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '&tab=advanced'); ?>" id="plugin-access-roles-form"> |
| 669 |
<?php wp_nonce_field('metasync_plugin_access_roles_nonce', 'plugin_access_roles_nonce'); ?> |
| 670 |
<input type="hidden" name="save_plugin_access_roles" value="yes" /> |
| 671 |
|
| 672 |
<div class="metasync-role-selector-container" style="background: var(--dashboard-card-bg-alt, rgba(255,255,255,0.05)); border: 1px solid var(--dashboard-border); border-radius: 8px; padding: 16px; max-height: 300px; overflow-y: auto;"> |
| 673 |
|
| 674 |
<!-- All Roles Option --> |
| 675 |
<label class="metasync-role-option-all" style="display: flex; align-items: center; gap: 10px; padding: 12px; cursor: pointer; border-radius: 6px; transition: background 0.2s ease;"> |
| 676 |
<input type="checkbox" |
| 677 |
id="plugin-access-all-roles" |
| 678 |
name="plugin_access_roles[]" |
| 679 |
value="all" |
| 680 |
style="width: 18px; height: 18px; cursor: pointer;" |
| 681 |
<?php checked($all_roles_checked, true); ?> /> |
| 682 |
<strong style="color: var(--dashboard-text-primary);">All Roles</strong> |
| 683 |
</label> |
| 684 |
|
| 685 |
<!-- Divider --> |
| 686 |
<hr style="border: none; border-top: 1px solid var(--dashboard-border); margin: 12px 0;"> |
| 687 |
|
| 688 |
<?php |
| 689 |
if (!empty($all_roles) && is_array($all_roles)) { |
| 690 |
foreach ($all_roles as $role_key => $role_details) { |
| 691 |
if ($role_key === 'administrator') { |
| 692 |
continue; |
| 693 |
} |
| 694 |
|
| 695 |
if (!isset($role_details['name'])) { |
| 696 |
continue; |
| 697 |
} |
| 698 |
|
| 699 |
$is_checked = is_array($selected_roles) && in_array($role_key, $selected_roles); |
| 700 |
$role_name = translate_user_role($role_details['name']); |
| 701 |
?> |
| 702 |
<label class="metasync-role-option" style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; cursor: pointer; border-radius: 6px; transition: background 0.2s ease;"> |
| 703 |
<input type="checkbox" |
| 704 |
class="plugin-access-individual-role" |
| 705 |
name="plugin_access_roles[]" |
| 706 |
value="<?php echo esc_attr($role_key); ?>" |
| 707 |
style="width: 18px; height: 18px; cursor: pointer;" |
| 708 |
<?php checked($is_checked, true); ?> /> |
| 709 |
<span style="color: var(--dashboard-text-primary);"><?php echo esc_html($role_name); ?></span> |
| 710 |
</label> |
| 711 |
<?php |
| 712 |
} |
| 713 |
} |
| 714 |
?> |
| 715 |
|
| 716 |
</div> |
| 717 |
|
| 718 |
<!-- Description --> |
| 719 |
<div style="background: rgba(59, 130, 246, 0.1); border: 1px solid rgba(59, 130, 246, 0.3); border-radius: 8px; padding: 16px; margin: 20px 0;"> |
| 720 |
<p style="color: var(--dashboard-text-secondary); margin: 0; font-size: 13px;"> |
| 721 |
<strong style="color: var(--dashboard-info, #3b82f6);">How it works:</strong><br> |
| 722 |
<strong>Administrators always have access</strong> to this plugin regardless of settings.<br> |
| 723 |
<strong>By default</strong>, only Administrators can access the plugin (no roles selected).<br> |
| 724 |
If <strong>"All Roles"</strong> is selected, all users will see the plugin.<br> |
| 725 |
Users with unchecked roles will not see the plugin in their WordPress admin area. |
| 726 |
</p> |
| 727 |
</div> |
| 728 |
|
| 729 |
<button type="submit" class="metasync-btn-primary" style="background: var(--dashboard-gradient-primary); color: #ffffff; border: none; padding: 12px 24px; border-radius: 8px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 8px rgba(0, 0, 0, 0.15)';" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 2px 4px rgba(0, 0, 0, 0.1)';"> |
| 730 |
Save Access Settings |
| 731 |
</button> |
| 732 |
</form> |
| 733 |
</div> |
| 734 |
<script> |
| 735 |
jQuery(document).ready(function($) { |
| 736 |
var $allRolesCheckbox = $('#plugin-access-all-roles'); |
| 737 |
var $individualRoles = $('.plugin-access-individual-role'); |
| 738 |
|
| 739 |
$allRolesCheckbox.on('change', function() { |
| 740 |
if ($(this).is(':checked')) { |
| 741 |
$individualRoles.prop('checked', false); |
| 742 |
} |
| 743 |
}); |
| 744 |
|
| 745 |
$individualRoles.on('change', function() { |
| 746 |
if ($(this).is(':checked')) { |
| 747 |
$allRolesCheckbox.prop('checked', false); |
| 748 |
} |
| 749 |
}); |
| 750 |
}); |
| 751 |
</script> |
| 752 |
<?php |
| 753 |
} |
| 754 |
|
| 755 |
public function get_default_execution_settings() { |
| 756 |
return array( |
| 757 |
'max_execution_time' => 30, |
| 758 |
'max_memory_limit' => 256, |
| 759 |
'log_batch_size' => 1000, |
| 760 |
'action_scheduler_batches' => 1, |
| 761 |
'otto_rate_limit' => 10, |
| 762 |
'otto_cache_ttl' => 30, |
| 763 |
'queue_cleanup_days' => 31 |
| 764 |
); |
| 765 |
} |
| 766 |
|
| 767 |
public function get_execution_setting($key, $default = null) { |
| 768 |
$settings = get_option('metasync_execution_settings', array()); |
| 769 |
$defaults = $this->get_default_execution_settings(); |
| 770 |
|
| 771 |
if (empty($settings)) { |
| 772 |
return isset($defaults[$key]) ? $defaults[$key] : $default; |
| 773 |
} |
| 774 |
|
| 775 |
return isset($settings[$key]) ? $settings[$key] : (isset($defaults[$key]) ? $defaults[$key] : $default); |
| 776 |
} |
| 777 |
|
| 778 |
public function get_all_execution_settings() { |
| 779 |
$settings = get_option('metasync_execution_settings', array()); |
| 780 |
$defaults = $this->get_default_execution_settings(); |
| 781 |
|
| 782 |
return wp_parse_args($settings, $defaults); |
| 783 |
} |
| 784 |
|
| 785 |
public function can_change_memory_limit() { |
| 786 |
$current_limit = ini_get('memory_limit'); |
| 787 |
|
| 788 |
$test_result = @ini_set('memory_limit', $current_limit); |
| 789 |
|
| 790 |
return $test_result !== false; |
| 791 |
} |
| 792 |
|
| 793 |
public function get_server_limits() { |
| 794 |
$max_execution_time = ini_get('max_execution_time'); |
| 795 |
$memory_limit = ini_get('memory_limit'); |
| 796 |
|
| 797 |
// Prefer the server-configured value: WordPress (wp_raise_memory_limit) |
| 798 |
// or this plugin (apply_memory_limit) may have already changed the |
| 799 |
// runtime limit via ini_set, so ini_get no longer reflects the server |
| 800 |
// configuration. get_cfg_var reads the php.ini value untouched. |
| 801 |
$config_memory_limit = get_cfg_var('memory_limit'); |
| 802 |
if ($config_memory_limit !== false && $config_memory_limit !== '') { |
| 803 |
$memory_limit = $config_memory_limit; |
| 804 |
} |
| 805 |
|
| 806 |
$memory_limit_mb = $this->parse_memory_limit_to_mb($memory_limit); |
| 807 |
|
| 808 |
$can_change_memory = $this->can_change_memory_limit(); |
| 809 |
|
| 810 |
$memory_limit_display = $memory_limit_mb == -1 ? 'Unlimited' : $memory_limit_mb . ' MB'; |
| 811 |
|
| 812 |
return array( |
| 813 |
'max_execution_time' => $max_execution_time == -1 ? 'Unlimited' : $max_execution_time . ' seconds', |
| 814 |
'memory_limit' => $memory_limit_display, |
| 815 |
'max_execution_time_raw' => $max_execution_time, |
| 816 |
'memory_limit_raw' => $memory_limit_mb, |
| 817 |
'can_change_memory' => $can_change_memory |
| 818 |
); |
| 819 |
} |
| 820 |
|
| 821 |
/** |
| 822 |
* Allowed range for the Max Memory Limit setting: 64 MB up to the |
| 823 |
* server-configured memory limit (null max when the server is unlimited). |
| 824 |
*/ |
| 825 |
public function get_memory_limit_allowed_range() { |
| 826 |
$server_limits = $this->get_server_limits(); |
| 827 |
$max = $server_limits['memory_limit_raw'] == -1 ? null : max(64, (int) $server_limits['memory_limit_raw']); |
| 828 |
|
| 829 |
return array( |
| 830 |
'min' => 64, |
| 831 |
'max' => $max |
| 832 |
); |
| 833 |
} |
| 834 |
|
| 835 |
public function apply_memory_limit() { |
| 836 |
if (!$this->can_change_memory_limit()) { |
| 837 |
return false; |
| 838 |
} |
| 839 |
|
| 840 |
$memory_limit_mb = $this->get_execution_setting('max_memory_limit'); |
| 841 |
|
| 842 |
$result = @ini_set('memory_limit', $memory_limit_mb . 'M'); |
| 843 |
|
| 844 |
return $result !== false; |
| 845 |
} |
| 846 |
|
| 847 |
public function parse_memory_limit_to_mb($memory_limit) { |
| 848 |
if ($memory_limit == -1 || $memory_limit == '-1') { |
| 849 |
return -1; |
| 850 |
} |
| 851 |
|
| 852 |
$memory_limit = trim($memory_limit); |
| 853 |
$last = strtolower(substr($memory_limit, -1)); |
| 854 |
$value = (int) $memory_limit; |
| 855 |
|
| 856 |
switch ($last) { |
| 857 |
case 'g': |
| 858 |
$value *= 1024; |
| 859 |
break; |
| 860 |
case 'm': |
| 861 |
break; |
| 862 |
case 'k': |
| 863 |
$value /= 1024; |
| 864 |
break; |
| 865 |
} |
| 866 |
|
| 867 |
return $value; |
| 868 |
} |
| 869 |
|
| 870 |
public function render_execution_settings_section() { |
| 871 |
$settings = $this->get_all_execution_settings(); |
| 872 |
$server_limits = $this->get_server_limits(); |
| 873 |
$memory_range = $this->get_memory_limit_allowed_range(); |
| 874 |
|
| 875 |
$warnings = array(); |
| 876 |
if ((int)$server_limits['max_execution_time_raw'] !== -1 && (int)$settings['max_execution_time'] > (int)$server_limits['max_execution_time_raw']) { |
| 877 |
$warnings['max_execution_time'] = true; |
| 878 |
} |
| 879 |
if ($server_limits['memory_limit_raw'] != -1 && $settings['max_memory_limit'] > $server_limits['memory_limit_raw']) { |
| 880 |
$warnings['max_memory_limit'] = true; |
| 881 |
} |
| 882 |
?> |
| 883 |
<div style="background: var(--dashboard-card-bg); padding: 20px; border-radius: 8px;"> |
| 884 |
<p style="color: var(--dashboard-text-secondary); margin: 0 0 20px 0;"> |
| 885 |
Configure resource limits and execution parameters for plugin operations. Adjust these settings based on your server capabilities. |
| 886 |
</p> |
| 887 |
|
| 888 |
<form id="metasync-execution-settings-form" method="post"> |
| 889 |
<?php wp_nonce_field('metasync_execution_settings_nonce', 'execution_settings_nonce'); ?> |
| 890 |
|
| 891 |
<!-- Execution & Memory Section --> |
| 892 |
<div style="background: var(--dashboard-card-bg-alt, rgba(255,255,255,0.05)); border: 1px solid var(--dashboard-border); border-radius: 8px; padding: 20px; margin-bottom: 20px;"> |
| 893 |
<h3 style="color: var(--dashboard-text-primary); margin: 0 0 16px 0; font-size: 16px; font-weight: 600;">Execution & Memory</h3> |
| 894 |
|
| 895 |
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px;"> |
| 896 |
<div> |
| 897 |
<label style="display: block; color: var(--dashboard-text-primary); margin-bottom: 8px; font-weight: 500;"> |
| 898 |
Max Execution Time: |
| 899 |
<?php Metasync::render_tooltip_icon('max_execution_time', 'How many seconds a single plugin operation (like a bulk sync or scan) is allowed to run before PHP stops it. Raise this only if you see timeout errors, and only up to what your host allows.'); ?> |
| 900 |
</label> |
| 901 |
<div style="display: flex; align-items: center; gap: 10px;"> |
| 902 |
<input type="number" |
| 903 |
id="max_execution_time" |
| 904 |
name="max_execution_time" |
| 905 |
value="<?php echo esc_attr($settings['max_execution_time']); ?>" |
| 906 |
min="1" |
| 907 |
max="300" |
| 908 |
required |
| 909 |
style="width: 100px; padding: 8px; border: 1px solid var(--dashboard-border); border-radius: 6px; background: var(--dashboard-card-bg); color: var(--dashboard-text-primary);" /> |
| 910 |
<span style="color: var(--dashboard-text-secondary);">seconds</span> |
| 911 |
</div> |
| 912 |
<p style="color: var(--dashboard-text-secondary); font-size: 12px; margin: 4px 0 0 0;"> |
| 913 |
Server Limit: <?php echo esc_html($server_limits['max_execution_time']); ?> |
| 914 |
</p> |
| 915 |
<p id="max_execution_time_warning" style="display: none; color: #f59e0b; font-size: 12px; margin: 4px 0 0 0;"> |
| 916 |
<span class="dashicons dashicons-warning" style="margin-right:4px;font-size:14px;width:14px;height:14px;color:#f59e0b;"></span> |
| 917 |
<span>Configured value exceeds server limit</span> |
| 918 |
</p> |
| 919 |
<?php if (isset($warnings['max_execution_time'])): ?> |
| 920 |
<script> |
| 921 |
jQuery(document).ready(function($) { |
| 922 |
$('#max_execution_time_warning').show(); |
| 923 |
}); |
| 924 |
</script> |
| 925 |
<?php endif; ?> |
| 926 |
</div> |
| 927 |
|
| 928 |
<div> |
| 929 |
<label style="display: block; color: var(--dashboard-text-primary); margin-bottom: 8px; font-weight: 500;"> |
| 930 |
Max Memory Limit: |
| 931 |
<?php Metasync::render_tooltip_icon('max_memory_limit', 'How much memory (RAM) the plugin is allowed to use for a single operation before PHP stops it. Raise this only if you see "out of memory" errors, and only up to what your host allows.'); ?> |
| 932 |
</label> |
| 933 |
<div style="display: flex; align-items: center; gap: 10px;"> |
| 934 |
<input type="number" |
| 935 |
id="max_memory_limit" |
| 936 |
name="max_memory_limit" |
| 937 |
value="<?php echo esc_attr($settings['max_memory_limit']); ?>" |
| 938 |
min="<?php echo esc_attr($memory_range['min']); ?>" |
| 939 |
<?php if ($memory_range['max'] !== null): ?>max="<?php echo esc_attr($memory_range['max']); ?>"<?php endif; ?> |
| 940 |
<?php if (!$server_limits['can_change_memory']): ?> |
| 941 |
readonly |
| 942 |
disabled |
| 943 |
<?php endif; ?> |
| 944 |
required |
| 945 |
style="width: 100px; padding: 8px; border: 1px solid var(--dashboard-border); border-radius: 6px; background: <?php echo $server_limits['can_change_memory'] ? 'var(--dashboard-card-bg)' : 'rgba(128, 128, 128, 0.1)'; ?>; color: <?php echo $server_limits['can_change_memory'] ? 'var(--dashboard-text-primary)' : 'var(--dashboard-text-secondary)'; ?>; cursor: <?php echo $server_limits['can_change_memory'] ? 'text' : 'not-allowed'; ?>;" /> |
| 946 |
<span style="color: var(--dashboard-text-secondary);">MB</span> |
| 947 |
</div> |
| 948 |
<p style="color: var(--dashboard-text-secondary); font-size: 12px; margin: 4px 0 0 0;"> |
| 949 |
Server Limit: <?php echo esc_html($server_limits['memory_limit']); ?> · Allowed: <?php echo $memory_range['max'] !== null ? esc_html($memory_range['min'] . '–' . $memory_range['max'] . ' MB') : esc_html($memory_range['min'] . ' MB or higher'); ?> |
| 950 |
</p> |
| 951 |
<?php if (!$server_limits['can_change_memory']): ?> |
| 952 |
<p style="color: #f59e0b; font-size: 12px; margin: 4px 0 0 0; display: flex; align-items: center; gap: 4px;"> |
| 953 |
<span class="dashicons dashicons-lock" style="font-size:14px;width:14px;height:14px;color:#f59e0b;"></span> |
| 954 |
<span>Server does not allow changing memory limit. This setting is read-only.</span> |
| 955 |
</p> |
| 956 |
<?php else: ?> |
| 957 |
<p id="max_memory_limit_warning" style="display: none; color: #f59e0b; font-size: 12px; margin: 4px 0 0 0;"> |
| 958 |
<span class="dashicons dashicons-warning" style="margin-right:4px;font-size:14px;width:14px;height:14px;color:#f59e0b;"></span> |
| 959 |
<span>Configured value exceeds server limit</span> |
| 960 |
</p> |
| 961 |
<?php if (isset($warnings['max_memory_limit'])): ?> |
| 962 |
<script> |
| 963 |
jQuery(document).ready(function($) { |
| 964 |
$('#max_memory_limit_warning').show(); |
| 965 |
}); |
| 966 |
</script> |
| 967 |
<?php endif; ?> |
| 968 |
<?php endif; ?> |
| 969 |
</div> |
| 970 |
</div> |
| 971 |
</div> |
| 972 |
|
| 973 |
<!-- Batch Processing Section --> |
| 974 |
<div style="background: var(--dashboard-card-bg-alt, rgba(255,255,255,0.05)); border: 1px solid var(--dashboard-border); border-radius: 8px; padding: 20px; margin-bottom: 20px;"> |
| 975 |
<h3 style="color: var(--dashboard-text-primary); margin: 0 0 16px 0; font-size: 16px; font-weight: 600;">Batch Processing</h3> |
| 976 |
|
| 977 |
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px;"> |
| 978 |
<div> |
| 979 |
<label style="display: block; color: var(--dashboard-text-primary); margin-bottom: 8px; font-weight: 500;"> |
| 980 |
Log Processing Batch Size: |
| 981 |
<?php Metasync::render_tooltip_icon('log_batch_size', 'How many log lines the plugin processes at once when scanning its own logs. Lower it if this causes slowdowns on smaller hosting plans; most sites can leave the default.'); ?> |
| 982 |
</label> |
| 983 |
<div style="display: flex; align-items: center; gap: 10px;"> |
| 984 |
<input type="number" |
| 985 |
id="log_batch_size" |
| 986 |
name="log_batch_size" |
| 987 |
value="<?php echo esc_attr($settings['log_batch_size']); ?>" |
| 988 |
min="100" |
| 989 |
max="5000" |
| 990 |
required |
| 991 |
style="width: 100px; padding: 8px; border: 1px solid var(--dashboard-border); border-radius: 6px; background: var(--dashboard-card-bg); color: var(--dashboard-text-primary);" /> |
| 992 |
<span style="color: var(--dashboard-text-secondary);">lines</span> |
| 993 |
</div> |
| 994 |
</div> |
| 995 |
|
| 996 |
<div> |
| 997 |
<label style="display: block; color: var(--dashboard-text-primary); margin-bottom: 8px; font-weight: 500;"> |
| 998 |
Action Scheduler Concurrent Batches: |
| 999 |
<?php Metasync::render_tooltip_icon('action_scheduler_batches', 'How many background sync jobs the plugin is allowed to run at the same time. Higher values finish syncing faster but use more server resources at once — lower this if your site slows down during syncs.'); ?> |
| 1000 |
</label> |
| 1001 |
<div style="display: flex; align-items: center; gap: 10px;"> |
| 1002 |
<input type="number" |
| 1003 |
id="action_scheduler_batches" |
| 1004 |
name="action_scheduler_batches" |
| 1005 |
value="<?php echo esc_attr($settings['action_scheduler_batches']); ?>" |
| 1006 |
min="1" |
| 1007 |
max="10" |
| 1008 |
required |
| 1009 |
style="width: 100px; padding: 8px; border: 1px solid var(--dashboard-border); border-radius: 6px; background: var(--dashboard-card-bg); color: var(--dashboard-text-primary);" /> |
| 1010 |
</div> |
| 1011 |
<p style="color: #f59e0b; font-size: 12px; margin: 4px 0 0 0; display: flex; align-items: center; gap: 4px;"> |
| 1012 |
<span class="dashicons dashicons-warning" style="font-size:14px;width:14px;height:14px;color:#f59e0b;"></span> |
| 1013 |
<span>Higher values increase server load</span> |
| 1014 |
</p> |
| 1015 |
</div> |
| 1016 |
</div> |
| 1017 |
</div> |
| 1018 |
|
| 1019 |
<!-- API Rate Limiting Section --> |
| 1020 |
<div style="background: var(--dashboard-card-bg-alt, rgba(255,255,255,0.05)); border: 1px solid var(--dashboard-border); border-radius: 8px; padding: 20px; margin-bottom: 20px;"> |
| 1021 |
<h3 style="color: var(--dashboard-text-primary); margin: 0 0 16px 0; font-size: 16px; font-weight: 600;">API Rate Limiting</h3> |
| 1022 |
|
| 1023 |
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px;"> |
| 1024 |
<div> |
| 1025 |
<label style="display: block; color: var(--dashboard-text-primary); margin-bottom: 8px; font-weight: 500;"> |
| 1026 |
<?php echo esc_html(Metasync::get_whitelabel_otto_name()); ?> API Calls Per Minute: |
| 1027 |
<?php Metasync::render_tooltip_icon('otto_rate_limit', 'The maximum number of requests per minute the plugin will send to ' . Metasync::get_whitelabel_otto_name() . '\'s servers. Lower it if you\'re seeing rate-limit errors; most sites can leave the default.'); ?> |
| 1028 |
</label> |
| 1029 |
<div style="display: flex; align-items: center; gap: 10px;"> |
| 1030 |
<input type="number" |
| 1031 |
id="otto_rate_limit" |
| 1032 |
name="otto_rate_limit" |
| 1033 |
value="<?php echo esc_attr($settings['otto_rate_limit']); ?>" |
| 1034 |
min="1" |
| 1035 |
max="60" |
| 1036 |
required |
| 1037 |
style="width: 100px; padding: 8px; border: 1px solid var(--dashboard-border); border-radius: 6px; background: var(--dashboard-card-bg); color: var(--dashboard-text-primary);" /> |
| 1038 |
</div> |
| 1039 |
</div> |
| 1040 |
|
| 1041 |
<div> |
| 1042 |
<label style="display: block; color: var(--dashboard-text-primary); margin-bottom: 8px; font-weight: 500;"> |
| 1043 |
Queue Auto-Cleanup: |
| 1044 |
<?php Metasync::render_tooltip_icon('queue_cleanup_days', 'How many days a completed background job is kept before the plugin automatically deletes its record. Lower it to save database space; raise it if you need to look back further when troubleshooting.'); ?> |
| 1045 |
</label> |
| 1046 |
<div style="display: flex; align-items: center; gap: 10px;"> |
| 1047 |
<input type="number" |
| 1048 |
id="queue_cleanup_days" |
| 1049 |
name="queue_cleanup_days" |
| 1050 |
value="<?php echo esc_attr($settings['queue_cleanup_days']); ?>" |
| 1051 |
min="7" |
| 1052 |
max="90" |
| 1053 |
required |
| 1054 |
style="width: 100px; padding: 8px; border: 1px solid var(--dashboard-border); border-radius: 6px; background: var(--dashboard-card-bg); color: var(--dashboard-text-primary);" /> |
| 1055 |
<span style="color: var(--dashboard-text-secondary);">days</span> |
| 1056 |
</div> |
| 1057 |
</div> |
| 1058 |
</div> |
| 1059 |
</div> |
| 1060 |
|
| 1061 |
<!-- Save Button --> |
| 1062 |
<div style="margin-top: 20px;"> |
| 1063 |
<button type="submit" |
| 1064 |
id="metasync-execution-settings-save-btn" |
| 1065 |
class="button button-primary" |
| 1066 |
style="padding: 10px 20px; font-size: 14px; font-weight: 500;"> |
| 1067 |
<span class="save-text">Save Settings</span> |
| 1068 |
<span class="save-spinner" style="display: none; margin-left: 8px;">⏳</span> |
| 1069 |
</button> |
| 1070 |
</div> |
| 1071 |
|
| 1072 |
<!-- Success/Error Messages --> |
| 1073 |
<div id="metasync-execution-settings-message" style="display: none; margin-top: 16px; padding: 12px; border-radius: 6px;"></div> |
| 1074 |
</form> |
| 1075 |
</div> |
| 1076 |
|
| 1077 |
<?php |
| 1078 |
// Real-time validation and AJAX save for this form live in |
| 1079 |
// admin/js/metasync-execution-settings.js (enqueued on this page). |
| 1080 |
// The previous inline duplicate was removed to avoid a double-bound |
| 1081 |
// submit handler that fired the save request twice. |
| 1082 |
?> |
| 1083 |
<?php |
| 1084 |
} |
| 1085 |
|
| 1086 |
public function get_field_tooltips() { |
| 1087 |
$plugin_name = Metasync::get_effective_plugin_name(); |
| 1088 |
$otto_name = Metasync::get_whitelabel_otto_name(); |
| 1089 |
|
| 1090 |
return array( |
| 1091 |
'searchatlas_api_key' => sprintf('Connect your WordPress site to your %1$s dashboard to retrieve your API key and %2$s UUID. This does not create a WordPress login session.', $plugin_name, $otto_name), |
| 1092 |
'apikey' => sprintf('Auto-generated authentication token used for secure API communication between your WordPress site and %s services. You can refresh this token if needed for security purposes.', $plugin_name), |
| 1093 |
'otto_pixel_uuid' => sprintf('Your unique %s tracking pixel identifier. This UUID is used to track %s modifications and analytics on your website pages.', $otto_name, $otto_name), |
| 1094 |
'otto_disable_on_loggedin' => sprintf('Disable %s modifications when you are logged in to WordPress. This allows you to see and edit the original content without %s\'s enhancements during editing sessions.', $otto_name, $otto_name), |
| 1095 |
'otto_disable_preview_button' => sprintf('Hide the %s frontend toolbar that displays the status indicator, preview button, and debug button. Enable this for a cleaner frontend experience.', $otto_name), |
| 1096 |
'otto_wp_rocket_compat' => sprintf('WP Rocket Compatibility Mode: Controls how %s interacts with WP Rocket. "Auto" (recommended) allows both to work together by avoiding DONOTCACHEPAGE constant unless necessary for Brizy pages or SG Optimizer conflicts. This ensures WP Rocket\'s JavaScript delay and optimization features continue working.', $otto_name), |
| 1097 |
'otto_sg_optimizer_compat' => sprintf('SiteGround Optimizer Compatibility Mode: Controls how %s renders on SiteGround hosting. "Auto" (recommended) uses the internal HTTP fetch, which protects themes that defer inline CSS to the footer (e.g. Divi). "Buffer Mode" renders in-process with no internal fetch — faster on sites whose pages can never be host-cached, for example when a plugin starts a PHP session on every request (BookingPress sessions are removed automatically, so this does not apply to them).', $otto_name), |
| 1098 |
'bookingpress_session_compat' => 'BookingPress Session Compatibility: Controls whether MetaSync removes the PHP session BookingPress starts on every public page, so hosts such as SiteGround can page-cache the site again.', |
| 1099 |
'otto_disable_for_bots' => sprintf('Enable bot detection to automatically skip %s processing for search engine crawlers, SEO tools, and other bots. This reduces unnecessary API calls and improves performance.', $otto_name), |
| 1100 |
'otto_bot_whitelist' => sprintf('Enter bot names or user-agent patterns (one per line) that should always be processed by %s, even when "Disable for Bots" is enabled. For example: Googlebot, Bingbot. This allows you to ensure specific search engines always see your optimized content.', $otto_name), |
| 1101 |
'otto_bot_blacklist' => sprintf('Enter bot names or user-agent patterns (one per line) that should always be blocked from %s processing, regardless of other settings. For example: BadBot, MaliciousCrawler. Use this to exclude problematic crawlers or unwanted traffic sources.', $otto_name), |
| 1102 |
'otto_bot_statistics_link' => 'View detailed bot detection statistics including total hits, API calls saved, breakdown by bot type, and unique bot entries with hit counts.', |
| 1103 |
'disable_common_robots_metabox' => sprintf('Hide the Common Robots meta box and stop %1$s emitting index/noindex and follow/nofollow directives on the front end. %1$s also stops overriding WordPress core or another SEO plugin\'s robots tag. Saved values are kept and restored if you re-enable this.', Metasync::get_effective_plugin_name('MetaSync')), |
| 1104 |
'disable_advance_robots_metabox' => sprintf('Hide the Advanced Robots meta box and stop %1$s emitting max-snippet, max-image-preview and max-video-preview directives, including the site-wide advanced defaults. Saved values are kept and restored if you re-enable this.', Metasync::get_effective_plugin_name('MetaSync')), |
| 1105 |
'disable_redirection_metabox' => 'Hide the Redirection meta box and stop redirects configured through it from running. Rules added by hand on the Redirections screen keep working. Saved redirects are kept and resume if you re-enable this.', |
| 1106 |
'disable_canonical_metabox' => sprintf('Hide the Canonical meta box and stop %1$s emitting or overriding the canonical URL on every render path. WordPress core and third-party canonicals are left in place. Saved values are kept and restored if you re-enable this.', Metasync::get_effective_plugin_name('MetaSync')), |
| 1107 |
'disable_social_opengraph_metabox' => sprintf('Hide the Social Media & Open Graph meta box and stop %1$s emitting any Open Graph, Twitter or article tags. Tags from another SEO plugin are left in place. Saved values are kept and restored if you re-enable this.', Metasync::get_effective_plugin_name('MetaSync')), |
| 1108 |
'disable_schema_markup_metabox' => sprintf('Hide the Schema Markup meta box and stop %1$s emitting any JSON-LD, including the site-wide Local SEO markup and OTTO structured data. Breadcrumb markup has its own setting. Saved values are kept and restored if you re-enable this.', Metasync::get_effective_plugin_name('MetaSync')), |
| 1109 |
'disable_seo_metabox' => 'Hide the SEO Title & Meta Description meta box from post and page edit screens. This removes the Classic editor SEO fields (the Gutenberg sidebar is unaffected).', |
| 1110 |
'open_external_links' => sprintf('Automatically add target="_blank" attribute to external links appearing in your posts, pages, and other post types when rendered by %s.', $otto_name), |
| 1111 |
'content_genius_sync_roles' => 'Select which WordPress user roles should be synchronized with Content Genius. This determines which users will have their profiles and permissions synced for content collaboration.', |
| 1112 |
'permalink_structure' => sprintf('Displays your current WordPress permalink structure. %s works best with pretty permalinks (not "Plain"). If you see a warning, visit Settings > Permalinks to change your structure.', $plugin_name), |
| 1113 |
'hide_dashboard_framework' => sprintf('Hide the main %s dashboard from the WordPress admin menu. This is useful if you want to reduce menu clutter but still keep the plugin active.', $plugin_name), |
| 1114 |
'show_admin_bar_status' => sprintf('Display the %s status indicator in the WordPress admin bar at the top of your screen. This provides quick visibility of plugin status and key metrics.', $plugin_name), |
| 1115 |
'enable_auto_updates' => sprintf('Allow WordPress to automatically update the %s plugin when new versions are released. Recommended for security patches, but you may prefer manual updates for major versions.', $plugin_name), |
| 1116 |
'import_external_data' => sprintf('Import your existing SEO settings and metadata from other popular SEO plugins like Yoast, Rank Math, or All in One SEO. This makes migration to %s seamless without losing your SEO data.', $plugin_name), |
| 1117 |
'import_seo_metadata' => sprintf('Migrate your existing SEO titles and meta descriptions from Yoast, Rank Math, or All in One SEO. This one-click import preserves your search rankings by copying your optimized meta data to %s, even if the source plugin is deactivated.', $plugin_name), |
| 1118 |
'default_page_builder' => 'Choose how Content Genius stores synced articles. "Gutenberg" works with all themes and page builders. Selecting a specific builder converts content to that builder\'s widget format, which inherits the builder\'s global typography and layout styles.', |
| 1119 |
'og_image_dimensions' => 'Adds your share image\'s width, height, and file type to the Open Graph tags so Facebook, LinkedIn, and others render your preview instantly at the right size instead of guessing. Leave on unless another plugin already outputs these.', |
| 1120 |
'article_timestamps' => 'Tells social platforms and search engines when each post was first published and last updated, so shares and rich results show accurate dates. Safe to leave enabled.', |
| 1121 |
'article_author' => 'Adds a link to the author\'s profile or archive page to each post\'s social tags, so shared articles can show author attribution. You can override the author URL per-post. Turn off if you\'d rather not expose author pages.', |
| 1122 |
'article_section' => 'Labels each post with its main topic (its primary category) in the social tags, helping platforms categorize your content. Falls back to the first category if no primary one is set.', |
| 1123 |
'article_tags' => 'Adds each of your WordPress post tags as a separate social "tag" so platforms understand the topics a post covers. Turn off if you don\'t want your tags exposed in share metadata.', |
| 1124 |
'twitter_image_alt' => 'Adds descriptive alt text to the image shown when your page is shared on X/Twitter, improving accessibility for screen-reader users. Uses your saved alt text or the image\'s own alt attribute.' |
| 1125 |
); |
| 1126 |
} |
| 1127 |
|
| 1128 |
public function get_field_section($field_id) { |
| 1129 |
$sections = $this->get_accordion_sections_config(); |
| 1130 |
|
| 1131 |
foreach ($sections as $section_key => $section_data) { |
| 1132 |
if (in_array($field_id, $section_data['fields'])) { |
| 1133 |
return $section_key; |
| 1134 |
} |
| 1135 |
} |
| 1136 |
|
| 1137 |
return null; |
| 1138 |
} |
| 1139 |
|
| 1140 |
public function render_accordion_sections($page) { |
| 1141 |
global $wp_settings_sections, $wp_settings_fields; |
| 1142 |
|
| 1143 |
if (!isset($wp_settings_fields[$page])) { |
| 1144 |
return; |
| 1145 |
} |
| 1146 |
|
| 1147 |
$sections_config = $this->get_accordion_sections_config(); |
| 1148 |
|
| 1149 |
echo '<div class="metasync-settings-accordion">'; |
| 1150 |
|
| 1151 |
uasort($sections_config, function($a, $b) { |
| 1152 |
return $a['priority'] - $b['priority']; |
| 1153 |
}); |
| 1154 |
|
| 1155 |
foreach ($sections_config as $section_key => $section_data) { |
| 1156 |
$section_id = 'metasync-section-' . $section_key; |
| 1157 |
$is_open = $section_data['default_open']; |
| 1158 |
$aria_expanded = $is_open ? 'true' : 'false'; |
| 1159 |
$content_state = $is_open ? 'open' : 'closed'; |
| 1160 |
|
| 1161 |
echo '<div class="metasync-accordion-section" data-section="' . esc_attr($section_key) . '">'; |
| 1162 |
|
| 1163 |
echo '<div class="metasync-accordion-header" role="button" tabindex="0" aria-expanded="' . $aria_expanded . '" aria-controls="' . $section_id . '">'; |
| 1164 |
echo '<div class="metasync-accordion-title">'; |
| 1165 |
echo '<span class="metasync-accordion-icon"><span class="dashicons dashicons-' . esc_attr($section_data['icon']) . '"></span></span>'; |
| 1166 |
echo '<div class="metasync-accordion-text">'; |
| 1167 |
echo '<h3>' . esc_html($section_data['title']) . '</h3>'; |
| 1168 |
echo '<p class="metasync-accordion-description">' . esc_html($section_data['description']) . '</p>'; |
| 1169 |
echo '</div>'; |
| 1170 |
echo '</div>'; |
| 1171 |
echo '<button type="button" class="metasync-accordion-toggle" aria-label="Toggle section">'; |
| 1172 |
echo '<span class="toggle-icon">▼</span>'; |
| 1173 |
echo '</button>'; |
| 1174 |
echo '</div>'; |
| 1175 |
|
| 1176 |
echo '<div class="metasync-accordion-content" id="' . $section_id . '" data-state="' . $content_state . '">'; |
| 1177 |
|
| 1178 |
if (isset($section_data['render_callback']) && is_callable($section_data['render_callback'])) { |
| 1179 |
call_user_func($section_data['render_callback']); |
| 1180 |
} elseif (isset($section_data['fields']) && is_array($section_data['fields'])) { |
| 1181 |
echo '<table class="form-table" role="presentation">'; |
| 1182 |
|
| 1183 |
$tooltips = $this->get_field_tooltips(); |
| 1184 |
|
| 1185 |
foreach ($section_data['fields'] as $field_id) { |
| 1186 |
if (isset($wp_settings_fields[$page]['metasync_settings'][$field_id])) { |
| 1187 |
$field = $wp_settings_fields[$page]['metasync_settings'][$field_id]; |
| 1188 |
|
| 1189 |
echo '<tr>'; |
| 1190 |
echo '<th scope="row">'; |
| 1191 |
if (!empty($field['title'])) { |
| 1192 |
echo '<div class="metasync-field-label-wrapper">'; |
| 1193 |
echo '<label for="' . esc_attr($field['id']) . '">' . $field['title'] . '</label>'; |
| 1194 |
|
| 1195 |
if (isset($tooltips[$field_id])) { |
| 1196 |
echo '<button type="button" class="metasync-tooltip-trigger" data-tooltip-id="' . esc_attr($field_id) . '" aria-label="More information">'; |
| 1197 |
echo '<svg class="metasync-info-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">'; |
| 1198 |
echo '<circle cx="12" cy="12" r="10"></circle>'; |
| 1199 |
echo '<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>'; |
| 1200 |
echo '<line x1="12" y1="17" x2="12.01" y2="17"></line>'; |
| 1201 |
echo '</svg>'; |
| 1202 |
echo '</button>'; |
| 1203 |
|
| 1204 |
echo '<div class="metasync-tooltip" id="tooltip-' . esc_attr($field_id) . '" role="tooltip">'; |
| 1205 |
echo '<div class="metasync-tooltip-arrow"></div>'; |
| 1206 |
echo '<div class="metasync-tooltip-content">' . esc_html($tooltips[$field_id]) . '</div>'; |
| 1207 |
echo '</div>'; |
| 1208 |
} |
| 1209 |
|
| 1210 |
echo '</div>'; |
| 1211 |
} |
| 1212 |
echo '</th>'; |
| 1213 |
echo '<td>'; |
| 1214 |
call_user_func($field['callback'], $field['args']); |
| 1215 |
echo '</td>'; |
| 1216 |
echo '</tr>'; |
| 1217 |
} |
| 1218 |
} |
| 1219 |
|
| 1220 |
echo '</table>'; |
| 1221 |
} |
| 1222 |
|
| 1223 |
echo '</div>'; |
| 1224 |
echo '</div>'; |
| 1225 |
} |
| 1226 |
|
| 1227 |
echo '</div>'; |
| 1228 |
} |
| 1229 |
|
| 1230 |
public function render_llms_txt_section() { |
| 1231 |
global $wp_settings_fields; |
| 1232 |
|
| 1233 |
$page = Metasync_Admin::$page_slug . '_general'; |
| 1234 |
$section = Metasync_Admin::SECTION_LLMS_TXT; |
| 1235 |
|
| 1236 |
if (empty($wp_settings_fields[$page][$section])) { |
| 1237 |
return; |
| 1238 |
} |
| 1239 |
|
| 1240 |
echo '<table class="form-table" role="presentation">'; |
| 1241 |
$llms_tooltips = array( |
| 1242 |
'llms_txt_enabled' => 'Publishes a file at yoursite.com/llms.txt listing your key pages so AI assistants like ChatGPT and Perplexity can find and cite your content. Safe to leave off if you don\'t want AI crawlers guided to your pages.', |
| 1243 |
'llms_full_enabled' => 'Also publishes a larger file with the full text of your posts (not just links). This exposes complete article content and can be large — keep the post limit low on smaller hosting plans.', |
| 1244 |
); |
| 1245 |
foreach ($wp_settings_fields[$page][$section] as $field) { |
| 1246 |
echo '<tr>'; |
| 1247 |
echo '<th scope="row">'; |
| 1248 |
if (!empty($field['title'])) { |
| 1249 |
echo '<div class="metasync-field-label-wrapper">'; |
| 1250 |
echo '<label for="' . esc_attr($field['id']) . '">' . $field['title'] . '</label>'; |
| 1251 |
if (isset($llms_tooltips[$field['id']])) { |
| 1252 |
Metasync::render_tooltip_icon($field['id'], $llms_tooltips[$field['id']]); |
| 1253 |
} |
| 1254 |
echo '</div>'; |
| 1255 |
} |
| 1256 |
echo '</th>'; |
| 1257 |
echo '<td>'; |
| 1258 |
call_user_func($field['callback'], $field['args']); |
| 1259 |
echo '</td>'; |
| 1260 |
echo '</tr>'; |
| 1261 |
} |
| 1262 |
echo '</table>'; |
| 1263 |
?> |
| 1264 |
<script> |
| 1265 |
(function(){ |
| 1266 |
function toggleLlms(){ |
| 1267 |
var enabled = document.getElementById('llms_txt_enabled'); |
| 1268 |
var fullEnabled = document.getElementById('llms_full_enabled'); |
| 1269 |
var deps = document.querySelectorAll('.llms-txt-dependent'); |
| 1270 |
var fullDeps = document.querySelectorAll('.llms-full-dependent'); |
| 1271 |
var i; |
| 1272 |
for(i = 0; i < deps.length; i++){ |
| 1273 |
var row = deps[i].closest('tr'); |
| 1274 |
if(row) row.style.display = enabled && enabled.checked ? '' : 'none'; |
| 1275 |
} |
| 1276 |
for(i = 0; i < fullDeps.length; i++){ |
| 1277 |
var row = fullDeps[i].closest('tr'); |
| 1278 |
if(row) row.style.display = (enabled && enabled.checked && fullEnabled && fullEnabled.checked) ? '' : 'none'; |
| 1279 |
} |
| 1280 |
} |
| 1281 |
document.addEventListener('DOMContentLoaded', function(){ |
| 1282 |
var enabled = document.getElementById('llms_txt_enabled'); |
| 1283 |
var fullEnabled = document.getElementById('llms_full_enabled'); |
| 1284 |
if(enabled) enabled.addEventListener('change', toggleLlms); |
| 1285 |
if(fullEnabled) fullEnabled.addEventListener('change', toggleLlms); |
| 1286 |
toggleLlms(); |
| 1287 |
}); |
| 1288 |
})(); |
| 1289 |
</script> |
| 1290 |
<?php |
| 1291 |
} |
| 1292 |
|
| 1293 |
// ──────────────────────────────────────────────────────────────── |
| 1294 |
// Settings field callbacks |
| 1295 |
// ──────────────────────────────────────────────────────────────── |
| 1296 |
|
| 1297 |
public function metasync_settings_genkey_callback() |
| 1298 |
{ |
| 1299 |
$current_token = Metasync::get_option('general')['apikey'] ?? ''; |
| 1300 |
|
| 1301 |
if (!empty($current_token)) { |
| 1302 |
$display_value = $current_token; |
| 1303 |
$status_message = 'Plugin Auth Token is active and ready for authentication.'; |
| 1304 |
$refresh_help = 'Click refresh to generate a new token and update the heartbeat API.'; |
| 1305 |
} else { |
| 1306 |
$display_value = 'Auto-generated when connecting to ' . esc_html(Metasync::get_effective_plugin_name()); |
| 1307 |
$status_message = 'Plugin Auth Token will be automatically generated when you click "Connect to ' . esc_html(Metasync::get_effective_plugin_name()) . '".'; |
| 1308 |
$refresh_help = 'You can also manually generate a token by clicking refresh.'; |
| 1309 |
} |
| 1310 |
|
| 1311 |
printf('<div class="metasync-auth-token-row">'); |
| 1312 |
printf( |
| 1313 |
'<input type="text" id="apikey" name="' . Metasync_Admin::option_key . '[general][apikey]" value="%s" size="40" class="regular-text" readonly="readonly" />', |
| 1314 |
esc_attr($display_value) |
| 1315 |
); |
| 1316 |
printf('<button type="button" id="refresh-plugin-auth-token" class="button button-secondary metasync-refresh-token-btn"><span class="dashicons dashicons-controls-repeat"></span> Refresh Token</button>'); |
| 1317 |
printf('</div>'); |
| 1318 |
printf('<p class="description">%s %s</p>', $status_message, $refresh_help); |
| 1319 |
} |
| 1320 |
|
| 1321 |
public function linkgraph_token_callback() |
| 1322 |
{ |
| 1323 |
printf( |
| 1324 |
'<input type="text" id="linkgraph_token" name="' . Metasync_Admin::option_key . '[general][linkgraph_token]" value="%s" size="25" readonly="readonly" />', |
| 1325 |
isset(Metasync::get_option('general')['linkgraph_token']) ? esc_attr(Metasync::get_option('general')['linkgraph_token']) : '' |
| 1326 |
); |
| 1327 |
|
| 1328 |
printf( |
| 1329 |
'<input type="text" id="linkgraph_customer_id" name="' . Metasync_Admin::option_key . '[general][linkgraph_customer_id]" value="%s" size="25" readonly="readonly" />', |
| 1330 |
isset(Metasync::get_option('general')['linkgraph_customer_id']) ? esc_attr(Metasync::get_option('general')['linkgraph_customer_id']) : '' |
| 1331 |
); |
| 1332 |
|
| 1333 |
?> |
| 1334 |
<button type="button" class="button button-primary" id="lgloginbtn">Fetch Token</button> |
| 1335 |
<input type="text" id="lgusername" class="input lguser hidden" placeholder="username" /> |
| 1336 |
<input type="text" id="lgpassword" class="input lguser hidden" placeholder="password" /> |
| 1337 |
<p id="lgerror" class="notice notice-error hidden" style="display: none;"></p> |
| 1338 |
<?php |
| 1339 |
} |
| 1340 |
|
| 1341 |
|
| 1342 |
public function time_elapsed_string($datetime, $full = false) |
| 1343 |
{ |
| 1344 |
if(empty($datetime)){ |
| 1345 |
return ""; |
| 1346 |
} |
| 1347 |
# Stored timestamps use current_time('mysql') which is the WP site's |
| 1348 |
# local timezone (no offset marker on the string). Constructing |
| 1349 |
# DateTime without a timezone would default to UTC and produce a |
| 1350 |
# bogus diff equal to the WP timezone offset (QA: "4 hours ago" |
| 1351 |
# after a 4-minute sync on non-UTC sites). |
| 1352 |
$wp_tz = function_exists('wp_timezone') ? wp_timezone() : new DateTimeZone('UTC'); |
| 1353 |
$now = new DateTime('now', $wp_tz); |
| 1354 |
$ago = new DateTime($datetime, $wp_tz); |
| 1355 |
|
| 1356 |
$diff = $now->diff($ago); |
| 1357 |
|
| 1358 |
$string = [ |
| 1359 |
'y' => 'year', |
| 1360 |
'm' => 'month', |
| 1361 |
'w' => 'week', |
| 1362 |
'd' => 'day', |
| 1363 |
'h' => 'hour', |
| 1364 |
'i' => 'minute', |
| 1365 |
's' => 'second', |
| 1366 |
]; |
| 1367 |
|
| 1368 |
foreach ($string as $k => &$v) { |
| 1369 |
if (isset($diff->$k) && $diff->$k) { |
| 1370 |
$v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : ''); |
| 1371 |
} else { |
| 1372 |
unset($string[$k]); |
| 1373 |
} |
| 1374 |
} |
| 1375 |
if (!$full) $string = array_slice($string, 0, 1); |
| 1376 |
return $string ? implode(', ', $string) . ' ago' : 'just now'; |
| 1377 |
} |
| 1378 |
|
| 1379 |
public function searchatlas_api_key_callback() |
| 1380 |
{ |
| 1381 |
// Whether a key is stored at rest (encrypted or legacy plaintext). |
| 1382 |
$stored_api_key = Metasync::get_option('general')['searchatlas_api_key'] ?? ''; |
| 1383 |
$has_api_key = ($stored_api_key !== ''); |
| 1384 |
|
| 1385 |
// Decrypted key for masking only. Never rendered in cleartext. |
| 1386 |
// false => an encrypted key exists but cannot be decrypted (salts changed). |
| 1387 |
$decrypted_api_key = Metasync::get_searchatlas_api_key(); |
| 1388 |
$decrypt_failed = ($decrypted_api_key === false); |
| 1389 |
|
| 1390 |
// Build a masked representation: bullets + last 4 chars. The cleartext |
| 1391 |
// key is never emitted into the page. |
| 1392 |
$masked_api_key = ''; |
| 1393 |
if (!$decrypt_failed && $decrypted_api_key !== '') { |
| 1394 |
$masked_api_key = str_repeat('*', 8) . substr($decrypted_api_key, -4); |
| 1395 |
} |
| 1396 |
|
| 1397 |
$otto_uuid = isset(Metasync::get_option('general')['otto_pixel_uuid']) ? Metasync::get_option('general')['otto_pixel_uuid'] : ''; |
| 1398 |
|
| 1399 |
$has_otto_uuid = !empty($otto_uuid); |
| 1400 |
|
| 1401 |
$is_fully_connected = $this->admin_instance->is_heartbeat_connected(); |
| 1402 |
|
| 1403 |
printf('<div class="metasync-sa-connect-container">'); |
| 1404 |
|
| 1405 |
printf('<div class="metasync-sa-connect-title">'); |
| 1406 |
printf('One-Click Authentication'); |
| 1407 |
printf('</div>'); |
| 1408 |
|
| 1409 |
printf('<div class="metasync-sa-connect-description">'); |
| 1410 |
if ($is_fully_connected) { |
| 1411 |
printf('Your %s account is fully synced with active heartbeat API. You can re-authenticate to refresh your connection or connect a different account.', esc_html(Metasync::get_effective_plugin_name())); |
| 1412 |
} elseif ($has_api_key && !$has_otto_uuid) { |
| 1413 |
printf('Your %s API key is configured, but %s UUID is missing. Please re-authenticate to complete the setup.', esc_html(Metasync::get_effective_plugin_name()), esc_html(Metasync::get_whitelabel_otto_name())); |
| 1414 |
} else { |
| 1415 |
printf('Connect your %s account with one click. This will automatically configure your API key and %s UUID below, enabling all plugin features.', esc_html(Metasync::get_effective_plugin_name()), esc_html(Metasync::get_whitelabel_otto_name())); |
| 1416 |
} |
| 1417 |
printf('</div>'); |
| 1418 |
|
| 1419 |
?> |
| 1420 |
<div class="metasync-mcp-consent" style="margin-top: 15px; padding: 12px 15px; background: #f0f9ff; border-left: 3px solid #0ea5e9; border-radius: 4px;"> |
| 1421 |
<p style="margin: 0; color: #334155; font-size: 13px; line-height: 1.6;"> |
| 1422 |
<strong>AI-Powered SEO Automation:</strong> By authenticating, you authorize <strong><?php echo esc_html(Metasync::get_effective_plugin_name()); ?> Brain</strong> (our AI assistant) to access your WordPress admin capabilities through the Model Context Protocol (MCP). This enables intelligent automation for SEO optimizations, content enhancements, and performance improvements. |
| 1423 |
</p> |
| 1424 |
</div> |
| 1425 |
<?php |
| 1426 |
|
| 1427 |
printf('<div class="metasync-sa-connect-buttons">'); |
| 1428 |
|
| 1429 |
printf('<button type="button" id="connect-searchatlas-btn" class="metasync-sa-connect-btn">'); |
| 1430 |
if ($is_fully_connected) { |
| 1431 |
printf('Re-authenticate with %s', esc_html(Metasync::get_effective_plugin_name())); |
| 1432 |
} elseif ($has_api_key && !$has_otto_uuid) { |
| 1433 |
printf('Complete Authentication Setup'); |
| 1434 |
} else { |
| 1435 |
printf('Connect to %s', esc_html(Metasync::get_effective_plugin_name())); |
| 1436 |
} |
| 1437 |
printf('</button>'); |
| 1438 |
|
| 1439 |
if ($has_api_key) { |
| 1440 |
printf('<button type="button" id="reset-searchatlas-auth" class="metasync-sa-reset-btn" style="margin-left: 10px;">'); |
| 1441 |
printf('Disconnect Account'); |
| 1442 |
printf('</button>'); |
| 1443 |
} |
| 1444 |
|
| 1445 |
printf('</div>'); |
| 1446 |
|
| 1447 |
printf('<div style="margin-top: 15px;">'); |
| 1448 |
printf('<details style="margin-top: 10px;">'); |
| 1449 |
printf('<summary style="cursor: pointer; color: #666; font-size: 13px;">Authentication Tips</summary>'); |
| 1450 |
printf('<div style="padding: 10px 0; color: #666; font-size: 13px; line-height: 1.5;">'); |
| 1451 |
printf('• Make sure you have a %s account before connecting<br/>', esc_html(Metasync::get_effective_plugin_name())); |
| 1452 |
printf('• The authentication window will open in a popup - please allow popups<br/>'); |
| 1453 |
printf('• The process typically takes 15-30 seconds to complete<br/>'); |
| 1454 |
printf('• Your API key will be stored securely and shown masked below<br/>'); |
| 1455 |
printf('• If you encounter issues, try disabling ad blockers temporarily<br/>'); |
| 1456 |
printf('• Contact <a href="mailto:%s">%s</a> if you need assistance', Metasync::SUPPORT_EMAIL, Metasync::SUPPORT_EMAIL); |
| 1457 |
printf('</div>'); |
| 1458 |
printf('</details>'); |
| 1459 |
printf('</div>'); |
| 1460 |
|
| 1461 |
printf('</div>'); |
| 1462 |
|
| 1463 |
printf('<div style="margin-top: 20px;">'); |
| 1464 |
printf('<label for="searchatlas-api-key" style="font-weight: 600; display: block; margin-bottom: 8px;">'); |
| 1465 |
printf('%s API Key', esc_html(Metasync::get_effective_plugin_name())); |
| 1466 |
if ($is_fully_connected) { |
| 1467 |
printf('<span style="color: #46b450; margin-left: 10px; font-weight: normal;">✓ Synced</span>'); |
| 1468 |
} elseif ($has_api_key && !$has_otto_uuid) { |
| 1469 |
printf('<span style="color: #ff8c00; margin-left: 10px; font-weight: normal;">Partial Connection (Missing %s UUID)</span>', esc_html(Metasync::get_whitelabel_otto_name())); |
| 1470 |
} |
| 1471 |
printf('</label>'); |
| 1472 |
|
| 1473 |
// Read-only masked display. The API key is managed via one-click |
| 1474 |
// authentication only; the cleartext key is never rendered into the |
| 1475 |
// page HTML (View Source / DOM). No form input is emitted, so saving |
| 1476 |
// settings cannot overwrite or expose the stored key. |
| 1477 |
$display_has_key = (!$decrypt_failed && $masked_api_key !== '') ? '1' : '0'; |
| 1478 |
printf( |
| 1479 |
'<div id="searchatlas-api-key-display" class="metasync-api-key-masked" data-has-key="%s" style="display:inline-flex; align-items:center; min-width:320px; height:42px; padding:0 14px; font-family:monospace; font-size:15px; letter-spacing:4px; background:var(--dashboard-input-bg, #22272e); border:1px solid var(--dashboard-border, #3a424d); border-radius:6px; color:var(--dashboard-text-primary, #e6e8eb); cursor:default; user-select:none;">', |
| 1480 |
esc_attr($display_has_key) |
| 1481 |
); |
| 1482 |
if ($decrypt_failed) { |
| 1483 |
printf('<em style="color:#b32d2e; font-family:sans-serif; letter-spacing:normal; font-style:normal; font-size:14px;">Stored API key could not be read. Please re-authenticate above.</em>'); |
| 1484 |
} elseif ($masked_api_key !== '') { |
| 1485 |
echo esc_html($masked_api_key); |
| 1486 |
} else { |
| 1487 |
printf('<em style="color:#646970;">Not configured</em>'); |
| 1488 |
} |
| 1489 |
printf('</div>'); |
| 1490 |
|
| 1491 |
printf('<p class="description" style="margin-top: 8px;">'); |
| 1492 |
if ($decrypt_failed) { |
| 1493 |
printf('Your stored API key could not be decrypted (your site security keys may have changed). Please re-authenticate above to reconnect.'); |
| 1494 |
} elseif ($is_fully_connected) { |
| 1495 |
printf('Your %s API key is stored securely and used for communication with the platform. Use the authentication button above to refresh or change accounts.', esc_html(Metasync::get_effective_plugin_name())); |
| 1496 |
} elseif ($has_api_key && !$has_otto_uuid) { |
| 1497 |
printf('Your API key is configured but %s UUID is missing. Re-authenticate above to complete the setup and enable dashboard access.', esc_html(Metasync::get_whitelabel_otto_name())); |
| 1498 |
} else { |
| 1499 |
printf('Use the authentication button above to securely connect your account. Your API key is stored encrypted and shown masked here.'); |
| 1500 |
} |
| 1501 |
printf('</p>'); |
| 1502 |
|
| 1503 |
printf('</div>'); |
| 1504 |
|
| 1505 |
if( isset(Metasync::get_option('general')['searchatlas_api_key'])&&Metasync::get_option('general')['searchatlas_api_key']!=''){ |
| 1506 |
$timestamp = @Metasync::get_option('general')['send_auth_token_timestamp']; |
| 1507 |
printf( |
| 1508 |
'<p id="sendAuthTokenTimestamp" class="descriptionValue">%s (%s)</p>', |
| 1509 |
esc_attr($timestamp), |
| 1510 |
$this->time_elapsed_string($timestamp) |
| 1511 |
); |
| 1512 |
|
| 1513 |
|
| 1514 |
} |
| 1515 |
} |
| 1516 |
|
| 1517 |
public function bing_site_verification_callback() |
| 1518 |
{ |
| 1519 |
printf( |
| 1520 |
'<input type="text" id="bing_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][bing_site_verification]" value="%s" size="50" />', |
| 1521 |
isset(Metasync::get_option('searchengines')['bing_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['bing_site_verification']) : '' |
| 1522 |
); |
| 1523 |
|
| 1524 |
printf(' <br> <span class="description"> Enter Bing Webmaster Tools verification code: </span> '); |
| 1525 |
printf(' <a href="https://www.bing.com/webmasters/about" target="_blank">Get from here</a> <br> '); |
| 1526 |
} |
| 1527 |
|
| 1528 |
public function yandex_site_verification_callback() |
| 1529 |
{ |
| 1530 |
printf( |
| 1531 |
'<input type="text" id="yandex_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][yandex_site_verification]" value="%s" size="50" />', |
| 1532 |
isset(Metasync::get_option('searchengines')['yandex_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['yandex_site_verification']) : '' |
| 1533 |
); |
| 1534 |
|
| 1535 |
printf(' <br> <span class="description"> Enter Yandex verification code: </span>'); |
| 1536 |
printf(' <a href="https://passport.yandex.com/auth" target="_blank">Get from here</a> <br> '); |
| 1537 |
} |
| 1538 |
|
| 1539 |
public function google_site_verification_callback() |
| 1540 |
{ |
| 1541 |
printf( |
| 1542 |
'<input type="text" id="google_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][google_site_verification]" value="%s" size="50" />', |
| 1543 |
isset(Metasync::get_option('searchengines')['google_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['google_site_verification']) : '' |
| 1544 |
); |
| 1545 |
|
| 1546 |
printf(' <br> <span class="description"> Enter Google Search Console verification code: </span>'); |
| 1547 |
printf(' <a href="https://www.google.com/webmasters/verification" target="_blank">Get from here</a> <br> '); |
| 1548 |
} |
| 1549 |
|
| 1550 |
public function pinterest_site_verification_callback() |
| 1551 |
{ |
| 1552 |
printf( |
| 1553 |
'<input type="text" id="pinterest_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][pinterest_site_verification]" value="%s" size="50" />', |
| 1554 |
isset(Metasync::get_option('searchengines')['pinterest_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['pinterest_site_verification']) : '' |
| 1555 |
); |
| 1556 |
|
| 1557 |
printf(' <br> <span class="description"> Enter Pinterest verification code: </span>'); |
| 1558 |
printf(' <a href="https://in.pinterest.com/" target="_blank">Get from here</a> <br> '); |
| 1559 |
} |
| 1560 |
|
| 1561 |
public function local_seo_person_organization_callback() |
| 1562 |
{ |
| 1563 |
$person_organization = Metasync::get_option('localseo')['local_seo_person_organization'] ?? ''; |
| 1564 |
$valid_person_organizations = array('Person', 'Organization'); |
| 1565 |
?> |
| 1566 |
<div class="metasync-type-select-wrap"> |
| 1567 |
<select id="local_seo_person_organization" name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][local_seo_person_organization]') ?>"> |
| 1568 |
<?php |
| 1569 |
if (!in_array($person_organization, $valid_person_organizations, true)) { |
| 1570 |
printf('<option value="0" selected="selected">Select Type</option>'); |
| 1571 |
} |
| 1572 |
printf('<option value="Person" %s >Person</option>', selected('Person', esc_attr($person_organization))); |
| 1573 |
printf('<option value="Organization" %s >Organization</option>', selected('Organization', esc_attr($person_organization))); |
| 1574 |
?> |
| 1575 |
</select> |
| 1576 |
</div> |
| 1577 |
<?php |
| 1578 |
printf(' <br> <span class="description"> Choose whether the site represents a person or an organization. </span>'); |
| 1579 |
} |
| 1580 |
|
| 1581 |
public function local_seo_name_callback() |
| 1582 |
{ |
| 1583 |
printf( |
| 1584 |
'<input type="text" id="local_seo_name" name="' . Metasync_Admin::option_key . '[localseo][local_seo_name]" value="%s" size="50" />', |
| 1585 |
isset(Metasync::get_option('localseo')['local_seo_name']) ? esc_attr(Metasync::get_option('localseo')['local_seo_name']) : get_bloginfo() |
| 1586 |
); |
| 1587 |
|
| 1588 |
printf(' <br> <span class="description"> Your name or company name </span>'); |
| 1589 |
} |
| 1590 |
|
| 1591 |
public function local_seo_logo_callback() |
| 1592 |
{ |
| 1593 |
$local_seo_logo = Metasync::get_option('localseo')['local_seo_logo'] ?? ''; |
| 1594 |
|
| 1595 |
// Rows saved by the legacy sanitizer hold the corrupted form of |
| 1596 |
// an attachment ID ("http://45589"). Repair it before anything reads |
| 1597 |
// the value — both so the preview resolves and so the hidden input |
| 1598 |
// re-submits the recovered ID instead of the corrupted string, which |
| 1599 |
// would otherwise survive every subsequent Save Settings. |
| 1600 |
$local_seo_logo = metasync_repair_scheme_prefixed_media_id($local_seo_logo); |
| 1601 |
|
| 1602 |
printf( |
| 1603 |
'<input type="hidden" id="local_seo_logo" name="' . Metasync_Admin::option_key . '[localseo][local_seo_logo]" value="%s" size="50" />', |
| 1604 |
esc_attr((string) $local_seo_logo) |
| 1605 |
); |
| 1606 |
|
| 1607 |
printf(' <br> <input class="button-secondary no-loading" type="button" id="logo_upload_button" value="Add or Upload File">'); |
| 1608 |
|
| 1609 |
printf(' <br><br> <span class="description bold"> Min Size: 160Χ90px, Max Size: 1920X1080px. </span> <br> <span class="description"> A squared image is preferred by the search engines. </span> <br><br> '); |
| 1610 |
|
| 1611 |
printf('<span class="metasync-logo-preview-wrap">'); |
| 1612 |
|
| 1613 |
// The stored value may be an attachment ID or a raw URL, so resolve |
| 1614 |
// both the way the front-end schema output already does. Anything that |
| 1615 |
// resolves to nothing — a deleted attachment, a legacy value mangled by |
| 1616 |
// an earlier save — must render no preview at all, rather than an empty |
| 1617 |
// src next to an orphaned remove button. |
| 1618 |
$logo_url = $this->resolve_media_url($local_seo_logo, 'medium'); |
| 1619 |
|
| 1620 |
printf( |
| 1621 |
'<img src="%s" id="local_seo_business_logo" width="300"%s>', |
| 1622 |
esc_url($logo_url), |
| 1623 |
$logo_url ? '' : ' style="display:none"' |
| 1624 |
); |
| 1625 |
|
| 1626 |
$button_type = $logo_url ? 'button' : 'hidden'; |
| 1627 |
printf('<input type="%s" class="button-secondary no-loading metasync-logo-remove-btn" id="local_seo_logo_close_btn" value="X">', $button_type); |
| 1628 |
printf('</span>'); |
| 1629 |
} |
| 1630 |
|
| 1631 |
/** |
| 1632 |
* Resolve a stored media setting to a displayable URL. |
| 1633 |
* |
| 1634 |
* These settings accept either a media-library attachment ID or a raw URL; |
| 1635 |
* returns an empty string when the value resolves to neither, so callers |
| 1636 |
* can distinguish "no image" from "image we could not resolve". |
| 1637 |
* |
| 1638 |
* @param mixed $value Attachment ID or URL as stored in the options. |
| 1639 |
* @param string $size Registered image size to request for an attachment. |
| 1640 |
* @return string Resolved URL, or '' when there is nothing to show. |
| 1641 |
*/ |
| 1642 |
private function resolve_media_url($value, $size = 'medium') |
| 1643 |
{ |
| 1644 |
if (empty($value)) { |
| 1645 |
return ''; |
| 1646 |
} |
| 1647 |
|
| 1648 |
// A row saved by the legacy sanitizer stores a corrupted attachment ID ("http://45589"), |
| 1649 |
// which is neither resolvable as an ID nor a usable image URL. |
| 1650 |
$value = metasync_repair_scheme_prefixed_media_id($value); |
| 1651 |
|
| 1652 |
if (empty($value)) { |
| 1653 |
return ''; |
| 1654 |
} |
| 1655 |
|
| 1656 |
if (is_numeric($value)) { |
| 1657 |
$url = wp_get_attachment_image_url((int) $value, $size); |
| 1658 |
return $url ? $url : ''; |
| 1659 |
} |
| 1660 |
|
| 1661 |
return (string) $value; |
| 1662 |
} |
| 1663 |
|
| 1664 |
public function local_seo_url_callback() |
| 1665 |
{ |
| 1666 |
printf( |
| 1667 |
'<input type="text" id="local_seo_url" name="' . Metasync_Admin::option_key . '[localseo][local_seo_url]" value="%s" size="50" />', |
| 1668 |
isset(Metasync::get_option('localseo')['local_seo_url']) ? esc_attr(Metasync::get_option('localseo')['local_seo_url']) : home_url() |
| 1669 |
); |
| 1670 |
|
| 1671 |
printf(' <br> <span class="description"> URL of the item. </span>'); |
| 1672 |
} |
| 1673 |
|
| 1674 |
public function local_seo_email_callback() |
| 1675 |
{ |
| 1676 |
printf( |
| 1677 |
'<input type="text" id="local_seo_email" name="' . Metasync_Admin::option_key . '[localseo][local_seo_email]" value="%s" size="50" />', |
| 1678 |
isset(Metasync::get_option('localseo')['local_seo_email']) ? esc_attr(Metasync::get_option('localseo')['local_seo_email']) : '' |
| 1679 |
); |
| 1680 |
|
| 1681 |
printf(' <br> <span class="description"> Search engines display your email address. </span>'); |
| 1682 |
} |
| 1683 |
|
| 1684 |
public function local_seo_phone_callback() |
| 1685 |
{ |
| 1686 |
printf( |
| 1687 |
'<input type="text" id="local_seo_phone" name="' . Metasync_Admin::option_key . '[localseo][local_seo_phone]" value="%s" size="50" />', |
| 1688 |
isset(Metasync::get_option('localseo')['local_seo_phone']) ? esc_attr(Metasync::get_option('localseo')['local_seo_phone']) : '' |
| 1689 |
); |
| 1690 |
|
| 1691 |
printf(' <br> <span class="description"> Search engines may prominently display your contact phone number for mobile users. </span>'); |
| 1692 |
} |
| 1693 |
|
| 1694 |
public function local_seo_address_callback() |
| 1695 |
{ |
| 1696 |
printf( |
| 1697 |
'<input type="text" id="local_seo_address_street" name="' . Metasync_Admin::option_key . '[localseo][address][street]" value="%s" size="50" placeholder="Street Address"/> <br>', |
| 1698 |
isset(Metasync::get_option('localseo')['address']['street']) ? esc_attr(Metasync::get_option('localseo')['address']['street']) : '' |
| 1699 |
); |
| 1700 |
|
| 1701 |
printf( |
| 1702 |
'<input type="text" id="local_seo_address_locality" name="' . Metasync_Admin::option_key . '[localseo][address][locality]" value="%s" size="50" placeholder="Locality"/> <br>', |
| 1703 |
isset(Metasync::get_option('localseo')['address']['locality']) ? esc_attr(Metasync::get_option('localseo')['address']['locality']) : '' |
| 1704 |
); |
| 1705 |
|
| 1706 |
printf( |
| 1707 |
'<input type="text" id="local_seo_address_region" name="' . Metasync_Admin::option_key . '[localseo][address][region]" value="%s" size="50" placeholder="Region"/> <br>', |
| 1708 |
isset(Metasync::get_option('localseo')['address']['region']) ? esc_attr(Metasync::get_option('localseo')['address']['region']) : '' |
| 1709 |
); |
| 1710 |
|
| 1711 |
printf( |
| 1712 |
'<input type="text" id="local_seo_address_postalcode" name="' . Metasync_Admin::option_key . '[localseo][address][postalcode]" value="%s" size="50" placeholder="Postal Code"/> <br>', |
| 1713 |
isset(Metasync::get_option('localseo')['address']['postalcode']) ? esc_attr(Metasync::get_option('localseo')['address']['postalcode']) : '' |
| 1714 |
); |
| 1715 |
|
| 1716 |
printf( |
| 1717 |
'<input type="text" id="local_seo_address_country" name="' . Metasync_Admin::option_key . '[localseo][address][country]" value="%s" size="50" placeholder="Country"/> <br>', |
| 1718 |
isset(Metasync::get_option('localseo')['address']['country']) ? esc_attr(Metasync::get_option('localseo')['address']['country']) : '' |
| 1719 |
); |
| 1720 |
} |
| 1721 |
|
| 1722 |
public function local_seo_business_type_callback() |
| 1723 |
{ |
| 1724 |
$types = self::get_business_types(); |
| 1725 |
sort($types); |
| 1726 |
|
| 1727 |
$business_type = Metasync::get_option('localseo')['local_seo_business_type'] ?? ''; |
| 1728 |
|
| 1729 |
?> |
| 1730 |
<select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][local_seo_business_type]') ?>"> |
| 1731 |
<option value='0'>Select Business Type</option> |
| 1732 |
<?php |
| 1733 |
foreach ($types as $type) { |
| 1734 |
printf('<option value="%s" %s >%s</option>', $type, selected($type, esc_attr($business_type)), $type); |
| 1735 |
} |
| 1736 |
?> |
| 1737 |
</select> |
| 1738 |
<?php |
| 1739 |
} |
| 1740 |
|
| 1741 |
public function local_seo_opening_hours_callback() |
| 1742 |
{ |
| 1743 |
$days_name = ['Monday', 'Tuseday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; |
| 1744 |
$days = isset(Metasync::get_option('localseo')['days']) ? Metasync::get_option('localseo')['days'] : ''; |
| 1745 |
$times = isset(Metasync::get_option('localseo')['times']) ? Metasync::get_option('localseo')['times'] : ''; |
| 1746 |
|
| 1747 |
?> |
| 1748 |
<ul id="daysTime"> |
| 1749 |
<?php |
| 1750 |
$opening_days = []; |
| 1751 |
if ($days && $times) { |
| 1752 |
$opening_days = array_combine($days, $times); |
| 1753 |
} |
| 1754 |
foreach ($opening_days as $day_name => $day_time) { |
| 1755 |
?> |
| 1756 |
<li> |
| 1757 |
<select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][days][]') ?>"> |
| 1758 |
<?php |
| 1759 |
foreach ($days_name as $name) { |
| 1760 |
printf('<option value="%s" %s >%s</option>', $name, selected(esc_attr($name), esc_attr($day_name)), esc_attr($name)); |
| 1761 |
} |
| 1762 |
?> |
| 1763 |
</select> |
| 1764 |
<input type="text" name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][times][]') ?>" value="<?php echo esc_attr($day_time) ?>"> |
| 1765 |
<button id="timeDelete">Delete</button> |
| 1766 |
</li> |
| 1767 |
<?php } ?> |
| 1768 |
<?php if (empty($opening_days)) { ?> |
| 1769 |
<li> |
| 1770 |
<select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][days][]') ?>"> |
| 1771 |
<?php |
| 1772 |
foreach ($days_name as $name) { |
| 1773 |
printf('<option value="%s" >%s</option>', esc_attr($name), esc_attr($name)); |
| 1774 |
} |
| 1775 |
?> |
| 1776 |
</select> |
| 1777 |
<input type="text" name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][times][]') ?>" value=""> |
| 1778 |
<button id="timeDelete">Delete</button> |
| 1779 |
</li> |
| 1780 |
<?php } ?> |
| 1781 |
</ul> |
| 1782 |
<?php |
| 1783 |
|
| 1784 |
printf(' <input type="hidden" id="days_time_count" value="%s"/>', count($opening_days)); |
| 1785 |
printf(' <input class="button-secondary" type="button" id="addNewTime" value="Add Time">'); |
| 1786 |
printf(' <br> <span class="description"> Select opening hours. You can add multiple sets if you have different opening or closing hours on some days or if you have a mid-day break. Times are specified using 24:00 time. </span>'); |
| 1787 |
} |
| 1788 |
|
| 1789 |
public function local_seo_phone_numbers_callback() |
| 1790 |
{ |
| 1791 |
$number_types = ['Customer Service', 'Technical Support', 'Billing Support', 'Bill Payment', 'Sales', 'Reservations', 'Credit Card Support', 'Emergency', 'Baggage Tracking', 'Roadside Assistance', 'Package Tracking']; |
| 1792 |
$types = isset(Metasync::get_option('localseo')['phonetype']) ? Metasync::get_option('localseo')['phonetype'] : ''; |
| 1793 |
$numbers = isset(Metasync::get_option('localseo')['phonenumber']) ? Metasync::get_option('localseo')['phonenumber'] : ''; |
| 1794 |
|
| 1795 |
?> |
| 1796 |
|
| 1797 |
<ul id="phone-numbers"> |
| 1798 |
<?php |
| 1799 |
$phone_numbers = []; |
| 1800 |
if ($types && $numbers) { |
| 1801 |
$phone_numbers = array_combine($types, $numbers); |
| 1802 |
} |
| 1803 |
foreach ($phone_numbers as $phone_type => $phone_number) { |
| 1804 |
?> |
| 1805 |
<li> |
| 1806 |
<select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][phonetype][]') ?>"> |
| 1807 |
<?php |
| 1808 |
foreach ($number_types as $type) { |
| 1809 |
printf('<option value="%s" %s >%s</option>', esc_attr($type), selected(esc_attr($type), esc_attr($phone_type)), esc_attr($type)); |
| 1810 |
} |
| 1811 |
?> |
| 1812 |
</select> |
| 1813 |
<input type="text" name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][phonenumber][]') ?>" value="<?php echo esc_attr($phone_number) ?>"> |
| 1814 |
<button id="number-delete">Delete</button> |
| 1815 |
</li> |
| 1816 |
<?php } ?> |
| 1817 |
<?php if (empty($phone_numbers)) { ?> |
| 1818 |
<li> |
| 1819 |
<select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][phonetype][]') ?>"> |
| 1820 |
<?php |
| 1821 |
foreach ($number_types as $type) { |
| 1822 |
printf('<option value="%s" >%s</option>', esc_attr($type), esc_attr($type)); |
| 1823 |
} |
| 1824 |
?> |
| 1825 |
</select> |
| 1826 |
<input type="text" name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][phonenumber][]') ?>" value=""> |
| 1827 |
<button id="number-delete">Delete</button> |
| 1828 |
</li> |
| 1829 |
<?php } ?> |
| 1830 |
</ul> |
| 1831 |
<?php |
| 1832 |
|
| 1833 |
printf(' <input type="hidden" id="phone_number_count" value="%s"/>', count($phone_numbers)); |
| 1834 |
printf(' <input class="button-secondary" type="button" id="addNewNumber" value="Add Number">'); |
| 1835 |
printf(' <br> <span class="description"> Search engines may prominently display your contact phone number for mobile users. </span>'); |
| 1836 |
} |
| 1837 |
|
| 1838 |
public function local_seo_price_range_callback() |
| 1839 |
{ |
| 1840 |
printf( |
| 1841 |
'<input type="text" id="local_seo_price_range" name="' . Metasync_Admin::option_key . '[localseo][local_seo_price_range]" value="%s" size="50" />', |
| 1842 |
isset(Metasync::get_option('localseo')['local_seo_price_range']) ? esc_attr(Metasync::get_option('localseo')['local_seo_price_range']) : '' |
| 1843 |
); |
| 1844 |
printf(' <br> <span class="description"> The price range of the business, for example $$$. </span>'); |
| 1845 |
} |
| 1846 |
|
| 1847 |
public function local_seo_about_page_callback() |
| 1848 |
{ |
| 1849 |
?> |
| 1850 |
<select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][local_seo_about_page]') ?>"> |
| 1851 |
<option value='0'>Select About Page</option> |
| 1852 |
<?php |
| 1853 |
$about_page = Metasync::get_option('localseo')['local_seo_about_page'] ?? ''; |
| 1854 |
$pages = get_pages(); |
| 1855 |
foreach ($pages as $page) { |
| 1856 |
printf('<option value="%s" %s >%s</option>', $page->ID, selected($page->ID, esc_attr($about_page)), $page->post_title); |
| 1857 |
} |
| 1858 |
?> |
| 1859 |
</select> |
| 1860 |
<?php |
| 1861 |
printf(' <br> <span class="description"> Search engines tag your about us page. </span>'); |
| 1862 |
} |
| 1863 |
|
| 1864 |
public function local_seo_contact_page_callback() |
| 1865 |
{ |
| 1866 |
?> |
| 1867 |
<select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][local_seo_contact_page]') ?>"> |
| 1868 |
<option value='0'>Select Contact Page</option> |
| 1869 |
<?php |
| 1870 |
$contact_page = Metasync::get_option('localseo')['local_seo_contact_page'] ?? ''; |
| 1871 |
$pages = get_pages(); |
| 1872 |
foreach ($pages as $page) { |
| 1873 |
printf('<option value="%s" %s >%s</option>', $page->ID, selected($page->ID, esc_attr($contact_page)), $page->post_title); |
| 1874 |
} |
| 1875 |
?> |
| 1876 |
</select> |
| 1877 |
<?php |
| 1878 |
printf(' <br> <span class="description"> Search engines tag your contact page. </span>'); |
| 1879 |
} |
| 1880 |
|
| 1881 |
public function local_seo_map_key_callback() |
| 1882 |
{ |
| 1883 |
printf( |
| 1884 |
'<input type="text" id="local_seo_map_key" name="' . Metasync_Admin::option_key . '[localseo][local_seo_map_key]" value="%s" size="50" />', |
| 1885 |
isset(Metasync::get_option('localseo')['local_seo_map_key']) ? esc_attr(Metasync::get_option('localseo')['local_seo_map_key']) : '' |
| 1886 |
); |
| 1887 |
|
| 1888 |
printf(' <br> <span class="description"> An API Key is required to display embedded Google Maps on your site. </span>'); |
| 1889 |
} |
| 1890 |
|
| 1891 |
public function local_seo_geo_coordinates_callback() |
| 1892 |
{ |
| 1893 |
printf( |
| 1894 |
'<input type="text" id="local_seo_geo_coordinates" name="' . Metasync_Admin::option_key . '[localseo][local_seo_geo_coordinates]" value="%s" size="50" />', |
| 1895 |
isset(Metasync::get_option('localseo')['local_seo_geo_coordinates']) ? esc_attr(Metasync::get_option('localseo')['local_seo_geo_coordinates']) : '' |
| 1896 |
); |
| 1897 |
|
| 1898 |
printf(' <br> <span class="description"> Latitude and longitude values separated by comma. </span>'); |
| 1899 |
} |
| 1900 |
|
| 1901 |
public function header_snippets_callback() |
| 1902 |
{ |
| 1903 |
printf( |
| 1904 |
'<textarea class="wide-text" id="header_snippets" rows="8" name="' . Metasync_Admin::option_key . '[codesnippets][header_snippet]" >%s</textarea>', |
| 1905 |
isset(Metasync::get_option('codesnippets')['header_snippet']) ? esc_attr(Metasync::get_option('codesnippets')['header_snippet']) : '' |
| 1906 |
); |
| 1907 |
} |
| 1908 |
|
| 1909 |
public function footer_snippets_callback() |
| 1910 |
{ |
| 1911 |
printf( |
| 1912 |
'<textarea class="wide-text" id="footer_snippets" rows="8" name="' . Metasync_Admin::option_key . '[codesnippets][footer_snippet]" >%s</textarea>', |
| 1913 |
isset(Metasync::get_option('codesnippets')['footer_snippet']) ? esc_attr(Metasync::get_option('codesnippets')['footer_snippet']) : '' |
| 1914 |
); |
| 1915 |
} |
| 1916 |
|
| 1917 |
public function common_robot_meta_tags_callback() |
| 1918 |
{ |
| 1919 |
$common_robots_meta = Metasync::get_option('common_robots_meta') ?? Metasync::get_option('common_robots_mata') ?? ''; |
| 1920 |
|
| 1921 |
?> |
| 1922 |
<ul class="checkbox-list"> |
| 1923 |
<li> |
| 1924 |
<input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[common_robots_meta][noindex]') ?>" id="robots_common2" value="noindex" <?php isset($common_robots_meta['noindex']) ? checked('noindex', $common_robots_meta['noindex']) : '' ?>> |
| 1925 |
<label for="robots_common2">No Index </br> |
| 1926 |
<span class="description"> |
| 1927 |
<span>Search engines not indexed and displayed this pages in search engine results</span> |
| 1928 |
</span> |
| 1929 |
</label> |
| 1930 |
</li> |
| 1931 |
<li> |
| 1932 |
<input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[common_robots_meta][nofollow]') ?>" id="robots_common3" value="nofollow" <?php isset($common_robots_meta['nofollow']) ? checked('nofollow', $common_robots_meta['nofollow']) : '' ?>> |
| 1933 |
<label for="robots_common3">No Follow </br> |
| 1934 |
<span class="description"> |
| 1935 |
<span>Search engines not follow the links on the pages</span> |
| 1936 |
</span> |
| 1937 |
</label> |
| 1938 |
</li> |
| 1939 |
<li> |
| 1940 |
<input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[common_robots_meta][noarchive]') ?>" id="robots_common4" value="noarchive" <?php isset($common_robots_meta['noarchive']) ? checked('noarchive', $common_robots_meta['noarchive']) : '' ?>> |
| 1941 |
<label for="robots_common4">No Archive </br> |
| 1942 |
<span class="description"> |
| 1943 |
<span>Search engines not showing Cached links for pages</span> |
| 1944 |
</span> |
| 1945 |
</label> |
| 1946 |
</li> |
| 1947 |
<li> |
| 1948 |
<input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[common_robots_meta][noimageindex]') ?>" id="robots_common5" value="noimageindex" <?php isset($common_robots_meta['noimageindex']) ? checked('noimageindex', $common_robots_meta['noimageindex']) : '' ?>> |
| 1949 |
<label for="robots_common5">No Image Index </br> |
| 1950 |
<span class="description"> |
| 1951 |
<span>If you do not want to apear your pages as the referring page for images that appear in image search results</span> |
| 1952 |
</span> |
| 1953 |
</label> |
| 1954 |
</li> |
| 1955 |
<li> |
| 1956 |
<input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[common_robots_meta][nosnippet]') ?>" id="robots_common6" value="nosnippet" <?php isset($common_robots_meta['nosnippet']) ? checked('nosnippet', $common_robots_meta['nosnippet']) : '' ?>> |
| 1957 |
<label for="robots_common6">No Snippet </br> |
| 1958 |
<span class="description"> |
| 1959 |
<span>Search engines not snippet to show in the search results</span> |
| 1960 |
</span> |
| 1961 |
</label> |
| 1962 |
</li> |
| 1963 |
</ul> |
| 1964 |
<?php |
| 1965 |
} |
| 1966 |
|
| 1967 |
/** |
| 1968 |
* @deprecated Use common_robot_meta_tags_callback() instead |
| 1969 |
*/ |
| 1970 |
public function common_robot_mata_tags_callback() |
| 1971 |
{ |
| 1972 |
return $this->common_robot_meta_tags_callback(); |
| 1973 |
} |
| 1974 |
|
| 1975 |
public function advance_robot_meta_tags_callback() |
| 1976 |
{ |
| 1977 |
$advance_robots_meta = Metasync::get_option('advance_robots_meta') ?? Metasync::get_option('advance_robots_mata') ?? ''; |
| 1978 |
|
| 1979 |
$snippet_advance_robots_enable = $advance_robots_meta['max-snippet']['enable'] ?? ''; |
| 1980 |
$snippet_advance_robots_length = $advance_robots_meta['max-snippet']['length'] ?? '-1'; |
| 1981 |
$video_advance_robots_enable = $advance_robots_meta['max-video-preview']['enable'] ?? ''; |
| 1982 |
$video_advance_robots_length = $advance_robots_meta['max-video-preview']['length'] ?? '-1'; |
| 1983 |
$image_advance_robots_enable = $advance_robots_meta['max-image-preview']['enable'] ?? ''; |
| 1984 |
$image_advance_robots_length = $advance_robots_meta['max-image-preview']['length'] ?? ''; |
| 1985 |
|
| 1986 |
?> |
| 1987 |
<ul class="checkbox-list"> |
| 1988 |
<li> |
| 1989 |
<label for="advanced_robots_snippet"> |
| 1990 |
<input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[advance_robots_meta][max-snippet][enable]') ?>" id="advanced_robots_snippet" value="1" <?php checked('1', esc_attr($snippet_advance_robots_enable)) ?>> |
| 1991 |
Snippet </br> |
| 1992 |
<input type="number" class="input-length" name="<?php echo esc_attr(Metasync_Admin::option_key . '[advance_robots_meta][max-snippet][length]') ?>" id="advanced_robots_snippet_value" value="<?php echo esc_attr($snippet_advance_robots_length); ?>" min="-1"> </br> |
| 1993 |
<span class="description"> |
| 1994 |
<span>Add maximum text-length, in characters, of a snippet for your page.</span> |
| 1995 |
</span> |
| 1996 |
</label> |
| 1997 |
</li> |
| 1998 |
<li> |
| 1999 |
<label for="advanced_robots_video"> |
| 2000 |
<input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[advance_robots_meta][max-video-preview][enable]') ?>" id="advanced_robots_video" value="1" <?php checked('1', esc_attr($video_advance_robots_enable)) ?>> |
| 2001 |
Video Preview </br> |
| 2002 |
<input type="number" class="input-length" name="<?php echo esc_attr(Metasync_Admin::option_key . '[advance_robots_meta][max-video-preview][length]') ?>" id="advanced_robots_video_value" value="<?php echo esc_attr($video_advance_robots_length); ?>" min="-1"> </br> |
| 2003 |
<span class="description"> |
| 2004 |
<span>Add maximum duration in seconds of an animated video preview.</span> |
| 2005 |
</span> |
| 2006 |
</label> |
| 2007 |
</li> |
| 2008 |
<li> |
| 2009 |
<label for="advanced_robots_image"> |
| 2010 |
<input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[advance_robots_meta][max-image-preview][enable]') ?>" id="advanced_robots_image" value="1" <?php checked('1', esc_attr($image_advance_robots_enable)); ?>> |
| 2011 |
Image Preview </br> |
| 2012 |
<select class="input-length" name="<?php echo esc_attr(Metasync_Admin::option_key . '[advance_robots_meta][max-image-preview][length]') ?>" id="advanced_robots_image_value"> |
| 2013 |
<option value="large" <?php selected('large', esc_attr($image_advance_robots_length)) ?>>Large</option> |
| 2014 |
<option value="standard" <?php selected('standard', esc_attr($image_advance_robots_length)) ?>>Standard</option> |
| 2015 |
<option value="none" <?php selected('none', esc_attr($image_advance_robots_length)) ?>>None</option> |
| 2016 |
</select> |
| 2017 |
</br> |
| 2018 |
<span class="description"> |
| 2019 |
<span>Add maximum size of image preview to show the images on this page.</span> |
| 2020 |
</span> |
| 2021 |
</label> |
| 2022 |
</li> |
| 2023 |
</ul> |
| 2024 |
<?php |
| 2025 |
} |
| 2026 |
|
| 2027 |
/** |
| 2028 |
* @deprecated Use advance_robot_meta_tags_callback() instead |
| 2029 |
*/ |
| 2030 |
public function advance_robot_mata_tags_callback() |
| 2031 |
{ |
| 2032 |
return $this->advance_robot_meta_tags_callback(); |
| 2033 |
} |
| 2034 |
|
| 2035 |
public function global_twitter_card_type_callback() |
| 2036 |
{ |
| 2037 |
$twitter_card_type = Metasync::get_option('twitter_card_type') ?? ''; |
| 2038 |
?> |
| 2039 |
|
| 2040 |
<select class="input-length" name="<?php echo esc_attr(Metasync_Admin::option_key . '[twitter_card_type]') ?>" id="twitter_card_type"> |
| 2041 |
<option value="summary_large_image" <?php selected('summary_large_image', esc_attr($twitter_card_type)) ?>>Summary Large Image</option> |
| 2042 |
<option value="summary_card" <?php selected('summary_card', esc_attr($twitter_card_type)) ?>>Summary Card</option> |
| 2043 |
</select> |
| 2044 |
|
| 2045 |
<?php |
| 2046 |
} |
| 2047 |
|
| 2048 |
public function global_open_graph_meta_callback() |
| 2049 |
{ |
| 2050 |
printf( |
| 2051 |
'<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][open_graph_meta_tags]" value="true" %s />', |
| 2052 |
isset(Metasync::get_option('common_meta_settings')['open_graph_meta_tags']) && Metasync::get_option('common_meta_settings')['open_graph_meta_tags'] == 'true' ? 'checked' : '' |
| 2053 |
); |
| 2054 |
printf(' <br> <span class="description"> Automatically add the Open Graph meta tags in a page or post.</span>'); |
| 2055 |
} |
| 2056 |
|
| 2057 |
public function global_facebook_meta_callback() |
| 2058 |
{ |
| 2059 |
printf( |
| 2060 |
'<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][facebook_meta_tags]" value="true" %s />', |
| 2061 |
isset(Metasync::get_option('common_meta_settings')['facebook_meta_tags']) && Metasync::get_option('common_meta_settings')['facebook_meta_tags'] == 'true' ? 'checked' : '' |
| 2062 |
); |
| 2063 |
printf(' <br> <span class="description"> Automatically add the Facebook meta tags in a page or post.</span>'); |
| 2064 |
} |
| 2065 |
|
| 2066 |
public function global_twitter_meta_callback() |
| 2067 |
{ |
| 2068 |
printf( |
| 2069 |
'<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][twitter_meta_tags]" value="true" %s />', |
| 2070 |
isset(Metasync::get_option('common_meta_settings')['twitter_meta_tags']) && Metasync::get_option('common_meta_settings')['twitter_meta_tags'] == 'true' ? 'checked' : '' |
| 2071 |
); |
| 2072 |
printf(' <br> <span class="description"> Automatically add the Twitter meta tags in a page or post.</span>'); |
| 2073 |
} |
| 2074 |
|
| 2075 |
public function og_image_dimensions_callback() |
| 2076 |
{ |
| 2077 |
$val = Metasync::get_option('common_meta_settings')['og_image_dimensions'] ?? 'true'; |
| 2078 |
printf( |
| 2079 |
'<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][og_image_dimensions]" value="true" %s />', |
| 2080 |
$val === 'true' ? 'checked' : '' |
| 2081 |
); |
| 2082 |
printf('<span class="description"> Output og:image:width, og:image:height, and og:image:type when OG image metadata is available.</span>'); |
| 2083 |
} |
| 2084 |
|
| 2085 |
public function article_timestamps_callback() |
| 2086 |
{ |
| 2087 |
$val = Metasync::get_option('common_meta_settings')['article_timestamps'] ?? 'true'; |
| 2088 |
printf( |
| 2089 |
'<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_timestamps]" value="true" %s />', |
| 2090 |
$val === 'true' ? 'checked' : '' |
| 2091 |
); |
| 2092 |
printf('<span class="description"> Output article:published_time and article:modified_time from the post publish and modified dates (ISO 8601).</span>'); |
| 2093 |
} |
| 2094 |
|
| 2095 |
public function article_author_callback() |
| 2096 |
{ |
| 2097 |
$val = Metasync::get_option('common_meta_settings')['article_author'] ?? 'true'; |
| 2098 |
printf( |
| 2099 |
'<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_author]" value="true" %s />', |
| 2100 |
$val === 'true' ? 'checked' : '' |
| 2101 |
); |
| 2102 |
printf('<span class="description"> Output article:author using the post author website URL or archive URL (overridable per-post via _metasync_og_article_author meta).</span>'); |
| 2103 |
} |
| 2104 |
|
| 2105 |
public function article_section_callback() |
| 2106 |
{ |
| 2107 |
$val = Metasync::get_option('common_meta_settings')['article_section'] ?? 'true'; |
| 2108 |
printf( |
| 2109 |
'<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_section]" value="true" %s />', |
| 2110 |
$val === 'true' ? 'checked' : '' |
| 2111 |
); |
| 2112 |
printf('<span class="description"> Output article:section from the primary category (falls back to first category).</span>'); |
| 2113 |
} |
| 2114 |
|
| 2115 |
public function article_tags_callback() |
| 2116 |
{ |
| 2117 |
$val = Metasync::get_option('common_meta_settings')['article_tags'] ?? 'true'; |
| 2118 |
printf( |
| 2119 |
'<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_tags]" value="true" %s />', |
| 2120 |
$val === 'true' ? 'checked' : '' |
| 2121 |
); |
| 2122 |
printf('<span class="description"> Output one article:tag meta tag per WordPress post tag.</span>'); |
| 2123 |
} |
| 2124 |
|
| 2125 |
public function twitter_image_alt_callback() |
| 2126 |
{ |
| 2127 |
$val = Metasync::get_option('common_meta_settings')['twitter_image_alt'] ?? 'true'; |
| 2128 |
printf( |
| 2129 |
'<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][twitter_image_alt]" value="true" %s />', |
| 2130 |
$val === 'true' ? 'checked' : '' |
| 2131 |
); |
| 2132 |
printf('<span class="description"> Output twitter:image:alt using the stored alt text or the OG image attachment alt attribute.</span>'); |
| 2133 |
} |
| 2134 |
|
| 2135 |
public function facebook_page_url_callback() |
| 2136 |
{ |
| 2137 |
$facebook_page_url = Metasync::get_option('social_meta')['facebook_page_url'] ?? ''; |
| 2138 |
printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_page_url]" value="%s" size="50" />', esc_attr($facebook_page_url)); |
| 2139 |
printf('<br><span class="description"> Enter your Facebook page URL. eg: <code>https://www.facebook.com/MetaSync/</code> </span>'); |
| 2140 |
} |
| 2141 |
|
| 2142 |
public function facebook_authorship_callback() |
| 2143 |
{ |
| 2144 |
$facebook_authorship = Metasync::get_option('social_meta')['facebook_authorship'] ?? ''; |
| 2145 |
printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_authorship]" value="%s" size="50" />', esc_attr($facebook_authorship)); |
| 2146 |
printf('<br><span class="description"> Enter Facebook profile URL to show Facebook Authorship when your articles are being shared on Facebook. eg: <code>https://www.facebook.com/shahrukh/</code> </span>'); |
| 2147 |
} |
| 2148 |
|
| 2149 |
public function facebook_admin_callback() |
| 2150 |
{ |
| 2151 |
$facebook_admin = Metasync::get_option('social_meta')['facebook_admin'] ?? ''; |
| 2152 |
printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_admin]" value="%s" size="50" />', esc_attr($facebook_admin)); |
| 2153 |
printf(' <br> <span class="description"> Enter numeric user ID of Facebook. </span>'); |
| 2154 |
} |
| 2155 |
|
| 2156 |
public function facebook_app_callback() |
| 2157 |
{ |
| 2158 |
$facebook_app = Metasync::get_option('social_meta')['facebook_app'] ?? ''; |
| 2159 |
printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_app]" value="%s" size="50" />', esc_attr($facebook_app)); |
| 2160 |
printf(' <br> <span class="description"> Enter numeric app ID of Facebook </span>'); |
| 2161 |
} |
| 2162 |
|
| 2163 |
public function facebook_secret_callback() |
| 2164 |
{ |
| 2165 |
$facebook_secret = Metasync::get_option('social_meta')['facebook_secret'] ?? ''; |
| 2166 |
printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_secret]" value="%s" size="50" />', esc_attr($facebook_secret)); |
| 2167 |
printf(' <br> <span class="description"> Enter alphanumeric access token from Facebook. </span>'); |
| 2168 |
} |
| 2169 |
|
| 2170 |
public function twitter_username_callback() |
| 2171 |
{ |
| 2172 |
$twitter_username = Metasync::get_option('social_meta')['twitter_username'] ?? ''; |
| 2173 |
printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][twitter_username]" value="%s" size="50" />', esc_attr($twitter_username)); |
| 2174 |
printf(' <br> <span class="description"> Twitter username of the author to add <code>twitter:creator</code> tag to post. eg: <code>MetaSync</code> </span>'); |
| 2175 |
} |
| 2176 |
|
| 2177 |
public static function get_business_types() |
| 2178 |
{ |
| 2179 |
$business_type = [ |
| 2180 |
'Airline', |
| 2181 |
'Consortium', |
| 2182 |
'Corporation', |
| 2183 |
'Educational Organization', |
| 2184 |
'College Or University', |
| 2185 |
'Elementary School', |
| 2186 |
'High School', |
| 2187 |
'Middle School', |
| 2188 |
'Preschool', |
| 2189 |
'School', |
| 2190 |
'Funding Scheme', |
| 2191 |
'Government Organization', |
| 2192 |
'Library System', |
| 2193 |
'Local Business', |
| 2194 |
'Animal Shelter', |
| 2195 |
'Archive Organization', |
| 2196 |
'Automotive Business', |
| 2197 |
'Auto Body Shop', |
| 2198 |
'Auto Dealer', |
| 2199 |
'Auto Parts Store', |
| 2200 |
'Auto Rental', |
| 2201 |
'Auto Repair', |
| 2202 |
'Auto Wash', |
| 2203 |
'Gas Station', |
| 2204 |
'Motorcycle Dealer', |
| 2205 |
'Motorcycle Repair', |
| 2206 |
'Child Care', |
| 2207 |
'Dry Cleaning Or Laundry', |
| 2208 |
'Emergency Service', |
| 2209 |
'Fire Station', |
| 2210 |
'Hospital', |
| 2211 |
'Police Station', |
| 2212 |
'Employment Agency', |
| 2213 |
'Entertainment Business', |
| 2214 |
'Adult Entertainment', |
| 2215 |
'Amusement Park', |
| 2216 |
'Art Gallery', |
| 2217 |
'Casino', |
| 2218 |
'Comedy Club', |
| 2219 |
'Movie Theater', |
| 2220 |
'Night Club', |
| 2221 |
'Financial Service', |
| 2222 |
'Accounting Service', |
| 2223 |
'Automated Teller', |
| 2224 |
'Bank Or CreditUnion', |
| 2225 |
'Insurance Agency', |
| 2226 |
'Food Establishment', |
| 2227 |
'Bakery', |
| 2228 |
'Bar Or Pub', |
| 2229 |
'Brewery', |
| 2230 |
'Cafe Or CoffeeShop', |
| 2231 |
'Distillery', |
| 2232 |
'Fast Food Restaurant', |
| 2233 |
'IceCream Shop', |
| 2234 |
'Restaurant', |
| 2235 |
'Winery', |
| 2236 |
'Government Office', |
| 2237 |
'Post Office', |
| 2238 |
'Health And Beauty Business', |
| 2239 |
'Beauty Salon', |
| 2240 |
'Day Spa', |
| 2241 |
'Hair Salon', |
| 2242 |
'Health Club', |
| 2243 |
'Nail Salon', |
| 2244 |
'Tattoo Parlor', |
| 2245 |
'Home And Construction Business', |
| 2246 |
'Electrician', |
| 2247 |
'General Contractor', |
| 2248 |
'HVAC Business', |
| 2249 |
'House Painter', |
| 2250 |
'Locksmith', |
| 2251 |
'Moving Company', |
| 2252 |
'Plumber', |
| 2253 |
'Roofing Contractor', |
| 2254 |
'Internet Cafe', |
| 2255 |
'Legal Service', |
| 2256 |
'Attorney', |
| 2257 |
'Notary', |
| 2258 |
'Library', |
| 2259 |
'Lodging Business', |
| 2260 |
'Bed And Breakfast', |
| 2261 |
'Campground', |
| 2262 |
'Hostel', |
| 2263 |
'Hotel', |
| 2264 |
'Motel', |
| 2265 |
'Resort', |
| 2266 |
'Ski Resort', |
| 2267 |
'Medical Business', |
| 2268 |
'Community Health', |
| 2269 |
'Dentist', |
| 2270 |
'Dermatology', |
| 2271 |
'Diet Nutrition', |
| 2272 |
'Emergency', |
| 2273 |
'Geriatric', |
| 2274 |
'Gynecologic', |
| 2275 |
'Medical Clinic', |
| 2276 |
'Optician', |
| 2277 |
'Pharmacy', |
| 2278 |
'Physician', |
| 2279 |
'Professional Service', |
| 2280 |
'Radio Station', |
| 2281 |
'Real Estate Agent', |
| 2282 |
'Recycling Center', |
| 2283 |
'Self Storage', |
| 2284 |
'Shopping Center', |
| 2285 |
'Sports Activity Location', |
| 2286 |
'Bowling Alley', |
| 2287 |
'Exercise Gym', |
| 2288 |
'Golf Course', |
| 2289 |
'Public Swimming Pool', |
| 2290 |
'Ski Resort', |
| 2291 |
'Sports Club', |
| 2292 |
'Stadium Or Arena', |
| 2293 |
'Tennis Complex', |
| 2294 |
'Store', |
| 2295 |
'Bike Store', |
| 2296 |
'Book Store', |
| 2297 |
'Clothing Store', |
| 2298 |
'Computer Store', |
| 2299 |
'Convenience Store', |
| 2300 |
'Department Store', |
| 2301 |
'Electronics Store', |
| 2302 |
'Florist', |
| 2303 |
'Furniture Store', |
| 2304 |
'Garden Store', |
| 2305 |
'Grocery Store', |
| 2306 |
'Hardware Store', |
| 2307 |
'Hobby Shop', |
| 2308 |
'Home Goods Store', |
| 2309 |
'Jewelry Store', |
| 2310 |
'Liquor Store', |
| 2311 |
'Mens Clothing Store', |
| 2312 |
'Mobile Phone Store', |
| 2313 |
'Movie Rental Store', |
| 2314 |
'Music Store', |
| 2315 |
'Office Equipment Store', |
| 2316 |
'Outlet Store', |
| 2317 |
'Pawn Shop', |
| 2318 |
'Pet Store', |
| 2319 |
'Shoe Store', |
| 2320 |
'Sporting GoodsStore', |
| 2321 |
'Tire Shop', |
| 2322 |
'Toy Store', |
| 2323 |
'Wholesale Store', |
| 2324 |
'Television Station', |
| 2325 |
'Tourist Information Center', |
| 2326 |
'Travel Agency', |
| 2327 |
'Tree Services', |
| 2328 |
'Medical Organization', |
| 2329 |
'Diagnostic Lab', |
| 2330 |
'Veterinary Care', |
| 2331 |
'NGO', |
| 2332 |
'News Media Organization', |
| 2333 |
'Performing Group', |
| 2334 |
'Dance Group', |
| 2335 |
'Music Group', |
| 2336 |
'Theater Group', |
| 2337 |
'Project', |
| 2338 |
'Funding Agency', |
| 2339 |
'Research Project', |
| 2340 |
'Sports Organization', |
| 2341 |
'Sports Team', |
| 2342 |
'Workers Union', |
| 2343 |
]; |
| 2344 |
|
| 2345 |
return $business_type; |
| 2346 |
} |
| 2347 |
|
| 2348 |
/** |
| 2349 |
* Render breadcrumbs settings section |
| 2350 |
* |
| 2351 |
* Rendered by the dedicated Breadcrumbs admin page. The three checkbox |
| 2352 |
* fields each ship a hidden companion input so their keys are always |
| 2353 |
* present in the submitted payload, which lets the save handlers treat an |
| 2354 |
* absent key as "this form does not manage that field" instead of |
| 2355 |
* "unchecked". |
| 2356 |
*/ |
| 2357 |
public function render_breadcrumbs_section() { |
| 2358 |
$options = Metasync::get_option('breadcrumbs', array()); |
| 2359 |
|
| 2360 |
$defaults = array( |
| 2361 |
'enabled' => true, |
| 2362 |
'separator' => '»', |
| 2363 |
'home_label' => 'Home', |
| 2364 |
'home_url' => '', |
| 2365 |
'show_current_page' => true, |
| 2366 |
'prefix_text' => '', |
| 2367 |
'archive_label_format' => '{name}', |
| 2368 |
'disable_schema' => false, |
| 2369 |
); |
| 2370 |
|
| 2371 |
$options = wp_parse_args($options, $defaults); |
| 2372 |
|
| 2373 |
# Older builds stored the separator as an HTML entity (e.g. "»"). |
| 2374 |
# Decode before matching so the saved choice still lights up its radio. |
| 2375 |
$separator_presets = array('»', '/', '>', '·'); |
| 2376 |
$separator_current = html_entity_decode((string) $options['separator'], ENT_QUOTES, 'UTF-8'); |
| 2377 |
if ($separator_current === '') { |
| 2378 |
$separator_current = '»'; |
| 2379 |
} |
| 2380 |
# A separator that predates the radio picker (or came from the old free |
| 2381 |
# text field) gets its own option so it stays selected and round-trips. |
| 2382 |
$separator_custom = in_array($separator_current, $separator_presets, true) ? '' : $separator_current; |
| 2383 |
?> |
| 2384 |
<div style="background: var(--dashboard-card-bg); padding: 20px; border-radius: 8px;"> |
| 2385 |
|
| 2386 |
<!-- Enable/Disable --> |
| 2387 |
<div style="margin-bottom: 24px;"> |
| 2388 |
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;"> |
| 2389 |
<input type="hidden" name="metasync_options[breadcrumbs][enabled]" value="0"> |
| 2390 |
<input type="checkbox" |
| 2391 |
name="metasync_options[breadcrumbs][enabled]" |
| 2392 |
value="1" |
| 2393 |
<?php checked($options['enabled'], 1); ?> |
| 2394 |
style="width: 18px; height: 18px; cursor: pointer;"> |
| 2395 |
<span style="font-weight: 500; color: var(--dashboard-text);">Enable breadcrumbs globally</span> |
| 2396 |
<?php Metasync::render_tooltip_icon('breadcrumbs_enabled', 'Shows a trail of links (e.g. Home > Blog > This Post) near the top of your pages so visitors can see where they are and navigate back up a level. Turn off to hide breadcrumbs everywhere.'); ?> |
| 2397 |
</label> |
| 2398 |
<p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);"> |
| 2399 |
Enable or disable breadcrumb navigation on your site |
| 2400 |
</p> |
| 2401 |
</div> |
| 2402 |
|
| 2403 |
<!-- Disable Schema Markup --> |
| 2404 |
<div style="margin-bottom: 24px;"> |
| 2405 |
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;"> |
| 2406 |
<input type="hidden" name="metasync_options[breadcrumbs][disable_schema]" value="0"> |
| 2407 |
<input type="checkbox" |
| 2408 |
name="metasync_options[breadcrumbs][disable_schema]" |
| 2409 |
value="1" |
| 2410 |
<?php checked($options['disable_schema'], 1); ?> |
| 2411 |
style="width: 18px; height: 18px; cursor: pointer;"> |
| 2412 |
<span style="font-weight: 500; color: var(--dashboard-text);">Disable breadcrumb schema markup (JSON-LD)</span> |
| 2413 |
<?php Metasync::render_tooltip_icon('breadcrumbs_disable_schema', 'Leave off. Only check this if another SEO plugin (Yoast, Rank Math, etc.) already sends breadcrumb data to Google, so you don\'t send it twice.'); ?> |
| 2414 |
</label> |
| 2415 |
<p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);"> |
| 2416 |
When checked, MetaSync will not output the <code style="background: rgba(0,0,0,0.1); padding: 2px 4px; border-radius: 3px;">BreadcrumbList</code> JSON-LD in <code style="background: rgba(0,0,0,0.1); padding: 2px 4px; border-radius: 3px;"><head></code>. Use this if another SEO plugin already handles breadcrumb schema. |
| 2417 |
</p> |
| 2418 |
</div> |
| 2419 |
|
| 2420 |
<!-- Separator Character --> |
| 2421 |
<div style="margin-bottom: 24px;"> |
| 2422 |
<label for="breadcrumb_separator" style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--dashboard-text);"> |
| 2423 |
Separator Character |
| 2424 |
</label> |
| 2425 |
<div style="display: flex; gap: 12px; flex-wrap: wrap;"> |
| 2426 |
<?php foreach ($separator_presets as $separator_option) : ?> |
| 2427 |
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;"> |
| 2428 |
<input type="radio" name="metasync_options[breadcrumbs][separator]" value="<?php echo esc_attr($separator_option); ?>" <?php checked($separator_current, $separator_option); ?>> |
| 2429 |
<span style="color: var(--dashboard-text);"><?php echo esc_html($separator_option); ?></span> |
| 2430 |
</label> |
| 2431 |
<?php endforeach; ?> |
| 2432 |
<div style="display: flex; align-items: center; gap: 8px;"> |
| 2433 |
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;"> |
| 2434 |
<input type="radio" id="metasync-breadcrumb-separator-custom" name="metasync_options[breadcrumbs][separator]" value="<?php echo esc_attr($separator_custom); ?>" <?php checked($separator_custom !== ''); ?>> |
| 2435 |
<span style="color: var(--dashboard-text);">Custom</span> |
| 2436 |
</label> |
| 2437 |
<input type="text" value="<?php echo esc_attr($separator_custom); ?>" placeholder="~" maxlength="16" aria-label="Custom separator character" oninput="var r=document.getElementById('metasync-breadcrumb-separator-custom');r.value=this.value;r.checked=true" style="width: 80px; padding: 8px 10px; background: var(--dashboard-input-bg); border: 1px solid var(--dashboard-border); border-radius: 6px; color: var(--dashboard-text); font-size: 14px;"> |
| 2438 |
</div> |
| 2439 |
</div> |
| 2440 |
<p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);"> |
| 2441 |
Choose how to separate breadcrumb items |
| 2442 |
</p> |
| 2443 |
</div> |
| 2444 |
|
| 2445 |
<!-- Home Label --> |
| 2446 |
<div style="margin-bottom: 24px;"> |
| 2447 |
<label for="breadcrumb_home_label" style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--dashboard-text);"> |
| 2448 |
Home Label |
| 2449 |
</label> |
| 2450 |
<input type="text" |
| 2451 |
id="breadcrumb_home_label" |
| 2452 |
name="metasync_options[breadcrumbs][home_label]" |
| 2453 |
value="<?php echo esc_attr($options['home_label']); ?>" |
| 2454 |
placeholder="Home" |
| 2455 |
style="width: 100%; padding: 10px 12px; background: var(--dashboard-input-bg); border: 1px solid var(--dashboard-border); border-radius: 6px; color: var(--dashboard-text); font-size: 14px; box-sizing: border-box;"> |
| 2456 |
<p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);"> |
| 2457 |
Label for the home/root breadcrumb item |
| 2458 |
</p> |
| 2459 |
</div> |
| 2460 |
|
| 2461 |
<!-- Home URL --> |
| 2462 |
<div style="margin-bottom: 24px;"> |
| 2463 |
<label for="breadcrumb_home_url" style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--dashboard-text);"> |
| 2464 |
Home URL |
| 2465 |
</label> |
| 2466 |
<input type="url" |
| 2467 |
id="breadcrumb_home_url" |
| 2468 |
name="metasync_options[breadcrumbs][home_url]" |
| 2469 |
value="<?php echo esc_attr($options['home_url']); ?>" |
| 2470 |
placeholder="<?php echo esc_attr(home_url('/')); ?>" |
| 2471 |
style="width: 100%; padding: 10px 12px; background: var(--dashboard-input-bg); border: 1px solid var(--dashboard-border); border-radius: 6px; color: var(--dashboard-text); font-size: 14px; box-sizing: border-box;"> |
| 2472 |
<p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);"> |
| 2473 |
Leave empty to use your site home URL |
| 2474 |
</p> |
| 2475 |
</div> |
| 2476 |
|
| 2477 |
<!-- Show Current Page --> |
| 2478 |
<div style="margin-bottom: 24px;"> |
| 2479 |
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;"> |
| 2480 |
<input type="hidden" name="metasync_options[breadcrumbs][show_current_page]" value="0"> |
| 2481 |
<input type="checkbox" |
| 2482 |
name="metasync_options[breadcrumbs][show_current_page]" |
| 2483 |
value="1" |
| 2484 |
<?php checked($options['show_current_page'], 1); ?> |
| 2485 |
style="width: 18px; height: 18px; cursor: pointer;"> |
| 2486 |
<span style="font-weight: 500; color: var(--dashboard-text);">Show current page</span> |
| 2487 |
</label> |
| 2488 |
<p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);"> |
| 2489 |
Include the current page as the last (unlinked) breadcrumb item |
| 2490 |
</p> |
| 2491 |
</div> |
| 2492 |
|
| 2493 |
<!-- Prefix Text --> |
| 2494 |
<div style="margin-bottom: 24px;"> |
| 2495 |
<label for="breadcrumb_prefix" style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--dashboard-text);"> |
| 2496 |
Prefix Text |
| 2497 |
</label> |
| 2498 |
<input type="text" |
| 2499 |
id="breadcrumb_prefix" |
| 2500 |
name="metasync_options[breadcrumbs][prefix_text]" |
| 2501 |
value="<?php echo esc_attr($options['prefix_text']); ?>" |
| 2502 |
placeholder='e.g., "You are here:"' |
| 2503 |
style="width: 100%; padding: 10px 12px; background: var(--dashboard-input-bg); border: 1px solid var(--dashboard-border); border-radius: 6px; color: var(--dashboard-text); font-size: 14px; box-sizing: border-box;"> |
| 2504 |
<p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);"> |
| 2505 |
Optional text to display before the breadcrumbs (leave empty for none) |
| 2506 |
</p> |
| 2507 |
</div> |
| 2508 |
|
| 2509 |
<!-- Archive Label Format --> |
| 2510 |
<div style="margin-bottom: 24px;"> |
| 2511 |
<label for="breadcrumb_archive_format" style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--dashboard-text);"> |
| 2512 |
Archive Label Format |
| 2513 |
<?php Metasync::render_tooltip_icon('breadcrumb_archive_format', 'On category/archive pages, {name} is replaced with that section\'s name. Type {name} alone for just the name, or something like "Category: {name}".'); ?> |
| 2514 |
</label> |
| 2515 |
<input type="text" |
| 2516 |
id="breadcrumb_archive_format" |
| 2517 |
name="metasync_options[breadcrumbs][archive_label_format]" |
| 2518 |
value="<?php echo esc_attr($options['archive_label_format']); ?>" |
| 2519 |
placeholder="{name}" |
| 2520 |
style="width: 100%; padding: 10px 12px; background: var(--dashboard-input-bg); border: 1px solid var(--dashboard-border); border-radius: 6px; color: var(--dashboard-text); font-size: 14px; box-sizing: border-box;"> |
| 2521 |
<p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);"> |
| 2522 |
Use <code style="background: rgba(0,0,0,0.1); padding: 2px 4px; border-radius: 3px;">{name}</code> as a placeholder for the archive name. Example: <code style="background: rgba(0,0,0,0.1); padding: 2px 4px; border-radius: 3px;">Category: {name}</code> |
| 2523 |
</p> |
| 2524 |
</div> |
| 2525 |
|
| 2526 |
</div> |
| 2527 |
<?php |
| 2528 |
} |
| 2529 |
} |
| 2530 |
|