diff --git a/inc/apis/schemas/checkout-form-create.php b/inc/apis/schemas/checkout-form-create.php index ff8a9b512..012a42418 100644 --- a/inc/apis/schemas/checkout-form-create.php +++ b/inc/apis/schemas/checkout-form-create.php @@ -60,13 +60,14 @@ 'required' => false, ], 'template' => [ - 'description' => __("Template mode. Can be either 'blank', 'single-step' or 'multi-step'.", 'ultimate-multisite'), + 'description' => __("Template mode. Can be either 'blank', 'single-step', 'multi-step' or 'simple'.", 'ultimate-multisite'), 'type' => 'string', 'required' => false, 'enum' => [ 'blank', 'single-step', 'multi-step', + 'simple', ], ], 'date_created' => [ diff --git a/inc/apis/schemas/checkout-form-update.php b/inc/apis/schemas/checkout-form-update.php index ca206765b..636f9c340 100644 --- a/inc/apis/schemas/checkout-form-update.php +++ b/inc/apis/schemas/checkout-form-update.php @@ -60,13 +60,14 @@ 'required' => false, ], 'template' => [ - 'description' => __("Template mode. Can be either 'blank', 'single-step' or 'multi-step'.", 'ultimate-multisite'), + 'description' => __("Template mode. Can be either 'blank', 'single-step', 'multi-step' or 'simple'.", 'ultimate-multisite'), 'type' => 'string', 'required' => false, 'enum' => [ 'blank', 'single-step', 'multi-step', + 'simple', ], ], 'date_created' => [ diff --git a/inc/checkout/class-checkout.php b/inc/checkout/class-checkout.php index 357072e5a..0552724fa 100644 --- a/inc/checkout/class-checkout.php +++ b/inc/checkout/class-checkout.php @@ -1056,6 +1056,16 @@ protected function maybe_create_customer() { $username = wu_username_from_email($this->request_or_session('email_address')); } + /* + * Resolve the password: use the submitted value, or generate one + * when the auto_generate_password flag is present in the session. + */ + $password = $this->request_or_session('password'); + + if ($this->request_or_session('auto_generate_password')) { + $password = wp_generate_password(16, true, false); + } + /* * If we get to this point, * we don't have an existing customer. @@ -1065,7 +1075,7 @@ protected function maybe_create_customer() { $customer_data = [ 'username' => $username, 'email' => $this->request_or_session('email_address'), - 'password' => $this->request_or_session('password'), + 'password' => $password, 'email_verification' => $this->get_customer_email_verification_status(), 'signup_form' => $form_slug, 'meta' => [], @@ -2113,6 +2123,33 @@ public function get_checkout_variables() { return apply_filters('wu_get_checkout_variables', $variables, $this); } + /** + * Returns true when the current checkout form has a password field + * configured with auto_generate_password enabled. + * + * Used to suppress client-side password validation rules when no + * password input is rendered. + * + * @since 2.0.20 + * @return bool + */ + protected function form_has_auto_generate_password(): bool { + + if ( ! $this->checkout_form) { + return false; + } + + foreach ($this->checkout_form->get_settings() as $step) { + foreach (wu_get_isset($step, 'fields', []) as $field) { + if ('password' === wu_get_isset($field, 'type') && ! empty($field['auto_generate_password'])) { + return true; + } + } + } + + return false; + } + /** * Converts the PHP validation rules into a JS-friendly structure. * @@ -2132,6 +2169,15 @@ public function get_js_validation_rules(): array { $raw_rules = $this->validation_rules(); + /* + * When the checkout form uses auto-generated passwords, strip the + * password-related rules from the JS ruleset so the client-side + * validator does not block submission on a field that is never shown. + */ + if ($this->form_has_auto_generate_password()) { + unset($raw_rules['password'], $raw_rules['password_conf'], $raw_rules['valid_password']); + } + /* * Rules that require a database lookup or complex server-side logic. * These are skipped for client-side validation. @@ -2260,16 +2306,22 @@ public function validation_rules() { * * First, let's set upm the general rules: */ + /* + * When the password field is set to auto-generate, the customer does + * not submit a password value, so we must not require it. + */ + $auto_generate_password = $this->request_or_session('auto_generate_password'); + $rules = [ 'email_address' => 'required_without:user_id|email|unique:\WP_User,email', 'email_address_confirmation' => 'same:email_address', 'username' => 'required_without:user_id|alpha_dash|min:4|lowercase|unique:\WP_User,login', - 'password' => 'required_without:user_id|min:6', - 'password_conf' => 'same:password', + 'password' => $auto_generate_password ? '' : 'required_without:user_id|min:6', + 'password_conf' => $auto_generate_password ? '' : 'same:password', 'template_id' => 'integer|site_template', 'products' => 'products', 'gateway' => '', - 'valid_password' => 'accepted', + 'valid_password' => $auto_generate_password ? '' : 'accepted', 'billing_country' => 'country|required_with:billing_country', 'billing_zip_code' => 'required_with:billing_zip_code', 'billing_state' => 'state', diff --git a/inc/checkout/signup-fields/class-signup-field-password.php b/inc/checkout/signup-fields/class-signup-field-password.php index 202b9dbe6..2abe076bb 100644 --- a/inc/checkout/signup-fields/class-signup-field-password.php +++ b/inc/checkout/signup-fields/class-signup-field-password.php @@ -123,6 +123,7 @@ public function get_icon() { public function defaults() { return [ + 'auto_generate_password' => false, 'password_confirm_field' => false, 'password_confirm_label' => __('Confirm Password', 'ultimate-multisite'), ]; @@ -166,17 +167,33 @@ public function force_attributes() { public function get_fields() { return [ + 'auto_generate_password' => [ + 'type' => 'toggle', + 'title' => __('Auto-generate', 'ultimate-multisite'), + 'desc' => __('Check this option to auto-generate a secure password for the customer. The password will be emailed to them after signup.', 'ultimate-multisite'), + 'tooltip' => '', + 'value' => 0, + 'html_attr' => [ + 'v-model' => 'auto_generate_password', + ], + ], 'password_strength_meter' => [ - 'type' => 'toggle', - 'title' => __('Display Password Strength Meter', 'ultimate-multisite'), - 'desc' => __('Adds a password strength meter below the password field. Enabling this option also enforces passwords to be strong.', 'ultimate-multisite'), - 'value' => 1, + 'type' => 'toggle', + 'title' => __('Display Password Strength Meter', 'ultimate-multisite'), + 'desc' => __('Adds a password strength meter below the password field. Enabling this option also enforces passwords to be strong.', 'ultimate-multisite'), + 'value' => 1, + 'wrapper_html_attr' => [ + 'v-show' => '!auto_generate_password', + ], ], 'password_confirm_field' => [ - 'type' => 'toggle', - 'title' => __('Display Password Confirm Field', 'ultimate-multisite'), - 'desc' => __('Adds a "Confirm your Password" field below the default password field to reduce the chance of making a mistake.', 'ultimate-multisite'), - 'value' => 1, + 'type' => 'toggle', + 'title' => __('Display Password Confirm Field', 'ultimate-multisite'), + 'desc' => __('Adds a "Confirm your Password" field below the default password field to reduce the chance of making a mistake.', 'ultimate-multisite'), + 'value' => 1, + 'wrapper_html_attr' => [ + 'v-show' => '!auto_generate_password', + ], ], ]; } @@ -197,6 +214,20 @@ public function to_fields_array($attributes) { return []; } + /* + * Auto-generate mode: emit a hidden flag so the checkout handler + * knows to generate a password server-side. No visible fields needed. + */ + if (! empty($attributes['auto_generate_password'])) { + return [ + 'auto_generate_password' => [ + 'type' => 'hidden', + 'id' => 'auto_generate_password', + 'value' => '1', + ], + ]; + } + $checkout_fields = []; $checkout_fields['password'] = [ diff --git a/inc/models/class-checkout-form.php b/inc/models/class-checkout-form.php index 638110868..ee023dc77 100644 --- a/inc/models/class-checkout-form.php +++ b/inc/models/class-checkout-form.php @@ -148,7 +148,7 @@ public function validation_rules() { 'allowed_countries' => 'default:', 'thank_you_page_id' => 'integer', 'conversion_snippets' => 'nullable|default:', - 'template' => 'in:blank,single-step,multi-step', + 'template' => 'in:blank,single-step,multi-step,simple', ]; } @@ -527,6 +527,8 @@ public function use_template($template = 'single-step'): void { $this->set_settings($fields); } elseif ('single-step' === $template) { $fields = $this->get_single_step_template(); + } elseif ('simple' === $template) { + $fields = $this->get_simple_template(); } $this->set_settings($fields); @@ -641,6 +643,127 @@ private function get_single_step_template() { return apply_filters('wu_checkout_form_single_step_template', $steps); } + /** + * Get the contents of the simple template. + * + * A minimal single-step form where the customer only enters their email + * address. Username, password, site title, and site URL are all + * auto-generated. A product must be pre-selected; template selection is + * shown only when no template has been pre-selected via the URL. + * + * @since 2.0.20 + * @return array + */ + private function get_simple_template() { + + $plan_ids = wu_get_plans(['fields' => 'ids']); + + $steps = [ + [ + 'id' => 'checkout', + 'name' => __('Checkout', 'ultimate-multisite'), + 'desc' => '', + 'fields' => [ + [ + 'step' => 'checkout', + 'name' => __('Pre-selected Products', 'ultimate-multisite'), + 'type' => 'products', + 'id' => 'products', + 'products' => implode(',', $plan_ids), + ], + [ + 'step' => 'checkout', + 'name' => __('Email', 'ultimate-multisite'), + 'type' => 'email', + 'id' => 'email_address', + 'required' => true, + 'placeholder' => '', + 'tooltip' => '', + ], + [ + 'step' => 'checkout', + 'name' => __('Username', 'ultimate-multisite'), + 'type' => 'username', + 'id' => 'username', + 'required' => true, + 'placeholder' => '', + 'tooltip' => '', + 'auto_generate_username' => true, + ], + [ + 'step' => 'checkout', + 'name' => __('Password', 'ultimate-multisite'), + 'type' => 'password', + 'id' => 'password', + 'required' => true, + 'placeholder' => '', + 'tooltip' => '', + 'auto_generate_password' => true, + ], + [ + 'step' => 'checkout', + 'name' => __('Site Title', 'ultimate-multisite'), + 'type' => 'site_title', + 'id' => 'site_title', + 'required' => true, + 'placeholder' => '', + 'tooltip' => '', + 'auto_generate_site_title' => true, + ], + [ + 'step' => 'checkout', + 'name' => __('Site URL', 'ultimate-multisite'), + 'type' => 'site_url', + 'id' => 'site_url', + 'required' => true, + 'placeholder' => '', + 'tooltip' => '', + 'auto_generate_site_url' => true, + ], + [ + 'step' => 'checkout', + 'name' => __('Template Selection', 'ultimate-multisite'), + 'type' => 'template_selection', + 'id' => 'template_selection', + 'template_selection_type' => 'all', + 'template_selection_template' => 'clean', + 'hide_template_selection_when_pre_selected' => true, + ], + [ + 'step' => 'checkout', + 'name' => __('Your Order', 'ultimate-multisite'), + 'type' => 'order_summary', + 'id' => 'order_summary', + 'order_summary_template' => 'clean', + 'table_columns' => 'simple', + ], + [ + 'step' => 'checkout', + 'name' => __('Payment Method', 'ultimate-multisite'), + 'type' => 'payment', + 'id' => 'payment', + ], + [ + 'step' => 'checkout', + 'name' => __('Billing Address', 'ultimate-multisite'), + 'type' => 'billing_address', + 'id' => 'billing_address', + 'required' => true, + 'zip_and_country' => '1', + ], + [ + 'step' => 'checkout', + 'name' => __('Checkout', 'ultimate-multisite'), + 'type' => 'submit_button', + 'id' => 'checkout', + ], + ], + ], + ]; + + return apply_filters('wu_checkout_form_simple_template', $steps); + } + /** * Get the contents of the multi step template. * @@ -1160,6 +1283,7 @@ public function save() { $step_types = [ 'multi-step', 'single-step', + 'simple', ]; if ($this->template && in_array($this->template, $step_types, true)) {