PATH:
home
/
cf7x
/
public_html
/
wp-content_
/
plugins
/
email-validator
/
includes
<?php /** * Admin interface for the Email Validator plugin. */ if ( ! defined( 'ABSPATH' ) ) { exit; } class Email_Validator_Admin { // Ensure REST API routes are registered public static function register_rest_api() { $admin = new self(); if (method_exists($admin, 'register_rest_routes')) { $admin->register_rest_routes(); } } // AJAX handler to list uploaded files public function ajax_list_uploaded_files() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => __( 'Unauthorized', 'email-validator' ) ], 403 ); } check_ajax_referer( 'eva_ajax', 'nonce' ); $files = get_option( 'eva_uploaded_files', [] ); if ( ! is_array( $files ) ) { $files = []; } error_log('EVA_DEBUG: Retrieved uploaded files: ' . print_r($files, true)); wp_send_json_success( [ 'files' => $files ] ); } // AJAX handler to delete an uploaded file by index public function ajax_delete_uploaded_file() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => __( 'Unauthorized', 'email-validator' ) ], 403 ); } check_ajax_referer( 'eva_ajax', 'nonce' ); $index = isset( $_POST['index'] ) ? intval( $_POST['index'] ) : -1; $uploads = get_option( 'eva_uploaded_files', [] ); if ( ! is_array( $uploads ) || $index < 0 || $index >= count( $uploads ) ) { wp_send_json_error( [ 'message' => __( 'Invalid file index.', 'email-validator' ) ], 400 ); } array_splice( $uploads, $index, 1 ); update_option( 'eva_uploaded_files', $uploads ); wp_send_json_success( [ 'files' => $uploads ] ); } // AJAX handler to fetch latest 50 validation records public function ajax_get_latest_validation_records() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => __( 'Unauthorized', 'email-validator' ) ], 403 ); } check_ajax_referer( 'eva_ajax', 'nonce' ); global $wpdb; $table = $wpdb->prefix . 'all_emails'; $table_exists = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table ) ); if ( ! $table_exists ) { wp_send_json_success( [ 'total' => 0, 'processed' => 0, 'pending' => 0, 'percent' => 0 ] ); } $total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM $table" ); $processed = (int) $wpdb->get_var( "SELECT COUNT(*) FROM $table WHERE status IS NOT NULL AND status != ''" ); $pending = (int) $wpdb->get_var( "SELECT COUNT(*) FROM $table WHERE status IS NULL OR status = ''" ); $percent = $total > 0 ? min(100, (int) floor(($processed / $total) * 100)) : 0; wp_send_json_success([ 'total' => $total, 'processed' => $processed, 'pending' => $pending, 'percent' => $percent ]); } public function register_rest_routes() { register_rest_route( 'email-validator/v1', '/emails/export', [ 'methods' => 'GET', 'callback' => [ $this, 'rest_export_emails_csv' ], 'permission_callback' => '__return_true', 'args' => [ 'scope' => [ 'type' => 'string', 'enum' => [ 'page', 'all' ], 'default' => 'page', 'sanitize_callback' => static function ( $value ) { return 'all' === $value ? 'all' : 'page'; }, ], ], ] ); register_rest_route( 'email-validator/v1', '/emails/meta', [ 'methods' => 'GET', 'callback' => [ $this, 'rest_get_emails_meta' ], 'permission_callback' => '__return_true', ] ); register_rest_route( 'email-validator/v1', '/emails', [ 'methods' => 'GET', 'callback' => [ $this, 'rest_get_emails' ], 'permission_callback' => '__return_true', 'args' => [ 'paged' => [ 'type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint', ], 'per_page' => [ 'type' => 'integer', 'default' => 20, 'sanitize_callback' => 'absint', ], ], ] ); } public function rest_get_emails( $request ) { global $wpdb; $table = $wpdb->prefix . 'all_emails'; $paged = max( 1, (int) $request->get_param( 'paged' ) ); $per_page = max( 1, (int) $request->get_param( 'per_page' ) ); $offset = ( $paged - 1 ) * $per_page; $search = trim( $request->get_param( 'search' ) ); $syn_status = trim( $request->get_param( 'syn_status' ) ); $status = trim( $request->get_param( 'status' ) ); $brand = trim( $request->get_param( 'brand' ) ); $table_exists = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table ) ); if ( ! $table_exists ) { return [ 'emails' => [], 'total' => 0, 'paged' => $paged, 'per_page' => $per_page ]; } $where = []; $params = []; if ( $search ) { $where[] = "email LIKE %s"; $params[] = '%' . $wpdb->esc_like( $search ) . '%'; } if ( $syn_status ) { $where[] = "syn_status = %s"; $params[] = $syn_status; } if ( $status ) { $where[] = "status = %s"; $params[] = $status; } if ( $brand ) { $where[] = "brand = %s"; $params[] = $brand; } $where_sql = $where ? ( 'WHERE ' . implode( ' AND ', $where ) ) : ''; $total_sql = "SELECT COUNT(*) FROM $table $where_sql"; $total = $params ? (int) $wpdb->get_var( $wpdb->prepare( $total_sql, ...$params ) ) : (int) $wpdb->get_var( $total_sql ); // Only select needed columns for performance $select_columns = '`id`,`email`,`first_name`,`last_name`,`status`,`syn_date`,`syn_status`,`log`,`brand`'; $data_sql = "SELECT $select_columns FROM $table $where_sql ORDER BY id DESC LIMIT %d OFFSET %d"; $data_params = $params; $data_params[] = $per_page; $data_params[] = $offset; $emails = $data_params ? $wpdb->get_results( $wpdb->prepare( $data_sql, ...$data_params ), ARRAY_A ) : $wpdb->get_results( $wpdb->prepare( $data_sql, $per_page, $offset ), ARRAY_A ); return [ 'emails' => $emails, 'total' => $total, 'paged' => $paged, 'per_page' => $per_page, ]; } public function rest_get_emails_meta( $request ) { global $wpdb; $table = $wpdb->prefix . 'all_emails'; $table_exists = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table ) ); if ( ! $table_exists ) { return [ 'brands' => [], 'syn_statuses' => [], 'statuses' => [] ]; } $brands = array_filter(array_map('trim', $wpdb->get_col( "SELECT DISTINCT brand FROM $table WHERE brand IS NOT NULL AND brand != '' ORDER BY brand ASC" ))); $syn_statuses = array_filter(array_map('trim', $wpdb->get_col( "SELECT DISTINCT syn_status FROM $table WHERE syn_status IS NOT NULL AND syn_status != '' ORDER BY syn_status ASC" ))); $statuses = array_filter(array_map('trim', $wpdb->get_col( "SELECT DISTINCT status FROM $table WHERE status IS NOT NULL AND status != '' ORDER BY status ASC" ))); return [ 'brands' => array_values(array_unique($brands)), 'syn_statuses' => array_values(array_unique($syn_statuses)), 'statuses' => array_values(array_unique($statuses)), ]; } /** * REST API handler to export filtered email list as CSV */ public function rest_export_emails_csv( $request ) { global $wpdb; $table = $wpdb->prefix . 'all_emails'; $paged = max( 1, (int) $request->get_param( 'paged' ) ); $per_page = max( 1, (int) $request->get_param( 'per_page' ) ); $offset = ( $paged - 1 ) * $per_page; $scope = 'all' === $request->get_param( 'scope' ) ? 'all' : 'page'; $search = trim( $request->get_param( 'search' ) ); $syn_status = trim( $request->get_param( 'syn_status' ) ); $status = trim( $request->get_param( 'status' ) ); $brand = trim( $request->get_param( 'brand' ) ); $table_exists = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table ) ); if ( ! $table_exists ) { return new WP_Error( 'no_table', __( 'Email table not found.', 'email-validator' ), [ 'status' => 404 ] ); } $where = []; $params = []; if ( $search ) { $where[] = "email LIKE %s"; $params[] = '%' . $wpdb->esc_like( $search ) . '%'; } if ( $syn_status ) { $where[] = "syn_status = %s"; $params[] = $syn_status; } if ( $status ) { $where[] = "status = %s"; $params[] = $status; } if ( $brand ) { $where[] = "brand = %s"; $params[] = $brand; } $where_sql = $where ? ( 'WHERE ' . implode( ' AND ', $where ) ) : ''; $select_columns = '`id`,`email`,`first_name`,`last_name`,`status`,`syn_date`,`syn_status`,`log`,`brand`'; $data_sql = "SELECT $select_columns FROM $table $where_sql ORDER BY id ASC"; $data_params = $params; if ( 'page' === $scope ) { $data_sql .= ' LIMIT %d OFFSET %d'; $data_params[] = $per_page; $data_params[] = $offset; } if ( ! empty( $data_params ) ) { $emails = $wpdb->get_results( $wpdb->prepare( $data_sql, ...$data_params ), ARRAY_A ); } else { $emails = $wpdb->get_results( $data_sql, ARRAY_A ); } // Output CSV using fputcsv for consistent quoting/escaping $csv = fopen( 'php://temp', 'r+' ); $header = [ 'id', 'email', 'first_name', 'last_name', 'status', 'syn_date', 'syn_status', 'log', 'brand' ]; fputcsv( $csv, $header ); foreach ( $emails as $row ) { $fields = []; foreach ( $header as $key ) { $fields[] = isset( $row[ $key ] ) && null !== $row[ $key ] ? $row[ $key ] : ''; } fputcsv( $csv, $fields ); } rewind( $csv ); $csv_data = stream_get_contents( $csv ); fclose( $csv ); $csv_data = str_replace( "\n", "\r\n", $csv_data ); return new WP_REST_Response( $csv_data, 200, [ 'Content-Type' => 'text/csv', 'Content-Disposition' => 'attachment; filename="email-list.csv"', ] ); } /** * Intercept REST responses and stream CSV payloads directly. * * @param bool $served Whether the request has already been served. * @param WP_REST_Response $result Result to send to the client. * @param WP_REST_Request $request Request used to generate the response. * @param WP_REST_Server $server Server instance. * @return bool Whether the request has been served. */ public function maybe_serve_csv_response( $served, $result, $request, $server ) { if ( ! $request instanceof WP_REST_Request ) { return $served; } if ( '/email-validator/v1/emails/export' !== $request->get_route() ) { return $served; } $data = $result->get_data(); if ( ! is_string( $data ) ) { return $served; } $headers = $result->get_headers(); $content_type = isset( $headers['Content-Type'] ) ? $headers['Content-Type'] : 'text/csv; charset=utf-8'; if ( ! headers_sent() ) { header( 'Content-Type: ' . $content_type ); if ( isset( $headers['Content-Disposition'] ) ) { header( 'Content-Disposition: ' . $headers['Content-Disposition'] ); } header( 'Content-Length: ' . strlen( $data ) ); } echo $data; return true; } public function __construct() { $this->service = Email_Validator_Service::instance(); add_action( 'admin_menu', [ $this, 'register_menu' ] ); add_action( 'rest_api_init', [ 'Email_Validator_Admin', 'register_rest_api' ] ); add_filter( 'rest_pre_serve_request', [ $this, 'maybe_serve_csv_response' ], 10, 4 ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); add_action( 'admin_post_email_validator_import', [ $this, 'handle_import' ] ); add_action( 'admin_post_email_validator_run', [ $this, 'handle_manual_run' ] ); // AJAX endpoints for upload + batched import add_action( 'wp_ajax_eva_upload_csv', [ $this, 'ajax_upload_csv' ] ); add_action( 'wp_ajax_eva_import_batch', [ $this, 'ajax_import_batch' ] ); // AJAX endpoint for listing uploaded files add_action( 'wp_ajax_eva_list_uploaded_files', [ $this, 'ajax_list_uploaded_files' ] ); // AJAX endpoint for deleting uploaded file add_action( 'wp_ajax_eva_delete_uploaded_file', [ $this, 'ajax_delete_uploaded_file' ] ); // AJAX endpoint for latest validation records add_action( 'wp_ajax_eva_get_latest_validation_records', [ $this, 'ajax_get_latest_validation_records' ] ); // AJAX endpoints for start/stop validation add_action( 'wp_ajax_eva_start_validation', [ $this, 'ajax_start_validation' ] ); add_action( 'wp_ajax_eva_stop_validation', [ $this, 'ajax_stop_validation' ] ); // AJAX endpoint for email list add_action( 'wp_ajax_eva_get_emails', [ $this, 'ajax_get_emails' ] ); add_filter( 'upload_mimes', [ $this, 'sf_allow_csv_uploads' ] , 10, 1 ); } public function sf_allow_csv_uploads( $mime_types ) { // Allow multiple MIME variants for CSV uploads. $csv_mimes = [ 'text/csv', 'text/plain', 'application/csv', 'application/vnd.ms-excel', ]; // Set the primary MIME type WordPress will check first. $mime_types['csv'] = 'application/csv'; // Hook into wp_check_filetype_and_ext to allow variants add_filter( 'wp_check_filetype_and_ext', function( $data, $file, $filename, $mimes ) use ( $csv_mimes ) { $ext = pathinfo( $filename, PATHINFO_EXTENSION ); if ( strtolower( $ext ) === 'csv' ) { $file_mime = mime_content_type( $file ); if ( in_array( $file_mime, $csv_mimes, true ) ) { $data['ext'] = 'csv'; $data['type'] = 'text/csv'; } } return $data; }, 999, 4 ); return $mime_types; } public function ajax_get_emails(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => __( 'Unauthorized', 'email-validator' ) ], 403 ); } check_ajax_referer( 'eva_ajax', 'nonce' ); global $wpdb; $table = $wpdb->prefix . 'all_emails'; $paged = isset( $_GET['paged'] ) ? max( 1, (int) $_GET['paged'] ) : 1; $per_page = isset( $_GET['per_page'] ) ? max( 1, (int) $_GET['per_page'] ) : 20; $offset = ( $paged - 1 ) * $per_page; $table_exists = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table ) ); if ( ! $table_exists ) { wp_send_json_success( [ 'emails' => [], 'total' => 0, 'paged' => $paged, 'per_page' => $per_page ] ); } $total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM $table" ); $emails = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table ORDER BY id DESC LIMIT %d OFFSET %d", $per_page, $offset ), ARRAY_A ); wp_send_json_success( [ 'emails' => $emails, 'total' => $total, 'paged' => $paged, 'per_page' => $per_page, ] ); } public function render_dashboard(): void { echo '<div class="wrap"><h1>' . esc_html__( 'Email Validator Dashboard', 'email-validator' ) . '</h1></div>'; } public function render_file_upload(): void { echo '<div class="wrap"><h1>' . esc_html__( 'File Upload (AJAX Import)', 'email-validator' ) . '</h1>'; echo '<div id="eva-admin-app"></div>'; // Optionally show uploaded files table here if needed } public function render_run_validation(): void { echo '<div class="wrap"><h1>' . esc_html__( 'Run Validation', 'email-validator' ) . '</h1>'; echo '<div id="eva-run-validation-app"></div>'; // The React app will be mounted here by the frontend bundle. } public function render_email_list(): void { echo '<div class="wrap"><h1>' . esc_html__( 'Email List', 'email-validator' ) . '</h1>'; echo '<div id="eva-email-list-app"></div>'; } private Email_Validator_Service $service; private const TRANSIENT_TTL = 900; // 15 minutes // Register AJAX endpoint for email list outside constructor // This ensures it's only registered once and avoids redeclaration errors // Add this after the class definition: // add_action( 'wp_ajax_eva_get_emails', [ $admin_instance, 'ajax_get_emails' ] ); public function register_menu(): void { add_menu_page( __( 'Email Validator', 'email-validator' ), __( 'Email Validator', 'email-validator' ), 'manage_options', 'email-validator', [ $this, 'render_dashboard' ], 'dashicons-email', 25 ); add_submenu_page( 'email-validator', __( 'Dashboard', 'email-validator' ), __( 'Dashboard', 'email-validator' ), 'manage_options', 'email-validator', [ $this, 'render_dashboard' ] ); add_submenu_page( 'email-validator', __( 'File Upload', 'email-validator' ), __( 'File Upload', 'email-validator' ), 'manage_options', 'email-validator-upload', [ $this, 'render_file_upload' ] ); add_submenu_page( 'email-validator', __( 'Run Validation', 'email-validator' ), __( 'Run Validation', 'email-validator' ), 'manage_options', 'email-validator-run', [ $this, 'render_run_validation' ] ); add_submenu_page( 'email-validator', __( 'Email List', 'email-validator' ), __( 'Email List', 'email-validator' ), 'manage_options', 'email-validator-list', [ $this, 'render_email_list' ] ); } // Removed unused render_page method after refactor. public function handle_import(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_die( __( 'You do not have permission to perform this action.', 'email-validator' ) ); } check_admin_referer( 'email_validator_import' ); if ( empty( $_FILES['email_validator_csv'] ) || ! isset( $_FILES['email_validator_csv']['tmp_name'] ) ) { wp_safe_redirect( $this->get_page_url( [ 'message' => 'no-file' ] ) ); exit; } $file = $_FILES['email_validator_csv']; if ( ! empty( $file['error'] ) ) { wp_safe_redirect( $this->get_page_url( [ 'message' => 'upload-error' ] ) ); exit; } require_once ABSPATH . 'wp-admin/includes/file.php'; $filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'] ); if ( empty( $filetype['ext'] ) || 'csv' !== $filetype['ext'] ) { wp_safe_redirect( $this->get_page_url( [ 'message' => 'invalid-type' ] ) ); exit; } $uploaded = wp_handle_upload( $file, [ 'test_form' => false ] ); if ( isset( $uploaded['error'] ) ) { wp_safe_redirect( $this->get_page_url( [ 'message' => 'upload-error' ] ) ); exit; } $stats = $this->service->import_csv( $uploaded['file'] ); if ( file_exists( $uploaded['file'] ) ) { unlink( $uploaded['file'] ); } $message = sprintf( /* translators: 1: processed count 2: inserted count 3: updated count */ __( 'Processed %1$d rows. Inserted: %2$d, Updated: %3$d.', 'email-validator' ), $stats['processed'], $stats['inserted'], $stats['updated'] ); wp_safe_redirect( $this->get_page_url( [ 'message' => rawurlencode( $message ) ] ) ); exit; } public function handle_manual_run(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_die( __( 'You do not have permission to perform this action.', 'email-validator' ) ); } check_admin_referer( 'email_validator_run' ); $limit = isset( $_POST['limit'] ) ? max( 1, min( 5000, (int) $_POST['limit'] ) ) : 500; if ( function_exists( 'email_validator_ensure_table' ) ) { email_validator_ensure_table(); } $results = $this->service->validate_pending_with_logs( $limit ); $this->set_last_run_results( $results ); $message = sprintf( /* translators: 1: processed count 2: updated count */ __( 'Validation completed. Processed %1$d, Updated %2$d.', 'email-validator' ), (int) $results['processed'], (int) $results['updated'] ); wp_safe_redirect( $this->get_page_url( [ 'message' => rawurlencode( $message ), 'show' => 'results' ] ) . '#run-results' ); exit; } // AJAX handler to start validation public function ajax_start_validation() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => __( 'Unauthorized', 'email-validator' ) ], 403 ); } check_ajax_referer( 'eva_ajax', 'nonce' ); $limit = isset( $_POST['limit'] ) ? max( 1, min( 5000, (int) $_POST['limit'] ) ) : 50; if ( function_exists( 'email_validator_ensure_table' ) ) { email_validator_ensure_table(); } $results = $this->service->validate_pending_with_logs( $limit ); $this->set_last_run_results( $results ); wp_send_json_success( [ 'results' => $results ] ); } // AJAX handler to stop validation (dummy, for UI) public function ajax_stop_validation() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => __( 'Unauthorized', 'email-validator' ) ], 403 ); } check_ajax_referer( 'eva_ajax', 'nonce' ); // For real async jobs, you would signal a cancel here wp_send_json_success( [ 'stopped' => true ] ); } private function get_status_message(): string { if ( empty( $_GET['message'] ) ) { return ''; } $raw = rawurldecode( (string) wp_unslash( $_GET['message'] ) ); return match ( $raw ) { 'no-file' => __( 'No file was uploaded.', 'email-validator' ), 'invalid-type' => __( 'Please upload a valid CSV file.', 'email-validator' ), 'upload-error' => __( 'File upload failed. Please try again.', 'email-validator' ), default => sanitize_text_field( $raw ), }; } private function get_page_url( array $args = [] ): string { $base = add_query_arg( [ 'page' => 'email-validator' ], admin_url( 'admin.php' ) ); if ( empty( $args ) ) { return $base; } return add_query_arg( $args, $base ); } public function enqueue_assets( $hook ): void { $main_file = dirname( __FILE__, 2 ) . '/email-validator.php'; $root_dir = dirname( plugin_dir_path( __FILE__ ) ); wp_enqueue_style( 'email-validator-admin', plugins_url( 'assets/email-validator-admin.css', $main_file ), [], file_exists( $root_dir . '/assets/email-validator-admin.css' ) ? filemtime( $root_dir . '/assets/email-validator-admin.css' ) : '1.0.0' ); // Enqueue main app for dashboard, upload, and list if ( 'toplevel_page_email-validator' === $hook || 'email-validator_page_email-validator-upload' === $hook || 'email-validator_page_email-validator-list' === $hook ) { $asset_file = $root_dir . '/build/index.asset.php'; $script_deps = [ 'wp-element', 'wp-i18n' ]; $script_version = file_exists( $root_dir . '/build/index.js' ) ? filemtime( $root_dir . '/build/index.js' ) : '1.0.0'; if ( file_exists( $asset_file ) ) { $asset = include $asset_file; if ( is_array( $asset ) ) { $script_deps = $asset['dependencies'] ?? $script_deps; $script_version = $asset['version'] ?? $script_version; } } wp_enqueue_script( 'email-validator-admin', plugins_url( 'build/index.js', $main_file ), $script_deps, $script_version, true ); $turbo_supported = apply_filters( 'email_validator_turbo_supported', $this->service->is_turbo_available() ); wp_localize_script( 'email-validator-admin', 'EVA', [ 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'restBase' => get_site_url(), 'nonce' => wp_create_nonce( 'eva_ajax' ), 'turboSupported' => (bool) $turbo_supported, ] ); } // Enqueue run-validation app for run page if ('email-validator_page_email-validator-run' === $hook) { $asset_file = $root_dir . '/build/run-validation.asset.php'; $script_deps = [ 'wp-element', 'wp-i18n' ]; $script_version = file_exists( $root_dir . '/build/run-validation.js' ) ? filemtime( $root_dir . '/build/run-validation.js' ) : '1.0.0'; if ( file_exists( $asset_file ) ) { $asset = include $asset_file; if ( is_array( $asset ) ) { $script_deps = $asset['dependencies'] ?? $script_deps; $script_version = $asset['version'] ?? $script_version; } } wp_enqueue_script( 'email-validator-run-validation', plugins_url( 'build/run-validation.js', $main_file ), $script_deps, $script_version, true ); $turbo_supported = apply_filters( 'email_validator_turbo_supported', $this->service->is_turbo_available() ); wp_localize_script( 'email-validator-run-validation', 'EVA', [ 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'eva_ajax' ), 'turboSupported' => (bool) $turbo_supported, ] ); } } public function ajax_upload_csv(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => __( 'Unauthorized', 'email-validator' ) ], 403 ); } check_ajax_referer( 'eva_ajax', 'nonce' ); if ( empty( $_FILES['file'] ) ) { wp_send_json_error( [ 'message' => __( 'No file uploaded.', 'email-validator' ) ], 400 ); } require_once ABSPATH . 'wp-admin/includes/file.php'; $file = $_FILES['file']; $uploaded = wp_handle_upload( $file, [ 'test_form' => false ] ); if ( isset( $uploaded['error'] ) ) { wp_send_json_error( [ 'message' => $uploaded['error'] ], 400 ); } $path = $uploaded['file']; // Save uploaded file info for meta box $uploads = get_option( 'eva_uploaded_files', [] ); $uploads[] = [ 'name' => $file['name'], 'size' => $file['size'], 'time' => time(), 'url' => isset($uploaded['url']) ? $uploaded['url'] : '', ]; update_option( 'eva_uploaded_files', $uploads ); error_log('EVA_DEBUG: Saved uploaded files: ' . print_r($uploads, true)); // Count lines quickly $total = 0; try { $spl = new SplFileObject( $path ); $spl->seek( PHP_INT_MAX ); $total = $spl->key() + 1; } catch ( Throwable $e ) { // Leave $total at 0 if counting fails. } // State for batch import $token = wp_generate_password( 20, false, false ); $has_header = ! empty( $_POST['hasHeader'] ) && '1' === (string) $_POST['hasHeader']; $turbo = ! empty( $_POST['turbo'] ) && '1' === (string) $_POST['turbo']; $turbo_supported = apply_filters( 'email_validator_turbo_supported', $this->service->is_turbo_available() ); $warning = ''; if ( $turbo && ! $turbo_supported ) { $turbo = false; $warning = __( 'Turbo mode is unavailable on this server. Import will use the normal engine.', 'email-validator' ); } $total_lines = (int) $total; $data_total = $total_lines; if ( $has_header && $data_total > 0 ) { $data_total--; } if ( $data_total < 0 ) { $data_total = 0; } $state = [ 'file' => $path, 'line' => 0, 'initialized' => false, 'header_map' => null, 'header_is_data'=> null, 'total_lines' => $total_lines, 'total' => $data_total, 'processed' => 0, 'inserted' => 0, 'updated' => 0, 'duplicates' => 0, 'failed' => 0, 'has_header' => (bool) $has_header, 'turbo' => (bool) $turbo, 'turbo_done' => false, 'notice' => $warning, ]; set_transient( 'eva_import_' . $token, $state, self::TRANSIENT_TTL ); wp_send_json_success( [ 'token' => $token, 'total' => $data_total, 'warn' => $warning ] ); } public function ajax_import_batch(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => __( 'Unauthorized', 'email-validator' ) ], 403 ); } check_ajax_referer( 'eva_ajax', 'nonce' ); $token = isset( $_POST['token'] ) ? sanitize_text_field( wp_unslash( $_POST['token'] ) ) : ''; $batch = isset( $_POST['batch'] ) ? max( 1, (int) $_POST['batch'] ) : 10000; $transient_key = 'eva_import_' . $token; $state = get_transient( $transient_key ); if ( ! is_array( $state ) || empty( $state['file'] ) || ! file_exists( $state['file'] ) ) { wp_send_json_error( [ 'message' => __( 'Invalid or expired token.', 'email-validator' ) ], 400 ); } $total_records = isset( $state['total'] ) ? (int) $state['total'] : 0; if ( $total_records <= 0 ) { $total_records = isset( $state['total_lines'] ) ? (int) $state['total_lines'] : 0; if ( ! empty( $state['has_header'] ) && $total_records > 0 ) { $total_records--; } if ( $total_records < 0 ) { $total_records = 0; } $state['total'] = $total_records; } if ( ! isset( $state['failed'] ) ) { $state['failed'] = 0; } if ( ! isset( $state['duplicates'] ) ) { $state['duplicates'] = (int) ( $state['updated'] ?? 0 ); } // Turbo mode uses MySQL LOAD DATA for maximum speed if ( ! empty( $state['turbo'] ) && empty( $state['turbo_done'] ) ) { $results = $this->service->turbo_import_load_data( $state['file'], [ 'has_header' => ! empty( $state['has_header'] ) ] ); if ( ! empty( $results['error'] ) ) { // Turbo failed; switch to normal mode and surface warning $state['turbo'] = false; $state['turbo_done'] = true; $state['notice'] = sprintf( /* translators: %s: database error message */ __( 'Turbo import failed (%s). Falling back to standard mode.', 'email-validator' ), wp_strip_all_tags( (string) $results['error'] ) ); } else { $state['processed'] = (int) ( $results['processed'] ?? $state['processed'] ); $state['inserted'] = (int) ( $results['inserted'] ?? $state['inserted'] ); $state['updated'] = (int) ( $results['updated'] ?? $state['updated'] ); $state['duplicates'] = (int) $state['updated']; $state['failed'] = max( 0, (int) $state['total'] - (int) $state['processed'] ); $state['turbo_done'] = true; $state['line'] = $state['total']; delete_transient( $transient_key ); wp_send_json_success( [ 'done' => true, 'percent' => 100, 'processed' => (int) $state['processed'], 'inserted' => (int) $state['inserted'], 'updated' => (int) $state['updated'], 'duplicates'=> (int) $state['duplicates'], 'failed' => (int) $state['failed'], 'total' => (int) $state['total'], 'remaining' => 0, 'notice' => __( 'Turbo import completed via LOAD DATA.', 'email-validator' ), ] ); } } $results = $this->service->import_csv_batch( $state['file'], $state, $batch ); $done = ! empty( $results['done'] ); $csv_total = isset($results['csv_total']) ? (int)$results['csv_total'] : (int)($state['csv_total'] ?? 0); $processed_total = (int) $state['processed']; $inserted_total = (int) $state['inserted']; $updated_total = (int) $state['updated']; $duplicates_total = (int) ( $state['duplicates'] ?? $updated_total ); $failed_total = (int) ( $state['failed'] ?? 0 ); $reported_total = max( 0, (int) $state['total'] ); $completed_total = $processed_total + $failed_total; if ( $reported_total > 0 ) { $remaining = max( 0, $reported_total - $completed_total ); $reported_total = $completed_total + $remaining; } else { $remaining = 0; $reported_total = $completed_total; } $percent = 0; if ( $reported_total > 0 ) { $percent = min( 100, (int) floor( ( $completed_total / $reported_total ) * 100 ) ); } if ( $done ) { $percent = 100; $remaining = 0; $reported_total = $completed_total; $state['total'] = $reported_total; delete_transient( $transient_key ); } else { $state['total'] = $reported_total; // Refresh TTL and save state with normalized totals. set_transient( $transient_key, $state, self::TRANSIENT_TTL ); } wp_send_json_success( [ 'done' => $done, 'percent' => $percent, 'csv_total' => $csv_total, 'processed' => $processed_total, 'inserted' => $inserted_total, 'updated' => $updated_total, 'duplicates'=> $duplicates_total, 'failed' => $failed_total, 'total' => $reported_total, 'remaining' => $remaining, 'notice' => ! empty( $state['notice'] ) ? sanitize_text_field( $state['notice'] ) : '', ] ); } private function get_transient_key(): string { $user_id = get_current_user_id(); return 'email_validator_last_run_' . ( $user_id ? (string) $user_id : '0' ); } private function set_last_run_results( array $results ): void { set_transient( $this->get_transient_key(), [ 'time' => current_time( 'mysql' ), 'results' => $results ], self::TRANSIENT_TTL ); } private function get_last_run_results(): array { $data = get_transient( $this->get_transient_key() ); return is_array( $data ) ? $data : []; } }
[-] class-email-validator-service.php
[open]
[+]
..
[-] class-email-validator-admin.php
[open]