repository = $repository; $this->calculator = $calculator; } /** * Recalculates from the history and stores the result. */ public function refresh( \WC_Product $product ): Reference { $intervals = $this->repository->get_intervals( $product->get_id(), PriceResolver::currency() ); $reference = $this->calculator->calculate( $intervals, time(), Settings::window_days(), Settings::mode(), CouponCampaigns::extra_intervals( $product, $intervals ) ); $this->store( $product->get_id(), $reference ); /** * Fires after the reference price of a product was recalculated. * * @param int $product_id * @param Reference $reference */ do_action( 'strikewise_reference_refreshed', $product->get_id(), $reference ); return $reference; } /** * Cached reference; recalculated if missing, made with other settings, * or (in "always" mode) calculated before today, because that window slides daily. */ public function get( \WC_Product $product ): Reference { $id = $product->get_id(); $calc = (int) get_post_meta( $id, self::META_CALC_AT, true ); $stale = 0 === $calc || get_post_meta( $id, self::META_CALC_VER, true ) !== self::settings_version(); $stale = $stale || ( ReferenceCalculator::MODE_ALWAYS === Settings::mode() && wp_date( 'Y-m-d', $calc ) !== wp_date( 'Y-m-d' ) ); if ( $stale ) { return $this->refresh( $product ); } $price = get_post_meta( $id, self::META_REF, true ); $t0 = get_post_meta( $id, self::META_T0, true ); return new Reference( (string) get_post_meta( $id, self::META_STATUS, true ), '' === $price ? null : (float) $price, '' === $t0 ? null : Repository::to_timestamp( $t0 ), (bool) get_post_meta( $id, self::META_COMPLETE, true ) ); } private function store( int $id, Reference $reference ): void { update_post_meta( $id, self::META_STATUS, $reference->status ); update_post_meta( $id, self::META_REF, null === $reference->price ? '' : (string) $reference->price ); update_post_meta( $id, self::META_COMPLETE, $reference->complete ? '1' : '' ); update_post_meta( $id, self::META_CALC_AT, (string) time() ); update_post_meta( $id, self::META_CALC_VER, self::settings_version() ); delete_post_meta( $id, self::META_CAMPAIGN ); // The cleanup job protects history rows of products with a running reduction via this key. if ( null !== $reference->t0 ) { update_post_meta( $id, self::META_T0, Repository::to_datetime( $reference->t0 ) ); } else { delete_post_meta( $id, self::META_T0 ); } } /** * Lowest price of the 30 days before a running public coupon campaign, if the * product costs more now (its price was raised before the campaign). Then the * shop has to show that lower price with the campaign (Commission guidance, 2.2). */ public function campaign_reference( \WC_Product $product ): ?float { $campaigns = CouponCampaigns::active_for( $product ); if ( ! $campaigns ) { return null; } // Cached per product: storefront lists call this for every product while a campaign runs. $key = CouponCampaigns::version(); $cached = (string) get_post_meta( $product->get_id(), self::META_CAMPAIGN, true ); if ( 0 === strpos( $cached, $key . '|' ) ) { $value = substr( $cached, strlen( $key ) + 1 ); return '' === $value ? null : (float) $value; } $value = $this->compute_campaign_reference( $product, (int) min( array_column( $campaigns, 'from' ) ) ); update_post_meta( $product->get_id(), self::META_CAMPAIGN, $key . '|' . ( null === $value ? '' : (string) $value ) ); return $value; } private function compute_campaign_reference( \WC_Product $product, int $start ): ?float { $reference = $this->calculator->calculate( $this->repository->get_intervals( $product->get_id(), PriceResolver::currency() ), $start, Settings::window_days(), ReferenceCalculator::MODE_ALWAYS ); $snapshot = PriceResolver::snapshot( $product ); if ( ! $reference->has_price() || null === $snapshot || (float) $reference->price >= $snapshot['price'] - 0.005 ) { return null; } return (float) $reference->price; } /** * Reduced since the product was added: it was never offered at a higher price, * so there is nothing it could be reduced from. */ public static function launched_reduced( \WC_Product $product, Reference $reference ): bool { $created = $product->get_date_created(); return Reference::STATUS_NO_HISTORY === $reference->status && null !== $reference->t0 && $created instanceof \WC_DateTime && $created->getTimestamp() >= $reference->t0 - DAY_IN_SECONDS; } private static function settings_version(): string { return Settings::mode() . '|' . Settings::window_days() . '|' . CouponCampaigns::version(); } }