PluginProbe
bbPress / 2.6.19
bbPress v2.6.19
2.6.19 2.6.18 2.6.17 trunk 2.0 2.0-beta-1 2.0-beta-2b 2.0-beta-3 2.0-beta-3b 2.0-rc-2 2.0-rc-3 2.0-rc-4 2.0-rc-5 2.0.1 2.0.2 2.0.3 2.1 2.1-beta-1 2.1-rc1 2.1-rc2 2.1-rc3 2.1-rc4 2.1.1 2.1.2 2.1.3 All 74 releases
← All changes | includes/admin/converters/PHPWind.php +158 -48 2.6.17 → 2.6.19 View file →
@@ -9,9 +9,9 @@
9 9
10 10 /**
11 11 * Implementation of PHPWind Forum converter.
12 12 *
13 - * @since 2.5.0 bbPress (r5142)
13 + * @since 2.5.0 bbPress (r5143)
14 14 *
15 15 * @link Codex Docs https://codex.bbpress.org/import-forums/phpwind
16 16 */
17 17 class PHPWind extends BBP_Converter_Base {
@@ -145,24 +145,24 @@
145 145 'default' => 'open'
146 146 );
147 147 // Forum dates.
148 148 $this->field_map[] = array(
149 - 'to_type' => 'bbs_forum',
149 + 'to_type' => 'forum',
150 150 'to_fieldname' => 'post_date',
151 151 'default' => date( 'Y-m-d H:i:s' ) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
152 152 );
153 153 $this->field_map[] = array(
154 - 'to_type' => 'bbs_forum',
154 + 'to_type' => 'forum',
155 155 'to_fieldname' => 'post_date_gmt',
156 156 'default' => date( 'Y-m-d H:i:s' ) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
157 157 );
158 158 $this->field_map[] = array(
159 - 'to_type' => 'bbs_forum',
159 + 'to_type' => 'forum',
160 160 'to_fieldname' => 'post_modified',
161 161 'default' => date( 'Y-m-d H:i:s' ) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
162 162 );
163 163 $this->field_map[] = array(
164 - 'to_type' => 'bbs_forum',
164 + 'to_type' => 'forum',
165 165 'to_fieldname' => 'post_modified_gmt',
166 166 'default' => date( 'Y-m-d H:i:s' ) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
167 167 );
168 168
@@ -295,9 +295,9 @@
295 295 'to_fieldname' => '_bbp_last_active_time',
296 296 'callback_method' => 'callback_datetime'
297 297 );
298 298
299 - // Topic status (Open or Closed, PHPWind v9.x 0=no, 1=closed & 2=open)
299 + // Topic status (PHPWind v9.x bitmask: 1=locked & 2=closed)
300 300 $this->field_map[] = array(
301 301 'from_tablename' => 'bbs_threads',
302 302 'from_fieldname' => 'tpcstatus',
303 303 'to_type' => 'topic',
@@ -407,9 +407,9 @@
407 407 /** User Section ******************************************************/
408 408
409 409 // Store old user id (Stored in usermeta)
410 410 $this->field_map[] = array(
411 - 'from_tablename' => 'user',
411 + 'from_tablename' => 'windid_user',
412 412 'from_fieldname' => 'uid',
413 413 'to_type' => 'user',
414 414 'to_fieldname' => '_bbp_old_user_id'
415 415 );
@@ -415,23 +415,22 @@
415 415 );
416 416
417 417 // Store old user password (Stored in usermeta serialized with salt)
418 418 $this->field_map[] = array(
419 - 'from_tablename' => 'user',
419 + 'from_tablename' => 'windid_user',
420 420 'from_fieldname' => 'password',
421 421 'to_type' => 'user',
422 - 'to_fieldname' => '_bbp_password'
423 - // 'callback_method' => 'callback_savepass'
422 + 'to_fieldname' => '_bbp_password',
423 + 'callback_method' => 'callback_savepass'
424 424 );
425 425
426 426 // Store old user salt (This is only used for the SELECT row info for the above password save)
427 -/* $this->field_map[] = array(
428 - 'from_tablename' => 'user',
429 - 'from_fieldname' => 'pass',
427 + $this->field_map[] = array(
428 + 'from_tablename' => 'windid_user',
429 + 'from_fieldname' => 'salt',
430 430 'to_type' => 'user',
431 431 'to_fieldname' => ''
432 432 );
433 -*/
434 433 // User password verify class (Stored in usermeta for verifying password)
435 434 $this->field_map[] = array(
436 435 'to_type' => 'user',
437 436 'to_fieldname' => '_bbp_class',
@@ -439,9 +438,9 @@
439 438 );
440 439
441 440 // User name.
442 441 $this->field_map[] = array(
443 - 'from_tablename' => 'user',
442 + 'from_tablename' => 'windid_user',
444 443 'from_fieldname' => 'username',
445 444 'to_type' => 'user',
446 445 'to_fieldname' => 'user_login'
447 446 );
@@ -447,9 +446,9 @@
447 446 );
448 447
449 448 // User nice name.
450 449 $this->field_map[] = array(
451 - 'from_tablename' => 'user',
450 + 'from_tablename' => 'windid_user',
452 451 'from_fieldname' => 'username',
453 452 'to_type' => 'user',
454 453 'to_fieldname' => 'user_nicename'
455 454 );
@@ -455,9 +454,9 @@
455 454 );
456 455
457 456 // User email.
458 457 $this->field_map[] = array(
459 - 'from_tablename' => 'user',
458 + 'from_tablename' => 'windid_user',
460 459 'from_fieldname' => 'email',
461 460 'to_type' => 'user',
462 461 'to_fieldname' => 'user_email'
463 462 );
@@ -463,9 +462,9 @@
463 462 );
464 463
465 464 // User registered.
466 465 $this->field_map[] = array(
467 - 'from_tablename' => 'user',
466 + 'from_tablename' => 'windid_user',
468 467 'from_fieldname' => 'regdate',
469 468 'to_type' => 'user',
470 469 'to_fieldname' => 'user_registered',
471 470 'callback_method' => 'callback_datetime'
@@ -472,12 +471,15 @@
472 471 );
473 472
474 473 // User display name.
475 474 $this->field_map[] = array(
476 - 'from_tablename' => 'user',
477 - 'from_fieldname' => 'realname',
478 - 'to_type' => 'user',
479 - 'to_fieldname' => 'display_name'
475 + 'from_tablename' => 'windid_user_info',
476 + 'from_fieldname' => 'realname',
477 + 'join_tablename' => 'windid_user',
478 + 'join_type' => 'LEFT',
479 + 'join_expression' => 'ON windid_user.uid = windid_user_info.uid',
480 + 'to_type' => 'user',
481 + 'to_fieldname' => 'display_name'
480 482 );
481 483 }
482 484
483 485 /**
@@ -488,16 +490,16 @@
488 490 return '';
489 491 }
490 492
491 493 /**
492 - * This method is to save the salt and password together. That
493 - * way when we authenticate it we can get it out of the database
494 - * as one value. Array values are auto sanitized by WordPress.
494 + * Save the salt and password together so both values are available during
495 + * authentication. Pre-slash the salt because WordPress removes one layer of
496 + * slashes when storing user metadata.
495 497 */
496 498 public function callback_savepass( $field, $row ) {
497 499 $pass_array = array(
498 500 'hash' => $field,
499 - 'salt' => $row['salt']
501 + 'salt' => wp_slash( isset( $row['salt'] ) ? (string) $row['salt'] : '' )
500 502 );
501 503
502 504 return $pass_array;
503 505 }
@@ -506,20 +508,17 @@
506 508 * This method is to take the pass out of the database and compare
507 509 * to a pass the user has typed in.
508 510 */
509 511 public function authenticate_pass( $password, $serialized_pass ) {
512 + if ( ! is_string( $password ) || ! is_string( $serialized_pass ) ) {
513 + return false;
514 + }
510 515
511 516 // Unserialize the password, with safeguards
512 - $pass_array = unserialize(
513 - $serialized_pass,
514 - array(
515 - 'allowed_classes' => false,
516 - 'max_depth' => 1
517 - )
518 - );
517 + $pass_array = $this->unserialize_pass( $serialized_pass );
519 518
520 519 // Bail if missing values
521 - if ( ! is_array( $pass_array ) || ! isset( $pass_array['hash'], $pass_array['salt'] ) ) {
520 + if ( ! is_array( $pass_array ) || ! isset( $pass_array['hash'], $pass_array['salt'] ) || ! is_string( $pass_array['hash'] ) || ! is_string( $pass_array['salt'] ) ) {
522 521 return false;
523 522 }
524 523
525 524 // Return comparison
@@ -529,8 +528,126 @@
529 528 );
530 529 }
531 530
532 531 /**
532 + * Upgrade password metadata written by earlier PHPWind imports.
533 + *
534 + * Earlier imports stored PHPWind's random synchronization token instead of
535 + * its login hash and salt. New imports use the parent callback and serialized
536 + * metadata containing the login hash and salt.
537 + *
538 + * @param string $username WordPress user login.
539 + * @param string $password Unslashed password for PHPWind.
540 + * @param string|null $wp_password Optional slashed password for WordPress.
541 + */
542 + public function callback_pass( $username = '', $password = '', $wp_password = null ) {
543 + $user = get_user_by( 'login', $username );
544 +
545 + if ( ! empty( $user ) && '' === $user->user_pass ) {
546 + $stored_hash = get_user_meta( $user->ID, '_bbp_password', true );
547 +
548 + if ( is_string( $stored_hash ) && preg_match( '/^[a-f0-9]{32}$/i', $stored_hash ) ) {
549 + $serialized_pass = $this->get_source_password( $user, $stored_hash );
550 +
551 + if ( is_string( $serialized_pass ) && $this->authenticate_pass( $password, $serialized_pass ) ) {
552 + $this->upgrade_user_pass( $user->ID, is_null( $wp_password ) ? $password : $wp_password );
553 + }
554 +
555 + return;
556 + }
557 + }
558 +
559 + parent::callback_pass( $username, $password, $wp_password );
560 + }
561 +
562 + /**
563 + * Upgrade a PHPWind hash copied directly into wp_users by an old import.
564 + *
565 + * Those imports stored PHPWind's random synchronization token, not its login
566 + * hash. Match that token against the retained source row before retrieving
567 + * the login hash and salt from WindID.
568 + *
569 + * @param WP_User $user WordPress user.
570 + * @param string $password Unslashed password for PHPWind.
571 + * @param string|null $wp_password Optional slashed password for WordPress.
572 + */
573 + public function callback_user_pass( $user, $password, $wp_password = null ) {
574 + if ( ! ( $user instanceof WP_User ) || ! is_string( $password ) || ! preg_match( '/^[a-f0-9]{32}$/i', $user->user_pass ) ) {
575 + return;
576 + }
577 +
578 + $wp_password = is_null( $wp_password ) ? $password : $wp_password;
579 +
580 + // Do not replace a password that WordPress or a password plugin already
581 + // recognizes, regardless of the stored hash format.
582 + if ( wp_check_password( $wp_password, $user->user_pass, $user->ID ) ) {
583 + return;
584 + }
585 +
586 + $serialized_pass = $this->get_source_password( $user, $user->user_pass );
587 +
588 + if ( is_string( $serialized_pass ) && $this->authenticate_pass( $password, $serialized_pass ) ) {
589 + $this->upgrade_user_pass( $user->ID, $wp_password );
590 + }
591 + }
592 +
593 + /**
594 + * Retrieve login metadata for a token written by an earlier import.
595 + *
596 + * @param WP_User $user WordPress user.
597 + * @param string $stored_hash Random token stored by the earlier import.
598 + * @return string|bool Serialized password metadata, or false on failure.
599 + */
600 + private function get_source_password( $user, $stored_hash ) {
601 + $old_user_id = (int) get_user_meta( $user->ID, '_bbp_old_user_id', true );
602 + if ( $old_user_id < 1 || get_transient( 'bbp_phpwind_source_connection_failed' ) ) {
603 + return false;
604 + }
605 +
606 + // Block concurrent login attempts while the source connection or query is pending.
607 + // Keep the backoff when either fails, so an unavailable source cannot stall
608 + // every login attempt for an imported username.
609 + set_transient( 'bbp_phpwind_source_connection_failed', 1, 5 * MINUTE_IN_SECONDS );
610 +
611 + if ( ! $this->opdb->db_connect( false ) ) {
612 + return false;
613 + }
614 +
615 + $legacy_table = $this->opdb->prefix . 'user';
616 + $windid_table = $this->opdb->prefix . 'windid_user';
617 + $source_user = $this->opdb->get_row(
618 + $this->opdb->prepare( "SELECT legacy.password AS legacy_password, windid.password, windid.salt FROM {$legacy_table} AS legacy INNER JOIN {$windid_table} AS windid ON windid.uid = legacy.uid WHERE legacy.uid = %d LIMIT 1", $old_user_id ),
619 + ARRAY_A
620 + );
621 + if ( empty( $this->opdb->last_error ) ) {
622 + delete_transient( 'bbp_phpwind_source_connection_failed' );
623 + }
624 +
625 + if ( ! is_array( $source_user ) || ! isset( $source_user['legacy_password'], $source_user['password'], $source_user['salt'] ) || ! is_string( $source_user['legacy_password'] ) || ! is_string( $source_user['password'] ) || ! is_string( $source_user['salt'] ) || ! hash_equals( $stored_hash, $source_user['legacy_password'] ) ) {
626 + return false;
627 + }
628 +
629 + return serialize(
630 + array(
631 + 'hash' => $source_user['password'],
632 + 'salt' => $source_user['salt'],
633 + )
634 + );
635 + }
636 +
637 + /**
638 + * Replace a verified PHPWind hash with a WordPress password.
639 + *
640 + * @param int $user_id WordPress user ID.
641 + * @param string $password Password to hash for WordPress.
642 + */
643 + private function upgrade_user_pass( $user_id, $password ) {
644 + wp_set_password( $password, $user_id );
645 + delete_user_meta( $user_id, '_bbp_password' );
646 + delete_user_meta( $user_id, '_bbp_class' );
647 + }
648 +
649 + /**
533 650 * Translate the forum type from PHPWind v9.x Capitalised case to WordPress's non-capatilise case strings.
534 651 *
535 652 * @param int $status PHPWind v9.x numeric forum type
536 653 * @return string WordPress safe
@@ -560,22 +677,15 @@
560 677 /**
561 678 * Translate the post status from PHPWind v9.x numerics to WordPress's strings.
562 679 *
563 680 * @param int $status PHPWind v9.x numeric topic status
564 - * @return string WordPress safe
681 + * @return string WordPress topic status.
565 682 */
566 - public function callback_topic_status( $status = 2 ) {
567 - switch ( $status ) {
568 - case 1 :
569 - $status = 'closed';
570 - break;
683 + public function callback_topic_status( $status = 0 ) {
684 + // PHPWind stores locked (0b0001) and closed (0b0010) flags in tpcstatus. bbPress represents either state as closed.
685 + $locked_or_closed_mask = 0b0011;
571 686
572 - case 2 :
573 - default :
574 - $status = 'publish';
575 - break;
576 - }
577 - return $status;
687 + return ( (int) $status & $locked_or_closed_mask ) ? 'closed' : 'publish';
578 688 }
579 689
580 690 /**
581 691 * Verify the topic/reply count.
@@ -580,11 +690,11 @@
580 690 /**
581 691 * Verify the topic/reply count.
582 692 *
583 693 * @param int $count PHPWind v9.x topic/reply counts
584 - * @return string WordPress safe
694 + * @return int Non-negative reply count.
585 695 */
586 696 public function callback_topic_reply_count( $count = 1 ) {
587 - $count = absint( (int) $count - 1 );
697 + $count = absint( $count );
588 698 return $count;
589 699 }
590 700 }