capability)) { wp_die(esc_html__('You do not have permission to export data.', 'gmap-embed')); } $export_type = isset($_POST['wgm_export_type']) ? sanitize_text_field(wp_unslash($_POST['wgm_export_type'])) : 'json'; $map_ids = []; if (isset($_POST['wgm_maps'])) { $raw_maps = map_deep(wp_unslash($_POST['wgm_maps']), 'sanitize_text_field'); if (is_array($raw_maps)) { $map_ids = array_map('intval', $raw_maps); } else { $map_ids[] = intval($raw_maps); } } // Initial export structure $export_data = [ 'creator' => 'WPGoogleMap', 'plugin_version' => WGM_PLUGIN_VERSION, 'json_version' => '1.1', 'maps' => [], 'markers' => [], 'categories' => [], ]; // 1. Always fetch Map data for selected IDs $maps = $this->get_all_maps($map_ids); $export_data['maps'] = $maps ? $maps : []; // 2. Fetch ALL markers for these maps (needed for category calculation even if not exporting) $map_ids_for_query = array_filter(array_column($export_data['maps'], 'id')); $related_markers = []; if (!empty($map_ids_for_query)) { global $wpdb; $ids_placeholders = implode(',', array_fill(0, count($map_ids_for_query), '%d')); // Building query string with placeholders for IN clause $markers_query = "SELECT id, map_id, marker_name, marker_desc, icon, address, lat_lng, have_marker_link, marker_link, marker_link_new_tab, animation, category_id, show_desc_by_default FROM {$wpdb->prefix}wgm_markers WHERE map_id IN ($ids_placeholders)"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $related_markers = $wpdb->get_results( $wpdb->prepare( $markers_query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $map_ids_for_query ), ARRAY_A ); } // Determine selected types from UI based on format $selected_types = []; if ($export_type === 'json') { $selected_types = isset($_POST['wgm_export_data_types']) ? array_map('sanitize_text_field', wp_unslash($_POST['wgm_export_data_types'])) : ['markers', 'categories']; } elseif ($export_type === 'csv') { // For CSV, we only allow one type, but we map it to our internal array structure $csv_type = isset($_POST['wgm_export_data_type']) ? sanitize_text_field(wp_unslash($_POST['wgm_export_data_type'])) : 'markers'; $selected_types = [$csv_type]; } // 3. Add Markers to export if selected if (in_array('markers', $selected_types)) { $export_data['markers'] = $related_markers ? $related_markers : []; } // 4. Calculate and Add Categories only if selected if (in_array('categories', $selected_types) && !empty($related_markers)) { // Collect used category IDs $raw_cat_ids = array_column($related_markers, 'category_id'); $used_cat_ids = []; foreach ($raw_cat_ids as $val) { if (empty($val)) continue; // Handle multiple categories (comma separated) $parts = explode(',', $val); foreach ($parts as $p) { $p = intval(trim($p)); if ($p > 0) { $used_cat_ids[] = $p; } } } $used_cat_ids = array_unique($used_cat_ids); if (!empty($used_cat_ids)) { global $wpdb; $categories_table = $wpdb->prefix . 'wgm_categories'; // Recursive fetch for parents // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $all_cats_pool = $wpdb->get_results("SELECT * FROM {$categories_table}", ARRAY_A); // Index by ID for easier lookup $cat_lookup = []; foreach ($all_cats_pool as $c) { $cat_lookup[intval($c['id'])] = $c; } $final_export_cats = []; $processed_ids = []; $queue = $used_cat_ids; while (!empty($queue)) { $cid = array_pop($queue); $cid = intval($cid); if (isset($processed_ids[$cid])) continue; if (!isset($cat_lookup[$cid])) continue; $processed_ids[$cid] = true; $cat_obj = $cat_lookup[$cid]; $final_export_cats[] = $cat_obj; // Add parent to queue if (!empty($cat_obj['parent_id'])) { $queue[] = intval($cat_obj['parent_id']); } } $export_data['categories'] = $final_export_cats; } } if ($export_type === 'json') { header('Content-Disposition: attachment; filename="gmap-export.json"'); header('Content-Type: application/json; charset=utf-8'); echo wp_json_encode($export_data, JSON_PRETTY_PRINT); exit; } elseif ($export_type === 'csv') { $export_data_type_csv = isset($_POST['wgm_export_data_type']) ? sanitize_text_field(wp_unslash($_POST['wgm_export_data_type'])) : ''; // CSV logic remains largely similar, just pulling from our new filtered dataset // Note: CSV export in this plugin generally implies flat single-type export. // Maps or Markers are the options in UI. $data_csv = []; if ($export_data_type_csv === 'maps') { $data_csv = $export_data['maps']; } elseif ($export_data_type_csv === 'markers') { $data_csv = $export_data['markers']; } elseif ($export_data_type_csv === 'categories') { // Use the processed categories (which includes dependencies) $data_csv = $export_data['categories']; } header('Content-Type: text/csv'); header('Content-Disposition: attachment; filename="gmap-export.csv"'); if (!empty($data_csv)) { // Get headers from first row $all_headers = array_keys(reset($data_csv)); // Filter out timestamps $headers = array_filter($all_headers, function($h) { return !in_array($h, ['created_at', 'updated_at']); }); $csv_content = ''; // Helper for manual CSV generation $to_csv_row = function($data) { foreach ($data as &$val) { $val = '"' . str_replace('"', '""', $val) . '"'; } return implode(',', $data) . "\n"; }; $csv_content .= $to_csv_row($headers); foreach ($data_csv as $row) { $filtered_row = []; foreach($headers as $h) { $filtered_row[] = isset($row[$h]) ? $row[$h] : ''; } $csv_content .= $to_csv_row($filtered_row); } echo $csv_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } exit; } } /** * Import data handler */ public function wgm_handle_import() { $redirect_with_error = function ($code, $message = '') { $args = array('wgm_import' => 'error', 'error_code' => $code); if (!empty($message)) { $args['error_msg'] = rawurlencode(sanitize_text_field(wp_strip_all_tags($message))); } if (defined('DOING_AJAX') && DOING_AJAX) { wp_send_json_error(array('message' => $message ?: $code)); } wp_safe_redirect(add_query_arg($args, wp_get_referer() ?: admin_url('admin.php?page=wpgmapembed-settings'))); exit; }; $nonce = isset($_POST['_wgm_import_nonce']) ? sanitize_text_field(wp_unslash($_POST['_wgm_import_nonce'])) : ''; if (empty($nonce) || !wp_verify_nonce($nonce, 'wgm_import_nonce')) { $redirect_with_error('invalid_nonce', 'Invalid import request.'); } if (!current_user_can($this->capability)) { $redirect_with_error('no_permission', 'You do not have permission to import data.'); } if ( empty($_FILES['wgm_import_file']) || !isset($_FILES['wgm_import_file']['error']) || $_FILES['wgm_import_file']['error'] !== UPLOAD_ERR_OK || !isset($_FILES['wgm_import_file']['tmp_name']) || // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Path sanitized below/validated. !is_uploaded_file($_FILES['wgm_import_file']['tmp_name']) ) { $redirect_with_error('no_file', 'No import file uploaded or upload error.'); } if (isset($_FILES['wgm_import_file']['size']) && $_FILES['wgm_import_file']['size'] > $this->max_import_size) { $redirect_with_error('file_too_large', 'Uploaded file exceeds maximum allowed size.'); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $uploaded = $_FILES['wgm_import_file']; $ext = strtolower(pathinfo($uploaded['name'], PATHINFO_EXTENSION)); // Fallback for extension check if needed if ($ext !== 'json' && $ext !== 'csv') { $redirect_with_error('invalid_file_type', 'Unsupported file type. Only JSON and CSV are allowed.'); } $import_type_ui = isset($_POST['wgm_import_type']) ? sanitize_text_field(wp_unslash($_POST['wgm_import_type'])) : 'json'; // If UI says json but file is csv (or vice versa), trust file extension or UI? Usually trust extension for processing logic. $process_type = ($ext === 'json') ? 'json' : 'csv'; $import_mode = isset($_POST['wgm_import_mode']) ? sanitize_text_field(wp_unslash($_POST['wgm_import_mode'])) : 'merge'; $import_target = isset($_POST['wgm_import_target']) ? sanitize_text_field(wp_unslash($_POST['wgm_import_target'])) : 'new'; $target_map_id = isset($_POST['wgm_target_map_id']) ? intval(wp_unslash($_POST['wgm_target_map_id'])) : 0; $dry_run = isset($_POST['wgm_import_dry_run']) && sanitize_text_field(wp_unslash($_POST['wgm_import_dry_run'])) === '1'; require_once ABSPATH . 'wp-admin/includes/file.php'; WP_Filesystem(); global $wp_filesystem; $file_contents = $wp_filesystem->get_contents($uploaded['tmp_name']); if ($file_contents === false) { $redirect_with_error('read_error', 'Failed to read uploaded file.'); } global $wpdb; $markers_table = "{$wpdb->prefix}wgm_markers"; $categories_table = "{$wpdb->prefix}wgm_categories"; // Helper to sanitize meta $sanitize_meta = function ($key, $value) { if (is_array($value) || is_object($value)) { return wp_json_encode($value); } if ($key === 'marker_desc' || $key === 'wgm_marker_desc') { return wp_kses_post((string) $value); } if ($key === 'wgm_theme_json') { return (string) $value; } return sanitize_text_field((string) $value); }; // ID Mappings $map_id_map = []; // old_id => new_id $cat_id_map = []; // old_id => new_id // --------------------- DELETE LOGIC --------------------- if (!$dry_run && $import_mode === 'replace') { $data_type = isset($_POST['wgm_import_data_type']) ? sanitize_text_field(wp_unslash($_POST['wgm_import_data_type'])) : 'markers'; if ($process_type === 'csv' && $data_type === 'categories') { // selective category wipe // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $wpdb->query("TRUNCATE TABLE {$categories_table}"); } elseif ($import_target === 'new') { // Wipe EVERYTHING (Standard for JSON or Marker->New Map batch) $existing_maps = get_posts(array('post_type' => 'wpgmapembed', 'numberposts' => -1, 'post_status' => 'any')); foreach ($existing_maps as $p) wp_delete_post($p->ID, true); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $wpdb->query("TRUNCATE TABLE {$markers_table}"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $wpdb->query("TRUNCATE TABLE {$categories_table}"); } elseif ($import_target === 'existing' && $target_map_id) { // Just wipe markers for this map // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query($wpdb->prepare("DELETE FROM {$markers_table} WHERE map_id = %d", intval($target_map_id))); } } // Existing category lookup (by name) - MUST BE AFTER WIPE // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $existing_cats = $wpdb->get_results("SELECT id, name FROM {$categories_table}", ARRAY_A); $cat_name_to_id = []; foreach ($existing_cats as $ecat) { $cat_name_to_id[strtolower($ecat['name'])] = intval($ecat['id']); } // --------------------- JSON PROCESSING --------------------- if ($process_type === 'json') { // Remove UTF-8 BOM if present $bom = pack('H*', 'EFBBBF'); if (substr($file_contents, 0, 3) === $bom) { $file_contents = substr($file_contents, 3); } $payload = json_decode($file_contents, true); if (!is_array($payload)) $redirect_with_error('invalid_json', 'Invalid JSON file.'); $maps = isset($payload['maps']) ? $payload['maps'] : []; $markers = isset($payload['markers']) ? $payload['markers'] : []; $categories = isset($payload['categories']) ? $payload['categories'] : []; // 1. Import Categories $created_cats = 0; $matched_cats = 0; if (!empty($categories)) { foreach ($categories as $cat) { $old_id = isset($cat['id']) ? intval($cat['id']) : 0; // remove id to auto-increment, remove dates to use current unset($cat['id'], $cat['created_at'], $cat['updated_at']); // Sanitize $cat['name'] = sanitize_text_field($cat['name']); $cat['icon'] = sanitize_text_field($cat['icon']); // Check for existing category by name $lower_name = strtolower($cat['name']); if (isset($cat_name_to_id[$lower_name])) { if ($old_id) $cat_id_map[$old_id] = $cat_name_to_id[$lower_name]; $matched_cats++; continue; } // Parent ID logic is tricky if IDs change. // Simplified: Reset parent to 0 for now to avoid broken trees, or try to map if parent imported first. // Ideally we should do a second pass potential parents, but for now lets default 0 if mapping not found. $cat['parent_id'] = 0; if (!$dry_run) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->insert($categories_table, $cat); $new_id = $wpdb->insert_id; if ($old_id) $cat_id_map[$old_id] = $new_id; // Add to local cache to prevent duplicates in same file $cat_name_to_id[strtolower($cat['name'])] = $new_id; } $created_cats++; } // Fix parents if possible (if we had improved logic we'd do a second pass update here using $cat_id_map) } // 2. Import Maps $created_maps = 0; foreach ($maps as $map) { $orig_id = isset($map['id']) ? intval($map['id']) : 0; $title = isset($map['wpgmap_title']) ? sanitize_text_field($map['wpgmap_title']) : __('Imported Map', 'gmap-embed'); // Skip map creation if importing into existing target if ($import_target === 'existing' && $target_map_id) { if ($orig_id) $map_id_map[$orig_id] = $target_map_id; // Optionally update options of the existing map? For now, assume we just want markers. continue; } if ($dry_run) { $map_id_map[$orig_id] = -($orig_id ?: ++$created_maps); $created_maps++; continue; } $new_id = $this->initiate_new_map($title); if ($new_id && !is_wp_error($new_id)) { $map_id_map[$orig_id] = $new_id; // Import Meta foreach ($map as $key => $val) { if ($key === 'id') continue; update_post_meta($new_id, $key, $sanitize_meta($key, $val)); } $created_maps++; } } // 2b. Map Fallback for Markers // If we are merging into an existing map but valid JSON didn't have map objects, map 0 => target if ($import_target === 'existing' && $target_map_id && empty($maps)) { $map_id_map[0] = $target_map_id; } // 3. Import Markers $inserted_markers = 0; foreach ($markers as $marker) { // Resolve Map ID $orig_map = isset($marker['map_id']) ? intval($marker['map_id']) : 0; $new_map = isset($map_id_map[$orig_map]) ? $map_id_map[$orig_map] : 0; if ($import_target === 'existing' && $target_map_id) { $new_map = $target_map_id; } // If we have no valid map destination, skip or create default? // If new_map is 0 and we are in new mode, we need a map. // Logic: create one catch-all map if needed? For now, skip orphans. if (!$new_map && !$dry_run) continue; // Resolve Category ID $orig_cat = isset($marker['category_id']) ? intval($marker['category_id']) : 0; $new_cat = isset($cat_id_map[$orig_cat]) ? $cat_id_map[$orig_cat] : 0; $insert = array( 'map_id' => $new_map, 'marker_name' => isset($marker['marker_name']) ? sanitize_text_field($marker['marker_name']) : '', 'marker_desc' => isset($marker['marker_desc']) ? $sanitize_meta('marker_desc', $marker['marker_desc']) : '', 'icon' => isset($marker['icon']) ? sanitize_text_field($marker['icon']) : '', 'address' => isset($marker['address']) ? sanitize_text_field($marker['address']) : '', 'lat_lng' => isset($marker['lat_lng']) ? sanitize_text_field($marker['lat_lng']) : '', 'have_marker_link' => isset($marker['have_marker_link']) ? sanitize_text_field($marker['have_marker_link']) : '', 'marker_link' => isset($marker['marker_link']) ? sanitize_text_field($marker['marker_link']) : '', 'marker_link_new_tab' => isset($marker['marker_link_new_tab']) ? intval($marker['marker_link_new_tab']) : 0, 'show_desc_by_default' => isset($marker['show_desc_by_default']) ? intval($marker['show_desc_by_default']) : 0, 'category_id' => $new_cat ); if (!$dry_run) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->insert($markers_table, $insert); } $inserted_markers++; } // Success Redirect if ($dry_run) { if (defined('DOING_AJAX') && DOING_AJAX) { wp_send_json_success(array( 'type' => 'json', 'maps' => $created_maps, 'markers' => $inserted_markers, 'categories' => $created_cats + $matched_cats )); } $query_args = array('wgm_import' => 'dryrun', 'maps_simulated' => $created_maps, 'markers_simulated' => $inserted_markers); } else { $query_args = array('wgm_import' => 'success'); } wp_safe_redirect(add_query_arg($query_args, wp_get_referer() ?: admin_url('admin.php'))); exit; } // --------------------- CSV PROCESSING --------------------- if ($process_type === 'csv') { $data_type = isset($_POST['wgm_import_data_type']) ? sanitize_text_field(wp_unslash($_POST['wgm_import_data_type'])) : 'markers'; global $wp_filesystem; WP_Filesystem(); $csv_content = $wp_filesystem->get_contents($uploaded['tmp_name']); if (empty($csv_content)) { wp_safe_redirect(add_query_arg(array('wgm_import' => 'error', 'error_code' => 'file_open_failed'), wp_get_referer())); exit; } $csv_rows = str_getcsv($csv_content, "\n"); $header_row = array_shift($csv_rows); $header = $header_row ? str_getcsv($header_row) : []; if (!$header) { wp_safe_redirect(add_query_arg(array('wgm_import' => 'error', 'error_code' => 'empty_csv'), wp_get_referer())); exit; } // Map Mapping Logic $mapping = []; if (!empty($_POST['wgm_import_mapping'])) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized recursively using map_deep below. $mapping = json_decode(wp_unslash($_POST['wgm_import_mapping']), true); $mapping = map_deep($mapping, 'sanitize_text_field'); } if ($data_type === 'categories') { // Pass 1: Insert Categories (ignore parents initially) $created_cats = 0; $temp_parent_map = []; // new_id => old_parent_id foreach ($csv_rows as $row_str) { if (empty(trim($row_str))) continue; $row = str_getcsv($row_str); if (count($row) !== count($header)) continue; $item = array_combine($header, $row); // Apply mapping if (!empty($mapping)) { $mapped_item = []; foreach ($item as $k => $v) { $key = isset($mapping[$k]) && $mapping[$k] ? $mapping[$k] : $k; $mapped_item[$key] = $v; } $item = $mapped_item; } $old_id = isset($item['id']) ? intval($item['id']) : 0; $old_parent_id = isset($item['parent_id']) ? intval($item['parent_id']) : 0; $cat_name = isset($item['name']) ? sanitize_text_field($item['name']) : 'Imported Category'; // Check for existing category by name $lower_name = strtolower($cat_name); if (isset($cat_name_to_id[$lower_name])) { if ($old_id) $cat_id_map[$old_id] = $cat_name_to_id[$lower_name]; continue; } if (!$dry_run) { $insert = [ 'name' => $cat_name, 'icon' => isset($item['icon']) ? sanitize_text_field($item['icon']) : '', 'parent_id' => 0, // Set to 0 initially 'created_at' => current_time('mysql'), 'updated_at' => current_time('mysql'), ]; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->insert($categories_table, $insert); $new_id = $wpdb->insert_id; if ($old_id) { $cat_id_map[$old_id] = $new_id; } // Add to local cache $cat_name_to_id[$lower_name] = $new_id; // Store parent ref for Pass 2 if it exists if ($old_parent_id) { $temp_parent_map[$new_id] = $old_parent_id; } } $created_cats++; } // Pass 2: Update Parents if (!$dry_run && !empty($temp_parent_map)) { foreach ($temp_parent_map as $child_new_id => $old_parent_id) { if (isset($cat_id_map[$old_parent_id])) { $new_parent_id = $cat_id_map[$old_parent_id]; // Update DB // Update DB // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->update( $categories_table, ['parent_id' => $new_parent_id], ['id' => $child_new_id], ['%d'], ['%d'] ); } } } $status = $dry_run ? 'dryrun' : 'success'; if ($dry_run && defined('DOING_AJAX') && DOING_AJAX) { wp_send_json_success(array( 'type' => 'categories', 'count' => $created_cats )); } wp_safe_redirect(add_query_arg(array('wgm_import' => $status, 'type' => 'categories', 'count' => $created_cats), wp_get_referer())); exit; } elseif ($data_type === 'markers') { $inserted = 0; foreach ($csv_rows as $row_str) { if (empty(trim($row_str))) continue; $row = str_getcsv($row_str); if (count($row) !== count($header)) continue; $item = array_combine($header, $row); // Apply mapping if ($mapping) { $newItem = []; foreach ($item as $k => $v) { $key = isset($mapping[$k]) && $mapping[$k] ? $mapping[$k] : $k; $newItem[$key] = $v; } $item = $newItem; } // Resolve Map $target = $target_map_id; // Default to selected if ($import_target === 'new') { static $new_csv_map_id = 0; if (!$new_csv_map_id && !$dry_run) { $new_csv_map_id = $this->initiate_new_map(__('Imported Map (CSV)', 'gmap-embed')); } $target = $new_csv_map_id; } $cat_id = isset($item['category_id']) ? intval($item['category_id']) : 0; if (!$dry_run && $target) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->insert($markers_table, array( 'map_id' => $target, 'marker_name' => isset($item['marker_name']) ? sanitize_text_field($item['marker_name']) : '', 'lat_lng' => isset($item['lat_lng']) ? sanitize_text_field($item['lat_lng']) : '', 'address' => isset($item['address']) ? sanitize_text_field($item['address']) : '', 'category_id' => $cat_id, 'icon' => isset($item['icon']) ? sanitize_text_field($item['icon']) : '', 'animation' => isset($item['animation']) ? sanitize_text_field($item['animation']) : '', 'have_marker_link' => isset($item['have_marker_link']) ? sanitize_text_field($item['have_marker_link']) : '0', 'marker_link' => isset($item['marker_link']) ? sanitize_text_field($item['marker_link']) : '', 'marker_link_new_tab' => isset($item['marker_link_new_tab']) ? intval($item['marker_link_new_tab']) : 0, 'show_desc_by_default' => isset($item['show_desc_by_default']) ? intval($item['show_desc_by_default']) : 0, 'marker_desc' => isset($item['marker_desc']) ? $sanitize_meta('marker_desc', $item['marker_desc']) : '' )); } $inserted++; } // Redirect... $status = $dry_run ? 'dryrun' : 'success'; if ($dry_run && defined('DOING_AJAX') && DOING_AJAX) { wp_send_json_success(array( 'type' => 'markers', 'count' => $inserted )); } wp_safe_redirect(add_query_arg(array('wgm_import' => $status, 'type' => 'markers', 'count' => $inserted), wp_get_referer())); exit; } } } /** * Preview Import Handler * Returns a small sample preview of uploaded content (CSV columns or JSON counts) */ public function wgm_import_preview() { // Nonce validation $nonce = isset($_POST['_wgm_import_nonce']) ? sanitize_text_field(wp_unslash($_POST['_wgm_import_nonce'])) : ''; /* phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- verified via wp_verify_nonce below */ if (empty($nonce) || !wp_verify_nonce($nonce, 'wgm_import_nonce')) { wp_send_json_error(['message' => __('Invalid security nonce. Please reload the page.', 'gmap-embed')]); } if (!current_user_can($this->capability)) { wp_send_json_error(['message' => __('You do not have permission to preview imports.', 'gmap-embed')]); } $type = isset($_POST['preview_type']) ? sanitize_text_field(wp_unslash($_POST['preview_type'])) : 'json'; $content = isset($_POST['content']) ? wp_unslash($_POST['content']) : ''; $max_rows = isset($_POST['max_rows']) ? intval($_POST['max_rows']) : 5; if (empty($content)) { wp_send_json_error(['message' => __('No content detected in the file.', 'gmap-embed')]); } if ($type === 'csv') { $rows_all = str_getcsv($content, "\n"); $header_row = array_shift($rows_all); $all_columns = $header_row ? str_getcsv($header_row) : []; if (!$all_columns) { wp_send_json_error(['message' => __('Failed to parse CSV header.', 'gmap-embed')]); } // Filter out columns we don't want to show (timestamps) $columns = array_values(array_filter($all_columns, function($c) { return !in_array($c, ['created_at', 'updated_at']); })); if (empty($columns)) { wp_send_json_error(['message' => __('Could not find valid columns in the CSV header.', 'gmap-embed')]); } $rows = []; $total_rows = 0; foreach ($rows_all as $row_str) { if (empty(trim($row_str))) continue; $row_data = str_getcsv($row_str); if (count($row_data) === count($all_columns)) { $total_rows++; // Only add to rows array if we haven't reached max_rows if (count($rows) < $max_rows) { $combined = array_combine($all_columns, $row_data); $filtered_row = []; foreach($columns as $c) { $filtered_row[$c] = isset($combined[$c]) ? $combined[$c] : ''; } $rows[] = $filtered_row; } } } wp_send_json_success([ 'type' => 'csv', 'columns' => $columns, 'rows' => $rows, 'total_rows' => $total_rows ]); } elseif ($type === 'json') { // Remove UTF-8 BOM if present $bom = pack('H*', 'EFBBBF'); if (substr($content, 0, 3) === $bom) { $content = substr($content, 3); } // JSON preview might fail if content is truncated (chunked) $data = json_decode($content, true); if (json_last_error() !== JSON_ERROR_NONE) { // If it failed and content is large, it's likely truncated if (strlen($content) >= 190000) { wp_send_json_error(['message' => __('The JSON file is too large to preview (truncated), but you can still attempt the import.', 'gmap-embed')]); } wp_send_json_error(['message' => __('Invalid JSON structure: ', 'gmap-embed') . json_last_error_msg()]); } wp_send_json_success([ 'type' => 'json', 'maps' => isset($data['maps']) ? $data['maps'] : [], 'markers' => isset($data['markers']) ? $data['markers'] : [], 'categories' => isset($data['categories']) ? $data['categories'] : [], ]); } wp_send_json_error(['message' => __('Unsupported preview format.', 'gmap-embed')]); } }