PluginProbe
UsersWP – Front-end login form, User Registration, User Profile & Members Directory plugin for WP / 1.2.73
UsersWP – Front-end login form, User Registration, User Profile & Members Directory plugin for WP v1.2.73
1.2.73 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 All 173 releases
← All changes | includes/class-import-export.php +373 -84 1.0.211.2.73 View file →
@@ -16,9 +16,52 @@
16 16 public $path;
17 17 public $total_rows;
18 18 public $imp_step;
19 19 public $skipped;
20 + private $empty;
21 + private $step;
22 + private $file;
23 + private $filename;
20 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 +
21 64 public function __construct() {
22 65 global $wp_filesystem;
23 66
24 67 if ( empty( $wp_filesystem ) ) {
@@ -33,45 +76,28 @@
33 76 $this->per_page = apply_filters('uwp_import_export_per_page', 20, $this);
34 77 $this->meta_table_name = get_usermeta_table_prefix() . 'uwp_usermeta';
35 78 $this->path = '';
36 79
37 - add_action( 'userswp_settings_import-export_tab_content', array($this, 'get_ie_content') );
38 - add_action( 'admin_init', array($this, 'uwp_process_settings_export') );
39 - add_action( 'admin_init', array($this, 'uwp_process_settings_import') );
40 - add_action( 'wp_ajax_uwp_ajax_export_users', array( $this, 'uwp_process_users_export' ) );
41 - add_action( 'wp_ajax_uwp_ajax_import_users', array( $this, 'uwp_process_users_import' ) );
42 - add_action( 'wp_ajax_uwp_ie_upload_file', array( $this, 'uwp_ie_upload_file' ) );
43 - add_action( 'wp_ajax_nopriv_uwp_ie_upload_file', array( $this, 'uwp_ie_upload_file' ) );
44 - add_action( 'admin_notices', array($this, 'uwp_ie_admin_notice') );
45 - add_filter( 'uwp_get_export_users_status', array( $this, 'uwp_get_export_users_status' ) );
46 - add_filter( 'uwp_get_import_users_status', array( $this, 'uwp_get_import_users_status' ) );
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' ) );
47 88 }
48 89
49 - public function get_ie_content() {
50 - $subtab = 'ie-users';
51 -
52 - if (isset($_GET['subtab'])) {
53 - $subtab = $_GET['subtab'];
54 - }
55 - ?>
56 - <div class="item-list-sub-tabs">
57 - <ul class="item-list-sub-tabs-ul">
58 - <li class="<?php if ($subtab == 'ie-users') { echo "current selected"; } ?>">
59 - <a href="<?php echo add_query_arg(array('tab' => 'import-export', 'subtab' => 'ie-users')); ?>"><?php echo __( 'Users', 'userswp' ); ?></a>
60 - </li>
61 - <li class="<?php if ($subtab == 'ie-settings') { echo "current selected"; } ?>">
62 - <a href="<?php echo add_query_arg(array('tab' => 'import-export', 'subtab' => 'ie-settings')); ?>"><?php echo __( 'Settings', 'userswp' ); ?></a>
63 - </li>
64 - </ul>
65 - </div>
66 - <?php
67 - if ($subtab == 'ie-users') {
68 - include_once( USERSWP_PATH . '/admin/settings/admin-settings-ie-users.php' );
69 - } elseif ($subtab == 'ie-settings') {
70 - include_once( USERSWP_PATH . '/admin/settings/admin-settings-ie-settings.php' );
71 - }
72 - }
73 -
90 + /**
91 + * Returns export file location
92 + *
93 + * @package userswp
94 + *
95 + * @param bool $relative
96 + *
97 + * @return string
98 + *
99 + */
74 100 public function export_location( $relative = false ) {
75 101 $upload_dir = wp_upload_dir();
76 102 $export_location = $relative ? trailingslashit( $upload_dir['baseurl'] ) . 'cache' : trailingslashit( $upload_dir['basedir'] ) . 'cache';
77 103 $export_location = apply_filters( 'uwp_export_location', $export_location, $relative );
@@ -78,13 +104,16 @@
78 104
79 105 return trailingslashit( $export_location );
80 106 }
81 107
82 - public function uwp_ie_admin_notice(){
108 + /**
109 + * Displays notice
110 + */
111 + public function ie_admin_notice(){
83 112 if(isset($_GET['imp-msg']) && 'success' == $_GET['imp-msg']){
84 113 ?>
85 114 <div class="notice notice-success is-dismissible">
86 - <p><?php _e( 'Settings imported successfully!', 'userswp' ); ?></p>
115 + <p><?php esc_html_e( 'Settings imported successfully!', 'userswp' ); ?></p>
87 116 </div>
88 117 <?php
89 118 }
90 119 }
@@ -91,9 +120,9 @@
91 120
92 121 /**
93 122 * Process a settings export that generates a .json file of the settings
94 123 */
95 - public function uwp_process_settings_export() {
124 + public function process_settings_export() {
96 125 if( empty( $_POST['uwp_ie_action'] ) || 'export_settings' != $_POST['uwp_ie_action'] )
97 126 return;
98 127 if( ! wp_verify_nonce( $_POST['uwp_export_nonce'], 'uwp_export_nonce' ) )
99 128 return;
@@ -111,9 +140,9 @@
111 140
112 141 /**
113 142 * Process a settings import from a json file
114 143 */
115 - public function uwp_process_settings_import() {
144 + public function process_settings_import() {
116 145 if( empty( $_POST['uwp_ie_action'] ) || 'import_settings' != $_POST['uwp_ie_action'] )
117 146 return;
118 147 if( ! wp_verify_nonce( $_POST['uwp_import_nonce'], 'uwp_import_nonce' ) )
119 148 return;
@@ -118,23 +147,27 @@
118 147 if( ! wp_verify_nonce( $_POST['uwp_import_nonce'], 'uwp_import_nonce' ) )
119 148 return;
120 149 if( ! current_user_can( 'manage_options' ) )
121 150 return;
122 - $extension = end( explode( '.', $_FILES['import_file']['name'] ) );
151 + $extension = explode( '.', $_FILES['import_file']['name'] );
152 + $extension = end( $extension );
123 153 if( $extension != 'json' ) {
124 - wp_die( sprintf(__( 'Please upload a valid .json file. %sGo Back%s' ), '<a href="'.admin_url( 'admin.php?page=userswp&tab=import-export&subtab=ie-settings' ).'">', '</a>' ));
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>' ) ) );
125 155 }
126 156 $import_file = $_FILES['import_file']['tmp_name'];
127 157 if( empty( $import_file ) ) {
128 - wp_die( sprintf(__( 'Please upload a file to import. %sGo Back%s' ), '<a href="'.admin_url( 'admin.php?page=userswp&tab=import-export&subtab=ie-settings' ).'">', '</a>' ));
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>' ) ) );
129 159 }
130 160 // Retrieve the settings from the file and convert the json object to an array.
131 161 $settings = (array) json_decode( file_get_contents( $import_file ), true );
132 162 update_option( 'uwp_settings', $settings );
133 - wp_safe_redirect( admin_url( 'admin.php?page=userswp&tab=import-export&subtab=ie-settings&imp-msg=success' ) ); exit;
163 + wp_safe_redirect( admin_url( 'admin.php?page=userswp&tab=import-export&section=settings&imp-msg=success' ) ); exit;
134 164 }
135 165
136 - public function uwp_process_users_export(){
166 + /**
167 + * Processes users export
168 + */
169 + public function process_users_export(){
137 170
138 171 $response = array();
139 172 $response['success'] = false;
140 173 $response['msg'] = __( 'Invalid export request found.', 'userswp' );
@@ -170,9 +203,9 @@
170 203 $response['msg'] = '';
171 204
172 205 if ( $done >= 100 ) {
173 206 $this->step = 'done';
174 - $new_filename = 'uwp-users-export-' . date( 'y-m-d-H-i' ) . '.csv';
207 + $new_filename = 'uwp-users-export-' . date( 'y-m-d-H-i' ) . '-' . wp_generate_password(12, false ) . '.csv';
175 208 $new_file = $this->export_dir . $new_filename;
176 209
177 210 if ( file_exists( $this->file ) ) {
178 211 $this->wp_filesystem->move( $this->file, $new_file, true );
@@ -192,8 +225,16 @@
192 225 wp_send_json( $response );
193 226
194 227 }
195 228
229 + /**
230 + * Sets export params
231 + *
232 + * @package userswp
233 + *
234 + * @param array $request
235 + *
236 + */
196 237 public function set_export_params( $request ) {
197 238 $this->empty = false;
198 239 $this->step = !empty( $request['step'] ) ? absint( $request['step'] ) : 1;
199 240 $this->filename = 'uwp-users-export-temp.csv';
@@ -203,8 +244,16 @@
203 244
204 245 do_action( 'uwp_export_users_set_params', $request );
205 246 }
206 247
248 + /**
249 + * Returns export file location
250 + *
251 + * @package userswp
252 + *
253 + * @return bool
254 + *
255 + */
207 256 public function check_export_location() {
208 257 try {
209 258 if ( empty( $this->wp_filesystem ) ) {
210 259 return __( 'Filesystem ERROR: Could not access filesystem.', 'userswp' );
@@ -217,27 +266,35 @@
217 266 $is_dir = $this->wp_filesystem->is_dir( $this->export_dir );
218 267 $is_writeable = $is_dir && is_writeable( $this->export_dir );
219 268
220 269 if ( $is_dir && $is_writeable ) {
221 - return true;
270 +
222 271 } else if ( $is_dir && !$is_writeable ) {
223 272 if ( !$this->wp_filesystem->chmod( $this->export_dir, FS_CHMOD_DIR ) ) {
224 273 return wp_sprintf( __( 'Filesystem ERROR: Export location %s is not writable, check your file permissions.', 'userswp' ), $this->export_dir );
225 274 }
226 -
227 - return true;
228 275 } else {
229 276 if ( !$this->wp_filesystem->mkdir( $this->export_dir, FS_CHMOD_DIR ) ) {
230 277 return wp_sprintf( __( 'Filesystem ERROR: Could not create directory %s. This is usually due to inconsistent file permissions.', 'userswp' ), $this->export_dir );
231 278 }
279 + }
232 280
233 - return true;
234 - }
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;
235 286 } catch ( Exception $e ) {
236 287 return $e->getMessage();
237 288 }
238 289 }
239 290
291 + /**
292 + * Processes export step
293 + *
294 + * @return bool
295 + *
296 + */
240 297 public function process_export_step() {
241 298 if ( $this->step < 2 ) {
242 299 @unlink( $this->file );
243 300 $this->print_columns();
@@ -251,8 +308,14 @@
251 308 return false;
252 309 }
253 310 }
254 311
312 + /**
313 + * Prints columns
314 + *
315 + * @return array
316 + *
317 + */
255 318 public function print_columns() {
256 319 $column_data = '';
257 320 $columns = $this->get_columns();
258 321 $i = 1;
@@ -267,13 +330,19 @@
267 330
268 331 return $column_data;
269 332 }
270 333
334 + /**
335 + * Returns export columns
336 + *
337 + * @return array
338 + *
339 + */
271 340 public function get_columns() {
272 341 global $wpdb;
273 342 $columns = array();
274 343
275 - foreach ( $wpdb->get_col( "DESC " . $this->meta_table_name, 0 ) as $column_name ) {
344 + foreach ( $wpdb->get_col( "DESC " . $this->meta_table_name, 0 ) as $column_name ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
276 345 $columns[] = $column_name;
277 346 }
278 347
279 348 return apply_filters( 'uwp_export_users_get_columns', $columns );
@@ -278,8 +347,16 @@
278 347
279 348 return apply_filters( 'uwp_export_users_get_columns', $columns );
280 349 }
281 350
351 + /**
352 + * Returns export data
353 + *
354 + * @package userswp
355 + *
356 + * @return array
357 + *
358 + */
282 359 public function get_export_data() {
283 360 global $wpdb;
284 361 if(!$this->step){
285 362 $page = 1;
@@ -287,16 +364,16 @@
287 364 $page = $this->step;
288 365 }
289 366
290 367 $page_start = absint( ( $page - 1 ) * $this->per_page );
291 - $data = $wpdb->get_results( "SELECT * FROM $this->meta_table_name WHERE 1=1 LIMIT $page_start,". $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
292 369 $i = 0;
293 370
294 371 foreach ($data as $u){
295 372 $user = get_userdata($u->user_id);
296 - $data[$i]->uwp_account_username = $user->user_login;
297 - $data[$i]->uwp_account_email = $user->user_email;
298 - $data[$i]->uwp_account_bio = $user->description;
373 + $data[$i]->username = $user->user_login;
374 + $data[$i]->email = $user->user_email;
375 + $data[$i]->bio = $user->description;
299 376 $i++;
300 377 }
301 378
302 379 return apply_filters( 'uwp_export_users_get_data', $data );
@@ -301,22 +378,35 @@
301 378
302 379 return apply_filters( 'uwp_export_users_get_data', $data );
303 380 }
304 381
382 + /**
383 + * Returns export status
384 + *
385 + * @return int
386 + *
387 + */
305 388 public function get_export_status() {
306 389 $status = 100;
307 390 return apply_filters( 'uwp_get_export_users_status', $status );
308 391 }
309 392
393 + /**
394 + * Prints CSV rows
395 + *
396 + * @return string
397 + *
398 + */
310 399 public function print_rows() {
311 400 $row_data = '';
312 401 $data = $this->get_export_data();
313 402 $columns = $this->get_columns();
314 403
315 - if ( $data ) {
404 + if ( is_array($data) && !empty($data) ) {
316 405 foreach ( $data as $row ) {
317 406 $i = 1;
318 407 foreach ( $row as $key => $column ) {
408 + $column = $this->escape_data( $column );
319 409 $row_data .= '"' . addslashes( preg_replace( "/\"/","'", $column ) ) . '"';
320 410 $row_data .= $i == count( $columns ) ? '' : ',';
321 411 $i++;
322 412 }
@@ -330,8 +420,14 @@
330 420
331 421 return false;
332 422 }
333 423
424 + /**
425 + * Returns export file content
426 + *
427 + * @return string
428 + *
429 + */
334 430 protected function get_export_file() {
335 431 $file = '';
336 432
337 433 if ( $this->wp_filesystem->exists( $this->file ) ) {
@@ -342,8 +438,16 @@
342 438
343 439 return $file;
344 440 }
345 441
442 + /**
443 + * Adds export data to CSV file
444 + *
445 + * @package userswp
446 + *
447 + * @param string $data
448 + *
449 + */
346 450 protected function attach_export_data( $data = '' ) {
347 451 $filedata = $this->get_export_file();
348 452 $filedata .= $data;
349 453
@@ -355,11 +459,17 @@
355 459
356 460 $this->empty = count( $rows ) == $columns ? true : false;
357 461 }
358 462
359 - public function uwp_get_export_users_status() {
463 + /**
464 + * Returns export user status
465 + *
466 + * @return int
467 + *
468 + */
469 + public function get_export_users_status() {
360 470 global $wpdb;
361 - $data = $wpdb->get_results("SELECT user_id FROM $this->meta_table_name WHERE 1=1");
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
362 472 $total = !empty( $data ) ? count( $data ) : 0;
363 473 $status = 100;
364 474
365 475 if ( $this->per_page > $total ) {
@@ -376,9 +486,22 @@
376 486
377 487 return $status;
378 488 }
379 489
380 - public function uwp_ie_upload_file(){
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 +
381 504 $upload_data = array(
382 505 'name' => $_FILES['import_file']['name'],
383 506 'type' => $_FILES['import_file']['type'],
384 507 'tmp_name' => $_FILES['import_file']['tmp_name'],
@@ -387,12 +510,15 @@
387 510 );
388 511
389 512 header('Content-Type: text/html; charset=' . get_option('blog_charset'));
390 513
514 + add_filter( 'upload_mimes', array( $this, 'allowed_upload_mimes' ) );
391 515 $uploaded_file = wp_handle_upload( $upload_data, array('test_form' => false) );
516 + remove_filter( 'upload_mimes', array( $this, 'allowed_upload_mimes' ) );
517 +
392 518 if ( isset( $uploaded_file['url'] ) ) {
393 519 $file_loc = $uploaded_file['url'];
394 - echo $file_loc;
520 + echo esc_url( $file_loc );exit;
395 521 } else {
396 522 echo 'error';
397 523 }
398 524 exit;
@@ -397,9 +523,13 @@
397 523 }
398 524 exit;
399 525 }
400 526
401 - public function uwp_process_users_import(){
527 + /**
528 + * Processes users import
529 + *
530 + */
531 + public function process_users_import(){
402 532
403 533 $response = array();
404 534 $response['success'] = false;
405 535 $response['msg'] = __( 'Invalid import request found.', 'userswp' );
@@ -463,9 +593,9 @@
463 593 $this->imp_step = 'done';
464 594 $response['msg'] = __( 'Users import completed.', 'userswp' );
465 595 }
466 596
467 - $response['data']['msg'] = $return['msg'];
597 + $response['data']['msg'] = ! empty( $return['msg'] ) ? $return['msg'] : $response['msg'];
468 598 $response['data']['step'] = $this->imp_step;
469 599 $response['data']['done'] = $done;
470 600 } else {
471 601 $response['msg'] = __( 'No valid data found for import.', 'userswp' );
@@ -474,8 +604,14 @@
474 604 wp_send_json( $response );
475 605
476 606 }
477 607
608 + /**
609 + * Processes import step
610 + *
611 + * @return array
612 + *
613 + */
478 614 public function process_import_step() {
479 615
480 616 $errors = new WP_Error();
481 617 if(is_null($this->path)){
@@ -490,26 +626,25 @@
490 626
491 627 if ( ! empty( $rows ) ) {
492 628 foreach ( $rows as $row ) {
493 629 if( empty($row) ) {
494 - $return['msg'] = __('Row - ' .$this->imp_step. ' Error: '. 'Skipped due to invalid/no data.','userswp');
630 + $return['msg'] = sprintf(__('Row - %s Error: '. 'Skipped due to invalid/no data.','userswp'), $this->imp_step);
495 631 continue;
496 632 }
497 633
498 - $username = isset($row['uwp_account_username']) ? $row['uwp_account_username'] : '';
499 - $email = isset($row['uwp_account_email']) ? $row['uwp_account_email'] : '';
500 - $first_name = isset($row['uwp_account_first_name']) ? $row['uwp_account_first_name'] : '';
501 - $last_name = isset($row['uwp_account_last_name']) ? $row['uwp_account_last_name'] : '';
502 - $bio = isset($row['uwp_account_bio']) ? $row['uwp_account_bio'] : '';
503 - $display_name = isset($row['uwp_account_display_name']) ? $row['uwp_account_display_name'] : '';
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']) : '';
504 640 $password = wp_generate_password();
505 641 $exclude = array('user_id');
506 642 $exclude = apply_filters('uwp_import_exclude_columns', $exclude, $row);
507 643
508 - if(isset($row['uwp_account_username']) && username_exists($row['uwp_account_username'])){
509 - $user = get_user_by('login', $row['uwp_account_username']);
644 + if(isset($row['username']) && username_exists($row['username'])){
645 + $user = get_user_by('login', $row['username']);
510 646 $user_id = $user->ID;
511 - $email = $row['uwp_account_email'];
512 647 if( !empty( $email ) && $update_existing = apply_filters('uwp_import_update_users', false, $row, $user_id) ) {
513 648 $args = array(
514 649 'ID' => $user_id,
515 650 'user_email' => $email,
@@ -519,16 +654,16 @@
519 654 'description' => $bio,
520 655 );
521 656 wp_update_user( $args );
522 657 }
523 - } elseif(isset($row['uwp_account_email']) && email_exists($row['uwp_account_email'])){
524 - $user = get_user_by('email', $row['uwp_account_email']);
658 + } elseif(isset($row['email']) && email_exists($row['email'])){
659 + $user = get_user_by('email', $row['email']);
525 660 $user_id = $user->ID;
526 661 } elseif((int)$row['user_id'] > 0){
527 662 $user = get_user_by('ID', $row['user_id']);
528 663 if(false === $user){
529 664 $userdata = array(
530 - 'user_login' => $row['uwp_account_username'],
665 + 'user_login' => $username,
531 666 'user_email' => $email,
532 667 'user_pass' => $password,
533 668 'first_name' => $first_name,
534 669 'last_name' => $last_name,
@@ -535,11 +670,12 @@
535 670 'description' => $bio,
536 671 'display_name'=> $display_name
537 672 );
538 673 $user_id = wp_insert_user( $userdata );
539 - wp_new_user_notification($user_id,null, 'user'); //send password reset link
674 + $notify = apply_filters('uwp_import_user_notify', 'user', $user_id);
675 + wp_new_user_notification($user_id,null, $notify); //send password reset link
540 676 } else {
541 - if( $user->user_login == $row['uwp_account_username'] ) { //check id passed in csv and existing username are same
677 + if( $user->user_login == $row['username'] ) { //check id passed in csv and existing username are same
542 678 $user_id = $row['user_id'];
543 679 if( !empty( $email ) && $email != $user->user_email && $update_existing = apply_filters('uwp_import_update_users', false, $row, $user_id)) {
544 680 $args = array(
545 681 'ID' => $user_id,
@@ -551,25 +687,38 @@
551 687 );
552 688 wp_update_user( $args );
553 689 }
554 690 } else {
555 - $return['msg'] = __('Row - ' .$this->imp_step. ' Error: '. 'User could not be created.','userswp');
691 + $return['msg'] = sprintf(__('Row - %s Error: '. 'User could not be created.','userswp'), $this->imp_step);
556 692 continue;
557 693 }
558 694 }
559 695 } else {
560 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 + }
561 707 }
562 708
563 709 if( !is_wp_error( $user_id ) ){
564 710 foreach ($row as $key => $value){
565 - if(!in_array($key, $exclude) && $update_existing = apply_filters('uwp_import_update_users', false, $row, $user_id)){
566 - $value = maybe_unserialize($value);
567 - uwp_update_usermeta($user_id, $key, $value);
711 + //Only write columns on the allowlist; denylist always wins.
712 + if ( ! $this->is_importable_column( $key ) ) {
713 + continue;
568 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);
569 718 }
570 719 } else {
571 - $return['msg'] = __('Row - ' .$this->imp_step. ' Error: '. $user_id->get_error_message(),'userswp');
720 + $return['msg'] = sprintf(__('Row - %s Error: %s','userswp'), $this->imp_step, $user_id->get_error_message());
572 721 continue;
573 722 }
574 723 }
575 724 $return['success'] = true;
@@ -577,8 +726,19 @@
577 726
578 727 return $return;
579 728 }
580 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 + */
581 741 public function get_csv_rows( $row = 0, $count = 1 ) {
582 742
583 743 $lc_all = setlocale( LC_ALL, 0 ); // Fix issue of fgetcsv ignores special characters when they are at the beginning of line
584 744 setlocale( LC_ALL, 'en_US.UTF-8' );
@@ -634,9 +794,8 @@
634 794 }
635 795
636 796 // A plugin may need to filter the data and meta
637 797 $userdata = apply_filters( 'uwp_import_userdata', $userdata, $usermeta );
638 - $usermeta = apply_filters( 'uwp_import_usermeta', $usermeta, $userdata );
639 798
640 799 if ( ! empty( $userdata ) ) {
641 800 $file[] = $userdata;
642 801 $f ++;
@@ -650,14 +809,26 @@
650 809 return $file;
651 810
652 811 }
653 812
813 + /**
814 + * Returns import status
815 + *
816 + * @return int
817 + *
818 + */
654 819 public function get_import_status() {
655 820 $status = 100;
656 821 return apply_filters( 'uwp_get_import_users_status', $status );
657 822 }
658 823
659 - public function uwp_get_import_users_status() {
824 + /**
825 + * Returns import user status
826 + *
827 + * @return int
828 + *
829 + */
830 + public function get_import_users_status() {
660 831
661 832 if ( $this->imp_step >= $this->total_rows ) {
662 833 $status = 100;
663 834 } else {
@@ -665,7 +836,125 @@
665 836 }
666 837
667 838 return $status;
668 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 + }
669 958
670 959 }
671 960 new UsersWP_Import_Export();