PluginProbe
bbPress / 2.6.17
bbPress v2.6.17
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 2.2 2.2.1 All 72 releases
bbpress / includes / core / abstraction.php

abstraction.php in bbPress 2.6.17, at includes/core/abstraction.php

755 lines 20.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * bbPress Abstractions
5 *
6 * This file contains functions for abstracting WordPress core functionality
7 * into convenient wrappers so they can be used more reliably.
8 *
9 * Many of the functions in this file are considered superfluous by
10 * WordPress coding standards, but they're handy for plugins of plugins to use.
11 *
12 * @package bbPress
13 * @subpackage Core
14 */
15
16 // Exit if accessed directly
17 defined( 'ABSPATH' ) || exit;
18
19 /**
20 * Setup Admin
21 *
22 * This exists outside of "/includes/admin/" because the converter may need to
23 * be setup to convert the passwords of users that were migrated from another
24 * forum platform.
25 *
26 * @since 2.6.0 bbPress (r2596)
27 */
28 function bbp_setup_admin() {
29 $bbp = bbpress();
30
31 // Skip if already setup
32 if ( empty( $bbp->admin ) ) {
33
34 // Require the admin class
35 require_once $bbp->includes_dir . 'admin/classes/class-bbp-admin.php';
36
37 // Setup
38 $bbp->admin = class_exists( 'BBP_Admin' )
39 ? new BBP_Admin()
40 : new stdClass();
41 }
42
43 // Return the admin object
44 return $bbp->admin;
45 }
46
47 /**
48 * Setup Converter
49 *
50 * This exists outside of "/includes/admin/" because the converter may need to
51 * be setup to convert the passwords of users that were migrated from another
52 * forum platform.
53 *
54 * @since 2.6.0 bbPress (r2596)
55 */
56 function bbp_setup_converter() {
57 $bbp_admin = bbp_setup_admin();
58
59 // Skip if already setup
60 if ( empty( $bbp_admin->converter ) ) {
61
62 // Require the converter files
63 require_once $bbp_admin->admin_dir . 'tools/converter.php';
64 require_once $bbp_admin->admin_dir . 'classes/class-bbp-converter.php';
65 require_once $bbp_admin->admin_dir . 'classes/class-bbp-converter-db.php';
66 require_once $bbp_admin->admin_dir . 'classes/class-bbp-converter-base.php';
67
68 // Setup
69 $bbp_admin->converter = class_exists( 'BBP_Converter' )
70 ? new BBP_Converter()
71 : new stdClass();
72 }
73
74 // Return the converter
75 return $bbp_admin->converter;
76 }
77
78 /** Globals *******************************************************************/
79
80 /**
81 * Lookup and return a global variable
82 *
83 * @since 2.5.8 bbPress (r5814)
84 *
85 * @param string $name Name of global variable
86 * @param string $type Type of variable to check with `is_a()`
87 * @param mixed $default Default value to return if no global found
88 *
89 * @return mixed Verified object if valid, Default or null if invalid
90 */
91 function bbp_get_global_object( $name = '', $type = '', $default = null ) {
92
93 // If no name passed
94 if ( empty( $name ) ) {
95 $retval = $default;
96
97 // If no global exists
98 } elseif ( ! isset( $GLOBALS[ $name ] ) ) {
99 $retval = $default;
100
101 // If not the correct type of global
102 } elseif ( ! empty( $type ) && ! is_a( $GLOBALS[ $name ], $type ) ) {
103 $retval = $default;
104
105 // Global variable exists
106 } else {
107 $retval = $GLOBALS[ $name ];
108 }
109
110 // Filter & return
111 return apply_filters( 'bbp_get_global_object', $retval, $name, $type, $default );
112 }
113
114 /**
115 * Get the `$wp_query` global without needing to declare it everywhere
116 *
117 * @since 2.6.0 bbPress (r6582)
118 *
119 * @return WP_Roles
120 */
121 function bbp_get_wp_query() {
122 return bbp_get_global_object( 'wp_query', 'WP_Query' );
123 }
124
125 /**
126 * Get the `$wp_roles` global without needing to declare it everywhere
127 *
128 * @since 2.2.0 bbPress (r4293)
129 *
130 * @return WP_Roles
131 */
132 function bbp_get_wp_roles() {
133 return bbp_get_global_object( 'wp_roles', 'WP_Roles' );
134 }
135
136 /**
137 * Return the database class being used to interface with the environment.
138 *
139 * This function is abstracted to avoid global touches to the primary database
140 * class. bbPress supports WordPress's `$wpdb` global by default, and can be
141 * filtered to support other configurations if needed.
142 *
143 * @since 2.5.8 bbPress (r5814)
144 *
145 * @return object
146 */
147 function bbp_db() {
148 return bbp_get_global_object( 'wpdb', 'WPDB' );
149 }
150
151 /**
152 * Atomically bump a numeric metadata value using compare-and-swap retries.
153 *
154 * Count updates normally require reading a value, changing it in PHP, and
155 * writing it back. Two requests can read the same value and overwrite one
156 * another's changes. This function avoids that lost update by making the write
157 * conditional on the value that was read. If another request changes the value
158 * first, the condition matches no rows, so the current value is read directly
159 * from the database and the calculation is retried.
160 *
161 * The first read uses the WordPress metadata API and its cache. Missing
162 * metadata is added through add_metadata() so its standard lifecycle continues
163 * to run. Existing metadata uses a conditional database update while preserving
164 * the standard update metadata short-circuit filter and before/after actions.
165 * The short-circuit filter runs once against the first sanitized candidate.
166 * Before actions run for every conditional attempt, while after actions run
167 * only after a successful write. Metadata caches are cleared between attempts
168 * and after successful writes. Values are sanitized through sanitize_meta(),
169 * cast to integers, and prevented from falling below zero.
170 *
171 * Retries are bounded and filterable. This function does not lock rows or hold
172 * a database transaction open, and returns false when there is no change, a
173 * database operation fails, or all attempts lose to concurrent writes. It is
174 * intended for uniquely keyed numeric count metadata; WordPress metadata tables
175 * do not enforce uniqueness during simultaneous first-time inserts.
176 *
177 * @since 2.6.17
178 *
179 * @see https://bbpress.trac.wordpress.org/ticket/3678
180 *
181 * @param string $meta_type Type of object metadata is for.
182 * @param int $object_id ID of the object metadata is for.
183 * @param string $meta_key Metadata key.
184 * @param int $difference Amount to add to the stored value.
185 * @param int $default Existing value to use when metadata is missing.
186 * @return bool True on success, false on failure or no change.
187 */
188 function bbp_bump_count_meta( $meta_type = '', $object_id = 0, $meta_key = '', $difference = 1, $default = 0 ) {
189
190 $object_id = (int) $object_id;
191 $difference = (int) $difference;
192 $default = (int) $default;
193
194 // Bail if required values are missing
195 if ( empty( $object_id ) || empty( $meta_key ) || empty( $difference ) ) {
196 return false;
197 }
198
199 /**
200 * Short-circuits bumping numeric metadata.
201 *
202 * Returning a non-null value prevents the normal metadata update.
203 *
204 * @since 2.6.17
205 *
206 * @param null|bool $check Whether to short-circuit the metadata update.
207 * @param string $meta_type Type of object metadata is for.
208 * @param int $object_id ID of the object metadata is for.
209 * @param string $meta_key Metadata key.
210 * @param int $difference Amount to add to the stored value.
211 * @param int $default Existing value to use when metadata is missing.
212 */
213 $check = apply_filters( 'bbp_pre_bump_count_meta', null, $meta_type, $object_id, $meta_key, $difference, $default );
214 if ( null !== $check ) {
215 return (bool) $check;
216 }
217
218 /**
219 * Filters the metadata types that support atomic count updates.
220 *
221 * @since 2.6.17
222 *
223 * @param array $meta_types Supported metadata types.
224 * @param string $meta_type Requested metadata type.
225 * @param int $object_id ID of the object metadata is for.
226 * @param string $meta_key Metadata key.
227 */
228 $meta_types = (array) apply_filters( 'bbp_bump_count_meta_types', array( 'post', 'user', 'term', 'comment' ), $meta_type, $object_id, $meta_key );
229
230 // Bail if the metadata type is unsupported
231 if ( ! in_array( $meta_type, $meta_types, true ) ) {
232 return false;
233 }
234
235 $bbp_db = bbp_db();
236 $table_name = sanitize_key( $meta_type . 'meta' );
237 $table = isset( $bbp_db->{$table_name} ) ? $bbp_db->{$table_name} : '';
238 $column = sanitize_key( $meta_type . '_id' );
239 $id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id';
240
241 // Bail if the metadata table does not exist
242 if ( empty( $table ) ) {
243 return false;
244 }
245
246 /**
247 * Filters the maximum number of conditional metadata write attempts.
248 *
249 * @since 2.6.17
250 *
251 * @param int $max_attempts Maximum number of attempts.
252 * @param string $meta_type Type of object metadata is for.
253 * @param int $object_id ID of the object metadata is for.
254 * @param string $meta_key Metadata key.
255 * @param int $difference Amount to add to the stored value.
256 * @param int $default Existing value to use when metadata is missing.
257 */
258 $max_attempts = (int) apply_filters( 'bbp_bump_count_meta_max_attempts', 5, $meta_type, $object_id, $meta_key, $difference, $default );
259 $max_attempts = max( 1, $max_attempts );
260 $checked = false;
261 $subtype = get_object_subtype( $meta_type, $object_id );
262 $count_query = $bbp_db->prepare( "SELECT meta_value FROM {$table} WHERE meta_key = %s AND {$column} = %d LIMIT 1", $meta_key, $object_id );
263
264 // Retry when another request updates the same value first
265 for ( $attempt = 0; $attempt < $max_attempts; $attempt++ ) {
266 if ( empty( $attempt ) ) {
267 $exists = metadata_exists( $meta_type, $object_id, $meta_key );
268 $count = $exists
269 ? (int) get_metadata( $meta_type, $object_id, $meta_key, true )
270 : $default;
271 } else {
272 $stored = $bbp_db->get_var( $count_query );
273
274 // Bail on a database error
275 if ( ! empty( $bbp_db->last_error ) ) {
276 return false;
277 }
278
279 $exists = null !== $stored;
280 $count = $exists ? (int) $stored : $default;
281 }
282
283 $new_count = sanitize_meta( $meta_key, bbp_number_not_negative( $count + $difference ), $meta_type, $subtype );
284 $new_count = (int) $new_count;
285
286 // Allow metadata updates to be short-circuited as usual
287 if ( ! $checked ) {
288 $checked = true;
289 $check = apply_filters( "update_{$meta_type}_metadata", null, $object_id, $meta_key, $new_count, '' );
290
291 if ( null !== $check ) {
292 return (bool) $check;
293 }
294 }
295
296 // Bail if the count is already at its lower bound
297 if ( $new_count === $count ) {
298 if ( ! empty( $attempt ) ) {
299 return false;
300 }
301
302 $stored = $bbp_db->get_var( $count_query );
303
304 // Bail on a database error
305 if ( ! empty( $bbp_db->last_error ) ) {
306 return false;
307 }
308
309 $current_exists = null !== $stored;
310 $current_count = $current_exists ? (int) $stored : $default;
311
312 if ( ( $current_exists === $exists ) && ( $current_count === $count ) ) {
313 return false;
314 }
315
316 wp_cache_delete( $object_id, $meta_type . '_meta' );
317 continue;
318 }
319
320 // Add missing metadata using the standard WordPress lifecycle
321 if ( ! $exists ) {
322 if ( ! empty( add_metadata( $meta_type, $object_id, $meta_key, $new_count, true ) ) ) {
323 return true;
324 }
325
326 $stored = $bbp_db->get_var( $count_query );
327
328 // Bail if the add failed without a concurrent insert or on a database error
329 if ( ! empty( $bbp_db->last_error ) || ( null === $stored ) ) {
330 return false;
331 }
332
333 wp_cache_delete( $object_id, $meta_type . '_meta' );
334 continue;
335 }
336
337 $meta_ids = $bbp_db->get_col( $bbp_db->prepare( "SELECT {$id_column} FROM {$table} WHERE meta_key = %s AND {$column} = %d", $meta_key, $object_id ) );
338
339 // Bail on a database error
340 if ( ! empty( $bbp_db->last_error ) ) {
341 return false;
342 }
343
344 // Retry if metadata was removed after the cached existence check
345 if ( empty( $meta_ids ) ) {
346 wp_cache_delete( $object_id, $meta_type . '_meta' );
347 continue;
348 }
349
350 // Run the standard actions immediately before the conditional update
351 foreach ( $meta_ids as $meta_id ) {
352 do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $new_count );
353
354 if ( 'post' === $meta_type ) {
355 do_action( 'update_postmeta', $meta_id, $object_id, $meta_key, $new_count );
356 }
357 }
358
359 // Compare count metadata numerically to normalize stored numeric strings
360 $updated = $bbp_db->update(
361 $table,
362 array( 'meta_value' => $new_count ),
363 array(
364 $column => $object_id,
365 'meta_key' => $meta_key,
366 'meta_value' => $count
367 ),
368 array( '%d' ),
369 array( '%d', '%s', '%d' )
370 );
371
372 // Bail on a database error
373 if ( false === $updated ) {
374 return false;
375 }
376
377 wp_cache_delete( $object_id, $meta_type . '_meta' );
378
379 // Retry when another request updated the count first
380 if ( empty( $updated ) ) {
381 continue;
382 }
383
384 // Run the standard actions immediately after the conditional update
385 foreach ( $meta_ids as $meta_id ) {
386 do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $new_count );
387
388 if ( 'post' === $meta_type ) {
389 do_action( 'updated_postmeta', $meta_id, $object_id, $meta_key, $new_count );
390 }
391 }
392
393 return true;
394 }
395
396 return false;
397 }
398
399 /** Pagination ****************************************************************/
400
401 /**
402 * Return the rewrite rules class being used to interact with URLs.
403 *
404 * This function is abstracted to avoid global touches to the primary rewrite
405 * rules class. bbPress supports WordPress's `$wp_rewrite` by default, but can
406 * be filtered to support other configurations if needed.
407 *
408 * @since 2.5.8 bbPress (r5814)
409 *
410 * @return object
411 */
412 function bbp_rewrite() {
413 return bbp_get_global_object(
414 'wp_rewrite',
415 'WP_Rewrite',
416 (object) array(
417 'root' => '',
418 'pagination_base' => 'page',
419 )
420 );
421 }
422
423 /**
424 * Get the root URL
425 *
426 * @since 2.5.8 bbPress (r5814)
427 *
428 * @return string
429 */
430 function bbp_get_root_url() {
431
432 // Default
433 $retval = '';
434 $rewrite = bbp_rewrite();
435
436 // Use $wp_rewrite->root if available
437 if ( property_exists( $rewrite, 'root' ) ) {
438 $retval = $rewrite->root;
439 }
440
441 // Filter & return
442 return apply_filters( 'bbp_get_root_url', $retval );
443 }
444
445 /**
446 * Get the slug used for paginated requests
447 *
448 * @since 2.4.0 bbPress (r4926)
449 *
450 * @return string
451 */
452 function bbp_get_paged_slug() {
453
454 // Default
455 $retval = 'page';
456 $rewrite = bbp_rewrite();
457
458 // Use $wp_rewrite->pagination_base if available
459 if ( property_exists( $rewrite, 'pagination_base' ) ) {
460 $retval = $rewrite->pagination_base;
461 }
462
463 // Filter & return
464 return apply_filters( 'bbp_get_paged_slug', $retval );
465 }
466
467 /**
468 * Is the environment using pretty URLs?
469 *
470 * @since 2.5.8 bbPress (r5814)
471 *
472 * @global object $wp_rewrite The WP_Rewrite object
473 *
474 * @return bool
475 */
476 function bbp_use_pretty_urls() {
477
478 // Default
479 $retval = false;
480 $rewrite = bbp_rewrite();
481
482 // Use $wp_rewrite->using_permalinks() if available
483 if ( method_exists( $rewrite, 'using_permalinks' ) ) {
484 $retval = $rewrite->using_permalinks();
485 }
486
487 // Filter & return
488 return apply_filters( 'bbp_pretty_urls', $retval );
489 }
490
491 /**
492 * Remove the first-page from a pagination links result set, ensuring that it
493 * points to the canonical first page URL.
494 *
495 * This is a bit of an SEO hack, to guarantee that the first page in a loop will
496 * never have pagination appended to the end of it, regardless of what the other
497 * functions have decided for us.
498 *
499 * @since 2.6.0 bbPress (r6678)
500 *
501 * @param string $pagination_links The HTML links used for pagination
502 *
503 * @return string
504 */
505 function bbp_make_first_page_canonical( $pagination_links = '' ) {
506
507 // Default value
508 $retval = $pagination_links;
509
510 // Remove first page from pagination
511 if ( ! empty( $pagination_links ) ) {
512 $retval = bbp_use_pretty_urls()
513 ? str_replace( bbp_get_paged_slug() . '/1/', '', $pagination_links )
514 : preg_replace( '/&#038;paged=1(?=[^0-9])/m', '', $pagination_links );
515 }
516
517 // Filter & return
518 return apply_filters( 'bbp_make_first_page_canonical', $retval, $pagination_links );
519 }
520
521 /**
522 * A convenient wrapper for common calls to paginate_links(), complete with
523 * support for parameters that aren't used internally by bbPress.
524 *
525 * @since 2.6.0 bbPress (r6679)
526 *
527 * @param array $args
528 *
529 * @return string
530 */
531 function bbp_paginate_links( $args = array() ) {
532
533 // Maybe add view-all args
534 $add_args = empty( $args['add_args'] ) && bbp_get_view_all()
535 ? array( 'view' => 'all' )
536 : false;
537
538 // Pagination settings with filter
539 $r = bbp_parse_args(
540 $args,
541 array(
542
543 // Used by callers
544 'base' => '',
545 'total' => 1,
546 'current' => bbp_get_paged(),
547 'prev_next' => true,
548 'prev_text' => is_rtl() ? '&rarr;' : '&larr;',
549 'next_text' => is_rtl() ? '&larr;' : '&rarr;',
550 'mid_size' => 1,
551 'end_size' => 3,
552 'add_args' => $add_args,
553
554 // Unused by callers
555 'show_all' => false,
556 'type' => 'plain',
557 'format' => '',
558 'add_fragment' => '',
559 'before_page_number' => '',
560 'after_page_number' => ''
561 ),
562 'paginate_links'
563 );
564
565 // Return paginated links
566 return bbp_make_first_page_canonical( paginate_links( $r ) );
567 }
568
569 /**
570 * Parse the WordPress core version number
571 *
572 * @since 2.6.0 bbPress (r6051)
573 *
574 * @global string $wp_version
575 *
576 * @return string $wp_version
577 */
578 function bbp_get_major_wp_version() {
579 global $wp_version;
580
581 return (float) $wp_version;
582 }
583
584 /** Multisite *****************************************************************/
585
586 /**
587 * Is this a large bbPress installation?
588 *
589 * @since 2.6.0 bbPress (r6242)
590 *
591 * @return bool True if more than 10000 users, false not
592 */
593 function bbp_is_large_install() {
594
595 // Multisite has a function specifically for this
596 $retval = function_exists( 'wp_is_large_network' )
597 ? wp_is_large_network( 'users' )
598 : ( bbp_get_total_users() > 10000 );
599
600 // Filter & return
601 return (bool) apply_filters( 'bbp_is_large_install', $retval );
602 }
603
604 /**
605 * Get the total number of users on the forums
606 *
607 * @since 2.0.0 bbPress (r2769)
608 *
609 * @return int Total number of users
610 */
611 function bbp_get_total_users() {
612 $bbp_db = bbp_db();
613 $count = $bbp_db->get_var( "SELECT COUNT(ID) as c FROM {$bbp_db->users} WHERE user_status = '0'" );
614
615 // Filter & return
616 return (int) apply_filters( 'bbp_get_total_users', (int) $count );
617 }
618
619 /**
620 * Switch to a site in a multisite installation.
621 *
622 * If not a multisite installation, no switching will occur.
623 *
624 * @since 2.6.0 bbPress (r6733)
625 *
626 * @param int $site_id
627 */
628 function bbp_switch_to_site( $site_id = 0 ) {
629
630 // Switch to a specific site
631 if ( is_multisite() ) {
632 switch_to_blog( $site_id );
633 }
634 }
635
636 /**
637 * Switch back to the original site in a multisite installation.
638 *
639 * If not a multisite installation, no switching will occur.
640 *
641 * @since 2.6.0 bbPress (r6733)
642 */
643 function bbp_restore_current_site() {
644
645 // Switch back to the original site
646 if ( is_multisite() ) {
647 restore_current_blog();
648 }
649 }
650
651 /** Interception **************************************************************/
652
653 /**
654 * Generate a default intercept value.
655 *
656 * @since 2.6.0
657 *
658 * @staticvar mixed $rand Null by default, random string on first call
659 *
660 * @return string
661 */
662 function bbp_default_intercept() {
663 static $rand = null;
664
665 // Generate a new random and unique string
666 if ( null === $rand ) {
667
668 // If ext/hash is not present, compat.php's hash_hmac() does not support sha256.
669 $algo = function_exists( 'hash' )
670 ? 'sha256'
671 : 'sha1';
672
673 // Old WP installs may not have AUTH_SALT defined.
674 $salt = defined( 'AUTH_SALT' ) && AUTH_SALT
675 ? AUTH_SALT
676 : (string) wp_rand();
677
678 // Create unique ID
679 $rand = hash_hmac( $algo, uniqid( $salt, true ), $salt );
680 }
681
682 // Return random string (from locally static variable)
683 return $rand;
684 }
685
686 /**
687 * Whether a value has been intercepted
688 *
689 * @since 2.6.0
690 *
691 * @param bool $value
692 */
693 function bbp_is_intercepted( $value = '' ) {
694 return ( bbp_default_intercept() !== $value );
695 }
696
697 /**
698 * Allow interception of a method or function call.
699 *
700 * @since 2.6.0
701 *
702 * @param string $action Typically the name of the caller function
703 * @param array $args Typically the results of caller function func_get_args()
704 *
705 * @return mixed Intercept results. Default bbp_default_intercept().
706 */
707 function bbp_maybe_intercept( $action = '', $args = array() ) {
708
709 // Backwards compatibility juggle
710 $hook = ( false === strpos( $action, 'pre_' ) )
711 ? "pre_{$action}"
712 : $action;
713
714 // Default value
715 $default = bbp_default_intercept();
716
717 // Parse args
718 $r = bbp_parse_args( (array) $args, array(), 'maybe_intercept' );
719
720 // Bail if no args
721 if ( empty( $r ) ) {
722 return $default;
723 }
724
725 // Filter
726 $args = array_merge( array( $hook ), $r );
727 $filtered = call_user_func_array( 'apply_filters', $args );
728
729 // Return filtered value, or default if not intercepted
730 return ( reset( $r ) === $filtered )
731 ? $default
732 : $filtered;
733 }
734
735 /** Date/Time *****************************************************************/
736
737 /**
738 * Get an empty datetime value.
739 *
740 * @since 2.6.6 bbPress (r7094)
741 *
742 * @return string
743 */
744 function bbp_get_empty_datetime() {
745
746 // Get the database version
747 $db_version = bbp_db()->db_version();
748
749 // Default return value
750 $retval = '0000-00-00 00:00:00';
751
752 // Filter & return
753 return (string) apply_filters( 'bbp_get_default_zero_date', $retval, $db_version );
754 }
755