Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 92
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
My_Account
0.00% covered (danger)
0.00%
0 / 92
0.00% covered (danger)
0.00%
0 / 11
2256
0.00% covered (danger)
0.00%
0 / 1
 init_hooks
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 track_tabs
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
650
 track_save_address
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 track_add_payment_method
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 track_delete_payment_method
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 track_order_cancel_event
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
20
 track_order_pay_event
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 track_save_account_details
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 add_initiator_prop_to_my_account_action_links
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 add_initiator_prop_to_order_urls
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
 add_initiator_param_to_query_vars
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Events tracked on the My Account page.
4 *
5 * @package automattic/woocommerce-analytics
6 */
7
8namespace Automattic\Woocommerce_Analytics;
9
10/**
11 * Filters and Actions added to My Account pages to perform analytics
12 */
13class My_Account {
14
15    use Woo_Analytics_Trait;
16
17    /**
18     * Constructor.
19     */
20    public function init_hooks() {
21        add_action( 'woocommerce_account_content', array( $this, 'track_tabs' ) );
22        add_action( 'woocommerce_customer_save_address', array( $this, 'track_save_address' ), 10, 2 );
23        add_action( 'wp', array( $this, 'track_add_payment_method' ) );
24        add_action( 'wp', array( $this, 'track_delete_payment_method' ) );
25        add_action( 'woocommerce_save_account_details', array( $this, 'track_save_account_details' ) );
26        add_filter( 'woocommerce_my_account_my_orders_actions', array( $this, 'add_initiator_prop_to_my_account_action_links' ) );
27        add_action( 'woocommerce_cancelled_order', array( $this, 'track_order_cancel_event' ), 10, 0 );
28        add_action( 'before_woocommerce_pay', array( $this, 'track_order_pay_event' ) );
29        add_action( 'woocommerce_before_account_orders', array( $this, 'add_initiator_prop_to_order_urls' ), 9 );
30        add_filter( 'query_vars', array( $this, 'add_initiator_param_to_query_vars' ) );
31    }
32
33    /**
34     * Track my account tabs, we only trigger an event if a tab is viewed.
35     *
36     * We also track other events here, like order number clicks, order action clicks,
37     * address clicks, payment method add and delete.
38     */
39    public function track_tabs() {
40        global $wp;
41
42        // WooCommerce keeps a map of my-account endpoints keys and their custom permalinks.
43        $core_endpoints = WC()->query->get_query_vars();
44
45        if ( ! empty( $wp->query_vars ) ) {
46
47            foreach ( $wp->query_vars as $key => $value ) {
48                // we skip pagename.
49                if ( 'pagename' === $key ) {
50                    continue;
51                }
52
53                // When no permalink is set, the first page is page_id, so we skip it.
54                if ( 'page_id' === $key ) {
55                    continue;
56                }
57
58                // We don't want to track our own analytics params.
59                if ( '_wca_initiator' === $key ) {
60                    continue;
61                }
62
63                if ( isset( $core_endpoints['view-order'] ) && $core_endpoints['view-order'] === $key && is_numeric( $value ) ) {
64                    $initiator = get_query_var( '_wca_initiator' );
65                    if ( 'number' === $initiator ) {
66                        $this->enqueue_event( 'my_account_order_number_click' );
67                        continue;
68                    }
69                    if ( 'action' === $initiator ) {
70                        $this->enqueue_event( 'my_account_order_action_click', array( 'action' => 'view' ) );
71                        continue;
72                    }
73                }
74
75                if ( isset( $core_endpoints['edit-address'] ) && $core_endpoints['edit-address'] === $key && in_array( $value, array( 'billing', 'shipping' ), true ) ) {
76                    $refer = wp_get_referer();
77                    if ( $refer === wc_get_endpoint_url( 'edit-address', $value ) ) {
78                        // It means we're likely coming from the same page after a failed save and don't want to retrigger the address click event.
79                        continue;
80                    }
81
82                    $this->enqueue_event( 'my_account_address_click', array( 'address' => $value ) );
83                    continue;
84                }
85
86                if ( isset( $core_endpoints['add-payment-method'] ) && $core_endpoints['add-payment-method'] === $key ) {
87                    $this->enqueue_event( 'my_account_payment_add' );
88                    continue;
89                }
90
91                if ( isset( $core_endpoints['edit-address'] ) && $core_endpoints['edit-address'] ) {
92                    $refer = wp_get_referer();
93                    if ( $refer === wc_get_endpoint_url( 'edit-address', 'billing' ) || $refer === wc_get_endpoint_url( 'edit-address', 'shipping' ) ) {
94                        // It means we're likely coming from the edit page save and don't want to retrigger the page view event.
95                        continue;
96                    }
97                }
98                /**
99                 * The main dashboard view has page as key, so we rename it.
100                 */
101                if ( 'page' === $key ) {
102                    $key = 'dashboard';
103                }
104
105                /**
106                 * If a custom permalink is used for one of the pages, query_vars will have 2 keys, the custom permalink and the core endpoint key.
107                 * To avoid triggering the event twice, we skip the core one and only track the custom one.
108                 * Tracking the custom endpoint is safer than hoping the duplicated, redundant core endpoint is always present.
109                 */
110                if ( isset( $core_endpoints[ $key ] ) && $core_endpoints[ $key ] !== $key ) {
111                    continue;
112                }
113                /**
114                 * $core_endpoints is an array of core_permalink => custom_permalink,
115                 * query_vars gives us the custom_permalink, but we want to track it as core_permalink.
116                 */
117                if ( array_search( $key, $core_endpoints, true ) ) {
118                    $key = array_search( $key, $core_endpoints, true );
119                }
120
121                $this->enqueue_event( 'my_account_page_view', array( 'tab' => $key ) );
122            }
123        }
124    }
125
126    /**
127     * Track address save events, this can only come from the my account page.
128     *
129     * @param int    $customer_id The customer id.
130     * @param string $load_address The address type (billing, shipping).
131     */
132    public function track_save_address( $customer_id, $load_address ) {
133        WC_Analytics_Tracking::record_event( 'my_account_address_save', array( 'address' => $load_address ) );
134    }
135
136    /**
137     * Track payment method add events, this can only come from the my account page.
138     */
139    public function track_add_payment_method() {
140        if ( isset( $_POST['woocommerce_add_payment_method'] ) && isset( $_POST['payment_method'] ) ) {
141
142            $nonce_value = wc_get_var( $_REQUEST['woocommerce-add-payment-method-nonce'], wc_get_var( $_REQUEST['_wpnonce'], '' ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
143
144            if ( ! wp_verify_nonce( $nonce_value, 'woocommerce-add-payment-method' ) ) {
145                return;
146            }
147
148            WC_Analytics_Tracking::record_event( 'my_account_payment_save' );
149            return;
150        }
151    }
152
153    /**
154     * Track payment method delete events.
155     */
156    public function track_delete_payment_method() {
157        global $wp;
158        if ( isset( $wp->query_vars['delete-payment-method'] ) ) {
159            WC_Analytics_Tracking::record_event( 'my_account_payment_delete' );
160            return;
161        }
162    }
163
164    /**
165     * Track order cancel events.
166     */
167    public function track_order_cancel_event() {
168        if ( isset( $_GET['_wca_initiator'] ) && ( isset( $_GET['_wpnonce'] ) && wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), 'woocommerce-cancel_order' ) ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
169            WC_Analytics_Tracking::record_event( 'my_account_order_action_click', array( 'action' => 'cancel' ) );
170        }
171    }
172
173    /**
174     * Track order pay events.
175     */
176    public function track_order_pay_event() {
177        if ( isset( $_GET['_wca_initiator'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Recommended
178            WC_Analytics_Tracking::record_event( 'my_account_order_action_click', array( 'action' => 'pay' ) );
179        }
180    }
181
182    /**
183     * Track account details save events, this can only come from the my account page.
184     */
185    public function track_save_account_details() {
186        WC_Analytics_Tracking::record_event( 'my_account_details_save' );
187    }
188
189    /**
190     * Add referrer prop to my account action links
191     *
192     * @param array $actions My account action links.
193     * @return array
194     */
195    public function add_initiator_prop_to_my_account_action_links( $actions ) {
196        foreach ( $actions as $key => $action ) {
197            if ( ! isset( $action['url'] ) ) {
198                continue;
199            }
200
201            // Check if the action key is view, pay, or cancel.
202            if ( ! in_array( $key, array( 'view', 'pay', 'cancel' ), true ) ) {
203                continue;
204            }
205
206            $url                    = add_query_arg( array( '_wca_initiator' => 'action' ), $action['url'] );
207            $actions[ $key ]['url'] = $url;
208        }
209
210        return $actions;
211    }
212
213    /**
214     * Add an initiator prop to the order url.
215     *
216     * The get_view_order_url is used in a lot of places,
217     * so we want to limit it just to my account page.
218     */
219    public function add_initiator_prop_to_order_urls() {
220        add_filter(
221            'woocommerce_get_view_order_url',
222            function ( $url ) {
223                return add_query_arg( array( '_wca_initiator' => 'number' ), $url );
224            },
225            10,
226            1
227        );
228
229        add_filter(
230            'woocommerce_get_endpoint_url',
231            function ( $url, $endpoint ) {
232                if ( 'edit-address' === $endpoint ) {
233                    return add_query_arg( array( '_wca_initiator' => 'action' ), $url );
234                }
235                return $url;
236            },
237            10,
238            2
239        );
240    }
241
242    /**
243     * Add initiator to query vars
244     *
245     * @param array $query_vars Query vars.
246     * @return array
247     */
248    public function add_initiator_param_to_query_vars( $query_vars ) {
249        $query_vars[] = '_wca_initiator';
250        return $query_vars;
251    }
252}