Automattic /
jetpack
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * Jetpack_Google_Analytics_Universal hooks and and enqueues support for analytics.js |
||
| 5 | * https://developers.google.com/analytics/devguides/collection/analyticsjs/ |
||
| 6 | * https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce |
||
| 7 | * |
||
| 8 | * @author allendav |
||
| 9 | */ |
||
| 10 | |||
| 11 | /** |
||
| 12 | * Bail if accessed directly |
||
| 13 | */ |
||
| 14 | if ( ! defined( 'ABSPATH' ) ) { |
||
| 15 | exit; |
||
| 16 | } |
||
| 17 | |||
| 18 | class Jetpack_Google_Analytics_Universal { |
||
| 19 | public function __construct() { |
||
| 20 | add_filter( 'jetpack_wga_universal_commands', array( $this, 'maybe_anonymize_ip' ) ); |
||
| 21 | add_filter( 'jetpack_wga_universal_commands', array( $this, 'maybe_track_purchases' ) ); |
||
| 22 | |||
| 23 | add_action( 'wp_head', array( $this, 'wp_head' ), 999999 ); |
||
| 24 | |||
| 25 | add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'add_to_cart' ) ); |
||
| 26 | add_action( 'wp_footer', array( $this, 'loop_add_to_cart' ) ); |
||
| 27 | add_action( 'woocommerce_after_cart', array( $this, 'remove_from_cart' ) ); |
||
| 28 | add_action( 'woocommerce_after_mini_cart', array( $this, 'remove_from_cart' ) ); |
||
| 29 | add_filter( 'woocommerce_cart_item_remove_link', array( $this, 'remove_from_cart_attributes' ), 10, 2 ); |
||
| 30 | add_action( 'woocommerce_after_shop_loop_item', array( $this, 'listing_impression' ) ); |
||
| 31 | add_action( 'woocommerce_after_shop_loop_item', array( $this, 'listing_click' ) ); |
||
| 32 | add_action( 'woocommerce_after_single_product', array( $this, 'product_detail' ) ); |
||
| 33 | add_action( 'woocommerce_after_checkout_form', array( $this, 'checkout_process' ) ); |
||
| 34 | |||
| 35 | // we need to send a pageview command last - so we use priority 24 to add |
||
| 36 | // this command's JavaScript just before wc_print_js is called (pri 25) |
||
| 37 | add_action( 'wp_footer', array( $this, 'send_pageview_in_footer' ), 24 ); |
||
| 38 | } |
||
| 39 | |||
| 40 | public function wp_head() { |
||
| 41 | $tracking_code = Jetpack_Google_Analytics_Options::get_tracking_code(); |
||
| 42 | if ( empty( $tracking_code ) ) { |
||
| 43 | echo "<!-- No tracking ID configured for Jetpack Google Analytics -->\r\n"; |
||
| 44 | return; |
||
| 45 | } |
||
| 46 | |||
| 47 | // If we're in the admin_area, return without inserting code. |
||
| 48 | if ( is_admin() ) { |
||
| 49 | return; |
||
| 50 | } |
||
| 51 | |||
| 52 | if ( Jetpack_AMP_Support::is_amp_request() ) { |
||
| 53 | // For Reader mode — legacy. |
||
| 54 | add_filter( 'amp_post_template_analytics', 'Jetpack_Google_Analytics::amp_analytics_entries', 1000 ); |
||
| 55 | // For Standard and Transitional modes. |
||
| 56 | add_filter( 'amp_analytics_entries', 'Jetpack_Google_Analytics::amp_analytics_entries', 1000 ); |
||
| 57 | return; |
||
| 58 | } |
||
| 59 | |||
| 60 | /** |
||
| 61 | * Allow for additional elements to be added to the universal Google Analytics queue (ga) array |
||
| 62 | * |
||
| 63 | * @since 5.6.0 |
||
| 64 | * |
||
| 65 | * @param array $custom_vars Array of universal Google Analytics queue elements |
||
| 66 | */ |
||
| 67 | $universal_commands = apply_filters( 'jetpack_wga_universal_commands', array() ); |
||
| 68 | |||
| 69 | $async_code = " |
||
| 70 | <!-- Jetpack Google Analytics --> |
||
| 71 | <script> |
||
| 72 | window.ga = window.ga || function(){ ( ga.q = ga.q || [] ).push( arguments ) }; ga.l=+new Date; |
||
| 73 | ga( 'create', '%tracking_id%', 'auto' ); |
||
| 74 | ga( 'require', 'ec' ); |
||
| 75 | %universal_commands% |
||
| 76 | </script> |
||
| 77 | <script async src='https://www.google-analytics.com/analytics.js'></script> |
||
| 78 | <!-- End Jetpack Google Analytics --> |
||
| 79 | "; |
||
| 80 | $async_code = str_replace( '%tracking_id%', $tracking_code, $async_code ); |
||
| 81 | |||
| 82 | $universal_commands_string = implode( "\r\n", $universal_commands ); |
||
| 83 | $async_code = str_replace( '%universal_commands%', $universal_commands_string, $async_code ); |
||
| 84 | |||
| 85 | echo "$async_code\r\n"; |
||
| 86 | } |
||
| 87 | |||
| 88 | public function maybe_anonymize_ip( $command_array ) { |
||
| 89 | if ( Jetpack_Google_Analytics_Options::anonymize_ip_is_enabled() ) { |
||
| 90 | array_push( $command_array, "ga( 'set', 'anonymizeIp', true );" ); |
||
| 91 | } |
||
| 92 | |||
| 93 | return $command_array; |
||
| 94 | } |
||
| 95 | |||
| 96 | public function maybe_track_purchases( $command_array ) { |
||
| 97 | global $wp; |
||
| 98 | |||
| 99 | if ( ! Jetpack_Google_Analytics_Options::track_purchases_is_enabled() ) { |
||
| 100 | return $command_array; |
||
| 101 | } |
||
| 102 | |||
| 103 | if ( ! class_exists( 'WooCommerce' ) ) { |
||
| 104 | return $command_array; |
||
| 105 | } |
||
| 106 | |||
| 107 | $minimum_woocommerce_active = class_exists( 'WooCommerce' ) && version_compare( WC_VERSION, '3.0', '>=' ); |
||
| 108 | if ( ! $minimum_woocommerce_active ) { |
||
| 109 | return $command_array; |
||
| 110 | } |
||
| 111 | |||
| 112 | if ( ! is_order_received_page() ) { |
||
| 113 | return $command_array; |
||
| 114 | } |
||
| 115 | |||
| 116 | $order_id = isset( $wp->query_vars['order-received'] ) ? $wp->query_vars['order-received'] : 0; |
||
| 117 | if ( 0 == $order_id ) { |
||
| 118 | return $command_array; |
||
| 119 | } |
||
| 120 | |||
| 121 | // A 1 indicates we've already tracked this order - don't do it again |
||
| 122 | if ( 1 == get_post_meta( $order_id, '_ga_tracked', true ) ) { |
||
| 123 | return $command_array; |
||
| 124 | } |
||
| 125 | |||
| 126 | $order = new WC_Order( $order_id ); |
||
| 127 | $order_currency = $order->get_currency(); |
||
| 128 | $command = "ga( 'set', '&cu', '" . esc_js( $order_currency ) . "' );"; |
||
| 129 | array_push( $command_array, $command ); |
||
| 130 | |||
| 131 | // Order items |
||
| 132 | if ( $order->get_items() ) { |
||
| 133 | View Code Duplication | foreach ( $order->get_items() as $item ) { |
|
| 134 | $product = $order->get_product_from_item( $item ); |
||
| 135 | $product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product ); |
||
| 136 | |||
| 137 | $item_details = array( |
||
| 138 | 'id' => $product_sku_or_id, |
||
| 139 | 'name' => $item['name'], |
||
| 140 | 'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ), |
||
| 141 | 'price' => $order->get_item_total( $item ), |
||
| 142 | 'quantity' => $item['qty'], |
||
| 143 | ); |
||
| 144 | $command = "ga( 'ec:addProduct', " . wp_json_encode( $item_details ) . " );"; |
||
| 145 | array_push( $command_array, $command ); |
||
| 146 | } |
||
| 147 | } |
||
| 148 | |||
| 149 | // Order summary |
||
| 150 | $summary = array( |
||
| 151 | 'id' => $order->get_order_number(), |
||
| 152 | 'affiliation' => get_bloginfo( 'name' ), |
||
| 153 | 'revenue' => $order->get_total(), |
||
| 154 | 'tax' => $order->get_total_tax(), |
||
| 155 | 'shipping' => $order->get_total_shipping() |
||
| 156 | ); |
||
| 157 | $command = "ga( 'ec:setAction', 'purchase', " . wp_json_encode( $summary ) . " );"; |
||
| 158 | array_push( $command_array, $command ); |
||
| 159 | |||
| 160 | update_post_meta( $order_id, '_ga_tracked', 1 ); |
||
| 161 | |||
| 162 | return $command_array; |
||
| 163 | } |
||
| 164 | |||
| 165 | public function add_to_cart() { |
||
| 166 | if ( ! Jetpack_Google_Analytics_Options::track_add_to_cart_is_enabled() ) { |
||
| 167 | return; |
||
| 168 | } |
||
| 169 | |||
| 170 | if ( ! is_single() ) { |
||
| 171 | return; |
||
| 172 | } |
||
| 173 | |||
| 174 | global $product; |
||
| 175 | |||
| 176 | $product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product ); |
||
| 177 | $selector = ".single_add_to_cart_button"; |
||
| 178 | |||
| 179 | wc_enqueue_js( |
||
| 180 | "$( '" . esc_js( $selector ) . "' ).click( function() { |
||
| 181 | var productDetails = { |
||
| 182 | 'id': '" . esc_js( $product_sku_or_id ) . "', |
||
| 183 | 'name' : '" . esc_js( $product->get_title() ) . "', |
||
| 184 | 'quantity': $( 'input.qty' ).val() ? $( 'input.qty' ).val() : '1', |
||
| 185 | }; |
||
| 186 | ga( 'ec:addProduct', productDetails ); |
||
| 187 | ga( 'ec:setAction', 'add' ); |
||
| 188 | ga( 'send', 'event', 'UX', 'click', 'add to cart' ); |
||
| 189 | } );" |
||
| 190 | ); |
||
| 191 | } |
||
| 192 | |||
| 193 | public function loop_add_to_cart() { |
||
| 194 | if ( ! Jetpack_Google_Analytics_Options::track_add_to_cart_is_enabled() ) { |
||
| 195 | return; |
||
| 196 | } |
||
| 197 | |||
| 198 | if ( ! class_exists( 'WooCommerce' ) ) { |
||
| 199 | return; |
||
| 200 | } |
||
| 201 | |||
| 202 | $minimum_woocommerce_active = class_exists( 'WooCommerce' ) && version_compare( WC_VERSION, '3.0', '>=' ); |
||
| 203 | if ( ! $minimum_woocommerce_active ) { |
||
| 204 | return; |
||
| 205 | } |
||
| 206 | |||
| 207 | $selector = ".add_to_cart_button:not(.product_type_variable, .product_type_grouped)"; |
||
| 208 | |||
| 209 | wc_enqueue_js( |
||
| 210 | "$( '" . esc_js( $selector ) . "' ).click( function() { |
||
| 211 | var productSku = $( this ).data( 'product_sku' ); |
||
| 212 | var productID = $( this ).data( 'product_id' ); |
||
| 213 | var productDetails = { |
||
| 214 | 'id': productSku ? productSku : '#' + productID, |
||
| 215 | 'quantity': $( this ).data( 'quantity' ), |
||
| 216 | }; |
||
| 217 | ga( 'ec:addProduct', productDetails ); |
||
| 218 | ga( 'ec:setAction', 'add' ); |
||
| 219 | ga( 'send', 'event', 'UX', 'click', 'add to cart' ); |
||
| 220 | } );" |
||
| 221 | ); |
||
| 222 | } |
||
| 223 | |||
| 224 | public function remove_from_cart() { |
||
| 225 | if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) { |
||
| 226 | return; |
||
| 227 | } |
||
| 228 | |||
| 229 | if ( ! Jetpack_Google_Analytics_Options::track_remove_from_cart_is_enabled() ) { |
||
| 230 | return; |
||
| 231 | } |
||
| 232 | |||
| 233 | // We listen at div.woocommerce because the cart 'form' contents get forcibly |
||
| 234 | // updated and subsequent removals from cart would then not have this click |
||
| 235 | // handler attached |
||
| 236 | wc_enqueue_js( |
||
| 237 | "$( 'div.woocommerce' ).on( 'click', 'a.remove', function() { |
||
| 238 | var productSku = $( this ).data( 'product_sku' ); |
||
| 239 | var productID = $( this ).data( 'product_id' ); |
||
| 240 | var quantity = $( this ).parent().parent().find( '.qty' ).val() |
||
| 241 | var productDetails = { |
||
| 242 | 'id': productSku ? productSku : '#' + productID, |
||
| 243 | 'quantity': quantity ? quantity : '1', |
||
| 244 | }; |
||
| 245 | ga( 'ec:addProduct', productDetails ); |
||
| 246 | ga( 'ec:setAction', 'remove' ); |
||
| 247 | ga( 'send', 'event', 'UX', 'click', 'remove from cart' ); |
||
| 248 | } );" |
||
| 249 | ); |
||
| 250 | } |
||
| 251 | |||
| 252 | /** |
||
| 253 | * Adds the product ID and SKU to the remove product link (for use by remove_from_cart above) if not present |
||
| 254 | * |
||
| 255 | * @param string $url Full HTML a tag of the link to remove an item from the cart. |
||
| 256 | * @param string $key Unique Key ID for a cart item. |
||
| 257 | */ |
||
| 258 | View Code Duplication | public function remove_from_cart_attributes( $url, $key ) { |
|
| 259 | if ( false !== strpos( $url, 'data-product_id' ) ) { |
||
| 260 | return $url; |
||
| 261 | } |
||
| 262 | |||
| 263 | $item = WC()->cart->get_cart_item( $key ); |
||
| 264 | $product = $item['data']; |
||
| 265 | |||
| 266 | $new_attributes = sprintf( |
||
| 267 | '" data-product_id="%1$s" data-product_sku="%2$s">', |
||
| 268 | esc_attr( $product->get_id() ), |
||
| 269 | esc_attr( $product->get_sku() ) |
||
| 270 | ); |
||
| 271 | |||
| 272 | $url = str_replace( '">', $new_attributes, $url ); |
||
| 273 | return $url; |
||
| 274 | } |
||
| 275 | |||
| 276 | public function listing_impression() { |
||
| 277 | if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) { |
||
| 278 | return; |
||
| 279 | } |
||
| 280 | |||
| 281 | if ( ! Jetpack_Google_Analytics_Options::track_product_impressions_is_enabled() ) { |
||
| 282 | return; |
||
| 283 | } |
||
| 284 | |||
| 285 | if ( isset( $_GET['s'] ) ) { |
||
| 286 | $list = "Search Results"; |
||
| 287 | } else { |
||
| 288 | $list = "Product List"; |
||
| 289 | } |
||
| 290 | |||
| 291 | global $product, $woocommerce_loop; |
||
| 292 | $product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product ); |
||
| 293 | |||
| 294 | $item_details = array( |
||
| 295 | 'id' => $product_sku_or_id, |
||
| 296 | 'name' => $product->get_title(), |
||
| 297 | 'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ), |
||
| 298 | 'list' => $list, |
||
| 299 | 'position' => $woocommerce_loop['loop'] |
||
| 300 | ); |
||
| 301 | wc_enqueue_js( "ga( 'ec:addImpression', " . wp_json_encode( $item_details ) . " );" ); |
||
| 302 | } |
||
| 303 | |||
| 304 | public function listing_click() { |
||
| 305 | if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) { |
||
| 306 | return; |
||
| 307 | } |
||
| 308 | |||
| 309 | if ( ! Jetpack_Google_Analytics_Options::track_product_clicks_is_enabled() ) { |
||
| 310 | return; |
||
| 311 | } |
||
| 312 | |||
| 313 | if ( isset( $_GET['s'] ) ) { |
||
| 314 | $list = "Search Results"; |
||
| 315 | } else { |
||
| 316 | $list = "Product List"; |
||
| 317 | } |
||
| 318 | |||
| 319 | global $product, $woocommerce_loop; |
||
| 320 | $product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product ); |
||
| 321 | |||
| 322 | $selector = ".products .post-" . esc_js( $product->get_id() ) . " a"; |
||
| 323 | |||
| 324 | $item_details = array( |
||
| 325 | 'id' => $product_sku_or_id, |
||
| 326 | 'name' => $product->get_title(), |
||
| 327 | 'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ), |
||
| 328 | 'position' => $woocommerce_loop['loop'] |
||
| 329 | ); |
||
| 330 | |||
| 331 | wc_enqueue_js( |
||
| 332 | "$( '" . esc_js( $selector ) . "' ).click( function() { |
||
| 333 | if ( true === $( this ).hasClass( 'add_to_cart_button' ) ) { |
||
| 334 | return; |
||
| 335 | } |
||
| 336 | |||
| 337 | ga( 'ec:addProduct', " . wp_json_encode( $item_details ) . " ); |
||
| 338 | ga( 'ec:setAction', 'click', { list: '" . esc_js( $list ) . "' } ); |
||
| 339 | ga( 'send', 'event', 'UX', 'click', { list: '" . esc_js( $list ) . "' } ); |
||
| 340 | } );" |
||
| 341 | ); |
||
| 342 | } |
||
| 343 | |||
| 344 | public function product_detail() { |
||
| 345 | if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) { |
||
| 346 | return; |
||
| 347 | } |
||
| 348 | |||
| 349 | if ( ! Jetpack_Google_Analytics_Options::track_product_detail_view_is_enabled() ) { |
||
| 350 | return; |
||
| 351 | } |
||
| 352 | |||
| 353 | global $product; |
||
| 354 | $product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product ); |
||
| 355 | |||
| 356 | $item_details = array( |
||
| 357 | 'id' => $product_sku_or_id, |
||
| 358 | 'name' => $product->get_title(), |
||
| 359 | 'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ), |
||
| 360 | 'price' => $product->get_price() |
||
| 361 | ); |
||
| 362 | wc_enqueue_js( |
||
| 363 | "ga( 'ec:addProduct', " . wp_json_encode( $item_details ) . " );" . |
||
| 364 | "ga( 'ec:setAction', 'detail' );" |
||
| 365 | ); |
||
| 366 | } |
||
| 367 | |||
| 368 | public function checkout_process() { |
||
| 369 | if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) { |
||
| 370 | return; |
||
| 371 | } |
||
| 372 | |||
| 373 | if ( ! Jetpack_Google_Analytics_Options::track_checkout_started_is_enabled() ) { |
||
| 374 | return; |
||
| 375 | } |
||
| 376 | |||
| 377 | $universal_commands = array(); |
||
| 378 | $cart = WC()->cart->get_cart(); |
||
| 379 | |||
| 380 | View Code Duplication | foreach ( $cart as $cart_item_key => $cart_item ) { |
|
| 381 | /** |
||
| 382 | * This filter is already documented in woocommerce/templates/cart/cart.php |
||
| 383 | */ |
||
| 384 | $product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key ); |
||
|
0 ignored issues
–
show
|
|||
| 385 | $product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product ); |
||
| 386 | |||
| 387 | $item_details = array( |
||
| 388 | 'id' => $product_sku_or_id, |
||
| 389 | 'name' => $product->get_title(), |
||
| 390 | 'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ), |
||
| 391 | 'price' => $product->get_price(), |
||
| 392 | 'quantity' => $cart_item[ 'quantity' ] |
||
| 393 | ); |
||
| 394 | |||
| 395 | array_push( $universal_commands, "ga( 'ec:addProduct', " . wp_json_encode( $item_details ) . " );" ); |
||
| 396 | } |
||
| 397 | |||
| 398 | array_push( $universal_commands, "ga( 'ec:setAction','checkout' );" ); |
||
| 399 | |||
| 400 | wc_enqueue_js( implode( "\r\n", $universal_commands ) ); |
||
| 401 | } |
||
| 402 | |||
| 403 | public function send_pageview_in_footer() { |
||
| 404 | if ( ! Jetpack_Google_Analytics_Options::has_tracking_code() ) { |
||
| 405 | return; |
||
| 406 | } |
||
| 407 | |||
| 408 | if ( is_admin() ) { |
||
| 409 | return; |
||
| 410 | } |
||
| 411 | |||
| 412 | if ( ! class_exists( 'WooCommerce' ) ) { |
||
| 413 | return; |
||
| 414 | } |
||
| 415 | |||
| 416 | wc_enqueue_js( "ga( 'send', 'pageview' );" ); |
||
| 417 | } |
||
| 418 | } |
||
| 419 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignorePhpDoc annotation to the duplicate definition and it will be ignored.