Skip to content
Merged
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
46 changes: 42 additions & 4 deletions inc/checkout/class-checkout.php
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,7 @@
$existing_customer = wu_get_current_customer();

if ($existing_customer) {
$active_memberships = wu_get_memberships([

Check warning on line 753 in inc/checkout/class-checkout.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Opening parenthesis of a multi-line function call must be the last content on the line
'customer_id' => $existing_customer->get_id(),
'status__in' => [
Membership_Status::ACTIVE,
Expand All @@ -758,7 +758,7 @@
Membership_Status::ON_HOLD,
],
'number' => 1,
]);

Check warning on line 761 in inc/checkout/class-checkout.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Closing parenthesis of a multi-line function call must be on a line by itself

if ( ! empty($active_memberships)) {
$existing_membership = reset($active_memberships);
Expand Down Expand Up @@ -1136,11 +1136,27 @@
/*
* Resolve the password: use the submitted value, or generate one
* when the auto_generate_password flag is present in the session.
*
* When the password is auto-generated (e.g. the simple checkout
* preset), we deliberately leave $password empty in the data
* passed to wu_create_customer() so the user-creation path falls
* through to register_new_user(). That mirrors what WordPress
* does when an admin adds a user without a password from
* wp-admin/user-new.php — the user receives the standard
* "set your password" notification email. Once the user exists
* we apply the auto-generated password via wp_set_password()
* below so the immediate auto-login path still works for the
* rest of this request.
*/
$password = $this->request_or_session('password');
$submitted_password = $this->request_or_session('password');
$auto_generate_password = (bool) $this->request_or_session('auto_generate_password');
$generated_password = '';

if ($this->request_or_session('auto_generate_password')) {
$password = wp_generate_password(16, true, false);
if ($auto_generate_password) {
$generated_password = wp_generate_password(16, true, false);
$password_for_user = ''; // Triggers register_new_user() path with notification email.
} else {
$password_for_user = $submitted_password;
}

/*
Expand All @@ -1152,7 +1168,7 @@
$customer_data = [
'username' => $username,
'email' => $this->request_or_session('email_address'),
'password' => $password,
'password' => $password_for_user,
'email_verification' => $this->get_customer_email_verification_status(),
'signup_form' => $form_slug,
'meta' => [],
Expand All @@ -1167,7 +1183,7 @@
'email' => wp_get_current_user()->user_email,
'email_verification' => 'verified',
];
} elseif (isset($customer_data['email']) && ($existing_wp_user = get_user_by('email', $customer_data['email']))) {

Check warning on line 1186 in inc/checkout/class-checkout.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Assignments must be the first block of code on a line

Check warning on line 1186 in inc/checkout/class-checkout.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Variable assignment found within a condition. Did you mean to do a comparison ?
/*
* A WP user already exists with this email.
*
Expand Down Expand Up @@ -1197,6 +1213,28 @@
if (is_wp_error($customer)) {
return $customer;
}

/*
* If the password was auto-generated, apply it to the user we
* just created so the same-request auto-login (see
* login_customer_after_checkout()) and any code that needs
* a known credential within this request keeps working.
*
* The "set your password" notification email has already been
* dispatched by register_new_user() inside wu_create_customer().
* The reset-password link in that email remains valid because
* it is keyed on the user_login + a fresh activation key, not
* on the user's stored password hash.
*
* @since 2.6.0
*/
if ($auto_generate_password && $generated_password && ! is_wp_error($customer)) {
$new_user_id = $customer->get_user_id();

if ($new_user_id) {
wp_set_password($generated_password, $new_user_id);
}
}
}

/*
Expand Down Expand Up @@ -2052,15 +2090,15 @@
/* translators: %s: field label */
'field_invalid_email' => __('%s must be a valid email address.', 'ultimate-multisite'),
/* translators: 1: field label, 2: minimum character count */
'field_min_length' => __('%s must be at least %d characters.', 'ultimate-multisite'),

Check warning on line 2093 in inc/checkout/class-checkout.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Multiple placeholders in translatable strings should be ordered. Expected "%1$s, %2$d", but got "%s, %d" in '%s must be at least %d characters.'.
/* translators: 1: field label, 2: maximum character count */
'field_max_length' => __('%s must not exceed %d characters.', 'ultimate-multisite'),

Check warning on line 2095 in inc/checkout/class-checkout.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Multiple placeholders in translatable strings should be ordered. Expected "%1$s, %2$d", but got "%s, %d" in '%s must not exceed %d characters.'.
/* translators: %s: field label */
'field_alpha_dash' => __('%s may only contain letters, numbers, dashes, and underscores.', 'ultimate-multisite'),
/* translators: %s: field label */
'field_lowercase' => __('%s must be lowercase.', 'ultimate-multisite'),
/* translators: 1: field label, 2: other field label */
'field_same' => __('%s must match %s.', 'ultimate-multisite'),

Check warning on line 2101 in inc/checkout/class-checkout.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Multiple placeholders in translatable strings should be ordered. Expected "%1$s, %2$s", but got "%s, %s" in '%s must match %s.'.
/* translators: %s: field label */
'field_integer' => __('%s must be a whole number.', 'ultimate-multisite'),
/* translators: %s: field label */
Expand Down Expand Up @@ -2534,7 +2572,7 @@
*
* First, let's set upm the general rules:
*/
/*

Check warning on line 2575 in inc/checkout/class-checkout.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Empty line required before block comment
* When the password field is set to auto-generate, the customer does
* not submit a password value, so we must not require it.
*/
Expand Down
87 changes: 87 additions & 0 deletions tests/WP_Ultimo/Checkout/Checkout_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -1651,7 +1651,7 @@
public function test_get_js_validation_rules_is_filterable(): void {

add_filter('wu_checkout_js_validation_rules', function ($rules) {
$rules['custom_js_field'] = [['rule' => 'required', 'param' => null]];

Check warning on line 1654 in tests/WP_Ultimo/Checkout/Checkout_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

When a multi-item array uses associative keys, each value should start on a new line.
return $rules;
});

Expand Down Expand Up @@ -1805,7 +1805,7 @@
*/
public function test_setup_checkout_sets_already_setup_flag(): void {

$checkout = Checkout::get_instance();

Check warning on line 1808 in tests/WP_Ultimo/Checkout/Checkout_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Equals sign not aligned with surrounding assignments; expected 3 spaces but found 4 spaces
$reflection = new \ReflectionClass($checkout);
$setup_prop = $reflection->getProperty('already_setup');

Expand Down Expand Up @@ -2945,6 +2945,93 @@
unset($_REQUEST['email_address'], $_REQUEST['auto_generate_username'], $_REQUEST['password']);
}

/**
* Test maybe_create_customer with auto_generate_password sends the
* standard WordPress "set your password" notification email and applies
* the auto-generated password to the new user record.
*
* Regression test: previously, the auto-generated password was passed
* straight to wpmu_create_user(), which silently suppresses the user
* notification email. Now we route the empty-password path through
* register_new_user() so WP fires wp_send_new_user_notifications() with
* the 'both' flag, then apply the generated password via wp_set_password().
*/
public function test_maybe_create_customer_auto_generate_password_sends_notification(): void {

wp_set_current_user(0);

$checkout = Checkout::get_instance();
$reflection = new \ReflectionClass($checkout);
$method = $reflection->getMethod('maybe_create_customer');

if (PHP_VERSION_ID < 80100) {
$method->setAccessible(true);
}

$order_prop = $this->get_order_prop($reflection);
$order_prop->setValue($checkout, new Cart(['products' => []]));

$this->ensure_session($checkout);

$unique_suffix = time() . '_' . wp_rand(1000, 9999);
$email = 'autopwd_' . $unique_suffix . '@example.com';
$username = 'autopwd_' . $unique_suffix;

// Capture wp_new_user_notification firing for the user role.
$notification_fired = false;
$captured_user_id = 0;
$user_callback = static function ($user_id) use (&$notification_fired, &$captured_user_id) {
$notification_fired = true;
$captured_user_id = $user_id;
};
add_action('register_new_user', $user_callback);

// Block actual mail delivery during the test.
add_filter('pre_wp_mail', '__return_true', 1);

$_REQUEST['email_address'] = $email;
$_REQUEST['username'] = $username;
$_REQUEST['auto_generate_password'] = '1';
$_REQUEST['password'] = '';

$result = $method->invoke($checkout);

// Cleanup hooks/filters before assertions so a failure doesn't leak.
remove_action('register_new_user', $user_callback);
remove_filter('pre_wp_mail', '__return_true', 1);

if (is_wp_error($result)) {
$this->markTestSkipped('Customer creation failed: ' . $result->get_error_message());
}

$this->assertInstanceOf(\WP_Ultimo\Models\Customer::class, $result);
$this->assertTrue(
$notification_fired,
'register_new_user action should fire so WP sends the "set your password" email when auto_generate_password is set.'
);
$this->assertSame(
(int) $result->get_user_id(),
(int) $captured_user_id,
'register_new_user should fire for the same user that the customer record links to.'
);

// The user record exists and has a non-empty password hash (set by wp_set_password()).
$user = get_user_by('id', $result->get_user_id());
$this->assertInstanceOf(\WP_User::class, $user);
$this->assertNotEmpty($user->user_pass, 'Auto-generated password should be applied to the user record.');

// Cleanup
wpmu_delete_user($result->get_user_id());
$result->delete();
$order_prop->setValue($checkout, null);
unset(
$_REQUEST['email_address'],
$_REQUEST['username'],
$_REQUEST['auto_generate_password'],
$_REQUEST['password']
);
}

// -------------------------------------------------------------------------
// maybe_create_site — additional branches
// -------------------------------------------------------------------------
Expand Down
Loading