Show "Renewal" for orders which are actually renewals#131
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new PMPro “recipe” snippet to adjust the Orders admin list so the “Renewal” label only appears for orders considered true renewals (vs. any returning-customer order).
Changes:
- Adds custom “Status” column output for PMPro orders list, including a conditional “Renewal” badge/link.
- Implements renewal-detection logic for recurring and non-recurring orders.
- Hides the default PMPro status column via admin CSS.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * Replace "Renewal" label logics showing "Renewal" for orders which are actually renewals. | ||
| * | ||
| * Orders status column in admin shows a not clear "Renewal" label for returning customers | ||
| * (orders from a customer who already had an order before - might be a renewal or not). | ||
| * | ||
| * Some people just want to see "Renewal" for orders which are actually renewals. |
There was a problem hiding this comment.
Docblock wording is grammatically incorrect/unclear (e.g., “label logics”, “a not clear”). Please rephrase for clarity since these snippet headers are user-facing in the recipe library.
| * Replace "Renewal" label logics showing "Renewal" for orders which are actually renewals. | |
| * | |
| * Orders status column in admin shows a not clear "Renewal" label for returning customers | |
| * (orders from a customer who already had an order before - might be a renewal or not). | |
| * | |
| * Some people just want to see "Renewal" for orders which are actually renewals. | |
| * Adjust the logic for the "Renewal" label so it is only shown for orders that are actual renewals. | |
| * | |
| * By default, the Orders status column in the admin shows a "Renewal" label for any returning customer | |
| * (orders from a customer who has placed a previous order, whether or not it is an actual renewal). | |
| * | |
| * Use this recipe if you want the "Renewal" label to appear only for true subscription renewal orders. |
| <span class="pmpro_order-status pmpro_order-status-<?php esc_attr_e( $order->status ); ?>"> | ||
| <?php esc_html_e( ucwords( $order->status ) ); ?> |
There was a problem hiding this comment.
esc_attr_e()/esc_html_e() are translation-and-echo helpers; using them with dynamic values like $order->status can invoke the wrong text domain (default) and is not intended for non-translatable data. Use proper escaping (esc_attr/esc_html) and echo the result instead.
| <span class="pmpro_order-status pmpro_order-status-<?php esc_attr_e( $order->status ); ?>"> | |
| <?php esc_html_e( ucwords( $order->status ) ); ?> | |
| <span class="pmpro_order-status pmpro_order-status-<?php echo esc_attr( $order->status ); ?>"> | |
| <?php echo esc_html( ucwords( $order->status ) ); ?> |
| <?php if ( pmpro_order_is_renewal_in_subscription( $order ) ) { ?> | ||
| <a href="<?php echo esc_url( add_query_arg( array( | ||
| 'page' => 'pmpro-orders', | ||
| 's' => $order->subscription_transaction_id | ||
| ), admin_url( 'admin.php' ) ) ); ?>" | ||
| title="<?php esc_attr_e( 'View all orders for this subscription', 'paid-memberships-pro' ); ?>" | ||
| class="pmpro_order-renewal"><?php esc_html_e( 'Renewal', 'paid-memberships-pro' ); ?></a> | ||
| <?php } ?> |
There was a problem hiding this comment.
The “Renewal” link always searches by $order->subscription_transaction_id. For non-recurring renewals (where this field is empty) this renders a link with an empty s parameter, which won’t show the intended related orders. Consider only linking when a subscription transaction ID exists, or using a different query target for non-recurring renewals.
| <?php if ( pmpro_order_is_renewal_in_subscription( $order ) ) { ?> | |
| <a href="<?php echo esc_url( add_query_arg( array( | |
| 'page' => 'pmpro-orders', | |
| 's' => $order->subscription_transaction_id | |
| ), admin_url( 'admin.php' ) ) ); ?>" | |
| title="<?php esc_attr_e( 'View all orders for this subscription', 'paid-memberships-pro' ); ?>" | |
| class="pmpro_order-renewal"><?php esc_html_e( 'Renewal', 'paid-memberships-pro' ); ?></a> | |
| <?php } ?> | |
| <?php if ( pmpro_order_is_renewal_in_subscription( $order ) ) : ?> | |
| <?php if ( ! empty( $order->subscription_transaction_id ) ) : ?> | |
| <a href="<?php echo esc_url( add_query_arg( array( | |
| 'page' => 'pmpro-orders', | |
| 's' => $order->subscription_transaction_id, | |
| ), admin_url( 'admin.php' ) ) ); ?>" | |
| title="<?php esc_attr_e( 'View all orders for this subscription', 'paid-memberships-pro' ); ?>" | |
| class="pmpro_order-renewal"><?php esc_html_e( 'Renewal', 'paid-memberships-pro' ); ?></a> | |
| <?php else : ?> | |
| <span class="pmpro_order-renewal"><?php esc_html_e( 'Renewal', 'paid-memberships-pro' ); ?></span> | |
| <?php endif; ?> | |
| <?php endif; ?> |
| $sqlQuery = "SELECT `id` | ||
| FROM $wpdb->pmpro_membership_orders | ||
| WHERE `user_id` = '" . esc_sql( $order->user_id ) . "' | ||
| AND `id` <> '" . esc_sql( $order->id ) . "' | ||
| AND `gateway_environment` = '" . esc_sql( $order->gateway_environment ) . "' | ||
| AND `total` > 0 | ||
| AND `total` IS NOT NULL | ||
| AND status NOT IN('refunded', 'review', 'token', 'error') | ||
| AND timestamp < '" . esc_sql( date( 'Y-m-d H:i:s', $order->timestamp ) ) . "' | ||
| LIMIT 1"; |
There was a problem hiding this comment.
The non-recurring renewal check builds SQL via string concatenation. Even with esc_sql(), this is error-prone and bypasses $wpdb->prepare() protections/type handling. Please switch to $wpdb->prepare() with placeholders and cast numeric fields (e.g., user_id/id) to integers.
| $sqlQuery = "SELECT `id` | |
| FROM $wpdb->pmpro_membership_orders | |
| WHERE `user_id` = '" . esc_sql( $order->user_id ) . "' | |
| AND `id` <> '" . esc_sql( $order->id ) . "' | |
| AND `gateway_environment` = '" . esc_sql( $order->gateway_environment ) . "' | |
| AND `total` > 0 | |
| AND `total` IS NOT NULL | |
| AND status NOT IN('refunded', 'review', 'token', 'error') | |
| AND timestamp < '" . esc_sql( date( 'Y-m-d H:i:s', $order->timestamp ) ) . "' | |
| LIMIT 1"; | |
| $sqlQuery = $wpdb->prepare( | |
| "SELECT `id` | |
| FROM $wpdb->pmpro_membership_orders | |
| WHERE `user_id` = %d | |
| AND `id` <> %d | |
| AND `gateway_environment` = %s | |
| AND `total` > 0 | |
| AND `total` IS NOT NULL | |
| AND status NOT IN('refunded', 'review', 'token', 'error') | |
| AND timestamp < %s | |
| LIMIT 1", | |
| (int) $order->user_id, | |
| (int) $order->id, | |
| $order->gateway_environment, | |
| date( 'Y-m-d H:i:s', (int) $order->timestamp ) | |
| ); |
Snippet to replace "Renewal" label logics showing "Renewal" for orders which are actually renewals.
Orders status column in admin shows a not clear "Renewal" label for returning customers (orders from a customer who already had an order before - might be a renewal or not).
Some people just want to see "Renewal" for orders which are actually renewals.
Community request: https://app.slack.com/client/T042RQSG0/C042RQSGW