in the WooCommerce login form.
add_action( 'woocommerce_login_form_start', $this->get_method_proxy( 'render_signinwithgoogle_woocommerce' ) );
// Output the Sign in with Google
in any use of wp_login_form.
add_filter( 'login_form_top', $this->get_method_proxy( 'render_button_in_wp_login_form' ) );
// Delete client ID stored from previous module connection on module reconnection.
add_action(
'googlesitekit_save_settings_' . self::MODULE_SLUG,
function () {
if ( $this->is_connected() ) {
$this->existing_client_id->delete();
}
}
);
add_action( 'woocommerce_before_customer_login_form', array( $this, 'handle_woocommerce_errors' ), 1 );
// Check to see if the module is connected before registering the block.
if ( $this->is_connected() ) {
$this->sign_in_with_google_block->register();
}
}
/**
* Handles the callback request after the user signs in with Google.
*
* @since 1.140.0
*
* @param Authenticator_Interface $authenticator Authenticator instance.
*/
private function handle_auth_callback( Authenticator_Interface $authenticator ) {
$input = $this->context->input();
// Ignore the request if the request method is not POST.
$request_method = $input->filter( INPUT_SERVER, 'REQUEST_METHOD' );
if ( 'POST' !== $request_method ) {
return;
}
$redirect_to = $authenticator->authenticate_user( $input );
if ( ! empty( $redirect_to ) ) {
wp_safe_redirect( $redirect_to );
exit;
}
}
/**
* Adds custom errors if Google auth flow failed.
*
* @since 1.140.0
*
* @param WP_Error $error WP_Error instance.
* @return WP_Error $error WP_Error instance.
*/
public function handle_login_errors( $error ) {
$error_code = $this->context->input()->filter( INPUT_GET, 'error' );
if ( ! $error_code ) {
return $error;
}
switch ( $error_code ) {
case Authenticator::ERROR_INVALID_REQUEST:
/* translators: %s: Sign in with Google service name */
$error->add( self::MODULE_SLUG, sprintf( __( 'Login with %s failed.', 'google-site-kit' ), _x( 'Sign in with Google', 'Service name', 'google-site-kit' ) ) );
break;
case Authenticator::ERROR_SIGNIN_FAILED:
$error->add( self::MODULE_SLUG, __( 'The user is not registered on this site.', 'google-site-kit' ) );
break;
default:
break;
}
return $error;
}
/**
* Adds custom errors if Google auth flow failed on WooCommerce login.
*
* @since 1.145.0
*/
public function handle_woocommerce_errors() {
$err = $this->handle_login_errors( new WP_Error() );
if ( is_wp_error( $err ) && $err->has_errors() ) {
wc_add_notice( $err->get_error_message(), 'error' );
}
}
/**
* Cleans up when the module is deactivated.
*
* Persist the clientID on module disconnection, so it can be
* reused if the module were to be reconnected.
*
* @since 1.137.0
*/
public function on_deactivation() {
$pre_deactivation_settings = $this->get_settings()->get();
if ( ! empty( $pre_deactivation_settings['clientID'] ) ) {
$this->existing_client_id->set( $pre_deactivation_settings['clientID'] );
}
$this->get_settings()->delete();
}
/**
* Sets up information about the module.
*
* @since 1.137.0
*
* @return array Associative array of module info.
*/
protected function setup_info() {
return array(
'slug' => self::MODULE_SLUG,
'name' => _x( 'Sign in with Google', 'Service name', 'google-site-kit' ),
'description' => __( 'Improve user engagement, trust and data privacy, while creating a simple, secure and personalized experience for your visitors', 'google-site-kit' ),
'homepage' => __( 'https://developers.google.com/identity/gsi/web/guides/overview', 'google-site-kit' ),
);
}
/**
* Sets up the module's assets to register.
*
* @since 1.137.0
*
* @return Asset[] List of Asset objects.
*/
protected function setup_assets() {
$assets = array(
new Script(
'googlesitekit-modules-sign-in-with-google',
array(
'src' => $this->context->url( 'dist/assets/js/googlesitekit-modules-sign-in-with-google.js' ),
'dependencies' => array(
'googlesitekit-vendor',
'googlesitekit-api',
'googlesitekit-data',
'googlesitekit-modules',
'googlesitekit-datastore-site',
'googlesitekit-datastore-user',
'googlesitekit-components',
),
)
),
);
if ( Sign_In_With_Google_Block::can_register() && $this->is_connected() ) {
$assets[] = new Script(
'blocks-sign-in-with-google',
array(
'src' => $this->context->url( 'dist/assets/js/blocks/sign-in-with-google/index.js' ),
'dependencies' => array(),
'load_contexts' => array( Asset::CONTEXT_ADMIN_POST_EDITOR ),
)
);
$assets[] = new Stylesheet(
'blocks-sign-in-with-google-editor-styles',
array(
'src' => $this->context->url( 'dist/assets/js/blocks/sign-in-with-google/editor-styles.css' ),
'dependencies' => array(),
'load_contexts' => array( Asset::CONTEXT_ADMIN_POST_EDITOR ),
)
);
}
return $assets;
}
/**
* Sets up the module's settings instance.
*
* @since 1.137.0
*
* @return Settings
*/
protected function setup_settings() {
return new Settings( $this->options );
}
/**
* Checks whether the module is connected.
*
* A module being connected means that all steps required as part of its activation are completed.
*
* @since 1.139.0
*
* @return bool True if module is connected, false otherwise.
*/
public function is_connected() {
$options = $this->get_settings()->get();
if ( empty( $options['clientID'] ) ) {
return false;
}
return parent::is_connected();
}
/**
* Renders the placeholder Sign in with Google div for the WooCommerce
* login form.
*
* @since 1.147.0
*/
private function render_signinwithgoogle_woocommerce() {
/**
* Only render the button in a WooCommerce login page if:
*
* - the Sign in with Google module is connected
* - the user is not logged in
*/
if ( ! $this->is_connected() || is_user_logged_in() ) {
return;
}
?>
get_settings()->get();
// If there's no client ID available, don't render the button.
if ( ! $settings['clientID'] ) {
return false;
}
if ( substr( wp_login_url(), 0, 5 ) !== 'https' ) {
return false;
}
return true;
}
/**
* Renders the Sign in with Google JS script tags, One Tap code, and
* buttons.
*
* @since 1.139.0
* @since 1.144.0 Renamed to `render_signinwithgoogle` and conditionally
* rendered the code to replace buttons.
*/
private function render_signinwithgoogle() {
// `is_login()` isn't available until WP 6.1.
$is_wp_login = false !== stripos( wp_login_url(), $_SERVER['SCRIPT_NAME'] ?? '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
$is_woocommerce = class_exists( 'woocommerce' );
$is_woocommerce_login = did_action( 'woocommerce_login_form_start' );
$settings = $this->get_settings()->get();
$login_uri = add_query_arg( 'action', self::ACTION_AUTH, wp_login_url() );
if ( ! $this->can_render_signinwithgoogle() ) {
return;
}
$redirect_to = $this->context->input()->filter( INPUT_GET, 'redirect_to' );
if ( ! empty( $redirect_to ) ) {
$redirect_to = trim( $redirect_to );
}
$btn_args = array(
'theme' => $settings['theme'],
'text' => $settings['text'],
'shape' => $settings['shape'],
);
// Whether this is a WordPress/WooCommerce login page.
$is_login_page = $is_wp_login || $is_woocommerce_login;
// Check to see if we should show the One Tap prompt on this page.
//
// If this is not the WordPress or WooCommerce login page, check to
// see if "One Tap enabled on all pages" is set first. If it isnt:
// don't render the Sign in with Google JS.
$should_show_one_tap_prompt = ! empty( $settings['oneTapEnabled'] ) && (
// If One Tap is enabled at all, it should always appear on a login
// page.
$is_login_page ||
// Only show the prompt on other pages if the setting is enabled and
// the user isn't already signed in.
( $settings['oneTapOnAllPages'] && ! is_user_logged_in() )
);
// Set the cookie time to live to 5 minutes. If the redirect_to is
// empty, set the cookie to expire immediately.
$cookie_expire_time = 300000;
if ( empty( $redirect_to ) ) {
$cookie_expire_time *= -1;
}
// Render the Sign in with Google script.
ob_start();
?>
( () => {
async function handleCredentialResponse( response ) {
response.integration = 'woocommerce';
try {
const res = await fetch( '', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams( response )
} );
location.reload();
if ( res.ok && res.redirected ) {
location.assign( res.url );
}
} catch( error ) {
console.error( error );
}
}
google.accounts.id.initialize( {
client_id: '',
callback: handleCredentialResponse,
library_name: 'Site-Kit'
} );
const buttonDivToAddToLoginForm = document.createElement( 'div' );
buttonDivToAddToLoginForm.classList.add( 'googlesitekit-sign-in-with-google__frontend-output-button' );
document.getElementById( 'login' ).insertBefore( buttonDivToAddToLoginForm, document.getElementById( 'loginform' ) );
` elements with the "magic
* class" on the page.
*
* Mainly used by Gutenberg blocks.
*/
?>
document.querySelectorAll( '.googlesitekit-sign-in-with-google__frontend-output-button' ).forEach( ( siwgButtonDiv ) => {
google.accounts.id.renderButton( siwgButtonDiv, );
});
google.accounts.id.prompt();
const expires = new Date();
expires.setTime( expires.getTime() + );
document.cookie = "=;expires=" + expires.toUTCString() + ";path=";
} )();
\n" );
BC_Functions::wp_print_script_tag( array( 'src' => 'https://accounts.google.com/gsi/client' ) );
BC_Functions::wp_print_inline_script_tag( $inline_script );
print( "\n\n" );
}
/**
* Appends the Sign in with Google button to content of a WordPress filter.
*
* @since 1.149.0
*
* @param string $content Existing content.
* @return string Possibly modified content.
*/
private function render_button_in_wp_login_form( $content ) {
if ( $this->can_render_signinwithgoogle() ) {
$content .= '
';
}
return $content;
}
/**
* Gets the absolute number of users who have authenticated using Sign in with Google.
*
* @since 1.140.0
*
* @return int
*/
public function get_authenticated_users_count() {
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
return (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT( user_id ) FROM $wpdb->usermeta WHERE meta_key = %s",
$this->user_options->get_meta_key( Hashed_User_ID::OPTION )
)
);
}
/**
* Gets an array of debug field definitions.
*
* @since 1.140.0
*
* @return array
*/
public function get_debug_fields() {
$settings = $this->get_settings()->get();
$authenticated_user_count = $this->get_authenticated_users_count();
$debug_fields = array(
'sign_in_with_google_client_id' => array(
/* translators: %s: Sign in with Google service name */
'label' => sprintf( __( '%s: Client ID', 'google-site-kit' ), _x( 'Sign in with Google', 'Service name', 'google-site-kit' ) ),
'value' => $settings['clientID'],
'debug' => Debug_Data::redact_debug_value( $settings['clientID'] ),
),
'sign_in_with_google_shape' => array(
/* translators: %s: Sign in with Google service name */
'label' => sprintf( __( '%s: Shape', 'google-site-kit' ), _x( 'Sign in with Google', 'Service name', 'google-site-kit' ) ),
'value' => $this->get_settings()->get_label( 'shape', $settings['shape'] ),
'debug' => $settings['shape'],
),
'sign_in_with_google_text' => array(
/* translators: %s: Sign in with Google service name */
'label' => sprintf( __( '%s: Text', 'google-site-kit' ), _x( 'Sign in with Google', 'Service name', 'google-site-kit' ) ),
'value' => $this->get_settings()->get_label( 'text', $settings['text'] ),
'debug' => $settings['text'],
),
'sign_in_with_google_theme' => array(
/* translators: %s: Sign in with Google service name */
'label' => sprintf( __( '%s: Theme', 'google-site-kit' ), _x( 'Sign in with Google', 'Service name', 'google-site-kit' ) ),
'value' => $this->get_settings()->get_label( 'theme', $settings['theme'] ),
'debug' => $settings['theme'],
),
'sign_in_with_google_use_snippet' => array(
/* translators: %s: Sign in with Google service name */
'label' => sprintf( __( '%s: One Tap Enabled', 'google-site-kit' ), _x( 'Sign in with Google', 'Service name', 'google-site-kit' ) ),
'value' => $settings['oneTapEnabled'] ? __( 'Yes', 'google-site-kit' ) : __( 'No', 'google-site-kit' ),
'debug' => $settings['oneTapEnabled'] ? 'yes' : 'no',
),
'sign_in_with_google_authenticated_user_count' => array(
/* translators: %1$s: Sign in with Google service name */
'label' => sprintf( __( '%1$s: Number of users who have authenticated using %1$s', 'google-site-kit' ), _x( 'Sign in with Google', 'Service name', 'google-site-kit' ) ),
'value' => $authenticated_user_count,
'debug' => $authenticated_user_count,
),
);
return $debug_fields;
}
/**
* Implements mandatory interface method.
*
* This module doesn't use the usual tag registration within Site kit
* to place its snippet. However, it does leverage the Tag_Placement functionality
* to check if a tag is successfully placed or not within WordPress's Site Health.
*/
public function register_tag() {
}
/**
* Returns the Module_Tag_Matchers instance.
*
* @since 1.140.0
*
* @return Module_Tag_Matchers Module_Tag_Matchers instance.
*/
public function get_tag_matchers() {
return new Tag_Matchers();
}
/**
* Gets the URL of the page(s) where a tag for the module would be placed.
*
* For all modules like Analytics, Tag Manager, AdSense, Ads, etc. except for
* Sign in with Google, tags can be detected on the home page. SiwG places its
* snippet on the login page and thus, overrides this method.
*
* @since 1.140.0
*
* @return string|array
*/
public function get_content_url() {
$wp_login_url = wp_login_url();
if ( $this->is_woocommerce_active() ) {
$wc_login_page_id = wc_get_page_id( 'myaccount' );
$wc_login_url = get_permalink( $wc_login_page_id );
return array(
'WordPress Login Page' => $wp_login_url,
'WooCommerce Login Page' => $wc_login_url,
);
}
return $wp_login_url;
}
/**
* Checks if the Sign in with Google button, specifically inserted by Site Kit,
* is found in the provided content.
*
* This method overrides the `Module_With_Tag_Trait` implementation since the HTML
* comment inserted for SiwG's button is different to the standard comment inserted
* for other modules' script snippets. This should be improved as speicified in the
* TODO within the trait method.
*
* @since 1.140.0
*
* @param string $content Content to search for the button.
* @return bool TRUE if tag is found, FALSE if not.
*/
public function has_placed_tag_in_content( $content ) {
$search_string = 'Sign in with Google button added by Site Kit';
$search_translatable_string =
/* translators: %s: Sign in with Google service name */
sprintf( __( '%s button added by Site Kit', 'google-site-kit' ), _x( 'Sign in with Google', 'Service name', 'google-site-kit' ) );
if ( strpos( $content, $search_string ) !== false || strpos( $content, $search_translatable_string ) !== false ) {
return Module_Tag_Matchers::TAG_EXISTS_WITH_COMMENTS;
}
return Module_Tag_Matchers::NO_TAG_FOUND;
}
/**
* Returns the disconnect URL for the specified user.
*
* @since 1.141.0
*
* @param int $user_id WordPress User ID.
*/
public static function disconnect_url( $user_id ) {
return add_query_arg(
array(
'action' => self::ACTION_DISCONNECT,
'nonce' => wp_create_nonce( self::ACTION_DISCONNECT . '-' . $user_id ),
'user_id' => $user_id,
),
admin_url( 'index.php' )
);
}
/**
* Handles the disconnect action.
*
* @since 1.141.0
*/
public function handle_disconnect_user() {
$input = $this->context->input();
$nonce = $input->filter( INPUT_GET, 'nonce' );
$user_id = (int) $input->filter( INPUT_GET, 'user_id' );
$action = self::ACTION_DISCONNECT . '-' . $user_id;
if ( ! wp_verify_nonce( $nonce, $action ) ) {
$this->authentication->invalid_nonce_error( $action );
}
// Only allow this action for admins or users own setting.
if ( current_user_can( 'edit_user', $user_id ) ) {
$hashed_user_id = new Hashed_User_ID( new User_Options( $this->context, $user_id ) );
$hashed_user_id->delete();
wp_safe_redirect( add_query_arg( 'updated', true, get_edit_user_link( $user_id ) ) );
exit;
}
wp_safe_redirect( get_edit_user_link( $user_id ) );
exit;
}
/**
* Displays a disconnect button on user profile pages.
*
* @since 1.141.0
*
* @param WP_User $user WordPress user object.
*/
private function render_disconnect_profile( WP_User $user ) {
if ( ! current_user_can( 'edit_user', $user->ID ) ) {
return;
}
$hashed_user_id = new Hashed_User_ID( new User_Options( $this->context, $user->ID ) );
$current_user_google_id = $hashed_user_id->get();
// Don't show if the user does not have a Google ID saved in user meta.
if ( empty( $current_user_google_id ) ) {
return;
}
?>
ID ) {
esc_html_e(
'You can sign in with your Google account.',
'google-site-kit'
);
} else {
esc_html_e(
'This user can sign in with their Google account.',
'google-site-kit'
);
}
?>
existing_client_id->get();
if ( $existing_client_id ) {
$inline_data['existingClientID'] = $existing_client_id;
}
$is_woocommerce_active = $this->is_woocommerce_active();
$woocommerce_registration_enabled = $is_woocommerce_active ? get_option( 'woocommerce_enable_myaccount_registration' ) : null;
$inline_data['isWooCommerceActive'] = $is_woocommerce_active;
$inline_data['isWooCommerceRegistrationEnabled'] = $is_woocommerce_active && 'yes' === $woocommerce_registration_enabled;
// Add the data under the `sign-in-with-google` key to make it clear it's scoped to this module.
$modules_data['sign-in-with-google'] = $inline_data;
return $modules_data;
}
/**
* Helper method to determine if the WooCommerce plugin is active.
*
* @since 1.148.0
*
* @return bool True if active, false if not.
*/
protected function is_woocommerce_active() {
return class_exists( 'WooCommerce' );
}
}