PluginProbe
UsersWP – Front-end login form, User Registration, User Profile & Members Directory plugin for WP / trunk
UsersWP – Front-end login form, User Registration, User Profile & Members Directory plugin for WP vtrunk
1.2.72 1.2.71 1.2.70 1.2.69 1.2.68 1.2.67 1.2.66 1.2.65 1.2.64 1.2.63 trunk 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.20 1.0.21 1.0.22 1.0.23 All 172 releases
userswp / includes / class-import-export.php

class-import-export.php in UsersWP – Front-end login form, User Registration, User Profile & Members Directory plugin for WP trunk, at includes/class-import-export.php

960 lines 32.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 */
10 class 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']) ? sanitize_user($row['username']) : '';
635 $email = isset($row['email']) ? sanitize_email($row['email']) : '';
636 $first_name = isset($row['first_name']) ? sanitize_text_field($row['first_name']) : '';
637 $last_name = isset($row['last_name']) ? sanitize_text_field($row['last_name']) : '';
638 $bio = isset($row['bio']) ? sanitize_textarea_field($row['bio']) : '';
639 $display_name = isset($row['display_name']) ? sanitize_text_field($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 if( !empty( $email ) && $update_existing = apply_filters('uwp_import_update_users', false, $row, $user_id) ) {
648 $args = array(
649 'ID' => $user_id,
650 'user_email' => $email,
651 'display_name' => $display_name,
652 'first_name' => $first_name,
653 'last_name' => $last_name,
654 'description' => $bio,
655 );
656 wp_update_user( $args );
657 }
658 } elseif(isset($row['email']) && email_exists($row['email'])){
659 $user = get_user_by('email', $row['email']);
660 $user_id = $user->ID;
661 } elseif((int)$row['user_id'] > 0){
662 $user = get_user_by('ID', $row['user_id']);
663 if(false === $user){
664 $userdata = array(
665 'user_login' => $username,
666 'user_email' => $email,
667 'user_pass' => $password,
668 'first_name' => $first_name,
669 'last_name' => $last_name,
670 'description' => $bio,
671 'display_name'=> $display_name
672 );
673 $user_id = wp_insert_user( $userdata );
674 $notify = apply_filters('uwp_import_user_notify', 'user', $user_id);
675 wp_new_user_notification($user_id,null, $notify); //send password reset link
676 } else {
677 if( $user->user_login == $row['username'] ) { //check id passed in csv and existing username are same
678 $user_id = $row['user_id'];
679 if( !empty( $email ) && $email != $user->user_email && $update_existing = apply_filters('uwp_import_update_users', false, $row, $user_id)) {
680 $args = array(
681 'ID' => $user_id,
682 'user_email' => $email,
683 'first_name' => $first_name,
684 'last_name' => $last_name,
685 'description' => $bio,
686 'display_name' => $display_name
687 );
688 wp_update_user( $args );
689 }
690 } else {
691 $return['msg'] = sprintf(__('Row - %s Error: '. 'User could not be created.','userswp'), $this->imp_step);
692 continue;
693 }
694 }
695 } else {
696 $user_id = wp_create_user( $username, $password, $email );
697 if( !is_wp_error( $user_id ) ) {
698 $args = array(
699 'ID' => $user_id,
700 'first_name' => $first_name,
701 'last_name' => $last_name,
702 'description' => $bio,
703 'display_name' => $display_name
704 );
705 wp_update_user( $args );
706 }
707 }
708
709 if( !is_wp_error( $user_id ) ){
710 foreach ($row as $key => $value){
711 //Only write columns on the allowlist; denylist always wins.
712 if ( ! $this->is_importable_column( $key ) ) {
713 continue;
714 }
715 //Never deserialize CSV input. Cast to safe scalar string.
716 $value = $this->sanitize_import_value( $key, $value );
717 uwp_update_usermeta($user_id, $key, $value);
718 }
719 } else {
720 $return['msg'] = sprintf(__('Row - %s Error: %s','userswp'), $this->imp_step, $user_id->get_error_message());
721 continue;
722 }
723 }
724 $return['success'] = true;
725 }
726
727 return $return;
728 }
729
730 /**
731 * Returns CSV row
732 *
733 * @package userswp
734 *
735 * @param int $row
736 * @param int $count
737 *
738 * @return mixed
739 *
740 */
741 public function get_csv_rows( $row = 0, $count = 1 ) {
742
743 $lc_all = setlocale( LC_ALL, 0 ); // Fix issue of fgetcsv ignores special characters when they are at the beginning of line
744 setlocale( LC_ALL, 'en_US.UTF-8' );
745 $l = $f =0;
746 $headers = $file = array();
747 $userdata_fields = $this->get_columns();
748 if ( ( $handle = fopen( $this->path, "r" ) ) !== false ) {
749 while ( ( $line = fgetcsv( $handle, 100000, "," ) ) !== false ) {
750 // If the first line is empty, abort
751 // If another line is empty, just skip it
752 if ( empty( $line ) ) {
753 if ( $l === 0 )
754 break;
755 else
756 continue;
757 }
758
759 // If we are on the first line, the columns are the headers
760 if ( $l === 0 ) {
761 $headers = $line;
762 $l ++;
763 continue;
764 }
765
766 // only get the rows needed
767 if ( $row && $count ) {
768
769 // if we have everything we need then break;
770 if ( $l == $row + $count ) {
771 break;
772
773 // if its less than the start row then continue;
774 } elseif ( $l && $l < $row ) {
775 $l ++;
776 continue;
777
778 // if we have the count we need then break;
779 } elseif ( $f > $count ) {
780 break;
781 }
782 }
783
784 // Separate user data from meta
785 $userdata = $usermeta = array();
786 foreach ( $line as $ckey => $column ) {
787 $column_name = $headers[$ckey];
788 $column = trim( $column );
789 if ( in_array( $column_name, $userdata_fields ) ) {
790 $userdata[$column_name] = $column;
791 } else {
792 $usermeta[$column_name] = $column;
793 }
794 }
795
796 // A plugin may need to filter the data and meta
797 $userdata = apply_filters( 'uwp_import_userdata', $userdata, $usermeta );
798
799 if ( ! empty( $userdata ) ) {
800 $file[] = $userdata;
801 $f ++;
802 $l ++;
803 }
804 }
805 fclose( $handle );
806 }
807 setlocale( LC_ALL, $lc_all );
808
809 return $file;
810
811 }
812
813 /**
814 * Returns import status
815 *
816 * @return int
817 *
818 */
819 public function get_import_status() {
820 $status = 100;
821 return apply_filters( 'uwp_get_import_users_status', $status );
822 }
823
824 /**
825 * Returns import user status
826 *
827 * @return int
828 *
829 */
830 public function get_import_users_status() {
831
832 if ( $this->imp_step >= $this->total_rows ) {
833 $status = 100;
834 } else {
835 $status = ( ( 1 * $this->imp_step ) / $this->total_rows ) * 100;
836 }
837
838 return $status;
839 }
840
841 public function allowed_upload_mimes($mimes = array()) {
842 $mimes['csv'] = "text/csv";
843 return $mimes;
844 }
845
846 /**
847 * Sanitize a single CSV import value.
848 *
849 * CSV data is always a plain string. There is no legitimate reason for it
850 * to contain serialized PHP. We detect the serialization type-prefix
851 * signatures, reject them with a log entry, and return an empty string.
852 * All other values are cast to string and sanitized with sanitize_text_field().
853 *
854 * @param string $key The CSV column / meta key name.
855 * @param mixed $value The raw value from the CSV row.
856 * @return string A safe scalar string ready for DB insertion.
857 */
858 private function sanitize_import_value( $key, $value ) {
859 // Reject serialized payloads
860 if ( is_string( $value ) && preg_match( '/^[aAbBdDiIoOsScCnN][:;]/', ltrim( $value ) ) ) {
861 if ( function_exists( 'uwp_log' ) ) {
862 uwp_log( sprintf( 'Import security: serialized payload in column "%s" — discarded.', esc_attr( $key ) ) );
863 }
864 return '';
865 }
866
867 // File path columns: validate as a URL pointing inside the uploads directory only
868 if ( in_array( $key, array( 'avatar_thumb', 'banner_thumb' ), true ) ) {
869 return $this->sanitize_import_thumb( $value );
870 }
871
872 return sanitize_text_field( (string) $value );
873 }
874
875 /**
876 * Sanitizes a thumbnail path value from CSV import.
877 *
878 * Validates that the given path is a real, existing file located within
879 * the WordPress uploads directory, preventing path traversal attacks and
880 * references to arbitrary files outside the uploads directory.
881 *
882 * @param string $value Raw thumbnail path value from the CSV row.
883 * @return string Resolved absolute path if valid, empty string otherwise.
884 */
885 private function sanitize_import_thumb( $value ) {
886 $value = trim( (string) $value );
887
888 if ( empty( $value ) ) {
889 return '';
890 }
891
892 // Resolve any ../ traversal attempts before comparison
893 $real = realpath( $value );
894
895 if ( $real === false ) {
896 return ''; // Path doesn't exist on disk — reject
897 }
898
899 // Must stay within the uploads directory
900 $uploads = wp_upload_dir();
901 $base_dir = trailingslashit( realpath( $uploads['basedir'] ) );
902
903 if ( strpos( $real . DIRECTORY_SEPARATOR, $base_dir ) !== 0 ) {
904 if ( function_exists( 'uwp_log' ) ) {
905 uwp_log( sprintf(
906 'Import security: thumb path "%s" is outside uploads directory — discarded.',
907 esc_attr( $value )
908 ) );
909 }
910 return '';
911 }
912
913 // Must be an allowed image extension
914 $ext = strtolower( pathinfo( $real, PATHINFO_EXTENSION ) );
915 if ( ! in_array( $ext, array( 'jpg', 'jpeg', 'png', 'gif', 'webp' ), true ) ) {
916 return '';
917 }
918
919 return $real; // Return the resolved, canonical path
920 }
921
922 /**
923 * Return true only when the given column name is permitted for CSV import.
924 *
925 * @param string $column The CSV column / meta key name.
926 * @return bool
927 */
928 private function is_importable_column( $column ) {
929 $column = strtolower( trim( (string) $column ) );
930
931 // Denylist is checked first — it unconditionally blocks.
932 if ( in_array( $column, self::$import_meta_denylist, true ) ) {
933 return false;
934 }
935
936 return in_array( $column, self::$import_meta_allowlist, true );
937 }
938
939 /**
940 * Escape a string to be used in a CSV export.
941 *
942 * @see https://hackerone.com/reports/72785
943 *
944 * @since 1.2.3.10
945 *
946 * @param string $data Data to escape.
947 * @return string
948 */
949 public function escape_data( $data ) {
950 $escape_chars = array( '=', '+', '-', '@' );
951
952 if ( $data && in_array( substr( $data, 0, 1 ), $escape_chars, true ) ) {
953 $data = " " . $data;
954 }
955
956 return $data;
957 }
958
959 }
960 new UsersWP_Import_Export();