PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.2
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.2
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
← All changes | includes/class-metasync-settings-fields.php +265 -259 2.6.18 → 2.7.2 View file →
@@ -46,9 +46,23 @@
46 46 // Accordion / Section helpers
47 47 // ────────────────────────────────────────────────────────────────
48 48
49 49 public function get_accordion_sections_config() {
50 - return array(
50 + // Consent switch for writing into other SEO plugins. Loaded here rather
51 + // than through the committed classmap so a stale autoload map can never
52 + // make the switch — and with it the user's only way to stop those
53 + // writes — silently disappear from the page.
54 + require_once plugin_dir_path(dirname(__FILE__)) . 'admin/class-metasync-seo-sync-settings.php';
55 +
56 + $sections = array(
57 + 'analytics' => array(
58 + 'title' => 'Usage Data & Product Improvement',
59 + 'description' => 'Choose whether to share limited usage data to help improve MetaSync',
60 + 'icon' => 'chart-bar',
61 + 'priority' => 70,
62 + 'default_open' => false,
63 + 'render_callback' => array($this, 'render_analytics_section'),
64 + ),
51 65 'connection' => array(
52 66 'title' => 'Connection & Authentication',
53 67 'description' => 'Manage your API connection and authentication settings',
54 68 'icon' => 'admin-network',
@@ -68,9 +82,9 @@
68 82 'fields' => array(
69 83 'otto_pixel_uuid',
70 84 'otto_disable_on_loggedin',
71 85 'otto_disable_preview_button',
72 - 'otto_wp_rocket_compat',
86 + 'seo_priority',
73 87 )
74 88 ),
75 89 'edge_cache' => array(
76 90 'title' => 'Edge Cache / CDN',
@@ -79,8 +93,16 @@
79 93 'priority' => 22,
80 94 'default_open' => false,
81 95 'render_callback' => array('Metasync_Edge_Cache_Settings', 'render'),
82 96 ),
97 + 'seo_plugin_sync' => array(
98 + 'title' => 'Sync to Other SEO Plugins',
99 + 'description' => 'Control whether ' . Metasync::get_whitelabel_otto_name() . '\'s SEO values are written into Yoast, Rank Math or All in One SEO',
100 + 'icon' => 'update',
101 + 'priority' => 23,
102 + 'default_open' => false,
103 + 'render_callback' => array('Metasync_Seo_Sync_Settings', 'render'),
104 + ),
83 105 'bot_detection' => array(
84 106 'title' => 'Bot Detection & Filtering',
85 107 'description' => 'Manage bot traffic and reduce unnecessary API calls',
86 108 'icon' => 'filter',
@@ -92,16 +114,8 @@
92 114 'otto_bot_blacklist',
93 115 'otto_bot_statistics_link'
94 116 )
95 117 ),
96 - 'breadcrumbs' => array(
97 - 'title' => 'Breadcrumbs',
98 - 'description' => 'Configure breadcrumb navigation display',
99 - 'icon' => 'format-links',
100 - 'priority' => 35,
101 - 'default_open' => false,
102 - 'render_callback' => array($this, 'render_breadcrumbs_section')
103 - ),
104 118 'open_graph' => array(
105 119 'title' => 'Open Graph',
106 120 'description' => 'Control which Open Graph and article meta tags are output',
107 121 'icon' => 'share',
@@ -128,8 +142,9 @@
128 142 'disable_redirection_metabox',
129 143 'disable_canonical_metabox',
130 144 'disable_social_opengraph_metabox',
131 145 'disable_schema_markup_metabox',
146 + 'disable_language_alternates_metabox',
132 147 'disable_seo_metabox',
133 148 'open_external_links'
134 149 )
135 150 ),
@@ -170,13 +185,22 @@
170 185 'fields' => array(
171 186 'permalink_structure',
172 187 'hide_dashboard_framework',
173 188 'show_admin_bar_status',
174 - 'enable_auto_updates',
175 189 'import_external_data'
176 190 )
177 191 )
178 192 );
193 +
194 + // Hidden only when there is nothing to say: no SEO plugin installed, the
195 + // switch off, and no saved originals. It is never hidden by the
196 + // access-control or whitelabel flags that can conceal Advanced Settings
197 + // — a switch that rewrites another plugin's data must stay reachable.
198 + if (!Metasync_Seo_Sync_Settings::should_display()) {
199 + unset($sections['seo_plugin_sync']);
200 + }
201 +
202 + return $sections;
179 203 }
180 204
181 205 public function get_advanced_accordion_config() {
182 206 $config = array();
@@ -201,8 +225,17 @@
201 225 'default_open' => false,
202 226 'render_callback' => array($this->admin_instance, 'render_debug_mode_section')
203 227 );
204 228
229 + $config['headless_mode'] = array(
230 + 'title' => 'Headless Mode',
231 + 'description' => 'Connect WordPress content to a separate public frontend',
232 + 'icon' => 'admin-site-alt3',
233 + 'priority' => 29,
234 + 'default_open' => false,
235 + 'render_callback' => array(Metasync_Settings_Registration::instance(), 'render_headless_mode_section')
236 + );
237 +
205 238 $config['error_logs'] = array(
206 239 'title' => 'Error Logs',
207 240 'description' => 'View and manage error logs to troubleshoot issues',
208 241 'icon' => 'warning',
@@ -275,9 +308,9 @@
275 308 $content_state = $is_open ? 'open' : 'closed';
276 309
277 310 echo '<div class="metasync-accordion-section" data-section="' . esc_attr($section_key) . '">';
278 311
279 - echo '<div class="metasync-accordion-header" role="button" tabindex="0" aria-expanded="' . $aria_expanded . '" aria-controls="' . $section_id . '">';
312 + echo '<div class="metasync-accordion-header" role="button" tabindex="0" aria-expanded="' . esc_attr($aria_expanded) . '" aria-controls="' . esc_attr($section_id) . '">';
280 313 echo '<div class="metasync-accordion-title">';
281 314 echo '<span class="metasync-accordion-icon"><span class="dashicons dashicons-' . esc_attr($section_data['icon']) . '"></span></span>';
282 315 echo '<div class="metasync-accordion-text">';
283 316 echo '<h3>' . esc_html($section_data['title']) . '</h3>';
@@ -288,9 +321,9 @@
288 321 echo '<span class="toggle-icon">▼</span>';
289 322 echo '</button>';
290 323 echo '</div>';
291 324
292 - echo '<div class="metasync-accordion-content" id="' . $section_id . '" data-state="' . $content_state . '">';
325 + echo '<div class="metasync-accordion-content" id="' . esc_attr($section_id) . '" data-state="' . esc_attr($content_state) . '">';
293 326
294 327 if (isset($section_data['render_callback']) && is_callable($section_data['render_callback'])) {
295 328 call_user_func($section_data['render_callback']);
296 329 }
@@ -312,9 +345,8 @@
312 345 </h4>
313 346 <p style="color: var(--dashboard-text-secondary); margin: 0 0 12px 0;">This action will permanently delete:</p>
314 347 <ul style="color: var(--dashboard-text-secondary); margin: 0 0 12px 20px; line-height: 1.8;">
315 348 <li>All API keys and authentication tokens</li>
316 - <li>White label branding settings</li>
317 349 <li>Plugin configuration and preferences</li>
318 350 <li>Instant indexing settings</li>
319 351 <li>Media optimization settings and cache</li>
320 352 <li>Code minification settings</li>
@@ -319,9 +351,9 @@
319 351 <li>Media optimization settings and cache</li>
320 352 <li>Code minification settings</li>
321 353 <li>All cached data and crawl information</li>
322 354 </ul>
323 - <p style="color: var(--dashboard-warning, #f59e0b); margin: 0; font-weight: 600;">You will need to reconfigure the plugin completely after this reset.</p>
355 + <p style="color: var(--dashboard-warning, #f59e0b); margin: 0; font-weight: 600;">You will need to reconfigure the plugin's operational settings after this reset.</p>
324 356 </div>
325 357 <form method="post" action="" onsubmit="return confirmClearSettings(event)">
326 358 <?php wp_nonce_field('metasync_clear_all_settings_nonce', 'clear_all_settings_nonce'); ?>
327 359 <input type="hidden" name="clear_all_settings" value="yes" />
@@ -329,8 +361,31 @@
329 361 <span class="dashicons dashicons-trash" style="margin-top:3px;font-size:15px;width:15px;height:15px;"></span> Clear All Settings
330 362 </button>
331 363 </form>
332 364 </div>
365 + <div style="background: var(--dashboard-card-bg); padding: 20px; border-radius: 8px; margin-top: 20px;">
366 + <h4 style="color: var(--dashboard-text-primary, #1e1e1e); margin: 0 0 12px 0; display: flex; align-items: center; gap: 8px;">
367 + <span class="dashicons dashicons-universal-access-alt" style="font-size:18px;width:18px;height:18px;"></span>
368 + <span>Delete data on uninstall</span>
369 + </h4>
370 + <p style="color: var(--dashboard-text-secondary); margin: 0 0 12px 0;">
371 + 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.
372 + </p>
373 + <p style="color: var(--dashboard-text-secondary); margin: 0 0 12px 0;">
374 + The Google service account and the IndexNow API key are always deleted on uninstall, regardless of this setting.
375 + </p>
376 + <form method="post" action="">
377 + <?php wp_nonce_field('metasync_uninstall_data_toggle_nonce', 'uninstall_data_toggle_nonce'); ?>
378 + <input type="hidden" name="metasync_uninstall_data_toggle_form" value="1" />
379 + <label style="color: var(--dashboard-text-primary, #1e1e1e); display: flex; align-items: center; gap: 8px; margin: 0 0 12px 0; cursor: pointer;">
380 + <input type="checkbox" name="metasync_delete_data_on_uninstall" value="yes" <?php checked(get_option('metasync_delete_data_on_uninstall', 'no'), 'yes'); ?> />
381 + Delete all plugin data (tables, settings, per-post SEO values, logs) when the plugin is uninstalled
382 + </label>
383 + <button type="submit" class="button button-secondary">
384 + Save Uninstall Setting
385 + </button>
386 + </form>
387 + </div>
333 388 <script>
334 389 function confirmClearSettings(event) {
335 390 event.preventDefault();
336 391
@@ -338,9 +393,9 @@
338 393 if (!firstConfirm) {
339 394 return false;
340 395 }
341 396
342 - 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.");
397 + var secondConfirm = confirm("🚨 FINAL WARNING 🚨\n\nThis will delete:\n• All API keys and authentication tokens\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 plugin's operational settings from scratch.\n\nType 'DELETE' in the next prompt to confirm.");
343 398 if (!secondConfirm) {
344 399 return false;
345 400 }
346 401
@@ -361,14 +416,18 @@
361 416 if (!function_exists('google_index_direct')) {
362 417 if (file_exists(plugin_dir_path(dirname(__FILE__)) . 'google-index/google-index-init.php')) {
363 418 require_once plugin_dir_path(dirname(__FILE__)) . 'google-index/google-index-init.php';
364 419 } else {
365 - error_log('MetaSync Google Index: google-index-init.php not found at ' . plugin_dir_path(dirname(__FILE__)) . 'google-index/google-index-init.php');
420 + error_log('MetaSync Google Index: google-index-init.php not found at ' . plugin_dir_path(dirname(__FILE__)) . 'google-index/google-index-init.php'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets
366 421 return;
367 422 }
368 423 }
369 424
370 425 $google_index = google_index_direct();
426 + if (!$google_index) {
427 + // Nothing to render against, and calling through would be fatal.
428 + return;
429 + }
371 430 $service_info = $google_index->get_service_account_info();
372 431 $is_configured = !isset($service_info['error']);
373 432
374 433 $saved_json_display = $is_configured ? $google_index->get_redacted_config_json() : '';
@@ -647,9 +706,9 @@
647 706 <p style="color: var(--dashboard-text-secondary); margin: 0 0 20px 0;">
648 707 Select which user roles can see and access this plugin's menu, settings, and options in the WordPress admin area.
649 708 </p>
650 709
651 - <form method="post" action="<?php echo admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '&tab=advanced'); ?>" id="plugin-access-roles-form">
710 + <form method="post" action="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '&tab=advanced')); ?>" id="plugin-access-roles-form">
652 711 <?php wp_nonce_field('metasync_plugin_access_roles_nonce', 'plugin_access_roles_nonce'); ?>
653 712 <input type="hidden" name="save_plugin_access_roles" value="yes" />
654 713
655 714 <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;">
@@ -767,9 +826,9 @@
767 826
768 827 public function can_change_memory_limit() {
769 828 $current_limit = ini_get('memory_limit');
770 829
771 - $test_result = @ini_set('memory_limit', $current_limit);
830 + $test_result = @ini_set('memory_limit', $current_limit); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- runtime PHP setting with no WordPress API
772 831
773 832 return $test_result !== false;
774 833 }
775 834
@@ -821,9 +880,9 @@
821 880 }
822 881
823 882 $memory_limit_mb = $this->get_execution_setting('max_memory_limit');
824 883
825 - $result = @ini_set('memory_limit', $memory_limit_mb . 'M');
884 + $result = @ini_set('memory_limit', $memory_limit_mb . 'M'); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- runtime PHP setting with no WordPress API
826 885
827 886 return $result !== false;
828 887 }
829 888
@@ -1075,19 +1134,23 @@
1075 1134 '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),
1076 1135 '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),
1077 1136 '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),
1078 1137 '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),
1138 + 'seo_priority' => sprintf('Choose which value appears first on a page when both your custom SEO value (title, description or focus keyword, edited on the post/page screen) and an approved %s suggestion are available. This changes what visitors see; it does not delete or overwrite saved values.', $otto_name),
1079 1139 '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),
1140 + '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),
1141 + '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.',
1080 1142 '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),
1081 1143 '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),
1082 1144 '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),
1083 1145 '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.',
1084 - 'disable_common_robots_metabox' => 'Hide the Common Robots meta box from post and page edit screens. This removes the robots meta tag controls (index/noindex, follow/nofollow) from the editor interface.',
1085 - 'disable_advance_robots_metabox' => 'Hide the Advanced Robots meta box from post and page edit screens. This removes advanced robots directives like max-snippet, max-image-preview, and max-video-preview settings.',
1086 - 'disable_redirection_metabox' => 'Hide the Redirection meta box from post and page edit screens. This removes the URL redirect configuration options from the editor interface.',
1087 - 'disable_canonical_metabox' => 'Hide the Canonical URL meta box from post and page edit screens. This removes the canonical URL override field from the editor interface.',
1088 - 'disable_social_opengraph_metabox' => 'Hide the Social Media & Open Graph meta box from post and page edit screens. This removes Facebook, Twitter, and other social media meta tag controls from the editor.',
1089 - 'disable_schema_markup_metabox' => 'Hide the Schema Markup meta box from post and page edit screens. This removes the structured data (Article, FAQ, Product, Recipe, etc.) configuration from the editor interface.',
1146 + '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')),
1147 + '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')),
1148 + '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.',
1149 + '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')),
1150 + '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')),
1151 + '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')),
1152 + 'disable_language_alternates_metabox' => sprintf('Hide the Language Alternates (hreflang) panel in the block editor and stop %1$s emitting any hreflang tags on the front end, including the auto-detected WPML entries. While disabled, %1$s does not manage or override this data — another SEO plugin, or WPML itself, is free to handle it. Saved values are kept and restored if you re-enable this.', Metasync::get_effective_plugin_name('MetaSync')),
1090 1153 '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).',
1091 1154 '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),
1092 1155 '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.',
1093 1156 '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),
@@ -1092,9 +1155,8 @@
1092 1155 '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.',
1093 1156 '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),
1094 1157 '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),
1095 1158 '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),
1096 - '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),
1097 1159 '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),
1098 1160 '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),
1099 1161 '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.',
1100 1162 '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.',
@@ -1105,8 +1167,35 @@
1105 1167 '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.'
1106 1168 );
1107 1169 }
1108 1170
1171 + public function render_analytics_section() {
1172 + $enabled = get_option('metasync_analytics_opt_in', 'no') === 'yes';
1173 + $plugin_name = Metasync::get_effective_plugin_name();
1174 + /* translators: %s: Plugin name. */
1175 + $heading = sprintf(__('Help improve %s', 'metasync'), $plugin_name);
1176 + /* translators: %s: Plugin name. */
1177 + $intro = sprintf(__('Allow %s to collect limited usage analytics. This helps us understand which features are used, improve the product, and identify problems.', 'metasync'), $plugin_name);
1178 + /* translators: %s: Plugin name. */
1179 + $toggle_label = sprintf(__('Share usage data with %s', 'metasync'), $plugin_name);
1180 + ?>
1181 + <div class="metasync-analytics-consent-card">
1182 + <div class="metasync-analytics-consent-copy">
1183 + <h3><?php echo esc_html($heading); ?></h3>
1184 + <p><?php echo esc_html($intro); ?></p>
1185 + <p class="description"><?php esc_html_e('We collect limited technical information such as your site URL, plugin version, WordPress and PHP versions, user role, and feature usage. We do not collect passwords, API keys, customer content, names, or email addresses.', 'metasync'); ?></p>
1186 + </div>
1187 + <label class="metasync-consent-switch" for="metasync_analytics_opt_in">
1188 + <input type="hidden" name="metasync_analytics_opt_in" value="no" />
1189 + <input type="checkbox" id="metasync_analytics_opt_in" name="metasync_analytics_opt_in" value="yes" <?php checked($enabled); ?> />
1190 + <span class="metasync-consent-slider" aria-hidden="true"></span>
1191 + <span class="screen-reader-text"><?php echo esc_html($toggle_label); ?></span>
1192 + </label>
1193 + <span class="metasync-consent-status" aria-live="polite" data-enabled-label="<?php esc_attr_e('Enabled', 'metasync'); ?>" data-disabled-label="<?php esc_attr_e('Off', 'metasync'); ?>"><?php echo $enabled ? esc_html__('Enabled', 'metasync') : esc_html__('Off', 'metasync'); ?></span>
1194 + </div>
1195 + <?php
1196 + }
1197 +
1109 1198 public function get_field_section($field_id) {
1110 1199 $sections = $this->get_accordion_sections_config();
1111 1200
1112 1201 foreach ($sections as $section_key => $section_data) {
@@ -1140,9 +1229,9 @@
1140 1229 $content_state = $is_open ? 'open' : 'closed';
1141 1230
1142 1231 echo '<div class="metasync-accordion-section" data-section="' . esc_attr($section_key) . '">';
1143 1232
1144 - echo '<div class="metasync-accordion-header" role="button" tabindex="0" aria-expanded="' . $aria_expanded . '" aria-controls="' . $section_id . '">';
1233 + echo '<div class="metasync-accordion-header" role="button" tabindex="0" aria-expanded="' . esc_attr($aria_expanded) . '" aria-controls="' . esc_attr($section_id) . '">';
1145 1234 echo '<div class="metasync-accordion-title">';
1146 1235 echo '<span class="metasync-accordion-icon"><span class="dashicons dashicons-' . esc_attr($section_data['icon']) . '"></span></span>';
1147 1236 echo '<div class="metasync-accordion-text">';
1148 1237 echo '<h3>' . esc_html($section_data['title']) . '</h3>';
@@ -1153,9 +1242,9 @@
1153 1242 echo '<span class="toggle-icon">▼</span>';
1154 1243 echo '</button>';
1155 1244 echo '</div>';
1156 1245
1157 - echo '<div class="metasync-accordion-content" id="' . $section_id . '" data-state="' . $content_state . '">';
1246 + echo '<div class="metasync-accordion-content" id="' . esc_attr($section_id) . '" data-state="' . esc_attr($content_state) . '">';
1158 1247
1159 1248 if (isset($section_data['render_callback']) && is_callable($section_data['render_callback'])) {
1160 1249 call_user_func($section_data['render_callback']);
1161 1250 } elseif (isset($section_data['fields']) && is_array($section_data['fields'])) {
@@ -1170,9 +1259,9 @@
1170 1259 echo '<tr>';
1171 1260 echo '<th scope="row">';
1172 1261 if (!empty($field['title'])) {
1173 1262 echo '<div class="metasync-field-label-wrapper">';
1174 - echo '<label for="' . esc_attr($field['id']) . '">' . $field['title'] . '</label>';
1263 + echo '<label for="' . esc_attr($field['id']) . '">' . esc_html($field['title']) . '</label>';
1175 1264
1176 1265 if (isset($tooltips[$field_id])) {
1177 1266 echo '<button type="button" class="metasync-tooltip-trigger" data-tooltip-id="' . esc_attr($field_id) . '" aria-label="More information">';
1178 1267 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">';
@@ -1227,9 +1316,9 @@
1227 1316 echo '<tr>';
1228 1317 echo '<th scope="row">';
1229 1318 if (!empty($field['title'])) {
1230 1319 echo '<div class="metasync-field-label-wrapper">';
1231 - echo '<label for="' . esc_attr($field['id']) . '">' . $field['title'] . '</label>';
1320 + echo '<label for="' . esc_attr($field['id']) . '">' . esc_html($field['title']) . '</label>';
1232 1321 if (isset($llms_tooltips[$field['id']])) {
1233 1322 Metasync::render_tooltip_icon($field['id'], $llms_tooltips[$field['id']]);
1234 1323 }
1235 1324 echo '</div>';
@@ -1283,32 +1372,32 @@
1283 1372 $display_value = $current_token;
1284 1373 $status_message = 'Plugin Auth Token is active and ready for authentication.';
1285 1374 $refresh_help = 'Click refresh to generate a new token and update the heartbeat API.';
1286 1375 } else {
1287 - $display_value = 'Auto-generated when connecting to ' . esc_html(Metasync::get_effective_plugin_name());
1288 - $status_message = 'Plugin Auth Token will be automatically generated when you click "Connect to ' . esc_html(Metasync::get_effective_plugin_name()) . '".';
1376 + $display_value = 'Auto-generated when connecting to ' . Metasync::get_effective_plugin_name();
1377 + $status_message = 'Plugin Auth Token will be automatically generated when you click "Connect to ' . Metasync::get_effective_plugin_name() . '".';
1289 1378 $refresh_help = 'You can also manually generate a token by clicking refresh.';
1290 1379 }
1291 1380
1292 1381 printf('<div class="metasync-auth-token-row">');
1293 1382 printf(
1294 - '<input type="text" id="apikey" name="' . Metasync_Admin::option_key . '[general][apikey]" value="%s" size="40" class="regular-text" readonly="readonly" />',
1383 + '<input type="text" id="apikey" name="' . esc_attr(Metasync_Admin::option_key) . '[general][apikey]" value="%s" size="40" class="regular-text" readonly="readonly" />',
1295 1384 esc_attr($display_value)
1296 1385 );
1297 1386 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>');
1298 1387 printf('</div>');
1299 - printf('<p class="description">%s %s</p>', $status_message, $refresh_help);
1388 + printf('<p class="description">%s %s</p>', esc_html($status_message), esc_html($refresh_help));
1300 1389 }
1301 1390
1302 1391 public function linkgraph_token_callback()
1303 1392 {
1304 1393 printf(
1305 - '<input type="text" id="linkgraph_token" name="' . Metasync_Admin::option_key . '[general][linkgraph_token]" value="%s" size="25" readonly="readonly" />',
1394 + '<input type="text" id="linkgraph_token" name="' . esc_attr(Metasync_Admin::option_key) . '[general][linkgraph_token]" value="%s" size="25" readonly="readonly" />',
1306 1395 isset(Metasync::get_option('general')['linkgraph_token']) ? esc_attr(Metasync::get_option('general')['linkgraph_token']) : ''
1307 1396 );
1308 1397
1309 1398 printf(
1310 - '<input type="text" id="linkgraph_customer_id" name="' . Metasync_Admin::option_key . '[general][linkgraph_customer_id]" value="%s" size="25" readonly="readonly" />',
1399 + '<input type="text" id="linkgraph_customer_id" name="' . esc_attr(Metasync_Admin::option_key) . '[general][linkgraph_customer_id]" value="%s" size="25" readonly="readonly" />',
1311 1400 isset(Metasync::get_option('general')['linkgraph_customer_id']) ? esc_attr(Metasync::get_option('general')['linkgraph_customer_id']) : ''
1312 1401 );
1313 1402
1314 1403 ?>
@@ -1433,9 +1522,9 @@
1433 1522 printf('• The authentication window will open in a popup - please allow popups<br/>');
1434 1523 printf('• The process typically takes 15-30 seconds to complete<br/>');
1435 1524 printf('• Your API key will be stored securely and shown masked below<br/>');
1436 1525 printf('• If you encounter issues, try disabling ad blockers temporarily<br/>');
1437 - printf('• Contact <a href="mailto:%s">%s</a> if you need assistance', Metasync::SUPPORT_EMAIL, Metasync::SUPPORT_EMAIL);
1526 + printf('• Contact <a href="mailto:%s">%s</a> if you need assistance', esc_attr(Metasync::SUPPORT_EMAIL), esc_html(Metasync::SUPPORT_EMAIL));
1438 1527 printf('</div>');
1439 1528 printf('</details>');
1440 1529 printf('</div>');
1441 1530
@@ -1482,14 +1571,17 @@
1482 1571 printf('</p>');
1483 1572
1484 1573 printf('</div>');
1485 1574
1575 + // The OTTO Pixel UUID is shown once, by the read-only Settings-API
1576 + // field in the OTTO Server-Side Rendering section; do not repeat it here.
1577 +
1486 1578 if( isset(Metasync::get_option('general')['searchatlas_api_key'])&&Metasync::get_option('general')['searchatlas_api_key']!=''){
1487 1579 $timestamp = @Metasync::get_option('general')['send_auth_token_timestamp'];
1488 1580 printf(
1489 1581 '<p id="sendAuthTokenTimestamp" class="descriptionValue">%s (%s)</p>',
1490 1582 esc_attr($timestamp),
1491 - $this->time_elapsed_string($timestamp)
1583 + esc_html($this->time_elapsed_string($timestamp))
1492 1584 );
1493 1585
1494 1586
1495 1587 }
@@ -1497,9 +1589,9 @@
1497 1589
1498 1590 public function bing_site_verification_callback()
1499 1591 {
1500 1592 printf(
1501 - '<input type="text" id="bing_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][bing_site_verification]" value="%s" size="50" />',
1593 + '<input type="text" id="bing_site_verification" name="' . esc_attr(Metasync_Admin::option_key) . '[searchengines][bing_site_verification]" value="%s" size="50" />',
1502 1594 isset(Metasync::get_option('searchengines')['bing_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['bing_site_verification']) : ''
1503 1595 );
1504 1596
1505 1597 printf(' <br> <span class="description"> Enter Bing Webmaster Tools verification code: </span> ');
@@ -1508,9 +1600,9 @@
1508 1600
1509 1601 public function yandex_site_verification_callback()
1510 1602 {
1511 1603 printf(
1512 - '<input type="text" id="yandex_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][yandex_site_verification]" value="%s" size="50" />',
1604 + '<input type="text" id="yandex_site_verification" name="' . esc_attr(Metasync_Admin::option_key) . '[searchengines][yandex_site_verification]" value="%s" size="50" />',
1513 1605 isset(Metasync::get_option('searchengines')['yandex_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['yandex_site_verification']) : ''
1514 1606 );
1515 1607
1516 1608 printf(' <br> <span class="description"> Enter Yandex verification code: </span>');
@@ -1519,9 +1611,9 @@
1519 1611
1520 1612 public function google_site_verification_callback()
1521 1613 {
1522 1614 printf(
1523 - '<input type="text" id="google_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][google_site_verification]" value="%s" size="50" />',
1615 + '<input type="text" id="google_site_verification" name="' . esc_attr(Metasync_Admin::option_key) . '[searchengines][google_site_verification]" value="%s" size="50" />',
1524 1616 isset(Metasync::get_option('searchengines')['google_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['google_site_verification']) : ''
1525 1617 );
1526 1618
1527 1619 printf(' <br> <span class="description"> Enter Google Search Console verification code: </span>');
@@ -1530,9 +1622,9 @@
1530 1622
1531 1623 public function pinterest_site_verification_callback()
1532 1624 {
1533 1625 printf(
1534 - '<input type="text" id="pinterest_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][pinterest_site_verification]" value="%s" size="50" />',
1626 + '<input type="text" id="pinterest_site_verification" name="' . esc_attr(Metasync_Admin::option_key) . '[searchengines][pinterest_site_verification]" value="%s" size="50" />',
1535 1627 isset(Metasync::get_option('searchengines')['pinterest_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['pinterest_site_verification']) : ''
1536 1628 );
1537 1629
1538 1630 printf(' <br> <span class="description"> Enter Pinterest verification code: </span>');
@@ -1541,12 +1633,16 @@
1541 1633
1542 1634 public function local_seo_person_organization_callback()
1543 1635 {
1544 1636 $person_organization = Metasync::get_option('localseo')['local_seo_person_organization'] ?? '';
1637 + $valid_person_organizations = array('Person', 'Organization');
1545 1638 ?>
1546 1639 <div class="metasync-type-select-wrap">
1547 1640 <select id="local_seo_person_organization" name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][local_seo_person_organization]') ?>">
1548 1641 <?php
1642 + if (!in_array($person_organization, $valid_person_organizations, true)) {
1643 + printf('<option value="0" selected="selected">Select Type</option>');
1644 + }
1549 1645 printf('<option value="Person" %s >Person</option>', selected('Person', esc_attr($person_organization)));
1550 1646 printf('<option value="Organization" %s >Organization</option>', selected('Organization', esc_attr($person_organization)));
1551 1647 ?>
1552 1648 </select>
@@ -1557,10 +1653,10 @@
1557 1653
1558 1654 public function local_seo_name_callback()
1559 1655 {
1560 1656 printf(
1561 - '<input type="text" id="local_seo_name" name="' . Metasync_Admin::option_key . '[localseo][local_seo_name]" value="%s" size="50" />',
1562 - isset(Metasync::get_option('localseo')['local_seo_name']) ? esc_attr(Metasync::get_option('localseo')['local_seo_name']) : get_bloginfo()
1657 + '<input type="text" id="local_seo_name" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][local_seo_name]" value="%s" size="50" />',
1658 + isset(Metasync::get_option('localseo')['local_seo_name']) ? esc_attr(Metasync::get_option('localseo')['local_seo_name']) : esc_attr(get_bloginfo())
1563 1659 );
1564 1660
1565 1661 printf(' <br> <span class="description"> Your name or company name </span>');
1566 1662 }
@@ -1568,31 +1664,82 @@
1568 1664 public function local_seo_logo_callback()
1569 1665 {
1570 1666 $local_seo_logo = Metasync::get_option('localseo')['local_seo_logo'] ?? '';
1571 1667
1668 + // Rows saved by the legacy sanitizer hold the corrupted form of
1669 + // an attachment ID ("http://45589"). Repair it before anything reads
1670 + // the value — both so the preview resolves and so the hidden input
1671 + // re-submits the recovered ID instead of the corrupted string, which
1672 + // would otherwise survive every subsequent Save Settings.
1673 + $local_seo_logo = metasync_repair_scheme_prefixed_media_id($local_seo_logo);
1674 +
1572 1675 printf(
1573 - '<input type="hidden" id="local_seo_logo" name="' . Metasync_Admin::option_key . '[localseo][local_seo_logo]" value="%s" size="50" />',
1574 - isset(Metasync::get_option('localseo')['local_seo_logo']) ? esc_attr(Metasync::get_option('localseo')['local_seo_logo']) : ''
1676 + '<input type="hidden" id="local_seo_logo" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][local_seo_logo]" value="%s" size="50" />',
1677 + esc_attr((string) $local_seo_logo)
1575 1678 );
1576 1679
1577 - printf(' <br> <input class="button-secondary" type="button" id="logo_upload_button" value="Add or Upload File">');
1680 + printf(' <br> <input class="button-secondary no-loading" type="button" id="logo_upload_button" value="Add or Upload File">');
1578 1681
1579 1682 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> ');
1580 1683
1581 - printf('<img src="%s" id="local_seo_business_logo" width="300">', wp_get_attachment_image_src($local_seo_logo, 'medium')[0] ?? '');
1684 + printf('<span class="metasync-logo-preview-wrap">');
1582 1685
1583 - $button_type = 'hidden';
1584 - if ($local_seo_logo) {
1585 - $button_type = 'button';
1686 + // The stored value may be an attachment ID or a raw URL, so resolve
1687 + // both the way the front-end schema output already does. Anything that
1688 + // resolves to nothing — a deleted attachment, a legacy value mangled by
1689 + // an earlier save — must render no preview at all, rather than an empty
1690 + // src next to an orphaned remove button.
1691 + $logo_url = $this->resolve_media_url($local_seo_logo, 'medium');
1692 +
1693 + printf(
1694 + '<img src="%s" id="local_seo_business_logo" width="300"%s>',
1695 + esc_url($logo_url),
1696 + $logo_url ? '' : ' style="display:none"'
1697 + );
1698 +
1699 + $button_type = $logo_url ? 'button' : 'hidden';
1700 + printf('<input type="%s" class="button-secondary no-loading metasync-logo-remove-btn" id="local_seo_logo_close_btn" value="X">', esc_attr($button_type));
1701 + printf('</span>');
1702 + }
1703 +
1704 + /**
1705 + * Resolve a stored media setting to a displayable URL.
1706 + *
1707 + * These settings accept either a media-library attachment ID or a raw URL;
1708 + * returns an empty string when the value resolves to neither, so callers
1709 + * can distinguish "no image" from "image we could not resolve".
1710 + *
1711 + * @param mixed $value Attachment ID or URL as stored in the options.
1712 + * @param string $size Registered image size to request for an attachment.
1713 + * @return string Resolved URL, or '' when there is nothing to show.
1714 + */
1715 + private function resolve_media_url($value, $size = 'medium')
1716 + {
1717 + if (empty($value)) {
1718 + return '';
1586 1719 }
1587 - printf('<input type="%s" class="button-secondary" id="local_seo_logo_close_btn" value="X">', $button_type);
1720 +
1721 + // A row saved by the legacy sanitizer stores a corrupted attachment ID ("http://45589"),
1722 + // which is neither resolvable as an ID nor a usable image URL.
1723 + $value = metasync_repair_scheme_prefixed_media_id($value);
1724 +
1725 + if (empty($value)) {
1726 + return '';
1727 + }
1728 +
1729 + if (is_numeric($value)) {
1730 + $url = wp_get_attachment_image_url((int) $value, $size);
1731 + return $url ? $url : '';
1732 + }
1733 +
1734 + return (string) $value;
1588 1735 }
1589 1736
1590 1737 public function local_seo_url_callback()
1591 1738 {
1592 1739 printf(
1593 - '<input type="text" id="local_seo_url" name="' . Metasync_Admin::option_key . '[localseo][local_seo_url]" value="%s" size="50" />',
1594 - isset(Metasync::get_option('localseo')['local_seo_url']) ? esc_attr(Metasync::get_option('localseo')['local_seo_url']) : home_url()
1740 + '<input type="text" id="local_seo_url" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][local_seo_url]" value="%s" size="50" />',
1741 + isset(Metasync::get_option('localseo')['local_seo_url']) ? esc_attr(Metasync::get_option('localseo')['local_seo_url']) : esc_attr(home_url())
1595 1742 );
1596 1743
1597 1744 printf(' <br> <span class="description"> URL of the item. </span>');
1598 1745 }
@@ -1599,9 +1746,9 @@
1599 1746
1600 1747 public function local_seo_email_callback()
1601 1748 {
1602 1749 printf(
1603 - '<input type="text" id="local_seo_email" name="' . Metasync_Admin::option_key . '[localseo][local_seo_email]" value="%s" size="50" />',
1750 + '<input type="text" id="local_seo_email" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][local_seo_email]" value="%s" size="50" />',
1604 1751 isset(Metasync::get_option('localseo')['local_seo_email']) ? esc_attr(Metasync::get_option('localseo')['local_seo_email']) : ''
1605 1752 );
1606 1753
1607 1754 printf(' <br> <span class="description"> Search engines display your email address. </span>');
@@ -1609,9 +1756,9 @@
1609 1756
1610 1757 public function local_seo_phone_callback()
1611 1758 {
1612 1759 printf(
1613 - '<input type="text" id="local_seo_phone" name="' . Metasync_Admin::option_key . '[localseo][local_seo_phone]" value="%s" size="50" />',
1760 + '<input type="text" id="local_seo_phone" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][local_seo_phone]" value="%s" size="50" />',
1614 1761 isset(Metasync::get_option('localseo')['local_seo_phone']) ? esc_attr(Metasync::get_option('localseo')['local_seo_phone']) : ''
1615 1762 );
1616 1763
1617 1764 printf(' <br> <span class="description"> Search engines may prominently display your contact phone number for mobile users. </span>');
@@ -1619,29 +1766,29 @@
1619 1766
1620 1767 public function local_seo_address_callback()
1621 1768 {
1622 1769 printf(
1623 - '<input type="text" id="local_seo_address_street" name="' . Metasync_Admin::option_key . '[localseo][address][street]" value="%s" size="50" placeholder="Street Address"/> <br>',
1770 + '<input type="text" id="local_seo_address_street" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][address][street]" value="%s" size="50" placeholder="Street Address"/> <br>',
1624 1771 isset(Metasync::get_option('localseo')['address']['street']) ? esc_attr(Metasync::get_option('localseo')['address']['street']) : ''
1625 1772 );
1626 1773
1627 1774 printf(
1628 - '<input type="text" id="local_seo_address_locality" name="' . Metasync_Admin::option_key . '[localseo][address][locality]" value="%s" size="50" placeholder="Locality"/> <br>',
1775 + '<input type="text" id="local_seo_address_locality" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][address][locality]" value="%s" size="50" placeholder="Locality"/> <br>',
1629 1776 isset(Metasync::get_option('localseo')['address']['locality']) ? esc_attr(Metasync::get_option('localseo')['address']['locality']) : ''
1630 1777 );
1631 1778
1632 1779 printf(
1633 - '<input type="text" id="local_seo_address_region" name="' . Metasync_Admin::option_key . '[localseo][address][region]" value="%s" size="50" placeholder="Region"/> <br>',
1780 + '<input type="text" id="local_seo_address_region" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][address][region]" value="%s" size="50" placeholder="Region"/> <br>',
1634 1781 isset(Metasync::get_option('localseo')['address']['region']) ? esc_attr(Metasync::get_option('localseo')['address']['region']) : ''
1635 1782 );
1636 1783
1637 1784 printf(
1638 - '<input type="text" id="local_seo_address_postalcode" name="' . Metasync_Admin::option_key . '[localseo][address][postalcode]" value="%s" size="50" placeholder="Postal Code"/> <br>',
1785 + '<input type="text" id="local_seo_address_postalcode" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][address][postalcode]" value="%s" size="50" placeholder="Postal Code"/> <br>',
1639 1786 isset(Metasync::get_option('localseo')['address']['postalcode']) ? esc_attr(Metasync::get_option('localseo')['address']['postalcode']) : ''
1640 1787 );
1641 1788
1642 1789 printf(
1643 - '<input type="text" id="local_seo_address_country" name="' . Metasync_Admin::option_key . '[localseo][address][country]" value="%s" size="50" placeholder="Country"/> <br>',
1790 + '<input type="text" id="local_seo_address_country" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][address][country]" value="%s" size="50" placeholder="Country"/> <br>',
1644 1791 isset(Metasync::get_option('localseo')['address']['country']) ? esc_attr(Metasync::get_option('localseo')['address']['country']) : ''
1645 1792 );
1646 1793 }
1647 1794
@@ -1656,9 +1803,9 @@
1656 1803 <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][local_seo_business_type]') ?>">
1657 1804 <option value='0'>Select Business Type</option>
1658 1805 <?php
1659 1806 foreach ($types as $type) {
1660 - printf('<option value="%s" %s >%s</option>', $type, selected($type, esc_attr($business_type)), $type);
1807 + printf('<option value="%s" %s >%s</option>', esc_attr($type), selected($type, $business_type, false), esc_html($type));
1661 1808 }
1662 1809 ?>
1663 1810 </select>
1664 1811 <?php
@@ -1682,9 +1829,9 @@
1682 1829 <li>
1683 1830 <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][days][]') ?>">
1684 1831 <?php
1685 1832 foreach ($days_name as $name) {
1686 - printf('<option value="%s" %s >%s</option>', $name, selected(esc_attr($name), esc_attr($day_name)), esc_attr($name));
1833 + printf('<option value="%s" %s >%s</option>', esc_attr($name), selected($name, $day_name, false), esc_html($name));
1687 1834 }
1688 1835 ?>
1689 1836 </select>
1690 1837 <input type="text" name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][times][]') ?>" value="<?php echo esc_attr($day_time) ?>">
@@ -1763,9 +1910,9 @@
1763 1910
1764 1911 public function local_seo_price_range_callback()
1765 1912 {
1766 1913 printf(
1767 - '<input type="text" id="local_seo_price_range" name="' . Metasync_Admin::option_key . '[localseo][local_seo_price_range]" value="%s" size="50" />',
1914 + '<input type="text" id="local_seo_price_range" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][local_seo_price_range]" value="%s" size="50" />',
1768 1915 isset(Metasync::get_option('localseo')['local_seo_price_range']) ? esc_attr(Metasync::get_option('localseo')['local_seo_price_range']) : ''
1769 1916 );
1770 1917 printf(' <br> <span class="description"> The price range of the business, for example $$$. </span>');
1771 1918 }
@@ -1778,9 +1925,9 @@
1778 1925 <?php
1779 1926 $about_page = Metasync::get_option('localseo')['local_seo_about_page'] ?? '';
1780 1927 $pages = get_pages();
1781 1928 foreach ($pages as $page) {
1782 - printf('<option value="%s" %s >%s</option>', $page->ID, selected($page->ID, esc_attr($about_page)), $page->post_title);
1929 + printf('<option value="%s" %s >%s</option>', absint($page->ID), selected($page->ID, $about_page, false), esc_html($page->post_title));
1783 1930 }
1784 1931 ?>
1785 1932 </select>
1786 1933 <?php
@@ -1795,9 +1942,9 @@
1795 1942 <?php
1796 1943 $contact_page = Metasync::get_option('localseo')['local_seo_contact_page'] ?? '';
1797 1944 $pages = get_pages();
1798 1945 foreach ($pages as $page) {
1799 - printf('<option value="%s" %s >%s</option>', $page->ID, selected($page->ID, esc_attr($contact_page)), $page->post_title);
1946 + printf('<option value="%s" %s >%s</option>', absint($page->ID), selected($page->ID, $contact_page, false), esc_html($page->post_title));
1800 1947 }
1801 1948 ?>
1802 1949 </select>
1803 1950 <?php
@@ -1806,9 +1953,9 @@
1806 1953
1807 1954 public function local_seo_map_key_callback()
1808 1955 {
1809 1956 printf(
1810 - '<input type="text" id="local_seo_map_key" name="' . Metasync_Admin::option_key . '[localseo][local_seo_map_key]" value="%s" size="50" />',
1957 + '<input type="text" id="local_seo_map_key" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][local_seo_map_key]" value="%s" size="50" />',
1811 1958 isset(Metasync::get_option('localseo')['local_seo_map_key']) ? esc_attr(Metasync::get_option('localseo')['local_seo_map_key']) : ''
1812 1959 );
1813 1960
1814 1961 printf(' <br> <span class="description"> An API Key is required to display embedded Google Maps on your site. </span>');
@@ -1816,9 +1963,9 @@
1816 1963
1817 1964 public function local_seo_geo_coordinates_callback()
1818 1965 {
1819 1966 printf(
1820 - '<input type="text" id="local_seo_geo_coordinates" name="' . Metasync_Admin::option_key . '[localseo][local_seo_geo_coordinates]" value="%s" size="50" />',
1967 + '<input type="text" id="local_seo_geo_coordinates" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][local_seo_geo_coordinates]" value="%s" size="50" />',
1821 1968 isset(Metasync::get_option('localseo')['local_seo_geo_coordinates']) ? esc_attr(Metasync::get_option('localseo')['local_seo_geo_coordinates']) : ''
1822 1969 );
1823 1970
1824 1971 printf(' <br> <span class="description"> Latitude and longitude values separated by comma. </span>');
@@ -1826,9 +1973,9 @@
1826 1973
1827 1974 public function header_snippets_callback()
1828 1975 {
1829 1976 printf(
1830 - '<textarea class="wide-text" id="header_snippets" rows="8" name="' . Metasync_Admin::option_key . '[codesnippets][header_snippet]" >%s</textarea>',
1977 + '<textarea class="wide-text" id="header_snippets" rows="8" name="' . esc_attr(Metasync_Admin::option_key) . '[codesnippets][header_snippet]" >%s</textarea>',
1831 1978 isset(Metasync::get_option('codesnippets')['header_snippet']) ? esc_attr(Metasync::get_option('codesnippets')['header_snippet']) : ''
1832 1979 );
1833 1980 }
1834 1981
@@ -1834,163 +1981,13 @@
1834 1981
1835 1982 public function footer_snippets_callback()
1836 1983 {
1837 1984 printf(
1838 - '<textarea class="wide-text" id="footer_snippets" rows="8" name="' . Metasync_Admin::option_key . '[codesnippets][footer_snippet]" >%s</textarea>',
1985 + '<textarea class="wide-text" id="footer_snippets" rows="8" name="' . esc_attr(Metasync_Admin::option_key) . '[codesnippets][footer_snippet]" >%s</textarea>',
1839 1986 isset(Metasync::get_option('codesnippets')['footer_snippet']) ? esc_attr(Metasync::get_option('codesnippets')['footer_snippet']) : ''
1840 1987 );
1841 1988 }
1842 1989
1843 - public function no_index_posts_callback()
1844 - {
1845 - printf(
1846 - '<input type="checkbox" id="no_index_posts" name="' . Metasync_Admin::option_key . '[optimal_settings][no_index_posts]" value="true" %s />',
1847 - isset(Metasync::get_option('optimal_settings')['no_index_posts']) && Metasync::get_option('optimal_settings')['no_index_posts'] == 'true' ? 'checked' : ''
1848 - );
1849 -
1850 - printf(' <br> <span class="description"> Setting empty archives to <code>noindex</code> is useful for avoiding indexation of thin content pages and dilution of page rank. As soon as a post is added, the page is updated to index. </span>');
1851 - }
1852 -
1853 - public function no_follow_links_callback()
1854 - {
1855 - printf(
1856 - '<input type="checkbox" id="no_follow_links" name="' . Metasync_Admin::option_key . '[optimal_settings][no_follow_links]" value="true" %s />',
1857 - isset(Metasync::get_option('optimal_settings')['no_follow_links']) && Metasync::get_option('optimal_settings')['no_follow_links'] == 'true' ? 'checked' : ''
1858 - );
1859 -
1860 - printf(' <br> <span class="description"> Automatically add <code>rel="nofollow"</code> attribute to external links appearing in your posts, pages, and other post types. The attribute is dynamically applied when the url is displayed</span>');
1861 - }
1862 -
1863 - public function open_external_links_callback()
1864 - {
1865 - printf(
1866 - '<input type="checkbox" id="open_external_links" name="' . Metasync_Admin::option_key . '[optimal_settings][open_external_links]" value="true" %s />',
1867 - isset(Metasync::get_option('optimal_settings')['open_external_links']) && Metasync::get_option('optimal_settings')['open_external_links'] == 'true' ? 'checked' : ''
1868 - );
1869 -
1870 - printf(' <br> <span class="description"> Automatically add <code>target="_blank"</code> attribute to external links appearing in your posts, pages, and other post types. The attribute is applied when the url is displayed.</span>');
1871 - }
1872 -
1873 - public function add_alt_image_tags_callback()
1874 - {
1875 - printf(
1876 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[optimal_settings][add_alt_image_tags]" value="true" %s />',
1877 - isset(Metasync::get_option('optimal_settings')['add_alt_image_tags']) && Metasync::get_option('optimal_settings')['add_alt_image_tags'] == 'true' ? 'checked' : ''
1878 - );
1879 -
1880 - printf(' <br> <span class="description"> Automatically add <code>alt</code> attribute to Image Tags appearing in your posts, pages, and other post types. The attribute is applied when the content is displayed.</span>');
1881 - }
1882 -
1883 - public function add_title_image_tags_callback()
1884 - {
1885 - printf(
1886 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[optimal_settings][add_title_image_tags]" value="true" %s />',
1887 - isset(Metasync::get_option('optimal_settings')['add_title_image_tags']) && Metasync::get_option('optimal_settings')['add_title_image_tags'] == 'true' ? 'checked' : ''
1888 - );
1889 -
1890 - printf(' <br> <span class="description"> Automatically add <code>title</code> attribute to Image Tags appearing in your posts, pages, and other post types. The attribute is applied when the content is displayed.</span>');
1891 - }
1892 -
1893 - public function site_type_callback()
1894 - {
1895 -
1896 - $site_type = Metasync::get_option('optimal_settings')['site_info']['type'] ?? '';
1897 -
1898 - $types = [
1899 - ['name' => 'Personal Blog', 'value' => 'blog'],
1900 - ['name' => 'Community Blog/News Site', 'value' => 'news'],
1901 - ['name' => 'Personal Portfolio', 'value' => 'portfolio'],
1902 - ['name' => 'Small Business Site', 'value' => 'business'],
1903 - ['name' => 'Webshop', 'value' => 'webshop'],
1904 - ['name' => 'Other Personal Website', 'value' => 'otherpersonal'],
1905 - ['name' => 'Other Business Website', 'value' => 'otherbusiness'],
1906 - ];
1907 -
1908 - ?>
1909 - <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[optimal_settings][site_info][type]') ?>" id="site_info_type">
1910 - <?php
1911 - foreach ($types as $type) {
1912 - printf('<option value="%s" %s >%s</option>', esc_attr($type['value']), selected(esc_attr($type['value']), esc_attr($site_type)), ($type['name']));
1913 - }
1914 - ?>
1915 - </select>
1916 - <?php
1917 -
1918 - }
1919 -
1920 - public function site_business_type_callback()
1921 - {
1922 -
1923 - $business_type = Metasync::get_option('optimal_settings')['site_info']['business_type'] ?? '';
1924 -
1925 - $types = self::get_business_types();
1926 - sort($types);
1927 -
1928 - ?>
1929 - <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[optimal_settings][site_info][business_type]') ?>">
1930 - <option value='0'>Select Business Type</option>
1931 - <?php
1932 - foreach ($types as $type) {
1933 - printf('<option value="%s" %s >%s</option>', esc_attr($type), selected(esc_attr($type), esc_attr($business_type)), esc_attr($type));
1934 - }
1935 - ?>
1936 - </select>
1937 - <?php
1938 - }
1939 -
1940 - public function site_company_name_callback()
1941 - {
1942 -
1943 - $company_name = Metasync::get_option('optimal_settings')['site_info']['company_name'] ?? get_bloginfo('name');
1944 -
1945 - printf(
1946 - '<input type="text" name="' . Metasync_Admin::option_key . '[optimal_settings][site_info][company_name]" value="%s" size="50" />',
1947 - $company_name ? $company_name : get_bloginfo('name')
1948 - );
1949 - }
1950 -
1951 - public function site_google_logo_callback()
1952 - {
1953 -
1954 - $google_logo = Metasync::get_option('optimal_settings')['site_info']['google_logo'] ?? '';
1955 -
1956 - printf(
1957 - '<input type="hidden" id="site_google_logo" name="' . Metasync_Admin::option_key . '[optimal_settings][site_info][google_logo]" value="%s" size="50" />',
1958 - $google_logo
1959 - );
1960 -
1961 - printf(' <br> <input class="button-secondary" type="button" id="google_logo_btn" value="Add or Upload File">');
1962 - printf(' <br><br> <span class="description bold"> Min Size: 160X90px, Max Size: 1920X1080px. </span> <br> <span class="description"> A squared image is preferred by the search engines. </span> <br><br> ');
1963 - printf('<img src="%s" id="site_google_logo_img" width="300">', wp_get_attachment_image_src($google_logo, 'medium')[0] ?? '');
1964 -
1965 - $button_type = 'hidden';
1966 - if ($google_logo) {
1967 - $button_type = 'button';
1968 - }
1969 - printf('<input type="%s" class="button-secondary" id="site_google_logo_close_btn" value="X">', $button_type);
1970 - }
1971 -
1972 - public function site_social_share_image_callback()
1973 - {
1974 -
1975 - $social_share_image = Metasync::get_option('optimal_settings')['site_info']['social_share_image'] ?? '';
1976 -
1977 - printf(
1978 - '<input type="hidden" id="site_social_share_image" name="' . Metasync_Admin::option_key . '[optimal_settings][site_info][social_share_image]" value="%s" size="50" />',
1979 - $social_share_image
1980 - );
1981 -
1982 - printf(' <br> <input class="button-secondary" type="button" id="social_share_image_btn" value="Add or Upload File">');
1983 - printf(' <br><br> <span class="description bold"> The recommended image size is 1200 x 630 pixels. </span> <br> <span class="description"> When a featured image or an OpenGraph Image is not set for individual posts/pages/CPTs, this image will be used as a fallback thumbnail when your post is shared on Facebook. </span> <br><br> ');
1984 - printf('<img src="%s" id="site_social_share_img" width="300">', wp_get_attachment_image_src($social_share_image, 'medium')[0] ?? '');
1985 -
1986 - $button_type = 'hidden';
1987 - if ($social_share_image) {
1988 - $button_type = 'button';
1989 - }
1990 - printf('<input type="%s" class="button-secondary" id="site_social_image_close_btn" value="X">', $button_type);
1991 - }
1992 -
1993 1990 public function common_robot_meta_tags_callback()
1994 1991 {
1995 1992 $common_robots_meta = Metasync::get_option('common_robots_meta') ?? Metasync::get_option('common_robots_mata') ?? '';
1996 1993
@@ -1996,16 +1993,8 @@
1996 1993
1997 1994 ?>
1998 1995 <ul class="checkbox-list">
1999 1996 <li>
2000 - <input type="checkbox" name="<?php echo esc_attr(Metasync_Admin::option_key . '[common_robots_meta][index]') ?>" id="robots_common1" value="index" <?php isset($common_robots_meta['index']) ? checked('index', $common_robots_meta['index']) : '' ?>>
2001 - <label for="robots_common1">Index </br>
2002 - <span class="description">
2003 - <span>Search engines to index and show these pages in the search results.</span>
2004 - </span>
2005 - </label>
2006 - </li>
2007 - <li>
2008 1997 <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']) : '' ?>>
2009 1998 <label for="robots_common2">No Index </br>
2010 1999 <span class="description">
2011 2000 <span>Search engines not indexed and displayed this pages in search engine results</span>
@@ -2131,9 +2120,9 @@
2131 2120
2132 2121 public function global_open_graph_meta_callback()
2133 2122 {
2134 2123 printf(
2135 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][open_graph_meta_tags]" value="true" %s />',
2124 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][open_graph_meta_tags]" value="true" %s />',
2136 2125 isset(Metasync::get_option('common_meta_settings')['open_graph_meta_tags']) && Metasync::get_option('common_meta_settings')['open_graph_meta_tags'] == 'true' ? 'checked' : ''
2137 2126 );
2138 2127 printf(' <br> <span class="description"> Automatically add the Open Graph meta tags in a page or post.</span>');
2139 2128 }
@@ -2140,9 +2129,9 @@
2140 2129
2141 2130 public function global_facebook_meta_callback()
2142 2131 {
2143 2132 printf(
2144 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][facebook_meta_tags]" value="true" %s />',
2133 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][facebook_meta_tags]" value="true" %s />',
2145 2134 isset(Metasync::get_option('common_meta_settings')['facebook_meta_tags']) && Metasync::get_option('common_meta_settings')['facebook_meta_tags'] == 'true' ? 'checked' : ''
2146 2135 );
2147 2136 printf(' <br> <span class="description"> Automatically add the Facebook meta tags in a page or post.</span>');
2148 2137 }
@@ -2149,9 +2138,9 @@
2149 2138
2150 2139 public function global_twitter_meta_callback()
2151 2140 {
2152 2141 printf(
2153 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][twitter_meta_tags]" value="true" %s />',
2142 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][twitter_meta_tags]" value="true" %s />',
2154 2143 isset(Metasync::get_option('common_meta_settings')['twitter_meta_tags']) && Metasync::get_option('common_meta_settings')['twitter_meta_tags'] == 'true' ? 'checked' : ''
2155 2144 );
2156 2145 printf(' <br> <span class="description"> Automatically add the Twitter meta tags in a page or post.</span>');
2157 2146 }
@@ -2159,9 +2148,9 @@
2159 2148 public function og_image_dimensions_callback()
2160 2149 {
2161 2150 $val = Metasync::get_option('common_meta_settings')['og_image_dimensions'] ?? 'true';
2162 2151 printf(
2163 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][og_image_dimensions]" value="true" %s />',
2152 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][og_image_dimensions]" value="true" %s />',
2164 2153 $val === 'true' ? 'checked' : ''
2165 2154 );
2166 2155 printf('<span class="description"> Output og:image:width, og:image:height, and og:image:type when OG image metadata is available.</span>');
2167 2156 }
@@ -2169,9 +2158,9 @@
2169 2158 public function article_timestamps_callback()
2170 2159 {
2171 2160 $val = Metasync::get_option('common_meta_settings')['article_timestamps'] ?? 'true';
2172 2161 printf(
2173 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_timestamps]" value="true" %s />',
2162 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][article_timestamps]" value="true" %s />',
2174 2163 $val === 'true' ? 'checked' : ''
2175 2164 );
2176 2165 printf('<span class="description"> Output article:published_time and article:modified_time from the post publish and modified dates (ISO 8601).</span>');
2177 2166 }
@@ -2179,9 +2168,9 @@
2179 2168 public function article_author_callback()
2180 2169 {
2181 2170 $val = Metasync::get_option('common_meta_settings')['article_author'] ?? 'true';
2182 2171 printf(
2183 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_author]" value="true" %s />',
2172 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][article_author]" value="true" %s />',
2184 2173 $val === 'true' ? 'checked' : ''
2185 2174 );
2186 2175 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>');
2187 2176 }
@@ -2189,9 +2178,9 @@
2189 2178 public function article_section_callback()
2190 2179 {
2191 2180 $val = Metasync::get_option('common_meta_settings')['article_section'] ?? 'true';
2192 2181 printf(
2193 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_section]" value="true" %s />',
2182 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][article_section]" value="true" %s />',
2194 2183 $val === 'true' ? 'checked' : ''
2195 2184 );
2196 2185 printf('<span class="description"> Output article:section from the primary category (falls back to first category).</span>');
2197 2186 }
@@ -2199,9 +2188,9 @@
2199 2188 public function article_tags_callback()
2200 2189 {
2201 2190 $val = Metasync::get_option('common_meta_settings')['article_tags'] ?? 'true';
2202 2191 printf(
2203 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_tags]" value="true" %s />',
2192 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][article_tags]" value="true" %s />',
2204 2193 $val === 'true' ? 'checked' : ''
2205 2194 );
2206 2195 printf('<span class="description"> Output one article:tag meta tag per WordPress post tag.</span>');
2207 2196 }
@@ -2209,9 +2198,9 @@
2209 2198 public function twitter_image_alt_callback()
2210 2199 {
2211 2200 $val = Metasync::get_option('common_meta_settings')['twitter_image_alt'] ?? 'true';
2212 2201 printf(
2213 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][twitter_image_alt]" value="true" %s />',
2202 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][twitter_image_alt]" value="true" %s />',
2214 2203 $val === 'true' ? 'checked' : ''
2215 2204 );
2216 2205 printf('<span class="description"> Output twitter:image:alt using the stored alt text or the OG image attachment alt attribute.</span>');
2217 2206 }
@@ -2218,9 +2207,9 @@
2218 2207
2219 2208 public function facebook_page_url_callback()
2220 2209 {
2221 2210 $facebook_page_url = Metasync::get_option('social_meta')['facebook_page_url'] ?? '';
2222 - printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_page_url]" value="%s" size="50" />', esc_attr($facebook_page_url));
2211 + printf('<input type="text" name="' . esc_attr(Metasync_Admin::option_key) . '[social_meta][facebook_page_url]" value="%s" size="50" />', esc_attr($facebook_page_url));
2223 2212 printf('<br><span class="description"> Enter your Facebook page URL. eg: <code>https://www.facebook.com/MetaSync/</code> </span>');
2224 2213 }
2225 2214
2226 2215 public function facebook_authorship_callback()
@@ -2225,9 +2214,9 @@
2225 2214
2226 2215 public function facebook_authorship_callback()
2227 2216 {
2228 2217 $facebook_authorship = Metasync::get_option('social_meta')['facebook_authorship'] ?? '';
2229 - printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_authorship]" value="%s" size="50" />', esc_attr($facebook_authorship));
2218 + printf('<input type="text" name="' . esc_attr(Metasync_Admin::option_key) . '[social_meta][facebook_authorship]" value="%s" size="50" />', esc_attr($facebook_authorship));
2230 2219 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>');
2231 2220 }
2232 2221
2233 2222 public function facebook_admin_callback()
@@ -2232,9 +2221,9 @@
2232 2221
2233 2222 public function facebook_admin_callback()
2234 2223 {
2235 2224 $facebook_admin = Metasync::get_option('social_meta')['facebook_admin'] ?? '';
2236 - printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_admin]" value="%s" size="50" />', esc_attr($facebook_admin));
2225 + printf('<input type="text" name="' . esc_attr(Metasync_Admin::option_key) . '[social_meta][facebook_admin]" value="%s" size="50" />', esc_attr($facebook_admin));
2237 2226 printf(' <br> <span class="description"> Enter numeric user ID of Facebook. </span>');
2238 2227 }
2239 2228
2240 2229 public function facebook_app_callback()
@@ -2239,9 +2228,9 @@
2239 2228
2240 2229 public function facebook_app_callback()
2241 2230 {
2242 2231 $facebook_app = Metasync::get_option('social_meta')['facebook_app'] ?? '';
2243 - printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_app]" value="%s" size="50" />', esc_attr($facebook_app));
2232 + printf('<input type="text" name="' . esc_attr(Metasync_Admin::option_key) . '[social_meta][facebook_app]" value="%s" size="50" />', esc_attr($facebook_app));
2244 2233 printf(' <br> <span class="description"> Enter numeric app ID of Facebook </span>');
2245 2234 }
2246 2235
2247 2236 public function facebook_secret_callback()
@@ -2246,9 +2235,9 @@
2246 2235
2247 2236 public function facebook_secret_callback()
2248 2237 {
2249 2238 $facebook_secret = Metasync::get_option('social_meta')['facebook_secret'] ?? '';
2250 - printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][facebook_secret]" value="%s" size="50" />', esc_attr($facebook_secret));
2239 + printf('<input type="text" name="' . esc_attr(Metasync_Admin::option_key) . '[social_meta][facebook_secret]" value="%s" size="50" />', esc_attr($facebook_secret));
2251 2240 printf(' <br> <span class="description"> Enter alphanumeric access token from Facebook. </span>');
2252 2241 }
2253 2242
2254 2243 public function twitter_username_callback()
@@ -2253,9 +2242,9 @@
2253 2242
2254 2243 public function twitter_username_callback()
2255 2244 {
2256 2245 $twitter_username = Metasync::get_option('social_meta')['twitter_username'] ?? '';
2257 - printf('<input type="text" name="' . Metasync_Admin::option_key . '[social_meta][twitter_username]" value="%s" size="50" />', esc_attr($twitter_username));
2246 + printf('<input type="text" name="' . esc_attr(Metasync_Admin::option_key) . '[social_meta][twitter_username]" value="%s" size="50" />', esc_attr($twitter_username));
2258 2247 printf(' <br> <span class="description"> Twitter username of the author to add <code>twitter:creator</code> tag to post. eg: <code>MetaSync</code> </span>');
2259 2248 }
2260 2249
2261 2250 public static function get_business_types()
@@ -2430,8 +2419,14 @@
2430 2419 }
2431 2420
2432 2421 /**
2433 2422 * Render breadcrumbs settings section
2423 + *
2424 + * Rendered by the dedicated Breadcrumbs admin page. The three checkbox
2425 + * fields each ship a hidden companion input so their keys are always
2426 + * present in the submitted payload, which lets the save handlers treat an
2427 + * absent key as "this form does not manage that field" instead of
2428 + * "unchecked".
2434 2429 */
2435 2430 public function render_breadcrumbs_section() {
2436 2431 $options = Metasync::get_option('breadcrumbs', array());
2437 2432
@@ -2436,9 +2431,9 @@
2436 2431 $options = Metasync::get_option('breadcrumbs', array());
2437 2432
2438 2433 $defaults = array(
2439 2434 'enabled' => true,
2440 - 'separator' => '&raquo;',
2435 + 'separator' => '»',
2441 2436 'home_label' => 'Home',
2442 2437 'home_url' => '',
2443 2438 'show_current_page' => true,
2444 2439 'prefix_text' => '',
@@ -2446,8 +2441,19 @@
2446 2441 'disable_schema' => false,
2447 2442 );
2448 2443
2449 2444 $options = wp_parse_args($options, $defaults);
2445 +
2446 + # Older builds stored the separator as an HTML entity (e.g. "&raquo;").
2447 + # Decode before matching so the saved choice still lights up its radio.
2448 + $separator_presets = array('»', '/', '>', '·');
2449 + $separator_current = html_entity_decode((string) $options['separator'], ENT_QUOTES, 'UTF-8');
2450 + if ($separator_current === '') {
2451 + $separator_current = '»';
2452 + }
2453 + # A separator that predates the radio picker (or came from the old free
2454 + # text field) gets its own option so it stays selected and round-trips.
2455 + $separator_custom = in_array($separator_current, $separator_presets, true) ? '' : $separator_current;
2450 2456 ?>
2451 2457 <div style="background: var(--dashboard-card-bg); padding: 20px; border-radius: 8px;">
2452 2458
2453 2459 <!-- Enable/Disable -->
@@ -2452,8 +2458,9 @@
2452 2458
2453 2459 <!-- Enable/Disable -->
2454 2460 <div style="margin-bottom: 24px;">
2455 2461 <label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
2462 + <input type="hidden" name="metasync_options[breadcrumbs][enabled]" value="0">
2456 2463 <input type="checkbox"
2457 2464 name="metasync_options[breadcrumbs][enabled]"
2458 2465 value="1"
2459 2466 <?php checked($options['enabled'], 1); ?>
@@ -2468,8 +2475,9 @@
2468 2475
2469 2476 <!-- Disable Schema Markup -->
2470 2477 <div style="margin-bottom: 24px;">
2471 2478 <label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
2479 + <input type="hidden" name="metasync_options[breadcrumbs][disable_schema]" value="0">
2472 2480 <input type="checkbox"
2473 2481 name="metasync_options[breadcrumbs][disable_schema]"
2474 2482 value="1"
2475 2483 <?php checked($options['disable_schema'], 1); ?>
@@ -2487,24 +2495,21 @@
2487 2495 <label for="breadcrumb_separator" style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--dashboard-text);">
2488 2496 Separator Character
2489 2497 </label>
2490 2498 <div style="display: flex; gap: 12px; flex-wrap: wrap;">
2499 + <?php foreach ($separator_presets as $separator_option) : ?>
2491 2500 <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
2492 - <input type="radio" name="metasync_options[breadcrumbs][separator]" value="»" <?php checked($options['separator'], '»'); ?>>
2493 - <span style="color: var(--dashboard-text);">»</span>
2501 + <input type="radio" name="metasync_options[breadcrumbs][separator]" value="<?php echo esc_attr($separator_option); ?>" <?php checked($separator_current, $separator_option); ?>>
2502 + <span style="color: var(--dashboard-text);"><?php echo esc_html($separator_option); ?></span>
2494 2503 </label>
2495 - <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
2496 - <input type="radio" name="metasync_options[breadcrumbs][separator]" value="/" <?php checked($options['separator'], '/'); ?>>
2497 - <span style="color: var(--dashboard-text);">/</span>
2498 - </label>
2499 - <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
2500 - <input type="radio" name="metasync_options[breadcrumbs][separator]" value=">" <?php checked($options['separator'], '>'); ?>>
2501 - <span style="color: var(--dashboard-text);">></span>
2502 - </label>
2503 - <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
2504 - <input type="radio" name="metasync_options[breadcrumbs][separator]" value="·" <?php checked($options['separator'], '·'); ?>>
2505 - <span style="color: var(--dashboard-text);">·</span>
2506 - </label>
2504 + <?php endforeach; ?>
2505 + <div style="display: flex; align-items: center; gap: 8px;">
2506 + <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
2507 + <input type="radio" id="metasync-breadcrumb-separator-custom" name="metasync_options[breadcrumbs][separator]" value="<?php echo esc_attr($separator_custom); ?>" <?php checked($separator_custom !== ''); ?>>
2508 + <span style="color: var(--dashboard-text);">Custom</span>
2509 + </label>
2510 + <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;">
2511 + </div>
2507 2512 </div>
2508 2513 <p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);">
2509 2514 Choose how to separate breadcrumb items
2510 2515 </p>
@@ -2544,8 +2549,9 @@
2544 2549
2545 2550 <!-- Show Current Page -->
2546 2551 <div style="margin-bottom: 24px;">
2547 2552 <label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
2553 + <input type="hidden" name="metasync_options[breadcrumbs][show_current_page]" value="0">
2548 2554 <input type="checkbox"
2549 2555 name="metasync_options[breadcrumbs][show_current_page]"
2550 2556 value="1"
2551 2557 <?php checked($options['show_current_page'], 1); ?>