| @@ -168,8 +168,17 @@ | ||
| 168 | 168 | $result = $importer->import_schema($plugin); |
| 169 | 169 | break; |
| 170 | 170 | } |
| 171 | 171 | |
| 172 | + // A schema import that only found already-migrated or unsupported | |
| 173 | + // records is an accurate report, not a failure — keep the breakdown | |
| 174 | + // with the response so the UI can show each category separately. | |
| 175 | + if (isset($result['breakdown'])) { | |
| 176 | + $result['breakdown'] = array_map('intval', (array) $result['breakdown']); | |
| 177 | + } | |
| 178 | + | |
| 179 | + Metasync_PostHog::import_batch($plugin, $type, $result, $offset); | |
| 180 | + | |
| 172 | 181 | if ($result['success']) { |
| 173 | 182 | wp_send_json_success($result); |
| 174 | 183 | } else { |
| 175 | 184 | wp_send_json_error($result); |
| @@ -217,8 +226,12 @@ | ||
| 217 | 226 | ]; |
| 218 | 227 | |
| 219 | 228 | $result = $importer->import_seo_metadata($plugin, $options); |
| 220 | 229 | |
| 230 | + Metasync_PostHog::import_batch($plugin, 'seo_metadata', $result, $offset, array( | |
| 231 | + 'overwrite_existing' => $overwrite_existing, | |
| 232 | + )); | |
| 233 | + | |
| 221 | 234 | if ($result['success']) { |
| 222 | 235 | wp_send_json_success($result); |
| 223 | 236 | } else { |
| 224 | 237 | wp_send_json_error($result); |
| @@ -319,9 +332,9 @@ | ||
| 319 | 332 | $step = isset($_POST['step']) ? intval($_POST['step']) : 0; |
| 320 | 333 | $raw_data = isset($_POST['data']) ? $_POST['data'] : array(); |
| 321 | 334 | |
| 322 | 335 | // Allowlist top-level wizard data keys to prevent mass assignment |
| 323 | - $allowed_wizard_keys = array('verification', 'seo_settings', 'schema'); | |
| 336 | + $allowed_wizard_keys = array('verification', 'seo_settings', 'schema', 'analytics'); | |
| 324 | 337 | $data = is_array($raw_data) ? array_intersect_key($raw_data, array_flip($allowed_wizard_keys)) : array(); |
| 325 | 338 | |
| 326 | 339 | $options = get_option('metasync_options', array()); |
| 327 | 340 | |
| @@ -351,8 +364,13 @@ | ||
| 351 | 364 | $options['general']['enable_schema_markup'] = $data['schema']['enabled']; |
| 352 | 365 | $options['general']['default_schema_type'] = sanitize_text_field($data['schema']['default_type']); |
| 353 | 366 | } |
| 354 | 367 | |
| 368 | + if (isset($data['analytics']) && is_array($data['analytics'])) { | |
| 369 | + update_option('metasync_analytics_opt_in', !empty($data['analytics']['enabled']) ? 'yes' : 'no'); | |
| 370 | + update_option('metasync_analytics_consent_explicit', 'yes'); | |
| 371 | + } | |
| 372 | + | |
| 355 | 373 | update_option('metasync_options', $options); |
| 356 | 374 | |
| 357 | 375 | wp_send_json_success(array('message' => 'Progress saved')); |
| 358 | 376 | } |
| @@ -374,8 +392,14 @@ | ||
| 374 | 392 | |
| 375 | 393 | $user_id = get_current_user_id(); |
| 376 | 394 | delete_transient("metasync_wizard_state_{$user_id}"); |
| 377 | 395 | |
| 396 | + // Consent-gated product analytics: setup funnel completion. | |
| 397 | + $general_settings = Metasync::get_option('general'); | |
| 398 | + Metasync_PostHog::feature('setup_wizard_completed', array( | |
| 399 | + 'connected' => !empty($general_settings['searchatlas_api_key']), | |
| 400 | + )); | |
| 401 | + | |
| 378 | 402 | wp_send_json_success(array('message' => 'Wizard completed')); |
| 379 | 403 | } |
| 380 | 404 | |
| 381 | 405 | public function ajax_validate_robots() |
| @@ -688,8 +712,43 @@ | ||
| 688 | 712 | error_log('MetaSync: Transient cleanup failed - ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets |
| 689 | 713 | } |
| 690 | 714 | } |
| 691 | 715 | |
| 716 | + /** | |
| 717 | + * Collect the locally stored white-label settings for an issue-report payload. | |
| 718 | + * | |
| 719 | + * The values are attached to the Sentry event so support can tell from the | |
| 720 | + * event alone whether the reporting site is white-labeled. Local options | |
| 721 | + * can be stale — the reply-drafting process must still cross-check the | |
| 722 | + * backend account record before sending. | |
| 723 | + * | |
| 724 | + * Static and public so the unit suite can exercise the reads directly; the | |
| 725 | + * AJAX handler itself dies on wp_send_json_* before returning. | |
| 726 | + * | |
| 727 | + * @return array Keys: is_whitelabel, whitelabel_brand_name, whitelabel_support_email, site_url. | |
| 728 | + */ | |
| 729 | + public static function build_issue_report_whitelabel_context() | |
| 730 | + { | |
| 731 | + $whitelabel_settings = Metasync::get_whitelabel_settings(); | |
| 732 | + if (!is_array($whitelabel_settings)) { | |
| 733 | + $whitelabel_settings = array(); | |
| 734 | + } | |
| 735 | + $whitelabel_support_email = ''; | |
| 736 | + foreach (array('support_email_override', 'support_email', 'recovery_email') as $email_key) { | |
| 737 | + if (!empty($whitelabel_settings[$email_key])) { | |
| 738 | + $whitelabel_support_email = sanitize_email($whitelabel_settings[$email_key]); | |
| 739 | + break; | |
| 740 | + } | |
| 741 | + } | |
| 742 | + return array( | |
| 743 | + 'is_whitelabel' => Metasync::is_whitelabel_enabled(), | |
| 744 | + 'whitelabel_brand_name' => !empty($whitelabel_settings['company_name']) | |
| 745 | + ? sanitize_text_field($whitelabel_settings['company_name']) : '', | |
| 746 | + 'whitelabel_support_email' => $whitelabel_support_email, | |
| 747 | + 'site_url' => esc_url_raw(home_url()), | |
| 748 | + ); | |
| 749 | + } | |
| 750 | + | |
| 692 | 751 | public function ajax_submit_issue_report() |
| 693 | 752 | { |
| 694 | 753 | try { |
| 695 | 754 | # Verify nonce |
| @@ -760,8 +819,11 @@ | ||
| 760 | 819 | ); |
| 761 | 820 | } |
| 762 | 821 | } |
| 763 | 822 | |
| 823 | + # Read the locally stored white-label settings for the report payload. | |
| 824 | + $whitelabel_context = self::build_issue_report_whitelabel_context(); | |
| 825 | + | |
| 764 | 826 | # Get general options (same way as used throughout the plugin) |
| 765 | 827 | $general_options = Metasync::get_option('general'); |
| 766 | 828 | if (!is_array($general_options)) { |
| 767 | 829 | $general_options = array(); |
| @@ -821,9 +883,13 @@ | ||
| 821 | 883 | } |
| 822 | 884 | } else { |
| 823 | 885 | $feedback_data = array( |
| 824 | 886 | 'message' => $issue_message, |
| 825 | - 'severity' => $issue_severity | |
| 887 | + 'severity' => $issue_severity, | |
| 888 | + 'is_whitelabel' => $whitelabel_context['is_whitelabel'], | |
| 889 | + 'whitelabel_brand_name' => $whitelabel_context['whitelabel_brand_name'], | |
| 890 | + 'whitelabel_support_email' => $whitelabel_context['whitelabel_support_email'], | |
| 891 | + 'site_url' => $whitelabel_context['site_url'] | |
| 826 | 892 | ); |
| 827 | 893 | |
| 828 | 894 | # Add user information if requested |
| 829 | 895 | if ($include_user_info) { |
| @@ -1214,40 +1280,20 @@ | ||
| 1214 | 1280 | wp_die('Security verification failed.'); |
| 1215 | 1281 | } |
| 1216 | 1282 | |
| 1217 | 1283 | try { |
| 1218 | - # Get all whitelabel settings | |
| 1219 | - $whitelabel_settings = Metasync::get_whitelabel_settings(); | |
| 1220 | - | |
| 1221 | - # Export the settings password as plaintext (not the encrypted blob): | |
| 1222 | - # the encryption key derives from this site's salts, so the blob | |
| 1223 | - # would be undecryptable after import on a different site. The | |
| 1224 | - # importer re-encrypts it at rest with the destination site's salts. | |
| 1225 | - if (!empty($whitelabel_settings['settings_password'])) { | |
| 1226 | - $whitelabel_settings['settings_password'] = Metasync::get_whitelabel_password(); | |
| 1284 | + # Source the white-label export payload from the shared helper so | |
| 1285 | + # reset and export can never drift apart: whatever the reset | |
| 1286 | + # preserves is exactly what the export serialises. No API | |
| 1287 | + # credentials (apikey / searchatlas_api_key / otto_pixel_uuid / ...) | |
| 1288 | + # are ever serialised - the helper only captures the whitelabel | |
| 1289 | + # blob and the general branding keys. | |
| 1290 | + if (!class_exists('Metasync_Whitelabel_Preservation')) { | |
| 1291 | + require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-metasync-whitelabel-preservation.php'; | |
| 1227 | 1292 | } |
| 1228 | - | |
| 1229 | - # Get general settings that relate to whitelabel | |
| 1230 | - $general_settings = Metasync::get_option('general'); | |
| 1231 | - $whitelabel_related_general = array(); | |
| 1232 | - | |
| 1233 | - # Include ALL whitelabel-related general settings | |
| 1234 | - $whitelabel_keys = array( | |
| 1235 | - 'white_label_plugin_name', | |
| 1236 | - 'white_label_plugin_description', | |
| 1237 | - 'white_label_plugin_author', | |
| 1238 | - 'white_label_plugin_author_uri', | |
| 1239 | - 'white_label_plugin_uri', | |
| 1240 | - 'white_label_plugin_menu_slug', | |
| 1241 | - 'white_label_plugin_menu_icon', | |
| 1242 | - 'whitelabel_otto_name' | |
| 1243 | - ); | |
| 1244 | - | |
| 1245 | - foreach ($whitelabel_keys as $key) { | |
| 1246 | - if (isset($general_settings[$key])) { | |
| 1247 | - $whitelabel_related_general[$key] = $general_settings[$key]; | |
| 1248 | - } | |
| 1249 | - } | |
| 1293 | + $export_payload = Metasync_Whitelabel_Preservation::build_whitelabel_export_data(); | |
| 1294 | + $whitelabel_settings = $export_payload['whitelabel_settings']; | |
| 1295 | + $whitelabel_related_general = $export_payload['general_settings']; | |
| 1250 | 1296 | |
| 1251 | 1297 | # Bundle the menu icon file so it survives import on a different site. |
| 1252 | 1298 | # If the icon is a local URL (media library), replace it with a special |
| 1253 | 1299 | # marker and include the actual file in the ZIP under the plugin folder. |
| @@ -1449,9 +1495,9 @@ | ||
| 1449 | 1495 | echo '<div class="metasync-dashboard-widget-empty">'; |
| 1450 | 1496 | echo '<span class="dashicons dashicons-admin-page" style="font-size: 48px; opacity: 0.3; display: block; margin: 20px auto;"></span>'; |
| 1451 | 1497 | echo '<p style="text-align: center; color: #666;">'; |
| 1452 | 1498 | /* translators: %s: HTML source label. */ |
| 1453 | - echo sprintf(__('No pages created with %s yet.', 'metasync'), '<strong>' . esc_html($label) . '</strong>'); | |
| 1499 | + echo sprintf(esc_html__('No pages created with %s yet.', 'metasync'), '<strong>' . esc_html($label) . '</strong>'); | |
| 1454 | 1500 | echo '</p>'; |
| 1455 | 1501 | echo '<p style="text-align: center;">'; |
| 1456 | 1502 | echo '<a href="' . esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug)) . '" class="button button-primary">'; |
| 1457 | 1503 | echo esc_html__('Get Started', 'metasync'); |