Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare for new block variations 2/3: Make event query respect ORDERBY, ORDER and 'unfinished events' #889

Merged
merged 12 commits into from
Oct 12, 2024
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, Event::POST_TYPE );
$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.'
);
}
}
Loading