WordPress+BuddyPress注册页面404问题的解决

欢迎转载!转载时请注明出处:http://blog.csdn.net/nfer_zhuang/article/details/50316171

前言

WordPress自带的注册页面比较简陋,BuddyPress插件提供了一个比较丰富的注册页面,但是当我打开页面时却显示了“404 Not Found”(已打开WordPress的注册开关并设置了Buddypress的注册页面),本篇文章就是记录如何排查和解决该问题的。

问题截图

问题截图
注:我是在WordPress4.3.1版本下进行的测试

排查问题

确认BuddyPress页面设置

BuddyPress页面设置

通过截图,我们确认设置了使用BuddyPress的注册页面来替换原生页面。

确认页面是否可以直接打开

我们注意在每个设置的右边有一个view的按钮,那么直接点击这个按钮是否可以打开页面呢?
register页面
activate页面

通过截图,我们确认直接打开页面是OK的,至少说明设置是有效的,且页面存在。
但是,我们发现了一个奇怪的地方,就是这两个页面的URL中都带有一个index.php,这是个什么东西呢?看上去完全不符合常理啊。

固定链接

通过查询资料,我们发现这个index.php是根据固定链接中的设置添加到URL中的。
固定链接设置
那为什么WordPress会设置成/index.php/*这种反人类的方式呢?

源码分析

在wp-admin/includes/upgrade.php文件下有下述代码:

    /*
     * The Permalink structures to attempt.
     *
     * The first is designed for mod_rewrite or nginx rewriting.
     *
     * The second is PATHINFO-based permalinks for web server configurations
     * without a true rewrite module enabled.
     */
    $permalink_structures = array(
        '/%year%/%monthnum%/%day%/%postname%/',
        '/index.php/%year%/%monthnum%/%day%/%postname%/'
    );

    foreach ( (array) $permalink_structures as $permalink_structure ) {
        $wp_rewrite->set_permalink_structure( $permalink_structure );

        /*
         * Flush rules with the hard option to force refresh of the web-server's
         * rewrite config file (e.g. .htaccess or web.config).
         */
        $wp_rewrite->flush_rules( true );

        // Test against a real WordPress Post, or if none were created, a random 404 page.
        $test_url = get_permalink( 1 );

        if ( ! $test_url ) {
            $test_url = home_url( '/wordpress-check-for-rewrites/' );
        }

        /*
         * Send a request to the site, and check whether
         * the 'x-pingback' header is returned as expected.
         *
         * Uses wp_remote_get() instead of wp_remote_head() because web servers
         * can block head requests.
         */
        $response          = wp_remote_get( $test_url, array( 'timeout' => 5 ) );
        $x_pingback_header = wp_remote_retrieve_header( $response, 'x-pingback' );
        $pretty_permalinks = $x_pingback_header && $x_pingback_header === get_bloginfo( 'pingback_url' );

        if ( $pretty_permalinks ) {
            return true;
        }
    }

上述代码是在用户安装WordPress时候运行的代码片段,通过添加打印,我们确认上面的foreachloop在运行到'/index.php/%year%/%monthnum%/%day%/%postname%/'的时候才返回true。

回到上面permalink_structures数组的注释上:

    /*
     * The Permalink structures to attempt.
     *
     * The first is designed for mod_rewrite or nginx rewriting.
     *
     * The second is PATHINFO-based permalinks for web server configurations
     * without a true rewrite module enabled.
     */

我们了解到第一个固定链接格式是针对于支持mod_rewritenginx rewriting的,而第二个则是针对不支持rewrite的HTTP Server环境的。

我这里使用的是LAMP的环境,理论上是支持rewrite的,那么下一步我们就开始排查rewrite功能。

LAMP的rewrite功能

检查apache是否已经配置rewrite功能

通过搜索,确认在apache2中已经配置了rewrite功能

$ grep rewrite ./ -nR
./mods-available/rewrite.load:1:LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so
./mods-enabled/rewrite.load:1:LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so
$ ls /usr/lib/apache2/modules/mod_rewrite.so
/usr/lib/apache2/modules/mod_rewrite.so

我们发现在mods-enabled中已经包含rewrite.load,且mod_rewrite.so文件存在。

通过PHP检查rewrite功能是否已配置

stackoverflow中有一个回答中给出了一种方法:

in_array('mod_rewrite', apache_get_modules());

我们添加了一些打印信息:

<?php
if (in_array('mod_rewrite', apache_get_modules()))
  print "The apache module mod_rewrite is enabled.<br/>\n";
else
  print "The apache module mod_rewrite is NOT enabled.<br/>\n";

但是在cli下运行报错:

$ php apache_rewrite_check 
PHP Fatal error:  Call to undefined function apache_get_modules() in /home/nfer/bak/apache_rewrite_check on line 2

我们尝试在浏览器中访问该文件,发现可以运行,且输出了The apache module mod_rewrite is enabled.信息,证明在apache运行环境下通过PHP确认是支持rewrite功能的。

通过shell命令行检查rewrite功能是否已配置

同样,在该篇回答中给出了一种在Ubuntu命令行下检查是否打开了rewrite功能的方法:

$ apachectl -M | grep rewrite
 rewrite_module (shared)

通过PHP实际测试rewrite功能

按照stackoverflow给出的例子,我们创建了rewrite文件夹,并在该文件夹下创建了.htaccess和index.php文件,内容分别为:

$ cat .htaccess 
RewriteEngine On
RewriteRule ^inc/.*$ index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
$ cat index.php 
<?php
print "hello world.<br/>\n";

我们测试,通过http://localhost/rewrite/http://localhost/rewrite/123123http://localhost/rewrite/hello 以及任何http://localhost/rewrite/ 前缀的地址都能正确输出hello world.,即PHP的rewrite功能是OK的。

WordPress的rewrite判断

我们重点来看一下foreach的处理:

        $wp_rewrite->set_permalink_structure( $permalink_structure );

        /*
         * Flush rules with the hard option to force refresh of the web-server's
         * rewrite config file (e.g. .htaccess or web.config).
         */
        $wp_rewrite->flush_rules( true );

        // Test against a real WordPress Post, or if none were created, a random 404 page.
        $test_url = get_permalink( 1 );

        if ( ! $test_url ) {
            $test_url = home_url( '/wordpress-check-for-rewrites/' );
        }

        /*
         * Send a request to the site, and check whether
         * the 'x-pingback' header is returned as expected.
         *
         * Uses wp_remote_get() instead of wp_remote_head() because web servers
         * can block head requests.
         */
        $response          = wp_remote_get( $test_url, array( 'timeout' => 5 ) );
        $x_pingback_header = wp_remote_retrieve_header( $response, 'x-pingback' );
        $pretty_permalinks = $x_pingback_header && $x_pingback_header === get_bloginfo( 'pingback_url' );

        if ( $pretty_permalinks ) {
            return true;
        }

上面的测试主要包括以下几个步骤:
1. 设置固定链接格式
2. 将rewrite规则写入到文件中
3. 使用第一篇博客(id为1)来实际测试rewrite效果
4. 发送wp_remote_get请求并检测返回的header中是否包含x-pingback字段(URL可以访问的话,response中会包含x-pingback字段,否则不包含该字段)

注:更多关于x-pingback的资料请移步WordPress官方文档

flush_rules()

在上面的代码中有一处代码如下:

        /*
         * Flush rules with the hard option to force refresh of the web-server's
         * rewrite config file (e.g. .htaccess or web.config).
         */
        $wp_rewrite->flush_rules( true );

继续看flush_rules函数的实现:

    public function flush_rules( $hard = true ) {
        static $do_hard_later = null;

        // Prevent this action from running before everyone has registered their rewrites.
        if ( ! did_action( 'wp_loaded' ) ) {
            add_action( 'wp_loaded', array( $this, 'flush_rules' ) );
            $do_hard_later = ( isset( $do_hard_later ) ) ? $do_hard_later || $hard : $hard;
            return;
        }

        if ( isset( $do_hard_later ) ) {
            $hard = $do_hard_later;
            unset( $do_hard_later );
        }

        delete_option('rewrite_rules');
        $this->wp_rewrite_rules();

        /**
         * Filter whether a "hard" rewrite rule flush should be performed when requested.
         *
         * A "hard" flush updates .htaccess (Apache) or web.config (IIS).
         *
         * @since 3.7.0
         *
         * @param bool $hard Whether to flush rewrite rules "hard". Default true.
         */
        if ( ! $hard || ! apply_filters( 'flush_rewrite_rules_hard', true ) ) {
            return;
        }
        if ( function_exists( 'save_mod_rewrite_rules' ) )
            save_mod_rewrite_rules();
        if ( function_exists( 'iis7_save_url_rewrite_rules' ) )
            iis7_save_url_rewrite_rules();
    }

该函数包括以下流程:
1. 处理在wp_loaded之前的flush_rules请求
2. 更新rewrite_rules配置(到数据库中)
3. 更加环境不同,分别调用save_mod_rewrite_rules和iis7_save_url_rewrite_rules来写入rewrite规则到文件系统中

save_mod_rewrite_rules()

因为我使用的LAMP环境,因此在flush_rules()中实际作用的是save_mod_rewrite_rules()函数,实现如下:

function save_mod_rewrite_rules() {
    if ( is_multisite() )
        return;

    global $wp_rewrite;

    $home_path = get_home_path();
    $htaccess_file = $home_path.'.htaccess';

    /*
     * If the file doesn't already exist check for write access to the directory
     * and whether we have some rules. Else check for write access to the file.
     */
    if ((!file_exists($htaccess_file) && is_writable($home_path) && $wp_rewrite->using_mod_rewrite_permalinks()) || is_writable($htaccess_file)) {
        if ( got_mod_rewrite() ) {
            $rules = explode( "\n", $wp_rewrite->mod_rewrite_rules() );
            return insert_with_markers( $htaccess_file, 'WordPress', $rules );
        }
    }

    return false;
}

在上述代码中,有几处条件检查:
1. 判断htaccess_file文件是否不存在
2. 判断home_path目录是否可写
3. 判断htaccess_file文件是否可写

其实,看到这里我们需要检查是否在安装WordPress的时候WordPress程序不具有home_path目录和htaccess_file文件的写权限?

通过ls -l可以得到下述的列表:

/var/www/tag/WordPress_4_3_1$ ls -l ../
total 4
drwxrwxr-x 6 nfer nfer 4096 1215 17:38 WordPress_4_3_1

其中WordPress_4_3_1目录的读写权限是775,即当前用户和当前组均具有读写执行权限,其它用户只有读和执行权限并没有写入权限。我们再看一下apache进行的用户名:

/var/www/tag/WordPress_4_3_1$ ps auxf | grep apache
root      1071  0.0  0.2  38048  9024 ?        Ss   04:15   0:08 /usr/sbin/apache2 -k start
www-data  4695  0.0  1.0  73968 44636 ?        S    09:04   0:11  \_ /usr/sbin/apache2 -k start
www-data  4723  0.0  1.1  76304 46396 ?        S    09:14   0:10  \_ /usr/sbin/apache2 -k start

可以看到apache是通过root用户启动的,然后每个工作进程是使用www-data用户启动的。

修改文件拥有者并重新安装WordPress

通过下述命令,修改WordPress目录的拥有者为www-data:

/var/www/tag$ sudo chown www-data WordPress_4_3_1/ -R
[sudo] password for nfer: 
/var/www/tag$ ls -l
total 4
drwxrwxr-x 6 www-data nfer 4096 1215 17:38 WordPress_4_3_1

删除并重新创建数据库,删除wp-config.php文件,重新打开WordPress安装程序,整个流程完成后,我们再打开”设置-固定链接”页面,发现固定链接格式为'/%year%/%monthnum%/%day%/%postname%/'
正确的固定链接格式

总结

该文章描述了我从最开始遇到WordPress+BuddyPress无法打开注册页面问题,到一步步的通过查询资料、阅读相关源码,并最终解决问题的过程。在实际解决问题的过程中,有一些地方并没有详细展开,一些遗留问题已附在下面。
解决问题后,再次打开注册页面,效果如下:
注册页面

遗留问题

  1. 为什么在.htaccess无法写入的情况下,使用'/index.php/%year%/%monthnum%/%day%/%postname%/' rewrite规则?
  2. '/index.php/%year%/%monthnum%/%day%/%postname%/'是如何在.htaccess无法工作的情况下产生实际效果的?
  3. flush_rules函数中的wp_loaded逻辑是否有必要?

猜你喜欢

转载自blog.csdn.net/nfer_cn/article/details/50316171