Skip to content

Commit

Permalink
Merge pull request #889 from carstingaxion/feature/query-order-by
Browse files Browse the repository at this point in the history
Prepare for new block variations 2/3: Make event query respect ORDERBY, ORDER and 'unfinished events'
  • Loading branch information
mauteri authored Oct 12, 2024
2 parents 11e93cb + da7a512 commit 9baff99
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 33 deletions.
122 changes: 98 additions & 24 deletions includes/core/classes/class-event-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ protected function __construct() {
*/
protected function setup_hooks(): void {
add_action( 'pre_get_posts', array( $this, 'prepare_event_query_before_execution' ) );
add_filter( 'posts_clauses', array( $this, 'adjust_admin_event_sorting' ) );
add_filter( 'posts_clauses', array( $this, 'adjust_admin_event_sorting' ), 10, 2 );
}

/**
Expand Down Expand Up @@ -231,10 +231,10 @@ static function () use ( $page_id ) {
switch ( $events_query ) {
case 'upcoming':
remove_filter( 'posts_clauses', array( $this, 'adjust_sorting_for_past_events' ) );
add_filter( 'posts_clauses', array( $this, 'adjust_sorting_for_upcoming_events' ) );
add_filter( 'posts_clauses', array( $this, 'adjust_sorting_for_upcoming_events' ), 10, 2 );
break;
case 'past':
add_filter( 'posts_clauses', array( $this, 'adjust_sorting_for_past_events' ) );
add_filter( 'posts_clauses', array( $this, 'adjust_sorting_for_past_events' ), 10, 2 );
remove_filter( 'posts_clauses', array( $this, 'adjust_sorting_for_upcoming_events' ) );
break;
default:
Expand All @@ -249,13 +249,22 @@ static function () use ( $page_id ) {
* This method modifies the SQL query pieces, including join, where, orderby, etc., to adjust the sorting criteria
* for upcoming events in the query. It ensures that events are ordered by their start datetime in ascending order.
*
* @see https://developer.wordpress.org/reference/hooks/posts_clauses/
*
* @since 1.0.0
*
* @param array $query_pieces An array containing pieces of the SQL query.
* @param array $query_pieces An array containing pieces of the SQL query.
* @param WP_Query $query The WP_Query instance (passed by reference).
* @return array The modified SQL query pieces with adjusted sorting criteria for upcoming events.
*/
public function adjust_sorting_for_upcoming_events( array $query_pieces ): array {
return $this->adjust_event_sql( $query_pieces, 'upcoming', 'ASC' );
public function adjust_sorting_for_upcoming_events( array $query_pieces, WP_Query $query ): array {
return $this->adjust_event_sql(
$query_pieces,
'upcoming',
$query->get( 'order' ),
$query->get( 'orderby' ),
(bool) $query->get( 'include_unfinished' )
);
}

/**
Expand All @@ -264,12 +273,18 @@ public function adjust_sorting_for_upcoming_events( array $query_pieces ): array
* This method modifies the SQL query pieces, including join, where, orderby, etc., to adjust the sorting criteria
* for past events in the query. It ensures that events are ordered by their start datetime in the desired order.
*
* @param array $query_pieces An array containing pieces of the SQL query.
*
* @param array $query_pieces An array containing pieces of the SQL query.
* @param WP_Query $query The WP_Query instance (passed by reference).
* @return array The modified SQL query pieces with adjusted sorting criteria for past events.
*/
public function adjust_sorting_for_past_events( array $query_pieces ): array {
return $this->adjust_event_sql( $query_pieces, 'past' );
public function adjust_sorting_for_past_events( array $query_pieces, WP_Query $query ): array {
return $this->adjust_event_sql(
$query_pieces,
'past',
$query->get( 'order' ),
$query->get( 'orderby' ),
(bool) $query->get( 'include_unfinished' )
);
}

/**
Expand All @@ -280,18 +295,17 @@ public function adjust_sorting_for_past_events( array $query_pieces ): array {
*
* @since 1.0.0
*
* @param array $query_pieces An array containing pieces of the SQL query.
* @param array $query_pieces An array containing pieces of the SQL query.
* @param WP_Query $query The WP_Query instance (passed by reference).
* @return array The modified SQL query pieces with adjusted sorting criteria.
*/
public function adjust_admin_event_sorting( array $query_pieces ): array {
public function adjust_admin_event_sorting( array $query_pieces, WP_Query $query ): array {
if ( ! is_admin() ) {
return $query_pieces;
}

global $wp_query;

if ( 'datetime' === $wp_query->get( 'orderby' ) ) {
$query_pieces = $this->adjust_event_sql( $query_pieces, 'all', $wp_query->get( 'order' ) );
if ( 'datetime' === $query->get( 'orderby' ) ) {
$query_pieces = $this->adjust_event_sql( $query_pieces, 'all', $query->get( 'order' ) );
}

return $query_pieces;
Expand All @@ -304,14 +318,26 @@ public function adjust_admin_event_sorting( array $query_pieces ): array {
* the `gatherpress_events` table in the database join. It allows querying events based on different
* criteria such as upcoming or past events and specifying the event order (DESC or ASC).
*
* @see https://developer.wordpress.org/reference/hooks/posts_join/
* @see https://developer.wordpress.org/reference/hooks/posts_orderby/
* @see https://developer.wordpress.org/reference/hooks/posts_where/
*
* @since 1.0.0
*
* @param array $pieces An array of query pieces, including join, where, orderby, and more.
* @param string $type The type of events to query (options: 'all', 'upcoming', 'past').
* @param string $order The event order ('DESC' for descending or 'ASC' for ascending).
* @param array $pieces An array of query pieces, including join, where, orderby, and more.
* @param string $type The type of events to query (options: 'all', 'upcoming', 'past') (Default: 'all').
* @param string $order The event order ('DESC' for descending or 'ASC' for ascending) (Default: 'DESC').
* @param string[]|string $order_by List or singular string of ORDERBY statement(s) (Default: ['datetime']).
* @param bool $inclusive Whether to include currently running events in the query (Default: true).
* @return array An array containing adjusted SQL clauses for the Event query.
*/
public function adjust_event_sql( array $pieces, string $type = 'all', string $order = 'DESC' ): array {
public function adjust_event_sql(
array $pieces,
string $type = 'all',
string $order = 'DESC',
$order_by = array( 'datetime' ),
bool $inclusive = true
): array {
global $wpdb;

$defaults = array(
Expand All @@ -324,27 +350,75 @@ public function adjust_event_sql( array $pieces, string $type = 'all', string $o
'limits' => '',
);
$pieces = array_merge( $defaults, $pieces );
$table = sprintf( Event::TABLE_FORMAT, $wpdb->prefix );
$table = sprintf( Event::TABLE_FORMAT, $wpdb->prefix ); // Could also be (just) $wpdb->{gatherpress_events}.
$pieces['join'] .= ' LEFT JOIN ' . esc_sql( $table ) . ' ON ' . esc_sql( $wpdb->posts ) . '.ID='
. esc_sql( $table ) . '.post_id';
$order = strtoupper( $order );

if ( in_array( $order, array( 'DESC', 'ASC' ), true ) ) {
$pieces['orderby'] = sprintf( esc_sql( $table ) . '.datetime_start_gmt %s', esc_sql( $order ) );
// ORDERBY is an array, which allows to orderby multiple values.
// Currently, it is only allowed to order events by ONE value.
$order_by = ( is_array( $order_by ) ) ? $order_by[0] : $order_by;

switch ( strtolower( $order_by ) ) {
case 'id':
$pieces['orderby'] = sprintf( esc_sql( $wpdb->posts ) . '.ID %s', esc_sql( $order ) );
break;
case 'title':
$pieces['orderby'] = sprintf( esc_sql( $wpdb->posts ) . '.post_name %s', esc_sql( $order ) );
break;
case 'modified':
$pieces['orderby'] = sprintf( esc_sql( $wpdb->posts ) . '.post_modified_gmt %s', esc_sql( $order ) );
break;
case 'rand':
$pieces['orderby'] = esc_sql( 'RAND()' );
break;
case 'datetime':
default:
$pieces['orderby'] = sprintf( esc_sql( $table ) . '.datetime_start_gmt %s', esc_sql( $order ) );
break;
}
}

if ( 'all' === $type ) {
return $pieces;
}

$current = gmdate( Event::DATETIME_FORMAT, time() );
$column = $this->get_datetime_comparison_column( $type, $inclusive );

if ( 'upcoming' === $type ) {
$pieces['where'] .= $wpdb->prepare( ' AND %i.datetime_end_gmt >= %s', $table, $current );
$pieces['where'] .= $wpdb->prepare( ' AND %i.%i >= %s', $table, $column, $current );
} elseif ( 'past' === $type ) {
$pieces['where'] .= $wpdb->prepare( ' AND %i.datetime_end_gmt < %s', $table, $current );
$pieces['where'] .= $wpdb->prepare( ' AND %i.%i < %s', $table, $column, $current );
}

return $pieces;
}

/**
* Determine which db column to compare against,
* based on the type of event query (either upcoming or past)
* and if started but unfinished events should be included.
*
* @param string $type The type of events to query (options: 'all', 'upcoming', 'past') (Cannot be 'all' anymore).
* @param bool $inclusive Whether to include currently running events in the query.
*
* @return string Name of the DB column, which content to compare against the current time.
*/
protected static function get_datetime_comparison_column( string $type, bool $inclusive ): string {
if (
// Upcoming events, including ones that are running.
( $inclusive && 'upcoming' === $type ) ||
// Past events, that are finished already.
( ! $inclusive && 'past' === $type )
) {
return 'datetime_end_gmt';
}

// All others, means:
// - Upcoming events, without running events.
// - Past events, that are still running.
return 'datetime_start_gmt';
}
}
73 changes: 64 additions & 9 deletions test/unit/php/includes/core/classes/class-test-event-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use GatherPress\Core\Topic;
use GatherPress\Core\Venue;
use PMC\Unit_Test\Base;
use PMC\Unit_Test\Utility;

/**
* Class Test_Event_Query.
Expand Down Expand Up @@ -200,19 +201,19 @@ public function test_get_events_list(): void {
*/
public function test_adjust_admin_event_sorting(): void {
$instance = Event_Query::get_instance();
global $wp_query;

$this->mock->user( false, 'admin' );
$response = $instance->adjust_admin_event_sorting( array() );
$response = $instance->adjust_admin_event_sorting( array(), $wp_query );
$this->assertEmpty( $response, 'Failed to assert array is not empty' );

$this->mock->user( true, 'admin' );

// Set 'orderby' admin query to 'datetime'.
global $wp_query;
$wp_query->set( 'orderby', 'datetime' );

// Run function with empty array passed as 'pieces' argument.
$response = $instance->adjust_admin_event_sorting( array() );
$response = $instance->adjust_admin_event_sorting( array(), $wp_query );

// Assert that an array was generated from the adjustsql argument. todo: make this test more meaningful.
$this->assertNotEmpty( $response, 'Failed to assert array is empty' );
Expand All @@ -233,17 +234,71 @@ public function test_adjust_event_sql(): void {
$table = sprintf( Event::TABLE_FORMAT, $wpdb->prefix );
$retval = $instance->adjust_event_sql( array(), 'all', 'DESC' );

$this->assertStringContainsString( 'DESC', $retval['orderby'] );
$this->assertStringContainsString( '.datetime_start_gmt DESC', $retval['orderby'] );
$this->assertEmpty( $retval['where'] );

$retval = $instance->adjust_event_sql( array(), 'past', 'desc' );
$retval = $instance->adjust_event_sql( array(), 'past', 'desc' ); // inclusive will be TRUE by default.

$this->assertStringContainsString( '.datetime_start_gmt DESC', $retval['orderby'] );
$this->assertStringContainsString( "AND `{$table}`.`datetime_start_gmt` <", $retval['where'] );

$retval = $instance->adjust_event_sql( array(), 'past', 'desc', 'datetime', false );

$this->assertStringContainsString( 'DESC', $retval['orderby'] );
$this->assertStringContainsString( "AND `{$table}`.datetime_end_gmt <", $retval['where'] );
$this->assertStringContainsString( '.datetime_start_gmt DESC', $retval['orderby'] );
$this->assertStringContainsString( "AND `{$table}`.`datetime_end_gmt` <", $retval['where'] );

$retval = $instance->adjust_event_sql( array(), 'upcoming', 'ASC' );

$this->assertStringContainsString( 'ASC', $retval['orderby'] );
$this->assertStringContainsString( "AND `{$table}`.datetime_end_gmt >=", $retval['where'] );
$this->assertStringContainsString( '.datetime_start_gmt ASC', $retval['orderby'] );
$this->assertStringContainsString( "AND `{$table}`.`datetime_end_gmt` >=", $retval['where'] );

$retval = $instance->adjust_event_sql( array(), 'past', 'desc', 'id', false );

$this->assertStringContainsString( '.ID DESC', $retval['orderby'] );

$retval = $instance->adjust_event_sql( array(), 'past', 'desc', 'title', false );

$this->assertStringContainsString( '.post_name DESC', $retval['orderby'] );

$retval = $instance->adjust_event_sql( array(), 'past', 'desc', 'modified', false );

$this->assertStringContainsString( '.post_modified_gmt DESC', $retval['orderby'] );

$retval = $instance->adjust_event_sql( array(), 'upcoming', 'desc', 'rand', false );

$this->assertStringContainsString( 'RAND()', $retval['orderby'] );
}

/**
* Coverage for get_datetime_comparison_column method.
*
* @covers ::get_datetime_comparison_column
*
* @return void
*/
public function test_get_datetime_comparison_column(): void {
$instance = Event_Query::get_instance();

$this->assertSame(
'datetime_end_gmt',
Utility::invoke_hidden_method( $instance, 'get_datetime_comparison_column', array( 'upcoming', true ) ),
'Failed to assert, that inclusive, upcoming events should be ordered by datetime_end_gmt.'
);
$this->assertSame(
'datetime_start_gmt',
Utility::invoke_hidden_method( $instance, 'get_datetime_comparison_column', array( 'upcoming', false ) ),
'Failed to assert, that non-inclusive, upcoming events should be ordered by datetime_start_gmt.'
);

$this->assertSame(
'datetime_start_gmt',
Utility::invoke_hidden_method( $instance, 'get_datetime_comparison_column', array( 'past', true ) ),
'Failed to assert, that inclusive, past events should be ordered by datetime_start_gmt.'
);
$this->assertSame(
'datetime_end_gmt',
Utility::invoke_hidden_method( $instance, 'get_datetime_comparison_column', array( 'past', false ) ),
'Failed to assert, that non-inclusive, past events should be ordered by datetime_end_gmt.'
);
}
}

0 comments on commit 9baff99

Please sign in to comment.