PluginProbe
bbPress / 2.6.18
bbPress v2.6.18
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.18, at includes/admin/classes/class-bbp-converter-base.php

1,418 lines 43.6 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 (r3813)
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.1.0
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 = (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
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.6.18 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
343 // Connect to the source database only when conversion begins. This keeps
344 // first-login password upgrades independent of the source database.
345 if ( ! $this->opdb->db_connect( false ) ) {
346 $error = new WP_Error( 'bbp_converter_db_connection_failed', esc_html__( 'Database connection failed.', 'bbpress' ) );
347 wp_send_json_error( $error );
348 }
349
350 // Setup fields that depend on the connected source schema.
351 $this->setup_source_fields();
352
353 // Set some defaults
354 $has_insert = false;
355 $from_tablename = '';
356 $field_list = $from_tables = $tablefield_array = array();
357
358 // Toggle Table Name based on $to_type (destination)
359 switch ( $to_type ) {
360 case 'user' :
361 $tablename = $this->wpdb->users;
362 break;
363
364 case 'tags' :
365 $tablename = '';
366 break;
367
368 case 'forum_subscriptions' :
369 $tablename = $this->wpdb->postmeta;
370 break;
371
372 case 'topic_subscriptions' :
373 $tablename = $this->wpdb->postmeta;
374 break;
375
376 case 'favorites' :
377 $tablename = $this->wpdb->postmeta;
378 break;
379
380 default :
381 $tablename = $this->wpdb->posts;
382 }
383
384 // Get the fields from the destination table
385 if ( ! empty( $tablename ) ) {
386 $tablefield_array = $this->get_fields( $tablename );
387 }
388
389 /** Step 1 ************************************************************/
390
391 // Loop through the field maps, and look for to_type matches
392 foreach ( $this->field_map as $item ) {
393
394 // Yay a match, and we have a from table, too
395 if ( ( $item['to_type'] === $to_type ) && ! empty( $item['from_tablename'] ) ) {
396
397 // $from_tablename was set from a previous loop iteration
398 if ( ! empty( $from_tablename ) ) {
399
400 // Doing some joining
401 if ( ! in_array( $item['from_tablename'], $from_tables, true ) && in_array( $item['join_tablename'], $from_tables, true ) ) {
402 $from_tablename .= ' ' . $item['join_type'] . ' JOIN ' . $this->opdb->prefix . $item['from_tablename'] . ' AS ' . $item['from_tablename'] . ' ' . $item['join_expression'];
403 }
404
405 // $from_tablename needs to be set
406 } else {
407 $from_tablename = $item['from_tablename'] . ' AS ' . $item['from_tablename'];
408 }
409
410 // Specific FROM expression data used
411 if ( ! empty( $item['from_expression'] ) ) {
412
413 // No 'WHERE' in expression
414 if ( stripos( $from_tablename, 'WHERE' ) === false ) {
415 $from_tablename .= ' ' . $item['from_expression'];
416
417 // 'WHERE' in expression, so replace with 'AND'
418 } else {
419 $from_tablename .= ' ' . str_replace( 'WHERE', 'AND', $item['from_expression'] );
420 }
421 }
422
423 // Add tablename and fieldname to arrays, formatted for querying
424 $from_tables[] = $item['from_tablename'];
425 $field_list[] = 'convert(' . $item['from_tablename'] . '.' . $item['from_fieldname'] . ' USING "' . $this->charset . '") AS ' . $item['from_fieldname'];
426 }
427 }
428
429 /** Step 2 ************************************************************/
430
431 // We have a $from_tablename, so we want to get some data to convert
432 if ( ! empty( $from_tablename ) ) {
433
434 // Update rows
435 $this->count_rows_by_table( "{$this->opdb->prefix}{$from_tablename}" );
436
437 // Get some data from the old forums
438 $field_list = array_unique( $field_list );
439 $fields = implode( ',', $field_list );
440 $forum_query = "SELECT {$fields} FROM {$this->opdb->prefix}{$from_tablename} LIMIT {$start}, {$this->max_rows}";
441
442 // Set this query as the last one ran
443 $this->update_query( $forum_query );
444
445 // Get results as an array
446 $forum_array = $this->opdb->get_results( $forum_query, ARRAY_A );
447
448 // Query returned some results
449 if ( ! empty( $forum_array ) ) {
450
451 // Loop through results
452 foreach ( (array) $forum_array as $forum ) {
453
454 // Reset some defaults
455 $insert_post = $insert_postmeta = $insert_data = array();
456
457 // Loop through field map, again...
458 foreach ( $this->field_map as $row ) {
459
460 // Types match and to_fieldname is present. This means
461 // we have some work to do here.
462 if ( ( $row['to_type'] === $to_type ) && isset( $row['to_fieldname'] ) ) {
463
464 // This row has a destination that matches one of the
465 // columns in this table.
466 if ( in_array( $row['to_fieldname'], $tablefield_array, true ) ) {
467
468 // Allows us to set default fields.
469 if ( isset( $row['default'] ) ) {
470 $insert_post[ $row['to_fieldname'] ] = $row['default'];
471
472 // Translates a field from the old forum.
473 } elseif ( isset( $row['callback_method'] ) ) {
474 if ( ( 'callback_userid' === $row['callback_method'] ) && ( false === $this->convert_users ) ) {
475 $insert_post[ $row['to_fieldname'] ] = $forum[ $row['from_fieldname'] ];
476 } else {
477 $insert_post[ $row['to_fieldname'] ] = call_user_func_array( array( $this, $row['callback_method'] ), array( $forum[ $row['from_fieldname'] ], $forum ) );
478 }
479
480 // Maps the field from the old forum.
481 } else {
482 $insert_post[ $row['to_fieldname'] ] = $forum[ $row['from_fieldname'] ];
483 }
484
485 // Destination field is not empty, so we might need
486 // to do some extra work or set a default.
487 } elseif ( ! empty( $row['to_fieldname'] ) ) {
488
489 // Allows us to set default fields.
490 if ( isset( $row['default'] ) ) {
491 $insert_postmeta[ $row['to_fieldname'] ] = $row['default'];
492
493 // Translates a field from the old forum.
494 } elseif ( isset( $row['callback_method'] ) ) {
495 if ( ( 'callback_userid' === $row['callback_method'] ) && ( false === $this->convert_users ) ) {
496 $insert_postmeta[ $row['to_fieldname'] ] = $forum[ $row['from_fieldname'] ];
497 } else {
498 $insert_postmeta[ $row['to_fieldname'] ] = call_user_func_array( array( $this, $row['callback_method'] ), array( $forum[ $row['from_fieldname'] ], $forum ) );
499 }
500
501 // Maps the field from the old forum.
502 } else {
503 $insert_postmeta[ $row['to_fieldname'] ] = $forum[ $row['from_fieldname'] ];
504 }
505 }
506 }
507 }
508
509 /** Step 3 ************************************************/
510
511 // Something to insert into the destination field
512 if ( count( $insert_post ) > 0 || ( 'tags' === $to_type && count( $insert_postmeta ) > 0 ) ) {
513
514 switch ( $to_type ) {
515
516 /** New user **************************************/
517
518 case 'user' :
519 if ( username_exists( $insert_post['user_login'] ) ) {
520 $insert_post['user_login'] = "imported_{$insert_post['user_login']}";
521 }
522
523 if ( email_exists( $insert_post['user_email'] ) ) {
524 $insert_post['user_email'] = "imported_{$insert_post['user_email']}";
525 }
526
527 if ( empty( $insert_post['user_pass'] ) ) {
528 $insert_post['user_pass'] = '';
529 }
530
531 // Internally re-calls _exists() checks above.
532 // Also checks for existing nicename.
533 $post_id = wp_insert_user( $insert_post );
534
535 if ( is_numeric( $post_id ) ) {
536 foreach ( $insert_postmeta as $key => $value ) {
537 add_user_meta( $post_id, $key, $value, true );
538
539 if ( '_id' === substr( $key, -3 ) && ( true === $this->sync_table ) ) {
540 $this->wpdb->insert(
541 $this->sync_table_name,
542 array(
543 'value_type' => 'user',
544 'value_id' => $post_id,
545 'meta_key' => $key,
546 'meta_value' => $value
547 )
548 );
549 }
550 }
551 }
552 break;
553
554 /** New Topic-Tag *********************************/
555
556 case 'tags' :
557 $post_id = wp_set_object_terms( $insert_postmeta['objectid'], $insert_postmeta['name'], 'topic-tag', true );
558 $term = get_term_by( 'name', $insert_postmeta['name'], 'topic-tag');
559 if ( false !== $term ) {
560 wp_update_term(
561 $term->term_id,
562 'topic-tag',
563 array(
564 'description' => $insert_postmeta['description'],
565 'slug' => $insert_postmeta['slug']
566 )
567 );
568 }
569 break;
570
571 /** Forum Subscriptions ***************************/
572
573 case 'forum_subscriptions' :
574 $user_id = $insert_post['user_id'];
575 $items = wp_list_pluck( $insert_postmeta, '_bbp_forum_subscriptions' );
576 if ( is_numeric( $user_id ) && ! empty( $items ) ) {
577 foreach ( $items as $value ) {
578
579 // Maybe string with commas
580 $value = is_string( $value )
581 ? explode( ',', $value )
582 : (array) $value;
583
584 // Add user ID to forums subscribed users
585 foreach ( $value as $fav ) {
586 bbp_add_user_subscription( $user_id, $this->callback_forumid( $fav ) );
587 }
588 }
589 }
590 break;
591
592 /** Subscriptions *********************************/
593
594 case 'topic_subscriptions' :
595 $user_id = $insert_post['user_id'];
596 $items = wp_list_pluck( $insert_postmeta, '_bbp_subscriptions' );
597 if ( is_numeric( $user_id ) && ! empty( $items ) ) {
598 foreach ( $items as $value ) {
599
600 // Maybe string with commas
601 $value = is_string( $value )
602 ? explode( ',', $value )
603 : (array) $value;
604
605 // Add user ID to topics subscribed users
606 foreach ( $value as $fav ) {
607 bbp_add_user_subscription( $user_id, $this->callback_topicid( $fav ) );
608 }
609 }
610 }
611 break;
612
613 /** Favorites *************************************/
614
615 case 'favorites' :
616 $user_id = $insert_post['user_id'];
617 $items = wp_list_pluck( $insert_postmeta, '_bbp_favorites' );
618 if ( is_numeric( $user_id ) && ! empty( $items ) ) {
619 foreach ( $items as $value ) {
620
621 // Maybe string with commas
622 $value = is_string( $value )
623 ? explode( ',', $value )
624 : (array) $value;
625
626 // Add user ID to topics favorited users
627 foreach ( $value as $fav ) {
628 bbp_add_user_favorite( $user_id, $this->callback_topicid( $fav ) );
629 }
630 }
631 }
632 break;
633
634 /** Forum, Topic, Reply ***************************/
635
636 default :
637 $post_id = $this->insert_post( $insert_post );
638
639 if ( is_numeric( $post_id ) ) {
640 foreach ( $insert_postmeta as $key => $value ) {
641 add_post_meta( $post_id, $key, $value, true );
642
643 /**
644 * If we are using the sync_table add
645 * the meta '_id' keys to the table
646 *
647 * Forums: _bbp_old_forum_id // The old forum ID
648 * _bbp_old_forum_parent_id // The old forum parent ID
649 *
650 * Topics: _bbp_forum_id // The new forum ID
651 * _bbp_old_topic_id // The old topic ID
652 * _bbp_old_closed_status_id // The old topic open/closed status
653 * _bbp_old_sticky_status_id // The old topic sticky status
654 *
655 * Replies: _bbp_forum_id // The new forum ID
656 * _bbp_topic_id // The new topic ID
657 * _bbp_old_reply_id // The old reply ID
658 * _bbp_old_reply_to_id // The old reply to ID
659 */
660 if ( '_id' === substr( $key, -3 ) && ( true === $this->sync_table ) ) {
661 $this->wpdb->insert(
662 $this->sync_table_name,
663 array(
664 'value_type' => 'post',
665 'value_id' => $post_id,
666 'meta_key' => $key,
667 'meta_value' => $value
668 )
669 );
670 }
671
672 /**
673 * Replies need to save their old reply_to ID for
674 * hierarchical replies association. Later we update
675 * the _bbp_reply_to value with the new bbPress
676 * value using convert_reply_to_parents()
677 */
678 if ( ( 'reply' === $to_type ) && ( '_bbp_old_reply_to_id' === $key ) ) {
679 add_post_meta( $post_id, '_bbp_reply_to', $value );
680 }
681 }
682 }
683 break;
684 }
685 $has_insert = true;
686 }
687 }
688 }
689 }
690
691 return ! $has_insert;
692 }
693
694 /**
695 * Insert a converted post without changing imported counts.
696 *
697 * Converter field maps include the source forum and topic counts. Prevent
698 * post-status transition callbacks from incrementing those counts again as
699 * each converted topic and reply is inserted.
700 *
701 * @since 2.6.18
702 *
703 * @param array $post_data Converted post data.
704 * @return int|WP_Error Post ID on success, WP_Error on failure.
705 */
706 protected function insert_post( $post_data = array() ) {
707 $suppress_count_updates = function () {
708 return false;
709 };
710
711 add_filter( 'bbp_pre_update_counts_on_transition_post_status', $suppress_count_updates );
712
713 try {
714 $post_id = wp_insert_post( $post_data, true );
715 } finally {
716 remove_filter( 'bbp_pre_update_counts_on_transition_post_status', $suppress_count_updates );
717 }
718
719 return $post_id;
720 }
721
722 /**
723 * This method converts old forum hierarchy to new bbPress hierarchy.
724 */
725 public function convert_forum_parents( $start = 1 ) {
726 $has_update = false;
727 $query = ! empty( $this->sync_table )
728 ? $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' )
729 : $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' );
730
731 foreach ( $this->count_rows_by_results( $query ) as $row ) {
732 $parent_id = $this->callback_forumid( $row->meta_value );
733 $this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->posts} SET post_parent = %d WHERE ID = %d LIMIT 1", $parent_id, $row->value_id ) );
734 $has_update = true;
735 }
736
737 return ! $has_update;
738 }
739
740 /**
741 * This method converts old topic stickies to new bbPress stickies.
742 *
743 * @since 2.5.0 bbPress (r5170)
744 */
745 public function convert_topic_stickies( $start = 1 ) {
746 $has_update = false;
747 $query = ! empty( $this->sync_table )
748 ? $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' )
749 : $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' );
750
751 foreach ( $this->count_rows_by_results( $query ) as $row ) {
752 bbp_stick_topic( $row->value_id );
753 $has_update = true;
754 }
755
756 return ! $has_update;
757 }
758
759 /**
760 * This method converts old topic super stickies to new bbPress super stickies.
761 *
762 * @since 2.5.0 bbPress (r5170)
763 */
764 public function convert_topic_super_stickies( $start = 1 ) {
765 $has_update = false;
766 $query = ! empty( $this->sync_table )
767 ? $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' )
768 : $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' );
769
770 foreach ( $this->count_rows_by_results( $query ) as $row ) {
771 $super = true;
772 bbp_stick_topic( $row->value_id, $super );
773 $has_update = true;
774 }
775
776 return ! $has_update;
777 }
778
779 /**
780 * This method converts old closed topics to bbPress closed topics.
781 *
782 * @since 2.6.0 bbPress (r5425)
783 */
784 public function convert_topic_closed_topics( $start = 1 ) {
785 $has_update = false;
786 $query = ! empty( $this->sync_table )
787 ? $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' )
788 : $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' );
789
790 foreach ( $this->count_rows_by_results( $query ) as $row ) {
791 bbp_close_topic( $row->value_id );
792 $has_update = true;
793 }
794
795 return ! $has_update;
796 }
797
798 /**
799 * This method converts old reply_to post id to new bbPress reply_to post id.
800 *
801 * @since 2.4.0 bbPress (r5093)
802 */
803 public function convert_reply_to_parents( $start = 1 ) {
804 $has_update = false;
805 $query = ! empty( $this->sync_table )
806 ? $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' )
807 : $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' );
808
809 foreach ( $this->count_rows_by_results( $query ) as $row ) {
810 $reply_to = $this->callback_reply_to( $row->meta_value );
811 $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 ) );
812 $has_update = true;
813 }
814
815 return ! $has_update;
816 }
817
818 /**
819 * This method converts anonymous topics.
820 *
821 * @since 2.6.0 bbPress (r5538)
822 */
823 public function convert_anonymous_topic_authors( $start = 1 ) {
824
825 $has_update = false;
826
827 if ( ! empty( $this->sync_table ) ) {
828 $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
829 FROM {$this->sync_table_name} AS sync_table1
830 INNER JOIN {$this->sync_table_name} AS sync_table2
831 ON ( sync_table1.value_id = sync_table2.value_id )
832 WHERE sync_table1.meta_value = %s
833 AND sync_table2.meta_key = %s
834 LIMIT {$start}, {$this->max_rows}",
835 'true',
836 '_bbp_old_topic_author_name_id'
837 );
838 } else {
839 $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
840 FROM {$this->wpdb->postmeta} AS wp_postmeta1
841 INNER JOIN {$this->wpdb->postmeta} AS wp_postmeta2
842 ON ( wp_postmeta1.post_id = wp_postmeta2.post_id )
843 WHERE wp_postmeta1.meta_value = %s
844 AND wp_postmeta2.meta_key = %s
845 LIMIT {$start}, {$this->max_rows}",
846 'true',
847 '_bbp_old_topic_author_name_id'
848 );
849 }
850
851 foreach ( $this->count_rows_by_results( $query ) as $row ) {
852 $anonymous_topic_author_id = 0;
853 $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 ) );
854
855 add_post_meta( $row->topic_id, '_bbp_anonymous_name', $row->topic_author );
856
857 $has_update = true;
858 }
859
860 return ! $has_update;
861 }
862
863 /**
864 * This method converts anonymous replies.
865 *
866 * @since 2.6.0 bbPress (r5538)
867 */
868 public function convert_anonymous_reply_authors( $start = 1 ) {
869
870 $has_update = false;
871
872 if ( ! empty( $this->sync_table ) ) {
873 $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
874 FROM {$this->sync_table_name} AS sync_table1
875 INNER JOIN {$this->sync_table_name} AS sync_table2
876 ON ( sync_table1.value_id = sync_table2.value_id )
877 WHERE sync_table1.meta_value = %s
878 AND sync_table2.meta_key = %s
879 LIMIT {$start}, {$this->max_rows}",
880 'true',
881 '_bbp_old_reply_author_name_id'
882 );
883 } else {
884 $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
885 FROM {$this->wpdb->postmeta} AS wp_postmeta1
886 INNER JOIN {$this->wpdb->postmeta} AS wp_postmeta2
887 ON ( wp_postmeta1.post_id = wp_postmeta2.post_id )
888 WHERE wp_postmeta1.meta_value = %s
889 AND wp_postmeta2.meta_key = %s
890 LIMIT {$start}, {$this->max_rows}",
891 'true',
892 '_bbp_old_reply_author_name_id'
893 );
894 }
895
896 foreach ( $this->count_rows_by_results( $query ) as $row ) {
897 $anonymous_reply_author_id = 0;
898 $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 ) );
899
900 add_post_meta( $row->reply_id, '_bbp_anonymous_name', $row->reply_author );
901
902 $has_update = true;
903 }
904
905 return ! $has_update;
906 }
907
908 /**
909 * This method deletes data from the wp database.
910 *
911 * @since 2.6.0 bbPress (r6456)
912 */
913 public function clean() {
914
915 // Defaults
916 $has_delete = false;
917
918 /** Delete topics/forums/posts ****************************************/
919
920 $esc_like = $this->wpdb->esc_like( '_bbp_' ) . '%';
921 $query = ! empty( $this->sync_table )
922 ? $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' )
923 : $this->wpdb->prepare( "SELECT post_id AS value_id FROM {$this->wpdb->postmeta} WHERE meta_key LIKE %s GROUP BY post_id ORDER BY post_id DESC LIMIT {$this->max_rows}", $esc_like );
924
925 $posts = $this->get_results( $query, ARRAY_A );
926
927 if ( isset( $posts[0] ) && ! empty( $posts[0]['value_id'] ) ) {
928 foreach ( (array) $posts as $value ) {
929 $deleted = wp_delete_post( $value['value_id'], true );
930
931 // Only flag if not empty or error
932 if ( ( false === $has_delete ) && ! empty( $deleted ) && ! is_wp_error( $deleted ) ) {
933 $has_delete = true;
934 }
935 }
936 }
937
938 /** Delete users ******************************************************/
939
940 $query = ! empty( $this->sync_table )
941 ? $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' )
942 : $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' );
943
944 $users = $this->get_results( $query, ARRAY_A );
945
946 if ( ! empty( $users ) ) {
947 foreach ( $users as $value ) {
948 $deleted = wp_delete_user( $value['value_id'] );
949
950 // Only flag if not empty or error
951 if ( ( false === $has_delete ) && ! empty( $deleted ) && ! is_wp_error( $deleted ) ) {
952 $has_delete = true;
953 }
954 }
955 }
956
957 unset( $posts );
958 unset( $users );
959
960 return ! $has_delete;
961 }
962
963 /**
964 * This method deletes passwords from the wp database.
965 *
966 * @param int $start Current cleanup offset. Zero starts a new pass.
967 */
968 public function clean_passwords( $start = 0 ) {
969 $has_delete = false;
970 $max_rows = (int) $this->max_rows;
971
972 // Use an immutable cursor because native hashes are moved to user_pass and
973 // deleted from usermeta. An offset would skip rows as the result set shrinks.
974 $cursor = empty( $start )
975 ? 0
976 : (int) get_option( '_bbp_converter_passwords_cursor', 0 );
977
978 $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 );
979 $converted = $this->get_results( $query, ARRAY_A );
980
981 if ( ! empty( $converted ) ) {
982 foreach ( $converted as $value ) {
983 if ( is_serialized( $value['meta_value'] ) ) {
984 $this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->users} SET user_pass = '' WHERE ID = %d", $value['user_id'] ) );
985 } else {
986 $this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->users} SET user_pass = %s WHERE ID = %d", $value['meta_value'], $value['user_id'] ) );
987 delete_user_meta( $value['user_id'], '_bbp_password' );
988 }
989
990 clean_user_cache( $value['user_id'] );
991 $cursor = (int) $value['umeta_id'];
992 }
993 update_option( '_bbp_converter_passwords_cursor', $cursor, false );
994 $has_delete = true;
995 } else {
996 delete_option( '_bbp_converter_passwords_cursor' );
997 }
998
999 return ! $has_delete;
1000 }
1001
1002 /**
1003 * This method implements the authentication for the different forums.
1004 *
1005 * @param string $password Password.
1006 * @param string $hash Password hash.
1007 */
1008 abstract protected function authenticate_pass( $password, $hash );
1009
1010 /**
1011 * Info
1012 */
1013 abstract protected function info();
1014
1015 /**
1016 * This method grabs appropriate fields from the table specified
1017 *
1018 * @param string $tablename The table name to grab fields from
1019 */
1020 private function get_fields( $tablename = '' ) {
1021 $retval = array();
1022 $field_array = $this->get_results( "DESCRIBE {$tablename}", ARRAY_A );
1023
1024 // Bail if no fields
1025 if ( empty( $field_array ) ) {
1026 return $retval;
1027 }
1028
1029 // Add fields to array
1030 foreach ( $field_array as $field ) {
1031 if ( ! empty( $field['Field'] ) ) {
1032 $retval[] = $field['Field'];
1033 }
1034 }
1035
1036 // Add social fields for users table
1037 if ( $tablename === $this->wpdb->users ) {
1038 $retval[] = 'role';
1039 $retval[] = 'yim';
1040 $retval[] = 'aim';
1041 $retval[] = 'jabber';
1042 }
1043
1044 return $retval;
1045 }
1046
1047 /** Database Wrappers *****************************************************/
1048
1049 /**
1050 * Update the last query option and return results
1051 *
1052 * @param string $query
1053 */
1054 private function get_row( $query = '' ) {
1055 $this->update_query( $query );
1056
1057 return $this->wpdb->get_row( $query ); // phpcs:ignore
1058 }
1059
1060 /**
1061 * Update the last query option and return results
1062 *
1063 * @param string $query
1064 * @param string $output
1065 */
1066 private function get_results( $query = '', $output = OBJECT ) {
1067 $this->update_query( $query );
1068
1069 return (array) $this->wpdb->get_results( $query, $output ); // phpcs:ignore
1070 }
1071
1072 /**
1073 * Update the last query option and do a general query
1074 *
1075 * @param string $query
1076 */
1077 private function query( $query = '' ) {
1078 $this->update_query( $query );
1079
1080 return $this->wpdb->query( $query ); // phpcs:ignore
1081 }
1082
1083 /**
1084 * Update the last query ran
1085 *
1086 * @since 2.6.0 bbPress (r6637)
1087 *
1088 * @param string $query The literal MySQL query
1089 * @return bool
1090 */
1091 private function update_query( $query = '' ) {
1092 return update_option( '_bbp_converter_query', $query );
1093 }
1094
1095 /**
1096 * Update the number of rows in the current step
1097 *
1098 * @since 2.6.0 bbPress (r6637)
1099 *
1100 * @param string $query The literal MySQL query
1101 * @return array
1102 */
1103 private function count_rows_by_results( $query = '' ) {
1104 $results = $this->get_results( $query );
1105
1106 update_option( '_bbp_converter_rows_in_step', count( $results ) );
1107
1108 return $results;
1109 }
1110
1111 /**
1112 * Update the number of rows in the current step
1113 *
1114 * @since 2.6.0 bbPress (r6637)
1115 *
1116 * @param string $table_name The literal MySQL query
1117 * @return bool
1118 */
1119 private function count_rows_by_table( $table_name = '' ) {
1120 $count = (int) $this->opdb->get_var( "SELECT COUNT(*) FROM {$table_name}" );
1121
1122 return update_option( '_bbp_converter_rows_in_step', $count );
1123 }
1124
1125 /** Callbacks *************************************************************/
1126
1127 /**
1128 * Unserialize imported password metadata as an array.
1129 *
1130 * @since 2.6.18
1131 *
1132 * @param string $serialized_pass Serialized password metadata.
1133 * @return array|false Password metadata, or false when invalid.
1134 */
1135 protected function unserialize_pass( $serialized_pass = '' ) {
1136 if ( ! is_string( $serialized_pass ) || ! is_serialized( $serialized_pass ) || $this->serialized_pass_has_object( $serialized_pass ) ) {
1137 return false;
1138 }
1139
1140 // Malformed source metadata may still warn after is_serialized().
1141 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1142 $pass_array = @unserialize(
1143 $serialized_pass,
1144 array(
1145 'allowed_classes' => false,
1146 'max_depth' => 1,
1147 )
1148 );
1149
1150 return is_array( $pass_array )
1151 ? $pass_array
1152 : false;
1153 }
1154
1155 /**
1156 * Check serialized password metadata for object tokens without instantiating it.
1157 *
1158 * Serialized strings are skipped by their declared byte length so object-like
1159 * text inside a hash or salt does not cause a false positive.
1160 *
1161 * @since 2.6.18
1162 *
1163 * @param string $serialized_pass Serialized password metadata.
1164 * @return bool True when the value contains an object or is unsafe to parse.
1165 */
1166 private function serialized_pass_has_object( $serialized_pass = '' ) {
1167 $length = strlen( $serialized_pass );
1168
1169 for ( $offset = 0; $offset < $length; ++$offset ) {
1170 $token = $serialized_pass[ $offset ];
1171
1172 if ( in_array( $token, array( 'O', 'C', 'E' ), true ) && isset( $serialized_pass[ $offset + 1 ] ) && ':' === $serialized_pass[ $offset + 1 ] ) {
1173 return true;
1174 }
1175
1176 if ( 's' !== $token || ! isset( $serialized_pass[ $offset + 1 ] ) || ':' !== $serialized_pass[ $offset + 1 ] ) {
1177 continue;
1178 }
1179
1180 $length_end = strpos( $serialized_pass, ':"', $offset + 2 );
1181 if ( false === $length_end ) {
1182 return true;
1183 }
1184
1185 $string_length = substr( $serialized_pass, $offset + 2, $length_end - $offset - 2 );
1186 $string_start = $length_end + 2;
1187 if ( '' === $string_length || ! ctype_digit( $string_length ) || (int) $string_length > $length - $string_start ) {
1188 return true;
1189 }
1190
1191 $string_end = $string_start + (int) $string_length;
1192 if ( ! isset( $serialized_pass[ $string_end ] ) || '"' !== $serialized_pass[ $string_end ] ) {
1193 return true;
1194 }
1195
1196 $offset = $string_end;
1197 }
1198
1199 return false;
1200 }
1201
1202 /**
1203 * Run password through wp_hash_password()
1204 *
1205 * @since 2.6.18 Added the `$wp_password` parameter.
1206 *
1207 * @param string $username
1208 * @param string $password
1209 * @param string|null $wp_password Optional slashed password for WordPress.
1210 */
1211 public function callback_pass( $username = '', $password = '', $wp_password = null ) {
1212
1213 // Get user – Bail if not found
1214 $user = $this->get_row( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->users} WHERE user_login = %s AND user_pass = '' LIMIT 1", $username ) );
1215 if ( empty( $user ) ) {
1216 return;
1217 }
1218
1219 // Get usermeta – Bail if not found
1220 $usermeta = $this->get_row( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->usermeta} WHERE meta_key = %s AND user_id = %d LIMIT 1", '_bbp_password', $user->ID ) );
1221 if ( empty( $usermeta ) ) {
1222 return;
1223 }
1224
1225 // Bail if meta is not a string
1226 if ( empty( $usermeta->meta_value ) || ! is_serialized( $usermeta->meta_value ) ) {
1227 return;
1228 }
1229
1230 // Bail if meta is suspiciously long
1231 if ( strlen( $usermeta->meta_value ) > 512 ) {
1232 return;
1233 }
1234
1235 // Bail if platform-specific auth fails
1236 if ( ! $this->authenticate_pass( $password, $usermeta->meta_value ) ) {
1237 return;
1238 }
1239
1240 // Hash the password
1241 $new_pass = wp_hash_password( is_null( $wp_password ) ? $password : $wp_password );
1242
1243 // Update
1244 $this->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->users} SET user_pass = %s WHERE ID = %d", $new_pass, $user->ID ) );
1245
1246 // Clean up
1247 unset( $new_pass );
1248 delete_user_meta( $user->ID, '_bbp_password' );
1249 delete_user_meta( $user->ID, '_bbp_class' );
1250
1251 // Clean the cache for this user since their password was
1252 // upgraded from the old platform to the new.
1253 clean_user_cache( $user->ID );
1254 }
1255
1256 /**
1257 * A mini cache system to reduce database calls to forum ID's
1258 *
1259 * @param string $field
1260 * @return string
1261 */
1262 private function callback_forumid( $field ) {
1263 if ( ! isset( $this->map_forumid[ $field ] ) ) {
1264 $row = ! empty( $this->sync_table )
1265 ? $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 ) )
1266 : $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 ) );
1267
1268 $this->map_forumid[ $field ] = ! is_null( $row )
1269 ? $row->value_id
1270 : 0;
1271 }
1272
1273 return $this->map_forumid[ $field ];
1274 }
1275
1276 /**
1277 * A mini cache system to reduce database calls to topic ID's
1278 *
1279 * @param string $field
1280 * @return string
1281 */
1282 private function callback_topicid( $field ) {
1283 if ( ! isset( $this->map_topicid[ $field ] ) ) {
1284 $row = ! empty( $this->sync_table )
1285 ? $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 ) )
1286 : $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 ) );
1287
1288 $this->map_topicid[ $field ] = ! is_null( $row )
1289 ? $row->value_id
1290 : 0;
1291 }
1292
1293 return $this->map_topicid[ $field ];
1294 }
1295
1296 /**
1297 * A mini cache system to reduce database calls to reply_to post id.
1298 *
1299 * @since 2.4.0 bbPress (r5093)
1300 *
1301 * @param string $field
1302 * @return string
1303 */
1304 private function callback_reply_to( $field ) {
1305 if ( ! isset( $this->map_reply_to[ $field ] ) ) {
1306 $row = ! empty( $this->sync_table )
1307 ? $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 ) )
1308 : $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 ) );
1309
1310 $this->map_reply_to[ $field ] = ! is_null( $row )
1311 ? $row->value_id
1312 : 0;
1313 }
1314
1315 return $this->map_reply_to[ $field ];
1316 }
1317
1318 /**
1319 * A mini cache system to reduce database calls to user ID's
1320 *
1321 * @param string $field
1322 * @return string
1323 */
1324 private function callback_userid( $field ) {
1325 if ( ! isset( $this->map_userid[ $field ] ) ) {
1326 $row = ! empty( $this->sync_table )
1327 ? $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 ) )
1328 : $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 ) );
1329
1330 if ( ! is_null( $row ) ) {
1331 $this->map_userid[ $field ] = $row->value_id;
1332 } else {
1333 $this->map_userid[ $field ] = ( true === $this->convert_users )
1334 ? 0
1335 : $field;
1336 }
1337 }
1338
1339 return $this->map_userid[ $field ];
1340 }
1341
1342 /**
1343 * Check if the topic or reply author is anonymous
1344 *
1345 * @since 2.6.0 bbPress (r5544)
1346 *
1347 * @param string $field
1348 * @return string
1349 */
1350 private function callback_check_anonymous( $field ) {
1351 $field = ! $this->callback_userid( $field )
1352 ? 'true'
1353 : 'false';
1354
1355 return $field;
1356 }
1357
1358 /**
1359 * A mini cache system to reduce database calls map topics ID's to forum ID's
1360 *
1361 * @param string $field
1362 * @return string
1363 */
1364 private function callback_topicid_to_forumid( $field ) {
1365 $topicid = $this->callback_topicid( $field );
1366 if ( empty( $topicid ) ) {
1367 $this->map_topicid_to_forumid[ $topicid ] = 0;
1368 } elseif ( ! isset( $this->map_topicid_to_forumid[ $topicid ] ) ) {
1369 $row = $this->get_row( $this->wpdb->prepare( "SELECT post_parent FROM {$this->wpdb->posts} WHERE ID = %d LIMIT 1", $topicid ) );
1370
1371 $this->map_topicid_to_forumid[ $topicid ] = ! is_null( $row )
1372 ? $row->post_parent
1373 : 0;
1374 }
1375
1376 return $this->map_topicid_to_forumid[ $topicid ];
1377 }
1378
1379 protected function callback_slug( $field ) {
1380 return sanitize_title( $field );
1381 }
1382
1383 protected function callback_negative( $field ) {
1384 return ( $field < 0 )
1385 ? 0
1386 : $field;
1387 }
1388
1389 protected function callback_html( $field ) {
1390 require_once bbp_admin()->admin_dir . 'parser.php';
1391
1392 // Setup the BBCode parser
1393 $bbcode = BBCode::getInstance();
1394
1395 // Pass BBCode properties to the parser
1396 foreach ( $this->bbcode_parser_properties as $prop => $value ) {
1397 $bbcode->{$prop} = $value;
1398 }
1399
1400 return html_entity_decode( $bbcode->Parse( $field ) );
1401 }
1402
1403 protected function callback_null( $field ) {
1404 return is_null( $field )
1405 ? ''
1406 : $field;
1407 }
1408
1409 protected function callback_datetime( $field ) {
1410 // phpcs:disable WordPress.DateTime.RestrictedFunctions.date_date
1411 return is_numeric( $field )
1412 ? date( 'Y-m-d H:i:s', $field )
1413 : date( 'Y-m-d H:i:s', strtotime( $field ) );
1414 // phpcs:enable
1415 }
1416 }
1417 endif;
1418