Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds refund tracking functionality for Google Analytics by loading the GA script in the WordPress admin area on PMPro pages and sending refund events when orders are marked as refunded.
Changes:
- Added admin-side Google Analytics script loading on PMPro pages
- Implemented refund event tracking that fires when order status changes to refunded
- Added helper functions to structure and output refund event data to GA4
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| extract( $pmproga4_settings = get_option( 'pmproga4_settings', | ||
| array( | ||
| 'measurement_id' => '', | ||
| 'track_levels' => array() | ||
| ) | ||
| ) ); | ||
|
|
There was a problem hiding this comment.
The use of extract() is a discouraged practice in WordPress as it can lead to variable collision and make code harder to maintain. Consider accessing array values directly using $pmproga4_settings['measurement_id'] and $pmproga4_settings['track_levels'] instead.
| extract( $pmproga4_settings = get_option( 'pmproga4_settings', | |
| array( | |
| 'measurement_id' => '', | |
| 'track_levels' => array() | |
| ) | |
| ) ); | |
| $pmproga4_settings = get_option( | |
| 'pmproga4_settings', | |
| array( | |
| 'measurement_id' => '', | |
| 'track_levels' => array(), | |
| ) | |
| ); | |
| $measurement_id = isset( $pmproga4_settings['measurement_id'] ) ? $pmproga4_settings['measurement_id'] : ''; | |
| $track_levels = isset( $pmproga4_settings['track_levels'] ) ? $pmproga4_settings['track_levels'] : array(); |
| ?> | ||
| <!-- Paid Memberships Pro - Google Analytics --> | ||
| <script async src="https://www.googletagmanager.com/gtag/js?id=<?php echo esc_attr( $measurement_id ); ?>"></script> | ||
| <script <?php echo esc_attr($script_atts); ?>> |
There was a problem hiding this comment.
Missing space before the opening brace on the script tag attribute. For consistency with WordPress coding standards, there should be a space before the PHP echo statement: <script >
| <script <?php echo esc_attr($script_atts); ?>> | |
| <script <?php echo esc_attr( $script_atts ); ?>> |
| * Enqueue the Google Analytics script for a refunded order. | ||
| * | ||
| * @since TBD | ||
| */ | ||
| function pmproga4_load_admin_order_refunded_script( $pmpro_invoice ) { | ||
| pmproga4_refund_event( $pmpro_invoice ); | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
The function pmproga4_load_admin_order_refunded_script is defined but never used or hooked into WordPress. This appears to be dead code that should either be removed or properly integrated with an action hook.
| * Enqueue the Google Analytics script for a refunded order. | |
| * | |
| * @since TBD | |
| */ | |
| function pmproga4_load_admin_order_refunded_script( $pmpro_invoice ) { | |
| pmproga4_refund_event( $pmpro_invoice ); | |
| } | |
| /** |
| } | ||
|
|
||
| /** | ||
| * Function for refund event. |
There was a problem hiding this comment.
The function documentation is incomplete. It should include a proper description of what the function does, document the @param tag for $pmpro_invoice parameter, and specify the @return type (which appears to be void).
| * Function for refund event. | |
| * Build and enqueue the Google Analytics 4 refund event for a given order. | |
| * | |
| * @param object $pmpro_invoice Paid Memberships Pro invoice/order object used to populate refund data. | |
| * | |
| * @return void |
| if ( empty( $_REQUEST['page'] ) || strpos( $_REQUEST['page'], 'pmpro' ) === false ) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Inconsistent indentation: these lines use tabs while the surrounding code uses spaces. For consistency with WordPress coding standards and the rest of the file, use tabs for indentation.
| value: <?php echo $gtag_config_ecommerce_data['value']; ?>, | ||
| <?php if ( ! empty( $gtag_config_ecommerce_data['tax'] ) ) { ?> | ||
| tax: <?php echo $gtag_config_ecommerce_data['tax']; ?>, |
There was a problem hiding this comment.
Numeric values should be explicitly cast to ensure they are numeric types before output into JavaScript. Use floatval() for value and tax: value: and tax: . This prevents potential issues if the database values are unexpectedly non-numeric.
| value: <?php echo $gtag_config_ecommerce_data['value']; ?>, | |
| <?php if ( ! empty( $gtag_config_ecommerce_data['tax'] ) ) { ?> | |
| tax: <?php echo $gtag_config_ecommerce_data['tax']; ?>, | |
| value: <?php echo floatval( $gtag_config_ecommerce_data['value'] ); ?>, | |
| <?php if ( ! empty( $gtag_config_ecommerce_data['tax'] ) ) { ?> | |
| tax: <?php echo floatval( $gtag_config_ecommerce_data['tax'] ); ?>, |
| 'send_page_view': false, | ||
| } | ||
| ); | ||
| </script> |
There was a problem hiding this comment.
Inconsistent indentation: this line uses a tab while the surrounding code uses spaces. For consistency with WordPress coding standards and the rest of the file, use a tab for indentation.
| </script> | |
| </script> |
| */ | ||
| function pmproga4_refund_event( $pmpro_invoice ) { | ||
|
|
||
| // Set the ecommerce dataLayer script if the order ID matches the session variable. |
There was a problem hiding this comment.
The comment mentions checking if the order ID matches a session variable, but no session variable is being checked in this code. The comment should be updated to accurately reflect what the code does: checking if the invoice object and its ID are not empty.
| // Set the ecommerce dataLayer script if the order ID matches the session variable. | |
| // Set the ecommerce dataLayer script if the invoice and its ID are not empty. |
| // Set the ecommerce dataLayer script. | ||
| $gtag_config_ecommerce_data = array(); | ||
| $gtag_config_ecommerce_data['transaction_id'] = $pmpro_invoice->code; | ||
| $gtag_config_ecommerce_data['value'] = $pmpro_invoice->membership_level->initial_payment; | ||
|
|
||
| if ( ! empty( $pmpro_invoice->tax ) ) { | ||
| $gtag_config_ecommerce_data['tax'] = $pmpro_invoice->tax; | ||
| } else { | ||
| $gtag_config_ecommerce_data['tax'] = 0; | ||
| } | ||
|
|
||
| if ( $pmpro_invoice->getDiscountCode() ) { | ||
| $gtag_config_ecommerce_data['coupon'] = $pmpro_invoice->discount_code->code; | ||
| } else { | ||
| $gtag_config_ecommerce_data['coupon'] = ''; | ||
| } | ||
|
|
||
| // Build an array of product data. | ||
| $gtag_config_ecommerce_products = array(); | ||
| $gtag_config_ecommerce_products['item_id'] = $pmpro_invoice->membership_level->id; | ||
| $gtag_config_ecommerce_products['item_name'] = $pmpro_invoice->membership_level->name; | ||
| $gtag_config_ecommerce_products['affiliation'] = get_bloginfo( 'name' ); | ||
| if ( $pmpro_invoice->getDiscountCode() ) { | ||
| $gtag_config_ecommerce_products['coupon'] = $pmpro_invoice->discount_code->code; | ||
| } | ||
| $gtag_config_ecommerce_products['index'] = 0; | ||
| $gtag_config_ecommerce_products['price'] = $pmpro_invoice->membership_level->initial_payment; | ||
| $gtag_config_ecommerce_products['quantity'] = 1; | ||
| ?> | ||
| <script> | ||
| jQuery(document).ready(function() { | ||
| gtag( 'event', 'refund', { | ||
| transaction_id: '<?php echo $gtag_config_ecommerce_data['transaction_id']; ?>', | ||
| value: <?php echo $gtag_config_ecommerce_data['value']; ?>, | ||
| <?php if ( ! empty( $gtag_config_ecommerce_data['tax'] ) ) { ?> | ||
| tax: <?php echo $gtag_config_ecommerce_data['tax']; ?>, | ||
| <?php } ?> | ||
| <?php if( ! empty( $gtag_config_ecommerce_data['coupon'] ) ) { ?> | ||
| coupon: '<?php echo $gtag_config_ecommerce_data['coupon']; ?>', | ||
| <?php } ?> | ||
| items: [ <?php echo json_encode( $gtag_config_ecommerce_products ); ?> ] | ||
| }); | ||
| }); | ||
| </script> | ||
| <?php |
There was a problem hiding this comment.
After calling getMembershipLevel(), there's no verification that the membership_level property was successfully populated. Add a check to ensure $pmpro_invoice->membership_level is not empty before accessing its properties like initial_payment, id, and name to prevent potential PHP warnings or errors.
| // Set the ecommerce dataLayer script. | |
| $gtag_config_ecommerce_data = array(); | |
| $gtag_config_ecommerce_data['transaction_id'] = $pmpro_invoice->code; | |
| $gtag_config_ecommerce_data['value'] = $pmpro_invoice->membership_level->initial_payment; | |
| if ( ! empty( $pmpro_invoice->tax ) ) { | |
| $gtag_config_ecommerce_data['tax'] = $pmpro_invoice->tax; | |
| } else { | |
| $gtag_config_ecommerce_data['tax'] = 0; | |
| } | |
| if ( $pmpro_invoice->getDiscountCode() ) { | |
| $gtag_config_ecommerce_data['coupon'] = $pmpro_invoice->discount_code->code; | |
| } else { | |
| $gtag_config_ecommerce_data['coupon'] = ''; | |
| } | |
| // Build an array of product data. | |
| $gtag_config_ecommerce_products = array(); | |
| $gtag_config_ecommerce_products['item_id'] = $pmpro_invoice->membership_level->id; | |
| $gtag_config_ecommerce_products['item_name'] = $pmpro_invoice->membership_level->name; | |
| $gtag_config_ecommerce_products['affiliation'] = get_bloginfo( 'name' ); | |
| if ( $pmpro_invoice->getDiscountCode() ) { | |
| $gtag_config_ecommerce_products['coupon'] = $pmpro_invoice->discount_code->code; | |
| } | |
| $gtag_config_ecommerce_products['index'] = 0; | |
| $gtag_config_ecommerce_products['price'] = $pmpro_invoice->membership_level->initial_payment; | |
| $gtag_config_ecommerce_products['quantity'] = 1; | |
| ?> | |
| <script> | |
| jQuery(document).ready(function() { | |
| gtag( 'event', 'refund', { | |
| transaction_id: '<?php echo $gtag_config_ecommerce_data['transaction_id']; ?>', | |
| value: <?php echo $gtag_config_ecommerce_data['value']; ?>, | |
| <?php if ( ! empty( $gtag_config_ecommerce_data['tax'] ) ) { ?> | |
| tax: <?php echo $gtag_config_ecommerce_data['tax']; ?>, | |
| <?php } ?> | |
| <?php if( ! empty( $gtag_config_ecommerce_data['coupon'] ) ) { ?> | |
| coupon: '<?php echo $gtag_config_ecommerce_data['coupon']; ?>', | |
| <?php } ?> | |
| items: [ <?php echo json_encode( $gtag_config_ecommerce_products ); ?> ] | |
| }); | |
| }); | |
| </script> | |
| <?php | |
| if ( ! empty( $pmpro_invoice->membership_level ) ) { | |
| // Set the ecommerce dataLayer script. | |
| $gtag_config_ecommerce_data = array(); | |
| $gtag_config_ecommerce_data['transaction_id'] = $pmpro_invoice->code; | |
| $gtag_config_ecommerce_data['value'] = $pmpro_invoice->membership_level->initial_payment; | |
| if ( ! empty( $pmpro_invoice->tax ) ) { | |
| $gtag_config_ecommerce_data['tax'] = $pmpro_invoice->tax; | |
| } else { | |
| $gtag_config_ecommerce_data['tax'] = 0; | |
| } | |
| if ( $pmpro_invoice->getDiscountCode() ) { | |
| $gtag_config_ecommerce_data['coupon'] = $pmpro_invoice->discount_code->code; | |
| } else { | |
| $gtag_config_ecommerce_data['coupon'] = ''; | |
| } | |
| // Build an array of product data. | |
| $gtag_config_ecommerce_products = array(); | |
| $gtag_config_ecommerce_products['item_id'] = $pmpro_invoice->membership_level->id; | |
| $gtag_config_ecommerce_products['item_name'] = $pmpro_invoice->membership_level->name; | |
| $gtag_config_ecommerce_products['affiliation'] = get_bloginfo( 'name' ); | |
| if ( $pmpro_invoice->getDiscountCode() ) { | |
| $gtag_config_ecommerce_products['coupon'] = $pmpro_invoice->discount_code->code; | |
| } | |
| $gtag_config_ecommerce_products['index'] = 0; | |
| $gtag_config_ecommerce_products['price'] = $pmpro_invoice->membership_level->initial_payment; | |
| $gtag_config_ecommerce_products['quantity'] = 1; | |
| ?> | |
| <script> | |
| jQuery(document).ready(function() { | |
| gtag( 'event', 'refund', { | |
| transaction_id: '<?php echo $gtag_config_ecommerce_data['transaction_id']; ?>', | |
| value: <?php echo $gtag_config_ecommerce_data['value']; ?>, | |
| <?php if ( ! empty( $gtag_config_ecommerce_data['tax'] ) ) { ?> | |
| tax: <?php echo $gtag_config_ecommerce_data['tax']; ?>, | |
| <?php } ?> | |
| <?php if( ! empty( $gtag_config_ecommerce_data['coupon'] ) ) { ?> | |
| coupon: '<?php echo $gtag_config_ecommerce_data['coupon']; ?>', | |
| <?php } ?> | |
| items: [ <?php echo json_encode( $gtag_config_ecommerce_products ); ?> ] | |
| }); | |
| }); | |
| </script> | |
| <?php | |
| } |
| <?php if ( ! empty( $gtag_config_ecommerce_data['tax'] ) ) { ?> | ||
| tax: <?php echo $gtag_config_ecommerce_data['tax']; ?>, | ||
| <?php } ?> | ||
| <?php if( ! empty( $gtag_config_ecommerce_data['coupon'] ) ) { ?> |
There was a problem hiding this comment.
Missing space after 'if' keyword. WordPress coding standards require a space after control structure keywords: if ( ! empty( ... ) )
| <?php if( ! empty( $gtag_config_ecommerce_data['coupon'] ) ) { ?> | |
| <?php if ( ! empty( $gtag_config_ecommerce_data['coupon'] ) ) { ?> |
All Submissions:
Changes proposed in this Pull Request:
Now loading our Google Analytics code in the WordPress admin on PMPro pages when orders are updated. Could use further testing, for example, when the order comes in from the gateway. This is hard to test with the GA4 testing tool. I did process a few so my dev site will be able to see this tomorrow potentially.
Let's also consider if we should or should not offer a feature to disable refund tracking.
Other information:
Changelog entry