Delayed Account Creation: Support option to send password setup link to customer via email (#51227)
* Update form handling to work with automatically generated passwords * Keep user logged in after password reset. * Ignore comments for hooks in changed method * Add changefile(s) from automation for the following project(s): woocommerce-blocks, woocommerce * Checkmark list style --------- Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
parent
c5ed90535f
commit
d567c6c698
|
@ -32,7 +32,9 @@ const defaultTemplate = [
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'core/list',
|
'core/list',
|
||||||
{},
|
{
|
||||||
|
className: 'is-style-checkmark-list',
|
||||||
|
},
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
'core/list-item',
|
'core/list-item',
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { PRIVACY_URL, TERMS_URL } from '@woocommerce/block-settings';
|
||||||
import { ValidatedTextInput } from '@woocommerce/blocks-components';
|
import { ValidatedTextInput } from '@woocommerce/blocks-components';
|
||||||
import { useSelect } from '@wordpress/data';
|
import { useSelect } from '@wordpress/data';
|
||||||
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
|
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
|
||||||
|
import { getSetting } from '@woocommerce/settings';
|
||||||
|
|
||||||
const termsPageLink = TERMS_URL ? (
|
const termsPageLink = TERMS_URL ? (
|
||||||
<a href={ TERMS_URL } target="_blank" rel="noreferrer">
|
<a href={ TERMS_URL } target="_blank" rel="noreferrer">
|
||||||
|
@ -26,6 +27,67 @@ const privacyPageLink = PRIVACY_URL ? (
|
||||||
<span>{ __( 'Privacy Policy', 'woocommerce' ) }</span>
|
<span>{ __( 'Privacy Policy', 'woocommerce' ) }</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const PasswordField = ( {
|
||||||
|
isLoading,
|
||||||
|
password,
|
||||||
|
setPassword,
|
||||||
|
}: {
|
||||||
|
isLoading: boolean;
|
||||||
|
password: string;
|
||||||
|
setPassword: ( password: string ) => void;
|
||||||
|
} ) => {
|
||||||
|
const [ passwordStrength, setPasswordStrength ] = useState( 0 );
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ValidatedTextInput
|
||||||
|
disabled={ isLoading }
|
||||||
|
type="password"
|
||||||
|
label={ __( 'Password', 'woocommerce' ) }
|
||||||
|
className={ `wc-block-components-address-form__password` }
|
||||||
|
value={ password }
|
||||||
|
required={ true }
|
||||||
|
errorId={ 'account-password' }
|
||||||
|
customValidityMessage={ (
|
||||||
|
validity: ValidityState
|
||||||
|
): string | undefined => {
|
||||||
|
if (
|
||||||
|
validity.valueMissing ||
|
||||||
|
validity.badInput ||
|
||||||
|
validity.typeMismatch
|
||||||
|
) {
|
||||||
|
return __(
|
||||||
|
'Please enter a valid password',
|
||||||
|
'woocommerce'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} }
|
||||||
|
customValidation={ ( inputObject ) => {
|
||||||
|
if ( passwordStrength < 2 ) {
|
||||||
|
inputObject.setCustomValidity(
|
||||||
|
__(
|
||||||
|
'Please create a stronger password',
|
||||||
|
'woocommerce'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} }
|
||||||
|
onChange={ ( value: string ) => setPassword( value ) }
|
||||||
|
feedback={
|
||||||
|
<PasswordStrengthMeter
|
||||||
|
password={ password }
|
||||||
|
onChange={ ( strength: number ) =>
|
||||||
|
setPasswordStrength( strength )
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const Form = ( {
|
const Form = ( {
|
||||||
attributes: blockAttributes,
|
attributes: blockAttributes,
|
||||||
isEditor,
|
isEditor,
|
||||||
|
@ -35,7 +97,6 @@ const Form = ( {
|
||||||
} ) => {
|
} ) => {
|
||||||
const [ isLoading, setIsLoading ] = useState( false );
|
const [ isLoading, setIsLoading ] = useState( false );
|
||||||
const [ password, setPassword ] = useState( '' );
|
const [ password, setPassword ] = useState( '' );
|
||||||
const [ passwordStrength, setPasswordStrength ] = useState( 0 );
|
|
||||||
const hasValidationError = useSelect( ( select ) =>
|
const hasValidationError = useSelect( ( select ) =>
|
||||||
select( VALIDATION_STORE_KEY ).getValidationError( 'account-password' )
|
select( VALIDATION_STORE_KEY ).getValidationError( 'account-password' )
|
||||||
);
|
);
|
||||||
|
@ -44,6 +105,13 @@ const Form = ( {
|
||||||
( isEditor ? 'customer@email.com' : '' );
|
( isEditor ? 'customer@email.com' : '' );
|
||||||
const nonceToken = blockAttributes?.nonceToken || '';
|
const nonceToken = blockAttributes?.nonceToken || '';
|
||||||
|
|
||||||
|
// Passwords might not be required based on settings.
|
||||||
|
const registrationGeneratePassword = getSetting(
|
||||||
|
'registrationGeneratePassword',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
const needsPassword = ! registrationGeneratePassword && ! password;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className={ 'wc-block-order-confirmation-create-account-form' }
|
className={ 'wc-block-order-confirmation-create-account-form' }
|
||||||
|
@ -58,66 +126,29 @@ const Form = ( {
|
||||||
setIsLoading( true );
|
setIsLoading( true );
|
||||||
} }
|
} }
|
||||||
>
|
>
|
||||||
<p>
|
{ ! registrationGeneratePassword && (
|
||||||
{ createInterpolateElement(
|
<>
|
||||||
__( 'Set a password for <email/>', 'woocommerce' ),
|
<p>
|
||||||
{
|
{ createInterpolateElement(
|
||||||
email: <strong>{ customerEmail }</strong>,
|
__( 'Set a password for <email/>', 'woocommerce' ),
|
||||||
}
|
{
|
||||||
) }
|
email: <strong>{ customerEmail }</strong>,
|
||||||
</p>
|
|
||||||
<div>
|
|
||||||
<ValidatedTextInput
|
|
||||||
disabled={ isLoading }
|
|
||||||
type="password"
|
|
||||||
label={ __( 'Password', 'woocommerce' ) }
|
|
||||||
className={ `wc-block-components-address-form__password` }
|
|
||||||
value={ password }
|
|
||||||
required={ true }
|
|
||||||
errorId={ 'account-password' }
|
|
||||||
customValidityMessage={ (
|
|
||||||
validity: ValidityState
|
|
||||||
): string | undefined => {
|
|
||||||
if (
|
|
||||||
validity.valueMissing ||
|
|
||||||
validity.badInput ||
|
|
||||||
validity.typeMismatch
|
|
||||||
) {
|
|
||||||
return __(
|
|
||||||
'Please enter a valid password',
|
|
||||||
'woocommerce'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} }
|
|
||||||
customValidation={ ( inputObject ) => {
|
|
||||||
if ( passwordStrength < 2 ) {
|
|
||||||
inputObject.setCustomValidity(
|
|
||||||
__(
|
|
||||||
'Please create a stronger password',
|
|
||||||
'woocommerce'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} }
|
|
||||||
onChange={ ( value: string ) => setPassword( value ) }
|
|
||||||
feedback={
|
|
||||||
<PasswordStrengthMeter
|
|
||||||
password={ password }
|
|
||||||
onChange={ ( strength: number ) =>
|
|
||||||
setPasswordStrength( strength )
|
|
||||||
}
|
}
|
||||||
/>
|
) }
|
||||||
}
|
</p>
|
||||||
/>
|
<PasswordField
|
||||||
</div>
|
isLoading={ isLoading }
|
||||||
|
password={ password }
|
||||||
|
setPassword={ setPassword }
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) }
|
||||||
<Button
|
<Button
|
||||||
className={
|
className={
|
||||||
'wc-block-order-confirmation-create-account-button'
|
'wc-block-order-confirmation-create-account-button'
|
||||||
}
|
}
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={ !! hasValidationError || ! password || isLoading }
|
disabled={ !! hasValidationError || needsPassword || isLoading }
|
||||||
showSpinner={ isLoading }
|
showSpinner={ isLoading }
|
||||||
>
|
>
|
||||||
{ __( 'Create account', 'woocommerce' ) }
|
{ __( 'Create account', 'woocommerce' ) }
|
||||||
|
@ -126,16 +157,26 @@ const Form = ( {
|
||||||
<input type="hidden" name="password" value={ password } />
|
<input type="hidden" name="password" value={ password } />
|
||||||
<input type="hidden" name="create-account" value="1" />
|
<input type="hidden" name="create-account" value="1" />
|
||||||
<input type="hidden" name="_wpnonce" value={ nonceToken } />
|
<input type="hidden" name="_wpnonce" value={ nonceToken } />
|
||||||
<p className={ 'wc-block-order-confirmation-create-account-terms' }>
|
<div className="wc-block-order-confirmation-create-account-description">
|
||||||
{ createInterpolateElement(
|
{ registrationGeneratePassword && (
|
||||||
/* translators: %1$s terms page link, %2$s privacy page link. */
|
<p>
|
||||||
__(
|
{ __(
|
||||||
'By creating an account you agree to our <terms/> and <privacy/>.',
|
"We'll email you a link to set up an account password.",
|
||||||
'woocommerce'
|
'woocommerce'
|
||||||
),
|
) }
|
||||||
{ terms: termsPageLink, privacy: privacyPageLink }
|
</p>
|
||||||
) }
|
) }
|
||||||
</p>
|
<p>
|
||||||
|
{ createInterpolateElement(
|
||||||
|
/* translators: %1$s terms page link, %2$s privacy page link. */
|
||||||
|
__(
|
||||||
|
'By creating an account you agree to our <terms/> and <privacy/>.',
|
||||||
|
'woocommerce'
|
||||||
|
),
|
||||||
|
{ terms: termsPageLink, privacy: privacyPageLink }
|
||||||
|
) }
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -53,13 +53,15 @@
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wc-block-order-confirmation-create-account-terms {
|
.wc-block-order-confirmation-create-account-description {
|
||||||
@include font-size(small);
|
p {
|
||||||
text-align: center;
|
@include font-size(small);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
a,
|
a,
|
||||||
span {
|
span {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: tweak
|
||||||
|
Comment: Delayed account creation: Support option to send password setup link to customer via email.
|
||||||
|
|
|
@ -358,16 +358,21 @@ class WC_Shortcode_My_Account {
|
||||||
/**
|
/**
|
||||||
* Handles resetting the user's password.
|
* Handles resetting the user's password.
|
||||||
*
|
*
|
||||||
|
* @since 9.4.0 This will log the user in after resetting the password/session.
|
||||||
|
*
|
||||||
* @param object $user The user.
|
* @param object $user The user.
|
||||||
* @param string $new_pass New password for the user in plaintext.
|
* @param string $new_pass New password for the user in plaintext.
|
||||||
*/
|
*/
|
||||||
public static function reset_password( $user, $new_pass ) {
|
public static function reset_password( $user, $new_pass ) {
|
||||||
|
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||||
do_action( 'password_reset', $user, $new_pass );
|
do_action( 'password_reset', $user, $new_pass );
|
||||||
|
|
||||||
wp_set_password( $new_pass, $user->ID );
|
wp_set_password( $new_pass, $user->ID );
|
||||||
update_user_meta( $user->ID, 'default_password_nag', false );
|
update_user_meta( $user->ID, 'default_password_nag', false );
|
||||||
self::set_reset_password_cookie();
|
self::set_reset_password_cookie();
|
||||||
|
wc_set_customer_auth_cookie( $user->ID );
|
||||||
|
|
||||||
|
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||||
if ( ! apply_filters( 'woocommerce_disable_password_change_notification', false ) ) {
|
if ( ! apply_filters( 'woocommerce_disable_password_change_notification', false ) ) {
|
||||||
wp_password_change_notification( $user );
|
wp_password_change_notification( $user );
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ class CreateAccount extends AbstractOrderConfirmationBlock {
|
||||||
* @return \WP_Error|int
|
* @return \WP_Error|int
|
||||||
*/
|
*/
|
||||||
protected function process_form_post( $order ) {
|
protected function process_form_post( $order ) {
|
||||||
if ( ! isset( $_POST['create-account'], $_POST['email'], $_POST['password'], $_POST['_wpnonce'] ) ) {
|
if ( ! isset( $_POST['create-account'], $_POST['email'], $_POST['_wpnonce'] ) ) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,6 @@ class CreateAccount extends AbstractOrderConfirmationBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
$user_email = sanitize_email( wp_unslash( $_POST['email'] ) );
|
$user_email = sanitize_email( wp_unslash( $_POST['email'] ) );
|
||||||
$password = wp_unslash( $_POST['password'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
|
||||||
|
|
||||||
// Does order already have user?
|
// Does order already have user?
|
||||||
if ( $order->get_customer_id() ) {
|
if ( $order->get_customer_id() ) {
|
||||||
|
@ -61,8 +60,16 @@ class CreateAccount extends AbstractOrderConfirmationBlock {
|
||||||
return new \WP_Error( 'email_mismatch', __( 'The email address provided does not match the email address on this order.', 'woocommerce' ) );
|
return new \WP_Error( 'email_mismatch', __( 'The email address provided does not match the email address on this order.', 'woocommerce' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( empty( $password ) || strlen( $password ) < 8 ) {
|
$generate_password = get_option( 'woocommerce_registration_generate_password', false );
|
||||||
return new \WP_Error( 'password_too_short', __( 'Password must be at least 8 characters.', 'woocommerce' ) );
|
|
||||||
|
if ( $generate_password ) {
|
||||||
|
$password = ''; // Will be generated by wc_create_new_customer.
|
||||||
|
} else {
|
||||||
|
$password = wp_unslash( $_POST['password'] ?? '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||||
|
|
||||||
|
if ( empty( $password ) || strlen( $password ) < 8 ) {
|
||||||
|
return new \WP_Error( 'password_too_short', __( 'Password must be at least 8 characters.', 'woocommerce' ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$customer_id = wc_create_new_customer(
|
$customer_id = wc_create_new_customer(
|
||||||
|
@ -171,4 +178,17 @@ class CreateAccount extends AbstractOrderConfirmationBlock {
|
||||||
|
|
||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extra data passed through from server to client for block.
|
||||||
|
*
|
||||||
|
* @param array $attributes Any attributes that currently are available from the block.
|
||||||
|
* Note, this will be empty in the editor context when the block is
|
||||||
|
* not in the post content on editor load.
|
||||||
|
*/
|
||||||
|
protected function enqueue_data( array $attributes = [] ) {
|
||||||
|
parent::enqueue_data( $attributes );
|
||||||
|
|
||||||
|
$this->asset_data_registry->add( 'registrationGeneratePassword', filter_var( get_option( 'woocommerce_registration_generate_password' ), FILTER_VALIDATE_BOOLEAN ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue