Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelogs/fix-staging-clone-recurring-charges.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
significance: patch
type: fixed
entry: Improved prevention of recurring charges when the site URL no longer matches the stored lock.
21 changes: 9 additions & 12 deletions includes/class.llms.site.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ public static function check_status() {
}

return false;

}

/**
Expand Down Expand Up @@ -119,7 +118,6 @@ public static function get_url() {
* @param string $url The cleaned LLMS_Site URL.
*/
return apply_filters( 'llms_site_get_url', $url );

}

/**
Expand All @@ -129,6 +127,9 @@ public static function get_url() {
* uses the stored site setting (with a fallback to the default value), and
* a final fallback to `false` if the feature cannot be found.
*
* Recurring payments are treated as disabled when the site is a clone,
* so Action Scheduler charges skip without waiting for an admin to visit wp-admin.
*
* @since 3.0.0
* @since 4.12.0 Allow feature configuration via constants.
*
Expand All @@ -140,9 +141,12 @@ public static function get_feature( $feature ) {
$status = self::get_feature_constant( $feature );
if ( is_null( $status ) ) {

$features = self::get_features();
$status = isset( $features[ $feature ] ) ? $features[ $feature ] : false;

if ( 'recurring_payments' === $feature && self::is_clone() ) {
$status = false;
} else {
$features = self::get_features();
$status = isset( $features[ $feature ] ) ? $features[ $feature ] : false;
}
}

/**
Expand All @@ -154,7 +158,6 @@ public static function get_feature( $feature ) {
* @param string $feature The feature ID/key.
*/
return apply_filters( 'llms_site_get_feature', $status, $feature );

}

/**
Expand All @@ -176,7 +179,6 @@ protected static function get_feature_constant( $feature ) {
}

return null;

}

/**
Expand Down Expand Up @@ -206,7 +208,6 @@ public static function get_features() {
);

return get_option( 'llms_site_get_features', $defaults );

}

/**
Expand All @@ -223,7 +224,6 @@ public static function update_feature( $feature, $val ) {
$features = self::get_features();
$features[ $feature ] = $val;
update_option( 'llms_site_get_features', $features );

}

/**
Expand All @@ -249,7 +249,6 @@ public static function is_clone() {
* @param boolean $is_clone When `true` the site is considered a "clone", otherwise it is not.
*/
return apply_filters( 'llms_site_is_clone', $is_clone );

}

/**
Expand All @@ -273,7 +272,5 @@ public static function is_clone_ignored() {
* @param boolean $is_clone_ignored If `true`, the clone is ignored, otherwise it is not.
*/
return apply_filters( 'llms_site_is_clone_ignored', llms_parse_bool( get_option( 'llms_site_url_ignore', 'no' ) ) );

}

}
58 changes: 58 additions & 0 deletions tests/phpunit/unit-tests/class-llms-test-site.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,64 @@ public function test_get_set_features() {

}

/**
* Recurring payments read as disabled on a clone even when the stored feature is still enabled.
*
* @since [version]
*
* @return void
*/
public function test_get_feature_clone_disables_recurring_payments() {

$original_siteurl = get_option( 'siteurl' );
$original_lock = get_option( 'llms_site_url' );

try {
LLMS_Site::update_feature( 'recurring_payments', true );
$this->assertTrue( LLMS_Site::get_feature( 'recurring_payments' ) );

update_option( 'siteurl', 'http://fakeurl.tld' );
$this->assertTrue( LLMS_Site::is_clone() );
$this->assertFalse( LLMS_Site::get_feature( 'recurring_payments' ) );

update_option( 'siteurl', $original_siteurl );
$this->assertTrue( LLMS_Site::get_feature( 'recurring_payments' ) );
} finally {
update_option( 'siteurl', $original_siteurl );
update_option( 'llms_site_url', $original_lock );
}

}

/**
* A feature constant still wins when the site is a clone.
*
* @since [version]
*
* @runInSeparateProcess
* @preserveGlobalState disabled
*
* @return void
*/
public function test_get_feature_constant_overrides_clone() {

$original_siteurl = get_option( 'siteurl' );
$original_lock = get_option( 'llms_site_url' );

try {
LLMS_Site::update_feature( 'recurring_payments', true );
update_option( 'siteurl', 'http://fakeurl.tld' );
$this->assertTrue( LLMS_Site::is_clone() );

llms_maybe_define_constant( 'LLMS_SITE_FEATURE_RECURRING_PAYMENTS', true );
$this->assertTrue( LLMS_Site::get_feature( 'recurring_payments' ) );
} finally {
update_option( 'siteurl', $original_siteurl );
update_option( 'llms_site_url', $original_lock );
}

}


/**
* Test is_clone() function
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,36 @@ public function test_recurring_charge_staging_mode() {

}

/**
* Recurring charges skip on a clone even when the stored feature is still enabled.
*
* @since [version]
*
* @return void
*/
public function test_recurring_charge_skipped_on_clone_before_admin_visit() {

add_filter( 'llms_site_is_clone', '__return_true' );

try {
LLMS_Site::update_feature( 'recurring_payments', true );

$plan = $this->get_mock_plan( '200.00', 1 );
$order = $this->get_mock_order( $plan );

$skip_actions = did_action( 'llms_order_recurring_charge_skipped' );
$note_actions = did_action( 'llms_new_order_note_added' );

do_action( 'llms_charge_recurring_payment', $order->get( 'id' ) );

$this->assertSame( $note_actions + 1, did_action( 'llms_new_order_note_added' ) );
$this->assertSame( $skip_actions + 1, did_action( 'llms_order_recurring_charge_skipped' ) );
} finally {
remove_filter( 'llms_site_is_clone', '__return_true' );
}

}

/**
* Test gateway-related errors encountered during a recurring_charge attempt.
*
Expand Down
Loading