x1184的小站
x1184的小站
初探wordpress源码(二)-登陆机制
初探wordpress源码(二)-登陆机制

阅读说明

本文适合于有一定PHP基础的开发者阅读,包括但不限于OOP,Cookie,变量作用域,Hook等知识点,同时若具有数据库,HTML中Form,HTTP协议相关知识阅读体验更佳。

上次我们的分析主要集中在index页面加载过程中相关的文件和引用关系,剩余根目录还有一些单模块的文件,感觉零零散散的,经过整理:

  • wp-login.php 登陆
  • wp-signup.php 注册
  • wp-mail.php 发送邮件
  • wp-activate.php 验证邮件
  • wp-comments-post.php 发送评论
  • wp-cron.php 定时任务
  • wp-links-obml.php 用OPML XML的格式发送链接 (貌似还不是默认文件)
  • wp-trackback.php 文章被引用的追踪与回复(不是评论)
  • xmlrpx.php XML-RPC协议支持

然后经过进一步查看,其中又有部分文件是属于独立性很强的小功能,比如trackback和xml解析的一些,这些用的很少同时基本也仅仅作为一个入口来调用WPINC里的函数。所以我们今天的重点还是在登陆机制这块上了,让我们开始。

首先大致查看单个文件内容及Structure,一目了然的看到各自有哪些方法和变量就能大体估计出每个文件的重要性和大致功能。通过比较我们看到wp-login.php是结构稍显更复杂的,就代码行数来说也是最多的,我们就从它开始分析吧。

https://ae01.alicdn.com/kf/Ub1d1c4f943b444038b22f933ddb34248V.png
wp-login.php

wp-login.php

逐过程查看

https://ae01.alicdn.com/kf/U08d1a35d1c1842bf85d61c847e1737e06.png
查看定义的函数及解释
require( dirname(__FILE__) . '/wp-load.php' ); //加载load文件
if ( force_ssl_admin() && ! is_ssl() ) // Redirect to https login if forced to use SSL SSL加密连接时强制重定向的设定

//首先定义模板输出函数和生成js的函数
function login_header( $title = 'Log In', $message = '', $wp_error = null ) //输出登陆页头前端界面
function login_footer( $input_id = '' ) //输出登陆页脚前端界面
function wp_shake_js()  //表单抖动效果的js
function wp_login_viewport_meta() //指定name="viewport"的content
function retrieve_password() //找回密码的具体逻辑

$action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : 'login'; //默认action给个login
$errors = new WP_Error(); //实例化Error对象,后期基本用add方法添加成错误,之后redirect重定向回初始界面

if ( isset( $_GET['key'] ) ) { //出现认证密钥则action设为重置密码
	$action = 'resetpass';
}

$default_actions = array( //所有的小功能都包含进这个文件里
	'confirm_admin_email',
	'postpass',
	'logout',
	'lostpassword',
	'retrievepassword',
	'resetpass',
	'rp',
	'register',
	'login',
	'confirmaction',
	WP_Recovery_Mode_Link_Service::LOGIN_ACTION_ENTERED,
);

// Validate action so as to default to the login screen.
// 验证action是否在默认列表中以及检查过滤器
if ( ! in_array( $action, $default_actions, true ) && false === has_filter( 'login_form_' . $action ) ) {
	$action = 'login';
}

//给不同浏览器发送nocache的header并设置基本header
nocache_headers();  
header( 'Content-Type: ' . get_bloginfo( 'html_type' ) . '; charset=' . get_bloginfo( 'charset' ) );

if ( defined( 'RELOCATE' ) && RELOCATE ){} //是否开启短路径模式,和超全局变量PATHINFO有关 
//如果wp-config.php中定义了RELOCATE, 就更新db中的siteurl,具体逻辑就不展开了

//Set a cookie now to see if they are supported by the browser.
//设置cookie并根据协议调节ssl兼容性,之后测试TEST_COOKIE
$secure = ( 'https' === parse_url( wp_login_url(), PHP_URL_SCHEME ) );
setcookie( TEST_COOKIE, 'WP Cookie check', 0, COOKIEPATH, COOKIE_DOMAIN, $secure );
if ( SITECOOKIEPATH != COOKIEPATH ) {
setcookie( TEST_COOKIE, 'WP Cookie check', 0, SITECOOKIEPATH, COOKIE_DOMAIN, $secure );
}

do_action( 'login_init' ); //执行登陆初始化钩子
do_action( "login_form_{$action}" ); //执行登陆表单的某个动作的钩子

$http_post     = ( 'POST' === $_SERVER['REQUEST_METHOD'] ); 
$interim_login = isset( $_REQUEST['interim-login'] );//临时登陆的参数获取
//这个来自hidden隐藏域可能会被querystring设置

//过滤登录表单导航链接之间使用的分隔符。
$login_link_separator = apply_filters( 'login_link_separator', ' | ' );

接下来就是各种action的详细逻辑了,总体来说,这个页面包含了很多功能
switch ( $action ) {
	case 'confirm_admin_email':
	case 'postpass':
	case 'logout':
	case 'lostpassword':
	case 'retrievepassword':
	case 'resetpass':
	case 'rp':
	case 'register':
	case 'confirmaction':
	case 'login':
	default:
} // End action switch.

总体来说会先定义模板输出函数和js生成的函数,之后获取request方式和定义不同的case,之后我们详细解析下具体函数及case。

进入函数查看

模板输出函数

function login_header( $title = ‘Log In’, $message = ”, $wp_error = ” ) //设置公共头部信息

// Don't index any of these forms去除掉敏感页面元数据
add_action( 'login_head', 'wp_sensitive_page_meta' );
add_action( 'login_head', 'wp_login_viewport_meta' );

// Shake it!
$shake_error_codes = array( 'empty_password', 'empty_email', 'invalid_email', 
'invalidcombo', 'empty_username', 'invalid_username', 'incorrect_password', 'retrieve_password_email_failure' );

//Filters the error codes array for shaking the login form.使其运用过滤钩子
$shake_error_codes = apply_filters( 'shake_error_codes', $shake_error_codes ); 

if ( $shake_error_codes && $wp_error->has_errors() && 
in_array( $wp_error->get_error_code(), $shake_error_codes, true ) ) {
add_action( 'login_head', 'wp_shake_js', 12 ); //添加触发login_head时的shake动作
}

//translators: Login screen title. 1: Login screen name, 2: Network or site name. 
//登录屏幕标题由两部分组成 1:登录屏幕名称,2:网络或站点名称构成
$login_title = get_bloginfo( 'name', 'display' );
$login_title = sprintf( __( '%1$s ‹ %2$s — WordPress' ), $title, $login_title ); 

if ( wp_is_recovery_mode() ) //检测恢复模式状态

$login_title = apply_filters( 'login_title', $login_title, $title );//用钩子过滤login_title

此时输出包含$login_title; 的前端页面
wp_enqueue_style( 'login' ); //login的样式表入队

* Remove all stored post data on logging out.
* This could be added by add_action('login_head'...) like wp_shake_js(),
* but maybe better if it's not removable by plugins.
用if直接判断去用钩子清理更好因为能防止未触发钩子时不生效的问题
if ( 'loggedout' === $wp_error->get_error_code() ) {
?>
<script>if("sessionStorage" in window){try{for(var key in sessionStorage){if(key.indexOf("wp-autosave-")!=-1){sessionStorage.removeItem(key)}}}catch(e){}};</script>
<?php
}

do_action( 'login_enqueue_scripts' ); //使登录页面的脚本和样式入队时出发钩子
do_action( 'login_head' );

$login_header_url = __( 'https://wordpress.org/' );
$login_header_url = apply_filters( 'login_headerurl', $login_header_url );
$login_header_title = '';
$login_header_text = empty( $login_header_title ) ? __( 'Powered by WordPress' ) : $login_header_title;

$classes = array( 'login-action-' . $action, 'wp-core-ui' );//这个数组存储action类型及页面配置
if ( is_rtl() ) {
	$classes[] = 'rtl';
} //页面走向
if ( $interim_login ) { //
	$classes[] = 'interim-login';
//本地化时语言的设置
$classes[] = ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_locale() ) ) );

$classes = apply_filters( 'login_body_class', $classes, $action );//过滤登录页面正文类。
do_action( 'login_header' ); //login 页面的header打开时触发

//输出前端页面,内容为前面设置的$login_header_text
<h1><a href="<?php echo esc_url( $login_header_url ); ?>"><?php echo $login_header_text; ?></a></h1>

$message = apply_filters( 'login_message', $message );//为总函数传入的第二个参数
根据错误情况处理弹出错误提示
if ( $wp_error->has_errors() ){
if ( ! empty( $errors ) )
if ( ! empty( $messages ) )
}

function login_footer( $input_id = ” ) //设置公共底部返回栏

global $interim_login;
// Don't allow interim logins to navigate away from the page.不让临时登陆用户能导航到其他页面
if ( ! $interim_login ): ?>
<p id="backtoblog"><a href="<?php echo esc_url( home_url( '/' ) ); ?>"><?php printf( __( '← Back to %s' ), get_bloginfo( 'title', 'display' ) ); ?></a></p>
<?php endif; ?> //←作为实体相当于箭头,整个的内容就是返回你的博客,并且还有链接指向

//根据传进来的参数作为getElement目标并把焦点聚焦在其上,之后执行wpOnload()最后触发个钩子
<?php if ( !empty($input_id) ) : ?>
<script type="text/javascript">
try{document.getElementById('<?php echo $input_id; ?>').focus();}catch(e){}
if(typeof wpOnload=='function')wpOnload();
</script>
<?php endif; ?>

do_action( 'login_footer' );
工具函数

function retrieve_password()

// 先判断为空和为字符串再判断输入的是否是邮箱 是的话就通过email拿到用户数据$user_data
// 若是用户名则通过用户名拿到$user_data
if ( empty( $_POST['user_login'] ) || ! is_string( $_POST['user_login'] ) ) {
} elseif ( strpos( $_POST['user_login'], '@' ) ) {
	$user_data = get_user_by( 'email', trim( wp_unslash( $_POST['user_login'] ) ) );
	if ( empty( $user_data ) ) {
	}
} else {
	$login     = trim( $_POST['user_login'] );
	$user_data = get_user_by( 'login', $login );
}

do_action( 'lostpassword_post', $errors ); //参数传入钩子

if ( $errors->has_errors() ) 
if ( ! $user_data ) //查错后查用户数据是否有效

$user_login = $user_data->user_login;
$user_email = $user_data->user_email;
$key        = get_password_reset_key( $user_data ); //拿到名和email和重置key

if ( is_wp_error( $key ) )
//单用户站点 直接get_option获取站名 和 多用户站点 用The network object调site_name获取站点名
if ( is_multisite() ) {
	$site_name = get_network()->site_name;
} else {
	$site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
}

//定义邮件内容和标题
$message = __( 'Someone has requested a password reset for the following account:' ) . "\r\n\r\n";
...
$title = sprintf( __( '[%s] Password Reset' ), $site_name );

$title = apply_filters( 'retrieve_password_title', $title, $user_login, $user_data );
$message = apply_filters( 'retrieve_password_message', $message, $key, $user_login, $user_data ); //两个分别过滤

//这一步开始发邮件了 调用的是wp-includes/pluggable.php里的函数 而不是mail.php
//若有错误返回$errors若无错误返回true
if ( $message && ! wp_mail( $user_email, wp_specialchars_decode( $title ), $message ) )
action的具体逻辑
https://img.ocasis.cn/image/5e11608013eee
触发不同action的写法
case ‘register’
if ( is_multisite() ) {
	wp_redirect( apply_filters( 'wp_signup_location', network_site_url( 'wp-signup.php' ) ) );
	exit;
} //多站点环境直接重定向至wp-signup用专门的文件注册站点与用户

if ( ! get_option( 'users_can_register' ) ) //获取全局选项是否允许注册
$user_login = '';
$user_email = ''; //初始化字符串变量

//下面是提交数据的部分,填写数据在后面,源码这种写法把他们都放在一个case里了
if ( $http_post ) { //若是提交数据则是post方式,也遵循RestFul的设计
	if ( isset( $_POST['user_login'] ) && is_string( $_POST['user_login'] ) ) {
		$user_login = $_POST['user_login'];
	}
	if ( isset( $_POST['user_email'] ) && is_string( $_POST['user_email'] ) ) {
		$user_email = wp_unslash( $_POST['user_email'] );
	}
	$errors = register_new_user( $user_login, $user_email ); //拿到数据后这里才是实际的注册逻辑
	if ( ! is_wp_error( $errors ) ) {
		$redirect_to = ! empty( $_POST['redirect_to'] ) ? $_POST['redirect_to'] : 'wp-login.php?checkemail=registered';
		wp_safe_redirect( $redirect_to ); //捕获错误并安全重定向
		exit();
	}
}

$registration_redirect = ! empty( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : ''; //提交而来的隐藏域若有值则赋值
$redirect_to = apply_filters( 'registration_redirect', $registration_redirect );

login_header( __( 'Registration Form' ), '<p class="message register">' . __( 'Register For This Site' ) . '</p>', $errors ); //调用前面的模板生成函数生成标题

<form name="registerform" id="registerform" action="<?php echo esc_url( site_url( 'wp-login.php?action=register', 'login_post' ) ); ?>
...
do_action( 'register_form' );
...
</form>
//上面的前端部分就是负责显示注册的填写还有登陆及找回密码等选项的表单
login_footer( 'user_login' ); //调用前面的模板生成函数生成结尾,在页面上就是显示一个回退的块
break;
case ‘lostpassword’
case 'lostpassword':
case 'retrievepassword':
两个action都会使用这个逻辑

if ( $http_post ) { //post时调用函数执行重置的逻辑
	$errors = retrieve_password();
	if ( ! is_wp_error( $errors ) ) {
	}
}
if ( isset( $_GET['error'] ) ) //根据传参的error值确定错误
$lostpassword_redirect = ! empty( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : '';
$redirect_to = apply_filters( 'lostpassword_redirect', $lostpassword_redirect 

do_action( 'lost_password', $errors );

login_header( __( 'Lost Password' ), '<p class="message">' . __( 'Please enter your username or email address. You will receive a link to create a new password via email.' ) . '</p>', $errors ); //调用模板生成函数
$user_login = '';
if ( isset( $_POST['user_login'] ) && is_string( $_POST['user_login'] ) ) //从POST参数获取用户信息

中间也是填写的表单 和上面的register 一样
login_footer( 'user_login' );
break;
case ‘resetpass’
case 'resetpass':
case 'rp': 

//和lostpassword区别是 先忘记密码后发确认邮件之后点击这个链接重设密码
list( $rp_path ) = explode( '?', wp_unslash( $_SERVER['REQUEST_URI'] ) ); //拿query string参数
$rp_cookie       = 'wp-resetpass-' . COOKIEHASH;  //设置cookie的hash
if ( isset( $_GET['key'] ) ) //若有GET的key就存入cookie并去除querystring这种明文参数

if ( isset( $_COOKIE[ $rp_cookie ] ) && 0 < strpos( $_COOKIE[ $rp_cookie ], ':' ) ){
//从$_COOKIE[ $rp_cookie ]拿 $rp_login 和 $rp_key
$user = check_password_reset_key( $rp_key, $rp_login );
if ( isset( $_POST['pass1'] ) && ! hash_equals( $rp_key, $_POST['rp_key'] ) ) {
	$user = false; //cookie里的$rp_key和POST来的不同 或是 pass1 字段未设置 都跳出
	}
} else {
	$user = false;
}
if ( ! $user || is_wp_error( $user ) ) //若$user为假 或是 报错则重定向wp-login并清理cookie

$errors = new WP_Error();
if ( isset( $_POST['pass1'] ) && $_POST['pass1'] != $_POST['pass2'] )  //密码输入两次保证一致,pass12为两次输入
do_action( 'validate_password_reset', $errors, $user ); //触发 before the password reset procedure is validated.

if ( ( ! $errors->has_errors() ) && isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) {
	reset_password( $user, $_POST['pass1'] ); //这里开始重置
	setcookie( $rp_cookie, ' ', time() - YEAR_IN_SECONDS, $rp_path, COOKIE_DOMAIN, is_ssl(), true ); //清除cookie 之后弹出已成功的提示
	login_header( __( 'Password Reset' ), '<p class="message reset-pass">' . __( 'Your password has been reset.' ) . ' <a href="' . esc_url( wp_login_url() ) . '">' . __( 'Log in' ) . '</a></p>' );
	login_footer();
	exit;
}

//将相关用户脚本加入加载队列
wp_enqueue_script( 'utils' );
wp_enqueue_script( 'user-profile' );

login_header(xxx)
<form > //这里的form还有自动生成强密码和密码强度检测器的功能
	_e( 'Strength indicator' ); 和 _e( 'New password' );
do_action( 'resetpass_form', $user );//在用户密码重置表格中的“强度指示器”指示器后面触发。
//输入重设的表单前端页面
</form>
login_footer( 'user_pass' );
break;
case ‘logout’
check_admin_referer( 'log-out' ); //检查reffer信息查看用户是否从admin的log-out页面进入的
//说是为了防止clickjacking style attacks这种攻击
$user = wp_get_current_user(); 
wp_logout(); //退出登陆

if ( ! empty( $_REQUEST['redirect_to'] ) ) {
	$redirect_to           = $_REQUEST['redirect_to'];
	$requested_redirect_to = $redirect_to;
//前一个参数表示目的地,后一个表示querystring里目的地作为参数的变量
} else {
	$redirect_to = add_query_arg(
		array(
			'loggedout' => 'true',
			'wp_lang'   => get_user_locale( $user ),
		),
		wp_login_url()
	);
	$requested_redirect_to = '';
} 
//若指定了$_REQUEST['redirect_to'] 则两个re参数都为这个 下面过滤后也就是最终结果
//若没有指定 则第1个re手动添加一些querystring参数 过滤时第2个参数就没用了
$redirect_to = apply_filters( 'logout_redirect', $redirect_to, $requested_redirect_to, $user ); 

wp_safe_redirect( $redirect_to );
exit();
case ‘postpass’
核心就是
setcookie( 'wp-postpass_' . COOKIEHASH, $hasher->HashPassword( wp_unslash( $_POST['post_password'] ) ), $expire, COOKIEPATH, COOKIE_DOMAIN, $secure );
把POST传过来的密码值用hash加密后设置为wp-postpass_.COOKIEHASH的值,并设置好释放时间,域,ssl来达到加密的目的
何时用到?
有些贴子是受密码保护的, 这里处理用户提交的密码
<form action="http://127.0.0.1/note-wordpress/wp-login.php?action=postpass" class="post-password-form" method="post">
	<p>这是一篇受密码保护的文章,您需要提供访问密码:</p>
	<p><label for="pwbox-211">密码: 
	<input name="post_password" id="pwbox-211" type="password" size="20" /></label> 
	<input type="submit" name="Submit" value="提交" /></p>
</form>
case ‘confirm_admin_email’
当后台修改了管理员邮箱或是超过了有效时长,重新登陆时就会强制重定向至确认页面
这里就是控制admin_email_lifespan 也就是管理员邮箱有效时长的
// Note that `is_user_logged_in()` will return false immediately after logging in
// as the current user is not set, see wp-includes/pluggable.php.
// 若当前用户未被配置也会导致logged_in返回false
if ( ! is_user_logged_in() ) //返回false则重定向至wp_login_url
if ( ! empty( $_REQUEST['redirect_to'] ) ) //不为空则赋值为空赋admin_url
if ( current_user_can( 'manage_options' ) )//返回true则get_option( 'admin_email' )
//并赋值admin_email,否则重定向出去

$remind_interval = (int) apply_filters( 'admin_email_remind_interval', 3 * DAY_IN_SECONDS );//提醒间隔默认3天
if ( ! empty( $_GET['remind_me_later'] ) ) {//此参数设置是否延时提醒
	if ( ! wp_verify_nonce( $_GET['remind_me_later'], 'remind_me_later_nonce' ) ) {////检查是否有特定的随机数
		wp_safe_redirect( wp_login_url() );
		exit;
	}
	if ( $remind_interval > 0 ) {
		update_option( 'admin_email_lifespan', time() + $remind_interval );//增加admin_email_lifespan也就是邮箱有效期的时长
	}

	wp_safe_redirect( $redirect_to );
	exit;
}

if ( ! empty( $_POST['correct-admin-email'] ) ) { //此参数不空则邮箱已确认需要更新至新的一轮
	if ( ! check_admin_referer( 'confirm_admin_email', 'confirm_admin_email_nonce' ) ) {
		wp_safe_redirect( wp_login_url() );//检查是否有特定的随机数没有则弹出
		exit;
	}
$admin_email_check_interval = (int) apply_filters( 'admin_email_check_interval', 6 * MONTH_IN_SECONDS ); //设置具体隔多长时间

	if ( $admin_email_check_interval > 0 ) {
		update_option( 'admin_email_lifespan', time() + $admin_email_check_interval );//也是增加admin_email_lifespan也就是邮箱有效期的时长,不过这里的增加是6月一加的
	}

	wp_safe_redirect( $redirect_to ); //更改值后重定向出去
	exit;
}

login_header( __( 'Confirm your administration email' ), '', $errors );

do_action( 'admin_email_confirm', $errors );

关于确认的表单页面 主体是 'Please verify that the <strong>administration email</strong> for this website is still correct.'
和
'Current administration email: %s' ),'<strong>' . esc_html( $admin_email )
action指向 site_url( 'wp-login.php?action=confirm_admin_email', 'login_post' )

login_footer();
break;
case ‘confirmaction’
确认操作,不在本文件中触发
if ( ! isset( $_GET['request_id'] ) ) 
if ( ! isset( $_GET['confirm_key'] ) )

//获取id和序列化key后运行验证函数看是否出错
$request_id = (int) $_GET['request_id'];
$key        = sanitize_text_field( wp_unslash( $_GET['confirm_key'] ) );
$result     = wp_validate_user_request_key( $request_id, $key );

if ( is_wp_error( $result ) )//结果若错误则弹出

do_action( 'user_request_action_confirmed', $request_id );//用户已确认并触发钩子

$message = _wp_privacy_account_request_confirmed_message( $request_id );
login_header( __( 'User action confirmed.' ), $message );
login_footer();
exit;
case ‘login’
case 'login':
default:

回顾下之前的$interim_login 临时登陆变量
$interim_login = isset( $_REQUEST['interim-login'] );

interim-login
customize-login
redirect_to
testcookie cookie
四个隐藏域

$secure_cookie   = '';
$customize_login = isset( $_REQUEST['customize-login'] ); //获取自定义登陆参数
if ( $customize_login ) {
	wp_enqueue_script( 'customize-base' );
}

//检查log参数里是否设置并通过sanitize_user( $_POST['log'] )拿到user_name
//之后获取到用户之后get_user_option若us_ssl开启则调用force_ssl_admin( true ) 强行设置ssl
if ( ! empty( $_POST['log'] ) && ! force_ssl_admin() )

if ( isset( $_REQUEST['redirect_to'] ) ) //获取r_t参数若$secure_cookie有还改下http为https

$reauth = empty( $_REQUEST['reauth'] ) ? false : true; //二次验证 为空false
$user = wp_signon( array(), $secure_cookie ); //加上$secure_cookie获取到带认证的WP_USER对象

//若cookie为空则判断下浏览器的支持性好不好
if ( empty( $_COOKIE[ LOGGED_IN_COOKIE ] ) ) {
			if ( headers_sent() ) {
		}elseif ( isset( $_POST['testcookie'] ) && empty( $_COOKIE[ TEST_COOKIE ] ) ) {
	}

$requested_redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : ''; //目的地的querystring形式
$redirect_to = apply_filters( 'login_redirect', $redirect_to, $requested_redirect_to, $user );

if ( ! is_wp_error( $user ) && ! $reauth ){ //没二次验证没错误时
	if ( $interim_login ) { //若临时登陆有 这里就已经算是登陆成功了
		$message       = '<p class="message">' . __( 'You have logged in successfully.' ) . '</p>';
		$interim_login = 'success';
		login_header( '', $message );

		?>
		</div>
		<?php

	do_action( 'login_footer' );

	if ( $customize_login ) { //自定义登陆貌似还能自定义跳转
		?>
		<script type="text/javascript">setTimeout( function(){ new wp.customize.Messenger({ url: '<?php echo wp_customize_url(); ?>', channel: 'login' }).send('login') }, 1000 );</script>
		<?php
	}

	?>
	</body></html>
	<?php

	exit;
    }
    // Check if it is time to add a redirect to the admin email confirmation screen.
    if ( is_a( $user, 'WP_User' ) && $user->exists() && $user->has_cap( 'manage_options' ) )
    /* 若用户是has_cap( 'manage_options' )有管理权限的 则查看他的email地址有效时间和更改间隔
    若if ( $admin_email_check_interval > 0 && time() > $admin_email_lifespan )则带上
    array('action'  => 'confirm_admin_email','wp_lang' => get_user_locale( $user ),)
    重定向走*/


// If the user doesn't belong to a blog, send them to user admin. If the user can't edit posts, send them to their profile.
//若用户重定向为空或是为管理员地址时检查是否属于当前blog,这又涉及到multisite()的问题
//还有具体权限has_cap( 'read' )或has_cap( 'edit_posts' )
	if ( ( empty( $redirect_to ) || $redirect_to === 'wp-admin/' || $redirect_to === admin_url() ) ) {
	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();
	}

wp_redirect( $redirect_to );
exit;
}

wp_safe_redirect( $redirect_to );
exit;
}

到这里正式登陆的逻辑就完了,能跳转走的都跳了
注意回头看大逻辑的进入条件
if ( ! is_wp_error( $user ) && ! $reauth ) 
也就是重新认证的选项没有打开且user对象没有错误时才会进入
下面则是其他情况的补充了
如
$interim_login临时登陆
$reauth重新认证选项

		$errors = $user;
		// Clear errors if loggedout is set.
		if ( ! empty( $_GET['loggedout'] ) || $reauth ) {
			$errors = new WP_Error();
		}

		if ( empty( $_POST ) && $errors->get_error_codes() === array( 'empty_username', 'empty_password' ) ) {
			$errors = new WP_Error( '', '' );
		}

		if ( $interim_login ) {
			if ( ! $errors->has_errors() ) { //没错误说seesion过期了
				$errors->add( 'expired', __( 'Your session has expired. Please log in to continue where you left off.' ), 'message' );
			}
		} else {
			// 根据参数利用$errors->add往不同处跳转
			if ( isset( $_GET['loggedout'] ) && $_GET['loggedout'] ) {
				$errors->add( 'loggedout', __( 'You are now logged out.' ), 'message' );
			} elseif ( isset( $_GET['registration'] ) && 'disabled' === $_GET['registration'] ) {
				$errors->add( 'registerdisabled', __( 'User registration is currently not allowed.' ) );
			} elseif ( isset( $_GET['checkemail'] ) && 'confirm' === $_GET['checkemail'] ) {
				$errors->add( 'confirm', __( 'Check your email for the confirmation link.' ), 'message' );
			} elseif ( isset( $_GET['checkemail'] ) && 'newpass' === $_GET['checkemail'] ) {
				$errors->add( 'newpass', __( 'Check your email for your new password.' ), 'message' );
			} elseif ( isset( $_GET['checkemail'] ) && 'registered' === $_GET['checkemail'] ) {
				$errors->add( 'registered', __( 'Registration complete. Please check your email.' ), 'message' );
			} elseif ( strpos( $redirect_to, 'about.php?updated' ) ) {
				$errors->add( 'updated', __( '<strong>You have successfully updated WordPress!</strong> Please log back in to see what’s new.' ), 'message' );
			} elseif ( WP_Recovery_Mode_Link_Service::LOGIN_ACTION_ENTERED === $action ) {
				$errors->add( 'enter_recovery_mode', __( 'Recovery Mode Initialized. Please log in to continue.' ), 'message' );
			}
		}


到这里就是从log里拿记录和前端表单的代码了

$errors = apply_filters( 'wp_login_errors', $errors, $redirect_to );

// Clear any stale cookies.
if ( $reauth ) {
	wp_clear_auth_cookie();
}

login_header( __( 'Log In' ), '', $errors );

if ( isset( $_POST['log'] ) ) {
	$user_login = ( 'incorrect_password' === $errors->get_error_code() || 'empty_password' === $errors->get_error_code() ) ? esc_attr( wp_unslash( $_POST['log'] ) ) : '';
}

$rememberme = ! empty( $_POST['rememberme'] );

wp_enqueue_script( 'user-profile' );

接下来就是填写用户名和密码的表单还有记住密码name="rememberme"的选项

do_action( 'login_form' );

根据if ( $interim_login ) 和 if ( $customize_login )来控制他们的隐藏域
之后if ( ! $interim_login )  非登陆状态还显示注册和忘记密码的小选项

//自动对用户名或密码输入框聚焦的脚本
$login_script  = 'function wp_attempt_focus() {';
$login_script .= 'setTimeout( function() {';
$login_script .= 'try {';
if ( $user_login ) {
	$login_script .= 'd = document.getElementById( "user_pass" ); d.value = "";';
} else {
	$login_script .= 'd = document.getElementById( "user_login" );';
	if ( $errors->get_error_code() === 'invalid_username' ) {
		$login_script .= 'd.value = "";';
	}
}
$login_script .= 'd.focus(); d.select();';
$login_script .= '} catch( er ) {}';
$login_script .= '}, 200);';
$login_script .= "}\n"; // End of wp_attempt_focus().

if ( apply_filters( 'enable_login_autofocus', true ) && ! $error ) {
	$login_script .= "wp_attempt_focus();\n"; //加上定义js函数后的使用
}

// Run `wpOnload()` if defined.
$login_script .= "if ( typeof wpOnload === 'function' ) { wpOnload() }";

?>
<script type="text/javascript">
	<?php echo $login_script; ?> //运用脚本
</script>
<?php

if ( $interim_login ) {
	//批量设置a标签的2属性
	//links[i].target = '_blank';
	//links[i].rel = 'noreferrer noopener';
}

login_footer();
break;

这里做下难点回顾

checkemail 和 action关系
在 if ( $interim_login ) 中 $interim_login为false时 根据参数利用$errors->add往不同处跳转

$interim_login临时登陆
$reauth重新认证选项

$interim_login = isset($_REQUEST['interim-login']); //若能获取到则获取并且隐藏域设置value为1

$interim_login 相关逻辑

if ( $interim_login ) {
			if ( ! $errors->has_errors() ) { //没错误说seesion过期了
				$errors->add( 'expired', __( 'Your session has expired. Please log in to continue where you left off.' ), 'message' );
			}
		} else {
			// 根据参数利用$errors->add往不同处跳转
			if ( isset( $_GET['loggedout'] ) && $_GET['loggedout'] ) {
				$errors->add( 'loggedout', __( 'You are now logged out.' ), 'message' );
			} elseif {
			...
		    }
			} elseif ( WP_Recovery_Mode_Link_Service::LOGIN_ACTION_ENTERED === $action ) {
				$errors->add( 'enter_recovery_mode', __( 'Recovery Mode Initialized. Please log in to continue.' ), 'message' );
			}
		}


$reauth相关逻辑
$reauth = empty( $_REQUEST['reauth'] ) ? false : true; //重新认证 为空false

// Clear any stale cookies.
if ( $reauth ) {
	wp_clear_auth_cookie();
}

好了,到这里为止登陆相关的核心逻辑已经梳理完全了,下面是一些同在项目根目录可能被登陆逻辑所调用的特定功能的文件。也有可能是被其他文件调用的,至于他们为什么就放在根目录了,我的理解是不会被多次调用,不符合其他目录里库函数或是类的定义规范。这边我们除了三个很少用或是纯解析格式的文件,还有五个有明确单独功能的,让我们开始。

wp-mail.php

通过调用库函数发送邮件

基本就是引入库文件来实例POP3对象然后发邮件啦
require_once( ABSPATH . WPINC . '/class-pop3.php' );
$pop3 = new POP3();

wp-signup.php

多站点模式下站点和用户的注册

require( dirname( __FILE__ ) . '/wp-load.php' );
add_action( 'wp_head', 'wp_no_robots' ); //加了个反爬虫的钩子
require( dirname( __FILE__ ) . '/wp-blog-header.php' );
nocache_headers();

if ( is_array( get_site_option( 'illegal_names' ) ) && isset( $_GET['new'] ) && in_array( $_GET['new'], get_site_option( 'illegal_names' ) ) ) {
	wp_redirect( network_home_url() );
	die();
} //检查new参数对应值是否在非法名单中

Prints signup_header via wp_head
function do_signup_header() {
	/**
	 * Fires within the head section of the site sign-up screen.
	 *
	 * @since 3.0.0
	 */
	do_action( 'signup_header' );
}//响应钩子


add_action( 'wp_head', 'do_signup_header' );

if ( ! is_multisite() ) {
	wp_redirect( wp_registration_url() );
	die();
}

if ( ! is_main_site() ) {
	wp_redirect( network_site_url( 'wp-signup.php' ) );
	die();
}


do_action( 'before_signup_header' );
function wpmu_signup_stylesheet() //全局添加样式表
add_action( 'wp_head', 'wpmu_signup_stylesheet' );
get_header( 'wp-signup' );

do_action( 'before_signup_form' );

//公用的站点表单的显示函数 
里面分别负责 Blog name Blog Title  Site Language 隐私策略的逻辑 还判断is_subdomain_install()和is_user_logged_in()是否启用
最后执行钩子 do_action( 'signup_blogform', $errors );
function show_blog_form( $blogname = '', $blog_title = '', $errors = '' ) 
//公用的用户注册的显示函数 最后里面带个钩子do_action( 'signup_extra_fields', $errors );
function show_user_form( $user_name = '', $user_email = '', $errors = '' )

* Validate the new site signup
*
* @since MU (3.0.0)
*
* @return array Contains the new site data and error messages.
公用的过滤器 过滤站点注册信息
function validate_blog_form() {
	$user = '';
	if ( is_user_logged_in() ) {
		$user = wp_get_current_user();
	}

	return wpmu_validate_blog_signup( $_POST['blogname'], $_POST['blog_title'], $user );
}


* Validate user signup name and email
*
* @since MU (3.0.0)
*
* @return array Contains username, email, and error messages.
公用的过滤器 过滤用户注册信息
function validate_user_form() {
	return wpmu_validate_user_signup( $_POST['user_name'], $_POST['user_email'] );
}



//允许用户注册一个其他的新站点
function signup_another_blog( $blogname = '', $blog_title = '', $errors = '' )
function validate_another_blog_signup()
function confirm_another_blog_signup( $domain, $path, $blog_title, $user_name, $user_email = '', $meta = array(), $blog_id = 0 ) 
和下面一样也是展示->验证->确认 3步走

//设置新用户注册的前台页面 设好默认的$signup_user_defaults = array(...)
function signup_user( $user_name = '', $user_email = '', $errors = '' ) 
//处理用户注册的数据,并过滤后送交给wpmu_signup_user()方法完成注册,最后调用confirm_user_signup()
function validate_user_signup()
//前台提示用户注册成功
function confirm_user_signup( $user_name, $user_email )

//设置新的站点,提供站点注册的页面
function signup_blog( $user_name = '', $user_email = '', $blogname = '', $blog_title = '', $errors = '' ) 
//后台验证信息及初始化语言选项并应用过滤器,最后调用confirm
function validate_blog_signup() 
//前台提示站点注册成功
function confirm_blog_signup( $domain, $path, $blog_title, $user_name = '', $user_email = '', $meta = array() )


function signup_get_available_languages()  //检索可用语言
$active_signup = get_site_option( 'registration', 'none' );//调用WPINC里方法获得注册信息
$active_signup = apply_filters( 'wpmu_active_signup', $active_signup ); //过滤下激活信息
if ( current_user_can( 'manage_network' ) ) //控制管理网络

$current_user = wp_get_current_user();
if ( 'none' === $active_signup ) { //检查可注册性和是否登录
	_e( 'Registration has been disabled.' );
} elseif ( 'blog' === $active_signup && ! is_user_logged_in() ) {
} else {
}


do_action( 'after_signup_form' );
get_footer( 'wp-signup' );

wp-activate.php

注册成功邮箱确认

//引入load和blog-header文件
//多站点验证并重定向

$key    = '';
$result = null;

//默认常量COOKIEHASH拼接后拿到激活cookie
$activate_cookie       = 'wp-activate-' . COOKIEHASH;

//判断get和post获取的key是否相同,获取query string中key值并处理后赋值给key变量
if ( isset( $_GET['key'] ) && isset( $_POST['key'] ) && $_GET['key'] !== $_POST['key'] )

if ( $key ) {
	$redirect_url = remove_query_arg( 'key' );
	if ( $redirect_url !== remove_query_arg( false ) ) {
		setcookie( $activate_cookie, $key, 0, $activate_path, COOKIE_DOMAIN, is_ssl(), true );
		wp_safe_redirect( $redirect_url );
		exit;
	} else {
		$result = wpmu_activate_signup( $key );
	}
}
remove_query_arg函数查看源码后得知是去除多余querystring并将处理后的url作为字符串返回,remove_query_arg( false )查看源码是等于将3个false传给function add_query_arg( ...$args )之后逻辑返回原字符串

* @param string|array $key   Either a query variable key, or an associative array of query variables.
* @param string       $value Optional. Either a query variable value, or a URL to act upon.
* @param string       $url   Optional. A URL to act upon.
* @return string New URL query string (unescaped).

应该之后是相当于把`$_SERVER['REQUEST_URI']`和处理后的$redirect_url比较不相同就清除cookie并重定向出去
若成功则result被算出

if ( $result === null && isset( $_COOKIE[ $activate_cookie ] ) ) //若没写入激活cookie则写入

if ( $result === null || ( is_wp_error( $result ) && 'invalid_key' === $result->get_error_code() ) ) 
/*根据result正确性决定是否跳出
$result为wpmu_activate_signup( $key ) (这个是多站点的公共api wp-includes/ms-functions.php )
或者是 status_header( 400 ) 返回错误status code
经过和老版本对比,这种验证方式是新添加的,下面的是公用的。
*/

nocache_headers(); //为多个浏览器设置no_cache的header
do_action( 'activate_header' ); //直接执行activate_header这个钩子 生命周期是在before the Site Activation page is loaded
add_action( 'wp_head', 'do_activate_header' );  //action设为 钩子设为do_activate_header 查看后面钩子函数可知实际运行 activate_wp_head
get_header( 'wp-activate' ); //获取公共的页面 wp-includes/general-template.php 在里面也是执行了钩子do_action( 'get_header', 'wp-activate' );

钩子函数

		function do_activate_header() {
			/**
			 * Fires before the Site Activation page is loaded.
			 *
			 * Fires on the {@see 'wp_head'} action.
			 *
			 * @since 3.0.0
			 */
			do_action( 'activate_wp_head' );
		}


之后根据判断显示不同前端页面

if ( is_wp_error( $result ) && in_array( $result->get_error_code(), $valid_error_codes ) ) 
和
if ( $signup->domain . $signup->path == '' ) 
都通过显示您已注册成功点击这里登陆
第一次通过赋值 $signup = $result->get_error_data();

elseif ( $result === null || is_wp_error( $result ) )
显示出错跳出

else
显示刚刚注册好欢迎界面

wp-comments-post.php

wp-cron.php

总结

用户登陆这一块的文件详解都基本是这几天熬夜肝出来的,php这种传统的前后混合的写法可读性是真的减了不少分。目前新开的项目尽量还是前后端分离和跨域的方式去完成,或者是Laravel框架这种语法更优雅的。回到文件本身,能把完整的登录注册相关逻辑都汇总在单文件里,利用request类型同时完成前端页面展示和后端数据处理逻辑,同时兼顾https协议兼容性,cookie和interim_user确保持久化访问与安全性,通过阅读这一块的源码还是有很多写法可以学习的。

技术总结

wp-login执行顺序及调用关系图

https://img.ocasis.cn/image/5e116016f300e

引用列表

wordpress源码笔记

赞赏
欢迎留言交流讨论,留言必回。

admin

文章作者

一个平凡的追梦人

发表评论

textsms
account_circle
email

x1184的小站

初探wordpress源码(二)-登陆机制
关于登陆机制及各种验证机制的研究
扫描二维码继续阅读
2019-12-31