-
Notifications
You must be signed in to change notification settings - Fork 37
Create pending renewal order (improve safety/error handling) #768
Changes from all commits
25fc0ef
8427755
598bf55
5b78cbc
36ce48f
467409c
2deaa0f
72c4a08
10d531c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -247,21 +247,65 @@ public static function process_renewal_action_request( $subscription ) { | |||||||||
/** | ||||||||||
* Handles the action request to create a pending renewal order. | ||||||||||
* | ||||||||||
* @param array $subscription | ||||||||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 | ||||||||||
* @param WC_Subscription $subscription | ||||||||||
*/ | ||||||||||
public static function create_pending_renewal_action_request( $subscription ) { | ||||||||||
$subscription->add_order_note( __( 'Create pending renewal order requested by admin action.', 'woocommerce-subscriptions' ), false, true ); | ||||||||||
$subscription->update_status( 'on-hold' ); | ||||||||||
|
||||||||||
try { | ||||||||||
$subscription->update_status( 'on-hold' ); | ||||||||||
} catch ( Exception $e ) { | ||||||||||
wcs_add_admin_notice( | ||||||||||
__( 'Pending renewal order was not created, as it was not possible to update the subscription status.', 'woocommerce-subscriptions' ), | ||||||||||
'error' | ||||||||||
); | ||||||||||
return; | ||||||||||
} | ||||||||||
|
||||||||||
$renewal_order = wcs_create_renewal_order( $subscription ); | ||||||||||
|
||||||||||
if ( is_wp_error( $renewal_order ) ) { | ||||||||||
self::notify( | ||||||||||
$subscription, | ||||||||||
'error', | ||||||||||
esc_html__( 'Creation of the pending renewal order failed.', 'woocommerce-subscriptions' ) | ||||||||||
. ' ' . $renewal_order->get_error_message() | ||||||||||
); | ||||||||||
|
||||||||||
return; | ||||||||||
} | ||||||||||
|
||||||||||
if ( ! $subscription->is_manual() ) { | ||||||||||
$renewal_url = $renewal_order->get_edit_order_url(); | ||||||||||
|
||||||||||
try { | ||||||||||
// We need to pass the payment gateway instance to be compatible with WC < 3.0, only WC 3.0+ supports passing the string name. | ||||||||||
$renewal_order->set_payment_method( wc_get_payment_gateway_by_order( $subscription ) ); | ||||||||||
|
||||||||||
$renewal_order->set_payment_method( wc_get_payment_gateway_by_order( $subscription ) ); // We need to pass the payment gateway instance to be compatible with WC < 3.0, only WC 3.0+ supports passing the string name | ||||||||||
if ( is_callable( array( $renewal_order, 'save' ) ) ) { // WC 3.0+ | ||||||||||
$renewal_order->save(); | ||||||||||
} | ||||||||||
|
||||||||||
if ( is_callable( array( $renewal_order, 'save' ) ) ) { // WC 3.0+ | ||||||||||
$renewal_order->save(); | ||||||||||
wcs_add_admin_notice( | ||||||||||
sprintf( | ||||||||||
/* Translators: %1$s opening link tag, %2$s closing link tag. */ | ||||||||||
esc_html__( 'A pending %1$srenewal order%2$s was successfully created!', 'woocommerce-subscriptions' ), | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been pondering whether to send this feedback as it feels very minor... 😅 While testing these changes, I noticed that the The
Here's what I'm thinking
Curious to hear your thoughts on this! The other idea I had was to remove the initial order note at the start and instead pass "Create pending renewal order requested by admin action." as the $note param when calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Agreed, except (and here I'm probably missing a trick) I don't think the original Nonetheless, I agree with the concept, and committed f9114d0, which should give us the admin notice (top of screen) alongside the existing order note: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Btw, very glad to get notes like this. I think this is how we remove some of the papercuts from the UX (or, in this case, it's how we avoid adding new papercuts, so to speak). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Oh sorry I probably should've shared how I was testing this 😅
I'm not sure if this helps but here's a longer screenshot:
With the "requested by admin action" note being added at the start of the process, it acts as a separator but also adds context for why this renewal was created. For that reason, as well as keeping this action consistent with the "Process Renewal" action, I think we should add back this order note which was removed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Edit: ignore this comment, I see what you mean now.)
Hmm. I wasn't missing this before, and on trying to replicate once more I do still get the requested by admin action note: Or perhaps besides a subtle difference in testing, a further commit addressed this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ☝🏼 Ignore! |
||||||||||
'<a href="' . esc_url( $renewal_url ) . '">', | ||||||||||
'</a>' | ||||||||||
), | ||||||||||
'success' | ||||||||||
); | ||||||||||
} catch ( WC_Data_Exception $e ) { | ||||||||||
self::notify( | ||||||||||
$subscription, | ||||||||||
'error', | ||||||||||
sprintf( | ||||||||||
/* Translators: %1$s opening link tag, %2$s closing link tag. */ | ||||||||||
esc_html__( 'A %1$spending renewal order%2$s was successfully created, but there was a problem setting the payment method. Please review the order.', 'woocommerce-subscriptions' ), | ||||||||||
'<a href="' . esc_url( $renewal_url ) . '">', | ||||||||||
'</a>' | ||||||||||
) | ||||||||||
); | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
|
@@ -722,4 +766,22 @@ private static function reorder_subscription_line_items_meta_box() { | |||||||||
$items_meta_box['args'] | ||||||||||
); | ||||||||||
} | ||||||||||
|
||||||||||
/** | ||||||||||
* Notifies the user of an operational success or failure, and records a matching order note. | ||||||||||
* | ||||||||||
* In essence, it can be convenient to generate both an admin notice (to give the user some clear and | ||||||||||
* obvious feedback) and record the same as an order note (the admin notice could be missed, and is | ||||||||||
* auto-dismissed after the first view). | ||||||||||
* | ||||||||||
* @param WC_Subscription $subscription The subscription we are working with. | ||||||||||
* @param string $type Message type: 'success' or 'error. | ||||||||||
* @param string $message Message text, which will be used both for an admin notice and for the order note. | ||||||||||
* | ||||||||||
* @return void | ||||||||||
*/ | ||||||||||
private static function notify( WC_Subscription $subscription, $type, $message ) { | ||||||||||
$subscription->add_order_note( $message, false, true ); | ||||||||||
wcs_add_admin_notice( $message, $type ); | ||||||||||
} | ||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -109,7 +109,7 @@ function wcs_display_admin_notices( $clear = true ) { | |
continue; | ||
} | ||
|
||
$notice_output[] = esc_html( $notice['message'] ); | ||
$notice_output[] = $notice['message']; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does removing this I could be wrong by my understanding is that just having I'm sorry for putting you on this rollercoaster! I didn't even consider this escaping links issue when suggesting putting links in the admin notices 😭 What are your thoughts given the above? I'm thinking we roll back the idea of including a link in the notice, but then that means we don't have a need for the new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Who doesn't enjoy rollercoasters? 🎢 ... I think the links are useful—the user experience feels better—so it would be great to keep them if we can do so safely. Drawing some amount of inspiration from Woo Core's HtmlSanitizer, what if continue to use Kses, but with a tighter set of rules than we get from $allow_links = array(
'a' => array(
'href' => true,
),
);
wp_kses( $html, $allow_links ); We could expand this over time if, for instance, we wished to allow
I'm not tied to the new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would still allow bad URLs like this wouldn't it?
Maybe we can go a step further and manually escape all hrefs for example? $allow_links = [
'a' => [
'href' => true,
],
];
// Escape href values
$sanitized_message = preg_replace_callback(
'/href="([^"]*)"/i',
function ( $matches ) {
return 'href="' . esc_url( $matches[1] ) . '"';
},
$notice_message
);
wp_kses( $sanitized_message, $allow_links ); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
As with so many things in WordPress, this isn't fixed in stone, but assuming defaults that wouldn't be possible (because <a href="alert('XSS')">Click here</a> There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TIL! Thanks @barryhughes for sharing! I guess if some malicious actor has access to filter 'kses_allowed_protocols' then we're in deep trouble, unless another plugin adds it unknowing that is poses security risks. In any case, given the above, I think I'm fine to stick with either There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Massively overthinking this no-doubt, but wcs_add_admin_notice(
'A new order was created.',
links: [
__( 'Review new order', 'txtdomain' ) => get_admin_url( 'foo bar baz' ),
__( 'Cancel new order', 'txtdomain' ) => get_admin_url( 'bar baz foo' ),
__( 'Give us 5 stars', 'txtdomain' ) => get_admin_url( 'baz foo bar' ),
]
); The output might then look a little like this: Escaping being done as late as possible, from within To be clear, though, I'm just jamming on possibilities rather than pushing this as my preferred choice. I think it's fairly reasonable, and especially secure, but I also think using Kses is fine, or we could do as you suggest with a regex, or we could just drop the links from these messages (if anything, that's my least prefered option, because I think we lose something if we do, but it's definitely not a deal breaker). |
||
unset( $notices[ $index ] ); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,7 +35,17 @@ function wcs_create_renewal_order( $subscription ) { | |
|
||
WCS_Related_Order_Store::instance()->add_relation( $renewal_order, $subscription, 'renewal' ); | ||
|
||
return apply_filters( 'wcs_renewal_order_created', $renewal_order, $subscription ); | ||
/** | ||
* Provides an opportunity to monitor, interact with and replace renewal orders when they | ||
* are first created. | ||
* | ||
* @param WC_Order $renewal_order The renewal order. | ||
* @param WC_Subscription $subscription The subscription the renewal is related to. | ||
*/ | ||
$filtered_renewal_order = apply_filters( 'wcs_renewal_order_created', $renewal_order, $subscription ); | ||
|
||
// It is possible that a filter function will replace the renewal order with something else entirely. | ||
return $filtered_renewal_order instanceof WC_Order ? $filtered_renewal_order : $renewal_order; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just wanted to chime in to say I ❤️ this change. I was looking at #757 and I think that error wouldn't have occurred with this change. I wonder if it would be worth to report these kind of failures somewhere. I'm thinking that if there are 2 or 3+ hooks which use this filter correctly, the changes they make would be overridden by 1 poorly written piece of code that returns a bool or null etc. We don't have a good mechanism for doing this kind of thing but I'm thinking something like a WC log entry or something. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd definitely be up for developing this concept a little further (and agree there is lots of value in logging when things go wrong). |
||
} | ||
|
||
/** | ||
|
Uh oh!
There was an error while loading. Please reload this page.