Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
9.38% covered (danger)
9.38%
3 / 32
16.67% covered (danger)
16.67%
1 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
WooCommerce_Analytics_Utilities
10.00% covered (danger)
10.00%
3 / 30
16.67% covered (danger)
16.67%
1 / 6
282.17
0.00% covered (danger)
0.00%
0 / 1
 can_site_sync_orders
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_order_attribution_enabled
15.38% covered (danger)
15.38%
2 / 13
0.00% covered (danger)
0.00%
0 / 1
58.07
 normalize_order_status
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 datetime_to_object
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 format_utc_offset
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 get_site_datetimezone
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * WooCommerce Analytics sync helpers.
4 *
5 * Minimal slice of woocommerce-analytics' HelperTraits\Utilities trait: only the
6 * helpers referenced by the WooCommerce Analytics sync module and its registration.
7 *
8 * @package automattic/jetpack-sync
9 */
10
11namespace Automattic\Jetpack\Sync\Modules;
12
13use Automattic\WooCommerce\Internal\Features\FeaturesController;
14use DateTimeZone;
15use WC_DateTime;
16
17if ( ! defined( 'ABSPATH' ) ) {
18    exit( 0 );
19}
20
21/**
22 * Trait WooCommerce_Analytics_Utilities.
23 *
24 * WooCommerce is a runtime (not composer) dependency, so the WC symbols below are
25 * only ever reached when WooCommerce is active. Callers guard with class/function
26 * existence checks; see {@see \Automattic\Jetpack\Sync\WooCommerce_Analytics_Settings::register()}.
27 */
28trait WooCommerce_Analytics_Utilities {
29
30    /**
31     * Can site sync orders to WPCOM infrastructure.
32     *
33     * @return boolean
34     */
35    protected function can_site_sync_orders(): bool {
36        return $this->is_order_attribution_enabled();
37    }
38
39    /**
40     * Check if the order attribution feature is enabled.
41     *
42     * @return bool
43     */
44    protected function is_order_attribution_enabled(): bool {
45
46        if ( ! class_exists( FeaturesController::class ) || ! function_exists( 'wc_get_container' ) ) {
47            return false;
48        }
49
50        try {
51            $feature_controller = wc_get_container()->get( FeaturesController::class );
52            '@phan-var FeaturesController $feature_controller';
53            $is_enabled = $feature_controller->feature_is_enabled( 'order_attribution' );
54
55            /*
56             * When the feature settings form is submitted, feature_is_enabled won't return false right away
57             * We need to check what value was actually posted. Only checked options are posted.
58             * So if it's a POST request and $_POST[ 'woocommerce_feature_order_attribution_enabled' ] does not exist
59             * we optimistically assume this is now false.
60             */
61            // phpcs:disable WordPress.Security.NonceVerification.Recommended
62            if ( isset( $_GET['section'] ) && 'features' === $_GET['section'] ) {
63                // phpcs:disable WordPress.Security.NonceVerification.Missing
64                if ( isset( $_POST['woocommerce_feature_order_attribution_enabled'] ) ) {
65                    $posted_order_attribution = wc_clean( sanitize_text_field( wp_unslash( $_POST['woocommerce_feature_order_attribution_enabled'] ) ) );
66                    $is_enabled               = wc_string_to_bool( $posted_order_attribution );
67                } elseif ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] ) {
68                    $is_enabled = false;
69                }
70                // phpcs:enable WordPress.Security.NonceVerification.Missing
71            }
72            // phpcs:enable WordPress.Security.NonceVerification.Recommended
73
74            return $is_enabled;
75        } catch ( \Exception $e ) {
76            return false;
77        }
78    }
79
80    /**
81     * Maps order status provided by the user to the one used in the database.
82     *
83     * @param string $status Order status.
84     * @return string
85     */
86    protected static function normalize_order_status( $status ) {
87        $status                 = str_replace( 'wc-', '', $status );
88        $wc_order_status_keys   = array_keys( wc_get_order_statuses() );
89        $wc_order_status_keys[] = 'wc-checkout-draft'; // Related to Woo bug as `wc-checkout-draft` is missing from `wc_get_order_statuses`.
90
91        return in_array( 'wc-' . $status, $wc_order_status_keys, true ) ? 'wc-' . $status : $status;
92    }
93
94    /**
95     * Convert the WC_DateTime objects to stdClass objects to ensure they are properly encoded.
96     *
97     * @param WC_DateTime|mixed $wc_datetime The datetime object.
98     * @param bool              $utc         Whether to convert to UTC.
99     * @return object|null
100     */
101    protected static function datetime_to_object( $wc_datetime, $utc = false ) {
102        if ( is_string( $wc_datetime ) ) {
103            $wc_datetime = new WC_DateTime( $wc_datetime, self::get_site_datetimezone() );
104        }
105
106        if ( is_a( $wc_datetime, 'WC_DateTime' ) ) {
107            if ( $utc ) {
108                $wc_datetime->setTimezone( new DateTimeZone( 'UTC' ) );
109            } else {
110                $wc_datetime->setTimezone( self::get_site_datetimezone() );
111            }
112            return (object) (array) $wc_datetime;
113        }
114    }
115
116    /**
117     * Convert offset in seconds to ISO 8601 timezone offset format.
118     *
119     * @param int|float $offset_seconds The timezone offset in seconds.
120     * @return string The ISO 8601 timezone offset string (e.g., '+08:00', '-08:30', '+00:00').
121     */
122    protected static function format_utc_offset( $offset_seconds ) {
123        $hours   = intval( abs( $offset_seconds ) / HOUR_IN_SECONDS );
124        $minutes = intval( ( abs( $offset_seconds ) % HOUR_IN_SECONDS ) / MINUTE_IN_SECONDS );
125        $sign    = $offset_seconds >= 0 ? '+' : '-';
126
127        return sprintf( '%s%02d:%02d', $sign, $hours, $minutes );
128    }
129
130    /**
131     * Get DateTimeZone object for WooCommerce timezone.
132     *
133     * @return DateTimeZone The DateTimeZone object for the site timezone.
134     */
135    protected static function get_site_datetimezone() {
136        return new DateTimeZone( self::format_utc_offset( wc_timezone_offset() ) );
137    }
138}