getId(); if ( $id <= 0 ) { return; } update_post_meta( $id, self::META_TOTAL, (string) round( (float) $quote->getTotal(), 2 ) ); InvoiceTotalsCache::bumpVersion(); } public static function invalidate( int $quote_id ): void { delete_post_meta( $quote_id, self::META_TOTAL ); delete_option( self::OPTION_COMPLETE ); InvoiceTotalsCache::bumpVersion(); } public static function missingCount(): int { global $wpdb; return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} c ON c.post_id = p.ID AND c.meta_key = %s WHERE p.post_type = 'easy_invoice_quote' AND p.post_status = 'publish' AND c.meta_id IS NULL", self::META_TOTAL ) ); } /** @return int Still missing afterwards. */ public static function backfill( int $limit = 200 ): int { global $wpdb; $ids = array_map( 'intval', (array) $wpdb->get_col( $wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} c ON c.post_id = p.ID AND c.meta_key = %s WHERE p.post_type = 'easy_invoice_quote' AND p.post_status = 'publish' AND c.meta_id IS NULL ORDER BY p.ID DESC LIMIT %d", self::META_TOTAL, max( 1, $limit ) ) ) ); if ( ! $ids ) { return 0; } update_meta_cache( 'post', $ids ); $repository = \EasyInvoice\Providers\QuoteServiceProvider::getQuoteRepository(); foreach ( $ids as $id ) { $quote = $repository->find( $id ); if ( $quote ) { self::store( $quote ); } else { update_post_meta( $id, self::META_TOTAL, '0' ); } wp_cache_delete( $id, 'post_meta' ); } return self::missingCount(); } public static function scheduleBackfill(): void { if ( ! wp_next_scheduled( self::CRON_HOOK ) ) { wp_schedule_single_event( time() + 5, self::CRON_HOOK ); } } public static function cronBackfill(): void { if ( self::backfill( 500 ) > 0 ) { wp_schedule_single_event( time() + 10, self::CRON_HOOK ); } else { update_option( self::OPTION_COMPLETE, '1', false ); } } private static function ensure(): void { if ( '1' === get_option( self::OPTION_COMPLETE, '' ) ) { return; } if ( self::missingCount() > 0 && self::backfill( 200 ) > 0 ) { self::scheduleBackfill(); return; } update_option( self::OPTION_COMPLETE, '1', false ); } /** * Quote value by currency for VALUE_STATUSES. Memoised until the next document write. * * @return array */ public static function valueByCurrency(): array { $version = (int) get_option( 'easy_invoice_stats_version', 1 ); $hit = get_transient( 'ei_stats_quote_value' ); if ( is_array( $hit ) && isset( $hit['v'], $hit['d'] ) && (int) $hit['v'] === $version ) { return $hit['d']; } global $wpdb; self::ensure(); $site = strtoupper( (string) get_option( 'easy_invoice_currency_code', 'USD' ) ); $in = "'" . implode( "','", array_map( 'esc_sql', self::VALUE_STATUSES ) ) . "'"; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- statuses constant, escaped. $rows = $wpdb->get_results( $wpdb->prepare( "SELECT UPPER(COALESCE(NULLIF(NULLIF(cur.meta_value, ''), 'global'), %s)) AS currency, COUNT(*) AS n, SUM(CAST(c.meta_value AS DECIMAL(18,4))) AS amount FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} s ON s.post_id = p.ID AND s.meta_key = '_easy_invoice_quote_status' AND s.meta_value IN ({$in}) INNER JOIN {$wpdb->postmeta} c ON c.post_id = p.ID AND c.meta_key = %s LEFT JOIN {$wpdb->postmeta} cur ON cur.post_id = p.ID AND cur.meta_key = '_easy_invoice_quote_currency_code' WHERE p.post_type = 'easy_invoice_quote' AND p.post_status = 'publish' GROUP BY currency", $site, self::META_TOTAL ), ARRAY_A ); // phpcs:enable $out = []; foreach ( (array) $rows as $r ) { $out[ (string) $r['currency'] ] = [ 'amount' => round( (float) $r['amount'], 2 ), 'count' => (int) $r['n'] ]; } set_transient( 'ei_stats_quote_value', [ 'v' => $version, 'd' => $out ], 10 * MINUTE_IN_SECONDS ); return $out; } }