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
bbpress / includes / admin / classes / class-bbp-converter-base.php

class-bbp-converter-base.php in bbPress 2.6.19, at includes/admin/classes/class-bbp-converter-base.php

1,475 lines 45.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * bbPress Converter Base Class
5 *
6 * Based on the hard work of Adam Ellis
7 *
8 * @package bbPress
9 * @subpackage Administration
10 */
11
12 // Exit if accessed directly
13 defined( 'ABSPATH' ) || exit;
14
15 if ( ! class_exists( 'BBP_Converter_Base' ) ) :
16 /**
17 * Base class to be extended by specific individual importers
18 *
19 * phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
20 *
21 * @since 2.1.0 bbPress (r3816)
22 */
23 abstract class BBP_Converter_Base {
24
25 /**
26 * @var array() This is the field mapping array to process.
27 */
28 protected $field_map = array();
29
30 /**
31 * @var object This is the connection to the WordPress database.
32 */
33 protected $wpdb;
34
35 /**
36 * @var object This is the connection to the other platforms database.
37 */
38 protected $opdb;
39
40 /**
41 * @var int Halt on error. Default 0.
42 */
43 protected $halt = 0;
44
45 /**
46 * @var int Maximum number of rows to convert at 1 time. Default 100.
47 */
48 protected $max_rows = 100;
49
50 /**
51 * @var array() Map of topic to forum. It is for optimization.
52 */
53 protected $map_topicid_to_forumid = array();
54
55 /**
56 * @var array() Map of from old forum ids to new forum ids. It is for optimization.
57 */
58 protected $map_forumid = array();
59
60 /**
61 * @var array() Map of from old topic ids to new topic ids. It is for optimization.
62 */
63 protected $map_topicid = array();
64
65 /**
66 * @var array() Map of from old reply_to ids to new reply_to ids. It is for optimization.
67 */
68 protected $map_reply_to = array();
69
70 /**
71 * @var array() Map of from old user ids to new user ids. It is for optimization.
72 */
73 protected $map_userid = array();
74
75 /**
76 * @var string This is the charset for your wp database.
77 */
78 public $charset = '';
79
80 /**
81 * @var boolean Sync table available.
82 */
83 public $sync_table = false;
84
85 /**
86 * @var string Sync table name.
87 */
88 public $sync_table_name = '';
89
90 /**
91 * @var bool Whether users should be converted or not. Default false.
92 */
93 public $convert_users = false;
94
95 /**
96 * @var bool Whether to clean up any old converter data. Default false.
97 */
98 public $clean = false;
99
100 /**
101 * @var array Custom BBCode class properties in a key => value format
102 */
103 public $bbcode_parser_properties = array();
104
105 /** Methods ***************************************************************/
106
107 /**
108 * This is the constructor and it connects to the platform databases.
109 */
110 public function __construct() {
111 $this->init();
112 $this->setup_globals();
113 }
114
115 /**
116 * Initialize the converter
117 *
118 * @since 2.6.0 bbPress (r6456)
119 */
120 private function init() {
121
122 /** BBCode Parse Properties *******************************************/
123
124 // Setup smiley URL & path
125 $this->bbcode_parser_properties = array(
126 'smiley_url' => includes_url( 'images/smilies' ),
127 'smiley_dir' => '/' . WPINC . '/images/smilies'
128 );
129
130 /** Sanitize Options **************************************************/
131
132 $this->clean = ! empty( $_POST['_bbp_converter_clean'] );
133 $this->convert_users = current_user_can( 'bbp_tools_import_users' ) && (bool) get_option( '_bbp_converter_convert_users', false );
134 $this->halt = (bool) get_option( '_bbp_converter_halt', 0 );
135 $this->max_rows = (int) get_option( '_bbp_converter_rows', 100 );
136
137 /** Sanitize Connection ***********************************************/
138
139 $db_user = get_option( '_bbp_converter_db_user', DB_USER );
140 $db_pass = get_option( '_bbp_converter_db_pass', DB_PASSWORD );
141 $db_name = get_option( '_bbp_converter_db_name', DB_NAME );
142 $db_host = get_option( '_bbp_converter_db_server', DB_HOST );
143 $db_port = get_option( '_bbp_converter_db_port', '' );
144 $db_prefix = get_option( '_bbp_converter_db_prefix', '' );
145
146 // Maybe add port to server
147 if ( ! empty( $db_port ) && ! empty( $db_host ) && ! strstr( $db_host, ':' ) ) {
148 $db_host = "{$db_host}:{$db_port}";
149 }
150
151 /** Get database connections ******************************************/
152
153 // Setup WordPress Database
154 $this->wpdb = bbp_db();
155
156 // Setup old forum Database
157 $this->opdb = new BBP_Converter_DB( $db_user, $db_pass, $db_name, $db_host );
158
159 // Maybe setup the database prefix
160 $this->opdb->prefix = $db_prefix;
161
162 /**
163 * Don't wp_die() uncontrollably
164 */
165 $this->wpdb->show_errors( false );
166 $this->opdb->show_errors( false );
167
168 /**
169 * Syncing
170 */
171 $this->sync_table_name = $this->wpdb->prefix . 'bbp_converter_translator';
172 $this->sync_table = $this->sync_table_name === $this->wpdb->get_var( "SHOW TABLES LIKE '{$this->sync_table_name}'" )
173 ? true
174 : false;
175
176 /**
177 * Character set
178 */
179 $this->charset = ! empty( $this->wpdb->charset )
180 ? $this->wpdb->charset
181 : 'UTF8';
182
183 /**
184 * Default mapping.
185 */
186
187 /** Forum Section *****************************************************/
188
189 $this->field_map[] = array(
190 'to_type' => 'forum',
191 'to_fieldname' => 'post_status',
192 'default' => 'publish'
193 );
194 $this->field_map[] = array(
195 'to_type' => 'forum',
196 'to_fieldname' => 'comment_status',
197 'default' => 'closed'
198 );
199 $this->field_map[] = array(
200 'to_type' => 'forum',
201 'to_fieldname' => 'ping_status',
202 'default' => 'closed'
203 );
204 $this->field_map[] = array(
205 'to_type' => 'forum',
206 'to_fieldname' => 'post_type',
207 'default' => 'forum'
208 );
209
210 /** Topic Section *****************************************************/
211
212 $this->field_map[] = array(
213 'to_type' => 'topic',
214 'to_fieldname' => 'post_status',
215 'default' => 'publish'
216 );
217 $this->field_map[] = array(
218 'to_type' => 'topic',
219 'to_fieldname' => 'comment_status',
220 'default' => 'closed'
221 );
222 $this->field_map[] = array(
223 'to_type' => 'topic',
224 'to_fieldname' => 'ping_status',
225 'default' => 'closed'
226 );
227 $this->field_map[] = array(
228 'to_type' => 'topic',
229 'to_fieldname' => 'post_type',
230 'default' => 'topic'
231 );
232
233 /** Post Section ******************************************************/
234
235 $this->field_map[] = array(
236 'to_type' => 'reply',
237 'to_fieldname' => 'post_status',
238 'default' => 'publish'
239 );
240 $this->field_map[] = array(
241 'to_type' => 'reply',
242 'to_fieldname' => 'comment_status',
243 'default' => 'closed'
244 );
245 $this->field_map[] = array(
246 'to_type' => 'reply',
247 'to_fieldname' => 'ping_status',
248 'default' => 'closed'
249 );
250 $this->field_map[] = array(
251 'to_type' => 'reply',
252 'to_fieldname' => 'post_type',
253 'default' => 'reply'
254 );
255
256 /** User Section ******************************************************/
257
258 $this->field_map[] = array(
259 'to_type' => 'user',
260 'to_fieldname' => 'role',
261 'default' => get_option( 'default_role' )
262 );
263 }
264
265 /**
266 * Setup global values
267 */
268 public function setup_globals() {}
269
270 /**
271 * Setup fields that require an active source database connection.
272 *
273 * @since 2.6.18 bbPress (r7588)
274 */
275 protected function setup_source_fields() {}
276
277 /**
278 * Convert Forums
279 */
280 public function convert_forums( $start = 1 ) {
281 return $this->convert_table( 'forum', $start );
282 }
283
284 /**
285 * Convert Topics / Threads
286 */
287 public function convert_topics( $start = 1 ) {
288 return $this->convert_table( 'topic', $start );
289 }
290
291 /**
292 * Convert Posts
293 */
294 public function convert_replies( $start = 1 ) {
295 return $this->convert_table( 'reply', $start );
296 }
297
298 /**
299 * Convert Users
300 */
301 public function convert_users( $start = 1 ) {
302 return $this->convert_table( 'user', $start );
303 }
304
305 /**
306 * Convert Topic Tags
307 */
308 public function convert_tags( $start = 1 ) {
309 return $this->convert_table( 'tags', $start );
310 }
311
312 /**
313 * Convert Forum Subscriptions
314 */
315 public function convert_forum_subscriptions( $start = 1 ) {
316 return $this->convert_table( 'forum_subscriptions', $start );
317 }
318
319 /**
320 * Convert Topic Subscriptions
321 */
322 public function convert_topic_subscriptions( $start = 1 ) {
323 return $this->convert_table( 'topic_subscriptions', $start );
324 }
325
326 /**
327 * Convert Favorites
328 */
329 public function convert_favorites( $start = 1 ) {
330 return $this->convert_table( 'favorites', $start );
331 }
332
333 /**
334 * Convert Table
335 *
336 * @since 2.1.0 bbPress (r3816) Connects to the source database when conversion begins.
337 *
338 * @param string $to_type The destination type
339 * @param int $start Start row
340 */
341 public function convert_table( $to_type, $start ) {
342 // Without account import, do not create users or user engagements.
343 if ( ! $this->convert_users && in_array( $to_type, array( 'user', 'forum_subscriptions', 'topic_subscriptions', 'favorites' ), true ) ) {
344 return true;
345 }
346
347 // Connect to the source database only when conversion begins. This keeps
348 // first-login password upgrades independent of the source database.
349 if ( ! $this->opdb->db_connect( false ) ) {
350 $error = new WP_Error( 'bbp_converter_db_connection_failed', esc_html__( 'Database connection failed.', 'bbpress' ) );
351 wp_send_json_error( $error );
352 }
353
354 // Setup fields that depend on the connected source schema.
355 $this->setup_source_fields();
356
357 // Set some defaults
358 $has_insert = false;
359 $from_tablename = '';
360 $field_list = $from_tables = $tablefield_array = array();
361
362 // Toggle Table Name based on $to_type (destination)
363 switch ( $to_type ) {
364 case 'user' :
365 $tablename = $this->wpdb->users;
366 break;
367
368 case 'tags' :
369 $tablename = '';
370 break;
371
372 case 'forum_subscriptions' :
373 $tablename = $this->wpdb->postmeta;
374 break;
375
376 case 'topic_subscriptions' :
377 $tablename = $this->wpdb->postmeta;
378 break;
379
380 case 'favorites' :
381 $tablename = $this->wpdb->postmeta;
382 break;
383
384 default :
385 $tablename = $this->wpdb->posts;
386 }
387
388 // Get the fields from the destination table
389 if ( ! empty( $tablename ) ) {
390 $tablefield_array = $this->get_fields( $tablename );
391 }
392
393 /** Step 1 ************************************************************/
394
395 // Loop through the field maps, and look for to_type matches
396 foreach ( $this->field_map as $item ) {
397
398 // Yay a match, and we have a from table, too
399 if ( ( $item['to_type'] === $to_type ) && ! empty( $item['from_tablename'] ) ) {
400
401 // $from_tablename was set from a previous loop iteration
402 if ( ! empty( $from_tablename ) ) {
403
404 // Doing some joining
405 if ( ! in_array( $item['from_tablename'], $from_tables, true ) && in_array( $item['join_tablename'], $from_tables, true ) ) {
406 $from_tablename .= ' ' . $item['join_type'] . ' JOIN ' . $this->opdb->prefix . $item['from_tablename'] . ' AS ' . $item['from_tablename'] . ' ' . $item['join_expression'];
407 }
408
409 // $from_tablename needs to be set
410 } else {
411 $from_tablename = $item['from_tablename'] . ' AS ' . $item['from_tablename'];
412 }
413
414 // Specific FROM expression data used
415 if ( ! empty( $item['from_expression'] ) ) {
416
417 // No 'WHERE' in expression
418 if ( stripos( $from_tablename, 'WHERE' ) === false ) {
419 $from_tablename .= ' ' . $item['from_expression'];
420
421 // 'WHERE' in expression, so replace with 'AND'
422 } else {
423 $from_tablename .= ' ' . str_replace( 'WHERE', 'AND', $item['from_expression'] );
424 }
425 }
426
427 // Add tablename and fieldname to arrays, formatted for querying
428 $from_tables[] = $item['from_tablename'];
429 $field_list[] = 'convert(' . $item['from_tablename'] . '.' . $item['from_fieldname'] . ' USING "' . $this->charset . '") AS ' . $item['from_fieldname'];
430 }
431 }
432
433 /** Step 2 ************************************************************/
434
435 // We have a $from_tablename, so we want to get some data to convert
436 if ( ! empty( $from_tablename ) ) {
437
438 // Update rows
439 $this->count_rows_by_table( "{$this->opdb->prefix}{$from_tablename}" );
440
441 // Get some data from the old forums
442 $field_list = array_unique( $field_list );
443 $fields = implode( ',', $field_list );
444 $forum_query = "SELECT {$fields} FROM {$this->opdb->prefix}{$from_tablename} LIMIT {$start}, {$this->max_rows}";
445
446 // Set this query as the last one ran
447 $this->update_query( $forum_query );
448
449 // Get results as an array
450 $forum_array = $this->opdb->get_results( $forum_query, ARRAY_A );
451
452 // Query returned some results
453 if ( ! empty( $forum_array ) ) {
454
455 // Loop through results
456 foreach ( (array) $forum_array as $forum ) {
457
458 // Reset some defaults
459 $insert_post = $insert_postmeta = $insert_data = array();
460
461 // Loop through field map, again...
462 foreach ( $this->field_map as $row ) {
463
464 // Types match and to_fieldname is present. This means
465 // we have some work to do here.
466 if ( ( $row['to_type'] === $to_type ) && isset( $row['to_fieldname'] ) ) {
467
468 // This row has a destination that matches one of the
469 // columns in this table.
470 if ( in_array( $row['to_fieldname'], $tablefield_array, true ) ) {
471 // Source user IDs must not claim existing WordPress accounts.
472 if ( ( 'post_author' === $row['to_fieldname'] ) && ! $this->convert_users ) {
473 $insert_post['post_author'] = 0;
474
475 // Allows us to set default fields.
476 } elseif ( isset( $row['default'] ) ) {
477 $insert_post[ $row['to_fieldname'] ] = $row['default'];
478
479 // Translates a field from the old forum.
480 } elseif ( isset( $row['callback_method'] ) ) {
481 if ( ( 'callback_userid' === $row['callback_method'] ) && ( false === $this->convert_users ) ) {
482 $insert_post[ $row['to_fieldname'] ] = 0;
483 } else {
484 $insert_post[ $row['to_fieldname'] ] = call_user_func_array( array( $this, $row['callback_method'] ), array( $forum[ $row['from_fieldname'] ], $forum ) );
485 }
486
487 // Maps the field from the old forum.
488 } else {
489 $insert_post[ $row['to_fieldname'] ] = $forum[ $row['from_fieldname'] ];
490 }
491
492 // Destination field is not empty, so we might need
493 // to do some extra work or set a default.
494 } elseif ( ! empty( $row['to_fieldname'] ) ) {
495
496 // Allows us to set default fields.
497 if ( isset( $row['default'] ) ) {
498 $insert_postmeta[ $row['to_fieldname'] ] = $row['default'];
499
500 // Translates a field from the old forum.
501 } elseif ( isset( $row['callback_method'] ) ) {
502 if ( ( 'callback_userid' === $row['callback_method'] ) && ( false === $this->convert_users ) ) {
503 $insert_postmeta[ $row['to_fieldname'] ] = 0;
504 } else {
505 $insert_postmeta[ $row['to_fieldname'] ] = call_user_func_array( array( $this, $row['callback_method'] ), array( $forum[ $row['from_fieldname'] ], $forum ) );
506 }
507
508 // Maps the field from the old forum.
509 } else {
510 $insert_postmeta[ $row['to_fieldname'] ] = $forum[ $row['from_fieldname'] ];
511 }
512 }
513 }
514 }
515
516 /** Step 3 ************************************************/
517
518 // Something to insert into the destination field
519 if ( count( $insert_post ) > 0 || ( 'tags' === $to_type && count( $insert_postmeta ) > 0 ) ) {
520
521 switch ( $to_type ) {
522
523 /** New user **************************************/
524
525 case 'user' :
526 if ( username_exists( $insert_post['user_login'] ) ) {
527 $insert_post['user_login'] = "imported_{$insert_post['user_login']}";
528 }
529
530 if ( email_exists( $insert_post['user_email'] ) ) {
531 $insert_post['user_email'] = "imported_{$insert_post['user_email']}";
532 }
533
534 if ( empty( $insert_post['user_pass'] ) ) {
535 $insert_post['user_pass'] = '';
536 }
537
538 // Internally re-calls _exists() checks above.
539 // Also checks for existing nicename.
540 $post_id = wp_insert_user( $insert_post );
541
542 if ( is_numeric( $post_id ) ) {
543 foreach ( $insert_postmeta as $key => $value ) {
544 add_user_meta( $post_id, $key, $value, true );
545
546 if ( '_id' === substr( $key, -3 ) && ( true === $this->sync_table ) ) {
547 $this->wpdb->insert(
548 $this->sync_table_name,
549 array(
550 'value_type' => 'user',
551 'value_id' => $post_id,
552 'meta_key' => $key,
553 'meta_value' => $value
554 )
555 );
556 }
557 }
558 }
559 break;
560
561 /** New Topic-Tag *********************************/
562
563 case 'tags' :
564 $post_id = wp_set_object_terms( $insert_postmeta['objectid'], $insert_postmeta['name'], 'topic-tag', true );
565 $term = get_term_by( 'name', $insert_postmeta['name'], 'topic-tag');
566 if ( false !== $term ) {
567 wp_update_term(
568 $term->term_id,
569 'topic-tag',
570 array(
571 'description' => $insert_postmeta['description'],
572 'slug' => $insert_postmeta['slug']
573 )
574 );
575 }
576 break;
577
578 /** Forum Subscriptions ***************************/
579
580 case 'forum_subscriptions' :
581 $user_id = $insert_post['user_id'];
582 $items = wp_list_pluck( $insert_postmeta, '_bbp_forum_subscriptions' );
583 if ( is_numeric( $user_id ) && ! empty( $items ) ) {
584 foreach ( $items as $value ) {
585
586 // Maybe string with commas
587 $value = is_string( $value )
588 ? explode( ',', $value )
589 : (array) $value;
590
591 // Add user ID to forums subscribed users
592 foreach ( $value as $fav ) {
593 bbp_add_user_subscription( $user_id, $this->callback_forumid( $fav ) );
594 }
595 }
596 }
597 break;
598
599 /** Subscriptions *********************************/
600
601 case 'topic_subscriptions' :
602 $user_id = $insert_post['user_id'];
603 $items = wp_list_pluck( $insert_postmeta, '_bbp_subscriptions' );
604 if ( is_numeric( $user_id ) && ! empty( $items ) ) {
605 foreach ( $items as $value ) {
606
607 // Maybe string with commas
608 $value = is_string( $value )
609 ? explode( ',', $value )
610 : (array) $value;
611
612 // Add user ID to topics subscribed users
613 foreach ( $value as $fav ) {
614 bbp_add_user_subscription( $user_id, $this->callback_topicid( $fav ) );
615 }
616 }
617 }
618 break;
619
620 /** Favorites *************************************/
621
622 case 'favorites' :
623 $user_id = $insert_post['user_id'];
624 $items = wp_list_pluck( $insert_postmeta, '_bbp_favorites' );
625 if ( is_numeric( $user_id ) && ! empty( $items ) ) {
626 foreach ( $items as $value ) {
627
628 // Maybe string with commas
629 $value = is_string( $value )
630 ? explode( ',', $value )
631 : (array) $value;
632
633 // Add user ID to topics favorited users
634 foreach ( $value as $fav ) {
635 bbp_add_user_favorite( $user_id, $this->callback_topicid( $fav ) );
636 }
637 }
638 }
639 break;
640
641 /** Forum, Topic, Reply ***************************/
642
643 default :
644 $post_id = $this->insert_post( $insert_post );
645
646 if ( is_numeric( $post_id ) ) {
647 foreach ( $insert_postmeta as $key => $value ) {
648 add_post_meta( $post_id, $key, $value, true );
649
650 /**
651 * If we are using the sync_table add
652 * the meta '_id' keys to the table
653 *
654 * Forums: _bbp_old_forum_id // The old forum ID
655 * _bbp_old_forum_parent_id // The old forum parent ID
656 *
657 * Topics: _bbp_forum_id // The new forum ID
658 * _bbp_old_topic_id // The old topic ID
659 * _bbp_old_closed_status_id // The old topic open/closed status
660 * _bbp_old_sticky_status_id // The old topic sticky status
661 *
662 * Replies: _bbp_forum_id // The new forum ID
663 * _bbp_topic_id // The new topic ID
664 * _bbp_old_reply_id // The old reply ID
665 * _bbp_old_reply_to_id // The old reply to ID
666 */
667 if ( '_id' === substr( $key, -3 ) && ( true === $this->sync_table ) ) {
668 $this->wpdb->insert(
669 $this->sync_table_name,
670 array(
671 'value_type' => 'post',
672 'value_id' => $post_id,
673 'meta_key' => $key,
674 'meta_value' => $value
675 )
676 );
677 }
678
679 /**
680 * Replies need to save their old reply_to ID for
681 * hierarchical replies association. Later we update
682 * the _bbp_reply_to value with the new bbPress
683 * value using convert_reply_to_parents()
684 */
685 if ( ( 'reply' === $to_type ) && ( '_bbp_old_reply_to_id' === $key ) ) {
686 add_post_meta( $post_id, '_bbp_reply_to', $value );
687 }
688 }
689 }
690 break;
691 }
692 $has_insert = true;
693 }
694 }
695 }
696 }
697
698 return ! $has_insert;
699 }
700
701 /**
702 * Insert a converted post without changing imported counts.
703 *
704 * Converter field maps include the source forum and topic counts. Prevent
705 * post-status transition callbacks from incrementing those counts again as
706 * each converted topic and reply is inserted.
707 *
708 * @since 2.6.18 bbPress (r7600)
709 *
710 * @param array $post_data Converted post data.
711 * @return int|WP_Error Post ID on success, WP_Error on failure.
712 */
713 protected function insert_post( $post_data = array() ) {
714 // Keep safe source formatting without trusting imported HTML.
715 foreach ( array( 'post_content', 'post_excerpt' ) as $field ) {
716 if ( isset( $post_data[ $field ] ) ) {
717 $post_data[ $field ] = wp_kses_post( $post_data[ $field ] );
718 }
719 }
720 if ( isset( $post_data['post_title'] ) ) {
721 $post_data['post_title'] = wp_strip_all_tags( $post_data['post_title'] );
722 }
723
724 $suppress_count_updates = function () {
725 return false;
726 };
727
728 add_filter( 'bbp_pre_update_counts_on_transition_post_status', $suppress_count_updates );
729
730 try {
731 $post_id = wp_insert_post( $post_data, true );
732 } finally {
733 remove_filter( 'bbp_pre_update_counts_on_transition_post_status', $suppress_count_updates );
734 }
735
736 return $post_id;
737 }
738
739 /**
740 * This method converts old forum hierarchy to new bbPress hierarchy.
741 */
742 public function convert_forum_parents( $start = 1 ) {
743 $has_update = false;
744 $query = ! empty( $this->sync_table )
745 ? $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value > 0 LIMIT {$start}, {$this->max_rows}", '_bbp_old_forum_parent_id' )
746 : $this->wpdb->prepare( "SELECT post_id AS value_id, meta_value FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value > 0 LIMIT {$start}, {$this->max_rows}", '_bbp_old_forum_parent_id' );
747
748 foreach ( $this->count_rows_by_results( $query ) as $row ) {
749 $parent_id = $this->callback_forumid( $row->meta_value );
750 $this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->posts} SET post_parent = %d WHERE ID = %d LIMIT 1", $parent_id, $row->value_id ) );
751 $has_update = true;
752 }
753
754 return ! $has_update;
755 }
756
757 /**
758 * This method converts old topic stickies to new bbPress stickies.
759 *
760 * @since 2.5.0 bbPress (r5170)
761 */
762 public function convert_topic_stickies( $start = 1 ) {
763 $has_update = false;
764 $query = ! empty( $this->sync_table )
765 ? $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT {$start}, {$this->max_rows}", '_bbp_old_sticky_status_id', 'sticky' )
766 : $this->wpdb->prepare( "SELECT post_id AS value_id, meta_value FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s LIMIT {$start}, {$this->max_rows}", '_bbp_old_sticky_status_id', 'sticky' );
767
768 foreach ( $this->count_rows_by_results( $query ) as $row ) {
769 bbp_stick_topic( $row->value_id );
770 $has_update = true;
771 }
772
773 return ! $has_update;
774 }
775
776 /**
777 * This method converts old topic super stickies to new bbPress super stickies.
778 *
779 * @since 2.5.0 bbPress (r5170)
780 */
781 public function convert_topic_super_stickies( $start = 1 ) {
782 $has_update = false;
783 $query = ! empty( $this->sync_table )
784 ? $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT {$start}, {$this->max_rows}", '_bbp_old_sticky_status_id', 'super-sticky' )
785 : $this->wpdb->prepare( "SELECT post_id AS value_id, meta_value FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s LIMIT {$start}, {$this->max_rows}", '_bbp_old_sticky_status_id', 'super-sticky' );
786
787 foreach ( $this->count_rows_by_results( $query ) as $row ) {
788 $super = true;
789 bbp_stick_topic( $row->value_id, $super );
790 $has_update = true;
791 }
792
793 return ! $has_update;
794 }
795
796 /**
797 * This method converts old closed topics to bbPress closed topics.
798 *
799 * @since 2.6.0 bbPress (r5425)
800 */
801 public function convert_topic_closed_topics( $start = 1 ) {
802 $has_update = false;
803 $query = ! empty( $this->sync_table )
804 ? $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT {$start}, {$this->max_rows}", '_bbp_old_closed_status_id', 'closed' )
805 : $this->wpdb->prepare( "SELECT post_id AS value_id, meta_value FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s LIMIT {$start}, {$this->max_rows}", '_bbp_old_closed_status_id', 'closed' );
806
807 foreach ( $this->count_rows_by_results( $query ) as $row ) {
808 bbp_close_topic( $row->value_id );
809 $has_update = true;
810 }
811
812 return ! $has_update;
813 }
814
815 /**
816 * This method converts old reply_to post id to new bbPress reply_to post id.
817 *
818 * @since 2.4.0 bbPress (r5093)
819 */
820 public function convert_reply_to_parents( $start = 1 ) {
821 $has_update = false;
822 $query = ! empty( $this->sync_table )
823 ? $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value > 0 LIMIT {$start}, {$this->max_rows}", '_bbp_old_reply_to_id' )
824 : $this->wpdb->prepare( "SELECT post_id AS value_id, meta_value FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value > 0 LIMIT {$start}, {$this->max_rows}", '_bbp_old_reply_to_id' );
825
826 foreach ( $this->count_rows_by_results( $query ) as $row ) {
827 $reply_to = $this->callback_reply_to( $row->meta_value );
828 $this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->postmeta} SET meta_value = %s WHERE meta_key = %s AND post_id = %d LIMIT 1", $reply_to, '_bbp_reply_to', $row->value_id ) );
829 $has_update = true;
830 }
831
832 return ! $has_update;
833 }
834
835 /**
836 * This method converts anonymous topics.
837 *
838 * @since 2.6.0 bbPress (r5538)
839 */
840 public function convert_anonymous_topic_authors( $start = 1 ) {
841
842 $has_update = false;
843
844 if ( ! empty( $this->sync_table ) ) {
845 $query = $this->wpdb->prepare( "SELECT sync_table1.value_id AS topic_id, sync_table1.meta_value AS topic_is_anonymous, sync_table2.meta_value AS topic_author
846 FROM {$this->sync_table_name} AS sync_table1
847 INNER JOIN {$this->sync_table_name} AS sync_table2
848 ON ( sync_table1.value_id = sync_table2.value_id )
849 WHERE sync_table1.meta_value = %s
850 AND sync_table2.meta_key = %s
851 LIMIT {$start}, {$this->max_rows}",
852 'true',
853 '_bbp_old_topic_author_name_id'
854 );
855 } else {
856 $query = $this->wpdb->prepare( "SELECT wp_postmeta1.post_id AS topic_id, wp_postmeta1.meta_value AS topic_is_anonymous, wp_postmeta2.meta_value AS topic_author
857 FROM {$this->wpdb->postmeta} AS wp_postmeta1
858 INNER JOIN {$this->wpdb->postmeta} AS wp_postmeta2
859 ON ( wp_postmeta1.post_id = wp_postmeta2.post_id )
860 WHERE wp_postmeta1.meta_value = %s
861 AND wp_postmeta2.meta_key = %s
862 LIMIT {$start}, {$this->max_rows}",
863 'true',
864 '_bbp_old_topic_author_name_id'
865 );
866 }
867
868 foreach ( $this->count_rows_by_results( $query ) as $row ) {
869 $anonymous_topic_author_id = 0;
870 $this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->posts} SET post_author = %d WHERE ID = %d LIMIT 1", $anonymous_topic_author_id, $row->topic_id ) );
871
872 add_post_meta( $row->topic_id, '_bbp_anonymous_name', $row->topic_author );
873
874 $has_update = true;
875 }
876
877 return ! $has_update;
878 }
879
880 /**
881 * This method converts anonymous replies.
882 *
883 * @since 2.6.0 bbPress (r5538)
884 */
885 public function convert_anonymous_reply_authors( $start = 1 ) {
886
887 $has_update = false;
888
889 if ( ! empty( $this->sync_table ) ) {
890 $query = $this->wpdb->prepare( "SELECT sync_table1.value_id AS reply_id, sync_table1.meta_value AS reply_is_anonymous, sync_table2.meta_value AS reply_author
891 FROM {$this->sync_table_name} AS sync_table1
892 INNER JOIN {$this->sync_table_name} AS sync_table2
893 ON ( sync_table1.value_id = sync_table2.value_id )
894 WHERE sync_table1.meta_value = %s
895 AND sync_table2.meta_key = %s
896 LIMIT {$start}, {$this->max_rows}",
897 'true',
898 '_bbp_old_reply_author_name_id'
899 );
900 } else {
901 $query = $this->wpdb->prepare( "SELECT wp_postmeta1.post_id AS reply_id, wp_postmeta1.meta_value AS reply_is_anonymous, wp_postmeta2.meta_value AS reply_author
902 FROM {$this->wpdb->postmeta} AS wp_postmeta1
903 INNER JOIN {$this->wpdb->postmeta} AS wp_postmeta2
904 ON ( wp_postmeta1.post_id = wp_postmeta2.post_id )
905 WHERE wp_postmeta1.meta_value = %s
906 AND wp_postmeta2.meta_key = %s
907 LIMIT {$start}, {$this->max_rows}",
908 'true',
909 '_bbp_old_reply_author_name_id'
910 );
911 }
912
913 foreach ( $this->count_rows_by_results( $query ) as $row ) {
914 $anonymous_reply_author_id = 0;
915 $this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->posts} SET post_author = %d WHERE ID = %d LIMIT 1", $anonymous_reply_author_id, $row->reply_id ) );
916
917 add_post_meta( $row->reply_id, '_bbp_anonymous_name', $row->reply_author );
918
919 $has_update = true;
920 }
921
922 return ! $has_update;
923 }
924
925 /**
926 * This method deletes data from the wp database.
927 *
928 * @since 2.1.0 bbPress (r3816)
929 */
930 public function clean() {
931
932 // Defaults
933 $has_delete = false;
934
935 /** Delete topics/forums/posts ****************************************/
936
937 $esc_like = $this->wpdb->esc_like( '_bbp_' ) . '%';
938 $query = ! empty( $this->sync_table )
939 ? $this->wpdb->prepare( "SELECT value_id FROM {$this->sync_table_name} INNER JOIN {$this->wpdb->posts} ON(value_id = ID) WHERE meta_key LIKE %s AND value_type = %s GROUP BY value_id ORDER BY value_id DESC LIMIT {$this->max_rows}", $esc_like, 'post' )
940 : $this->wpdb->prepare( "SELECT postmeta.post_id AS value_id
941 FROM {$this->wpdb->postmeta} AS postmeta
942 INNER JOIN {$this->wpdb->posts} AS posts ON ( postmeta.post_id = posts.ID )
943 WHERE ( posts.post_type = %s AND postmeta.meta_key = %s )
944 OR ( posts.post_type = %s AND postmeta.meta_key = %s )
945 OR ( posts.post_type = %s AND postmeta.meta_key = %s )
946 GROUP BY postmeta.post_id ORDER BY postmeta.post_id DESC LIMIT {$this->max_rows}",
947 bbp_get_forum_post_type(),
948 '_bbp_old_forum_id',
949 bbp_get_topic_post_type(),
950 '_bbp_old_topic_id',
951 bbp_get_reply_post_type(),
952 '_bbp_old_reply_id'
953 );
954
955 $posts = $this->get_results( $query, ARRAY_A );
956
957 if ( isset( $posts[0] ) && ! empty( $posts[0]['value_id'] ) ) {
958 foreach ( (array) $posts as $value ) {
959 $deleted = wp_delete_post( $value['value_id'], true );
960
961 // Only flag if not empty or error
962 if ( ( false === $has_delete ) && ! empty( $deleted ) && ! is_wp_error( $deleted ) ) {
963 $has_delete = true;
964 }
965 }
966 }
967
968 // Imported accounts are network-wide and cannot be owned by one site.
969 if ( is_multisite() ) {
970 return ! $has_delete;
971 }
972
973 /** Delete users ******************************************************/
974
975 $query = ! empty( $this->sync_table )
976 ? $this->wpdb->prepare( "SELECT value_id FROM {$this->sync_table_name} INNER JOIN {$this->wpdb->users} ON(value_id = ID) WHERE meta_key = %s AND value_type = %s LIMIT {$this->max_rows}", '_bbp_old_user_id', 'user' )
977 : $this->wpdb->prepare( "SELECT user_id AS value_id FROM {$this->wpdb->usermeta} WHERE meta_key = %s LIMIT {$this->max_rows}", '_bbp_old_user_id' );
978
979 $users = $this->get_results( $query, ARRAY_A );
980
981 if ( ! empty( $users ) ) {
982 foreach ( $users as $value ) {
983 $deleted = wp_delete_user( $value['value_id'] );
984
985 // Only flag if not empty or error
986 if ( ( false === $has_delete ) && ! empty( $deleted ) && ! is_wp_error( $deleted ) ) {
987 $has_delete = true;
988 }
989 }
990 }
991
992 unset( $posts );
993 unset( $users );
994
995 return ! $has_delete;
996 }
997
998 /**
999 * This method deletes passwords from the wp database.
1000 *
1001 * @param int $start Current cleanup offset. Zero starts a new pass.
1002 */
1003 public function clean_passwords( $start = 0 ) {
1004 $has_delete = false;
1005 $max_rows = (int) $this->max_rows;
1006
1007 // Use an immutable cursor because native hashes are moved to user_pass and
1008 // deleted from usermeta. An offset would skip rows as the result set shrinks.
1009 $cursor = empty( $start )
1010 ? 0
1011 : (int) get_option( '_bbp_converter_passwords_cursor', 0 );
1012
1013 $query = $this->wpdb->prepare( "SELECT umeta_id, user_id, meta_value FROM {$this->wpdb->usermeta} WHERE meta_key = %s AND umeta_id > %d ORDER BY umeta_id ASC LIMIT {$max_rows}", '_bbp_password', $cursor );
1014 $converted = $this->get_results( $query, ARRAY_A );
1015
1016 if ( ! empty( $converted ) ) {
1017 foreach ( $converted as $value ) {
1018 if ( is_serialized( $value['meta_value'] ) ) {
1019 $this->update_password( $value['user_id'], '' );
1020 } else {
1021 $this->update_password( $value['user_id'], $value['meta_value'] );
1022 delete_user_meta( $value['user_id'], '_bbp_password' );
1023 }
1024
1025 clean_user_cache( $value['user_id'] );
1026 $cursor = (int) $value['umeta_id'];
1027 }
1028 update_option( '_bbp_converter_passwords_cursor', $cursor, false );
1029 $has_delete = true;
1030 } else {
1031 delete_option( '_bbp_converter_passwords_cursor' );
1032 }
1033
1034 return ! $has_delete;
1035 }
1036
1037 /**
1038 * This method implements the authentication for the different forums.
1039 *
1040 * @param string $password Password.
1041 * @param string $hash Password hash.
1042 */
1043 abstract protected function authenticate_pass( $password, $hash );
1044
1045 /**
1046 * Info
1047 */
1048 abstract protected function info();
1049
1050 /**
1051 * This method grabs appropriate fields from the table specified
1052 *
1053 * @param string $tablename The table name to grab fields from
1054 */
1055 private function get_fields( $tablename = '' ) {
1056 $retval = array();
1057 $field_array = $this->get_results( "DESCRIBE {$tablename}", ARRAY_A );
1058
1059 // Bail if no fields
1060 if ( empty( $field_array ) ) {
1061 return $retval;
1062 }
1063
1064 // Add fields to array
1065 foreach ( $field_array as $field ) {
1066 if ( ! empty( $field['Field'] ) ) {
1067 $retval[] = $field['Field'];
1068 }
1069 }
1070
1071 // Add social fields for users table
1072 if ( $tablename === $this->wpdb->users ) {
1073 $retval[] = 'role';
1074 $retval[] = 'yim';
1075 $retval[] = 'aim';
1076 $retval[] = 'jabber';
1077 }
1078
1079 return $retval;
1080 }
1081
1082 /** Database Wrappers *****************************************************/
1083
1084 /**
1085 * Update the last query option and return results
1086 *
1087 * @param string $query
1088 */
1089 private function get_row( $query = '' ) {
1090 $this->update_query( $query );
1091
1092 return $this->wpdb->get_row( $query ); // phpcs:ignore
1093 }
1094
1095 /**
1096 * Update the last query option and return results
1097 *
1098 * @param string $query
1099 * @param string $output
1100 */
1101 private function get_results( $query = '', $output = OBJECT ) {
1102 $this->update_query( $query );
1103
1104 return (array) $this->wpdb->get_results( $query, $output ); // phpcs:ignore
1105 }
1106
1107 /**
1108 * Update the last query option and do a general query
1109 *
1110 * @param string $query
1111 */
1112 private function query( $query = '' ) {
1113 $this->update_query( $query );
1114
1115 return $this->wpdb->query( $query ); // phpcs:ignore
1116 }
1117
1118 /**
1119 * Update a converted password without saving its hash in the last query option.
1120 *
1121 * @since 2.6.19 bbPress (r7641)
1122 *
1123 * @param int $user_id User ID.
1124 * @param string $password Password hash or an empty string.
1125 */
1126 private function update_password( $user_id, $password ) {
1127 $query = $this->wpdb->prepare( "UPDATE {$this->wpdb->users} SET user_pass = %s WHERE ID = %d", $password, $user_id );
1128
1129 return $this->wpdb->query( $query ); // phpcs:ignore
1130 }
1131
1132 /**
1133 * Update the last query ran
1134 *
1135 * @since 2.6.0 bbPress (r6638)
1136 *
1137 * @param string $query The literal MySQL query
1138 * @return bool
1139 */
1140 private function update_query( $query = '' ) {
1141 return update_option( '_bbp_converter_query', $query );
1142 }
1143
1144 /**
1145 * Update the number of rows in the current step
1146 *
1147 * @since 2.6.0 bbPress (r6681)
1148 *
1149 * @param string $query The literal MySQL query
1150 * @return array
1151 */
1152 private function count_rows_by_results( $query = '' ) {
1153 $results = $this->get_results( $query );
1154
1155 update_option( '_bbp_converter_rows_in_step', count( $results ) );
1156
1157 return $results;
1158 }
1159
1160 /**
1161 * Update the number of rows in the current step
1162 *
1163 * @since 2.6.0 bbPress (r6681)
1164 *
1165 * @param string $table_name The literal MySQL query
1166 * @return bool
1167 */
1168 private function count_rows_by_table( $table_name = '' ) {
1169 $count = (int) $this->opdb->get_var( "SELECT COUNT(*) FROM {$table_name}" );
1170
1171 return update_option( '_bbp_converter_rows_in_step', $count );
1172 }
1173
1174 /** Callbacks *************************************************************/
1175
1176 /**
1177 * Unserialize imported password metadata as an array.
1178 *
1179 * @since 2.6.18 bbPress (r7588)
1180 *
1181 * @param string $serialized_pass Serialized password metadata.
1182 * @return array|false Password metadata, or false when invalid.
1183 */
1184 protected function unserialize_pass( $serialized_pass = '' ) {
1185 if ( ! is_string( $serialized_pass ) || ! is_serialized( $serialized_pass ) || $this->serialized_pass_has_object( $serialized_pass ) ) {
1186 return false;
1187 }
1188
1189 // Malformed source metadata may still warn after is_serialized().
1190 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1191 $pass_array = @unserialize(
1192 $serialized_pass,
1193 array(
1194 'allowed_classes' => false,
1195 'max_depth' => 1,
1196 )
1197 );
1198
1199 return is_array( $pass_array )
1200 ? $pass_array
1201 : false;
1202 }
1203
1204 /**
1205 * Check serialized password metadata for object tokens without instantiating it.
1206 *
1207 * Serialized strings are skipped by their declared byte length so object-like
1208 * text inside a hash or salt does not cause a false positive.
1209 *
1210 * @since 2.6.18 bbPress (r7588)
1211 *
1212 * @param string $serialized_pass Serialized password metadata.
1213 * @return bool True when the value contains an object or is unsafe to parse.
1214 */
1215 private function serialized_pass_has_object( $serialized_pass = '' ) {
1216 $length = strlen( $serialized_pass );
1217
1218 for ( $offset = 0; $offset < $length; ++$offset ) {
1219 $token = $serialized_pass[ $offset ];
1220
1221 if ( in_array( $token, array( 'O', 'C', 'E' ), true ) && isset( $serialized_pass[ $offset + 1 ] ) && ':' === $serialized_pass[ $offset + 1 ] ) {
1222 return true;
1223 }
1224
1225 if ( 's' !== $token || ! isset( $serialized_pass[ $offset + 1 ] ) || ':' !== $serialized_pass[ $offset + 1 ] ) {
1226 continue;
1227 }
1228
1229 $length_end = strpos( $serialized_pass, ':"', $offset + 2 );
1230 if ( false === $length_end ) {
1231 return true;
1232 }
1233
1234 $string_length = substr( $serialized_pass, $offset + 2, $length_end - $offset - 2 );
1235 $string_start = $length_end + 2;
1236 if ( '' === $string_length || ! ctype_digit( $string_length ) || (int) $string_length > $length - $string_start ) {
1237 return true;
1238 }
1239
1240 $string_end = $string_start + (int) $string_length;
1241 if ( ! isset( $serialized_pass[ $string_end ] ) || '"' !== $serialized_pass[ $string_end ] ) {
1242 return true;
1243 }
1244
1245 $offset = $string_end;
1246 }
1247
1248 return false;
1249 }
1250
1251 /**
1252 * Run password through wp_hash_password()
1253 *
1254 * @since 2.1.0 bbPress (r3866) Added the `$wp_password` parameter.
1255 *
1256 * @param string $username
1257 * @param string $password
1258 * @param string|null $wp_password Optional slashed password for WordPress.
1259 */
1260 public function callback_pass( $username = '', $password = '', $wp_password = null ) {
1261
1262 // Password upgrades run during login, outside of converter progress.
1263 // Avoid writing these lookups to the last query option on every attempt.
1264
1265 // Get user – Bail if not found
1266 $user = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->users} WHERE user_login = %s AND user_pass = '' LIMIT 1", $username ) ); // phpcs:ignore
1267 if ( empty( $user ) ) {
1268 return;
1269 }
1270
1271 // Get usermeta – Bail if not found
1272 $usermeta = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->usermeta} WHERE meta_key = %s AND user_id = %d LIMIT 1", '_bbp_password', $user->ID ) ); // phpcs:ignore
1273 if ( empty( $usermeta ) ) {
1274 return;
1275 }
1276
1277 // Bail if meta is not a string
1278 if ( empty( $usermeta->meta_value ) || ! is_serialized( $usermeta->meta_value ) ) {
1279 return;
1280 }
1281
1282 // Bail if meta is suspiciously long
1283 if ( strlen( $usermeta->meta_value ) > 512 ) {
1284 return;
1285 }
1286
1287 // Bail if platform-specific auth fails
1288 if ( ! $this->authenticate_pass( $password, $usermeta->meta_value ) ) {
1289 return;
1290 }
1291
1292 // Hash the password
1293 $new_pass = wp_hash_password( is_null( $wp_password ) ? $password : $wp_password );
1294
1295 // Update
1296 $this->update_password( $user->ID, $new_pass );
1297
1298 // Clean up
1299 unset( $new_pass );
1300 delete_user_meta( $user->ID, '_bbp_password' );
1301 delete_user_meta( $user->ID, '_bbp_class' );
1302
1303 // Clean the cache for this user since their password was
1304 // upgraded from the old platform to the new.
1305 clean_user_cache( $user->ID );
1306 }
1307
1308 /**
1309 * A mini cache system to reduce database calls to forum ID's
1310 *
1311 * @param string $field
1312 * @return string
1313 */
1314 private function callback_forumid( $field ) {
1315 if ( ! isset( $this->map_forumid[ $field ] ) ) {
1316 $row = ! empty( $this->sync_table )
1317 ? $this->get_row( $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_forum_id', $field ) )
1318 : $this->get_row( $this->wpdb->prepare( "SELECT post_id AS value_id FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_forum_id', $field ) );
1319
1320 $this->map_forumid[ $field ] = ! is_null( $row )
1321 ? $row->value_id
1322 : 0;
1323 }
1324
1325 return $this->map_forumid[ $field ];
1326 }
1327
1328 /**
1329 * A mini cache system to reduce database calls to topic ID's
1330 *
1331 * @param string $field
1332 * @return string
1333 */
1334 private function callback_topicid( $field ) {
1335 if ( ! isset( $this->map_topicid[ $field ] ) ) {
1336 $row = ! empty( $this->sync_table )
1337 ? $this->get_row( $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_topic_id', $field ) )
1338 : $this->get_row( $this->wpdb->prepare( "SELECT post_id AS value_id FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_topic_id', $field ) );
1339
1340 $this->map_topicid[ $field ] = ! is_null( $row )
1341 ? $row->value_id
1342 : 0;
1343 }
1344
1345 return $this->map_topicid[ $field ];
1346 }
1347
1348 /**
1349 * A mini cache system to reduce database calls to reply_to post id.
1350 *
1351 * @since 2.4.0 bbPress (r5093)
1352 *
1353 * @param string $field
1354 * @return string
1355 */
1356 private function callback_reply_to( $field ) {
1357 if ( ! isset( $this->map_reply_to[ $field ] ) ) {
1358 $row = ! empty( $this->sync_table )
1359 ? $this->get_row( $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_reply_id', $field ) )
1360 : $this->get_row( $this->wpdb->prepare( "SELECT post_id AS value_id FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_reply_id', $field ) );
1361
1362 $this->map_reply_to[ $field ] = ! is_null( $row )
1363 ? $row->value_id
1364 : 0;
1365 }
1366
1367 return $this->map_reply_to[ $field ];
1368 }
1369
1370 /**
1371 * A mini cache system to reduce database calls to user ID's
1372 *
1373 * @param string $field
1374 * @return string
1375 */
1376 private function callback_userid( $field ) {
1377 // Source IDs must never resolve to unrelated network users.
1378 if ( ! $this->convert_users ) {
1379 return 0;
1380 }
1381
1382 if ( ! isset( $this->map_userid[ $field ] ) ) {
1383 $row = ! empty( $this->sync_table )
1384 ? $this->get_row( $this->wpdb->prepare( "SELECT value_id, meta_value FROM {$this->sync_table_name} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_user_id', $field ) )
1385 : $this->get_row( $this->wpdb->prepare( "SELECT user_id AS value_id FROM {$this->wpdb->usermeta} WHERE meta_key = %s AND meta_value = %s LIMIT 1", '_bbp_old_user_id', $field ) );
1386
1387 if ( ! is_null( $row ) ) {
1388 $this->map_userid[ $field ] = $row->value_id;
1389 } else {
1390 $this->map_userid[ $field ] = ( true === $this->convert_users )
1391 ? 0
1392 : $field;
1393 }
1394 }
1395
1396 return $this->map_userid[ $field ];
1397 }
1398
1399 /**
1400 * Check if the topic or reply author is anonymous
1401 *
1402 * @since 2.6.0 bbPress (r5544)
1403 *
1404 * @param string $field
1405 * @return string
1406 */
1407 private function callback_check_anonymous( $field ) {
1408 $field = ! $this->callback_userid( $field )
1409 ? 'true'
1410 : 'false';
1411
1412 return $field;
1413 }
1414
1415 /**
1416 * A mini cache system to reduce database calls map topics ID's to forum ID's
1417 *
1418 * @param string $field
1419 * @return string
1420 */
1421 private function callback_topicid_to_forumid( $field ) {
1422 $topicid = $this->callback_topicid( $field );
1423 if ( empty( $topicid ) ) {
1424 $this->map_topicid_to_forumid[ $topicid ] = 0;
1425 } elseif ( ! isset( $this->map_topicid_to_forumid[ $topicid ] ) ) {
1426 $row = $this->get_row( $this->wpdb->prepare( "SELECT post_parent FROM {$this->wpdb->posts} WHERE ID = %d LIMIT 1", $topicid ) );
1427
1428 $this->map_topicid_to_forumid[ $topicid ] = ! is_null( $row )
1429 ? $row->post_parent
1430 : 0;
1431 }
1432
1433 return $this->map_topicid_to_forumid[ $topicid ];
1434 }
1435
1436 protected function callback_slug( $field ) {
1437 return sanitize_title( $field );
1438 }
1439
1440 protected function callback_negative( $field ) {
1441 return ( $field < 0 )
1442 ? 0
1443 : $field;
1444 }
1445
1446 protected function callback_html( $field ) {
1447 require_once bbp_admin()->admin_dir . 'parser.php';
1448
1449 // Setup the BBCode parser
1450 $bbcode = BBCode::getInstance();
1451
1452 // Pass BBCode properties to the parser
1453 foreach ( $this->bbcode_parser_properties as $prop => $value ) {
1454 $bbcode->{$prop} = $value;
1455 }
1456
1457 return $bbcode->Parse( $field );
1458 }
1459
1460 protected function callback_null( $field ) {
1461 return is_null( $field )
1462 ? ''
1463 : $field;
1464 }
1465
1466 protected function callback_datetime( $field ) {
1467 // phpcs:disable WordPress.DateTime.RestrictedFunctions.date_date
1468 return is_numeric( $field )
1469 ? date( 'Y-m-d H:i:s', $field )
1470 : date( 'Y-m-d H:i:s', strtotime( $field ) );
1471 // phpcs:enable
1472 }
1473 }
1474 endif;
1475