From 15f0c6b3d82dc1589c4f613fd91c631308a7afbe Mon Sep 17 00:00:00 2001 From: Brian DiChiara <122309362+bd-viget@users.noreply.github.com> Date: Thu, 21 Dec 2023 09:24:31 -0600 Subject: [PATCH] Auction Status & Auction Start Cron (#111) * [#81] Set and Get the GUID for Auctions * [#81] Added Auction End Date/Time field * [#81] API access method + documentation * Fix a PHP bug. * VS Code settings and extensions recommendations * Update Auction Meta Box, Determine Auction Status * Just some PR feedback that got left off. * [#26] Data validation, restructure, & Status Column * [#26] Added Time Until info * Styling tweaks * [#26] Update Auction meta when auction has started. * [#26] Cron hook documentation * [#26] Last minute tweaks. * [#26] Better method name. * [#26] Removed a space * [#26] Swap API for $this * [#26] More last second fixes. * [N/A] Modified Auction Metrics block. * [N/A] Last change to Pattern --------- Co-authored-by: Nathan Schmidt <91974372+nathan-schmidt-viget@users.noreply.github.com> --- .../blocks/auction-metrics-general/block.json | 15 + .../blocks/auction-metrics-general/render.php | 50 +++ .../blocks/reward-product-gallery/block.php | 38 ++- .../goodbids/blocks/reward-product/block.json | 15 - .../goodbids/blocks/reward-product/render.php | 41 --- .../src/classes/Auctions/Auctions.php | 320 +++++++++++++++--- .../goodbids/src/classes/Auctions/Bids.php | 2 +- .../goodbids/src/classes/Network/Sites.php | 7 +- .../goodbids/views/admin/auctions/details.php | 27 ++ .../goodbids/views/admin/auctions/metrics.php | 48 +++ .../patterns/template-archive-auction.php | 2 +- .../views/patterns/template-auction.php | 3 +- docs/hooks/actions.md | 14 + 13 files changed, 447 insertions(+), 135 deletions(-) create mode 100644 client-mu-plugins/goodbids/blocks/auction-metrics-general/block.json create mode 100644 client-mu-plugins/goodbids/blocks/auction-metrics-general/render.php delete mode 100644 client-mu-plugins/goodbids/blocks/reward-product/block.json delete mode 100644 client-mu-plugins/goodbids/blocks/reward-product/render.php create mode 100644 client-mu-plugins/goodbids/views/admin/auctions/details.php create mode 100644 client-mu-plugins/goodbids/views/admin/auctions/metrics.php diff --git a/client-mu-plugins/goodbids/blocks/auction-metrics-general/block.json b/client-mu-plugins/goodbids/blocks/auction-metrics-general/block.json new file mode 100644 index 000000000..358cdb07e --- /dev/null +++ b/client-mu-plugins/goodbids/blocks/auction-metrics-general/block.json @@ -0,0 +1,15 @@ +{ + "name": "auction-metrics-general", + "title": "Auction Metrics (General)", + "description": "Displays the general Auction metrics.", + "icon": "awards", + "category": "goodbids", + "textdomain": "goodbids", + "keywords": ["custom", "details", "stats", "auction"], + "acf": { + "mode": "preview" + }, + "supports": { + "jsx": false + } +} diff --git a/client-mu-plugins/goodbids/blocks/auction-metrics-general/render.php b/client-mu-plugins/goodbids/blocks/auction-metrics-general/render.php new file mode 100644 index 000000000..7b57c6add --- /dev/null +++ b/client-mu-plugins/goodbids/blocks/auction-metrics-general/render.php @@ -0,0 +1,50 @@ +auctions->get_goal(); +$estimated_value = goodbids()->auctions->get_estimated_value(); +$expected_high_bid = goodbids()->auctions->get_expected_high_bid(); + +// Display an Admin message to make the block easier to find. +if ( ! $goal && ! $estimated_value && ! $expected_high_bid && is_admin() ) : + printf( + '

%s

', + esc_html__( 'No stats yet.', 'goodbids' ) + ); + return; +endif; +?> +
> + %s

', + esc_html__( wc_price( $goal ), 'goodbids' ) + ); + endif; + + // Estimated Value. + if ( $estimated_value ) : + printf( + '

%s

', + esc_html__( wc_price( $estimated_value ), 'goodbids' ) + ); + endif; + + // Expected High Bid. + if ( $expected_high_bid ) : + printf( + '

%s

', + esc_html__( wc_price( $expected_high_bid ), 'goodbids' ) + ); + endif; + ?> +
diff --git a/client-mu-plugins/goodbids/blocks/reward-product-gallery/block.php b/client-mu-plugins/goodbids/blocks/reward-product-gallery/block.php index c99c18131..a8b21e439 100644 --- a/client-mu-plugins/goodbids/blocks/reward-product-gallery/block.php +++ b/client-mu-plugins/goodbids/blocks/reward-product-gallery/block.php @@ -1,29 +1,33 @@ auctions->get_post_type() ) ) { + return; + } + + if ( current_theme_supports( 'wc-product-gallery-zoom' ) ) { + wp_enqueue_script( 'zoom' ); + } + if ( current_theme_supports( 'wc-product-gallery-slider' ) ) { + wp_enqueue_script( 'flexslider' ); + } + if ( current_theme_supports( 'wc-product-gallery-lightbox' ) ) { + wp_enqueue_script( 'photoswipe-ui-default' ); + wp_enqueue_style( 'photoswipe-default-skin' ); + add_action( 'wp_footer', 'woocommerce_photoswipe' ); } + wp_enqueue_script( 'wc-single-product' ); } ); diff --git a/client-mu-plugins/goodbids/blocks/reward-product/block.json b/client-mu-plugins/goodbids/blocks/reward-product/block.json deleted file mode 100644 index f668b4b27..000000000 --- a/client-mu-plugins/goodbids/blocks/reward-product/block.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "reward-product", - "title": "Reward Product", - "description": "Displays the reward product information", - "icon": "awards", - "category": "goodbids", - "textdomain": "goodbids", - "keywords": ["custom", "product", "reward", "auction"], - "acf": { - "mode": "preview" - }, - "supports": { - "jsx": false - } -} diff --git a/client-mu-plugins/goodbids/blocks/reward-product/render.php b/client-mu-plugins/goodbids/blocks/reward-product/render.php deleted file mode 100644 index 305d9535d..000000000 --- a/client-mu-plugins/goodbids/blocks/reward-product/render.php +++ /dev/null @@ -1,41 +0,0 @@ -auctions->get_goal(); -$estimated_value = goodbids()->auctions->get_estimated_value(); -$expected_high_bid = goodbids()->auctions->get_expected_high_bid(); -?> -
> - %s

', - esc_html__( $goal, 'goodbids' ) - ); - } - - // Estimated Value - if ( $estimated_value ) { - printf( - '

%s

', - esc_html__( $estimated_value, 'goodbids' ) - ); - } - - // Expected High Bid - if ( $expected_high_bid ) { - printf( - '

%s

', - esc_html__( $expected_high_bid, 'goodbids' ) - ); - } - ?> -
diff --git a/client-mu-plugins/goodbids/src/classes/Auctions/Auctions.php b/client-mu-plugins/goodbids/src/classes/Auctions/Auctions.php index ed288f1d6..185a5ec99 100644 --- a/client-mu-plugins/goodbids/src/classes/Auctions/Auctions.php +++ b/client-mu-plugins/goodbids/src/classes/Auctions/Auctions.php @@ -9,6 +9,7 @@ namespace GoodBids\Auctions; use WC_Order; +use WP_Query; /** * Class for Auctions @@ -53,12 +54,30 @@ class Auctions { */ const GUID_META_KEY = 'gb_guid'; + /** + * @since 1.0.0 + * @var string + */ + const CRON_AUCTION_START_HOOK = 'goodbids_auction_start_event'; + + /** + * @since 1.0.0 + * @var string + */ + const AUCTION_STARTED_META_KEY = '_auction_started'; + /** * @since 1.0.0 * @var Bids */ public Bids $bids; + /** + * @since 1.0.0 + * @var string + */ + public string $cron_interval = '1min'; + /** * Initialize Auctions * @@ -74,12 +93,15 @@ public function __construct() { // Init Rewards Category. $this->init_rewards_category(); - // Add Auction Metrics Meta Box. - $this->add_metrics_meta_box(); + // Add Auction Meta Box to display details and metrics. + $this->add_info_meta_box(); // Add custom Admin Columns for Auctions. $this->add_admin_columns(); + // Configure some values for new Auction posts. + $this->new_auction_post_init(); + // Update Bid Product when Auction is updated. $this->update_bid_product_on_auction_update(); @@ -91,6 +113,15 @@ public function __construct() { // Generate a unique ID for each Auction. $this->generate_guid_on_publish(); + + // Set up 1min Cron Job Schedule. + $this->add_one_min_cron_schedule(); + + // Schedule a cron job to trigger the start of auctions. + $this->schedule_bid_start_cron(); + + // Use cron action to start auctions. + $this->check_for_starting_auctions(); } /** @@ -165,7 +196,7 @@ function () { 'template' => $this->get_template(), ]; - register_post_type( self::POST_TYPE, $args ); + register_post_type( $this->get_post_type(), $args ); } ); } @@ -359,7 +390,13 @@ public function get_estimated_value( int $auction_id = null ): int { * @return string */ public function get_start_date_time( int $auction_id = null ): string { - return $this->get_setting( 'auction_start', $auction_id ); + $start = $this->get_setting( 'auction_start', $auction_id ); + + if ( ! $start ) { + return ''; + } + + return $start; } /** @@ -372,7 +409,13 @@ public function get_start_date_time( int $auction_id = null ): string { * @return string */ public function get_end_date_time( int $auction_id = null ): string { - return $this->get_setting( 'auction_end', $auction_id ); + $end = $this->get_setting( 'auction_end', $auction_id ); + + if ( ! $end ) { + return ''; + } + + return $end; } /** @@ -386,11 +429,33 @@ public function get_end_date_time( int $auction_id = null ): string { */ public function has_started( int $auction_id = null ): bool { $start_date_time = $this->get_start_date_time( $auction_id ); + if ( ! $start_date_time ) { + // TODO: Log error. + return false; + } + + return strtotime( $start_date_time ) < current_datetime()->format( 'U' ); + } + + /** + * Check if an Auction has ended. + * + * @since 1.0.0 + * + * @param ?int $auction_id + * + * @return bool + */ + public function has_ended( int $auction_id = null ): bool { + $end_date_time = $this->get_end_date_time( $auction_id ); + + if ( ! $end_date_time ) { + // TODO: Log error. return false; } - return strtotime( $start_date_time ) < time(); + return strtotime( $end_date_time ) < current_datetime()->format( 'U' ); } /** @@ -463,6 +528,34 @@ public function get_expected_high_bid( int $auction_id = null ): int { return intval( $this->get_setting( 'expected_high_bid', $auction_id ) ); } + /** + * Update Auction with some initial data when created. + * + * @since 1.0.0 + * + * @return void + */ + private function new_auction_post_init(): void { + add_action( + 'wp_after_insert_post', + function ( $post_id ): void { + // Bail if this is a revision. + if ( wp_is_post_revision( $post_id ) || 'publish' !== get_post_status( $post_id ) ) { + return; + } + + // Bail if not an Auction. + if ( $this->get_post_type() !== get_post_type( $post_id ) ) { + return; + } + + // Set started to 0 for easier querying. + update_post_meta( $post_id, self::AUCTION_STARTED_META_KEY, 0 ); + }, + 12 + ); + } + /** * Update the Bid Product when an Auction is updated. * @@ -475,7 +568,7 @@ private function update_bid_product_on_auction_update(): void { 'save_post', function ( int $post_id ) { // Bail if not an Auction and not published. - if ( wp_is_post_revision( $post_id ) || 'publish' !== get_post_status( $post_id ) || self::POST_TYPE !== get_post_type( $post_id ) ) { + if ( wp_is_post_revision( $post_id ) || 'publish' !== get_post_status( $post_id ) || $this->get_post_type() !== get_post_type( $post_id ) ) { return; } @@ -664,13 +757,40 @@ public function get_last_bid( int $auction_id ): ?WC_Order { } /** - * Add a meta box to show Auction metrics. + * Get the status of an Auction. + * + * @since 1.0.0 + * + * @param int $auction_id + * + * @return string + */ + public function get_status( int $auction_id ): string { + if ( 'publish' !== get_post_status( $auction_id ) ) { + return __( 'Draft', 'goodbids' ); + } + + $status = __( 'Upcoming', 'goodbids' ); + + if ( $this->has_started( $auction_id ) ) { + $status = __( 'Live', 'goodbids' ); + } + + if ( $this->has_ended( $auction_id ) ) { + $status = __( 'Closed', 'goodbids' ); + } + + return $status; + } + + /** + * Add a meta box to show Auction metrics and other details. * * @since 1.0.0 * * @return void */ - private function add_metrics_meta_box(): void { + private function add_info_meta_box(): void { add_action( 'current_screen', function (): void { @@ -681,9 +801,9 @@ function (): void { } add_meta_box( - 'goodbids-auction-metrics', - __( 'Auction Metrics', 'goodbids' ), - [ $this, 'metrics_meta_box' ], + 'goodbids-auction-info', + __( 'Auction Info', 'goodbids' ), + [ $this, 'info_meta_box' ], $screen->id, 'side' ); @@ -722,49 +842,31 @@ function ( int $order_id, int $auction_id ) { } /** - * Display the Auction Metrics + * Display the Auction Metrics and other details. * * @since 1.0.0 * * @return void */ - public function metrics_meta_box(): void { + public function info_meta_box(): void { $auction_id = $this->get_auction_id(); - printf( - '

%s
%s

', - esc_html__( 'Total Bids', 'goodbids' ), - esc_html( $this->get_bid_count( $auction_id ) ) - ); + // Display the Auction Details. + include GOODBIDS_PLUGIN_PATH . 'views/admin/auctions/details.php'; - printf( - '

%s
%s

', - esc_html__( 'Total Raised', 'goodbids' ), - wp_kses_post( wc_price( $this->get_total_raised( $auction_id ) ) ) - ); + echo '
'; - $bid_product = wc_get_product( $this->get_bid_product_id( $auction_id ) ); - - if ( $bid_product ) : - printf( - '

%s
%s

', - esc_html__( 'Current Bid', 'goodbids' ), - wp_kses_post( wc_price( $bid_product->get_price() ) ) - ); - endif; - - $last_bid = $this->get_last_bid( $auction_id ); - - if ( $last_bid ) { - printf( - '

%s
%s

', - esc_html__( 'Last Bid', 'goodbids' ), - esc_url( get_edit_post_link( $last_bid->get_id() ) ), - wp_kses_post( wc_price( $last_bid->get_total() ) ) - ); - } + // Display the Auction Metrics. + include GOODBIDS_PLUGIN_PATH . 'views/admin/auctions/metrics.php'; } + /** + * Insert custom metrics admin columns + * + * @since 1.0.0 + * + * @return void + */ private function add_admin_columns(): void { add_filter( 'manage_' . $this->get_post_type() . '_posts_columns', @@ -776,6 +878,7 @@ function ( array $columns ): array { // Insert Custom Columns after the Title column. if ( 'title' === $column ) { + $new_columns['status'] = __( 'Status', 'goodbids' ); $new_columns['starting_bid'] = __( 'Starting Bid', 'goodbids' ); $new_columns['bid_increment'] = __( 'Bid Increment', 'goodbids' ); $new_columns['total_bids'] = __( 'Total Bids', 'goodbids' ); @@ -792,7 +895,8 @@ function ( array $columns ): array { add_action( 'manage_' . $this->get_post_type() . '_posts_custom_column', function ( $column, $post_id ) { - $bid_cols = [ + // Columns that require a "published" status. + $published_cols = [ 'starting_bid', 'bid_increment', 'total_bids', @@ -802,13 +906,15 @@ function ( $column, $post_id ) { ]; // Bail early if Auction isn't published. - if ( in_array( $column, $bid_cols, true ) && 'publish' !== get_post_status( $post_id ) ) { + if ( in_array( $column, $published_cols, true ) && 'publish' !== get_post_status( $post_id ) ) { echo '—'; return; } // Output the column values. - if ( 'starting_bid' === $column ) { + if ( 'status' === $column ) { + echo esc_html( $this->get_status( $post_id ) ); + } elseif ( 'starting_bid' === $column ) { echo wp_kses_post( wc_price( $this->calculate_starting_bid( $post_id ) ) ); } elseif ( 'bid_increment' === $column ) { echo wp_kses_post( wc_price( $this->get_bid_increment( $post_id ) ) ); @@ -844,12 +950,10 @@ function ( string $html, int $post_id ) { return $html; } - $reward_id = goodbids()->auctions->get_reward_product_id( $post_id ); - $product = wc_get_product( $reward_id ); - $image_html = $product->get_image(); - return sprintf( - $image_html, - ); + $reward_id = $this->get_reward_product_id( $post_id ); + $product = wc_get_product( $reward_id ); + + return $product->get_image(); }, 10, 2 @@ -903,4 +1007,114 @@ function ( $post_id ): void { } ); } + + /** + * Add a 1min Cron Job Schedule. + * + * @since 1.0.0 + * + * @return void + */ + private function add_one_min_cron_schedule(): void { + add_filter( + 'cron_schedules', // phpcs:ignore + function ( array $schedules ): array { + // If one is already set, confirm it matches our schedule. + if ( ! empty( $schedules[ $this->cron_interval ] ) ) { + if ( MINUTE_IN_SECONDS === $schedules[ $this->cron_interval ]['interval'] ) { + return $schedules; + } + + $this->cron_interval = '1minute'; + } + + // Adds every minute cron schedule. + $schedules[ $this->cron_interval ] = [ + 'interval' => MINUTE_IN_SECONDS, + 'display' => __( 'Once Every Minute', 'goodbids' ), + ]; + + return $schedules; + } + ); + } + + /** + * Schedule a cron job that runs every minute to trigger auctions to start. + * + * @since 1.0.0 + * + * @return void + */ + private function schedule_bid_start_cron(): void { + add_action( + 'init', + function (): void { + if ( wp_next_scheduled( self::CRON_AUCTION_START_HOOK ) ) { + return; + } + + wp_schedule_event( current_datetime()->format( 'U' ), $this->cron_interval, self::CRON_AUCTION_START_HOOK ); + } + ); + } + + /** + * Check for Auctions that are starting during cron hook. + * + * @since 1.0.0 + * + * @return void + */ + private function check_for_starting_auctions(): void { + add_action( + self::CRON_AUCTION_START_HOOK, + function (): void { + $auctions = $this->get_starting_auctions(); + + if ( ! $auctions->have_posts() ) { + return; + } + + foreach ( $auctions->posts as $auction_id ) { + // TODO: Tell Node first. + // TODO: Wrap in condition based on Node API response. + // Update the Auction meta to indicate it has started. + update_post_meta( $auction_id, self::AUCTION_STARTED_META_KEY, 1 ); + } + } + ); + } + + /** + * Get Auctions that are starting. + * + * @since 1.0.0 + * + * @return WP_Query + */ + private function get_starting_auctions(): WP_Query { + // Use the current time + 1min to get Auctions about to start. + $auction_start = current_datetime()->add( new \DateInterval( 'PT1M' ) )->format( 'Y-m-d H:i:s' ); + + $args = [ + 'post_type' => $this->get_post_type(), + 'post_status' => 'publish', + 'posts_per_page' => -1, + 'return' => 'ids', + 'meta_query' => [ + [ + 'key' => 'auction_start', + 'value' => $auction_start, + 'compare' => '<=', + ], + [ + 'key' => self::AUCTION_STARTED_META_KEY, + 'value' => 0, + ], + ], + ]; + + return new WP_Query( $args ); + } } diff --git a/client-mu-plugins/goodbids/src/classes/Auctions/Bids.php b/client-mu-plugins/goodbids/src/classes/Auctions/Bids.php index b0150c3f9..067583cb4 100644 --- a/client-mu-plugins/goodbids/src/classes/Auctions/Bids.php +++ b/client-mu-plugins/goodbids/src/classes/Auctions/Bids.php @@ -57,7 +57,7 @@ function ( $post_id ): void { } // Bail if not an Auction. - if ( Auctions::POST_TYPE !== get_post_type( $post_id ) ) { + if ( goodbids()->auctions->get_post_type() !== get_post_type( $post_id ) ) { return; } diff --git a/client-mu-plugins/goodbids/src/classes/Network/Sites.php b/client-mu-plugins/goodbids/src/classes/Network/Sites.php index 20fd3d838..ac9929eee 100644 --- a/client-mu-plugins/goodbids/src/classes/Network/Sites.php +++ b/client-mu-plugins/goodbids/src/classes/Network/Sites.php @@ -422,7 +422,7 @@ function ( string $html, int $blog_id ) { } /** - * Set the archive to show nine posts per pagination + * Set the archive default to show 9 posts per page * * @since 1.0.0 * @@ -432,10 +432,7 @@ private function set_default_posts_per_page(): void { add_action( 'goodbids_init_site', function ( int $site_id ): void { - update_option( - 'posts_per_page', - 9 - ); + update_option( 'posts_per_page', 9 ); } ); } diff --git a/client-mu-plugins/goodbids/views/admin/auctions/details.php b/client-mu-plugins/goodbids/views/admin/auctions/details.php new file mode 100644 index 000000000..7f41c4013 --- /dev/null +++ b/client-mu-plugins/goodbids/views/admin/auctions/details.php @@ -0,0 +1,27 @@ + +

+ +get_start_date_time(); + +printf( + '

%s
%s%s

', + esc_html__( 'Status', 'goodbids' ), + esc_html( $this->get_status( $auction_id ) ), + + $start_time ? sprintf( + ' (%s)', + esc_html( human_time_diff( current_datetime()->format( 'U' ), strtotime( $start_time ) ) ) + ) : '' +); diff --git a/client-mu-plugins/goodbids/views/admin/auctions/metrics.php b/client-mu-plugins/goodbids/views/admin/auctions/metrics.php new file mode 100644 index 000000000..2a7f1d003 --- /dev/null +++ b/client-mu-plugins/goodbids/views/admin/auctions/metrics.php @@ -0,0 +1,48 @@ +get_bid_product_id( $auction_id ) ); +$last_bid = $this->get_last_bid( $auction_id ); +?> +
+

+ + %s
%s

', + esc_html__( 'Total Bids', 'goodbids' ), + esc_html( $this->get_bid_count( $auction_id ) ) + ); + + printf( + '

%s
%s

', + esc_html__( 'Total Raised', 'goodbids' ), + wp_kses_post( wc_price( $this->get_total_raised( $auction_id ) ) ) + ); + + if ( $bid_product ) : + printf( + '

%s
%s

', + esc_html__( 'Current Bid', 'goodbids' ), + wp_kses_post( wc_price( $bid_product->get_price() ) ) + ); + endif; + + if ( $last_bid ) : + printf( + '

%s
%s

', + esc_html__( 'Last Bid', 'goodbids' ), + esc_url( get_edit_post_link( $last_bid->get_id() ) ), + wp_kses_post( wc_price( $last_bid->get_total() ) ) + ); + endif; + ?> +
diff --git a/client-mu-plugins/goodbids/views/patterns/template-archive-auction.php b/client-mu-plugins/goodbids/views/patterns/template-archive-auction.php index 309f5c201..788f4ed66 100644 --- a/client-mu-plugins/goodbids/views/patterns/template-archive-auction.php +++ b/client-mu-plugins/goodbids/views/patterns/template-archive-auction.php @@ -5,8 +5,8 @@ * @since 1.0.0 * @package GoodBids */ -?> +?>
diff --git a/client-mu-plugins/goodbids/views/patterns/template-auction.php b/client-mu-plugins/goodbids/views/patterns/template-auction.php index 9cf1c5305..37c2f8ac8 100644 --- a/client-mu-plugins/goodbids/views/patterns/template-auction.php +++ b/client-mu-plugins/goodbids/views/patterns/template-auction.php @@ -9,7 +9,6 @@ */ ?> -
@@ -28,7 +27,7 @@ - + diff --git a/docs/hooks/actions.md b/docs/hooks/actions.md index 31e0c19c6..92186fe21 100644 --- a/docs/hooks/actions.md +++ b/docs/hooks/actions.md @@ -26,3 +26,17 @@ add_action( } ); ``` +### goodbids_auction_start_event + +Cron event, fired every minute. + +> **Be EXTREMELY CAUTIOUS using this hook.** + +```php +add_action( + 'goodbids_auction_start_event', + function (): void { + // Are you sure you want to use this hook? + } +); +```