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 +173 -91 2.6.25 → 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,11 +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',
73 - 'otto_sg_optimizer_compat',
74 - 'bookingpress_session_compat',
86 + 'seo_priority',
75 87 )
76 88 ),
77 89 'edge_cache' => array(
78 90 'title' => 'Edge Cache / CDN',
@@ -81,8 +93,16 @@
81 93 'priority' => 22,
82 94 'default_open' => false,
83 95 'render_callback' => array('Metasync_Edge_Cache_Settings', 'render'),
84 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 + ),
85 105 'bot_detection' => array(
86 106 'title' => 'Bot Detection & Filtering',
87 107 'description' => 'Manage bot traffic and reduce unnecessary API calls',
88 108 'icon' => 'filter',
@@ -94,16 +114,8 @@
94 114 'otto_bot_blacklist',
95 115 'otto_bot_statistics_link'
96 116 )
97 117 ),
98 - 'breadcrumbs' => array(
99 - 'title' => 'Breadcrumbs',
100 - 'description' => 'Configure breadcrumb navigation display',
101 - 'icon' => 'format-links',
102 - 'priority' => 35,
103 - 'default_open' => false,
104 - 'render_callback' => array($this, 'render_breadcrumbs_section')
105 - ),
106 118 'open_graph' => array(
107 119 'title' => 'Open Graph',
108 120 'description' => 'Control which Open Graph and article meta tags are output',
109 121 'icon' => 'share',
@@ -130,8 +142,9 @@
130 142 'disable_redirection_metabox',
131 143 'disable_canonical_metabox',
132 144 'disable_social_opengraph_metabox',
133 145 'disable_schema_markup_metabox',
146 + 'disable_language_alternates_metabox',
134 147 'disable_seo_metabox',
135 148 'open_external_links'
136 149 )
137 150 ),
@@ -172,13 +185,22 @@
172 185 'fields' => array(
173 186 'permalink_structure',
174 187 'hide_dashboard_framework',
175 188 'show_admin_bar_status',
176 - 'enable_auto_updates',
177 189 'import_external_data'
178 190 )
179 191 )
180 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;
181 203 }
182 204
183 205 public function get_advanced_accordion_config() {
184 206 $config = array();
@@ -203,8 +225,17 @@
203 225 'default_open' => false,
204 226 'render_callback' => array($this->admin_instance, 'render_debug_mode_section')
205 227 );
206 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 +
207 238 $config['error_logs'] = array(
208 239 'title' => 'Error Logs',
209 240 'description' => 'View and manage error logs to troubleshoot issues',
210 241 'icon' => 'warning',
@@ -277,9 +308,9 @@
277 308 $content_state = $is_open ? 'open' : 'closed';
278 309
279 310 echo '<div class="metasync-accordion-section" data-section="' . esc_attr($section_key) . '">';
280 311
281 - 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) . '">';
282 313 echo '<div class="metasync-accordion-title">';
283 314 echo '<span class="metasync-accordion-icon"><span class="dashicons dashicons-' . esc_attr($section_data['icon']) . '"></span></span>';
284 315 echo '<div class="metasync-accordion-text">';
285 316 echo '<h3>' . esc_html($section_data['title']) . '</h3>';
@@ -290,9 +321,9 @@
290 321 echo '<span class="toggle-icon">▼</span>';
291 322 echo '</button>';
292 323 echo '</div>';
293 324
294 - 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) . '">';
295 326
296 327 if (isset($section_data['render_callback']) && is_callable($section_data['render_callback'])) {
297 328 call_user_func($section_data['render_callback']);
298 329 }
@@ -314,9 +345,8 @@
314 345 </h4>
315 346 <p style="color: var(--dashboard-text-secondary); margin: 0 0 12px 0;">This action will permanently delete:</p>
316 347 <ul style="color: var(--dashboard-text-secondary); margin: 0 0 12px 20px; line-height: 1.8;">
317 348 <li>All API keys and authentication tokens</li>
318 - <li>White label branding settings</li>
319 349 <li>Plugin configuration and preferences</li>
320 350 <li>Instant indexing settings</li>
321 351 <li>Media optimization settings and cache</li>
322 352 <li>Code minification settings</li>
@@ -321,9 +351,9 @@
321 351 <li>Media optimization settings and cache</li>
322 352 <li>Code minification settings</li>
323 353 <li>All cached data and crawl information</li>
324 354 </ul>
325 - <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>
326 356 </div>
327 357 <form method="post" action="" onsubmit="return confirmClearSettings(event)">
328 358 <?php wp_nonce_field('metasync_clear_all_settings_nonce', 'clear_all_settings_nonce'); ?>
329 359 <input type="hidden" name="clear_all_settings" value="yes" />
@@ -363,9 +393,9 @@
363 393 if (!firstConfirm) {
364 394 return false;
365 395 }
366 396
367 - 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.");
368 398 if (!secondConfirm) {
369 399 return false;
370 400 }
371 401
@@ -386,14 +416,18 @@
386 416 if (!function_exists('google_index_direct')) {
387 417 if (file_exists(plugin_dir_path(dirname(__FILE__)) . 'google-index/google-index-init.php')) {
388 418 require_once plugin_dir_path(dirname(__FILE__)) . 'google-index/google-index-init.php';
389 419 } else {
390 - 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
391 421 return;
392 422 }
393 423 }
394 424
395 425 $google_index = google_index_direct();
426 + if (!$google_index) {
427 + // Nothing to render against, and calling through would be fatal.
428 + return;
429 + }
396 430 $service_info = $google_index->get_service_account_info();
397 431 $is_configured = !isset($service_info['error']);
398 432
399 433 $saved_json_display = $is_configured ? $google_index->get_redacted_config_json() : '';
@@ -672,9 +706,9 @@
672 706 <p style="color: var(--dashboard-text-secondary); margin: 0 0 20px 0;">
673 707 Select which user roles can see and access this plugin's menu, settings, and options in the WordPress admin area.
674 708 </p>
675 709
676 - <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">
677 711 <?php wp_nonce_field('metasync_plugin_access_roles_nonce', 'plugin_access_roles_nonce'); ?>
678 712 <input type="hidden" name="save_plugin_access_roles" value="yes" />
679 713
680 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;">
@@ -792,9 +826,9 @@
792 826
793 827 public function can_change_memory_limit() {
794 828 $current_limit = ini_get('memory_limit');
795 829
796 - $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
797 831
798 832 return $test_result !== false;
799 833 }
800 834
@@ -846,9 +880,9 @@
846 880 }
847 881
848 882 $memory_limit_mb = $this->get_execution_setting('max_memory_limit');
849 883
850 - $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
851 885
852 886 return $result !== false;
853 887 }
854 888
@@ -1100,8 +1134,9 @@
1100 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),
1101 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),
1102 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),
1103 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),
1104 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),
1105 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),
1106 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.',
1107 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),
@@ -1113,8 +1148,9 @@
1113 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.',
1114 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')),
1115 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')),
1116 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')),
1117 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).',
1118 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),
1119 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.',
1120 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),
@@ -1119,9 +1155,8 @@
1119 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.',
1120 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),
1121 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),
1122 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),
1123 - '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),
1124 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),
1125 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),
1126 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.',
1127 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.',
@@ -1132,8 +1167,35 @@
1132 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.'
1133 1168 );
1134 1169 }
1135 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 +
1136 1198 public function get_field_section($field_id) {
1137 1199 $sections = $this->get_accordion_sections_config();
1138 1200
1139 1201 foreach ($sections as $section_key => $section_data) {
@@ -1167,9 +1229,9 @@
1167 1229 $content_state = $is_open ? 'open' : 'closed';
1168 1230
1169 1231 echo '<div class="metasync-accordion-section" data-section="' . esc_attr($section_key) . '">';
1170 1232
1171 - 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) . '">';
1172 1234 echo '<div class="metasync-accordion-title">';
1173 1235 echo '<span class="metasync-accordion-icon"><span class="dashicons dashicons-' . esc_attr($section_data['icon']) . '"></span></span>';
1174 1236 echo '<div class="metasync-accordion-text">';
1175 1237 echo '<h3>' . esc_html($section_data['title']) . '</h3>';
@@ -1180,9 +1242,9 @@
1180 1242 echo '<span class="toggle-icon">▼</span>';
1181 1243 echo '</button>';
1182 1244 echo '</div>';
1183 1245
1184 - 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) . '">';
1185 1247
1186 1248 if (isset($section_data['render_callback']) && is_callable($section_data['render_callback'])) {
1187 1249 call_user_func($section_data['render_callback']);
1188 1250 } elseif (isset($section_data['fields']) && is_array($section_data['fields'])) {
@@ -1197,9 +1259,9 @@
1197 1259 echo '<tr>';
1198 1260 echo '<th scope="row">';
1199 1261 if (!empty($field['title'])) {
1200 1262 echo '<div class="metasync-field-label-wrapper">';
1201 - echo '<label for="' . esc_attr($field['id']) . '">' . $field['title'] . '</label>';
1263 + echo '<label for="' . esc_attr($field['id']) . '">' . esc_html($field['title']) . '</label>';
1202 1264
1203 1265 if (isset($tooltips[$field_id])) {
1204 1266 echo '<button type="button" class="metasync-tooltip-trigger" data-tooltip-id="' . esc_attr($field_id) . '" aria-label="More information">';
1205 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">';
@@ -1254,9 +1316,9 @@
1254 1316 echo '<tr>';
1255 1317 echo '<th scope="row">';
1256 1318 if (!empty($field['title'])) {
1257 1319 echo '<div class="metasync-field-label-wrapper">';
1258 - echo '<label for="' . esc_attr($field['id']) . '">' . $field['title'] . '</label>';
1320 + echo '<label for="' . esc_attr($field['id']) . '">' . esc_html($field['title']) . '</label>';
1259 1321 if (isset($llms_tooltips[$field['id']])) {
1260 1322 Metasync::render_tooltip_icon($field['id'], $llms_tooltips[$field['id']]);
1261 1323 }
1262 1324 echo '</div>';
@@ -1310,32 +1372,32 @@
1310 1372 $display_value = $current_token;
1311 1373 $status_message = 'Plugin Auth Token is active and ready for authentication.';
1312 1374 $refresh_help = 'Click refresh to generate a new token and update the heartbeat API.';
1313 1375 } else {
1314 - $display_value = 'Auto-generated when connecting to ' . esc_html(Metasync::get_effective_plugin_name());
1315 - $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() . '".';
1316 1378 $refresh_help = 'You can also manually generate a token by clicking refresh.';
1317 1379 }
1318 1380
1319 1381 printf('<div class="metasync-auth-token-row">');
1320 1382 printf(
1321 - '<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" />',
1322 1384 esc_attr($display_value)
1323 1385 );
1324 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>');
1325 1387 printf('</div>');
1326 - 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));
1327 1389 }
1328 1390
1329 1391 public function linkgraph_token_callback()
1330 1392 {
1331 1393 printf(
1332 - '<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" />',
1333 1395 isset(Metasync::get_option('general')['linkgraph_token']) ? esc_attr(Metasync::get_option('general')['linkgraph_token']) : ''
1334 1396 );
1335 1397
1336 1398 printf(
1337 - '<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" />',
1338 1400 isset(Metasync::get_option('general')['linkgraph_customer_id']) ? esc_attr(Metasync::get_option('general')['linkgraph_customer_id']) : ''
1339 1401 );
1340 1402
1341 1403 ?>
@@ -1460,9 +1522,9 @@
1460 1522 printf('• The authentication window will open in a popup - please allow popups<br/>');
1461 1523 printf('• The process typically takes 15-30 seconds to complete<br/>');
1462 1524 printf('• Your API key will be stored securely and shown masked below<br/>');
1463 1525 printf('• If you encounter issues, try disabling ad blockers temporarily<br/>');
1464 - 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));
1465 1527 printf('</div>');
1466 1528 printf('</details>');
1467 1529 printf('</div>');
1468 1530
@@ -1509,14 +1571,17 @@
1509 1571 printf('</p>');
1510 1572
1511 1573 printf('</div>');
1512 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 +
1513 1578 if( isset(Metasync::get_option('general')['searchatlas_api_key'])&&Metasync::get_option('general')['searchatlas_api_key']!=''){
1514 1579 $timestamp = @Metasync::get_option('general')['send_auth_token_timestamp'];
1515 1580 printf(
1516 1581 '<p id="sendAuthTokenTimestamp" class="descriptionValue">%s (%s)</p>',
1517 1582 esc_attr($timestamp),
1518 - $this->time_elapsed_string($timestamp)
1583 + esc_html($this->time_elapsed_string($timestamp))
1519 1584 );
1520 1585
1521 1586
1522 1587 }
@@ -1524,9 +1589,9 @@
1524 1589
1525 1590 public function bing_site_verification_callback()
1526 1591 {
1527 1592 printf(
1528 - '<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" />',
1529 1594 isset(Metasync::get_option('searchengines')['bing_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['bing_site_verification']) : ''
1530 1595 );
1531 1596
1532 1597 printf(' <br> <span class="description"> Enter Bing Webmaster Tools verification code: </span> ');
@@ -1535,9 +1600,9 @@
1535 1600
1536 1601 public function yandex_site_verification_callback()
1537 1602 {
1538 1603 printf(
1539 - '<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" />',
1540 1605 isset(Metasync::get_option('searchengines')['yandex_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['yandex_site_verification']) : ''
1541 1606 );
1542 1607
1543 1608 printf(' <br> <span class="description"> Enter Yandex verification code: </span>');
@@ -1546,9 +1611,9 @@
1546 1611
1547 1612 public function google_site_verification_callback()
1548 1613 {
1549 1614 printf(
1550 - '<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" />',
1551 1616 isset(Metasync::get_option('searchengines')['google_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['google_site_verification']) : ''
1552 1617 );
1553 1618
1554 1619 printf(' <br> <span class="description"> Enter Google Search Console verification code: </span>');
@@ -1557,9 +1622,9 @@
1557 1622
1558 1623 public function pinterest_site_verification_callback()
1559 1624 {
1560 1625 printf(
1561 - '<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" />',
1562 1627 isset(Metasync::get_option('searchengines')['pinterest_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['pinterest_site_verification']) : ''
1563 1628 );
1564 1629
1565 1630 printf(' <br> <span class="description"> Enter Pinterest verification code: </span>');
@@ -1588,10 +1653,10 @@
1588 1653
1589 1654 public function local_seo_name_callback()
1590 1655 {
1591 1656 printf(
1592 - '<input type="text" id="local_seo_name" name="' . Metasync_Admin::option_key . '[localseo][local_seo_name]" value="%s" size="50" />',
1593 - 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())
1594 1659 );
1595 1660
1596 1661 printf(' <br> <span class="description"> Your name or company name </span>');
1597 1662 }
@@ -1607,9 +1672,9 @@
1607 1672 // would otherwise survive every subsequent Save Settings.
1608 1673 $local_seo_logo = metasync_repair_scheme_prefixed_media_id($local_seo_logo);
1609 1674
1610 1675 printf(
1611 - '<input type="hidden" id="local_seo_logo" name="' . Metasync_Admin::option_key . '[localseo][local_seo_logo]" value="%s" size="50" />',
1676 + '<input type="hidden" id="local_seo_logo" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][local_seo_logo]" value="%s" size="50" />',
1612 1677 esc_attr((string) $local_seo_logo)
1613 1678 );
1614 1679
1615 1680 printf(' <br> <input class="button-secondary no-loading" type="button" id="logo_upload_button" value="Add or Upload File">');
@@ -1631,9 +1696,9 @@
1631 1696 $logo_url ? '' : ' style="display:none"'
1632 1697 );
1633 1698
1634 1699 $button_type = $logo_url ? 'button' : 'hidden';
1635 - printf('<input type="%s" class="button-secondary no-loading metasync-logo-remove-btn" id="local_seo_logo_close_btn" value="X">', $button_type);
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));
1636 1701 printf('</span>');
1637 1702 }
1638 1703
1639 1704 /**
@@ -1671,10 +1736,10 @@
1671 1736
1672 1737 public function local_seo_url_callback()
1673 1738 {
1674 1739 printf(
1675 - '<input type="text" id="local_seo_url" name="' . Metasync_Admin::option_key . '[localseo][local_seo_url]" value="%s" size="50" />',
1676 - 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())
1677 1742 );
1678 1743
1679 1744 printf(' <br> <span class="description"> URL of the item. </span>');
1680 1745 }
@@ -1681,9 +1746,9 @@
1681 1746
1682 1747 public function local_seo_email_callback()
1683 1748 {
1684 1749 printf(
1685 - '<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" />',
1686 1751 isset(Metasync::get_option('localseo')['local_seo_email']) ? esc_attr(Metasync::get_option('localseo')['local_seo_email']) : ''
1687 1752 );
1688 1753
1689 1754 printf(' <br> <span class="description"> Search engines display your email address. </span>');
@@ -1691,9 +1756,9 @@
1691 1756
1692 1757 public function local_seo_phone_callback()
1693 1758 {
1694 1759 printf(
1695 - '<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" />',
1696 1761 isset(Metasync::get_option('localseo')['local_seo_phone']) ? esc_attr(Metasync::get_option('localseo')['local_seo_phone']) : ''
1697 1762 );
1698 1763
1699 1764 printf(' <br> <span class="description"> Search engines may prominently display your contact phone number for mobile users. </span>');
@@ -1701,29 +1766,29 @@
1701 1766
1702 1767 public function local_seo_address_callback()
1703 1768 {
1704 1769 printf(
1705 - '<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>',
1706 1771 isset(Metasync::get_option('localseo')['address']['street']) ? esc_attr(Metasync::get_option('localseo')['address']['street']) : ''
1707 1772 );
1708 1773
1709 1774 printf(
1710 - '<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>',
1711 1776 isset(Metasync::get_option('localseo')['address']['locality']) ? esc_attr(Metasync::get_option('localseo')['address']['locality']) : ''
1712 1777 );
1713 1778
1714 1779 printf(
1715 - '<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>',
1716 1781 isset(Metasync::get_option('localseo')['address']['region']) ? esc_attr(Metasync::get_option('localseo')['address']['region']) : ''
1717 1782 );
1718 1783
1719 1784 printf(
1720 - '<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>',
1721 1786 isset(Metasync::get_option('localseo')['address']['postalcode']) ? esc_attr(Metasync::get_option('localseo')['address']['postalcode']) : ''
1722 1787 );
1723 1788
1724 1789 printf(
1725 - '<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>',
1726 1791 isset(Metasync::get_option('localseo')['address']['country']) ? esc_attr(Metasync::get_option('localseo')['address']['country']) : ''
1727 1792 );
1728 1793 }
1729 1794
@@ -1738,9 +1803,9 @@
1738 1803 <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][local_seo_business_type]') ?>">
1739 1804 <option value='0'>Select Business Type</option>
1740 1805 <?php
1741 1806 foreach ($types as $type) {
1742 - 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));
1743 1808 }
1744 1809 ?>
1745 1810 </select>
1746 1811 <?php
@@ -1764,9 +1829,9 @@
1764 1829 <li>
1765 1830 <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][days][]') ?>">
1766 1831 <?php
1767 1832 foreach ($days_name as $name) {
1768 - 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));
1769 1834 }
1770 1835 ?>
1771 1836 </select>
1772 1837 <input type="text" name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][times][]') ?>" value="<?php echo esc_attr($day_time) ?>">
@@ -1845,9 +1910,9 @@
1845 1910
1846 1911 public function local_seo_price_range_callback()
1847 1912 {
1848 1913 printf(
1849 - '<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" />',
1850 1915 isset(Metasync::get_option('localseo')['local_seo_price_range']) ? esc_attr(Metasync::get_option('localseo')['local_seo_price_range']) : ''
1851 1916 );
1852 1917 printf(' <br> <span class="description"> The price range of the business, for example $$$. </span>');
1853 1918 }
@@ -1860,9 +1925,9 @@
1860 1925 <?php
1861 1926 $about_page = Metasync::get_option('localseo')['local_seo_about_page'] ?? '';
1862 1927 $pages = get_pages();
1863 1928 foreach ($pages as $page) {
1864 - 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));
1865 1930 }
1866 1931 ?>
1867 1932 </select>
1868 1933 <?php
@@ -1877,9 +1942,9 @@
1877 1942 <?php
1878 1943 $contact_page = Metasync::get_option('localseo')['local_seo_contact_page'] ?? '';
1879 1944 $pages = get_pages();
1880 1945 foreach ($pages as $page) {
1881 - 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));
1882 1947 }
1883 1948 ?>
1884 1949 </select>
1885 1950 <?php
@@ -1888,9 +1953,9 @@
1888 1953
1889 1954 public function local_seo_map_key_callback()
1890 1955 {
1891 1956 printf(
1892 - '<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" />',
1893 1958 isset(Metasync::get_option('localseo')['local_seo_map_key']) ? esc_attr(Metasync::get_option('localseo')['local_seo_map_key']) : ''
1894 1959 );
1895 1960
1896 1961 printf(' <br> <span class="description"> An API Key is required to display embedded Google Maps on your site. </span>');
@@ -1898,9 +1963,9 @@
1898 1963
1899 1964 public function local_seo_geo_coordinates_callback()
1900 1965 {
1901 1966 printf(
1902 - '<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" />',
1903 1968 isset(Metasync::get_option('localseo')['local_seo_geo_coordinates']) ? esc_attr(Metasync::get_option('localseo')['local_seo_geo_coordinates']) : ''
1904 1969 );
1905 1970
1906 1971 printf(' <br> <span class="description"> Latitude and longitude values separated by comma. </span>');
@@ -1908,9 +1973,9 @@
1908 1973
1909 1974 public function header_snippets_callback()
1910 1975 {
1911 1976 printf(
1912 - '<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>',
1913 1978 isset(Metasync::get_option('codesnippets')['header_snippet']) ? esc_attr(Metasync::get_option('codesnippets')['header_snippet']) : ''
1914 1979 );
1915 1980 }
1916 1981
@@ -1916,9 +1981,9 @@
1916 1981
1917 1982 public function footer_snippets_callback()
1918 1983 {
1919 1984 printf(
1920 - '<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>',
1921 1986 isset(Metasync::get_option('codesnippets')['footer_snippet']) ? esc_attr(Metasync::get_option('codesnippets')['footer_snippet']) : ''
1922 1987 );
1923 1988 }
1924 1989
@@ -2055,9 +2120,9 @@
2055 2120
2056 2121 public function global_open_graph_meta_callback()
2057 2122 {
2058 2123 printf(
2059 - '<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 />',
2060 2125 isset(Metasync::get_option('common_meta_settings')['open_graph_meta_tags']) && Metasync::get_option('common_meta_settings')['open_graph_meta_tags'] == 'true' ? 'checked' : ''
2061 2126 );
2062 2127 printf(' <br> <span class="description"> Automatically add the Open Graph meta tags in a page or post.</span>');
2063 2128 }
@@ -2064,9 +2129,9 @@
2064 2129
2065 2130 public function global_facebook_meta_callback()
2066 2131 {
2067 2132 printf(
2068 - '<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 />',
2069 2134 isset(Metasync::get_option('common_meta_settings')['facebook_meta_tags']) && Metasync::get_option('common_meta_settings')['facebook_meta_tags'] == 'true' ? 'checked' : ''
2070 2135 );
2071 2136 printf(' <br> <span class="description"> Automatically add the Facebook meta tags in a page or post.</span>');
2072 2137 }
@@ -2073,9 +2138,9 @@
2073 2138
2074 2139 public function global_twitter_meta_callback()
2075 2140 {
2076 2141 printf(
2077 - '<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 />',
2078 2143 isset(Metasync::get_option('common_meta_settings')['twitter_meta_tags']) && Metasync::get_option('common_meta_settings')['twitter_meta_tags'] == 'true' ? 'checked' : ''
2079 2144 );
2080 2145 printf(' <br> <span class="description"> Automatically add the Twitter meta tags in a page or post.</span>');
2081 2146 }
@@ -2083,9 +2148,9 @@
2083 2148 public function og_image_dimensions_callback()
2084 2149 {
2085 2150 $val = Metasync::get_option('common_meta_settings')['og_image_dimensions'] ?? 'true';
2086 2151 printf(
2087 - '<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 />',
2088 2153 $val === 'true' ? 'checked' : ''
2089 2154 );
2090 2155 printf('<span class="description"> Output og:image:width, og:image:height, and og:image:type when OG image metadata is available.</span>');
2091 2156 }
@@ -2093,9 +2158,9 @@
2093 2158 public function article_timestamps_callback()
2094 2159 {
2095 2160 $val = Metasync::get_option('common_meta_settings')['article_timestamps'] ?? 'true';
2096 2161 printf(
2097 - '<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 />',
2098 2163 $val === 'true' ? 'checked' : ''
2099 2164 );
2100 2165 printf('<span class="description"> Output article:published_time and article:modified_time from the post publish and modified dates (ISO 8601).</span>');
2101 2166 }
@@ -2103,9 +2168,9 @@
2103 2168 public function article_author_callback()
2104 2169 {
2105 2170 $val = Metasync::get_option('common_meta_settings')['article_author'] ?? 'true';
2106 2171 printf(
2107 - '<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 />',
2108 2173 $val === 'true' ? 'checked' : ''
2109 2174 );
2110 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>');
2111 2176 }
@@ -2113,9 +2178,9 @@
2113 2178 public function article_section_callback()
2114 2179 {
2115 2180 $val = Metasync::get_option('common_meta_settings')['article_section'] ?? 'true';
2116 2181 printf(
2117 - '<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 />',
2118 2183 $val === 'true' ? 'checked' : ''
2119 2184 );
2120 2185 printf('<span class="description"> Output article:section from the primary category (falls back to first category).</span>');
2121 2186 }
@@ -2123,9 +2188,9 @@
2123 2188 public function article_tags_callback()
2124 2189 {
2125 2190 $val = Metasync::get_option('common_meta_settings')['article_tags'] ?? 'true';
2126 2191 printf(
2127 - '<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 />',
2128 2193 $val === 'true' ? 'checked' : ''
2129 2194 );
2130 2195 printf('<span class="description"> Output one article:tag meta tag per WordPress post tag.</span>');
2131 2196 }
@@ -2133,9 +2198,9 @@
2133 2198 public function twitter_image_alt_callback()
2134 2199 {
2135 2200 $val = Metasync::get_option('common_meta_settings')['twitter_image_alt'] ?? 'true';
2136 2201 printf(
2137 - '<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 />',
2138 2203 $val === 'true' ? 'checked' : ''
2139 2204 );
2140 2205 printf('<span class="description"> Output twitter:image:alt using the stored alt text or the OG image attachment alt attribute.</span>');
2141 2206 }
@@ -2142,9 +2207,9 @@
2142 2207
2143 2208 public function facebook_page_url_callback()
2144 2209 {
2145 2210 $facebook_page_url = Metasync::get_option('social_meta')['facebook_page_url'] ?? '';
2146 - 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));
2147 2212 printf('<br><span class="description"> Enter your Facebook page URL. eg: <code>https://www.facebook.com/MetaSync/</code> </span>');
2148 2213 }
2149 2214
2150 2215 public function facebook_authorship_callback()
@@ -2149,9 +2214,9 @@
2149 2214
2150 2215 public function facebook_authorship_callback()
2151 2216 {
2152 2217 $facebook_authorship = Metasync::get_option('social_meta')['facebook_authorship'] ?? '';
2153 - 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));
2154 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>');
2155 2220 }
2156 2221
2157 2222 public function facebook_admin_callback()
@@ -2156,9 +2221,9 @@
2156 2221
2157 2222 public function facebook_admin_callback()
2158 2223 {
2159 2224 $facebook_admin = Metasync::get_option('social_meta')['facebook_admin'] ?? '';
2160 - 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));
2161 2226 printf(' <br> <span class="description"> Enter numeric user ID of Facebook. </span>');
2162 2227 }
2163 2228
2164 2229 public function facebook_app_callback()
@@ -2163,9 +2228,9 @@
2163 2228
2164 2229 public function facebook_app_callback()
2165 2230 {
2166 2231 $facebook_app = Metasync::get_option('social_meta')['facebook_app'] ?? '';
2167 - 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));
2168 2233 printf(' <br> <span class="description"> Enter numeric app ID of Facebook </span>');
2169 2234 }
2170 2235
2171 2236 public function facebook_secret_callback()
@@ -2170,9 +2235,9 @@
2170 2235
2171 2236 public function facebook_secret_callback()
2172 2237 {
2173 2238 $facebook_secret = Metasync::get_option('social_meta')['facebook_secret'] ?? '';
2174 - 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));
2175 2240 printf(' <br> <span class="description"> Enter alphanumeric access token from Facebook. </span>');
2176 2241 }
2177 2242
2178 2243 public function twitter_username_callback()
@@ -2177,9 +2242,9 @@
2177 2242
2178 2243 public function twitter_username_callback()
2179 2244 {
2180 2245 $twitter_username = Metasync::get_option('social_meta')['twitter_username'] ?? '';
2181 - 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));
2182 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>');
2183 2248 }
2184 2249
2185 2250 public static function get_business_types()
@@ -2354,8 +2419,14 @@
2354 2419 }
2355 2420
2356 2421 /**
2357 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".
2358 2429 */
2359 2430 public function render_breadcrumbs_section() {
2360 2431 $options = Metasync::get_option('breadcrumbs', array());
2361 2432
@@ -2360,9 +2431,9 @@
2360 2431 $options = Metasync::get_option('breadcrumbs', array());
2361 2432
2362 2433 $defaults = array(
2363 2434 'enabled' => true,
2364 - 'separator' => '&raquo;',
2435 + 'separator' => '»',
2365 2436 'home_label' => 'Home',
2366 2437 'home_url' => '',
2367 2438 'show_current_page' => true,
2368 2439 'prefix_text' => '',
@@ -2370,8 +2441,19 @@
2370 2441 'disable_schema' => false,
2371 2442 );
2372 2443
2373 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;
2374 2456 ?>
2375 2457 <div style="background: var(--dashboard-card-bg); padding: 20px; border-radius: 8px;">
2376 2458
2377 2459 <!-- Enable/Disable -->
@@ -2376,8 +2458,9 @@
2376 2458
2377 2459 <!-- Enable/Disable -->
2378 2460 <div style="margin-bottom: 24px;">
2379 2461 <label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
2462 + <input type="hidden" name="metasync_options[breadcrumbs][enabled]" value="0">
2380 2463 <input type="checkbox"
2381 2464 name="metasync_options[breadcrumbs][enabled]"
2382 2465 value="1"
2383 2466 <?php checked($options['enabled'], 1); ?>
@@ -2392,8 +2475,9 @@
2392 2475
2393 2476 <!-- Disable Schema Markup -->
2394 2477 <div style="margin-bottom: 24px;">
2395 2478 <label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
2479 + <input type="hidden" name="metasync_options[breadcrumbs][disable_schema]" value="0">
2396 2480 <input type="checkbox"
2397 2481 name="metasync_options[breadcrumbs][disable_schema]"
2398 2482 value="1"
2399 2483 <?php checked($options['disable_schema'], 1); ?>
@@ -2411,24 +2495,21 @@
2411 2495 <label for="breadcrumb_separator" style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--dashboard-text);">
2412 2496 Separator Character
2413 2497 </label>
2414 2498 <div style="display: flex; gap: 12px; flex-wrap: wrap;">
2499 + <?php foreach ($separator_presets as $separator_option) : ?>
2415 2500 <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
2416 - <input type="radio" name="metasync_options[breadcrumbs][separator]" value="»" <?php checked($options['separator'], '»'); ?>>
2417 - <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>
2418 2503 </label>
2419 - <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
2420 - <input type="radio" name="metasync_options[breadcrumbs][separator]" value="/" <?php checked($options['separator'], '/'); ?>>
2421 - <span style="color: var(--dashboard-text);">/</span>
2422 - </label>
2423 - <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
2424 - <input type="radio" name="metasync_options[breadcrumbs][separator]" value=">" <?php checked($options['separator'], '>'); ?>>
2425 - <span style="color: var(--dashboard-text);">></span>
2426 - </label>
2427 - <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
2428 - <input type="radio" name="metasync_options[breadcrumbs][separator]" value="·" <?php checked($options['separator'], '·'); ?>>
2429 - <span style="color: var(--dashboard-text);">·</span>
2430 - </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>
2431 2512 </div>
2432 2513 <p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);">
2433 2514 Choose how to separate breadcrumb items
2434 2515 </p>
@@ -2468,8 +2549,9 @@
2468 2549
2469 2550 <!-- Show Current Page -->
2470 2551 <div style="margin-bottom: 24px;">
2471 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">
2472 2554 <input type="checkbox"
2473 2555 name="metasync_options[breadcrumbs][show_current_page]"
2474 2556 value="1"
2475 2557 <?php checked($options['show_current_page'], 1); ?>