深入理解 WordPress `get_blogs_of_user()` 函数的源码:如何获取指定用户所属的所有站点。

各位WordPress探险家们,欢迎来到今天的源码解剖课堂!今天我们要一起扒一扒WordPress里一个相当实用的函数,get_blogs_of_user(),看看它是怎么把一个用户“扒”得精光,哦不,是找到这个用户所属的所有站点的。

开场白:用户与站点,一场不得不说的关系

在WordPress的世界里,用户和站点之间的关系,就像鱼和水,鸟和树林,程序员和Bug…总之,它们是紧密相连的。一个用户可以只属于一个站点,也可以是多个站点的成员,拥有不同的角色和权限。而get_blogs_of_user()函数,就是用来查询指定用户,在哪些站点里有“户口”,也就是哪些站点里有它的身影。

正文:get_blogs_of_user()的源码解剖

好了,废话不多说,咱们直接上源码,看看这个函数到底是怎么工作的。

/**
 * Retrieve all blogs of a user.
 *
 * @since 3.0.0
 *
 * @param int $user_id User ID.
 * @param bool $all Whether to retrieve all sites of the user. Default is false.
 * @return array An array of site ID keys and site URL values.
 */
function get_blogs_of_user( $user_id, $all = false ) {
    global $wpdb;

    $user_id = (int) $user_id;

    if ( empty( $user_id ) ) {
        return array();
    }

    /**
     * Filters the blogs of a user.
     *
     * @since 3.0.0
     *
     * @param array $blogs Array of site IDs and site URL values.
     * @param int   $user_id User ID.
     * @param bool  $all    Whether to retrieve all sites of the user. Default is false.
     */
    $blogs = apply_filters( 'pre_get_blogs_of_user', null, $user_id, $all );
    if ( ! is_null( $blogs ) ) {
        return $blogs;
    }

    $cache_key = "blogs_of_user_{$user_id}";
    if ( $all ) {
        $cache_key .= '_all';
    }

    $cache = wp_cache_get( $cache_key, 'site-transient' );

    if ( false !== $cache ) {
        /**
         * Filters the blogs of a user from the cache.
         *
         * @since 3.0.0
         *
         * @param array $cache Array of site IDs and site URL values.
         * @param int   $user_id User ID.
         * @param bool  $all    Whether to retrieve all sites of the user. Default is false.
         */
        $cache = apply_filters( 'get_blogs_of_user_cache', $cache, $user_id, $all );
        return $cache;
    }

    $blogs = array();

    if ( is_multisite() ) {
        if ( $all ) {
            $query = $wpdb->prepare(
                "SELECT blog_id FROM {$wpdb->prefix}users WHERE user_id = %d",
                $user_id
            );
            $ids = $wpdb->get_col( $query );
        } else {
            $query = $wpdb->prepare(
                "SELECT blog_id FROM {$wpdb->prefix}usermeta WHERE user_id = %d AND meta_key = 'wp_%d_capabilities' AND meta_value NOT LIKE '%%s:13:"administrator"%%'",
                $user_id,
                $user_id
            );

            $ids = $wpdb->get_col( $query );
        }

        if ( ! empty( $ids ) ) {
            foreach ( $ids as $id ) {
                $id = (int) $id;
                $details = get_blog_details( $id );
                if ( $details ) {
                    $blogs[ $id ] = $details->siteurl;
                }
            }
        }
    } else {
        $blogs[ get_current_blog_id() ] = get_home_url();
    }

    wp_cache_set( $cache_key, $blogs, 'site-transient', DAY_IN_SECONDS );

    /**
     * Filters the blogs of a user.
     *
     * @since 3.0.0
     *
     * @param array $blogs Array of site IDs and site URL values.
     * @param int   $user_id User ID.
     * @param bool  $all    Whether to retrieve all sites of the user. Default is false.
     */
    return apply_filters( 'get_blogs_of_user', $blogs, $user_id, $all );
}

源码分解:步步为营,逐行剖析

咱们把这段代码拆开,像拆解一个精密的机械钟表一样,看看每个零件都干了啥。

  1. 函数定义和参数说明

    function get_blogs_of_user( $user_id, $all = false ) {
    • $user_id: 必须的参数,要查询的用户ID。
    • $all: 可选参数,一个布尔值。如果设置为 true,就查询用户所有的站点(包括管理员权限的站点)。如果设置为 false(默认值),则只查询用户非管理员权限的站点。
  2. 参数校验和默认值处理

    global $wpdb;
    
    $user_id = (int) $user_id;
    
    if ( empty( $user_id ) ) {
        return array();
    }
    • 首先,声明全局变量 $wpdb,这是WordPress用来操作数据库的核心对象。
    • 然后,强制将 $user_id 转换为整数类型,防止SQL注入之类的安全问题。
    • 接着,判断 $user_id 是否为空。如果为空,说明没有提供用户ID,直接返回一个空数组。
  3. pre_get_blogs_of_user 过滤器

    $blogs = apply_filters( 'pre_get_blogs_of_user', null, $user_id, $all );
    if ( ! is_null( $blogs ) ) {
        return $blogs;
    }
    • 这是一个非常重要的环节,它允许开发者在函数执行的核心逻辑之前,通过过滤器来改变函数的行为或者直接返回结果。
    • apply_filters() 函数会调用所有挂载到 pre_get_blogs_of_user 钩子上的函数。
    • 如果任何一个挂载的函数返回了非 null 的值,那么这个值就会被作为 get_blogs_of_user() 的最终结果返回,后面的代码就不会执行了。这就像一个“短路”操作,可以用来实现自定义的缓存机制或者权限控制。
  4. 缓存机制

    $cache_key = "blogs_of_user_{$user_id}";
    if ( $all ) {
        $cache_key .= '_all';
    }
    
    $cache = wp_cache_get( $cache_key, 'site-transient' );
    
    if ( false !== $cache ) {
        $cache = apply_filters( 'get_blogs_of_user_cache', $cache, $user_id, $all );
        return $cache;
    }
    • WordPress使用了缓存来提高性能。这里先生成一个缓存键 $cache_key,它基于 $user_id$all 的值。
    • 然后,使用 wp_cache_get() 函数尝试从缓存中获取数据。
    • 如果缓存中存在数据($cache !== false),则应用 get_blogs_of_user_cache 过滤器,允许开发者修改缓存的数据,并直接返回缓存的数据。
  5. 多站点判断和数据库查询

    $blogs = array();
    
    if ( is_multisite() ) {
        if ( $all ) {
            $query = $wpdb->prepare(
                "SELECT blog_id FROM {$wpdb->prefix}users WHERE user_id = %d",
                $user_id
            );
            $ids = $wpdb->get_col( $query );
        } else {
            $query = $wpdb->prepare(
                "SELECT blog_id FROM {$wpdb->prefix}usermeta WHERE user_id = %d AND meta_key = 'wp_%d_capabilities' AND meta_value NOT LIKE '%%s:13:"administrator"%%'",
                $user_id,
                $user_id
            );
    
            $ids = $wpdb->get_col( $query );
        }
    
        if ( ! empty( $ids ) ) {
            foreach ( $ids as $id ) {
                $id = (int) $id;
                $details = get_blog_details( $id );
                if ( $details ) {
                    $blogs[ $id ] = $details->siteurl;
                }
            }
        }
    } else {
        $blogs[ get_current_blog_id() ] = get_home_url();
    }
    • is_multisite() 函数判断当前WordPress是否是多站点模式。
    • 如果是多站点模式:
      • 如果 $alltrue,则查询 wp_users 表,找出 $user_id 存在的所有 blog_id。注意,这个表在多站点中存在链接用户与站点的关系,但仅限于用户最初被创建时所在的站点。
      • 如果 $allfalse,则查询 wp_usermeta 表,找出 $user_idwp_{blog_id}_capabilities 元数据,并排除 administrator 角色,从而找到用户不是管理员的站点。这里用到了 LIKE '%%s:13:"administrator"%%' 来排除管理员角色,这是一种比较hacky的做法,因为角色信息是序列化存储的。
      • 获取到 blog_id 列表后,遍历这些ID,使用 get_blog_details() 函数获取站点的详细信息,并将站点的ID和URL存储到 $blogs 数组中。
    • 如果不是多站点模式:
      • 直接将当前站点的ID和URL存储到 $blogs 数组中。
  6. 设置缓存

    wp_cache_set( $cache_key, $blogs, 'site-transient', DAY_IN_SECONDS );
    • 将查询到的站点信息存储到缓存中,以便下次更快地获取。缓存的有效期是 DAY_IN_SECONDS(一天)。
  7. get_blogs_of_user 过滤器

    return apply_filters( 'get_blogs_of_user', $blogs, $user_id, $all );
    • 这是另一个过滤器,允许开发者在函数返回结果之前,对结果进行最后的修改。

代码示例:如何使用get_blogs_of_user()

<?php
// 获取用户ID为1的所有站点(包括管理员权限的站点)
$user_id = 1;
$all_sites = get_blogs_of_user( $user_id, true );

echo "用户ID为 {$user_id} 的所有站点:<br>";
echo "<ul>";
foreach ( $all_sites as $blog_id => $blog_url ) {
    echo "<li>站点ID:{$blog_id},站点URL:{$blog_url}</li>";
}
echo "</ul>";

// 获取用户ID为2的非管理员站点
$user_id = 2;
$non_admin_sites = get_blogs_of_user( $user_id ); // $all 默认为 false

echo "用户ID为 {$user_id} 的非管理员站点:<br>";
echo "<ul>";
foreach ( $non_admin_sites as $blog_id => $blog_url ) {
    echo "<li>站点ID:{$blog_id},站点URL:{$blog_url}</li>";
}
echo "</ul>";
?>

注意事项和坑点

  • 多站点环境下的wp_users表: 在多站点环境中,wp_users表存储的用户数据,其blog_id列指示了用户最初被创建时所在的站点。这并不意味着用户仅属于该站点,用户可能被添加到其他站点。因此,如果你需要获取用户的所有站点,通常需要结合wp_usermeta表的信息。

  • 角色判断的hacky方式: 使用 LIKE '%%s:13:"administrator"%%' 来判断角色是否是管理员,这种方式依赖于角色信息序列化的格式,如果WordPress的角色管理机制发生变化,这段代码可能会失效。更健壮的做法是使用WordPress提供的角色管理API,例如 get_user_meta( $user_id, 'wp_{blog_id}_capabilities', true ) 获取用户的角色,然后判断是否包含 administrator 角色。

  • 缓存问题: 虽然WordPress使用了缓存来提高性能,但在某些情况下,缓存可能会导致数据不一致。例如,如果用户被添加或移除站点后,缓存没有及时更新,那么get_blogs_of_user()可能会返回过时的数据。可以使用 wp_cache_delete() 函数来手动清除缓存。

  • 性能问题: 在多站点环境下,如果站点数量非常多,get_blogs_of_user() 函数可能会执行较慢,因为它需要查询数据库并遍历所有站点。可以考虑使用更高效的查询方式或者自定义的缓存机制来优化性能。

get_blogs_of_user的优化方向

既然说到了性能,我们不妨再深入探讨一下get_blogs_of_user的优化方向。毕竟,任何代码都有改进的空间,对吧?

  1. 更精确的角色判断:

    正如前面提到的,使用LIKE语句判断角色并不靠谱。更稳妥的方式是直接获取用户的角色信息,然后进行判断。可以这样修改:

    if ( ! $all ) {
        $sites = get_sites();
        $ids = array();
        foreach ($sites as $site) {
            $blog_id = $site->blog_id;
            $capabilities = get_user_meta($user_id, 'wp_' . $blog_id . '_capabilities', true);
            if (isset($capabilities['administrator'])) {
                continue; // Skip administrator roles.
            }
            $ids[] = $blog_id;
        }
    }

    这种方式虽然代码稍微多了一些,但是更准确,也更易于维护。

  2. 更高效的缓存策略:

    当前的缓存策略是简单的将结果缓存一段时间。可以考虑使用更精细的缓存策略,例如:

    • 基于事件的缓存失效: 当用户被添加到站点或从站点移除时,清除相关的缓存。可以使用add_user_to_blogremove_user_from_blog钩子来实现。
    • 分层缓存: 将站点信息存储在不同的缓存层级,例如内存缓存(Memcached或Redis)和数据库缓存。
  3. 数据库查询优化:

    如果站点数量非常多,数据库查询可能会成为瓶颈。可以考虑使用更高效的查询方式,例如:

    • 使用JOIN查询:wp_usermeta表和wp_blogs表进行JOIN查询,一次性获取所有需要的信息。
    • 使用索引: 确保wp_usermeta表的user_idmeta_key列上有索引。

总结:掌握利器,纵横WordPress世界

今天我们一起深入剖析了WordPress的get_blogs_of_user()函数,从源码到示例,再到注意事项和优化方向,相信大家对这个函数有了更深入的理解。

记住,理解源码是成为WordPress高手的必经之路。掌握这些利器,你就可以在WordPress的世界里纵横驰骋,解决各种复杂的问题,成为真正的探险家!下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注