Plugin Directory

source: userswp/trunk/includes/class-import-export.php

Last change on this file was 3491454, checked in by stiofansisland, 2 days ago

Update to version 1.2.58 from GitHub

File size: 32.3 KB
Line 
1<?php
2/**
3 * UsersWP Notice display functions.
4 *
5 * All UsersWP notice display related functions can be found here.
6 *
7 * @since      1.0.0
8 * @author     GeoDirectory Team <info@wpgeodirectory.com>
9 */
10class UsersWP_Import_Export {
11    private $wp_filesystem;
12    private $export_dir;
13    private $export_url;
14    public $per_page;
15    public $meta_table_name;
16    public $path;
17    public $total_rows;
18    public $imp_step;
19    public $skipped;
20        private $empty;
21        private $step;
22        private $file;
23        private $filename;
24
25    /**
26     * Columns that are NEVER writable via CSV import.
27     * Denylist wins over allowlist — a column here can never be accidentally
28     * re-enabled by adding it to $import_meta_allowlist.
29     *
30     * @var array
31     */
32    private static $import_meta_denylist = array(
33        'user_id',       // Primary key — never importable.
34        'old_password',  // Credential-adjacent — must not be set via import.
35    );
36
37    /**
38     * Columns that ARE permitted in a CSV import (positive / allowlist).
39     * Everything not listed here is silently skipped — default-deny.
40     * Add new safe meta keys here deliberately; never use a wildcard.
41     *
42     * @var array
43     */
44    private static $import_meta_allowlist = array(
45        // Core WP user fields
46        'username',
47        'email',
48        'display_name',
49        'first_name',
50        'last_name',
51        'description',
52        'user_url',
53        'user_registered',
54        'role',
55        // uwp_usermeta safe fields
56        'bio',
57        'phone',
58        'user_privacy',
59        'avatar_thumb',
60        'banner_thumb',
61    );
62
63
64    public function __construct() {
65        global $wp_filesystem;
66
67        if ( empty( $wp_filesystem ) ) {
68            require_once( ABSPATH . '/wp-admin/includes/file.php' );
69            WP_Filesystem();
70            global $wp_filesystem;
71        }
72
73        $this->wp_filesystem    = $wp_filesystem;
74        $this->export_dir       = $this->export_location();
75        $this->export_url       = $this->export_location( true );
76        $this->per_page         = apply_filters('uwp_import_export_per_page', 20, $this);
77        $this->meta_table_name  = get_usermeta_table_prefix() . 'uwp_usermeta';
78        $this->path  = '';
79
80        add_action( 'admin_init', array($this, 'process_settings_export') );
81        add_action( 'admin_init', array($this, 'process_settings_import') );
82        add_action( 'wp_ajax_uwp_ajax_export_users', array( $this, 'process_users_export' ) );
83        add_action( 'wp_ajax_uwp_ajax_import_users', array( $this, 'process_users_import' ) );
84        add_action( 'wp_ajax_uwp_ie_upload_file', array( $this, 'ie_upload_file' ) );
85        add_action( 'admin_notices', array($this, 'ie_admin_notice') );
86        add_filter( 'uwp_get_export_users_status', array( $this, 'get_export_users_status' ) );
87        add_filter( 'uwp_get_import_users_status', array( $this, 'get_import_users_status' ) );
88     }
89
90        /**
91         * Returns export file location
92         *
93         * @package     userswp
94         *
95         * @param       bool     $relative
96         *
97         * @return      string
98     *
99         */
100    public function export_location( $relative = false ) {
101        $upload_dir         = wp_upload_dir();
102        $export_location    = $relative ? trailingslashit( $upload_dir['baseurl'] ) . 'cache' : trailingslashit( $upload_dir['basedir'] ) . 'cache';
103        $export_location    = apply_filters( 'uwp_export_location', $export_location, $relative );
104
105        return trailingslashit( $export_location );
106    }
107
108        /**
109         * Displays notice
110         */
111    public function ie_admin_notice(){
112        if(isset($_GET['imp-msg']) && 'success' == $_GET['imp-msg']){
113            ?>
114            <div class="notice notice-success is-dismissible">
115                <p><?php esc_html_e( 'Settings imported successfully!', 'userswp' ); ?></p>
116            </div>
117            <?php
118        }
119    }
120
121    /**
122     * Process a settings export that generates a .json file of the settings
123     */
124    public function process_settings_export() {
125        if( empty( $_POST['uwp_ie_action'] ) || 'export_settings' != $_POST['uwp_ie_action'] )
126            return;
127        if( ! wp_verify_nonce( $_POST['uwp_export_nonce'], 'uwp_export_nonce' ) )
128            return;
129        if( ! current_user_can( 'manage_options' ) )
130            return;
131        $settings = get_option( 'uwp_settings' );
132        ignore_user_abort( true );
133        nocache_headers();
134        header( 'Content-Type: application/json; charset=utf-8' );
135        header( 'Content-Disposition: attachment; filename=uwp-settings-export-' . date( 'm-d-Y' ) . '.json' );
136        header( "Expires: 0" );
137        echo json_encode( $settings );
138        exit;
139    }
140
141    /**
142     * Process a settings import from a json file
143     */
144    public function process_settings_import() {
145        if( empty( $_POST['uwp_ie_action'] ) || 'import_settings' != $_POST['uwp_ie_action'] )
146            return;
147        if( ! wp_verify_nonce( $_POST['uwp_import_nonce'], 'uwp_import_nonce' ) )
148            return;
149        if( ! current_user_can( 'manage_options' ) )
150            return;
151        $extension = explode( '.', $_FILES['import_file']['name'] );
152        $extension = end( $extension );
153        if( $extension != 'json' ) {
154            wp_die( esc_html( wp_sprintf( __( 'Please upload a valid .json file. %sGo Back%s', 'userswp' ), '<a href="' . esc_url( admin_url( 'admin.php?page=userswp&tab=import-export&section=settings' ) ) . '">', '</a>' ) ) );
155        }
156        $import_file = $_FILES['import_file']['tmp_name'];
157        if( empty( $import_file ) ) {
158            wp_die( esc_html( wp_sprintf( __( 'Please upload a file to import. %sGo Back%s', 'userswp' ), '<a href="' . esc_url( admin_url( 'admin.php?page=userswp&tab=import-export&section=settings' ) ) . '">', '</a>' ) ) );
159        }
160        // Retrieve the settings from the file and convert the json object to an array.
161        $settings = (array) json_decode( file_get_contents( $import_file ), true );
162        update_option( 'uwp_settings', $settings );
163        wp_safe_redirect( admin_url( 'admin.php?page=userswp&tab=import-export&section=settings&imp-msg=success' ) ); exit;
164    }
165
166        /**
167         * Processes users export
168         */
169    public function process_users_export(){
170
171        $response               = array();
172        $response['success']    = false;
173        $response['msg']        = __( 'Invalid export request found.', 'userswp' );
174
175        if ( empty( $_POST['data'] ) || !current_user_can( 'manage_options' ) ) {
176            wp_send_json( $response );
177        }
178
179        parse_str( $_POST['data'], $data );
180
181        $data['step']   = !empty( $_POST['step'] ) ? absint( $_POST['step'] ) : 1;
182
183        $_REQUEST = (array)$data;
184        if ( !( !empty( $_REQUEST['uwp_export_users_nonce'] ) && wp_verify_nonce( $_REQUEST['uwp_export_users_nonce'], 'uwp_export_users_nonce' ) ) ) {
185            $response['msg']    = __( 'Security check failed.', 'userswp' );
186            wp_send_json( $response );
187        }
188
189        if ( ( $error = $this->check_export_location() ) !== true ) {
190            $response['msg'] = __( 'Filesystem ERROR: ' . $error, 'userswp' );
191            wp_send_json( $response );
192        }
193
194        $this->set_export_params( $_REQUEST );
195
196        $return = $this->process_export_step();
197        $done   = $this->get_export_status();
198
199        if ( $return ) {
200            $this->step += 1;
201
202            $response['success']    = true;
203            $response['msg']        = '';
204
205            if ( $done >= 100 ) {
206                $this->step     = 'done';
207                $new_filename   = 'uwp-users-export-' . date( 'y-m-d-H-i' ) . '-' . wp_generate_password(12, false ) . '.csv';
208                $new_file       = $this->export_dir . $new_filename;
209
210                if ( file_exists( $this->file ) ) {
211                    $this->wp_filesystem->move( $this->file, $new_file, true );
212                }
213
214                if ( file_exists( $new_file ) ) {
215                    $response['data']['file'] = array( 'u' => $this->export_url . $new_filename, 's' => size_format( filesize( $new_file ), 2 ) );
216                }
217            }
218
219            $response['data']['step']   = $this->step;
220            $response['data']['done']   = $done;
221        } else {
222            $response['msg']    = __( 'No data found for export.', 'userswp' );
223        }
224
225        wp_send_json( $response );
226
227    }
228
229        /**
230         * Sets export params
231         *
232         * @package     userswp
233         *
234         * @param       array     $request
235         *
236         */
237    public function set_export_params( $request ) {
238        $this->empty    = false;
239        $this->step     = !empty( $request['step'] ) ? absint( $request['step'] ) : 1;
240        $this->filename = 'uwp-users-export-temp.csv';
241        $this->file     = $this->export_dir . $this->filename;
242        $chunk_per_page = !empty( $request['uwp_ie_chunk_size'] ) ? absint( $request['uwp_ie_chunk_size'] ) : 0;
243        $this->per_page = $chunk_per_page < 50 || $chunk_per_page > 100000 ? 5000 : $chunk_per_page;
244
245        do_action( 'uwp_export_users_set_params', $request );
246    }
247
248        /**
249         * Returns export file location
250         *
251         * @package     userswp
252         *
253         * @return      bool
254         *
255         */
256    public function check_export_location() {
257        try {
258            if ( empty( $this->wp_filesystem ) ) {
259                return __( 'Filesystem ERROR: Could not access filesystem.', 'userswp' );
260            }
261
262            if ( is_wp_error( $this->wp_filesystem ) ) {
263                return __( 'Filesystem ERROR: ' . $this->wp_filesystem->get_error_message(), 'userswp' );
264            }
265
266            $is_dir         = $this->wp_filesystem->is_dir( $this->export_dir );
267            $is_writeable   = $is_dir && is_writeable( $this->export_dir );
268
269            if ( $is_dir && $is_writeable ) {
270
271            } else if ( $is_dir && !$is_writeable ) {
272                if ( !$this->wp_filesystem->chmod( $this->export_dir, FS_CHMOD_DIR ) ) {
273                    return wp_sprintf( __( 'Filesystem ERROR: Export location %s is not writable, check your file permissions.', 'userswp' ), $this->export_dir );
274                }
275            } else {
276                if ( !$this->wp_filesystem->mkdir( $this->export_dir, FS_CHMOD_DIR ) ) {
277                    return wp_sprintf( __( 'Filesystem ERROR: Could not create directory %s. This is usually due to inconsistent file permissions.', 'userswp' ), $this->export_dir );
278                }
279            }
280
281                if(!$this->wp_filesystem->exists( $this->export_dir . '/index.php')){
282                        $this->wp_filesystem->copy( USERSWP_PATH . 'assets/index.php', $this->export_dir . '/index.php' );
283                }
284
285                return true;
286        } catch ( Exception $e ) {
287            return $e->getMessage();
288        }
289    }
290
291        /**
292         * Processes export step
293         *
294         * @return      bool
295         *
296         */
297    public function process_export_step() {
298        if ( $this->step < 2 ) {
299            @unlink( $this->file );
300            $this->print_columns();
301        }
302
303        $return = $this->print_rows();
304
305        if ( $return ) {
306            return true;
307        } else {
308            return false;
309        }
310    }
311
312        /**
313         * Prints columns
314         *
315         * @return      array
316         *
317         */
318    public function print_columns() {
319        $column_data    = '';
320        $columns        = $this->get_columns();
321        $i              = 1;
322        foreach( $columns as $key => $column ) {
323            $column_data .= '"' . addslashes( $column ) . '"';
324            $column_data .= $i == count( $columns ) ? '' : ',';
325            $i++;
326        }
327        $column_data .= "\r\n";
328
329        $this->attach_export_data( $column_data );
330
331        return $column_data;
332    }
333
334        /**
335         * Returns export columns
336         *
337         * @return      array
338         *
339         */
340    public function get_columns() {
341        global $wpdb;
342        $columns = array();
343
344        foreach ( $wpdb->get_col( "DESC " . $this->meta_table_name, 0 ) as $column_name ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
345            $columns[] = $column_name;
346        }
347
348        return apply_filters( 'uwp_export_users_get_columns', $columns );
349    }
350
351        /**
352         * Returns export data
353         *
354         * @package     userswp
355         *
356         * @return      array
357         *
358         */
359    public function get_export_data() {
360        global $wpdb;
361        if(!$this->step){
362            $page = 1;
363        } else {
364            $page = $this->step;
365        }
366
367        $page_start = absint( ( $page - 1 ) * $this->per_page );
368        $data = $wpdb->get_results( "SELECT * FROM $this->meta_table_name WHERE 1=1 LIMIT $page_start,". $this->per_page); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
369        $i = 0;
370
371        foreach ($data as $u){
372            $user = get_userdata($u->user_id);
373            $data[$i]->username = $user->user_login;
374            $data[$i]->email = $user->user_email;
375            $data[$i]->bio = $user->description;
376            $i++;
377        }
378
379        return apply_filters( 'uwp_export_users_get_data', $data );
380    }
381
382        /**
383         * Returns export status
384         *
385         * @return      int
386         *
387         */
388    public function get_export_status() {
389        $status = 100;
390        return apply_filters( 'uwp_get_export_users_status', $status );
391    }
392
393        /**
394         * Prints CSV rows
395         *
396         * @return      string
397         *
398         */
399    public function print_rows() {
400        $row_data   = '';
401        $data       = $this->get_export_data();
402        $columns    = $this->get_columns();
403
404        if ( is_array($data) && !empty($data) ) {
405            foreach ( $data as $row ) {
406                $i = 1;
407                foreach ( $row as $key => $column ) {
408                    $column = $this->escape_data( $column );
409                    $row_data .= '"' . addslashes( preg_replace( "/\"/","'", $column ) ) . '"';
410                    $row_data .= $i == count( $columns ) ? '' : ',';
411                    $i++;
412                }
413                $row_data .= "\r\n";
414            }
415
416            $this->attach_export_data( $row_data );
417
418            return $row_data;
419        }
420
421        return false;
422    }
423
424        /**
425         * Returns export file content
426         *
427         * @return      string
428         *
429         */
430    protected function get_export_file() {
431        $file = '';
432
433        if ( $this->wp_filesystem->exists( $this->file ) ) {
434            $file = $this->wp_filesystem->get_contents( $this->file );
435        } else {
436            $this->wp_filesystem->put_contents( $this->file, '' );
437        }
438
439        return $file;
440    }
441
442        /**
443         * Adds export data to CSV file
444         *
445         * @package     userswp
446         *
447         * @param       string     $data
448         *
449         */
450    protected function attach_export_data( $data = '' ) {
451        $filedata   = $this->get_export_file();
452        $filedata   .= $data;
453
454        $this->wp_filesystem->put_contents( $this->file, $filedata );
455
456        $rows       = file( $this->file, FILE_SKIP_EMPTY_LINES );
457        $columns    = $this->get_columns();
458        $columns    = empty( $columns ) ? 0 : 1;
459
460        $this->empty = count( $rows ) == $columns ? true : false;
461    }
462
463        /**
464         * Returns export user status
465         *
466         * @return      int
467         *
468         */
469    public function get_export_users_status() {
470        global $wpdb;
471        $data       = $wpdb->get_results("SELECT user_id FROM $this->meta_table_name WHERE 1=1"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
472        $total      = !empty( $data ) ? count( $data ) : 0;
473        $status     = 100;
474
475        if ( $this->per_page > $total ) {
476            $this->per_page = $total;
477        }
478
479        if ( $total > 0 ) {
480            $status = ( ( $this->per_page * $this->step ) / $total ) * 100;
481        }
482
483        if ( $status > 100 ) {
484            $status = 100;
485        }
486
487        return $status;
488    }
489
490        /**
491         * Uploaded file handling
492         *
493         * @return      string
494         *
495         */
496    public function ie_upload_file(){
497
498        if ( ! current_user_can( 'manage_options' ) ) {
499            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'userswp' ) ), 403 );
500        }
501
502        check_ajax_referer( 'uwp-ie-file-upload-nonce', 'nonce' );
503
504        $upload_data = array(
505            'name'     => $_FILES['import_file']['name'],
506            'type'     => $_FILES['import_file']['type'],
507            'tmp_name' => $_FILES['import_file']['tmp_name'],
508            'error'    => $_FILES['import_file']['error'],
509            'size'     => $_FILES['import_file']['size']
510        );
511
512        header('Content-Type: text/html; charset=' . get_option('blog_charset'));
513
514            add_filter( 'upload_mimes', array( $this, 'allowed_upload_mimes' ) );
515        $uploaded_file = wp_handle_upload( $upload_data, array('test_form' => false) );
516            remove_filter( 'upload_mimes', array( $this, 'allowed_upload_mimes' ) );
517
518        if ( isset( $uploaded_file['url'] ) ) {
519            $file_loc = $uploaded_file['url'];
520            echo esc_url( $file_loc );exit;
521        } else {
522            echo 'error';
523        }
524        exit;
525    }
526
527        /**
528         * Processes users import
529         *
530         */
531    public function process_users_import(){
532
533        $response               = array();
534        $response['success']    = false;
535        $response['msg']        = __( 'Invalid import request found.', 'userswp' );
536
537        if ( empty( $_POST['data'] ) || !current_user_can( 'manage_options' ) ) {
538            wp_send_json( $response );
539        }
540
541        parse_str( $_POST['data'], $data );
542
543        $this->imp_step   = !empty( $_POST['step'] ) ? absint( $_POST['step'] ) : 1;
544
545        $_REQUEST = (array)$data;
546        if ( !( !empty( $_REQUEST['uwp_import_users_nonce'] ) && wp_verify_nonce( $_REQUEST['uwp_import_users_nonce'], 'uwp_import_users_nonce' ) ) ) {
547            $response['msg']    = __( 'Security check failed.', 'userswp' );
548            wp_send_json( $response );
549        }
550
551        $allowed = array('csv');
552        $import_file = $data['uwp_import_users_file'];
553        $uploads      = wp_upload_dir();
554        $csv_file_array = explode( '/', $import_file );
555        $csv_filename = end( $csv_file_array );
556        $this->path = $uploads['path'].'/'.$csv_filename;
557
558        $ext = pathinfo($csv_filename, PATHINFO_EXTENSION);
559        if (!in_array($ext, $allowed)) {
560            $response['msg']    = __( 'Invalid file type, please upload .csv file.', 'userswp' );
561            wp_send_json( $response );
562        }
563
564        $lc_all = setlocale( LC_ALL, 0 );
565        setlocale( LC_ALL, 'en_US.UTF-8' );
566        if ( ( $handle = fopen( $this->path, "r" ) ) !== false ) {
567            while ( ( $data = fgetcsv( $handle, 100000, "," ) ) !== false ) {
568                if ( ! empty( $data ) ) {
569                    $file[] = $data;
570                }
571            }
572            fclose( $handle );
573        }
574        setlocale( LC_ALL, $lc_all );
575
576        $this->total_rows = ( ! empty( $file ) && count( $file ) > 1 ) ? count( $file ) - 1 : 0;
577
578        $return = $this->process_import_step();
579        $done   = $this->get_import_status();
580
581        if ( $return['success'] ) {
582            $response['total']['msg'] = '';
583            if($this->total_rows > 0 && $this->imp_step == 1) {
584                $response['total']['msg'] = __( 'Total '.$this->total_rows. ' item(s) found.', 'userswp' );
585            }
586
587            $this->imp_step += 1;
588
589            $response['success']    = true;
590            $response['msg'] = '';
591
592            if ( $done >= 100 ) {
593                $this->imp_step     = 'done';
594                $response['msg']    = __( 'Users import completed.', 'userswp' );
595            }
596
597            $response['data']['msg']    = ! empty( $return['msg'] ) ? $return['msg'] : $response['msg'];
598            $response['data']['step']   = $this->imp_step;
599            $response['data']['done']   = $done;
600        } else {
601            $response['msg']    = __( 'No valid data found for import.', 'userswp' );
602        }
603
604        wp_send_json( $response );
605
606    }
607
608        /**
609         * Processes import step
610         *
611         * @return      array
612         *
613         */
614    public function process_import_step() {
615
616        $errors = new WP_Error();
617        if(is_null($this->path)){
618            $errors->add('no_csv_file', __('No csv file found.','userswp'));
619        }
620
621        set_time_limit(0);
622
623        $return = array('success' => false);
624
625        $rows = $this->get_csv_rows($this->imp_step, 1);
626
627        if ( ! empty( $rows ) ) {
628            foreach ( $rows as $row ) {
629                if( empty($row) ) {
630                    $return['msg'] = sprintf(__('Row - %s Error: '. 'Skipped due to invalid/no data.','userswp'), $this->imp_step);
631                    continue;
632                }
633
634                $username = isset($row['username']) ? $row['username'] : '';
635                $email = isset($row['email']) ? $row['email'] : '';
636                $first_name = isset($row['first_name']) ? $row['first_name'] : '';
637                $last_name = isset($row['last_name']) ? $row['last_name'] : '';
638                $bio = isset($row['bio']) ? $row['bio'] : '';
639                $display_name = isset($row['display_name']) ? $row['display_name'] : '';
640                $password = wp_generate_password();
641                $exclude = array('user_id');
642                $exclude = apply_filters('uwp_import_exclude_columns', $exclude, $row);
643
644                if(isset($row['username']) && username_exists($row['username'])){
645                    $user = get_user_by('login', $row['username']);
646                    $user_id = $user->ID;
647                    $email = $row['email'];
648                    if( !empty( $email ) && $update_existing = apply_filters('uwp_import_update_users', false, $row, $user_id) ) {
649                        $args = array(
650                            'ID'         => $user_id,
651                            'user_email' => $email,
652                            'display_name' => $display_name,
653                            'first_name' => $first_name,
654                            'last_name' => $last_name,
655                            'description' => $bio,
656                        );
657                        wp_update_user( $args );
658                    }
659                } elseif(isset($row['email']) && email_exists($row['email'])){
660                    $user = get_user_by('email', $row['email']);
661                    $user_id = $user->ID;
662                } elseif((int)$row['user_id'] > 0){
663                    $user = get_user_by('ID', $row['user_id']);
664                    if(false === $user){
665                        $userdata = array(
666                            'user_login'  =>  $row['username'],
667                            'user_email'  =>  $email,
668                            'user_pass'   =>  $password,
669                            'first_name' => $first_name,
670                            'last_name' => $last_name,
671                            'description' => $bio,
672                            'display_name'=>  $display_name
673                        );
674                        $user_id = wp_insert_user( $userdata );
675                            $notify = apply_filters('uwp_import_user_notify', 'user', $user_id);
676                            wp_new_user_notification($user_id,null, $notify); //send password reset link
677                    } else {
678                        if( $user->user_login == $row['username'] ) { //check id passed in csv and existing username are same
679                            $user_id = $row['user_id'];
680                            if( !empty( $email ) && $email != $user->user_email && $update_existing = apply_filters('uwp_import_update_users', false, $row, $user_id)) {
681                                $args = array(
682                                    'ID'         => $user_id,
683                                    'user_email' => $email,
684                                    'first_name' => $first_name,
685                                    'last_name' => $last_name,
686                                    'description' => $bio,
687                                    'display_name' => $display_name
688                                );
689                                wp_update_user( $args );
690                            }
691                        } else {
692                            $return['msg'] = sprintf(__('Row - %s Error: '. 'User could not be created.','userswp'), $this->imp_step);
693                            continue;
694                        }
695                    }
696                } else {
697                    $user_id = wp_create_user( $username, $password, $email );
698                        if( !is_wp_error( $user_id ) ) {
699                                $args = array(
700                                        'ID'           => $user_id,
701                                        'first_name'   => $first_name,
702                                        'last_name'    => $last_name,
703                                        'description'  => $bio,
704                                        'display_name' => $display_name
705                                );
706                                wp_update_user( $args );
707                        }
708                }
709
710                if( !is_wp_error( $user_id ) ){
711                    foreach ($row as $key => $value){
712                        //Only write columns on the allowlist; denylist always wins.
713                        if ( ! $this->is_importable_column( $key ) ) {
714                            continue;
715                        }
716                        //Never deserialize CSV input. Cast to safe scalar string.
717                        $value = $this->sanitize_import_value( $key, $value );
718                        uwp_update_usermeta($user_id, $key, $value);
719                    }
720                } else {
721                    $return['msg'] = sprintf(__('Row - %s Error: %s','userswp'), $this->imp_step, $user_id->get_error_message());
722                    continue;
723                }
724            }
725            $return['success'] = true;
726        }
727
728        return $return;
729    }
730
731        /**
732         * Returns CSV row
733         *
734         * @package     userswp
735         *
736         * @param       int     $row
737         * @param       int     $count
738         *
739         * @return      mixed
740         *
741         */
742    public function get_csv_rows( $row = 0, $count = 1 ) {
743
744        $lc_all = setlocale( LC_ALL, 0 ); // Fix issue of fgetcsv ignores special characters when they are at the beginning of line
745        setlocale( LC_ALL, 'en_US.UTF-8' );
746        $l = $f =0;
747        $headers = $file = array();
748        $userdata_fields = $this->get_columns();
749        if ( ( $handle = fopen( $this->path, "r" ) ) !== false ) {
750            while ( ( $line = fgetcsv( $handle, 100000, "," ) ) !== false ) {
751                // If the first line is empty, abort
752                // If another line is empty, just skip it
753                if ( empty( $line ) ) {
754                    if ( $l === 0 )
755                        break;
756                    else
757                        continue;
758                }
759
760                // If we are on the first line, the columns are the headers
761                if ( $l === 0 ) {
762                    $headers = $line;
763                    $l ++;
764                    continue;
765                }
766
767                // only get the rows needed
768                if ( $row && $count ) {
769
770                    // if we have everything we need then break;
771                    if ( $l == $row + $count ) {
772                        break;
773
774                        // if its less than the start row then continue;
775                    } elseif ( $l && $l < $row ) {
776                        $l ++;
777                        continue;
778
779                        // if we have the count we need then break;
780                    } elseif ( $f > $count ) {
781                        break;
782                    }
783                }
784
785                // Separate user data from meta
786                $userdata = $usermeta = array();
787                foreach ( $line as $ckey => $column ) {
788                    $column_name = $headers[$ckey];
789                    $column = trim( $column );
790                    if ( in_array( $column_name, $userdata_fields ) ) {
791                        $userdata[$column_name] = $column;
792                    } else {
793                        $usermeta[$column_name] = $column;
794                    }
795                }
796
797                // A plugin may need to filter the data and meta
798                $userdata = apply_filters( 'uwp_import_userdata', $userdata, $usermeta );
799
800                if ( ! empty( $userdata ) ) {
801                    $file[] = $userdata;
802                    $f ++;
803                    $l ++;
804                }
805            }
806            fclose( $handle );
807        }
808        setlocale( LC_ALL, $lc_all );
809
810        return $file;
811
812    }
813
814        /**
815         * Returns import status
816         *
817         * @return      int
818         *
819         */
820    public function get_import_status() {
821        $status = 100;
822        return apply_filters( 'uwp_get_import_users_status', $status );
823    }
824
825        /**
826         * Returns import user status
827         *
828         * @return      int
829         *
830         */
831    public function get_import_users_status() {
832
833        if ( $this->imp_step >= $this->total_rows ) {
834            $status = 100;
835        } else {
836            $status = ( ( 1 * $this->imp_step ) / $this->total_rows ) * 100;
837        }
838
839        return $status;
840    }
841
842    public function allowed_upload_mimes($mimes = array()) {
843            $mimes['csv'] = "text/csv";
844            return $mimes;
845    }
846
847    /**
848     * Sanitize a single CSV import value.
849     *
850     * CSV data is always a plain string. There is no legitimate reason for it
851     * to contain serialized PHP. We detect the serialization type-prefix
852     * signatures, reject them with a log entry, and return an empty string.
853     * All other values are cast to string and sanitized with sanitize_text_field().
854     *
855     * @param  string $key   The CSV column / meta key name.
856     * @param  mixed  $value The raw value from the CSV row.
857     * @return string        A safe scalar string ready for DB insertion.
858     */
859    private function sanitize_import_value( $key, $value ) {
860        // Reject serialized payloads
861        if ( is_string( $value ) && preg_match( '/^[aAbBdDiIoOsScCnN][:;]/', ltrim( $value ) ) ) {
862            if ( function_exists( 'uwp_log' ) ) {
863                uwp_log( sprintf( 'Import security: serialized payload in column "%s" — discarded.', esc_attr( $key ) ) );
864            }
865            return '';
866        }
867
868        // File path columns: validate as a URL pointing inside the uploads directory only
869        if ( in_array( $key, array( 'avatar_thumb', 'banner_thumb' ), true ) ) {
870            return $this->sanitize_import_thumb( $value );
871        }
872
873        return sanitize_text_field( (string) $value );
874    }
875
876    /**
877     * Sanitizes a thumbnail path value from CSV import.
878     *
879     * Validates that the given path is a real, existing file located within
880     * the WordPress uploads directory, preventing path traversal attacks and
881     * references to arbitrary files outside the uploads directory.
882     *
883     * @param  string $value Raw thumbnail path value from the CSV row.
884     * @return string        Resolved absolute path if valid, empty string otherwise.
885     */
886    private function sanitize_import_thumb( $value ) {
887        $value = trim( (string) $value );
888
889        if ( empty( $value ) ) {
890            return '';
891        }
892
893        // Resolve any ../ traversal attempts before comparison
894        $real = realpath( $value );
895
896        if ( $real === false ) {
897            return ''; // Path doesn't exist on disk — reject
898        }
899
900        // Must stay within the uploads directory
901        $uploads  = wp_upload_dir();
902        $base_dir = trailingslashit( realpath( $uploads['basedir'] ) );
903
904        if ( strpos( $real . DIRECTORY_SEPARATOR, $base_dir ) !== 0 ) {
905            if ( function_exists( 'uwp_log' ) ) {
906                uwp_log( sprintf(
907                    'Import security: thumb path "%s" is outside uploads directory — discarded.',
908                    esc_attr( $value )
909                ) );
910            }
911            return '';
912        }
913
914        // Must be an allowed image extension
915        $ext = strtolower( pathinfo( $real, PATHINFO_EXTENSION ) );
916        if ( ! in_array( $ext, array( 'jpg', 'jpeg', 'png', 'gif', 'webp' ), true ) ) {
917            return '';
918        }
919
920        return $real; // Return the resolved, canonical path
921    }
922
923    /**
924     * Return true only when the given column name is permitted for CSV import.
925     *
926     * @param  string $column The CSV column / meta key name.
927     * @return bool
928     */
929    private function is_importable_column( $column ) {
930        $column = strtolower( trim( (string) $column ) );
931
932        // Denylist is checked first — it unconditionally blocks.
933        if ( in_array( $column, self::$import_meta_denylist, true ) ) {
934            return false;
935        }
936
937        return in_array( $column, self::$import_meta_allowlist, true );
938    }
939
940        /**
941         * Escape a string to be used in a CSV export.
942         *
943         * @see https://hackerone.com/reports/72785
944         *
945         * @since 1.2.3.10
946         *
947         * @param string $data Data to escape.
948         * @return string
949         */
950        public function escape_data( $data ) {
951                $escape_chars = array( '=', '+', '-', '@' );
952
953                if ( $data && in_array( substr( $data, 0, 1 ), $escape_chars, true ) ) {
954                        $data = " " . $data;
955                }
956
957                return $data;
958        }
959
960}
961new UsersWP_Import_Export();
Note: See TracBrowser for help on using the repository browser.