From 363b226dd192ce8007f8d146c7c50aa64b5f5b51 Mon Sep 17 00:00:00 2001 From: Sorin Marta Date: Fri, 3 Jul 2026 16:51:35 +0300 Subject: [PATCH 1/4] Fix sticky listing order for plan custom sorts Featured listings now follow plan weight and title/date when directory sorting uses plan-order-title or plan-order-date. --- includes/class-query-integration.php | 95 ++++++++-- tests/wpunit/Listing/ListingQuerySortTest.php | 163 ++++++++++++++++++ 2 files changed, 242 insertions(+), 16 deletions(-) create mode 100644 tests/wpunit/Listing/ListingQuerySortTest.php diff --git a/includes/class-query-integration.php b/includes/class-query-integration.php index e6b06fd07..ebd0d55b8 100644 --- a/includes/class-query-integration.php +++ b/includes/class-query-integration.php @@ -341,26 +341,16 @@ private function get_sticky_query( $query ) { * @return string listing ids */ private function get_sticky_listing_ids( $query ) { - global $wpdb; - - $order_by = $query->get( 'orderby' ); - $order = $query->get( 'order' ); - $join_sort = in_array( $order_by, array( 'title', 'date', 'modified', 'author' ), true ); - - $query = "SELECT listing_id FROM {$wpdb->prefix}wpbdp_listings"; - if ( $join_sort ) { - $query .= " JOIN {$wpdb->posts} p ON p.ID = {$wpdb->prefix}wpbdp_listings.listing_id"; - } - $query .= ' WHERE is_sticky=1'; - if ( $join_sort ) { - $query .= " ORDER BY p.post_{$order_by} {$order}"; - } + $order_by = (string) $query->get( 'orderby' ); + $order = strtoupper( (string) $query->get( 'order' ) ); + $order = in_array( $order, array( 'ASC', 'DESC' ), true ) ? $order : 'ASC'; + $sql_query = $this->get_sticky_listing_ids_query( $order_by, $order ); $results = WPBDP_Utils::check_cache( array( - 'cache_key' => 'sticky_listing_idss', + 'cache_key' => $this->get_sticky_listing_ids_cache_key( $order_by, $order ), 'group' => 'wpbdp_listings', - 'query' => $query, + 'query' => $sql_query, 'type' => 'get_col', ) ); @@ -376,6 +366,79 @@ private function get_sticky_listing_ids( $query ) { return implode( ',', $results ); } + /** + * Get the SQL query for sticky listing ids. + * + * @since x.x + * + * @param string $order_by The orderby value from the current query. + * @param string $order The normalized order direction. + * + * @return string + */ + private function get_sticky_listing_ids_query( $order_by, $order ) { + global $wpdb; + + $query = "SELECT l.listing_id FROM {$wpdb->prefix}wpbdp_listings l"; + + switch ( $order_by ) { + case 'title': + case 'date': + case 'modified': + case 'author': + $query .= " JOIN {$wpdb->posts} p ON p.ID = l.listing_id"; + $query .= ' WHERE l.is_sticky=1'; + $query .= " ORDER BY p.post_{$order_by} {$order}"; + + break; + case 'paid': + case 'paid-title': + $next_order = 'paid' === $order_by ? 'post_date DESC' : 'post_title ASC'; + + $query .= " JOIN {$wpdb->posts} p ON p.ID = l.listing_id"; + $query .= ' WHERE l.is_sticky=1'; + $query .= " ORDER BY l.fee_price {$order}, p.{$next_order}"; + + break; + case 'plan-order-date': + case 'plan-order-title': + $plan_order = wpbdp_get_option( 'fee-order' ); + if ( is_array( $plan_order ) && isset( $plan_order['method'] ) && 'custom' === $plan_order['method'] ) { + $next_order = 'plan-order-date' === $order_by ? 'post_date' : 'post_title'; + + $query .= " JOIN {$wpdb->posts} p ON p.ID = l.listing_id"; + $query .= " LEFT JOIN {$wpdb->prefix}wpbdp_plans po ON po.id = l.fee_id"; + $query .= ' WHERE l.is_sticky=1'; + $query .= " ORDER BY po.weight DESC, p.{$next_order} {$order}"; + break; + } + + $query .= ' WHERE l.is_sticky=1'; + + break; + default: + $query .= ' WHERE l.is_sticky=1'; + + break; + } + + return $query; + } + + /** + * Get the cache key for sticky listing ids. + * + * @since x.x + * + * @param string $order_by The orderby value from the current query. + * @param string $order The normalized order direction. + * + * @return string + */ + private function get_sticky_listing_ids_cache_key( $order_by, $order ) { + return 'sticky_listing_ids_' . md5( $order_by . '|' . $order . '|' . maybe_serialize( wpbdp_get_option( 'fee-order' ) ) ); + } + // {{ Sort bar. public function sortbar_sort_options( $options ) { $sortbar_fields = wpbdp_sortbar_get_field_options(); diff --git a/tests/wpunit/Listing/ListingQuerySortTest.php b/tests/wpunit/Listing/ListingQuerySortTest.php new file mode 100644 index 000000000..1c4a4c54d --- /dev/null +++ b/tests/wpunit/Listing/ListingQuerySortTest.php @@ -0,0 +1,163 @@ + 'custom', + 'order' => 'asc', + ) + ); + wpbdp_set_option( 'prevent-sticky-on-directory-view', array() ); + WPBDP_Utils::cache_delete_group( 'wpbdp_listings' ); + } + + /** + * @since x.x + */ + public function testStickyListingsUsePlanOrderTitleSort() { + $plan = $this->create_fee_plan( 'sticky_plan_order_title', 10 ); + + $zebra = $this->create_listing_on_plan( 'Zebra Corp', $plan, '2026-01-01 00:00:00' ); + $alpha = $this->create_listing_on_plan( 'Alpha Inc', $plan, '2026-01-02 00:00:00' ); + $mango = $this->create_listing_on_plan( 'Mango Ltd', $plan, '2026-01-03 00:00:00' ); + + $listing_ids = array( $zebra, $alpha, $mango ); + + $this->assertSame( + array( $alpha, $mango, $zebra ), + $this->query_listing_ids( 'plan-order-title', 'ASC', $listing_ids ) + ); + + $this->assertSame( + array( $zebra, $mango, $alpha ), + $this->query_listing_ids( 'plan-order-title', 'DESC', $listing_ids ) + ); + } + + /** + * @since x.x + */ + public function testStickyListingsUsePlanOrderDateSort() { + $plan = $this->create_fee_plan( 'sticky_plan_order_date', 10 ); + + $newest = $this->create_listing_on_plan( 'Newest Listing', $plan, '2026-01-03 00:00:00' ); + $oldest = $this->create_listing_on_plan( 'Oldest Listing', $plan, '2026-01-01 00:00:00' ); + $middle = $this->create_listing_on_plan( 'Middle Listing', $plan, '2026-01-02 00:00:00' ); + + $this->assertSame( + array( $oldest, $middle, $newest ), + $this->query_listing_ids( 'plan-order-date', 'ASC', array( $newest, $oldest, $middle ) ) + ); + } + + /** + * @since x.x + * + * @param string $tag Fee plan tag. + * @param int $weight Fee plan weight. + * + * @return WPBDP__Fee_Plan + */ + private function create_fee_plan( $tag, $weight ) { + $fee = new WPBDP__Fee_Plan( + array( + 'label' => 'Sticky Plan ' . $weight, + 'amount' => 100, + 'days' => 365, + 'sticky' => 1, + 'recurring' => 0, + 'enabled' => 1, + 'images' => 0, + 'pricing_model' => 'flat', + 'supported_categories' => 'all', + 'tag' => $tag, + 'weight' => $weight, + ) + ); + + $result = $fee->save(); + if ( is_wp_error( $result ) ) { + $this->fail( 'Fee creation failed: ' . $result->get_error_message() ); + } + + $this->assertTrue( is_int( $fee->id ) ); + + return $fee; + } + + /** + * @since x.x + * + * @param string $title Post title. + * @param WPBDP__Fee_Plan $plan Fee plan. + * @param string $date Post date. + * + * @return int + */ + private function create_listing_on_plan( $title, $plan, $date ) { + $listing_id = wp_insert_post( + array( + 'post_author' => 1, + 'post_type' => WPBDP_POST_TYPE, + 'post_status' => 'publish', + 'post_title' => $title, + 'post_date' => $date, + 'post_date_gmt' => get_gmt_from_date( $date ), + ) + ); + + $this->assertTrue( is_int( $listing_id ) && 0 < $listing_id ); + $this->assertTrue( wpbdp_get_listing( $listing_id )->set_fee_plan( $plan ) ); + + return $listing_id; + } + + /** + * @since x.x + * + * @param string $order_by Order by value. + * @param string $order Order direction. + * @param int[] $listing_ids Listing ids. + * + * @return int[] + */ + private function query_listing_ids( $order_by, $order, $listing_ids ) { + $query = new WP_Query( + array( + 'post_type' => WPBDP_POST_TYPE, + 'post_status' => 'publish', + 'post__in' => $listing_ids, + 'orderby' => $order_by, + 'order' => $order, + 'posts_per_page' => -1, + 'wpbdp_shortcode' => true, + ) + ); + + return array_map( 'intval', wp_list_pluck( $query->posts, 'ID' ) ); + } +} From f6c2e478aac90bc96d16022ecc61b62800160caf Mon Sep 17 00:00:00 2001 From: Sorin Marta Date: Fri, 3 Jul 2026 16:54:43 +0300 Subject: [PATCH 2/4] Fix sticky sort test plan assignment assertion The test now accepts the database success result returned by set_fee_plan. --- tests/wpunit/Listing/ListingQuerySortTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/wpunit/Listing/ListingQuerySortTest.php b/tests/wpunit/Listing/ListingQuerySortTest.php index 1c4a4c54d..474fda80b 100644 --- a/tests/wpunit/Listing/ListingQuerySortTest.php +++ b/tests/wpunit/Listing/ListingQuerySortTest.php @@ -131,7 +131,7 @@ private function create_listing_on_plan( $title, $plan, $date ) { ); $this->assertTrue( is_int( $listing_id ) && 0 < $listing_id ); - $this->assertTrue( wpbdp_get_listing( $listing_id )->set_fee_plan( $plan ) ); + $this->assertNotFalse( wpbdp_get_listing( $listing_id )->set_fee_plan( $plan ) ); return $listing_id; } From 3216f70d660bba65f515fea03cd2e6bb596ca6e1 Mon Sep 17 00:00:00 2001 From: Sorin Marta Date: Fri, 3 Jul 2026 16:56:25 +0300 Subject: [PATCH 3/4] Fix sticky sort query test isolation The regression test now avoids post__in ordering and cleans created records between cases. --- tests/wpunit/Listing/ListingQuerySortTest.php | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/tests/wpunit/Listing/ListingQuerySortTest.php b/tests/wpunit/Listing/ListingQuerySortTest.php index 474fda80b..aea854db5 100644 --- a/tests/wpunit/Listing/ListingQuerySortTest.php +++ b/tests/wpunit/Listing/ListingQuerySortTest.php @@ -20,6 +20,16 @@ class ListingQuerySortTest extends WPUnitTestCase { */ protected $tester; + /** + * @var int[] + */ + private $created_listing_ids = array(); + + /** + * @var int[] + */ + private $created_plan_ids = array(); + /** * @since x.x */ @@ -35,6 +45,26 @@ protected function after_setup() { WPBDP_Utils::cache_delete_group( 'wpbdp_listings' ); } + /** + * @since x.x + */ + public function tearDown() : void { + foreach ( $this->created_listing_ids as $listing_id ) { + wp_delete_post( $listing_id, true ); + } + + foreach ( $this->created_plan_ids as $plan_id ) { + $plan = wpbdp_get_fee_plan( $plan_id ); + if ( $plan ) { + $plan->delete(); + } + } + + WPBDP_Utils::cache_delete_group( 'wpbdp_listings' ); + + parent::tearDown(); + } + /** * @since x.x */ @@ -105,6 +135,7 @@ private function create_fee_plan( $tag, $weight ) { } $this->assertTrue( is_int( $fee->id ) ); + $this->created_plan_ids[] = $fee->id; return $fee; } @@ -132,6 +163,7 @@ private function create_listing_on_plan( $title, $plan, $date ) { $this->assertTrue( is_int( $listing_id ) && 0 < $listing_id ); $this->assertNotFalse( wpbdp_get_listing( $listing_id )->set_fee_plan( $plan ) ); + $this->created_listing_ids[] = $listing_id; return $listing_id; } @@ -150,10 +182,9 @@ private function query_listing_ids( $order_by, $order, $listing_ids ) { array( 'post_type' => WPBDP_POST_TYPE, 'post_status' => 'publish', - 'post__in' => $listing_ids, 'orderby' => $order_by, 'order' => $order, - 'posts_per_page' => -1, + 'posts_per_page' => count( $listing_ids ), 'wpbdp_shortcode' => true, ) ); From 9982984abb68d67bfc16f0c8a751a64939204790 Mon Sep 17 00:00:00 2001 From: Sorin Marta Date: Fri, 3 Jul 2026 16:58:20 +0300 Subject: [PATCH 4/4] Target sticky sort regression at ID query The test now verifies the sticky ID order directly instead of relying on front-end query hooks. --- tests/wpunit/Listing/ListingQuerySortTest.php | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/tests/wpunit/Listing/ListingQuerySortTest.php b/tests/wpunit/Listing/ListingQuerySortTest.php index aea854db5..a764028ea 100644 --- a/tests/wpunit/Listing/ListingQuerySortTest.php +++ b/tests/wpunit/Listing/ListingQuerySortTest.php @@ -75,16 +75,14 @@ public function testStickyListingsUsePlanOrderTitleSort() { $alpha = $this->create_listing_on_plan( 'Alpha Inc', $plan, '2026-01-02 00:00:00' ); $mango = $this->create_listing_on_plan( 'Mango Ltd', $plan, '2026-01-03 00:00:00' ); - $listing_ids = array( $zebra, $alpha, $mango ); - $this->assertSame( array( $alpha, $mango, $zebra ), - $this->query_listing_ids( 'plan-order-title', 'ASC', $listing_ids ) + $this->get_display_listing_ids_from_sticky_query( 'plan-order-title', 'ASC' ) ); $this->assertSame( array( $zebra, $mango, $alpha ), - $this->query_listing_ids( 'plan-order-title', 'DESC', $listing_ids ) + $this->get_display_listing_ids_from_sticky_query( 'plan-order-title', 'DESC' ) ); } @@ -100,7 +98,7 @@ public function testStickyListingsUsePlanOrderDateSort() { $this->assertSame( array( $oldest, $middle, $newest ), - $this->query_listing_ids( 'plan-order-date', 'ASC', array( $newest, $oldest, $middle ) ) + $this->get_display_listing_ids_from_sticky_query( 'plan-order-date', 'ASC' ) ); } @@ -171,24 +169,22 @@ private function create_listing_on_plan( $title, $plan, $date ) { /** * @since x.x * - * @param string $order_by Order by value. - * @param string $order Order direction. - * @param int[] $listing_ids Listing ids. + * @param string $order_by Order by value. + * @param string $order Order direction. * * @return int[] */ - private function query_listing_ids( $order_by, $order, $listing_ids ) { - $query = new WP_Query( - array( - 'post_type' => WPBDP_POST_TYPE, - 'post_status' => 'publish', - 'orderby' => $order_by, - 'order' => $order, - 'posts_per_page' => count( $listing_ids ), - 'wpbdp_shortcode' => true, - ) - ); + private function get_display_listing_ids_from_sticky_query( $order_by, $order ) { + $query = new WP_Query(); + $query->set( 'orderby', $order_by ); + $query->set( 'order', $order ); + + $reflection = new \ReflectionClass( 'WPBDP__Query_Integration' ); + $method = $reflection->getMethod( 'get_sticky_listing_ids' ); + $method->setAccessible( true ); + + $sticky_ids = $method->invoke( $reflection->newInstanceWithoutConstructor(), $query ); - return array_map( 'intval', wp_list_pluck( $query->posts, 'ID' ) ); + return array_reverse( array_map( 'intval', explode( ',', $sticky_ids ) ) ); } }