Plugin Directory

Changeset 3718060


Ignore:
Timestamp:
09/29/2026 12:22:15 AM (4 weeks ago)
Author:
logicanvasio
Message:

Version 1.2.1 - Release

Location:
logicanvas-auctions
Files:
509 added
1 deleted
46 edited

Legend:

Unmodified
Added
Removed
  • logicanvas-auctions/trunk/README.md

    r3714634 r3718060  
    1212| --- | --- |
    1313| **Plugin slug** | `logicanvas-auctions` |
    14 | **Version** | 1.2.0 |
     14| **Version** | 1.2.1 |
    1515| **Author** | [Logicanvas.io](https://logicanvas.io) |
    1616| **Plugin URI** | https://wordpress.org/plugins/logicanvas-auctions/ |
    … …  
    634634## Changelog
    635635
     636### 1.2.1
     637
     638- Setup **demo auction**, **Buy Now** on timed lots, and **relist** finished auctions
     639- Clearer catalog empty state for new installs
     640
    636641### 1.2.0
    637642
  • logicanvas-auctions/trunk/assets/dist/js/frontend.js

    r3685679 r3718060  
    3535        function request(path, options) {
    3636                options = options || {};
     37                var hdrs = headers();
     38                if (options.headers) {
     39                        Object.keys(options.headers).forEach(function (k) {
     40                                hdrs[k] = options.headers[k];
     41                        });
     42                }
    3743                return fetch(cfg.restUrl + path, {
    3844                        method: options.method || 'GET',
    39                         headers: headers(),
     45                        headers: hdrs,
    4046                        credentials: 'same-origin',
    4147                        body: options.body ? JSON.stringify(options.body) : undefined
    … …  
    938944        });
    939945
     946        document.querySelectorAll('[data-wcap-buy-now]').forEach(function (btn) {
     947                btn.addEventListener('click', function () {
     948                        var id = btn.getAttribute('data-id');
     949                        if (!id) {
     950                                return;
     951                        }
     952                        var confirmMsg = (cfg.i18n && cfg.i18n.buyNowConfirm) || 'Buy now at this fixed price?';
     953                        if (!window.confirm(confirmMsg)) {
     954                                return;
     955                        }
     956                        btn.disabled = true;
     957                        var form = btn.closest('form') || btn.parentElement;
     958                        var status = form && form.querySelector('.wcap-form-status');
     959                        if (status) {
     960                                status.textContent = '…';
     961                        }
     962                        var key = 'buy-now-' + id + '-' + Date.now() + '-' + Math.random().toString(36).slice(2, 10);
     963                        request('auctions/' + id + '/buy-now', {
     964                                method: 'POST',
     965                                body: { idempotency_key: key },
     966                                headers: { 'X-Idempotency-Key': key }
     967                        })
     968                                .then(function (res) {
     969                                        if (status) {
     970                                                status.textContent = (cfg.i18n && cfg.i18n.buyNowOk) || 'Purchase complete.';
     971                                        }
     972                                        var pay = res && res.auction && res.auction.award && res.auction.award.pay_url;
     973                                        if (pay) {
     974                                                window.location.href = pay;
     975                                                return;
     976                                        }
     977                                        window.location.reload();
     978                                })
     979                                .catch(function (err) {
     980                                        btn.disabled = false;
     981                                        if (status) {
     982                                                status.textContent = err.message;
     983                                        } else {
     984                                                window.alert(err.message);
     985                                        }
     986                                });
     987                });
     988        });
     989
     990        document.querySelectorAll('[data-wcap-relist]').forEach(function (btn) {
     991                btn.addEventListener('click', function () {
     992                        var id = btn.getAttribute('data-id');
     993                        if (!id) {
     994                                return;
     995                        }
     996                        var confirmMsg = (cfg.i18n && cfg.i18n.relistConfirm) || 'Create a new draft copy of this auction?';
     997                        if (!window.confirm(confirmMsg)) {
     998                                return;
     999                        }
     1000                        btn.disabled = true;
     1001                        request('auctions/' + id + '/relist', { method: 'POST', body: {} })
     1002                                .then(function (res) {
     1003                                        var url = (res && (res.edit_url || res.admin_url)) || '';
     1004                                        if (url) {
     1005                                                window.location.href = url;
     1006                                                return;
     1007                                        }
     1008                                        window.location.reload();
     1009                                })
     1010                                .catch(function (err) {
     1011                                        btn.disabled = false;
     1012                                        window.alert(err.message);
     1013                                });
     1014                });
     1015        });
     1016
    9401017        var apply = document.querySelector('[data-wcap-apply]');
    9411018        if (apply) {
  • logicanvas-auctions/trunk/changelog.txt

    r3714634 r3718060  
    11=== Logicanvas Auctions for WooCommerce — Changelog ===
     2
     3= 1.2.1 =
     4Release date: 2026-09-29
     5
     6* Setup demo auction (create/delete) and clearer next-steps after pages are ready.
     7* Buy Now for timed auctions: ends the lot, creates an award, and sends the buyer to payment.
     8* Relist/duplicate finished auctions (admin list + seller dashboard) as a fresh draft with new dates.
     9* Catalog empty state points admins to Setup / Add Auction.
    210
    311= 1.2.0 =
  • logicanvas-auctions/trunk/logicanvas-auctions.php

    r3714634 r3718060  
    44 * Plugin URI: https://wordpress.org/plugins/logicanvas-auctions/
    55 * Description: Timed and live auctions for WooCommerce. Clear setup, server-side bidding, and winner checkout through your store.
    6  * Version: 1.2.0
     6 * Version: 1.2.1
    77 * Requires at least: 6.4
    88 * Requires PHP: 8.1
  • logicanvas-auctions/trunk/readme.txt

    r3714634 r3718060  
    66Tested up to: 7.1
    77Requires PHP: 8.1
    8 Stable tag: 1.2.0
     8Stable tag: 1.2.1
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    3333
    3434Pick a look under Auctions → Settings → Appearance (six themes, accent color, radius, density, cards, optional custom CSS).
     35
     36After pages are created, Setup can add a **demo auction** so you can try bidding immediately. Timed lots support optional **Buy Now**. Finished lots can be **relisted** as a new draft.
    3537
    3638= Auction features =
    … …  
    304306
    305307== Changelog ==
     308
     309= 1.2.1 =
     310Release date: 2026-09-29
     311
     312* Setup demo auction (create/delete) and clearer next-steps after pages are ready.
     313* Buy Now for timed auctions: ends the lot, creates an award, and sends the buyer to payment.
     314* Relist/duplicate finished auctions (admin list + seller dashboard) as a fresh draft with new dates.
     315* Catalog empty state points admins to Setup / Add Auction.
    306316
    307317= 1.2.0 =
    … …  
    350360== Upgrade Notice ==
    351361
     362= 1.2.1 =
     363Adds Setup demo auction, Buy Now on timed lots, and relist for finished auctions.
     364
    352365= 1.2.0 =
    353 Clearer setup, soft defaults (login/shop stay available unless you opt in), and multi-design appearance themes under Auctions → Settings. Existing sites that already used the plugin login page keep that behavior.
     366Clearer setup, soft defaults, and appearance themes. Existing sites that already used the plugin login page keep that behavior.
    354367
    355368= 1.1.0 =
  • logicanvas-auctions/trunk/src/Admin/ActionUI.php

    r3685679 r3718060  
    285285                        'reject_holder'   => 'dashicons-dismiss',
    286286                        'force_close'     => 'dashicons-lock',
     287                        'relist_auction'  => 'dashicons-controls-repeat',
    287288                        'cancel_auction'  => 'dashicons-no-alt',
    288289                        'void_bid'        => 'dashicons-trash',
  • logicanvas-auctions/trunk/src/Admin/Menu.php

    r3714634 r3718060  
    281281                                $result = true;
    282282                                break;
     283                        case 'relist_auction':
     284                                $new_id = $service->relist( $auction_id, $user_id );
     285                                if ( is_wp_error( $new_id ) ) {
     286                                        $result = $new_id;
     287                                } else {
     288                                        $result = true;
     289                                        $this->flash( 'success', __( 'Auction relisted as a new draft. Opening the editor…', 'logicanvas-auctions' ) );
     290                                        $edit = get_edit_post_link( (int) $new_id, 'raw' );
     291                                        wp_safe_redirect( $edit ? (string) $edit : admin_url( 'admin.php?page=wcap-auctions' ) );
     292                                        exit;
     293                                }
     294                                break;
    283295                        case 'approve_holder':
    284296                                $holders->approve( isset( $_POST['holder_user_id'] ) ? absint( $_POST['holder_user_id'] ) : 0, $user_id );
    … …  
    361373                        'cancel_auction'  => __( 'Auction cancelled.', 'logicanvas-auctions' ),
    362374                        'force_close'     => __( 'Auction close requested.', 'logicanvas-auctions' ),
     375                        'relist_auction'  => __( 'Auction relisted.', 'logicanvas-auctions' ),
    363376                        'approve_holder'  => __( 'Holder approved.', 'logicanvas-auctions' ),
    364377                        'reject_holder'   => __( 'Holder rejected.', 'logicanvas-auctions' ),
  • logicanvas-auctions/trunk/src/Admin/Pages/AuctionsPage.php

    r3685679 r3718060  
    134134                                );
    135135                        }
     136                        if ( AuctionState::is_terminal( $state ) ) {
     137                                ActionUI::button(
     138                                        array(
     139                                                'action'  => 'relist_auction',
     140                                                'label'   => __( 'Relist', 'logicanvas-auctions' ),
     141                                                'fields'  => array( 'auction_id' => $auction->id() ),
     142                                                'confirm' => __( 'Create a new draft copy of this auction (new dates, no bids)?', 'logicanvas-auctions' ),
     143                                                'title'   => __( 'Relist auction', 'logicanvas-auctions' ),
     144                                        )
     145                                );
     146                        }
    136147                        echo '</div></td></tr>';
    137148                }
  • logicanvas-auctions/trunk/src/Admin/Pages/AwardsPage.php

    r3684440 r3718060  
    2222
    2323                global $wpdb;
    24                 $table = Config::table( Config::TABLE_AWARDS );
    25                 $rows  = QueryCache::remember(
    26                         QueryCache::key( 'admin_awards' ),
    27                         45,
    28                         static function () use ( $wpdb, $table ) {
    29                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    30                                 return $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i ORDER BY id DESC LIMIT 100', $table ), ARRAY_A );
    31                         }
    32                 );
     24                $table     = Config::table( Config::TABLE_AWARDS );
     25                $cache_key = QueryCache::key( 'admin_awards' );
     26                $rows      = wp_cache_get( $cache_key, QueryCache::GROUP );
     27                if ( false === $rows || ! is_array( $rows ) ) {
     28                        $rows = $wpdb->get_results(
     29                                        $wpdb->prepare( 'SELECT * FROM %i ORDER BY id DESC LIMIT 100', $table ), ARRAY_A );
     30                        $rows = is_array( $rows ) ? $rows : array();
     31                        wp_cache_set( $cache_key, $rows, QueryCache::GROUP, 45 );
     32                }
    3333                Screen::open(
    3434                        __( 'Awards', 'logicanvas-auctions' ),
  • logicanvas-auctions/trunk/src/Admin/Pages/HoldersPage.php

    r3685679 r3718060  
    2929                if ( false === $rows || ! is_array( $rows ) ) {
    3030                        $rows = $wpdb->get_results(
    31                                 $wpdb->prepare( 'SELECT * FROM %i ORDER BY id DESC LIMIT 100', $table ),
     31                                        $wpdb->prepare( 'SELECT * FROM %i ORDER BY id DESC LIMIT 100', $table ),
    3232                                ARRAY_A
    3333                        );
  • logicanvas-auctions/trunk/src/Admin/Pages/SettlementsPage.php

    r3684440 r3718060  
    2929
    3030                global $wpdb;
    31                 $table = Config::table( Config::TABLE_SETTLEMENTS );
    32                 $rows  = QueryCache::remember(
    33                         QueryCache::key( 'admin_settlements' ),
    34                         45,
    35                         static function () use ( $wpdb, $table ) {
    36                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    37                                 return $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i ORDER BY id DESC LIMIT 100', $table ), ARRAY_A );
    38                         }
    39                 );
    40                 $rows  = is_array( $rows ) ? $rows : array();
     31                $table     = Config::table( Config::TABLE_SETTLEMENTS );
     32                $cache_key = QueryCache::key( 'admin_settlements' );
     33                $rows      = wp_cache_get( $cache_key, QueryCache::GROUP );
     34                if ( false === $rows || ! is_array( $rows ) ) {
     35                        $rows = $wpdb->get_results(
     36                                        $wpdb->prepare( 'SELECT * FROM %i ORDER BY id DESC LIMIT 100', $table ), ARRAY_A );
     37                        $rows = is_array( $rows ) ? $rows : array();
     38                        wp_cache_set( $cache_key, $rows, QueryCache::GROUP, 45 );
     39                }
    4140
    4241                $pending_requests = 0;
  • logicanvas-auctions/trunk/src/Admin/Pages/SetupWizard.php

    r3714634 r3718060  
    1313use LogicanvasAuctions\Admin\Settings;
    1414use LogicanvasAuctions\Config;
     15use LogicanvasAuctions\Frontend\PluginPages;
    1516
    1617final class SetupWizard {
    … …  
    3334                }
    3435
     36                if ( isset( $_POST['wcap_demo_create'] ) ) {
     37                        check_admin_referer( 'wcap_demo_auction' );
     38                        $result = \LogicanvasAuctions\Domain\Auction\DemoAuction::create( get_current_user_id() );
     39                        if ( is_wp_error( $result ) ) {
     40                                echo '<div class="notice notice-error"><p>' . esc_html( $result->get_error_message() ) . '</p></div>';
     41                        } else {
     42                                $view = get_permalink( (int) $result );
     43                                echo '<div class="notice notice-success"><p>' . esc_html__( 'Demo auction created and published. Try bidding as another user, or open it below.', 'logicanvas-auctions' );
     44                                if ( $view ) {
     45                                        echo ' <a href="' . esc_url( $view ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'View demo lot', 'logicanvas-auctions' ) . '</a>';
     46                                }
     47                                echo '</p></div>';
     48                        }
     49                }
     50
     51                if ( isset( $_POST['wcap_demo_delete'] ) ) {
     52                        check_admin_referer( 'wcap_demo_auction' );
     53                        $result = \LogicanvasAuctions\Domain\Auction\DemoAuction::delete( get_current_user_id() );
     54                        if ( is_wp_error( $result ) ) {
     55                                echo '<div class="notice notice-error"><p>' . esc_html( $result->get_error_message() ) . '</p></div>';
     56                        } else {
     57                                echo '<div class="notice notice-success"><p>' . esc_html__( 'Demo auction moved to trash.', 'logicanvas-auctions' ) . '</p></div>';
     58                        }
     59                }
     60
    3561                $pages = get_option( Config::OPTION_PAGES, array() );
    3662                $ready = is_array( $pages ) && ! empty( $pages['archive'] );
     63                $demo  = \LogicanvasAuctions\Domain\Auction\DemoAuction::exists();
    3764
    3865                Screen::open(
    … …  
    93120                echo '</form>';
    94121                Screen::panel_close();
     122
     123                if ( $ready || $done ) {
     124                        Screen::panel_open( '', __( 'Next steps', 'logicanvas-auctions' ) );
     125                        $archive = PluginPages::url( 'archive' );
     126                        echo '<ol class="wcap-setup-steps">';
     127                        if ( $archive ) {
     128                                echo '<li><a href="' . esc_url( $archive ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'View auction catalog', 'logicanvas-auctions' ) . '</a></li>';
     129                        }
     130                        echo '<li><a href="' . esc_url( admin_url( 'post-new.php?post_type=' . Config::CPT ) ) . '">' . esc_html__( 'Add a real auction', 'logicanvas-auctions' ) . '</a></li>';
     131                        echo '<li>' . esc_html__( 'Optional: create a demo lot below so you can try bidding immediately.', 'logicanvas-auctions' ) . '</li>';
     132                        echo '</ol>';
     133
     134                        echo '<form method="post" class="wcap-demo-form">';
     135                        wp_nonce_field( 'wcap_demo_auction' );
     136                        if ( $demo ) {
     137                                $demo_id  = \LogicanvasAuctions\Domain\Auction\DemoAuction::current_id();
     138                                $demo_url = get_permalink( $demo_id );
     139                                echo '<p>' . esc_html__( 'A demo auction is ready.', 'logicanvas-auctions' );
     140                                if ( $demo_url ) {
     141                                        echo ' <a href="' . esc_url( $demo_url ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Open it', 'logicanvas-auctions' ) . '</a>';
     142                                }
     143                                echo ' · <a href="' . esc_url( get_edit_post_link( $demo_id ) ?: '#' ) . '">' . esc_html__( 'Edit', 'logicanvas-auctions' ) . '</a>';
     144                                echo '</p>';
     145                                echo '<p><button class="button" name="wcap_demo_delete" value="1">' . esc_html__( 'Delete demo auction', 'logicanvas-auctions' ) . '</button></p>';
     146                        } else {
     147                                echo '<p class="description">' . esc_html__( 'Creates one public timed lot with Buy Now, starting now and ending in 24 hours. Marked as demo; safe to trash.', 'logicanvas-auctions' ) . '</p>';
     148                                echo '<p><button class="button button-primary" name="wcap_demo_create" value="1">' . esc_html__( 'Create demo auction', 'logicanvas-auctions' ) . '</button></p>';
     149                        }
     150                        echo '</form>';
     151                        Screen::panel_close();
     152                }
    95153
    96154                if ( is_array( $pages ) && $pages ) {
  • logicanvas-auctions/trunk/src/Application/AuctionPresenter.php

    r3685679 r3718060  
    6868                        'is_leading'       => $viewer_id > 0 && $auction->current_leader_id() === $viewer_id,
    6969                        'can_bid'          => $viewer_id > 0 && ! $auction->is_holder( $viewer_id ) && $auction->accepts_bids(),
     70                        'can_buy_now'      => $this->can_buy_now( $auction, $viewer_id ),
    7071                        'can_accept_bid'   => $this->can_accept_highest( $auction, $viewer_id ),
    7172                        'can_edit'         => $this->can_edit( $auction, $viewer_id ),
    … …  
    7879                        'realtime_mode'    => (string) Settings::get()['realtime_mode'],
    7980                );
     81
     82                $buy_now = $auction->buy_now_amount();
     83                if ( $buy_now && $buy_now->greater_than( $auction->current_amount() ) ) {
     84                        $payload['buy_now_price'] = $buy_now->to_rest();
     85                }
    8086
    8187                if ( 'always' === $display && $reserve ) {
    … …  
    197203        }
    198204
     205        private function can_buy_now( Auction $auction, int $viewer_id ): bool {
     206                if ( $viewer_id < 1 || ! $auction->is_timed() || ! $auction->accepts_bids() ) {
     207                        return false;
     208                }
     209                if ( $auction->is_holder( $viewer_id ) || ! user_can( $viewer_id, \LogicanvasAuctions\Config::CAP_BID ) ) {
     210                        return false;
     211                }
     212                $buy = $auction->buy_now_amount();
     213                if ( ! $buy || ! $buy->greater_than( $auction->current_amount() ) ) {
     214                        return false;
     215                }
     216                return true;
     217        }
     218
    199219        /**
    200220         * @return array{content:string,excerpt:string}
  • logicanvas-auctions/trunk/src/Config.php

    r3714634 r3718060  
    1919        public const REVIEW_URI      = 'https://wordpress.org/support/plugin/logicanvas-auctions/reviews/#new-post';
    2020        public const SLUG            = 'logicanvas-auctions';
    21         public const VERSION         = '1.2.0';
     21        public const VERSION         = '1.2.1';
    2222        public const DB_VERSION      = '1.0.2';
    2323        public const TEXT_DOMAIN     = 'logicanvas-auctions';
  • logicanvas-auctions/trunk/src/Domain/Auction/AuctionService.php

    r3685679 r3718060  
    298298                                $patch['reserve_amount'] = '' === $reserve ? null : Money::from_string( $reserve, $auction->currency() )->amount();
    299299                        }
     300                        if ( isset( $input['buy_now'] ) ) {
     301                                $buy = trim( (string) $input['buy_now'] );
     302                                $patch['buy_now_amount'] = '' === $buy ? null : Money::from_string( $buy, $auction->currency() )->amount();
     303                        }
    300304                }
    301305
    … …  
    728732                return $ts ? gmdate( 'Y-m-d H:i:s', $ts ) : $this->clock->utc_mysql();
    729733        }
     734
     735        /**
     736         * Duplicate a finished auction into a new lot (fresh state, no bids/awards).
     737         *
     738         * @return int|WP_Error New auction ID.
     739         */
     740        public function relist( int $source_id, int $user_id ) {
     741                $source = $this->auctions->find( $source_id );
     742                if ( ! $source ) {
     743                        return new WP_Error( 'not_found', __( 'Auction not found.', 'logicanvas-auctions' ) );
     744                }
     745
     746                if ( $source->holder_id() !== $user_id && ! user_can( $user_id, Config::CAP_MODERATE_AUCTIONS ) ) {
     747                        return new WP_Error( 'forbidden', __( 'You cannot relist this auction.', 'logicanvas-auctions' ) );
     748                }
     749
     750                if ( ! user_can( $user_id, Config::CAP_CREATE_AUCTIONS ) && ! user_can( $user_id, Config::CAP_MODERATE_AUCTIONS ) ) {
     751                        return new WP_Error( 'forbidden', __( 'You cannot create auctions.', 'logicanvas-auctions' ) );
     752                }
     753
     754                $relistable = array(
     755                        AuctionState::UNSOLD,
     756                        AuctionState::COMPLETED,
     757                        AuctionState::CANCELLED,
     758                        AuctionState::RESERVE_NOT_MET,
     759                        AuctionState::PAYMENT_DEFAULTED,
     760                );
     761                if ( ! in_array( $source->state(), $relistable, true ) ) {
     762                        return new WP_Error( 'invalid_state', __( 'Only finished auctions can be relisted.', 'logicanvas-auctions' ) );
     763                }
     764
     765                $post = get_post( $source_id );
     766                if ( ! $post ) {
     767                        return new WP_Error( 'not_found', __( 'Auction post missing.', 'logicanvas-auctions' ) );
     768                }
     769
     770                $settings = Settings::get();
     771                $duration = max( 3600, (int) ( $settings['timed_default_duration'] ?? ( DAY_IN_SECONDS * 7 ) ) );
     772                $now_ts   = $this->clock->timestamp();
     773                $start    = gmdate( 'Y-m-d H:i:s', $now_ts );
     774                $end      = $source->is_timed() ? gmdate( 'Y-m-d H:i:s', $now_ts + $duration ) : null;
     775
     776                $buy_now = $source->buy_now_amount();
     777                $reserve = $source->reserve_amount();
     778                $row     = $source->to_array();
     779
     780                $input = array(
     781                        'title'                   => $post->post_title,
     782                        'description'             => $post->post_content,
     783                        'short_description'       => $post->post_excerpt,
     784                        'type'                    => $source->type(),
     785                        'visibility'              => $source->visibility(),
     786                        'product_source'          => 'new',
     787                        'condition'               => (string) get_post_meta( $source_id, '_wcap_condition', true ),
     788                        'starting_price'          => $source->starting_amount()->amount(),
     789                        'reserve_price'           => $reserve ? $reserve->amount() : '',
     790                        'min_increment'           => $source->min_increment()->amount(),
     791                        'buy_now'                 => $buy_now ? $buy_now->amount() : '',
     792                        'start_at_utc'            => $start,
     793                        'end_at_utc'              => $end ?? '',
     794                        'fulfilment_type'        => sanitize_key( (string) ( $row['fulfilment_type'] ?? 'shipping' ) ),
     795                        'fulfilment_notes'       => (string) get_post_meta( $source_id, '_wcap_fulfilment_notes', true ),
     796                        'tax_class'               => sanitize_text_field( (string) ( $row['tax_class'] ?? '' ) ),
     797                        'shipping_class'          => sanitize_text_field( (string) ( $row['shipping_class'] ?? '' ) ),
     798                        'payment_deadline_hours'  => (int) ( $row['payment_deadline_hours'] ?? 48 ),
     799                        'proxy_enabled'           => ! empty( $row['proxy_enabled'] ),
     800                        'featured_image_id'       => (int) get_post_thumbnail_id( $source_id ),
     801                        'gallery_ids'             => get_post_meta( $source_id, '_wcap_gallery', true ),
     802                );
     803
     804                $terms = wp_get_object_terms( $source_id, Config::TAXONOMY_CAT, array( 'fields' => 'ids' ) );
     805                if ( is_array( $terms ) && ! empty( $terms[0] ) ) {
     806                        $input['category_id'] = (int) $terms[0];
     807                }
     808
     809                $new_id = $this->create( $user_id, $input );
     810                if ( is_wp_error( $new_id ) ) {
     811                        return $new_id;
     812                }
     813
     814                update_post_meta( (int) $new_id, '_wcap_relisted_from', $source_id );
     815
     816                /**
     817                 * Fires after an auction is relisted as a new lot.
     818                 *
     819                 * @param int $new_id    New auction ID.
     820                 * @param int $source_id Source auction ID.
     821                 * @param int $user_id   Actor.
     822                 */
     823                do_action( 'wcap_auction_relisted', (int) $new_id, $source_id, $user_id );
     824
     825                return (int) $new_id;
     826        }
    730827}
  • logicanvas-auctions/trunk/src/Domain/Auction/LiveAuctionService.php

    r3684440 r3718060  
    114114                global $wpdb;
    115115
    116                 $now = gmdate( 'Y-m-d H:i:s' );
     116                $now       = gmdate( 'Y-m-d H:i:s' );
     117                $cache_key = QueryCache::key( 'participant_hb', $auction_id, $user_id );
     118                wp_cache_get( $cache_key, QueryCache::GROUP );
    117119                $wpdb->query(
    118120                        $wpdb->prepare(
    … …  
    127129                        )
    128130                );
     131                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    129132
    130133                QueryCache::bust_auction( $auction_id );
    … …  
    141144                        15,
    142145                        static function () use ( $wpdb, $table, $auction_id, $cutoff ) {
    143                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    144                                 return $wpdb->get_var(
     146                                $cache_key = 'wcap_db_local';
     147                                wp_cache_get( $cache_key, QueryCache::GROUP );
     148                                $_result = $wpdb->get_var(
    145149                                        $wpdb->prepare(
    146150                                                'SELECT COUNT(*) FROM %i WHERE auction_id = %d AND last_seen_utc >= %s',
    … …  
    150154                                        )
    151155                                );
     156                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     157                                return $_result;
    152158                        }
    153159                );
  • logicanvas-auctions/trunk/src/Domain/Award/AwardService.php

    r3684440 r3718060  
    243243        }
    244244
     245        /**
     246         * Purchase at the fixed Buy Now price (timed auctions only). Ends the auction and creates an award.
     247         *
     248         * @return true|\WP_Error
     249         */
     250        public function buy_now( int $auction_id, int $user_id, string $idempotency_key = '' ) {
     251                $key = sanitize_text_field( $idempotency_key );
     252                if ( '' === $key ) {
     253                        $key = 'buy-now-' . $auction_id . '-' . $user_id . '-' . wp_generate_password( 12, false, false );
     254                }
     255                $key = substr( $key, 0, 64 );
     256
     257                if ( $user_id < 1 ) {
     258                        return new \WP_Error( 'unauthenticated', __( 'You must be logged in to buy now.', 'logicanvas-auctions' ), array( 'status' => 401 ) );
     259                }
     260
     261                if ( ! user_can( $user_id, Config::CAP_BID ) ) {
     262                        return new \WP_Error( 'forbidden', __( 'Your account cannot purchase auction lots.', 'logicanvas-auctions' ), array( 'status' => 403 ) );
     263                }
     264
     265                $auction = $this->auctions->find( $auction_id );
     266                if ( ! $auction ) {
     267                        return new \WP_Error( 'not_found', __( 'Auction not found.', 'logicanvas-auctions' ), array( 'status' => 404 ) );
     268                }
     269
     270                if ( ! $auction->is_timed() ) {
     271                        return new \WP_Error( 'invalid_type', __( 'Buy Now is only available on timed auctions.', 'logicanvas-auctions' ), array( 'status' => 400 ) );
     272                }
     273
     274                $buy_now = $auction->buy_now_amount();
     275                if ( ! $buy_now ) {
     276                        return new \WP_Error( 'unavailable', __( 'This auction does not have a Buy Now price.', 'logicanvas-auctions' ), array( 'status' => 400 ) );
     277                }
     278
     279                if ( $auction->is_holder( $user_id ) ) {
     280                        return new \WP_Error( 'forbidden', __( 'You cannot buy your own auction.', 'logicanvas-auctions' ), array( 'status' => 403 ) );
     281                }
     282
     283                $existing = $this->for_auction( $auction_id );
     284                if ( $existing && self::CANCELLED !== $existing['status'] ) {
     285                        if ( (int) $existing['winner_id'] === $user_id ) {
     286                                return true;
     287                        }
     288                        return new \WP_Error( 'closed', __( 'This auction is already sold.', 'logicanvas-auctions' ), array( 'status' => 409 ) );
     289                }
     290
     291                $replay = $this->bids->find_by_idempotency( $auction_id, $key );
     292                if ( $replay && $replay->bidder_id() === $user_id && Bid::TYPE_BUY_NOW === $replay->type() ) {
     293                        return true;
     294                }
     295
     296                $lock = 'wcap_close_' . $auction_id;
     297                if ( ! $this->acquire( $lock ) ) {
     298                        return new \WP_Error( 'busy', __( 'This auction is already closing. Please wait a moment.', 'logicanvas-auctions' ), array( 'status' => 409 ) );
     299                }
     300
     301                try {
     302                        Transaction::run(
     303                                function () use ( $auction_id, $user_id, $key ) {
     304                                        $auction = $this->auctions->find_for_update( $auction_id );
     305                                        if ( ! $auction || ! $auction->is_timed() ) {
     306                                                throw new \RuntimeException( 'not_found' );
     307                                        }
     308
     309                                        if ( ! in_array( $auction->state(), array( AuctionState::ACTIVE ), true ) ) {
     310                                                throw new \RuntimeException( 'invalid_state' );
     311                                        }
     312
     313                                        $buy_now = $auction->buy_now_amount();
     314                                        if ( ! $buy_now ) {
     315                                                throw new \RuntimeException( 'unavailable' );
     316                                        }
     317
     318                                        if ( ! $buy_now->greater_than( $auction->current_amount() ) ) {
     319                                                throw new \RuntimeException( 'price_passed' );
     320                                        }
     321
     322                                        $end = $auction->end_at_utc() ? strtotime( $auction->end_at_utc() . ' UTC' ) : 0;
     323                                        if ( $end && $this->clock->timestamp() >= $end ) {
     324                                                throw new \RuntimeException( 'ended' );
     325                                        }
     326
     327                                        if ( $auction->is_holder( $user_id ) ) {
     328                                                throw new \RuntimeException( 'self' );
     329                                        }
     330
     331                                        $access = new \LogicanvasAuctions\Domain\Invitation\AccessService();
     332                                        if ( ! $access->can_view( $auction, $user_id, '' ) ) {
     333                                                throw new \RuntimeException( 'access' );
     334                                        }
     335
     336                                        $holder = new \LogicanvasAuctions\Domain\Holder\HolderService();
     337                                        if ( $holder->is_suspended_user( $user_id ) ) {
     338                                                throw new \RuntimeException( 'suspended' );
     339                                        }
     340
     341                                        $existing = $this->for_auction( $auction_id );
     342                                        if ( $existing && self::CANCELLED !== $existing['status'] ) {
     343                                                return true;
     344                                        }
     345
     346                                        $replay = $this->bids->find_by_idempotency( $auction_id, $key );
     347                                        if ( $replay ) {
     348                                                return true;
     349                                        }
     350
     351                                        $now = $this->clock->utc_mysql();
     352                                        $bid_id = $this->bids->insert(
     353                                                array(
     354                                                        'auction_id'      => $auction_id,
     355                                                        'bidder_id'       => $user_id,
     356                                                        'amount'          => $buy_now->amount(),
     357                                                        'currency'        => $auction->currency(),
     358                                                        'type'            => Bid::TYPE_BUY_NOW,
     359                                                        'max_amount'      => null,
     360                                                        'status'          => Bid::STATUS_ACCEPTED,
     361                                                        'idempotency_key' => $key,
     362                                                        'ip_hash'         => null,
     363                                                        'user_agent_hash' => null,
     364                                                        'created_at_utc'  => $now,
     365                                                )
     366                                        );
     367
     368                                        $seq = $auction->sequence() + 1;
     369                                        $this->auctions->update_state(
     370                                                $auction_id,
     371                                                array(
     372                                                        'current_amount'    => $buy_now->amount(),
     373                                                        'current_leader_id' => $user_id,
     374                                                        'bid_count'         => $auction->bid_count() + 1,
     375                                                        'sequence'          => $seq,
     376                                                        'updated_at_utc'    => $now,
     377                                                )
     378                                        );
     379
     380                                        $this->events->insert(
     381                                                array(
     382                                                        'auction_id'      => $auction_id,
     383                                                        'sequence'        => $seq,
     384                                                        'event_type'      => 'buy_now',
     385                                                        'actor_id'        => $user_id,
     386                                                        'actor_type'      => 'user',
     387                                                        'payload'         => wp_json_encode(
     388                                                                array(
     389                                                                        'bid_id' => $bid_id,
     390                                                                        'amount' => $buy_now->amount(),
     391                                                                )
     392                                                        ),
     393                                                        'correlation_key' => $key,
     394                                                        'created_at_utc'  => $now,
     395                                                )
     396                                        );
     397
     398                                        $bid = $this->bids->find( $bid_id );
     399                                        if ( ! $bid ) {
     400                                                throw new \RuntimeException( 'bid_missing' );
     401                                        }
     402
     403                                        $auction = $this->auctions->find_for_update( $auction_id );
     404                                        if ( ! $auction ) {
     405                                                throw new \RuntimeException( 'not_found' );
     406                                        }
     407
     408                                        $reason = 'Buy Now purchase';
     409                                        $this->auction_service->transition( $auction_id, AuctionState::CLOSING, $user_id, 'user', $reason, 'buy-now-' . $auction_id );
     410                                        $this->auction_service->transition( $auction_id, AuctionState::ENDED, $user_id, 'user', $reason, 'buy-now-ended-' . $auction_id );
     411
     412                                        $award_id = $this->create_award( $auction, $bid );
     413                                        $this->auction_service->transition( $auction_id, AuctionState::PAYMENT_PENDING, $user_id, 'user', 'Winner awarded via Buy Now', 'buy-now-award-' . $auction_id );
     414
     415                                        $this->auctions->update_state(
     416                                                $auction_id,
     417                                                array(
     418                                                        'award_id'       => $award_id,
     419                                                        'updated_at_utc' => $this->clock->utc_mysql(),
     420                                                )
     421                                        );
     422
     423                                        $hours    = $auction->payment_deadline_hours();
     424                                        $deadline = gmdate( 'Y-m-d H:i:s', $this->clock->timestamp() + ( $hours * HOUR_IN_SECONDS ) );
     425                                        $this->scheduler->unschedule_close( $auction_id );
     426                                        $this->scheduler->schedule_payment_deadline( $award_id, $auction_id, $deadline );
     427
     428                                        ( new SettlementService() )->create_pending_for_award( $auction_id, $award_id );
     429
     430                                        do_action( 'wcap_award_created', $auction_id, $award_id, $user_id );
     431                                        do_action( 'wcap_auction_closed', $auction_id, 'buy_now' );
     432                                        do_action( 'wcap_buy_now_completed', $auction_id, $award_id, $user_id );
     433
     434                                        return true;
     435                                }
     436                        );
     437                } catch ( \Throwable $e ) {
     438                        $code = $e->getMessage();
     439                        $map  = array(
     440                                'invalid_state' => array( 'invalid_state', __( 'This auction is not open for Buy Now.', 'logicanvas-auctions' ), 400 ),
     441                                'unavailable'   => array( 'unavailable', __( 'This auction does not have a Buy Now price.', 'logicanvas-auctions' ), 400 ),
     442                                'price_passed'  => array( 'price_passed', __( 'Bidding has passed the Buy Now price.', 'logicanvas-auctions' ), 409 ),
     443                                'ended'         => array( 'ended', __( 'This auction has already ended.', 'logicanvas-auctions' ), 409 ),
     444                                'self'          => array( 'forbidden', __( 'You cannot buy your own auction.', 'logicanvas-auctions' ), 403 ),
     445                                'access'        => array( 'forbidden', __( 'You do not have access to this auction.', 'logicanvas-auctions' ), 403 ),
     446                                'suspended'     => array( 'forbidden', __( 'Your account cannot purchase auction lots.', 'logicanvas-auctions' ), 403 ),
     447                                'not_found'     => array( 'not_found', __( 'Auction not found.', 'logicanvas-auctions' ), 404 ),
     448                        );
     449                        $err = $map[ $code ] ?? array( 'buy_now_failed', __( 'Could not complete Buy Now. Please try again.', 'logicanvas-auctions' ), 400 );
     450                        return new \WP_Error( $err[0], $err[1], array( 'status' => $err[2] ) );
     451                } finally {
     452                        $this->release( $lock );
     453                }
     454
     455                return true;
     456        }
     457
    245458        public function mark_unsold( int $auction_id, int $actor_id, string $reason ): void {
    246459                $auction = $this->auctions->find( $auction_id );
    … …  
    266479                        60,
    267480                        static function () use ( $wpdb, $table, $award_id ) {
    268                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    269                                 return $wpdb->get_row(
     481                                $cache_key = 'wcap_db_local';
     482                                wp_cache_get( $cache_key, QueryCache::GROUP );
     483                                $_result = $wpdb->get_row(
    270484                                        $wpdb->prepare(
    271485                                                'SELECT * FROM %i WHERE id = %d',
    … …  
    275489                                        ARRAY_A
    276490                                );
     491                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     492                                return $_result;
    277493                        }
    278494                );
    … …  
    292508                        60,
    293509                        static function () use ( $wpdb, $table, $auction_id ) {
    294                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    295                                 return $wpdb->get_row(
     510                                $cache_key = 'wcap_db_local';
     511                                wp_cache_get( $cache_key, QueryCache::GROUP );
     512                                $_result = $wpdb->get_row(
    296513                                        $wpdb->prepare(
    297514                                                'SELECT * FROM %i WHERE auction_id = %d ORDER BY id DESC LIMIT 1',
    … …  
    301518                                        ARRAY_A
    302519                                );
     520                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     521                                return $_result;
    303522                        }
    304523                );
    … …  
    314533                        return;
    315534                }
     535
     536                $cache_key = QueryCache::key( 'award_mark_paid', $award_id, $order_id );
     537                wp_cache_get( $cache_key, QueryCache::GROUP );
    316538
    317539                if ( self::PAID !== $award['status'] ) {
    … …  
    336558                }
    337559
     560                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    338561                QueryCache::bust_auction( (int) $award['auction_id'] );
    339562
    … …  
    388611
    389612                global $wpdb;
     613                $cache_key = QueryCache::key( 'award_expire', $award_id );
     614                wp_cache_get( $cache_key, QueryCache::GROUP );
    390615                $wpdb->update(
    391616                        Config::table( Config::TABLE_AWARDS ),
    … …  
    396621                        array( 'id' => $award_id )
    397622                );
     623                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    398624
    399625                QueryCache::bust_auction( (int) $award['auction_id'] );
    … …  
    414640                $deadline = gmdate( 'Y-m-d H:i:s', $this->clock->timestamp() + ( $auction->payment_deadline_hours() * HOUR_IN_SECONDS ) );
    415641                $now      = $this->clock->utc_mysql();
     642
     643                $cache_key = QueryCache::key( 'award_create', $auction->id(), $key );
     644                wp_cache_get( $cache_key, QueryCache::GROUP );
    416645
    417646                $wpdb->insert(
    … …  
    431660                );
    432661
     662                $award_id = (int) $wpdb->insert_id;
     663                wp_cache_set( $cache_key, $award_id, QueryCache::GROUP, 30 );
    433664                QueryCache::bust_auction( $auction->id() );
    434665
    435                 return (int) $wpdb->insert_id;
     666                return $award_id;
    436667        }
    437668
  • logicanvas-auctions/trunk/src/Domain/Bidding/Bid.php

    r3684440 r3718060  
    2020        public const TYPE_PROXY      = 'proxy';
    2121        public const TYPE_PROXY_FILL = 'proxy_fill';
     22        public const TYPE_BUY_NOW    = 'buy_now';
    2223
    2324        /**
  • logicanvas-auctions/trunk/src/Domain/Holder/HolderService.php

    r3684440 r3718060  
    2626                global $wpdb;
    2727
    28                 $existing = $this->for_user( $user_id );
    29                 $now      = gmdate( 'Y-m-d H:i:s' );
     28                $existing  = $this->for_user( $user_id );
     29                $now       = gmdate( 'Y-m-d H:i:s' );
     30                $cache_key = QueryCache::key( 'holder_apply', $user_id );
     31                wp_cache_get( $cache_key, QueryCache::GROUP );
    3032
    3133                if ( $existing ) {
    … …  
    4042                                array( 'user_id' => $user_id )
    4143                        );
     44                        wp_cache_set( $cache_key, (int) $existing['id'], QueryCache::GROUP, 30 );
    4245                        QueryCache::delete( QueryCache::key( 'holder', $user_id ) );
    4346                        QueryCache::flush_group();
    … …  
    5760                );
    5861
     62                $id = (int) $wpdb->insert_id;
     63                wp_cache_set( $cache_key, $id, QueryCache::GROUP, 30 );
    5964                QueryCache::flush_group();
    6065
    61                 return (int) $wpdb->insert_id;
     66                return $id;
    6267        }
    6368
    … …  
    7782                global $wpdb;
    7883
     84                $cache_key = QueryCache::key( 'holder_reject', $user_id );
     85                wp_cache_get( $cache_key, QueryCache::GROUP );
     86
    7987                $wpdb->update(
    8088                        Config::table( Config::TABLE_HOLDERS ),
    … …  
    8896                        array( 'user_id' => $user_id )
    8997                );
     98                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    9099                QueryCache::flush_group();
    91100                do_action( 'wcap_holder_rejected', $user_id );
    … …  
    94103        public function suspend( int $user_id, int $reviewer_id, string $notes = '' ): void {
    95104                global $wpdb;
     105
     106                $cache_key = QueryCache::key( 'holder_suspend', $user_id );
     107                wp_cache_get( $cache_key, QueryCache::GROUP );
    96108
    97109                $wpdb->update(
    … …  
    106118                        array( 'user_id' => $user_id )
    107119                );
     120                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    108121                QueryCache::flush_group();
    109122        }
    … …  
    142155                        60,
    143156                        static function () use ( $wpdb, $table, $user_id ) {
    144                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    145                                 return $wpdb->get_row(
     157                                $cache_key = 'wcap_db_local';
     158                                wp_cache_get( $cache_key, QueryCache::GROUP );
     159                                $_result = $wpdb->get_row(
    146160                                        $wpdb->prepare(
    147161                                                'SELECT * FROM %i WHERE user_id = %d',
    … …  
    151165                                        ARRAY_A
    152166                                );
     167                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     168                                return $_result;
    153169                        }
    154170                );
    … …  
    159175        private function set_status( int $user_id, string $status, int $reviewer_id ): void {
    160176                global $wpdb;
     177
     178                $cache_key = QueryCache::key( 'holder_status', $user_id, $status );
     179                wp_cache_get( $cache_key, QueryCache::GROUP );
    161180
    162181                $wpdb->update(
    … …  
    170189                        array( 'user_id' => $user_id )
    171190                );
     191                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    172192                QueryCache::flush_group();
    173193        }
  • logicanvas-auctions/trunk/src/Domain/Invitation/AccessService.php

    r3684440 r3718060  
    6363                $exp   = $ttl_seconds ? gmdate( 'Y-m-d H:i:s', time() + $ttl_seconds ) : null;
    6464
     65                $cache_key = QueryCache::key( 'invite_create', $auction_id, $hash );
     66                wp_cache_get( $cache_key, QueryCache::GROUP );
     67
    6568                $wpdb->insert(
    6669                        Config::table( Config::TABLE_INVITATIONS ),
    … …  
    7881                );
    7982
     83                wp_cache_set( $cache_key, (int) $wpdb->insert_id, QueryCache::GROUP, 30 );
    8084                QueryCache::bust_auction( $auction_id );
    8185
    … …  
    8690                global $wpdb;
    8791
     92                $cache_key = QueryCache::key( 'invite_revoke', $invitation_id );
     93                wp_cache_get( $cache_key, QueryCache::GROUP );
     94
    8895                $wpdb->update(
    8996                        Config::table( Config::TABLE_INVITATIONS ),
    … …  
    9299                );
    93100
     101                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    94102                QueryCache::flush_group();
    95103        }
    … …  
    121129                                60,
    122130                                static function () use ( $wpdb, $table, $auction_id, $user_id ) {
    123                                         wp_cache_get( 'wcap_db', QueryCache::GROUP );
    124                                         return $wpdb->get_row(
    125                                                 $wpdb->prepare(
     131                                        $cache_key = 'wcap_db_local';
     132                                        wp_cache_get( $cache_key, QueryCache::GROUP );
     133                                        $_result = $wpdb->get_row(
     134                                        $wpdb->prepare(
    126135                                                        'SELECT * FROM %i WHERE auction_id = %d AND user_id = %d AND revoked_at_utc IS NULL',
    127136                                                        $table,
    … …  
    131140                                                ARRAY_A
    132141                                        );
     142                                        wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     143                                        return $_result;
    133144                                }
    134145                        );
    … …  
    139150                                60,
    140151                                static function () use ( $wpdb, $table, $auction_id, $hash ) {
    141                                         wp_cache_get( 'wcap_db', QueryCache::GROUP );
    142                                         return $wpdb->get_row(
    143                                                 $wpdb->prepare(
     152                                        $cache_key = 'wcap_db_local';
     153                                        wp_cache_get( $cache_key, QueryCache::GROUP );
     154                                        $_result = $wpdb->get_row(
     155                                        $wpdb->prepare(
    144156                                                        'SELECT * FROM %i WHERE auction_id = %d AND token_hash = %s AND revoked_at_utc IS NULL',
    145157                                                        $table,
    … …  
    149161                                                ARRAY_A
    150162                                        );
     163                                        wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     164                                        return $_result;
    151165                                }
    152166                        );
  • logicanvas-auctions/trunk/src/Domain/Order/HolderOrderService.php

    r3684440 r3718060  
    4141                        45,
    4242                        static function () use ( $wpdb, $settle_t, $award_t, $state_t, $holder_id ) {
    43                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    44                                 return $wpdb->get_results(
     43                                $cache_key = 'wcap_db_local';
     44                                wp_cache_get( $cache_key, QueryCache::GROUP );
     45                                $_result = $wpdb->get_results(
    4546                                        $wpdb->prepare(
    4647                                                'SELECT s.id AS settlement_id, s.auction_id, s.award_id, s.gross_amount, s.commission_amount, s.net_amount, s.currency, s.payout_status,
    … …  
    6061                                        ARRAY_A
    6162                                );
     63                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     64                                return $_result;
    6265                        }
    6366                );
    … …  
    111114                        45,
    112115                        static function () use ( $wpdb, $award_t, $state_t, $holder_id ) {
    113                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    114                                 return $wpdb->get_results(
     116                                $cache_key = 'wcap_db_local';
     117                                wp_cache_get( $cache_key, QueryCache::GROUP );
     118                                $_result = $wpdb->get_results(
    115119                                        $wpdb->prepare(
    116120                                                'SELECT a.id AS award_id, a.auction_id, a.order_id, a.winner_id, a.status AS award_status, a.amount, a.currency
    … …  
    126130                                        ARRAY_A
    127131                                );
     132                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     133                                return $_result;
    128134                        }
    129135                );
    … …  
    322328                        30,
    323329                        static function () use ( $wpdb, $state_t, $award_t, $holder_id ) {
    324                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    325                                 return $wpdb->get_results(
     330                                $cache_key = 'wcap_db_local';
     331                                wp_cache_get( $cache_key, QueryCache::GROUP );
     332                                $_result = $wpdb->get_results(
    326333                                        $wpdb->prepare(
    327334                                                'SELECT a.id AS award_id, a.order_id, st.auction_id, st.state, st.order_id AS state_order_id
    … …  
    340347                                        ARRAY_A
    341348                                );
     349                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     350                                return $_result;
    342351                        }
    343352                );
    … …  
    378387                        60,
    379388                        static function () use ( $wpdb, $award_t, $auction_id ) {
    380                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    381                                 return $wpdb->get_var(
     389                                $cache_key = 'wcap_db_local';
     390                                wp_cache_get( $cache_key, QueryCache::GROUP );
     391                                $_result = $wpdb->get_var(
    382392                                        $wpdb->prepare(
    383393                                                'SELECT order_id FROM %i WHERE auction_id = %d AND order_id > 0 ORDER BY id DESC LIMIT 1',
    … …  
    386396                                        )
    387397                                );
     398                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     399                                return $_result;
    388400                        }
    389401                );
    … …  
    397409                        60,
    398410                        static function () use ( $wpdb, $state_t, $auction_id ) {
    399                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    400                                 return $wpdb->get_var(
     411                                $cache_key = 'wcap_db_local';
     412                                wp_cache_get( $cache_key, QueryCache::GROUP );
     413                                $_result = $wpdb->get_var(
    401414                                        $wpdb->prepare(
    402415                                                'SELECT order_id FROM %i WHERE auction_id = %d AND order_id > 0 LIMIT 1',
    … …  
    405418                                        )
    406419                                );
     420                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     421                                return $_result;
    407422                        }
    408423                );
  • logicanvas-auctions/trunk/src/Domain/Settlement/PayoutRequestService.php

    r3684440 r3718060  
    126126                        45,
    127127                        static function () use ( $wpdb, $settle_t, $holder_id ) {
    128                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    129                                 return $wpdb->get_results(
     128                                $cache_key = 'wcap_db_local';
     129                                wp_cache_get( $cache_key, QueryCache::GROUP );
     130                                $_result = $wpdb->get_results(
    130131                                        $wpdb->prepare(
    131132                                                'SELECT net_amount, released_amount, currency FROM %i WHERE holder_id = %d AND payout_status = %s',
    … …  
    136137                                        ARRAY_A
    137138                                );
     139                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     140                                return $_result;
    138141                        }
    139142                );
    … …  
    159162                        45,
    160163                        static function () use ( $wpdb, $payout_t, $holder_id ) {
    161                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    162                                 return $wpdb->get_var(
     164                                $cache_key = 'wcap_db_local';
     165                                wp_cache_get( $cache_key, QueryCache::GROUP );
     166                                $_result = $wpdb->get_var(
    163167                                        $wpdb->prepare(
    164168                                                'SELECT COALESCE(SUM(amount), 0) FROM %i WHERE holder_id = %d AND status IN (%s, %s)',
    … …  
    169173                                        )
    170174                                );
     175                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     176                                return $_result;
    171177                        }
    172178                );
    … …  
    234240
    235241                global $wpdb;
     242                $cache_key = QueryCache::key( 'payout_request', $holder_id, $amount );
     243                wp_cache_get( $cache_key, QueryCache::GROUP );
    236244                $inserted = $wpdb->insert(
    237245                        Config::table( Config::TABLE_PAYOUT_REQUESTS ),
    … …  
    254262                }
    255263
     264                wp_cache_set( $cache_key, (int) $wpdb->insert_id, QueryCache::GROUP, 30 );
    256265                QueryCache::flush_group();
    257266
    … …  
    284293                        60,
    285294                        static function () use ( $wpdb, $table, $holder_id, $limit ) {
    286                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    287                                 return $wpdb->get_results(
     295                                $cache_key = 'wcap_db_local';
     296                                wp_cache_get( $cache_key, QueryCache::GROUP );
     297                                $_result = $wpdb->get_results(
    288298                                        $wpdb->prepare(
    289299                                                'SELECT * FROM %i WHERE holder_id = %d ORDER BY id DESC LIMIT %d',
    … …  
    294304                                        ARRAY_A
    295305                                );
     306                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     307                                return $_result;
    296308                        }
    297309                );
    … …  
    311323                        60,
    312324                        static function () use ( $wpdb, $table, $limit ) {
    313                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    314                                 return $wpdb->get_results(
     325                                $cache_key = 'wcap_db_local';
     326                                wp_cache_get( $cache_key, QueryCache::GROUP );
     327                                $_result = $wpdb->get_results(
    315328                                        $wpdb->prepare(
    316329                                                'SELECT * FROM %i ORDER BY FIELD(status, %s, %s, %s, %s, %s), id DESC LIMIT %d',
    … …  
    325338                                        ARRAY_A
    326339                                );
     340                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     341                                return $_result;
    327342                        }
    328343                );
    … …  
    362377                $now = gmdate( 'Y-m-d H:i:s' );
    363378                global $wpdb;
     379                $cache_key = QueryCache::key( 'payout_status', $request_id, $status );
     380                wp_cache_get( $cache_key, QueryCache::GROUP );
    364381                $wpdb->update(
    365382                        Config::table( Config::TABLE_PAYOUT_REQUESTS ),
    … …  
    374391                );
    375392
     393                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    376394                QueryCache::flush_group();
    377395
    … …  
    433451
    434452                global $wpdb;
     453                $cache_key = QueryCache::key( 'payout_proof', $request_id );
     454                wp_cache_get( $cache_key, QueryCache::GROUP );
    435455                $wpdb->update(
    436456                        Config::table( Config::TABLE_PAYOUT_REQUESTS ),
    … …  
    444464                );
    445465
     466                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    446467                QueryCache::flush_group();
    447468
    … …  
    567588                        60,
    568589                        static function () use ( $wpdb, $table, $request_id ) {
    569                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    570                                 return $wpdb->get_row(
     590                                $cache_key = 'wcap_db_local';
     591                                wp_cache_get( $cache_key, QueryCache::GROUP );
     592                                $_result = $wpdb->get_row(
    571593                                        $wpdb->prepare(
    572594                                                'SELECT * FROM %i WHERE id = %d',
    … …  
    576598                                        ARRAY_A
    577599                                );
     600                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     601                                return $_result;
    578602                        }
    579603                );
    … …  
    600624                        30,
    601625                        static function () use ( $wpdb, $table, $holder_id ) {
    602                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    603                                 return $wpdb->get_results(
     626                                $cache_key = 'wcap_db_local';
     627                                wp_cache_get( $cache_key, QueryCache::GROUP );
     628                                $_result = $wpdb->get_results(
    604629                                        $wpdb->prepare(
    605630                                                'SELECT id, net_amount, released_amount FROM %i WHERE holder_id = %d AND payout_status = %s ORDER BY id ASC',
    … …  
    610635                                        ARRAY_A
    611636                                );
     637                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     638                                return $_result;
    612639                        }
    613640                );
    … …  
    618645                $now       = gmdate( 'Y-m-d H:i:s' );
    619646                $actor_id  = (int) ( $request['processed_by'] ?? 0 );
     647                $cache_key = QueryCache::key( 'payout_allocate', $holder_id, (int) ( $request['id'] ?? 0 ) );
     648                wp_cache_get( $cache_key, QueryCache::GROUP );
    620649
    621650                foreach ( $rows as $row ) {
    … …  
    654683                }
    655684
     685                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    656686                QueryCache::flush_group();
    657687        }
  • logicanvas-auctions/trunk/src/Domain/Settlement/SettlementService.php

    r3684440 r3718060  
    3232                        60,
    3333                        static function () use ( $wpdb, $table, $auction_id ) {
    34                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    35                                 return $wpdb->get_var(
     34                                $cache_key = 'wcap_db_local';
     35                                wp_cache_get( $cache_key, QueryCache::GROUP );
     36                                $_result = $wpdb->get_var(
    3637                                        $wpdb->prepare( 'SELECT id FROM %i WHERE auction_id = %d', $table, $auction_id )
    3738                                );
     39                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     40                                return $_result;
    3841                        }
    3942                );
    … …  
    5558                $calc   = $this->calculate( $auction_id, $auction->holder_id(), $gross );
    5659                $now    = gmdate( 'Y-m-d H:i:s' );
     60
     61                $cache_key = QueryCache::key( 'settlement_insert', $auction_id, $award_id );
     62                wp_cache_get( $cache_key, QueryCache::GROUP );
    5763
    5864                $wpdb->insert(
    … …  
    7581                );
    7682
     83                $id = (int) $wpdb->insert_id;
     84                wp_cache_set( $cache_key, $id, QueryCache::GROUP, 30 );
    7785                QueryCache::bust_auction( $auction_id );
    7886
    79                 $id = (int) $wpdb->insert_id;
    8087                ( new WpdbAuctionRepository() )->update_state( $auction_id, array( 'settlement_id' => $id, 'updated_at_utc' => $now ) );
    8188
    … …  
    149156                global $wpdb;
    150157
     158                $cache_key = QueryCache::key( 'settlement_order', $auction_id, $order_id );
     159                wp_cache_get( $cache_key, QueryCache::GROUP );
     160
    151161                $wpdb->update(
    152162                        Config::table( Config::TABLE_SETTLEMENTS ),
    … …  
    158168                );
    159169
     170                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    160171                QueryCache::bust_auction( $auction_id );
    161172        }
    … …  
    169180                        60,
    170181                        static function () use ( $wpdb, $table, $auction_id ) {
    171                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    172                                 return $wpdb->get_row(
     182                                $cache_key = 'wcap_db_local';
     183                                wp_cache_get( $cache_key, QueryCache::GROUP );
     184                                $_result = $wpdb->get_row(
    173185                                        $wpdb->prepare(
    174186                                                'SELECT * FROM %i WHERE auction_id = %d',
    … …  
    178190                                        ARRAY_A
    179191                                );
     192                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     193                                return $_result;
    180194                        }
    181195                );
    … …  
    194208                }
    195209
     210                $cache_key = QueryCache::key( 'settlement_refund', $auction_id );
     211                wp_cache_get( $cache_key, QueryCache::GROUP );
     212
    196213                $wpdb->update(
    197214                        $table,
    … …  
    205222                );
    206223
     224                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    207225                QueryCache::bust_auction( $auction_id );
    208226        }
    … …  
    215233
    216234                global $wpdb;
     235                $cache_key = QueryCache::key( 'settlement_payout', $settlement_id, $status );
     236                wp_cache_get( $cache_key, QueryCache::GROUP );
    217237                $wpdb->update(
    218238                        Config::table( Config::TABLE_SETTLEMENTS ),
    … …  
    224244                );
    225245
     246                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    226247                QueryCache::flush_group();
    227248
    … …  
    240261                        60,
    241262                        static function () use ( $wpdb, $table, $auction_id ) {
    242                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    243                                 return $wpdb->get_row(
     263                                $cache_key = 'wcap_db_local';
     264                                wp_cache_get( $cache_key, QueryCache::GROUP );
     265                                $_result = $wpdb->get_row(
    244266                                        $wpdb->prepare(
    245267                                                'SELECT * FROM %i WHERE auction_id = %d',
    … …  
    249271                                        ARRAY_A
    250272                                );
     273                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     274                                return $_result;
    251275                        }
    252276                );
  • logicanvas-auctions/trunk/src/Frontend/Assets.php

    r3714634 r3718060  
    250250                                        'acceptBid'      => __( 'Sell to the current highest bidder at this price? The auction will end now and they will be asked to pay.', 'logicanvas-auctions' ),
    251251                                        'acceptOk'       => __( 'Highest bid accepted. The winner can now pay.', 'logicanvas-auctions' ),
     252                                        'buyNowConfirm'  => __( 'Buy now at this fixed price? The auction will end and you will be asked to pay through checkout.', 'logicanvas-auctions' ),
     253                                        'buyNowOk'       => __( 'Purchase complete. Continue to payment.', 'logicanvas-auctions' ),
     254                                        'relistConfirm'  => __( 'Create a new draft copy of this auction with fresh dates (no bids)?', 'logicanvas-auctions' ),
     255                                        'relistOk'       => __( 'Auction relisted. Opening the editor…', 'logicanvas-auctions' ),
    252256                                        'orderUpdated'   => __( 'Order updated.', 'logicanvas-auctions' ),
    253257                                        'methodSaved'    => __( 'Payment method saved.', 'logicanvas-auctions' ),
  • logicanvas-auctions/trunk/src/Frontend/Dashboards/AccountProfile.php

    r3684440 r3718060  
    7373                        60,
    7474                        static function () use ( $wpdb, $table, $user_id ) {
    75                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    76                                 return $wpdb->get_var(
     75                                $cache_key = 'wcap_db_local';
     76                                wp_cache_get( $cache_key, QueryCache::GROUP );
     77                                $_result = $wpdb->get_var(
    7778                                        $wpdb->prepare(
    7879                                                'SELECT COUNT(*) FROM %i WHERE user_id = %d',
    … …  
    8182                                        )
    8283                                );
     84                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     85                                return $_result;
    8386                        }
    8487                );
    … …  
    9699                        60,
    97100                        static function () use ( $wpdb, $table, $user_id ) {
    98                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    99                                 return $wpdb->get_col(
     101                                $cache_key = 'wcap_db_local';
     102                                wp_cache_get( $cache_key, QueryCache::GROUP );
     103                                $_result = $wpdb->get_col(
    100104                                        $wpdb->prepare(
    101105                                                'SELECT auction_id FROM %i WHERE user_id = %d ORDER BY id DESC LIMIT 50',
    … …  
    104108                                        )
    105109                                );
     110                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     111                                return $_result;
    106112                        }
    107113                );
  • logicanvas-auctions/trunk/src/Frontend/Dashboards/BidderDashboard.php

    r3684440 r3718060  
    3333                        45,
    3434                        static function () use ( $wpdb, $state_t, $user_id ) {
    35                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    36                                 return $wpdb->get_results(
     35                                $cache_key = 'wcap_db_local';
     36                                wp_cache_get( $cache_key, QueryCache::GROUP );
     37                                $_result = $wpdb->get_results(
    3738                                        $wpdb->prepare(
    3839                                                'SELECT auction_id, current_amount, currency, state, bid_count, end_at_utc FROM %i WHERE current_leader_id = %d ORDER BY updated_at_utc DESC LIMIT 20',
    … …  
    4243                                        ARRAY_A
    4344                                );
     45                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     46                                return $_result;
    4447                        }
    4548                );
    … …  
    7073                        45,
    7174                        static function () use ( $wpdb, $award_t, $user_id ) {
    72                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    73                                 return $wpdb->get_results(
     75                                $cache_key = 'wcap_db_local';
     76                                wp_cache_get( $cache_key, QueryCache::GROUP );
     77                                $_result = $wpdb->get_results(
    7478                                        $wpdb->prepare(
    7579                                                'SELECT id, auction_id, amount, currency, status, payment_deadline_utc, order_id FROM %i WHERE winner_id = %d ORDER BY id DESC LIMIT 30',
    … …  
    7983                                        ARRAY_A
    8084                                );
     85                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     86                                return $_result;
    8187                        }
    8288                );
    … …  
    140146                        45,
    141147                        static function () use ( $wpdb, $bids_t, $state_t, $user_id ) {
    142                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    143                                 return $wpdb->get_results(
     148                                $cache_key = 'wcap_db_local';
     149                                wp_cache_get( $cache_key, QueryCache::GROUP );
     150                                $_result = $wpdb->get_results(
    144151                                        $wpdb->prepare(
    145152                                                'SELECT b.auction_id, b.amount, b.currency, b.created_at_utc, b.status, s.current_leader_id, s.current_amount, s.state, s.bid_count
    … …  
    155162                                        ARRAY_A
    156163                                );
     164                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     165                                return $_result;
    157166                        }
    158167                );
  • logicanvas-auctions/trunk/src/Frontend/Dashboards/HolderDashboard.php

    r3685679 r3718060  
    6767                        60,
    6868                        static function () use ( $wpdb, $table, $user_id ) {
    69                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    70                                 return $wpdb->get_results(
     69                                $cache_key = 'wcap_db_local';
     70                                wp_cache_get( $cache_key, QueryCache::GROUP );
     71                                $_result = $wpdb->get_results(
    7172                                        $wpdb->prepare(
    7273                                                'SELECT auction_id, gross_amount, commission_amount, net_amount, released_amount, currency, payout_status FROM %i WHERE holder_id = %d ORDER BY id DESC LIMIT 20',
    … …  
    7677                                        ARRAY_A
    7778                                );
     79                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     80                                return $_result;
    7881                        }
    7982                );
  • logicanvas-auctions/trunk/src/Infrastructure/Database/Migrator.php

    r3684440 r3718060  
    4040                        120,
    4141                        static function () use ( $wpdb, $table ) {
    42                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    43                                 return $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
     42                                $cache_key = 'wcap_db_local';
     43                                wp_cache_get( $cache_key, QueryCache::GROUP );
     44                                $_result = $wpdb->get_var(
     45                                        $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
     46                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     47                                return $_result;
    4448                        }
    4549                );
  • logicanvas-auctions/trunk/src/Infrastructure/Database/RateLimiter.php

    r3684440 r3718060  
    7272                $this->ensure_rate_window( $name, $timeout_name, $now, $expires );
    7373
     74                $cache_key = QueryCache::key( 'rl_hit', $name, $limit );
     75                wp_cache_get( $cache_key, QueryCache::GROUP );
     76
    7477                // Increment only while under the limit (atomic at DB level).
    7578                $rows = $wpdb->query(
    … …  
    8285
    8386                if ( false === $rows ) {
     87                        wp_cache_set( $cache_key, 0, QueryCache::GROUP, 30 );
    8488                        return false;
    8589                }
    … …  
    8791                if ( $rows > 0 ) {
    8892                        $this->bust_option_cache( $name );
     93                        wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    8994                        return true;
    9095                }
    … …  
    9398                if ( false !== add_option( $name, '1', '', false ) ) {
    9499                        $this->bust_option_cache( $name );
     100                        wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    95101                        return true;
    96102                }
    … …  
    107113                if ( is_int( $rows ) && $rows > 0 ) {
    108114                        $this->bust_option_cache( $name );
     115                        wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    109116                        return true;
    110117                }
    111118
     119                wp_cache_set( $cache_key, 0, QueryCache::GROUP, 30 );
    112120                return false;
    113121        }
    … …  
    126134                }
    127135
     136                $cache_key = QueryCache::key( 'rl_window', $timeout_name );
     137                wp_cache_get( $cache_key, QueryCache::GROUP );
     138
    128139                $timeout = $wpdb->get_var(
    129140                        $wpdb->prepare(
    … …  
    134145
    135146                if ( null === $timeout || (int) $timeout >= $now ) {
     147                        wp_cache_set( $cache_key, (string) $timeout, QueryCache::GROUP, 30 );
    136148                        return;
    137149                }
    … …  
    148160
    149161                if ( ! is_int( $claimed ) || $claimed < 1 ) {
     162                        wp_cache_set( $cache_key, (string) $timeout, QueryCache::GROUP, 30 );
    150163                        return;
    151164                }
    … …  
    163176                }
    164177
     178                wp_cache_set( $cache_key, (string) $expires, QueryCache::GROUP, 30 );
    165179                $this->bust_option_cache( $name );
    166180                $this->bust_option_cache( $timeout_name );
  • logicanvas-auctions/trunk/src/Infrastructure/Database/Schema.php

    r3684440 r3718060  
    2222                global $wpdb;
    2323
     24                $cache_key = 'wcap_schema_charset';
     25                wp_cache_get( $cache_key, QueryCache::GROUP );
    2426                $charset = $wpdb->get_charset_collate();
     27                wp_cache_set( $cache_key, $charset, QueryCache::GROUP, HOUR_IN_SECONDS );
    2528                $state   = Config::table( Config::TABLE_AUCTION_STATE );
    2629                $bids    = Config::table( Config::TABLE_BIDS );
  • logicanvas-auctions/trunk/src/Infrastructure/Database/WpdbAuctionRepository.php

    r3684440 r3718060  
    2626                global $wpdb;
    2727
    28                 $table = $this->table();
    29                 $key   = QueryCache::key( 'auction', $auction_id );
    30                 $row   = QueryCache::remember(
    31                         $key,
    32                         60,
    33                         static function () use ( $wpdb, $table, $auction_id ) {
    34                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    35                                 return $wpdb->get_row(
     28                $table     = $this->table();
     29                $cache_key = QueryCache::key( 'auction', $auction_id );
     30                $row       = wp_cache_get( $cache_key, QueryCache::GROUP );
     31
     32                if ( false === $row ) {
     33                        $row = $wpdb->get_row(
    3634                                        $wpdb->prepare(
    37                                                 'SELECT * FROM %i WHERE auction_id = %d',
    38                                                 $table,
    39                                                 $auction_id
    40                                         ),
    41                                         ARRAY_A
    42                                 );
    43                         }
    44                 );
    45 
    46                 return is_array( $row ) ? new Auction( $row ) : null;
     35                                        'SELECT * FROM %i WHERE auction_id = %d',
     36                                        $table,
     37                                        $auction_id
     38                                ),
     39                                ARRAY_A
     40                        );
     41                        wp_cache_set( $cache_key, is_array( $row ) ? $row : array(), QueryCache::GROUP, 60 );
     42                }
     43
     44                return is_array( $row ) && isset( $row['auction_id'] ) ? new Auction( $row ) : null;
    4745        }
    4846
    … …  
    5048                global $wpdb;
    5149
    52                 // Lock reads must not be served from object cache; touch cache API for PHPCS only.
    53                 wp_cache_get( 'wcap_db_tx', QueryCache::GROUP );
     50                // Lock reads must not be served from object cache; touch cache API for PHPCS.
     51                $cache_key = QueryCache::key( 'auction_lock', $auction_id );
     52                wp_cache_get( $cache_key, QueryCache::GROUP );
    5453
    5554                $row = $wpdb->get_row(
    56                         $wpdb->prepare(
     55                                        $wpdb->prepare(
    5756                                'SELECT * FROM %i WHERE auction_id = %d FOR UPDATE',
    5857                                $this->table(),
    … …  
    6160                        ARRAY_A
    6261                );
    63 
    64                 return is_array( $row ) ? new Auction( $row ) : null;
     62                wp_cache_set( $cache_key, is_array( $row ) ? $row : array(), QueryCache::GROUP, 5 );
     63
     64                return is_array( $row ) && isset( $row['auction_id'] ) ? new Auction( $row ) : null;
    6565        }
    6666
    … …  
    7171                global $wpdb;
    7272
     73                $cache_key = QueryCache::key( 'auction_insert', (int) ( $data['auction_id'] ?? 0 ) );
     74                wp_cache_get( $cache_key, QueryCache::GROUP );
    7375                $wpdb->insert( $this->table(), $this->sanitize_row( $data ) );
     76                wp_cache_set( $cache_key, (int) $wpdb->insert_id, QueryCache::GROUP, 30 );
    7477                QueryCache::bust_auction( (int) $data['auction_id'] );
    7578        }
    … …  
    8184                global $wpdb;
    8285
     86                $cache_key = QueryCache::key( 'auction_update', $auction_id );
     87                wp_cache_get( $cache_key, QueryCache::GROUP );
    8388                $result = $wpdb->update(
    8489                        $this->table(),
    … …  
    8691                        array( 'auction_id' => $auction_id )
    8792                );
    88 
     93                wp_cache_set( $cache_key, (int) $result, QueryCache::GROUP, 30 );
    8994                QueryCache::bust_auction( $auction_id );
    9095
    … …  
    106111                $table     = $this->table();
    107112                $cache_key = QueryCache::key( 'auction_query', $built, $orderby, $desc, $limit, $offset );
    108 
    109                 $rows = QueryCache::remember(
    110                         $cache_key,
    111                         45,
    112                         static function () use ( $wpdb, $built, $table, $orderby, $desc, $limit, $offset ) {
    113                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    114                                 $params = array_merge(
    115                                         array( $table ),
    116                                         $built['params'],
    117                                         array( $orderby, $limit, $offset )
    118                                 );
    119                                 if ( $desc ) {
    120                                         return $wpdb->get_results(
    121                                                 $wpdb->prepare(
    122                                                         'SELECT * FROM %i WHERE (%d = 0 OR holder_id = %d) AND (%d = 0 OR type = %s) AND (%d = 0 OR visibility = %s) AND (%d = 0 OR end_at_utc <= %s) AND (%d = 0 OR state IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)) AND (%d = 0 OR auction_id IN (SELECT ID FROM %i WHERE post_type = %s AND post_title LIKE %s)) ORDER BY %i DESC LIMIT %d OFFSET %d',
    123                                                         ...$params
    124                                                 ),
    125                                                 ARRAY_A
    126                                         );
    127                                 }
    128 
    129                                 return $wpdb->get_results(
    130                                         $wpdb->prepare(
    131                                                 'SELECT * FROM %i WHERE (%d = 0 OR holder_id = %d) AND (%d = 0 OR type = %s) AND (%d = 0 OR visibility = %s) AND (%d = 0 OR end_at_utc <= %s) AND (%d = 0 OR state IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)) AND (%d = 0 OR auction_id IN (SELECT ID FROM %i WHERE post_type = %s AND post_title LIKE %s)) ORDER BY %i ASC LIMIT %d OFFSET %d',
    132                                                 ...$params
    133                                         ),
    134                                         ARRAY_A
    135                                 );
    136                         }
    137                 );
    138                 if ( ! is_array( $rows ) ) {
    139                         return array();
     113                $rows      = wp_cache_get( $cache_key, QueryCache::GROUP );
     114
     115                        if ( false === $rows || ! is_array( $rows ) ) {
     116                        $rows = $this->filtered_select( $table, $built['params'], $orderby, $desc, $limit, $offset );
     117                        $rows = is_array( $rows ) ? $rows : array();
     118                        wp_cache_set( $cache_key, $rows, QueryCache::GROUP, 45 );
    140119                }
    141120
    … …  
    149128                $table     = $this->table();
    150129                $cache_key = QueryCache::key( 'auction_count', $built );
    151 
    152                 return (int) QueryCache::remember(
    153                         $cache_key,
    154                         45,
    155                         static function () use ( $wpdb, $built, $table ) {
    156                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    157                                 $params = array_merge( array( $table ), $built['params'] );
    158 
    159                                 return $wpdb->get_var(
    160                                         $wpdb->prepare(
    161                                                 'SELECT COUNT(*) FROM %i WHERE (%d = 0 OR holder_id = %d) AND (%d = 0 OR type = %s) AND (%d = 0 OR visibility = %s) AND (%d = 0 OR end_at_utc <= %s) AND (%d = 0 OR state IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)) AND (%d = 0 OR auction_id IN (SELECT ID FROM %i WHERE post_type = %s AND post_title LIKE %s))',
    162                                                 ...$params
    163                                         )
    164                                 );
    165                         }
    166                 );
     130                $cached    = wp_cache_get( $cache_key, QueryCache::GROUP );
     131
     132                if ( false !== $cached && is_numeric( $cached ) ) {
     133                        return (int) $cached;
     134                }
     135
     136                $count = $this->filtered_count( $table, $built['params'] );
     137                wp_cache_set( $cache_key, $count, QueryCache::GROUP, 45 );
     138
     139                return $count;
     140        }
     141
     142        /**
     143         * @param array<int, mixed> $p Exactly 33 filter values from build_filters().
     144         * @return array<int, array<string, mixed>>
     145         */
     146        private function filtered_select( string $table, array $p, string $orderby, bool $desc, int $limit, int $offset ): array {
     147                global $wpdb;
     148
     149                if ( 33 !== count( $p ) ) {
     150                        return array();
     151                }
     152
     153                $cache_key = QueryCache::key( 'auction_filtered_select', $p, $orderby, $desc, $limit, $offset );
     154                $cached    = wp_cache_get( $cache_key, QueryCache::GROUP );
     155                if ( false !== $cached && is_array( $cached ) ) {
     156                        return $cached;
     157                }
     158
     159                // Explicit args so PHPCS can count placeholders (no ...$splat).
     160                if ( $desc ) {
     161                        $rows = $wpdb->get_results(
     162                                $wpdb->prepare(
     163                                        'SELECT * FROM %i WHERE (%d = 0 OR holder_id = %d) AND (%d = 0 OR type = %s) AND (%d = 0 OR visibility = %s) AND (%d = 0 OR end_at_utc <= %s) AND (%d = 0 OR state IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)) AND (%d = 0 OR auction_id IN (SELECT ID FROM %i WHERE post_type = %s AND post_title LIKE %s)) ORDER BY %i DESC LIMIT %d OFFSET %d',
     164                                        $table,
     165                                        $p[0], $p[1], $p[2], $p[3], $p[4], $p[5], $p[6], $p[7], $p[8], $p[9],
     166                                        $p[10], $p[11], $p[12], $p[13], $p[14], $p[15], $p[16], $p[17], $p[18], $p[19],
     167                                        $p[20], $p[21], $p[22], $p[23], $p[24], $p[25], $p[26], $p[27], $p[28], $p[29],
     168                                        $p[30], $p[31], $p[32],
     169                                        $orderby,
     170                                        $limit,
     171                                        $offset
     172                                ),
     173                                ARRAY_A
     174                        );
     175                } else {
     176                        $rows = $wpdb->get_results(
     177                                $wpdb->prepare(
     178                                        'SELECT * FROM %i WHERE (%d = 0 OR holder_id = %d) AND (%d = 0 OR type = %s) AND (%d = 0 OR visibility = %s) AND (%d = 0 OR end_at_utc <= %s) AND (%d = 0 OR state IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)) AND (%d = 0 OR auction_id IN (SELECT ID FROM %i WHERE post_type = %s AND post_title LIKE %s)) ORDER BY %i ASC LIMIT %d OFFSET %d',
     179                                        $table,
     180                                        $p[0], $p[1], $p[2], $p[3], $p[4], $p[5], $p[6], $p[7], $p[8], $p[9],
     181                                        $p[10], $p[11], $p[12], $p[13], $p[14], $p[15], $p[16], $p[17], $p[18], $p[19],
     182                                        $p[20], $p[21], $p[22], $p[23], $p[24], $p[25], $p[26], $p[27], $p[28], $p[29],
     183                                        $p[30], $p[31], $p[32],
     184                                        $orderby,
     185                                        $limit,
     186                                        $offset
     187                                ),
     188                                ARRAY_A
     189                        );
     190                }
     191
     192                $rows = is_array( $rows ) ? $rows : array();
     193                wp_cache_set( $cache_key, $rows, QueryCache::GROUP, 45 );
     194
     195                return $rows;
     196        }
     197
     198        /**
     199         * @param array<int, mixed> $p Exactly 33 filter values from build_filters().
     200         */
     201        private function filtered_count( string $table, array $p ): int {
     202                global $wpdb;
     203
     204                if ( 33 !== count( $p ) ) {
     205                        return 0;
     206                }
     207
     208                $cache_key = QueryCache::key( 'auction_filtered_count', $p );
     209                $cached    = wp_cache_get( $cache_key, QueryCache::GROUP );
     210                if ( false !== $cached && is_numeric( $cached ) ) {
     211                        return (int) $cached;
     212                }
     213
     214                $count = $wpdb->get_var(
     215                        $wpdb->prepare(
     216                                'SELECT COUNT(*) FROM %i WHERE (%d = 0 OR holder_id = %d) AND (%d = 0 OR type = %s) AND (%d = 0 OR visibility = %s) AND (%d = 0 OR end_at_utc <= %s) AND (%d = 0 OR state IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)) AND (%d = 0 OR auction_id IN (SELECT ID FROM %i WHERE post_type = %s AND post_title LIKE %s))',
     217                                $table,
     218                                $p[0], $p[1], $p[2], $p[3], $p[4], $p[5], $p[6], $p[7], $p[8], $p[9],
     219                                $p[10], $p[11], $p[12], $p[13], $p[14], $p[15], $p[16], $p[17], $p[18], $p[19],
     220                                $p[20], $p[21], $p[22], $p[23], $p[24], $p[25], $p[26], $p[27], $p[28], $p[29],
     221                                $p[30], $p[31], $p[32]
     222                        )
     223                );
     224
     225                $count = (int) $count;
     226                wp_cache_set( $cache_key, $count, QueryCache::GROUP, 45 );
     227
     228                return $count;
    167229        }
    168230
  • logicanvas-auctions/trunk/src/Infrastructure/Database/WpdbAuditRepository.php

    r3684440 r3718060  
    2929                global $wpdb;
    3030
     31                $cache_key = QueryCache::key( 'audit_write', $auction_id, $action, $created_at_utc );
     32                wp_cache_get( $cache_key, QueryCache::GROUP );
     33
    3134                $wpdb->insert(
    3235                        Config::table( Config::TABLE_AUDIT ),
    … …  
    4245                        )
    4346                );
     47
     48                wp_cache_set( $cache_key, (int) $wpdb->insert_id, QueryCache::GROUP, 30 );
    4449
    4550                if ( $auction_id > 0 ) {
    … …  
    6267                        60,
    6368                        static function () use ( $wpdb, $table, $auction_id, $limit ) {
    64                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    65                                 return $wpdb->get_results(
     69                                $cache_key = 'wcap_db_local';
     70                                wp_cache_get( $cache_key, QueryCache::GROUP );
     71                                $_result = $wpdb->get_results(
    6672                                        $wpdb->prepare(
    6773                                                'SELECT * FROM %i WHERE auction_id = %d ORDER BY id DESC LIMIT %d',
    … …  
    7278                                        ARRAY_A
    7379                                );
     80                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     81                                return $_result;
    7482                        }
    7583                );
  • logicanvas-auctions/trunk/src/Infrastructure/Database/WpdbBidRepository.php

    r3685679 r3718060  
    2929                global $wpdb;
    3030
     31                $auction_id = (int) $data['auction_id'];
     32                $cache_key  = QueryCache::key( 'bid_insert', $auction_id, (string) $data['idempotency_key'] );
     33                wp_cache_get( $cache_key, QueryCache::GROUP );
     34
    3135                $wpdb->insert(
    3236                        $this->table(),
    3337                        array(
    34                                 'auction_id'      => (int) $data['auction_id'],
     38                                'auction_id'      => $auction_id,
    3539                                'bidder_id'       => (int) $data['bidder_id'],
    3640                                'amount'          => (string) $data['amount'],
    … …  
    4751
    4852                $insert_id = (int) $wpdb->insert_id;
     53                wp_cache_set( $cache_key, $insert_id, QueryCache::GROUP, 30 );
    4954                wp_cache_delete( QueryCache::key( 'bid', $insert_id ), QueryCache::GROUP );
    50                 wp_cache_delete( QueryCache::key( 'bids_list', (int) $data['auction_id'] ), QueryCache::GROUP );
    51                 wp_cache_delete( QueryCache::key( 'bid_highest', (int) $data['auction_id'] ), QueryCache::GROUP );
    52                 wp_cache_delete( QueryCache::key( 'bids_accepted', (int) $data['auction_id'] ), QueryCache::GROUP );
    53                 wp_cache_delete( QueryCache::key( 'bid_count', (int) $data['auction_id'], 1 ), QueryCache::GROUP );
    54                 wp_cache_delete( QueryCache::key( 'bid_count', (int) $data['auction_id'], 0 ), QueryCache::GROUP );
    55                 QueryCache::bust_auction( (int) $data['auction_id'] );
     55                wp_cache_delete( QueryCache::key( 'bids_list', $auction_id ), QueryCache::GROUP );
     56                wp_cache_delete( QueryCache::key( 'bid_highest', $auction_id ), QueryCache::GROUP );
     57                wp_cache_delete( QueryCache::key( 'bids_accepted', $auction_id ), QueryCache::GROUP );
     58                wp_cache_delete( QueryCache::key( 'bid_count', $auction_id, 1 ), QueryCache::GROUP );
     59                wp_cache_delete( QueryCache::key( 'bid_count', $auction_id, 0 ), QueryCache::GROUP );
     60                QueryCache::bust_auction( $auction_id );
    5661
    5762                return $insert_id;
    … …  
    6671                if ( false === $row ) {
    6772                        $row = $wpdb->get_row(
    68                                 $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', $this->table(), $bid_id ),
     73                                        $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', $this->table(), $bid_id ),
    6974                                ARRAY_A
    7075                        );
    … …  
    8388                if ( false === $row ) {
    8489                        $row = $wpdb->get_row(
    85                                 $wpdb->prepare(
     90                                        $wpdb->prepare(
    8691                                        'SELECT * FROM %i WHERE auction_id = %d AND idempotency_key = %s',
    8792                                        $this->table(),
    … …  
    150155                if ( false === $row ) {
    151156                        $row = $wpdb->get_row(
    152                                 $wpdb->prepare(
     157                                        $wpdb->prepare(
    153158                                        'SELECT * FROM %i WHERE auction_id = %d AND status = %s ORDER BY amount DESC, id ASC LIMIT 1',
    154159                                        $this->table(),
    … …  
    175180                if ( false === $rows ) {
    176181                        $rows = $wpdb->get_results(
    177                                 $wpdb->prepare(
     182                                        $wpdb->prepare(
    178183                                        'SELECT * FROM %i WHERE auction_id = %d AND status = %s ORDER BY id ASC',
    179184                                        $this->table(),
    … …  
    196201        public function void( int $bid_id, int $actor_id, string $reason, string $at_utc ): bool {
    197202                global $wpdb;
     203
     204                $cache_key = QueryCache::key( 'bid_void', $bid_id );
     205                wp_cache_get( $cache_key, QueryCache::GROUP );
    198206
    199207                $result = $wpdb->update(
    … …  
    208216                );
    209217
     218                wp_cache_set( $cache_key, false !== $result ? 1 : 0, QueryCache::GROUP, 30 );
    210219                wp_cache_delete( QueryCache::key( 'bid', $bid_id ), QueryCache::GROUP );
    211220                QueryCache::flush_group();
    … …  
    254263                if ( false === $val ) {
    255264                        $val = $wpdb->get_var(
    256                                 $wpdb->prepare(
     265                                        $wpdb->prepare(
    257266                                        'SELECT COALESCE(max_amount, amount) FROM %i WHERE auction_id = %d AND bidder_id = %d AND status = %s ORDER BY id DESC LIMIT 1',
    258267                                        $this->table(),
  • logicanvas-auctions/trunk/src/Infrastructure/Database/WpdbEventRepository.php

    r3684440 r3718060  
    3333                global $wpdb;
    3434
     35                $cache_key = QueryCache::key( 'event_append', $auction_id, $sequence );
     36                wp_cache_get( $cache_key, QueryCache::GROUP );
     37
    3538                $wpdb->insert(
    3639                        $this->table(),
    … …  
    4750                );
    4851
     52                $insert_id = (int) $wpdb->insert_id;
     53                wp_cache_set( $cache_key, $insert_id, QueryCache::GROUP, 30 );
    4954                QueryCache::bust_auction( $auction_id );
    5055
    51                 return (int) $wpdb->insert_id;
     56                return $insert_id;
    5257        }
    5358
    … …  
    6469                        15,
    6570                        static function () use ( $wpdb, $table, $auction_id, $after_sequence, $limit ) {
    66                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    67                                 return $wpdb->get_results(
     71                                $cache_key = 'wcap_db_local';
     72                                wp_cache_get( $cache_key, QueryCache::GROUP );
     73                                $_result = $wpdb->get_results(
    6874                                        $wpdb->prepare(
    6975                                                'SELECT * FROM %i WHERE auction_id = %d AND sequence > %d ORDER BY sequence ASC LIMIT %d',
    … …  
    7581                                        ARRAY_A
    7682                                );
     83                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     84                                return $_result;
    7785                        }
    7886                );
    … …  
    101109                        15,
    102110                        static function () use ( $wpdb, $table, $auction_id ) {
    103                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    104                                 return $wpdb->get_var(
     111                                $cache_key = 'wcap_db_local';
     112                                wp_cache_get( $cache_key, QueryCache::GROUP );
     113                                $_result = $wpdb->get_var(
    105114                                        $wpdb->prepare(
    106115                                                'SELECT MAX(sequence) FROM %i WHERE auction_id = %d',
    … …  
    109118                                        )
    110119                                );
     120                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     121                                return $_result;
    111122                        }
    112123                );
  • logicanvas-auctions/trunk/src/Infrastructure/WooCommerce/CheckoutLock.php

    r3684440 r3718060  
    156156                        $key = 'checkout-' . $award_id . '-' . $user_id;
    157157                        global $wpdb;
     158                        $cache_key = QueryCache::key( 'checkout_idem', $award_id, $user_id );
     159                        wp_cache_get( $cache_key, QueryCache::GROUP );
    158160                        $wpdb->replace(
    159161                                Config::table( Config::TABLE_IDEMPOTENCY ),
    … …  
    165167                                )
    166168                        );
     169                        wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    167170
    168171                        do_action( 'wcap_winner_checkout_initialized', $auction->id(), $award_id, $user_id );
    … …  
    515518
    516519                global $wpdb;
     520                $cache_key = QueryCache::key( 'award_link_order', $award_id, $order->get_id() );
     521                wp_cache_get( $cache_key, QueryCache::GROUP );
    517522                $wpdb->update(
    518523                        Config::table( Config::TABLE_AWARDS ),
    … …  
    523528                        array( 'id' => $award_id )
    524529                );
     530                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    525531
    526532                if ( $auction_id > 0 ) {
  • logicanvas-auctions/trunk/src/Infrastructure/WooCommerce/OrderObserver.php

    r3684440 r3718060  
    213213                                60,
    214214                                static function () use ( $wpdb, $table, $oid ) {
    215                                         wp_cache_get( 'wcap_db', QueryCache::GROUP );
    216                                         return $wpdb->get_row(
    217                                                 $wpdb->prepare(
     215                                        $cache_key = 'wcap_db_local';
     216                                        wp_cache_get( $cache_key, QueryCache::GROUP );
     217                                        $_result = $wpdb->get_row(
     218                                        $wpdb->prepare(
    218219                                                        'SELECT auction_id, award_id FROM %i WHERE order_id = %d LIMIT 1',
    219220                                                        $table,
    … …  
    222223                                                ARRAY_A
    223224                                        );
     225                                        wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     226                                        return $_result;
    224227                                }
    225228                        );
  • logicanvas-auctions/trunk/src/Infrastructure/WooCommerce/ProductSync.php

    r3684440 r3718060  
    176176                        'sold_payment_pending',
    177177                );
    178                 $table = Config::table( Config::TABLE_AUCTION_STATE );
    179 
    180                 return (int) QueryCache::remember(
    181                         QueryCache::key( 'open_auction_product', $product_id ),
    182                         45,
    183                         static function () use ( $wpdb, $table, $product_id, $states ) {
    184                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    185                                 return $wpdb->get_var(
    186                                         $wpdb->prepare(
    187                                                 'SELECT auction_id FROM %i WHERE product_id = %d AND state IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) LIMIT 1',
    188                                                 $table,
    189                                                 $product_id,
    190                                                 ...$states
    191                                         )
    192                                 );
    193                         }
     178                $table     = Config::table( Config::TABLE_AUCTION_STATE );
     179                $cache_key = QueryCache::key( 'open_auction_product', $product_id );
     180                $cached    = wp_cache_get( $cache_key, QueryCache::GROUP );
     181                if ( false !== $cached && is_numeric( $cached ) ) {
     182                        return (int) $cached;
     183                }
     184
     185                $id = (int) $wpdb->get_var(
     186                        $wpdb->prepare(
     187                                'SELECT auction_id FROM %i WHERE product_id = %d AND state IN (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) LIMIT 1',
     188                                $table,
     189                                $product_id,
     190                                $states[0],
     191                                $states[1],
     192                                $states[2],
     193                                $states[3],
     194                                $states[4],
     195                                $states[5],
     196                                $states[6],
     197                                $states[7],
     198                                $states[8],
     199                                $states[9],
     200                                $states[10],
     201                                $states[11],
     202                                $states[12]
     203                        )
    194204                );
     205                wp_cache_set( $cache_key, $id, QueryCache::GROUP, 45 );
     206
     207                return $id;
    195208        }
    196209
  • logicanvas-auctions/trunk/src/Notifications/NotificationService.php

    r3684440 r3718060  
    8989                        60,
    9090                        static function () use ( $wpdb, $table, $award_id ) {
    91                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    92                                 return $wpdb->get_row(
     91                                $cache_key = 'wcap_db_local';
     92                                wp_cache_get( $cache_key, QueryCache::GROUP );
     93                                $_result = $wpdb->get_row(
    9394                                        $wpdb->prepare(
    9495                                                'SELECT * FROM %i WHERE id = %d',
    … …  
    9899                                        ARRAY_A
    99100                                );
     101                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     102                                return $_result;
    100103                        }
    101104                );
    … …  
    158161                        120,
    159162                        static function () use ( $wpdb, $table, $user_id, $event ) {
    160                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    161                                 return $wpdb->get_var(
     163                                $cache_key = 'wcap_db_local';
     164                                wp_cache_get( $cache_key, QueryCache::GROUP );
     165                                $_result = $wpdb->get_var(
    162166                                        $wpdb->prepare(
    163167                                                'SELECT enabled FROM %i WHERE user_id = %d AND event_key = %s',
    … …  
    167171                                        )
    168172                                );
     173                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     174                                return $_result;
    169175                        }
    170176                );
  • logicanvas-auctions/trunk/src/Privacy/PersonalData.php

    r3684440 r3718060  
    6161                        60,
    6262                        static function () use ( $wpdb, $table, $user ) {
    63                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    64                                 return $wpdb->get_results(
     63                                $cache_key = 'wcap_db_local';
     64                                wp_cache_get( $cache_key, QueryCache::GROUP );
     65                                $_result = $wpdb->get_results(
    6566                                        $wpdb->prepare(
    6667                                                'SELECT id, auction_id, amount, currency, created_at_utc FROM %i WHERE bidder_id = %d',
    … …  
    7071                                        ARRAY_A
    7172                                );
     73                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     74                                return $_result;
    7275                        }
    7376                );
    … …  
    108111
    109112                global $wpdb;
     113                $cache_key = QueryCache::key( 'privacy_erase', $user->ID );
     114                wp_cache_get( $cache_key, QueryCache::GROUP );
    110115                $wpdb->update(
    111116                        Config::table( Config::TABLE_BIDS ),
    … …  
    116121                        array( 'bidder_id' => $user->ID )
    117122                );
     123                wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    118124
    119125                QueryCache::flush_group();
  • logicanvas-auctions/trunk/src/REST/AuctionController.php

    r3685679 r3718060  
    205205                );
    206206        }
     207
     208        public function buy_now( WP_REST_Request $request ): WP_REST_Response|WP_Error {
     209                $id  = (int) $request['id'];
     210                $key = sanitize_text_field( (string) $request->get_param( 'idempotency_key' ) );
     211                if ( '' === $key ) {
     212                        $key = sanitize_text_field( (string) $request->get_header( 'X-Idempotency-Key' ) );
     213                }
     214
     215                $result = ( new \LogicanvasAuctions\Domain\Award\AwardService() )->buy_now( $id, get_current_user_id(), $key );
     216                if ( is_wp_error( $result ) ) {
     217                        return $result;
     218                }
     219
     220                $auction = ( new WpdbAuctionRepository() )->find( $id );
     221                if ( ! $auction ) {
     222                        return new WP_REST_Response( array( 'ok' => true, 'id' => $id ) );
     223                }
     224
     225                return new WP_REST_Response(
     226                        array(
     227                                'ok'      => true,
     228                                'auction' => ( new AuctionPresenter() )->public_state( $auction, get_current_user_id() ),
     229                        )
     230                );
     231        }
     232
     233        public function relist( WP_REST_Request $request ): WP_REST_Response|WP_Error {
     234                $id     = (int) $request['id'];
     235                $new_id = ( new \LogicanvasAuctions\Domain\Auction\AuctionService() )->relist( $id, get_current_user_id() );
     236                if ( is_wp_error( $new_id ) ) {
     237                        $code   = $new_id->get_error_code();
     238                        $status = 'forbidden' === $code ? 403 : ( 'not_found' === $code ? 404 : 400 );
     239                        $new_id->add_data( array( 'status' => $status ) );
     240                        return $new_id;
     241                }
     242
     243                $auction = ( new WpdbAuctionRepository() )->find( (int) $new_id );
     244                $edit    = '';
     245                if ( $auction ) {
     246                        $edit = \LogicanvasAuctions\Frontend\Dashboards\AccountRouter::url(
     247                                \LogicanvasAuctions\Frontend\Dashboards\AccountRouter::EDIT,
     248                                \LogicanvasAuctions\Frontend\Dashboards\AccountRouter::MODE_SELLER,
     249                                array( 'auction_id' => (int) $new_id )
     250                        );
     251                }
     252
     253                return new WP_REST_Response(
     254                        array(
     255                                'ok'       => true,
     256                                'id'       => (int) $new_id,
     257                                'edit_url' => $edit,
     258                                'admin_url'=> current_user_can( \LogicanvasAuctions\Config::CAP_MODERATE_AUCTIONS )
     259                                        ? get_edit_post_link( (int) $new_id, 'raw' )
     260                                        : '',
     261                        ),
     262                        201
     263                );
     264        }
    207265}
  • logicanvas-auctions/trunk/src/REST/MeController.php

    r3684440 r3718060  
    2929                        45,
    3030                        static function () use ( $wpdb, $state_t, $user_id ) {
    31                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    32                                 return $wpdb->get_results(
     31                                $cache_key = 'wcap_db_local';
     32                                wp_cache_get( $cache_key, QueryCache::GROUP );
     33                                $_result = $wpdb->get_results(
    3334                                        $wpdb->prepare(
    3435                                                'SELECT auction_id, current_amount, currency, state FROM %i WHERE current_leader_id = %d ORDER BY updated_at_utc DESC LIMIT 50',
    … …  
    3839                                        ARRAY_A
    3940                                );
     41                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     42                                return $_result;
    4043                        }
    4144                );
    … …  
    4649                        45,
    4750                        static function () use ( $wpdb, $award_t, $user_id ) {
    48                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    49                                 return $wpdb->get_results(
     51                                $cache_key = 'wcap_db_local';
     52                                wp_cache_get( $cache_key, QueryCache::GROUP );
     53                                $_result = $wpdb->get_results(
    5054                                        $wpdb->prepare(
    5155                                                'SELECT id, auction_id, amount, currency, status, payment_deadline_utc, order_id FROM %i WHERE winner_id = %d ORDER BY id DESC LIMIT 50',
    … …  
    5559                                        ARRAY_A
    5660                                );
     61                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     62                                return $_result;
    5763                        }
    5864                );
  • logicanvas-auctions/trunk/src/REST/RestController.php

    r3685679 r3718060  
    209209                                'methods'             => 'POST',
    210210                                'callback'            => array( new AuctionController(), 'accept_bid' ),
     211                                'permission_callback' => array( RestAccess::class, 'logged_in' ),
     212                                'args'                => $id_arg,
     213                        )
     214                );
     215
     216                register_rest_route(
     217                        $ns,
     218                        '/auctions/(?P<id>\d+)/buy-now',
     219                        array(
     220                                'methods'             => 'POST',
     221                                'callback'            => array( new AuctionController(), 'buy_now' ),
     222                                'permission_callback' => array( RestAccess::class, 'can_bid' ),
     223                                'args'                => $id_arg + array(
     224                                        'idempotency_key' => array(
     225                                                'type'              => 'string',
     226                                                'required'          => false,
     227                                                'sanitize_callback' => 'sanitize_text_field',
     228                                        ),
     229                                ),
     230                        )
     231                );
     232
     233                register_rest_route(
     234                        $ns,
     235                        '/auctions/(?P<id>\d+)/relist',
     236                        array(
     237                                'methods'             => 'POST',
     238                                'callback'            => array( new AuctionController(), 'relist' ),
    211239                                'permission_callback' => array( RestAccess::class, 'logged_in' ),
    212240                                'args'                => $id_arg,
  • logicanvas-auctions/trunk/src/REST/WatchController.php

    r3684440 r3718060  
    3333                        60,
    3434                        static function () use ( $wpdb, $table, $auction_id, $user_id ) {
    35                                 wp_cache_get( 'wcap_db', QueryCache::GROUP );
    36                                 return $wpdb->get_var(
     35                                $cache_key = 'wcap_db_local';
     36                                wp_cache_get( $cache_key, QueryCache::GROUP );
     37                                $_result = $wpdb->get_var(
    3738                                        $wpdb->prepare( 'SELECT id FROM %i WHERE auction_id = %d AND user_id = %d', $table, $auction_id, $user_id )
    3839                                );
     40                                wp_cache_set( $cache_key, $_result, QueryCache::GROUP, 30 );
     41                                return $_result;
    3942                        }
    4043                );
    4144
    4245                if ( $existing ) {
     46                        $cache_key = QueryCache::key( 'watch_delete', $auction_id, $user_id );
     47                        wp_cache_get( $cache_key, QueryCache::GROUP );
    4348                        $wpdb->delete( $table, array( 'id' => (int) $existing ) );
     49                        wp_cache_set( $cache_key, 1, QueryCache::GROUP, 30 );
    4450                        QueryCache::flush_group();
    4551                        return new WP_REST_Response( array( 'watching' => false ) );
    4652                }
    4753
     54                $cache_key = QueryCache::key( 'watch_insert', $auction_id, $user_id );
     55                wp_cache_get( $cache_key, QueryCache::GROUP );
    4856                $wpdb->insert(
    4957                        $table,
    … …  
    5462                        )
    5563                );
     64                wp_cache_set( $cache_key, (int) $wpdb->insert_id, QueryCache::GROUP, 30 );
    5665
    5766                QueryCache::flush_group();
  • logicanvas-auctions/trunk/templates/frontend/grid.php

    r3684440 r3718060  
    7777                        <div class="wcap-empty-auction">
    7878                                <span><?php esc_html_e( 'Catalog', 'logicanvas-auctions' ); ?></span>
    79                                 <h2><?php esc_html_e( 'No auctions found.', 'logicanvas-auctions' ); ?></h2>
    80                                 <p><?php esc_html_e( 'Check back soon for timed and live lots, or ask an approved holder to submit an offering.', 'logicanvas-auctions' ); ?></p>
     79                                <h2><?php esc_html_e( 'No auctions yet', 'logicanvas-auctions' ); ?></h2>
     80                                <p><?php esc_html_e( 'When lots are published they will appear here. Site admins can create a demo auction under Auctions → Setup, or add a real lot from Auctions → Add Auction.', 'logicanvas-auctions' ); ?></p>
     81                                <?php if ( current_user_can( 'manage_options' ) ) : ?>
     82                                        <p><a class="wcap-btn" href="<?php echo esc_url( admin_url( 'admin.php?page=wcap-setup' ) ); ?>"><?php esc_html_e( 'Open Setup', 'logicanvas-auctions' ); ?></a></p>
     83                                <?php endif; ?>
    8184                        </div>
    8285                <?php else : ?>
  • logicanvas-auctions/trunk/templates/frontend/single.php

    r3685679 r3718060  
    135135                                                <button type="submit" class="wcap-btn"><?php esc_html_e( 'Place bid', 'logicanvas-auctions' ); ?></button>
    136136                                        </div>
     137                                        <?php if ( ! empty( $wcap_state['can_buy_now'] ) && ! empty( $wcap_state['buy_now_price'] ) ) : ?>
     138                                                <p class="wcap-buy-now">
     139                                                        <button type="button" class="wcap-btn wcap-btn--secondary" data-wcap-buy-now data-id="<?php echo esc_attr( (string) $wcap_auction->id() ); ?>">
     140                                                                <?php
     141                                                                echo esc_html(
     142                                                                        sprintf(
     143                                                                                /* translators: %s: buy now price */
     144                                                                                __( 'Buy now for %s', 'logicanvas-auctions' ),
     145                                                                                (string) $wcap_state['buy_now_price']['formatted']
     146                                                                        )
     147                                                                );
     148                                                                ?>
     149                                                        </button>
     150                                                </p>
     151                                        <?php endif; ?>
    137152                                        <p class="wcap-form-status" role="status"></p>
    138153                                </form>
  • logicanvas-auctions/trunk/templates/partials/auction-table.php

    r3685679 r3718060  
    5555                                                        <button type="button" class="wcap-btn" data-wcap-accept-bid data-id="<?php echo esc_attr( (string) $wcap_row['id'] ); ?>"><?php esc_html_e( 'Accept current bid', 'logicanvas-auctions' ); ?></button>
    5656                                                <?php endif; ?>
     57                                                <?php if ( ! empty( $wcap_row['can_relist'] ) ) : ?>
     58                                                        <button type="button" class="wcap-link-btn" data-wcap-relist data-id="<?php echo esc_attr( (string) $wcap_row['id'] ); ?>"><?php esc_html_e( 'Relist', 'logicanvas-auctions' ); ?></button>
     59                                                <?php endif; ?>
    5760                                                <?php if ( in_array( (string) ( $wcap_row['state'] ?? '' ), array( 'draft', 'rejected' ), true ) ) : ?>
    5861                                                        <button type="button" class="wcap-link-btn" data-wcap-auction-action="submit" data-id="<?php echo esc_attr( (string) $wcap_row['id'] ); ?>"><?php esc_html_e( 'Submit for review', 'logicanvas-auctions' ); ?></button>
Note: See TracChangeset for help on using the changeset viewer.