Skip to:
Content

bbPress.org


Ignore:
Timestamp:
09/10/2026 12:35:43 AM (3 weeks ago)
Author:
johnjamesjacoby
Message:

Counts: synchronize metadata across concurrent writes.

Introduce bbp_bump_count_meta() to update existing numeric metadata with bounded compare-and-swap retries while preserving WordPress metadata filters, actions, sanitization, and cache invalidation. Apply atomic differences to forum, topic, reply, ancestor, and user contribution counts.

Reconcile counts, engagements, and voices across status transitions, permanent deletion, author changes and reassignment, moderator move/merge/split operations, and repair recounts. Correct recursive forum totals and reply visibility, and preserve term-backed favorites and subscriptions during engagement rebuilds.

Document the count-hook compatibility changes and new helper arguments, and add regression coverage for single-site and multisite workflows.

In trunk, for 2.7.

Props alex-ye.
Fixes #2233.
Fixes #3678.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/testcases/users/functions/counts.php

    r7449 r7467  
    247247                $count = bbp_get_user_topic_count( $u, $integer );
    248248                $this->assertSame( $int_value + 1, $count );
     249        }
     250
     251        /**
     252         * @covers ::bbp_bump_user_topic_count
     253         * @covers ::bbp_update_user_topic_count
     254         * @ticket BBP3678
     255         */
     256        public function test_bbp_bump_user_topic_count_preserves_an_interleaved_update() {
     257                $user_id     = $this->factory->user->create();
     258                $interleaved = false;
     259                $callback    = function( $count, $filtered_user_id ) use ( &$interleaved ) {
     260                        if ( ! $interleaved ) {
     261                                $interleaved = true;
     262                                bbp_bump_user_topic_count( $filtered_user_id );
     263                        }
     264
     265                        return $count;
     266                };
     267
     268                bbp_update_user_topic_count( $user_id, 0 );
     269                add_filter( 'bbp_get_user_topic_count_int', $callback, 10, 2 );
     270                bbp_bump_user_topic_count( $user_id );
     271                remove_filter( 'bbp_get_user_topic_count_int', $callback, 10 );
     272
     273                $this->assertSame( 2, bbp_get_user_topic_count( $user_id, true ) );
     274        }
     275
     276        /**
     277         * @covers ::bbp_bump_user_topic_count
     278         * @covers ::bbp_update_user_topic_count
     279         */
     280        public function test_bbp_bump_user_topic_count_preserves_count_filters() {
     281                $user_id      = $this->factory->user->create();
     282                $atomic_calls = 0;
     283                $bump         = function( $count ) {
     284                        return $count + 4;
     285                };
     286                $update   = function( $count ) {
     287                        return $count + 3;
     288                };
     289                $atomic = function( $check ) use ( &$atomic_calls ) {
     290                        $atomic_calls++;
     291                        return $check;
     292                };
     293
     294                bbp_update_user_topic_count( $user_id, 5 );
     295                add_filter( 'bbp_bump_user_topic_count', $bump );
     296                add_filter( 'bbp_update_user_topic_count', $update );
     297                add_filter( 'bbp_pre_bump_count_meta', $atomic );
     298                bbp_bump_user_topic_count( $user_id );
     299                remove_filter( 'bbp_bump_user_topic_count', $bump, 10 );
     300                remove_filter( 'bbp_update_user_topic_count', $update, 10 );
     301                remove_filter( 'bbp_pre_bump_count_meta', $atomic, 10 );
     302
     303                $this->assertSame( 13, bbp_get_user_topic_count( $user_id, true ) );
     304                $this->assertSame( 0, $atomic_calls );
     305        }
     306
     307        /**
     308         * @covers ::bbp_bump_user_reply_count
     309         * @covers ::bbp_update_user_reply_count
     310         */
     311        public function test_bbp_bump_user_reply_count_preserves_an_absolute_update_filter() {
     312                $user_id      = $this->factory->user->create();
     313                $atomic_calls = 0;
     314                $update       = function() {
     315                        return 100;
     316                };
     317                $atomic = function( $check ) use ( &$atomic_calls ) {
     318                        $atomic_calls++;
     319                        return $check;
     320                };
     321
     322                bbp_update_user_reply_count( $user_id, 5 );
     323                add_filter( 'bbp_update_user_reply_count', $update );
     324                add_filter( 'bbp_pre_bump_count_meta', $atomic );
     325                bbp_bump_user_reply_count( $user_id );
     326                remove_filter( 'bbp_update_user_reply_count', $update, 10 );
     327                remove_filter( 'bbp_pre_bump_count_meta', $atomic, 10 );
     328
     329                $this->assertSame( 100, bbp_get_user_reply_count( $user_id, true ) );
     330                $this->assertSame( 0, $atomic_calls );
     331        }
     332
     333        /**
     334         * @covers ::bbp_bump_user_topic_count
     335         * @covers ::bbp_update_user_topic_count
     336         * @ticket BBP3678
     337         */
     338        public function test_bbp_bump_user_topic_count_uses_the_current_site_option() {
     339                if ( ! is_multisite() ) {
     340                        $this->markTestSkipped( 'Requires multisite.' );
     341                }
     342
     343                $user_id = $this->factory->user->create();
     344                $site_id = $this->factory->blog->create();
     345
     346                bbp_update_user_topic_count( $user_id, 5 );
     347                switch_to_blog( $site_id );
     348
     349                try {
     350                        bbp_update_user_topic_count( $user_id, 7 );
     351                        bbp_bump_user_topic_count( $user_id );
     352                        $this->assertSame( 8, bbp_get_user_topic_count( $user_id, true ) );
     353                        $this->assertSame( 8, (int) get_user_meta( $user_id, bbp_db()->get_blog_prefix() . '_bbp_topic_count', true ) );
     354                } finally {
     355                        restore_current_blog();
     356                }
     357
     358                $this->assertSame( 5, bbp_get_user_topic_count( $user_id, true ) );
    249359        }
    250360
    … …  
    402512
    403513        /**
     514         * @covers ::bbp_update_counts_on_user_reassignment
     515         * @ticket BBP3678
     516         */
     517        public function test_deleting_user_with_reassignment_updates_counts_and_engagements() {
     518                $deleted_user_id  = $this->factory->user->create();
     519                $reassign_user_id = $this->factory->user->create();
     520                $forum_id         = $this->factory->forum->create();
     521                $topic_id         = $this->factory->topic->create( array(
     522                        'post_author' => $deleted_user_id,
     523                        'post_parent' => $forum_id,
     524                        'topic_meta'  => array( 'forum_id' => $forum_id ),
     525                ) );
     526                $deleted_user_reply_id = $this->factory->reply->create( array(
     527                        'post_author' => $deleted_user_id,
     528                        'post_parent' => $topic_id,
     529                        'reply_meta'  => array(
     530                                'forum_id' => $forum_id,
     531                                'topic_id' => $topic_id,
     532                        ),
     533                ) );
     534                $this->factory->reply->create( array(
     535                        'post_author' => $reassign_user_id,
     536                        'post_parent' => $topic_id,
     537                        'reply_meta'  => array(
     538                                'forum_id' => $forum_id,
     539                                'topic_id' => $topic_id,
     540                        ),
     541                ) );
     542                $reply_only_topic_id = $this->factory->topic->create( array(
     543                        'post_author' => $reassign_user_id,
     544                        'post_parent' => $forum_id,
     545                        'topic_meta'  => array( 'forum_id' => $forum_id ),
     546                ) );
     547                $this->factory->reply->create( array(
     548                        'post_author' => $deleted_user_id,
     549                        'post_parent' => $reply_only_topic_id,
     550                        'reply_meta'  => array(
     551                                'forum_id' => $forum_id,
     552                                'topic_id' => $reply_only_topic_id,
     553                        ),
     554                ) );
     555
     556                $this->assertSame( 1, bbp_get_user_topic_count( $deleted_user_id, true ) );
     557                $this->assertSame( 2, bbp_get_user_reply_count( $deleted_user_id, true ) );
     558                $this->assertSame( 1, bbp_get_user_topic_count( $reassign_user_id, true ) );
     559                $this->assertSame( 1, bbp_get_user_reply_count( $reassign_user_id, true ) );
     560                $this->assertSame( 2, bbp_get_topic_voice_count( $topic_id, true ) );
     561                $this->assertSame( 2, bbp_get_topic_voice_count( $reply_only_topic_id, true ) );
     562
     563                wp_delete_user( $deleted_user_id, $reassign_user_id );
     564
     565                $this->assertSame( $reassign_user_id, (int) get_post_field( 'post_author', $topic_id ) );
     566                $this->assertSame( $reassign_user_id, (int) get_post_field( 'post_author', $deleted_user_reply_id ) );
     567                $this->assertSame( 2, bbp_get_user_topic_count( $reassign_user_id, true ) );
     568                $this->assertSame( 3, bbp_get_user_reply_count( $reassign_user_id, true ) );
     569                $this->assertSame( array( $reassign_user_id ), bbp_get_topic_engagements( $topic_id ) );
     570                $this->assertSame( array( $reassign_user_id ), bbp_get_topic_engagements( $reply_only_topic_id ) );
     571                $this->assertSame( 1, bbp_get_topic_voice_count( $topic_id, true ) );
     572                $this->assertSame( 1, bbp_get_topic_voice_count( $reply_only_topic_id, true ) );
     573        }
     574
     575        /**
     576         * @covers ::bbp_update_counts_on_user_reassignment
     577         * @ticket BBP3678
     578         */
     579        public function test_deleting_user_with_reassignment_updates_current_multisite_counts() {
     580                if ( ! is_multisite() ) {
     581                        $this->markTestSkipped( 'Requires multisite.' );
     582                }
     583
     584                $deleted_user_id  = $this->factory->user->create();
     585                $reassign_user_id = $this->factory->user->create();
     586                $site_id          = $this->factory->blog->create();
     587
     588                add_user_to_blog( $site_id, $deleted_user_id, 'subscriber' );
     589                add_user_to_blog( $site_id, $reassign_user_id, 'subscriber' );
     590                switch_to_blog( $site_id );
     591
     592                try {
     593                        $forum_id = $this->factory->forum->create();
     594                        $this->factory->topic->create( array(
     595                                'post_author' => $deleted_user_id,
     596                                'post_parent' => $forum_id,
     597                                'topic_meta'  => array( 'forum_id' => $forum_id ),
     598                        ) );
     599
     600                        wp_delete_user( $deleted_user_id, $reassign_user_id );
     601
     602                        $meta_key = bbp_db()->get_blog_prefix() . '_bbp_topic_count';
     603                        $this->assertSame( 1, bbp_get_user_topic_count( $reassign_user_id, true ) );
     604                        $this->assertSame( 1, (int) get_user_meta( $reassign_user_id, $meta_key, true ) );
     605                } finally {
     606                        restore_current_blog();
     607                }
     608        }
     609
     610        /**
     611         * @covers ::bbp_make_spam_user
     612         * @covers ::bbp_make_ham_user
     613         * @covers ::bbp_update_counts_on_transition_post_status
     614         * @ticket BBP3678
     615         */
     616        public function test_bbp_make_spam_and_ham_user_updates_counts() {
     617                $user_id  = $this->factory->user->create();
     618                $forum_id = $this->factory->forum->create();
     619                $topic_id = $this->factory->topic->create( array(
     620                        'post_author' => $user_id,
     621                        'post_parent' => $forum_id,
     622                        'topic_meta'  => array( 'forum_id' => $forum_id ),
     623                ) );
     624                $this->factory->reply->create( array(
     625                        'post_author' => $user_id,
     626                        'post_parent' => $topic_id,
     627                        'reply_meta'  => array(
     628                                'forum_id' => $forum_id,
     629                                'topic_id' => $topic_id,
     630                        ),
     631                ) );
     632
     633                $this->assertTrue( bbp_make_spam_user( $user_id ) );
     634                $this->assertSame( 0, bbp_get_user_topic_count( $user_id, true ) );
     635                $this->assertSame( 0, bbp_get_user_reply_count( $user_id, true ) );
     636                $this->assertSame( 0, bbp_get_forum_topic_count( $forum_id, false, true ) );
     637                $this->assertSame( 0, bbp_get_forum_reply_count( $forum_id, false, true ) );
     638                $this->assertSame( 1, bbp_get_forum_topic_count_hidden( $forum_id, false, true ) );
     639                $this->assertSame( 1, bbp_get_forum_reply_count_hidden( $forum_id, false, true ) );
     640
     641                $this->assertTrue( bbp_make_ham_user( $user_id ) );
     642                $this->assertSame( 1, bbp_get_user_topic_count( $user_id, true ) );
     643                $this->assertSame( 1, bbp_get_user_reply_count( $user_id, true ) );
     644                $this->assertSame( 1, bbp_get_forum_topic_count( $forum_id, false, true ) );
     645                $this->assertSame( 1, bbp_get_forum_reply_count( $forum_id, false, true ) );
     646                $this->assertSame( 0, bbp_get_forum_topic_count_hidden( $forum_id, false, true ) );
     647                $this->assertSame( 0, bbp_get_forum_reply_count_hidden( $forum_id, false, true ) );
     648        }
     649
     650        /**
     651         * @covers ::bbp_update_counts_on_post_author_change
     652         */
     653        public function test_changing_post_authors_updates_user_counts() {
     654                $old_user_id = $this->factory->user->create();
     655                $new_user_id = $this->factory->user->create();
     656                $forum_id    = $this->factory->forum->create();
     657                $topic_id    = $this->factory->topic->create( array(
     658                        'post_author' => $old_user_id,
     659                        'post_parent' => $forum_id,
     660                        'topic_meta'  => array( 'forum_id' => $forum_id ),
     661                ) );
     662                $reply_id = $this->factory->reply->create( array(
     663                        'post_author' => $old_user_id,
     664                        'post_parent' => $topic_id,
     665                        'reply_meta'  => array(
     666                                'forum_id' => $forum_id,
     667                                'topic_id' => $topic_id,
     668                        ),
     669                ) );
     670
     671                wp_update_post( array( 'ID' => $topic_id, 'post_author' => $new_user_id ) );
     672                $this->assertSame( 2, bbp_get_topic_voice_count( $topic_id, true ) );
     673
     674                wp_update_post( array( 'ID' => $reply_id, 'post_author' => $new_user_id ) );
     675                $this->assertSame( 1, bbp_get_topic_voice_count( $topic_id, true ) );
     676
     677                $this->assertSame( 0, bbp_get_user_topic_count( $old_user_id, true ) );
     678                $this->assertSame( 0, bbp_get_user_reply_count( $old_user_id, true ) );
     679                $this->assertSame( 1, bbp_get_user_topic_count( $new_user_id, true ) );
     680                $this->assertSame( 1, bbp_get_user_reply_count( $new_user_id, true ) );
     681        }
     682
     683        /**
     684         * @covers ::bbp_update_counts_on_post_author_change
     685         */
     686        public function test_changing_post_author_and_status_rebuilds_user_counts() {
     687                $old_user_id = $this->factory->user->create();
     688                $new_user_id = $this->factory->user->create();
     689                $forum_id    = $this->factory->forum->create();
     690                $topic_id    = $this->factory->topic->create( array(
     691                        'post_author' => $old_user_id,
     692                        'post_parent' => $forum_id,
     693                        'topic_meta'  => array( 'forum_id' => $forum_id ),
     694                ) );
     695
     696                wp_update_post( array(
     697                        'ID'          => $topic_id,
     698                        'post_author' => $new_user_id,
     699                        'post_status' => bbp_get_pending_status_id(),
     700                ) );
     701
     702                $this->assertSame( 0, bbp_get_user_topic_count( $old_user_id, true ) );
     703                $this->assertSame( 0, bbp_get_user_topic_count( $new_user_id, true ) );
     704        }
     705
     706        /**
    404707         * @covers ::bbp_increase_user_topic_count
    405708         */
Note: See TracChangeset for help on using the changeset viewer.