ID, TOKEN_USER_META, true ); $tokens = is_string( $tokens ) ? array( $tokens ) : $tokens; $new_token = sha1( wp_generate_password() ); switch ( $context ) { case 'sms': // helps to keep url link short due to 300 char limit for most of the SMS providers $new_token = substr( $new_token, 0, 12 ); break; case 'sms_code': // 6-digit PIN for SMS $new_token = wp_rand( 100000, 999999 ); break; case 'email_code': $new_token = strtoupper( substr( str_shuffle( 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' ), 0, 10 ) ); break; } /** * Filter the token * * @hook magic_login_create_user_token * * @param {string} $new_token New token * @param {int} $user->ID User ID * @param {string} $context Context * * @return {string} New value * @since 2.4 */ $new_token = apply_filters( 'magic_login_create_user_token', $new_token, $user->ID, $context ); $hashed_token = hash_hmac( 'sha256', $new_token, wp_salt() ); $ip = sha1( get_client_ip() ); if ( defined( 'WP_CLI' ) && WP_CLI ) { $ip = 'cli'; } $tokens[] = [ 'token' => $hashed_token, 'time' => time(), 'ip_hash' => $ip, ]; update_user_meta( $user->ID, TOKEN_USER_META, $tokens ); if ( absint( $settings['token_ttl'] ) > 0 ) { // eternal token wp_schedule_single_event( time() + ( $settings['token_ttl'] * MINUTE_IN_SECONDS ), CRON_HOOK_NAME, array( $user->ID ) ); } return $new_token; } /** * Create login link for given user * * @param object $user WP_User object * @param string $context Context (email|email_code|sms|sms_code) @since 2.4 * @param string $redirect_to Redirect URL * * @return mixed|string */ function create_login_link( $user, $context = 'email', $redirect_to = null ) { global $magic_login_token; $token = create_user_token( $user, $context ); $magic_login_token = $token; $query_args = array( 'user_id' => $user->ID, 'token' => $token, 'magic-login' => 1, ); if ( ! empty( $_POST['redirect_to'] ) ) { $query_args['redirect_to'] = urlencode( wp_unslash( $_POST['redirect_to'] ) ); // phpcs:ignore } if ( ! empty( $redirect_to ) ) { $query_args['redirect_to'] = urlencode( $redirect_to ); } $login_url = esc_url_raw( add_query_arg( $query_args, wp_login_url() ) ); /** * Filter the login URL * * @hook magic_login_create_login_link * * @param {string} $login_url Login URL * @param {object} $user WP_User object * @param {string} $context Context * @param {string} $redirect_to Redirect URL * * @return {string} New value * @since 2.4 */ $login_url = apply_filters( 'magic_login_create_login_link', $login_url, $user, $context, $redirect_to ); return $login_url; } /** * Get client raw ip * this should be hashed * * @return mixed */ function get_client_ip() { /** * `HTTP_X_FORWARDED_FOR` removed in 1.5 * Filters the ip address * * @hook magic_login_client_ip * * @param {string} REMOTE_ADDR * * @return {string} New value. * @since 1.5 */ return apply_filters( 'magic_login_client_ip', $_SERVER['REMOTE_ADDR'] ); // phpcs:ignore } /** * Get settings with defaults * * @return array * @since 1.0 */ function get_settings() { $defaults = [ 'is_default' => false, 'add_login_button' => true, 'token_ttl' => 5, 'token_validity' => 1, 'token_interval' => 'MINUTE', 'enable_brute_force_protection' => false, 'brute_force_bantime' => 60, // in minutes 'brute_force_login_attempt' => 10, 'brute_force_login_time' => 5, // in minutes 'enable_login_throttling' => false, 'login_throttling_limit' => 10, 'login_throttling_time' => 15, // in minutes 'enable_ip_check' => false, 'enable_domain_restriction' => false, 'allowed_domains' => '', 'login_email' => get_default_login_email_text(), 'enable_login_redirection' => false, 'default_redirection_url' => '', 'enforce_redirection_rules' => true, 'enable_wp_login_redirection' => false, 'enable_role_based_redirection' => false, 'role_based_redirection_rules' => [], 'email_subject' => __( 'Log in to {{SITENAME}}', 'magic-login' ), 'auto_login_links' => false, 'enable_ajax' => false, 'enable_woo_integration' => false, 'woo_position' => 'before', 'enable_woo_customer_login' => false, 'woo_customer_login_position' => 'before', 'enable_edd_checkout' => false, 'edd_checkout_position' => 'edd_before_purchase_form', 'enable_edd_login' => false, 'edd_login_position' => 'before', 'registration' => [ 'enable' => false, 'mode' => 'auto', // or standard|shortcode 'fallback_email_field' => true, // show email field on auto registration mode as a fallback 'show_name_field' => true, 'require_name_field' => true, 'show_terms_field' => false, 'require_terms_field' => false, 'terms' => '', 'send_email' => true, 'email_subject' => esc_html__( 'Welcome to {{SITENAME}}', 'magic-login' ), 'email_content' => get_default_registration_email_text(), 'enable_domain_restriction' => false, 'allowed_domains' => '', 'role' => '', ], 'spam_protection' => [ 'service' => 'recaptcha', 'enable_login' => false, 'enable_registration' => false, ], 'recaptcha' => [ 'type' => 'v3', // which version to use 'v2_checkbox' => [ 'site_key' => '', 'secret_key' => '', ], 'v2_invisible' => [ 'site_key' => '', 'secret_key' => '', ], 'v3' => [ 'site_key' => '', 'secret_key' => '', ], ], 'cf_turnstile' => [ 'site_key' => '', 'secret_key' => '', ], 'enable_rest_api' => false, 'sms' => [ 'enable' => false, 'provider' => 'twilio', 'twilio' => [ 'account_sid' => '', 'auth_token' => '', 'from' => '', ], /** * phone_only: Only send SMS if the user enters their phone number (instead of email) when logging in. * sms_and_email: If the user has a phone number linked to their account, send both SMS and email. * sms_or_email: Always send SMS if the user has a phone number, but do not send email notifications. */ 'sms_sending_strategy' => 'phone_only', 'wp_registration' => false, 'wp_require_phone' => false, 'magic_registration' => false, 'magic_registration_require_phone' => false, 'login_message' => esc_html__( 'Your login code is here: {{MAGIC_LOGIN_CODE}} Your code will expire in {{EXPIRES_WITH_INTERVAL}}', 'magic-login' ), 'send_registration_message' => false, 'registration_message' => esc_html__( 'Welcome {{FULL_NAME}}! 🎉 Your account on {{SITENAME}} has been created. You can login here: {{MAGIC_LINK}}.', 'magic-login' ), ], ]; if ( MAGIC_LOGIN_IS_NETWORK ) { $settings = get_site_option( SETTING_OPTION, [] ); } else { $settings = get_option( SETTING_OPTION, [] ); } // Merge settings with defaults, ensuring new additions and nested arrays are included $settings = array_replace_recursive( $defaults, $settings ); return $settings; } /** * Default login email message * * @return mixed|string|void */ function get_default_login_email_text() { /* translators: Do not translate USERNAME, SITENAME,EXPIRES, MAGIC_LINK, SITENAME, SITEUR, EXPIRES_WITH_INTERVAL: those are placeholders. */ $email_text = __( 'Hi {{USERNAME}}, Click and confirm that you want to log in to {{SITENAME}}. This link will expire in {{EXPIRES_WITH_INTERVAL}} and can only be used once: Log In Need the link? {{MAGIC_LINK}} You can safely ignore and delete this email if you do not want to log in. Regards, All at {{SITENAME}} {{SITEURL}}', 'magic-login' ); return $email_text; } /** * Is plugin activated network wide? * * @param string $plugin_file file path * * @return bool * @since 1.0 */ function is_network_wide( $plugin_file ) { if ( ! is_multisite() ) { return false; } if ( ! function_exists( 'is_plugin_active_for_network' ) ) { require_once ABSPATH . '/wp-admin/includes/plugin.php'; } return is_plugin_active_for_network( plugin_basename( $plugin_file ) ); } /** * Get login link * * @return mixed|string */ function get_magic_login_url() { _deprecated_function( __FUNCTION__, '2.3.4', __NAMESPACE__ . '\get_wp_login_url' ); return get_wp_login_url(); } /** * Get login link * * @return mixed|string */ function get_wp_login_url() { $url = site_url( 'wp-login.php?action=magic_login', 'login_post' ); /** * Filter the login URL for magic login * * @hook magic_login_get_wp_login_url * @since 2.3.4 */ $url = apply_filters( 'magic_login_get_wp_login_url', $url ); return esc_url_raw( $url ); } /** * Get user tokens * * @param int $user_id User ID * @param bool $clear_expired flag for clean-up expired tokens * * @return array|mixed */ function get_user_tokens( $user_id, $clear_expired = false ) { $tokens = get_user_meta( $user_id, TOKEN_USER_META, true ); $tokens = is_array( $tokens ) ? $tokens : []; /** * Filter user tokens * * @hook magic_login_user_tokens * * @param {array} $tokens User tokens. * @param {int} $user_id User ID. * @param {boolean} $clear_expired Whether to clear expired tokens or not. * * @return {array} New value * @since 2.1 */ $tokens = (array) apply_filters( 'magic_login_user_tokens', $tokens, $user_id, $clear_expired ); if ( $clear_expired ) { $ttl = get_ttl_by_user( $user_id ); if ( 0 === $ttl ) { // means token lives forever till used return $tokens; } foreach ( $tokens as $index => $token_data ) { if ( empty( $token_data ) || ! isset( $token_data['time'] ) ) { unset( $tokens[ $index ] ); continue; } if ( time() > absint( $token_data['time'] ) + ( $ttl * MINUTE_IN_SECONDS ) ) { unset( $tokens[ $index ] ); } } update_user_meta( $user_id, TOKEN_USER_META, $tokens ); } return $tokens; } /** * Get default redirect url for given user * * @param \WP_User $user User object * * @return string|void */ function get_user_default_redirect( $user ) { if ( is_multisite() && ! get_active_blog_for_user( $user->ID ) && ! is_super_admin( $user->ID ) ) { $redirect_to = user_admin_url(); } elseif ( is_multisite() && ! $user->has_cap( 'read' ) ) { $redirect_to = get_dashboard_url( $user->ID ); } elseif ( ! $user->has_cap( 'edit_posts' ) ) { $redirect_to = $user->has_cap( 'read' ) ? admin_url( 'profile.php' ) : home_url(); } else { $redirect_to = admin_url(); } return $redirect_to; } /** * Delete all token meta */ function delete_all_tokens() { global $wpdb; return $wpdb->delete( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->usermeta, [ 'meta_key' => TOKEN_USER_META, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key ] ); } /** * Allowed intervals for TTL. * * @return array * @since 1.2 */ function get_allowed_intervals() { return [ 'MINUTE' => esc_html__( 'Minute(s)', 'magic-login' ), 'HOUR' => esc_html__( 'Hour(s)', 'magic-login' ), 'DAY' => esc_html__( 'Day(s)', 'magic-login' ), ]; } /** * Convert minutes to possible time format * * @param int $timeout_in_minutes TTL in minutes * * @return array * @since 1.2 */ function get_ttl_with_interval( $timeout_in_minutes ) { $ttl = $timeout_in_minutes; $interval = 'MINUTE'; if ( $ttl > 0 ) { if ( 0 === (int) ( $ttl % 1440 ) ) { $ttl = $ttl / 1440; $interval = 'DAY'; } elseif ( 0 === (int) ( $ttl % 60 ) ) { $ttl = $ttl / 60; $interval = 'HOUR'; } } return array( $ttl, $interval, ); } /** * Get the documentation url * * @param string $path The path of documentation * @param string $fragment URL Fragment * * @return string final URL */ function get_doc_url( $path = null, $fragment = '' ) { $doc_base = 'https://handyplugins.co/'; $utm_parameters = '?utm_source=wp_admin&utm_medium=plugin&utm_campaign=settings_page'; if ( ! empty( $path ) ) { $doc_base .= ltrim( $path, '/' ); } $doc_url = trailingslashit( $doc_base ) . $utm_parameters; if ( ! empty( $fragment ) ) { $doc_url .= '#' . $fragment; } return $doc_url; } /** * Check whether current screen is magic login settings page or not * * @return bool * @since 1.2.1 */ function is_magic_login_settings_screen() { $current_screen = get_current_screen(); if ( ! is_a( $current_screen, '\WP_Screen' ) ) { return false; } if ( false !== strpos( $current_screen->base, 'magic-login' ) ) { return true; } return false; } /** * Mask given string * * @param string $input_string String * @param int $unmask_length The length of unmask * * @return string * @since 2.2 */ function mask_string( $input_string, $unmask_length ) { $output_string = substr( $input_string, 0, $unmask_length ); if ( strlen( $input_string ) > $unmask_length ) { $output_string .= str_repeat( '*', strlen( $input_string ) - $unmask_length ); } return $output_string; } /** * Check if the given value is masked * * @param string $value The value to check * @param int $mask_length The length of the mask * * @return bool * @since 2.2 */ function is_masked_value( $value, $mask_length = 3 ) { // Get the last characters of the string $last_chars = substr( $value, - $mask_length ); // Check if the last characters are asterisks return str_repeat( '*', $mask_length ) === $last_chars; } /** * Get email placeholders by user * * @param \WP_User $user User object * * @return array * @since 2.2 */ function get_email_placeholders_by_user( $user ) { if ( is_multisite() ) { $site_name = get_network()->site_name; } else { $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); } $settings = \MagicLogin\Utils\get_settings(); $ttl = get_ttl_by_user( $user->ID ); list( $token_ttl, $selected_interval ) = get_ttl_with_interval( $ttl ); $selected_interval_str = strtolower( $selected_interval ); $allowed_intervals = get_allowed_intervals(); if ( isset( $allowed_intervals[ $selected_interval ] ) ) { $selected_interval_str = strtolower( $allowed_intervals[ $selected_interval ] ); // translated interval } $placeholders = [ '{{SITEURL}}' => home_url(), '{{USERNAME}}' => $user->user_login, '{{FIRST_NAME}}' => $user->first_name, '{{LAST_NAME}}' => $user->last_name, '{{FULL_NAME}}' => $user->first_name . ' ' . $user->last_name, '{{DISPLAY_NAME}}' => $user->display_name, '{{USER_EMAIL}}' => $user->user_email, '{{SITENAME}}' => $site_name, '{{EXPIRES}}' => $ttl, '{{EXPIRES_WITH_INTERVAL}}' => $token_ttl . ' ' . $selected_interval_str, '{{TOKEN_VALIDITY_COUNT}}' => $settings['token_validity'], ]; return $placeholders; } /** * Get decrypted value * * @param string $value encrypted value * * @return bool|mixed|string * @since 2.2 */ function get_decrypted_value( $value ) { $encryption = new Encryption(); $decrypted_value = $encryption->decrypt( $value ); if ( false !== $decrypted_value ) { return $decrypted_value; } return $value; } /** * Get the token TTL by user * * @param int $user_id User ID * * @return int TTL in minutes * @since 2.2 */ function get_ttl_by_user( $user_id ) { $settings = \MagicLogin\Utils\get_settings(); $ttl = $settings['token_ttl']; /** * Filter the token TTL by user * * @hook magic_login_token_ttl_by_user * * @param {int} $ttl TTL in minutes * @param {int} $user_id User ID * * @return {int} New value * @since 2.2 */ return apply_filters( 'magic_login_token_ttl_by_user', $ttl, $user_id ); } /** * Default registration email message * * @return mixed|string|void * @since 2.2 */ function get_default_registration_email_text() { $email_text = __( 'Hi there,

Thank you for signing up to {{SITENAME}}! We are excited to have you on board.
To get started, simply use the magic link below to log in:

Click here to log in

If the button above does not work, you can also copy and paste the following URL into your browser:
{{MAGIC_LINK}}

We hope you enjoy your experience with us. If you have any questions or need assistance, feel free to reach out.

Regards,
All at {{SITENAME}}
{{SITEURL}}', 'magic-login' ); return $email_text; } /** * Get user by log input * * @param string $input Input. It can be username, email or phone number * * @return false|mixed|\WP_User|null * @since 2.4 */ function get_user_by_log_input( $input ) { $user = get_user_by( 'login', $input ); if ( ! defined( 'MAGIC_LOGIN_USERNAME_ONLY' ) || false === MAGIC_LOGIN_USERNAME_ONLY ) { if ( ! $user && strpos( $input, '@' ) ) { $user = get_user_by( 'email', $input ); } } return $user; }