PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.1
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.1
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 +133 -85 2.6.25 → 2.7.1 View file →
@@ -46,9 +46,15 @@
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(
51 57 'connection' => array(
52 58 'title' => 'Connection & Authentication',
53 59 'description' => 'Manage your API connection and authentication settings',
54 60 'icon' => 'admin-network',
@@ -68,8 +74,9 @@
68 74 'fields' => array(
69 75 'otto_pixel_uuid',
70 76 'otto_disable_on_loggedin',
71 77 'otto_disable_preview_button',
78 + 'seo_priority',
72 79 'otto_wp_rocket_compat',
73 80 'otto_sg_optimizer_compat',
74 81 'bookingpress_session_compat',
75 82 )
@@ -81,8 +88,16 @@
81 88 'priority' => 22,
82 89 'default_open' => false,
83 90 'render_callback' => array('Metasync_Edge_Cache_Settings', 'render'),
84 91 ),
92 + 'seo_plugin_sync' => array(
93 + 'title' => 'Sync to Other SEO Plugins',
94 + 'description' => 'Control whether ' . Metasync::get_whitelabel_otto_name() . '\'s SEO values are written into Yoast, Rank Math or All in One SEO',
95 + 'icon' => 'update',
96 + 'priority' => 23,
97 + 'default_open' => false,
98 + 'render_callback' => array('Metasync_Seo_Sync_Settings', 'render'),
99 + ),
85 100 'bot_detection' => array(
86 101 'title' => 'Bot Detection & Filtering',
87 102 'description' => 'Manage bot traffic and reduce unnecessary API calls',
88 103 'icon' => 'filter',
@@ -94,16 +109,8 @@
94 109 'otto_bot_blacklist',
95 110 'otto_bot_statistics_link'
96 111 )
97 112 ),
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 113 'open_graph' => array(
107 114 'title' => 'Open Graph',
108 115 'description' => 'Control which Open Graph and article meta tags are output',
109 116 'icon' => 'share',
@@ -130,8 +137,9 @@
130 137 'disable_redirection_metabox',
131 138 'disable_canonical_metabox',
132 139 'disable_social_opengraph_metabox',
133 140 'disable_schema_markup_metabox',
141 + 'disable_language_alternates_metabox',
134 142 'disable_seo_metabox',
135 143 'open_external_links'
136 144 )
137 145 ),
@@ -172,13 +180,22 @@
172 180 'fields' => array(
173 181 'permalink_structure',
174 182 'hide_dashboard_framework',
175 183 'show_admin_bar_status',
176 - 'enable_auto_updates',
177 184 'import_external_data'
178 185 )
179 186 )
180 187 );
188 +
189 + // Hidden only when there is nothing to say: no SEO plugin installed, the
190 + // switch off, and no saved originals. It is never hidden by the
191 + // access-control or whitelabel flags that can conceal Advanced Settings
192 + // — a switch that rewrites another plugin's data must stay reachable.
193 + if (!Metasync_Seo_Sync_Settings::should_display()) {
194 + unset($sections['seo_plugin_sync']);
195 + }
196 +
197 + return $sections;
181 198 }
182 199
183 200 public function get_advanced_accordion_config() {
184 201 $config = array();
@@ -203,8 +220,17 @@
203 220 'default_open' => false,
204 221 'render_callback' => array($this->admin_instance, 'render_debug_mode_section')
205 222 );
206 223
224 + $config['headless_mode'] = array(
225 + 'title' => 'Headless Mode',
226 + 'description' => 'Connect WordPress content to a separate public frontend',
227 + 'icon' => 'admin-site-alt3',
228 + 'priority' => 29,
229 + 'default_open' => false,
230 + 'render_callback' => array(Metasync_Settings_Registration::instance(), 'render_headless_mode_section')
231 + );
232 +
207 233 $config['error_logs'] = array(
208 234 'title' => 'Error Logs',
209 235 'description' => 'View and manage error logs to troubleshoot issues',
210 236 'icon' => 'warning',
@@ -277,9 +303,9 @@
277 303 $content_state = $is_open ? 'open' : 'closed';
278 304
279 305 echo '<div class="metasync-accordion-section" data-section="' . esc_attr($section_key) . '">';
280 306
281 - echo '<div class="metasync-accordion-header" role="button" tabindex="0" aria-expanded="' . $aria_expanded . '" aria-controls="' . $section_id . '">';
307 + echo '<div class="metasync-accordion-header" role="button" tabindex="0" aria-expanded="' . esc_attr($aria_expanded) . '" aria-controls="' . esc_attr($section_id) . '">';
282 308 echo '<div class="metasync-accordion-title">';
283 309 echo '<span class="metasync-accordion-icon"><span class="dashicons dashicons-' . esc_attr($section_data['icon']) . '"></span></span>';
284 310 echo '<div class="metasync-accordion-text">';
285 311 echo '<h3>' . esc_html($section_data['title']) . '</h3>';
@@ -290,9 +316,9 @@
290 316 echo '<span class="toggle-icon">▼</span>';
291 317 echo '</button>';
292 318 echo '</div>';
293 319
294 - echo '<div class="metasync-accordion-content" id="' . $section_id . '" data-state="' . $content_state . '">';
320 + echo '<div class="metasync-accordion-content" id="' . esc_attr($section_id) . '" data-state="' . esc_attr($content_state) . '">';
295 321
296 322 if (isset($section_data['render_callback']) && is_callable($section_data['render_callback'])) {
297 323 call_user_func($section_data['render_callback']);
298 324 }
@@ -386,14 +412,18 @@
386 412 if (!function_exists('google_index_direct')) {
387 413 if (file_exists(plugin_dir_path(dirname(__FILE__)) . 'google-index/google-index-init.php')) {
388 414 require_once plugin_dir_path(dirname(__FILE__)) . 'google-index/google-index-init.php';
389 415 } 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');
416 + 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 417 return;
392 418 }
393 419 }
394 420
395 421 $google_index = google_index_direct();
422 + if (!$google_index) {
423 + // Nothing to render against, and calling through would be fatal.
424 + return;
425 + }
396 426 $service_info = $google_index->get_service_account_info();
397 427 $is_configured = !isset($service_info['error']);
398 428
399 429 $saved_json_display = $is_configured ? $google_index->get_redacted_config_json() : '';
@@ -672,9 +702,9 @@
672 702 <p style="color: var(--dashboard-text-secondary); margin: 0 0 20px 0;">
673 703 Select which user roles can see and access this plugin's menu, settings, and options in the WordPress admin area.
674 704 </p>
675 705
676 - <form method="post" action="<?php echo admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '&tab=advanced'); ?>" id="plugin-access-roles-form">
706 + <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 707 <?php wp_nonce_field('metasync_plugin_access_roles_nonce', 'plugin_access_roles_nonce'); ?>
678 708 <input type="hidden" name="save_plugin_access_roles" value="yes" />
679 709
680 710 <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 +822,9 @@
792 822
793 823 public function can_change_memory_limit() {
794 824 $current_limit = ini_get('memory_limit');
795 825
796 - $test_result = @ini_set('memory_limit', $current_limit);
826 + $test_result = @ini_set('memory_limit', $current_limit); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- runtime PHP setting with no WordPress API
797 827
798 828 return $test_result !== false;
799 829 }
800 830
@@ -846,9 +876,9 @@
846 876 }
847 877
848 878 $memory_limit_mb = $this->get_execution_setting('max_memory_limit');
849 879
850 - $result = @ini_set('memory_limit', $memory_limit_mb . 'M');
880 + $result = @ini_set('memory_limit', $memory_limit_mb . 'M'); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- runtime PHP setting with no WordPress API
851 881
852 882 return $result !== false;
853 883 }
854 884
@@ -1100,8 +1130,9 @@
1100 1130 '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 1131 '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 1132 '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 1133 '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),
1134 + 'seo_priority' => sprintf('Choose which value appears first on a page when both your custom SEO value and an approved %s suggestion are available. This changes what visitors see; it does not delete or overwrite saved values.', $otto_name),
1104 1135 '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 1136 '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 1137 '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 1138 '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 +1144,9 @@
1113 1144 '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 1145 '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 1146 '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 1147 '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')),
1148 + '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 1149 '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 1150 '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 1151 '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 1152 '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 +1151,8 @@
1119 1151 '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 1152 '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 1153 '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 1154 '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 1155 '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 1156 '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 1157 '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 1158 '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.',
@@ -1167,9 +1198,9 @@
1167 1198 $content_state = $is_open ? 'open' : 'closed';
1168 1199
1169 1200 echo '<div class="metasync-accordion-section" data-section="' . esc_attr($section_key) . '">';
1170 1201
1171 - echo '<div class="metasync-accordion-header" role="button" tabindex="0" aria-expanded="' . $aria_expanded . '" aria-controls="' . $section_id . '">';
1202 + echo '<div class="metasync-accordion-header" role="button" tabindex="0" aria-expanded="' . esc_attr($aria_expanded) . '" aria-controls="' . esc_attr($section_id) . '">';
1172 1203 echo '<div class="metasync-accordion-title">';
1173 1204 echo '<span class="metasync-accordion-icon"><span class="dashicons dashicons-' . esc_attr($section_data['icon']) . '"></span></span>';
1174 1205 echo '<div class="metasync-accordion-text">';
1175 1206 echo '<h3>' . esc_html($section_data['title']) . '</h3>';
@@ -1180,9 +1211,9 @@
1180 1211 echo '<span class="toggle-icon">▼</span>';
1181 1212 echo '</button>';
1182 1213 echo '</div>';
1183 1214
1184 - echo '<div class="metasync-accordion-content" id="' . $section_id . '" data-state="' . $content_state . '">';
1215 + echo '<div class="metasync-accordion-content" id="' . esc_attr($section_id) . '" data-state="' . esc_attr($content_state) . '">';
1185 1216
1186 1217 if (isset($section_data['render_callback']) && is_callable($section_data['render_callback'])) {
1187 1218 call_user_func($section_data['render_callback']);
1188 1219 } elseif (isset($section_data['fields']) && is_array($section_data['fields'])) {
@@ -1197,9 +1228,9 @@
1197 1228 echo '<tr>';
1198 1229 echo '<th scope="row">';
1199 1230 if (!empty($field['title'])) {
1200 1231 echo '<div class="metasync-field-label-wrapper">';
1201 - echo '<label for="' . esc_attr($field['id']) . '">' . $field['title'] . '</label>';
1232 + echo '<label for="' . esc_attr($field['id']) . '">' . esc_html($field['title']) . '</label>';
1202 1233
1203 1234 if (isset($tooltips[$field_id])) {
1204 1235 echo '<button type="button" class="metasync-tooltip-trigger" data-tooltip-id="' . esc_attr($field_id) . '" aria-label="More information">';
1205 1236 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 +1285,9 @@
1254 1285 echo '<tr>';
1255 1286 echo '<th scope="row">';
1256 1287 if (!empty($field['title'])) {
1257 1288 echo '<div class="metasync-field-label-wrapper">';
1258 - echo '<label for="' . esc_attr($field['id']) . '">' . $field['title'] . '</label>';
1289 + echo '<label for="' . esc_attr($field['id']) . '">' . esc_html($field['title']) . '</label>';
1259 1290 if (isset($llms_tooltips[$field['id']])) {
1260 1291 Metasync::render_tooltip_icon($field['id'], $llms_tooltips[$field['id']]);
1261 1292 }
1262 1293 echo '</div>';
@@ -1310,32 +1341,32 @@
1310 1341 $display_value = $current_token;
1311 1342 $status_message = 'Plugin Auth Token is active and ready for authentication.';
1312 1343 $refresh_help = 'Click refresh to generate a new token and update the heartbeat API.';
1313 1344 } 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()) . '".';
1345 + $display_value = 'Auto-generated when connecting to ' . Metasync::get_effective_plugin_name();
1346 + $status_message = 'Plugin Auth Token will be automatically generated when you click "Connect to ' . Metasync::get_effective_plugin_name() . '".';
1316 1347 $refresh_help = 'You can also manually generate a token by clicking refresh.';
1317 1348 }
1318 1349
1319 1350 printf('<div class="metasync-auth-token-row">');
1320 1351 printf(
1321 - '<input type="text" id="apikey" name="' . Metasync_Admin::option_key . '[general][apikey]" value="%s" size="40" class="regular-text" readonly="readonly" />',
1352 + '<input type="text" id="apikey" name="' . esc_attr(Metasync_Admin::option_key) . '[general][apikey]" value="%s" size="40" class="regular-text" readonly="readonly" />',
1322 1353 esc_attr($display_value)
1323 1354 );
1324 1355 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 1356 printf('</div>');
1326 - printf('<p class="description">%s %s</p>', $status_message, $refresh_help);
1357 + printf('<p class="description">%s %s</p>', esc_html($status_message), esc_html($refresh_help));
1327 1358 }
1328 1359
1329 1360 public function linkgraph_token_callback()
1330 1361 {
1331 1362 printf(
1332 - '<input type="text" id="linkgraph_token" name="' . Metasync_Admin::option_key . '[general][linkgraph_token]" value="%s" size="25" readonly="readonly" />',
1363 + '<input type="text" id="linkgraph_token" name="' . esc_attr(Metasync_Admin::option_key) . '[general][linkgraph_token]" value="%s" size="25" readonly="readonly" />',
1333 1364 isset(Metasync::get_option('general')['linkgraph_token']) ? esc_attr(Metasync::get_option('general')['linkgraph_token']) : ''
1334 1365 );
1335 1366
1336 1367 printf(
1337 - '<input type="text" id="linkgraph_customer_id" name="' . Metasync_Admin::option_key . '[general][linkgraph_customer_id]" value="%s" size="25" readonly="readonly" />',
1368 + '<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 1369 isset(Metasync::get_option('general')['linkgraph_customer_id']) ? esc_attr(Metasync::get_option('general')['linkgraph_customer_id']) : ''
1339 1370 );
1340 1371
1341 1372 ?>
@@ -1460,9 +1491,9 @@
1460 1491 printf('• The authentication window will open in a popup - please allow popups<br/>');
1461 1492 printf('• The process typically takes 15-30 seconds to complete<br/>');
1462 1493 printf('• Your API key will be stored securely and shown masked below<br/>');
1463 1494 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);
1495 + printf('• Contact <a href="mailto:%s">%s</a> if you need assistance', esc_attr(Metasync::SUPPORT_EMAIL), esc_html(Metasync::SUPPORT_EMAIL));
1465 1496 printf('</div>');
1466 1497 printf('</details>');
1467 1498 printf('</div>');
1468 1499
@@ -1514,9 +1545,9 @@
1514 1545 $timestamp = @Metasync::get_option('general')['send_auth_token_timestamp'];
1515 1546 printf(
1516 1547 '<p id="sendAuthTokenTimestamp" class="descriptionValue">%s (%s)</p>',
1517 1548 esc_attr($timestamp),
1518 - $this->time_elapsed_string($timestamp)
1549 + esc_html($this->time_elapsed_string($timestamp))
1519 1550 );
1520 1551
1521 1552
1522 1553 }
@@ -1524,9 +1555,9 @@
1524 1555
1525 1556 public function bing_site_verification_callback()
1526 1557 {
1527 1558 printf(
1528 - '<input type="text" id="bing_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][bing_site_verification]" value="%s" size="50" />',
1559 + '<input type="text" id="bing_site_verification" name="' . esc_attr(Metasync_Admin::option_key) . '[searchengines][bing_site_verification]" value="%s" size="50" />',
1529 1560 isset(Metasync::get_option('searchengines')['bing_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['bing_site_verification']) : ''
1530 1561 );
1531 1562
1532 1563 printf(' <br> <span class="description"> Enter Bing Webmaster Tools verification code: </span> ');
@@ -1535,9 +1566,9 @@
1535 1566
1536 1567 public function yandex_site_verification_callback()
1537 1568 {
1538 1569 printf(
1539 - '<input type="text" id="yandex_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][yandex_site_verification]" value="%s" size="50" />',
1570 + '<input type="text" id="yandex_site_verification" name="' . esc_attr(Metasync_Admin::option_key) . '[searchengines][yandex_site_verification]" value="%s" size="50" />',
1540 1571 isset(Metasync::get_option('searchengines')['yandex_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['yandex_site_verification']) : ''
1541 1572 );
1542 1573
1543 1574 printf(' <br> <span class="description"> Enter Yandex verification code: </span>');
@@ -1546,9 +1577,9 @@
1546 1577
1547 1578 public function google_site_verification_callback()
1548 1579 {
1549 1580 printf(
1550 - '<input type="text" id="google_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][google_site_verification]" value="%s" size="50" />',
1581 + '<input type="text" id="google_site_verification" name="' . esc_attr(Metasync_Admin::option_key) . '[searchengines][google_site_verification]" value="%s" size="50" />',
1551 1582 isset(Metasync::get_option('searchengines')['google_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['google_site_verification']) : ''
1552 1583 );
1553 1584
1554 1585 printf(' <br> <span class="description"> Enter Google Search Console verification code: </span>');
@@ -1557,9 +1588,9 @@
1557 1588
1558 1589 public function pinterest_site_verification_callback()
1559 1590 {
1560 1591 printf(
1561 - '<input type="text" id="pinterest_site_verification" name="' . Metasync_Admin::option_key . '[searchengines][pinterest_site_verification]" value="%s" size="50" />',
1592 + '<input type="text" id="pinterest_site_verification" name="' . esc_attr(Metasync_Admin::option_key) . '[searchengines][pinterest_site_verification]" value="%s" size="50" />',
1562 1593 isset(Metasync::get_option('searchengines')['pinterest_site_verification']) ? esc_attr(Metasync::get_option('searchengines')['pinterest_site_verification']) : ''
1563 1594 );
1564 1595
1565 1596 printf(' <br> <span class="description"> Enter Pinterest verification code: </span>');
@@ -1588,10 +1619,10 @@
1588 1619
1589 1620 public function local_seo_name_callback()
1590 1621 {
1591 1622 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()
1623 + '<input type="text" id="local_seo_name" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][local_seo_name]" value="%s" size="50" />',
1624 + isset(Metasync::get_option('localseo')['local_seo_name']) ? esc_attr(Metasync::get_option('localseo')['local_seo_name']) : esc_attr(get_bloginfo())
1594 1625 );
1595 1626
1596 1627 printf(' <br> <span class="description"> Your name or company name </span>');
1597 1628 }
@@ -1607,9 +1638,9 @@
1607 1638 // would otherwise survive every subsequent Save Settings.
1608 1639 $local_seo_logo = metasync_repair_scheme_prefixed_media_id($local_seo_logo);
1609 1640
1610 1641 printf(
1611 - '<input type="hidden" id="local_seo_logo" name="' . Metasync_Admin::option_key . '[localseo][local_seo_logo]" value="%s" size="50" />',
1642 + '<input type="hidden" id="local_seo_logo" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][local_seo_logo]" value="%s" size="50" />',
1612 1643 esc_attr((string) $local_seo_logo)
1613 1644 );
1614 1645
1615 1646 printf(' <br> <input class="button-secondary no-loading" type="button" id="logo_upload_button" value="Add or Upload File">');
@@ -1631,9 +1662,9 @@
1631 1662 $logo_url ? '' : ' style="display:none"'
1632 1663 );
1633 1664
1634 1665 $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);
1666 + 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 1667 printf('</span>');
1637 1668 }
1638 1669
1639 1670 /**
@@ -1671,10 +1702,10 @@
1671 1702
1672 1703 public function local_seo_url_callback()
1673 1704 {
1674 1705 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()
1706 + '<input type="text" id="local_seo_url" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][local_seo_url]" value="%s" size="50" />',
1707 + isset(Metasync::get_option('localseo')['local_seo_url']) ? esc_attr(Metasync::get_option('localseo')['local_seo_url']) : esc_attr(home_url())
1677 1708 );
1678 1709
1679 1710 printf(' <br> <span class="description"> URL of the item. </span>');
1680 1711 }
@@ -1681,9 +1712,9 @@
1681 1712
1682 1713 public function local_seo_email_callback()
1683 1714 {
1684 1715 printf(
1685 - '<input type="text" id="local_seo_email" name="' . Metasync_Admin::option_key . '[localseo][local_seo_email]" value="%s" size="50" />',
1716 + '<input type="text" id="local_seo_email" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][local_seo_email]" value="%s" size="50" />',
1686 1717 isset(Metasync::get_option('localseo')['local_seo_email']) ? esc_attr(Metasync::get_option('localseo')['local_seo_email']) : ''
1687 1718 );
1688 1719
1689 1720 printf(' <br> <span class="description"> Search engines display your email address. </span>');
@@ -1691,9 +1722,9 @@
1691 1722
1692 1723 public function local_seo_phone_callback()
1693 1724 {
1694 1725 printf(
1695 - '<input type="text" id="local_seo_phone" name="' . Metasync_Admin::option_key . '[localseo][local_seo_phone]" value="%s" size="50" />',
1726 + '<input type="text" id="local_seo_phone" name="' . esc_attr(Metasync_Admin::option_key) . '[localseo][local_seo_phone]" value="%s" size="50" />',
1696 1727 isset(Metasync::get_option('localseo')['local_seo_phone']) ? esc_attr(Metasync::get_option('localseo')['local_seo_phone']) : ''
1697 1728 );
1698 1729
1699 1730 printf(' <br> <span class="description"> Search engines may prominently display your contact phone number for mobile users. </span>');
@@ -1701,29 +1732,29 @@
1701 1732
1702 1733 public function local_seo_address_callback()
1703 1734 {
1704 1735 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>',
1736 + '<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 1737 isset(Metasync::get_option('localseo')['address']['street']) ? esc_attr(Metasync::get_option('localseo')['address']['street']) : ''
1707 1738 );
1708 1739
1709 1740 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>',
1741 + '<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 1742 isset(Metasync::get_option('localseo')['address']['locality']) ? esc_attr(Metasync::get_option('localseo')['address']['locality']) : ''
1712 1743 );
1713 1744
1714 1745 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>',
1746 + '<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 1747 isset(Metasync::get_option('localseo')['address']['region']) ? esc_attr(Metasync::get_option('localseo')['address']['region']) : ''
1717 1748 );
1718 1749
1719 1750 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>',
1751 + '<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 1752 isset(Metasync::get_option('localseo')['address']['postalcode']) ? esc_attr(Metasync::get_option('localseo')['address']['postalcode']) : ''
1722 1753 );
1723 1754
1724 1755 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>',
1756 + '<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 1757 isset(Metasync::get_option('localseo')['address']['country']) ? esc_attr(Metasync::get_option('localseo')['address']['country']) : ''
1727 1758 );
1728 1759 }
1729 1760
@@ -1738,9 +1769,9 @@
1738 1769 <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][local_seo_business_type]') ?>">
1739 1770 <option value='0'>Select Business Type</option>
1740 1771 <?php
1741 1772 foreach ($types as $type) {
1742 - printf('<option value="%s" %s >%s</option>', $type, selected($type, esc_attr($business_type)), $type);
1773 + printf('<option value="%s" %s >%s</option>', esc_attr($type), selected($type, $business_type, false), esc_html($type));
1743 1774 }
1744 1775 ?>
1745 1776 </select>
1746 1777 <?php
@@ -1764,9 +1795,9 @@
1764 1795 <li>
1765 1796 <select name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][days][]') ?>">
1766 1797 <?php
1767 1798 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));
1799 + printf('<option value="%s" %s >%s</option>', esc_attr($name), selected($name, $day_name, false), esc_html($name));
1769 1800 }
1770 1801 ?>
1771 1802 </select>
1772 1803 <input type="text" name="<?php echo esc_attr(Metasync_Admin::option_key . '[localseo][times][]') ?>" value="<?php echo esc_attr($day_time) ?>">
@@ -1845,9 +1876,9 @@
1845 1876
1846 1877 public function local_seo_price_range_callback()
1847 1878 {
1848 1879 printf(
1849 - '<input type="text" id="local_seo_price_range" name="' . Metasync_Admin::option_key . '[localseo][local_seo_price_range]" value="%s" size="50" />',
1880 + '<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 1881 isset(Metasync::get_option('localseo')['local_seo_price_range']) ? esc_attr(Metasync::get_option('localseo')['local_seo_price_range']) : ''
1851 1882 );
1852 1883 printf(' <br> <span class="description"> The price range of the business, for example $$$. </span>');
1853 1884 }
@@ -1860,9 +1891,9 @@
1860 1891 <?php
1861 1892 $about_page = Metasync::get_option('localseo')['local_seo_about_page'] ?? '';
1862 1893 $pages = get_pages();
1863 1894 foreach ($pages as $page) {
1864 - printf('<option value="%s" %s >%s</option>', $page->ID, selected($page->ID, esc_attr($about_page)), $page->post_title);
1895 + printf('<option value="%s" %s >%s</option>', absint($page->ID), selected($page->ID, $about_page, false), esc_html($page->post_title));
1865 1896 }
1866 1897 ?>
1867 1898 </select>
1868 1899 <?php
@@ -1877,9 +1908,9 @@
1877 1908 <?php
1878 1909 $contact_page = Metasync::get_option('localseo')['local_seo_contact_page'] ?? '';
1879 1910 $pages = get_pages();
1880 1911 foreach ($pages as $page) {
1881 - printf('<option value="%s" %s >%s</option>', $page->ID, selected($page->ID, esc_attr($contact_page)), $page->post_title);
1912 + printf('<option value="%s" %s >%s</option>', absint($page->ID), selected($page->ID, $contact_page, false), esc_html($page->post_title));
1882 1913 }
1883 1914 ?>
1884 1915 </select>
1885 1916 <?php
@@ -1888,9 +1919,9 @@
1888 1919
1889 1920 public function local_seo_map_key_callback()
1890 1921 {
1891 1922 printf(
1892 - '<input type="text" id="local_seo_map_key" name="' . Metasync_Admin::option_key . '[localseo][local_seo_map_key]" value="%s" size="50" />',
1923 + '<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 1924 isset(Metasync::get_option('localseo')['local_seo_map_key']) ? esc_attr(Metasync::get_option('localseo')['local_seo_map_key']) : ''
1894 1925 );
1895 1926
1896 1927 printf(' <br> <span class="description"> An API Key is required to display embedded Google Maps on your site. </span>');
@@ -1898,9 +1929,9 @@
1898 1929
1899 1930 public function local_seo_geo_coordinates_callback()
1900 1931 {
1901 1932 printf(
1902 - '<input type="text" id="local_seo_geo_coordinates" name="' . Metasync_Admin::option_key . '[localseo][local_seo_geo_coordinates]" value="%s" size="50" />',
1933 + '<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 1934 isset(Metasync::get_option('localseo')['local_seo_geo_coordinates']) ? esc_attr(Metasync::get_option('localseo')['local_seo_geo_coordinates']) : ''
1904 1935 );
1905 1936
1906 1937 printf(' <br> <span class="description"> Latitude and longitude values separated by comma. </span>');
@@ -1908,9 +1939,9 @@
1908 1939
1909 1940 public function header_snippets_callback()
1910 1941 {
1911 1942 printf(
1912 - '<textarea class="wide-text" id="header_snippets" rows="8" name="' . Metasync_Admin::option_key . '[codesnippets][header_snippet]" >%s</textarea>',
1943 + '<textarea class="wide-text" id="header_snippets" rows="8" name="' . esc_attr(Metasync_Admin::option_key) . '[codesnippets][header_snippet]" >%s</textarea>',
1913 1944 isset(Metasync::get_option('codesnippets')['header_snippet']) ? esc_attr(Metasync::get_option('codesnippets')['header_snippet']) : ''
1914 1945 );
1915 1946 }
1916 1947
@@ -1916,9 +1947,9 @@
1916 1947
1917 1948 public function footer_snippets_callback()
1918 1949 {
1919 1950 printf(
1920 - '<textarea class="wide-text" id="footer_snippets" rows="8" name="' . Metasync_Admin::option_key . '[codesnippets][footer_snippet]" >%s</textarea>',
1951 + '<textarea class="wide-text" id="footer_snippets" rows="8" name="' . esc_attr(Metasync_Admin::option_key) . '[codesnippets][footer_snippet]" >%s</textarea>',
1921 1952 isset(Metasync::get_option('codesnippets')['footer_snippet']) ? esc_attr(Metasync::get_option('codesnippets')['footer_snippet']) : ''
1922 1953 );
1923 1954 }
1924 1955
@@ -2055,9 +2086,9 @@
2055 2086
2056 2087 public function global_open_graph_meta_callback()
2057 2088 {
2058 2089 printf(
2059 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][open_graph_meta_tags]" value="true" %s />',
2090 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][open_graph_meta_tags]" value="true" %s />',
2060 2091 isset(Metasync::get_option('common_meta_settings')['open_graph_meta_tags']) && Metasync::get_option('common_meta_settings')['open_graph_meta_tags'] == 'true' ? 'checked' : ''
2061 2092 );
2062 2093 printf(' <br> <span class="description"> Automatically add the Open Graph meta tags in a page or post.</span>');
2063 2094 }
@@ -2064,9 +2095,9 @@
2064 2095
2065 2096 public function global_facebook_meta_callback()
2066 2097 {
2067 2098 printf(
2068 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][facebook_meta_tags]" value="true" %s />',
2099 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][facebook_meta_tags]" value="true" %s />',
2069 2100 isset(Metasync::get_option('common_meta_settings')['facebook_meta_tags']) && Metasync::get_option('common_meta_settings')['facebook_meta_tags'] == 'true' ? 'checked' : ''
2070 2101 );
2071 2102 printf(' <br> <span class="description"> Automatically add the Facebook meta tags in a page or post.</span>');
2072 2103 }
@@ -2073,9 +2104,9 @@
2073 2104
2074 2105 public function global_twitter_meta_callback()
2075 2106 {
2076 2107 printf(
2077 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][twitter_meta_tags]" value="true" %s />',
2108 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][twitter_meta_tags]" value="true" %s />',
2078 2109 isset(Metasync::get_option('common_meta_settings')['twitter_meta_tags']) && Metasync::get_option('common_meta_settings')['twitter_meta_tags'] == 'true' ? 'checked' : ''
2079 2110 );
2080 2111 printf(' <br> <span class="description"> Automatically add the Twitter meta tags in a page or post.</span>');
2081 2112 }
@@ -2083,9 +2114,9 @@
2083 2114 public function og_image_dimensions_callback()
2084 2115 {
2085 2116 $val = Metasync::get_option('common_meta_settings')['og_image_dimensions'] ?? 'true';
2086 2117 printf(
2087 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][og_image_dimensions]" value="true" %s />',
2118 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][og_image_dimensions]" value="true" %s />',
2088 2119 $val === 'true' ? 'checked' : ''
2089 2120 );
2090 2121 printf('<span class="description"> Output og:image:width, og:image:height, and og:image:type when OG image metadata is available.</span>');
2091 2122 }
@@ -2093,9 +2124,9 @@
2093 2124 public function article_timestamps_callback()
2094 2125 {
2095 2126 $val = Metasync::get_option('common_meta_settings')['article_timestamps'] ?? 'true';
2096 2127 printf(
2097 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_timestamps]" value="true" %s />',
2128 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][article_timestamps]" value="true" %s />',
2098 2129 $val === 'true' ? 'checked' : ''
2099 2130 );
2100 2131 printf('<span class="description"> Output article:published_time and article:modified_time from the post publish and modified dates (ISO 8601).</span>');
2101 2132 }
@@ -2103,9 +2134,9 @@
2103 2134 public function article_author_callback()
2104 2135 {
2105 2136 $val = Metasync::get_option('common_meta_settings')['article_author'] ?? 'true';
2106 2137 printf(
2107 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_author]" value="true" %s />',
2138 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][article_author]" value="true" %s />',
2108 2139 $val === 'true' ? 'checked' : ''
2109 2140 );
2110 2141 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 2142 }
@@ -2113,9 +2144,9 @@
2113 2144 public function article_section_callback()
2114 2145 {
2115 2146 $val = Metasync::get_option('common_meta_settings')['article_section'] ?? 'true';
2116 2147 printf(
2117 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_section]" value="true" %s />',
2148 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][article_section]" value="true" %s />',
2118 2149 $val === 'true' ? 'checked' : ''
2119 2150 );
2120 2151 printf('<span class="description"> Output article:section from the primary category (falls back to first category).</span>');
2121 2152 }
@@ -2123,9 +2154,9 @@
2123 2154 public function article_tags_callback()
2124 2155 {
2125 2156 $val = Metasync::get_option('common_meta_settings')['article_tags'] ?? 'true';
2126 2157 printf(
2127 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][article_tags]" value="true" %s />',
2158 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][article_tags]" value="true" %s />',
2128 2159 $val === 'true' ? 'checked' : ''
2129 2160 );
2130 2161 printf('<span class="description"> Output one article:tag meta tag per WordPress post tag.</span>');
2131 2162 }
@@ -2133,9 +2164,9 @@
2133 2164 public function twitter_image_alt_callback()
2134 2165 {
2135 2166 $val = Metasync::get_option('common_meta_settings')['twitter_image_alt'] ?? 'true';
2136 2167 printf(
2137 - '<input type="checkbox" name="' . Metasync_Admin::option_key . '[common_meta_settings][twitter_image_alt]" value="true" %s />',
2168 + '<input type="checkbox" name="' . esc_attr(Metasync_Admin::option_key) . '[common_meta_settings][twitter_image_alt]" value="true" %s />',
2138 2169 $val === 'true' ? 'checked' : ''
2139 2170 );
2140 2171 printf('<span class="description"> Output twitter:image:alt using the stored alt text or the OG image attachment alt attribute.</span>');
2141 2172 }
@@ -2142,9 +2173,9 @@
2142 2173
2143 2174 public function facebook_page_url_callback()
2144 2175 {
2145 2176 $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));
2177 + 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 2178 printf('<br><span class="description"> Enter your Facebook page URL. eg: <code>https://www.facebook.com/MetaSync/</code> </span>');
2148 2179 }
2149 2180
2150 2181 public function facebook_authorship_callback()
@@ -2149,9 +2180,9 @@
2149 2180
2150 2181 public function facebook_authorship_callback()
2151 2182 {
2152 2183 $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));
2184 + printf('<input type="text" name="' . esc_attr(Metasync_Admin::option_key) . '[social_meta][facebook_authorship]" value="%s" size="50" />', esc_attr($facebook_authorship));
2154 2185 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 2186 }
2156 2187
2157 2188 public function facebook_admin_callback()
@@ -2156,9 +2187,9 @@
2156 2187
2157 2188 public function facebook_admin_callback()
2158 2189 {
2159 2190 $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));
2191 + printf('<input type="text" name="' . esc_attr(Metasync_Admin::option_key) . '[social_meta][facebook_admin]" value="%s" size="50" />', esc_attr($facebook_admin));
2161 2192 printf(' <br> <span class="description"> Enter numeric user ID of Facebook. </span>');
2162 2193 }
2163 2194
2164 2195 public function facebook_app_callback()
@@ -2163,9 +2194,9 @@
2163 2194
2164 2195 public function facebook_app_callback()
2165 2196 {
2166 2197 $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));
2198 + printf('<input type="text" name="' . esc_attr(Metasync_Admin::option_key) . '[social_meta][facebook_app]" value="%s" size="50" />', esc_attr($facebook_app));
2168 2199 printf(' <br> <span class="description"> Enter numeric app ID of Facebook </span>');
2169 2200 }
2170 2201
2171 2202 public function facebook_secret_callback()
@@ -2170,9 +2201,9 @@
2170 2201
2171 2202 public function facebook_secret_callback()
2172 2203 {
2173 2204 $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));
2205 + printf('<input type="text" name="' . esc_attr(Metasync_Admin::option_key) . '[social_meta][facebook_secret]" value="%s" size="50" />', esc_attr($facebook_secret));
2175 2206 printf(' <br> <span class="description"> Enter alphanumeric access token from Facebook. </span>');
2176 2207 }
2177 2208
2178 2209 public function twitter_username_callback()
@@ -2177,9 +2208,9 @@
2177 2208
2178 2209 public function twitter_username_callback()
2179 2210 {
2180 2211 $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));
2212 + printf('<input type="text" name="' . esc_attr(Metasync_Admin::option_key) . '[social_meta][twitter_username]" value="%s" size="50" />', esc_attr($twitter_username));
2182 2213 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 2214 }
2184 2215
2185 2216 public static function get_business_types()
@@ -2354,8 +2385,14 @@
2354 2385 }
2355 2386
2356 2387 /**
2357 2388 * Render breadcrumbs settings section
2389 + *
2390 + * Rendered by the dedicated Breadcrumbs admin page. The three checkbox
2391 + * fields each ship a hidden companion input so their keys are always
2392 + * present in the submitted payload, which lets the save handlers treat an
2393 + * absent key as "this form does not manage that field" instead of
2394 + * "unchecked".
2358 2395 */
2359 2396 public function render_breadcrumbs_section() {
2360 2397 $options = Metasync::get_option('breadcrumbs', array());
2361 2398
@@ -2360,9 +2397,9 @@
2360 2397 $options = Metasync::get_option('breadcrumbs', array());
2361 2398
2362 2399 $defaults = array(
2363 2400 'enabled' => true,
2364 - 'separator' => '&raquo;',
2401 + 'separator' => '»',
2365 2402 'home_label' => 'Home',
2366 2403 'home_url' => '',
2367 2404 'show_current_page' => true,
2368 2405 'prefix_text' => '',
@@ -2370,8 +2407,19 @@
2370 2407 'disable_schema' => false,
2371 2408 );
2372 2409
2373 2410 $options = wp_parse_args($options, $defaults);
2411 +
2412 + # Older builds stored the separator as an HTML entity (e.g. "&raquo;").
2413 + # Decode before matching so the saved choice still lights up its radio.
2414 + $separator_presets = array('»', '/', '>', '·');
2415 + $separator_current = html_entity_decode((string) $options['separator'], ENT_QUOTES, 'UTF-8');
2416 + if ($separator_current === '') {
2417 + $separator_current = '»';
2418 + }
2419 + # A separator that predates the radio picker (or came from the old free
2420 + # text field) gets its own option so it stays selected and round-trips.
2421 + $separator_custom = in_array($separator_current, $separator_presets, true) ? '' : $separator_current;
2374 2422 ?>
2375 2423 <div style="background: var(--dashboard-card-bg); padding: 20px; border-radius: 8px;">
2376 2424
2377 2425 <!-- Enable/Disable -->
@@ -2376,8 +2424,9 @@
2376 2424
2377 2425 <!-- Enable/Disable -->
2378 2426 <div style="margin-bottom: 24px;">
2379 2427 <label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
2428 + <input type="hidden" name="metasync_options[breadcrumbs][enabled]" value="0">
2380 2429 <input type="checkbox"
2381 2430 name="metasync_options[breadcrumbs][enabled]"
2382 2431 value="1"
2383 2432 <?php checked($options['enabled'], 1); ?>
@@ -2392,8 +2441,9 @@
2392 2441
2393 2442 <!-- Disable Schema Markup -->
2394 2443 <div style="margin-bottom: 24px;">
2395 2444 <label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
2445 + <input type="hidden" name="metasync_options[breadcrumbs][disable_schema]" value="0">
2396 2446 <input type="checkbox"
2397 2447 name="metasync_options[breadcrumbs][disable_schema]"
2398 2448 value="1"
2399 2449 <?php checked($options['disable_schema'], 1); ?>
@@ -2411,24 +2461,21 @@
2411 2461 <label for="breadcrumb_separator" style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--dashboard-text);">
2412 2462 Separator Character
2413 2463 </label>
2414 2464 <div style="display: flex; gap: 12px; flex-wrap: wrap;">
2465 + <?php foreach ($separator_presets as $separator_option) : ?>
2415 2466 <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>
2467 + <input type="radio" name="metasync_options[breadcrumbs][separator]" value="<?php echo esc_attr($separator_option); ?>" <?php checked($separator_current, $separator_option); ?>>
2468 + <span style="color: var(--dashboard-text);"><?php echo esc_html($separator_option); ?></span>
2418 2469 </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>
2470 + <?php endforeach; ?>
2471 + <div style="display: flex; align-items: center; gap: 8px;">
2472 + <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
2473 + <input type="radio" id="metasync-breadcrumb-separator-custom" name="metasync_options[breadcrumbs][separator]" value="<?php echo esc_attr($separator_custom); ?>" <?php checked($separator_custom !== ''); ?>>
2474 + <span style="color: var(--dashboard-text);">Custom</span>
2475 + </label>
2476 + <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;">
2477 + </div>
2431 2478 </div>
2432 2479 <p style="margin: 8px 0 0 0; font-size: 12px; color: var(--dashboard-text-secondary);">
2433 2480 Choose how to separate breadcrumb items
2434 2481 </p>
@@ -2468,8 +2515,9 @@
2468 2515
2469 2516 <!-- Show Current Page -->
2470 2517 <div style="margin-bottom: 24px;">
2471 2518 <label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
2519 + <input type="hidden" name="metasync_options[breadcrumbs][show_current_page]" value="0">
2472 2520 <input type="checkbox"
2473 2521 name="metasync_options[breadcrumbs][show_current_page]"
2474 2522 value="1"
2475 2523 <?php checked($options['show_current_page'], 1); ?>