diff --git a/includes/reader-revenue/woocommerce/class-woocommerce-connection.php b/includes/reader-revenue/woocommerce/class-woocommerce-connection.php index fc1c5835e0..48f1f7ce36 100644 --- a/includes/reader-revenue/woocommerce/class-woocommerce-connection.php +++ b/includes/reader-revenue/woocommerce/class-woocommerce-connection.php @@ -35,6 +35,7 @@ public static function init() { include_once __DIR__ . '/class-woocommerce-cover-fees.php'; include_once __DIR__ . '/class-woocommerce-order-utm.php'; include_once __DIR__ . '/class-woocommerce-products.php'; + include_once __DIR__ . '/class-woocommerce-duplicate-orders.php'; \add_action( 'admin_init', [ __CLASS__, 'disable_woocommerce_setup' ] ); \add_filter( 'option_woocommerce_subscriptions_allow_switching', [ __CLASS__, 'force_allow_subscription_switching' ], 10, 2 ); diff --git a/includes/reader-revenue/woocommerce/class-woocommerce-duplicate-orders.php b/includes/reader-revenue/woocommerce/class-woocommerce-duplicate-orders.php new file mode 100644 index 0000000000..310747a469 --- /dev/null +++ b/includes/reader-revenue/woocommerce/class-woocommerce-duplicate-orders.php @@ -0,0 +1,250 @@ + true, + 'limit' => $per_page, + 'status' => [ 'wc-completed' ], + 'offset' => $current_page * $per_page, + 'date_completed' => '>' . ( time() - $cutoff_time ), + ] + ); + + if ( defined( 'WP_CLI' ) && WP_CLI && $order_result->max_num_pages > 0 ) { + \WP_CLI::line( sprintf( 'Processing page %d/%d of orders.', $current_page + 1, $order_result->max_num_pages ) ); + } + + $orders = $order_result->orders; + $order_duplicates = []; + + foreach ( $orders as $order ) { + $email = $order->get_billing_email(); + $amount = $order->get_total(); + $date = $order->get_date_created()->date( 'Y-m-d' ); + + if ( \wcs_order_contains_renewal( $order ) || \wcs_order_contains_resubscribe( $order ) ) { + continue; + } + + if ( ! isset( $order_duplicates[ $email ] ) ) { + $order_duplicates[ $email ] = []; + } + + if ( ! isset( $order_duplicates[ $email ][ $amount ] ) ) { + $order_duplicates[ $email ][ $amount ] = []; + } + + if ( ! isset( $order_duplicates[ $email ][ $amount ][ $date ] ) ) { + $order_duplicates[ $email ][ $amount ][ $date ] = []; + } + + $order_duplicates[ $email ][ $amount ][ $date ][] = $order->get_id(); + } + + foreach ( $order_duplicates as $email => $amounts ) { + foreach ( $amounts as $amount => $dates ) { + foreach ( $dates as $date => $order_ids ) { + if ( count( $order_ids ) > 1 ) { + sort( $order_ids ); + $ids = implode( ',', $order_ids ); + $results[ $ids ] = [ + 'email' => $email, + 'amount' => $amount, + 'date' => $date, + 'ids' => $ids, + ]; + } + } + } + } + + if ( $order_result->total > 0 ) { + $current_page++; + return self::get_order_duplicates( $cutoff_time, $current_page, $results ); + } + + return $results; + } + + /** + * Check for duplicate orders and save the result in an option. + * + * @param number $cutoff_time The cutoff time in the past (how many seconds ago). + * @param bool $save Whether to save the result as the option. + * @param bool $upsert Whether to upsert the option (merge with existing). + */ + public static function check_for_order_duplicates( $cutoff_time = DAY_IN_SECONDS, $save = false, $upsert = true ): array { + $order_duplicates = self::get_order_duplicates( $cutoff_time ); + if ( empty( $order_duplicates ) ) { + return []; + } + if ( $save ) { + if ( $upsert ) { + $existing_order_duplicates = get_option( self::DUPLICATED_ORDERS_OPTION_NAME, [] ); + foreach ( $existing_order_duplicates as $key => $value ) { + if ( isset( $order_duplicates[ $key ] ) ) { + continue; + } + $order_duplicates[ $key ] = $value; + } + } + update_option( self::DUPLICATED_ORDERS_OPTION_NAME, $order_duplicates ); + } + return $order_duplicates; + } + + /** + * Display an admin notice if duplicate orders are found. + */ + public static function display_admin_notice(): void { + if ( ! function_exists( 'wc_price' ) ) { + return; + } + $existing_order_duplicates = get_option( self::DUPLICATED_ORDERS_OPTION_NAME, [] ); + if ( empty( $existing_order_duplicates ) ) { + return; + } + $dismissed_duplicates = get_option( self::DISMISSED_DUPLICATES_OPTION_NAME, [] ); + ?> +
+ +
+ + + + +
+
+ ] + * : The cutoff time in the past (e.g. "2 months"). + * + * [--save] + * : Whether to save the results for display in the admin panel. + * + * ## EXAMPLES + * + * wp newspack detect-order-duplicates --cutoff_time='2 months' --save + * + * @param array $args Positional args. + * @param array $assoc_args Associative args. + */ + public static function cli_upsert_order_duplicates( $args, $assoc_args ) { + $cutoff_time_str = isset( $assoc_args['cutoff_time'] ) ? $assoc_args['cutoff_time'] : '1 month'; + $cutoff_time = strtotime( $cutoff_time_str ) - time(); + $save_as_option = isset( $assoc_args['save'] ) ? $assoc_args['save'] : false; + + $duplicates = self::check_for_order_duplicates( $cutoff_time, $save_as_option, false ); + + if ( empty( $duplicates ) ) { + \WP_CLI::success( 'No duplicate orders found.' ); + } else { + \WP_CLI::success( sprintf( '%d duplicate order series found.', count( $duplicates ) ) ); + } + } +} + +WooCommerce_Duplicate_Orders::init();