repo = $repo ? $repo : new PriceRevisionRepository(); } /** * Observe a WC product or variation. * * @param \WC_Product $product Product. * @param string $source capture_source. * @param string $continuity continuous|gap_detected. * @return array{changed:bool,id?:int} */ public function observe_product( $product, $source = 'admin', $continuity = 'continuous' ) { if ( ! $product || ! is_object( $product ) ) { return array( 'changed' => false ); } $is_variation = method_exists( $product, 'is_type' ) && $product->is_type( 'variation' ); $variation_id = $is_variation ? (int) $product->get_id() : 0; $product_id = $is_variation ? (int) $product->get_parent_id() : (int) $product->get_id(); if ( $product_id <= 0 ) { return array( 'changed' => false ); } $snapshot = $this->snapshot_from_product( $product ); $hash = $this->revision_hash( $product_id, $variation_id, $snapshot ); $input = hash( 'sha256', wp_json_encode( array( 'product_id' => $product_id, 'variation_id' => $variation_id, 'hash' => $hash, 'source' => $source, 'at' => gmdate( 'Y-m-d H:i:s' ), ) ) ); $open = $this->repo->get_open( $product_id, $variation_id ); if ( $open && $open['price_revision_hash'] === $hash ) { return array( 'changed' => false, 'id' => (int) $open['id'], ); } $now = gmdate( 'Y-m-d H:i:s' ); $user_id = function_exists( 'get_current_user_id' ) ? (int) get_current_user_id() : 0; $row = array( 'product_id' => $product_id, 'variation_id' => $variation_id, 'regular_price' => $snapshot['regular_price'], 'sale_price' => $snapshot['sale_price'], 'sale_from_gmt' => $snapshot['sale_from_gmt'], 'sale_to_gmt' => $snapshot['sale_to_gmt'], 'price_revision_hash' => $hash, 'valid_from_gmt' => $now, 'valid_to_gmt' => null, 'captured_at_gmt' => $now, 'capture_source' => $source, 'continuity_state' => $continuity, 'detector_version' => self::DETECTOR_VERSION, 'input_hash' => $input, ); if ( $user_id > 0 ) { $row['captured_by_user_id'] = $user_id; } $id = $this->repo->replace_open( $row, $open ? (int) $open['id'] : null, $now ); return array( 'changed' => false !== $id && $id > 0, 'id' => $id ? (int) $id : 0, ); } /** * Observe by product/variation IDs. * * @param int $product_id Product ID. * @param int $variation_id Variation ID. * @param string $source Source. * @param string $continuity Continuity. * @return array{changed:bool,id?:int} */ public function observe_ids( $product_id, $variation_id = 0, $source = 'admin', $continuity = 'continuous' ) { if ( ! function_exists( 'wc_get_product' ) ) { return array( 'changed' => false ); } $id = $variation_id > 0 ? (int) $variation_id : (int) $product_id; $product = wc_get_product( $id ); if ( ! $product ) { return array( 'changed' => false ); } return $this->observe_product( $product, $source, $continuity ); } /** * @param \WC_Product $product Product. * @return array{regular_price:?string,sale_price:?string,sale_from_gmt:?string,sale_to_gmt:?string} */ public function snapshot_from_product( $product ) { $regular = $product->get_regular_price(); $sale = $product->get_sale_price(); $from = $product->get_date_on_sale_from( 'edit' ); $to = $product->get_date_on_sale_to( 'edit' ); return array( 'regular_price' => ( '' === $regular || null === $regular ) ? null : (string) $regular, 'sale_price' => ( '' === $sale || null === $sale ) ? null : (string) $sale, 'sale_from_gmt' => $from ? gmdate( 'Y-m-d H:i:s', $from->getTimestamp() ) : null, 'sale_to_gmt' => $to ? gmdate( 'Y-m-d H:i:s', $to->getTimestamp() ) : null, ); } /** * Deterministic material hash. * * @param int $product_id Product ID. * @param int $variation_id Variation ID. * @param array $snapshot Snapshot. * @return string */ public function revision_hash( $product_id, $variation_id, array $snapshot ) { return hash( 'sha256', wp_json_encode( array( 'product_id' => (int) $product_id, 'variation_id' => (int) $variation_id, 'regular_price' => $snapshot['regular_price'], 'sale_price' => $snapshot['sale_price'], 'sale_from_gmt' => $snapshot['sale_from_gmt'], 'sale_to_gmt' => $snapshot['sale_to_gmt'], 'detector' => self::DETECTOR_VERSION, ) ) ); } /** * Whether a revision proves an active sale at order time. * * @param array $revision Revision row. * @param string $at_gmt Order created GMT. * @return bool */ public static function revision_proves_sale( array $revision, $at_gmt ) { if ( empty( $revision['sale_price'] ) && '0' !== (string) $revision['sale_price'] ) { return false; } if ( 'continuous' !== $revision['continuity_state'] ) { return false; } if ( ! empty( $revision['sale_from_gmt'] ) && $at_gmt < $revision['sale_from_gmt'] ) { return false; } if ( ! empty( $revision['sale_to_gmt'] ) && $at_gmt >= $revision['sale_to_gmt'] ) { return false; } return true; } }