一、需求分析与应用场景
在WordPress网站管理中,有时需要限制不同用户组只能浏览或访问特定的分类内容。这种需求常见于:
- 企业内部知识库:不同部门只能查看本部门的文档
- 在线教育平台:学生只能访问自己购买的课程分类
- 多作者博客:作者只能管理自己负责的专栏分类
- 会员制网站:不同会员等级访问不同内容分类
- 多站点协作:合作伙伴只能查看相关分类的内容
二、核心技术实现方案
2.1 基于用户角色的分类权限控制
<?php
/**
* WordPress用户分类访问权限控制核心类
*/
class WP_Category_Access_Control {
private $category_restrictions = array();
public function __construct() {
// 初始化分类权限规则
$this->init_restrictions();
// 添加必要的钩子
add_action('pre_get_posts', array($this, 'filter_query_by_category'));
add_action('template_redirect', array($this, 'check_single_post_access'));
add_filter('get_terms_args', array($this, 'filter_visible_categories'), 10, 2);
add_filter('widget_categories_args', array($this, 'filter_category_widget'));
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_init', array($this, 'register_settings'));
// 添加用户权限检查
add_action('user_register', array($this, 'set_default_category_access'));
add_action('edit_user_profile', array($this, 'add_user_category_access_fields'));
add_action('show_user_profile', array($this, 'add_user_category_access_fields'));
add_action('personal_options_update', array($this, 'save_user_category_access'));
add_action('edit_user_profile_update', array($this, 'save_user_category_access'));
}
/**
* 初始化权限规则配置
*/
private function init_restrictions() {
// 从数据库加载配置
$this->category_restrictions = get_option('category_access_restrictions', array());
// 默认权限规则示例
$default_rules = array(
'subscriber' => array(1, 2), // 订阅者只能访问ID为1,2的分类
'contributor' => array(1, 2, 3),
'author' => array(1, 2, 3, 4),
'editor' => array(), // 空数组表示可以访问所有分类
'administrator' => array(), // 管理员可以访问所有分类
);
// 如果没有配置,使用默认规则
if (empty($this->category_restrictions)) {
update_option('category_access_restrictions', $default_rules);
$this->category_restrictions = $default_rules;
}
}
/**
* 在文章查询时过滤分类
*/
public function filter_query_by_category($query) {
// 不在后台、主查询、非归档页面中应用限制
if (is_admin() || !$query->is_main_query() || !$query->is_archive()) {
return;
}
// 获取当前用户角色
$user = wp_get_current_user();
$allowed_categories = $this->get_allowed_categories_for_user($user);
// 如果用户有访问限制
if (!empty($allowed_categories)) {
if ($query->is_category() || $query->is_home() || $query->is_search()) {
// 设置分类查询参数
$tax_query = array(
array(
'taxonomy' => 'category',
'field' => 'term_id',
'terms' => $allowed_categories,
'operator' => 'IN',
)
);
// 合并现有的tax_query
$existing_tax_query = $query->get('tax_query');
if (!empty($existing_tax_query)) {
$tax_query = array_merge(array('relation' => 'AND'), $existing_tax_query, $tax_query);
}
$query->set('tax_query', $tax_query);
}
}
return $query;
}
/**
* 检查单篇文章访问权限
*/
public function check_single_post_access() {
if (!is_single() && !is_singular()) {
return;
}
global $post;
if (!$post) {
return;
}
$user = wp_get_current_user();
$allowed_categories = $this->get_allowed_categories_for_user($user);
// 如果没有限制,允许访问
if (empty($allowed_categories)) {
return;
}
// 检查文章是否在允许的分类中
$post_categories = wp_get_post_categories($post->ID, array('fields' => 'ids'));
$has_access = false;
foreach ($post_categories as $category_id) {
if (in_array($category_id, $allowed_categories)) {
$has_access = true;
break;
}
}
// 如果没有访问权限,重定向或显示错误
if (!$has_access) {
$this->handle_access_denied();
}
}
/**
* 过滤可见分类
*/
public function filter_visible_categories($args, $taxonomies) {
if (is_admin() || !in_array('category', $taxonomies)) {
return $args;
}
$user = wp_get_current_user();
$allowed_categories = $this->get_allowed_categories_for_user($user);
if (!empty($allowed_categories)) {
$args['include'] = $allowed_categories;
}
return $args;
}
/**
* 过滤分类小工具
*/
public function filter_category_widget($args) {
$user = wp_get_current_user();
$allowed_categories = $this->get_allowed_categories_for_user($user);
if (!empty($allowed_categories)) {
$args['include'] = $allowed_categories;
}
return $args;
}
/**
* 处理无权限访问
*/
private function handle_access_denied() {
$redirect_url = apply_filters('category_access_denied_redirect', home_url());
wp_die(
'<h1>' . __('访问受限', 'textdomain') . '</h1>' .
'<p>' . __('您没有权限查看此内容。', 'textdomain') . '</p>' .
'<p><a href="' . esc_url($redirect_url) . '">' . __('返回首页', 'textdomain') . '</a></p>',
__('访问受限', 'textdomain'),
array('response' => 403)
);
// 或者重定向
// wp_redirect($redirect_url);
// exit;
}
/**
* 获取用户允许访问的分类
*/
private function get_allowed_categories_for_user($user) {
// 管理员可以访问所有分类
if (in_array('administrator', (array) $user->roles)) {
return array();
}
$allowed_categories = array();
// 检查用户角色对应的权限
foreach ($user->roles as $role) {
if (isset($this->category_restrictions[$role])) {
$role_categories = $this->category_restrictions[$role];
if (empty($role_categories)) {
return array(); // 空数组表示可以访问所有
}
$allowed_categories = array_merge($allowed_categories, $role_categories);
}
}
// 检查用户特定的权限覆盖
$user_specific_categories = get_user_meta($user->ID, '_allowed_categories', true);
if (!empty($user_specific_categories)) {
$allowed_categories = $user_specific_categories;
}
return array_unique($allowed_categories);
}
/**
* 添加管理员菜单
*/
public function add_admin_menu() {
add_options_page(
__('分类访问控制', 'textdomain'),
__('分类访问控制', 'textdomain'),
'manage_options',
'category-access-control',
array($this, 'admin_settings_page')
);
}
/**
* 注册设置
*/
public function register_settings() {
register_setting('category_access_control', 'category_access_restrictions');
}
/**
* 管理员设置页面
*/
public function admin_settings_page() {
?>
<div class="wrap">
<h1><?php _e('分类访问控制设置', 'textdomain'); ?></h1>
<form method="post" action="options.php">
<?php settings_fields('category_access_control'); ?>
<table class="form-table">
<tr>
<th scope="row"><?php _e('用户角色权限设置', 'textdomain'); ?></th>
<td>
<p class="description"><?php _e('为空表示可以访问所有分类。可以设置多个分类ID,用逗号分隔。', 'textdomain'); ?></p>
<?php
$roles = wp_roles()->get_names();
$restrictions = get_option('category_access_restrictions', array());
foreach ($roles as $role_slug => $role_name) {
$value = isset($restrictions[$role_slug]) ? implode(',', $restrictions[$role_slug]) : '';
?>
<p>
<label for="role_<?php echo esc_attr($role_slug); ?>">
<strong><?php echo esc_html($role_name); ?>:</strong>
</label><br>
<input type="text"
id="role_<?php echo esc_attr($role_slug); ?>"
name="category_access_restrictions[<?php echo esc_attr($role_slug); ?>]"
value="<?php echo esc_attr($value); ?>"
class="regular-text"
placeholder="<?php esc_attr_e('例如: 1,2,3', 'textdomain'); ?>">
<span class="description"><?php _e('分类ID,用逗号分隔', 'textdomain'); ?></span>
</p>
<?php
}
?>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
</div>
<?php
}
/**
* 用户注册时设置默认分类权限
*/
public function set_default_category_access($user_id) {
$user = get_userdata($user_id);
if ($user && !empty($user->roles)) {
$role = $user->roles[0];
$restrictions = get_option('category_access_restrictions', array());
if (isset($restrictions[$role])) {
update_user_meta($user_id, '_allowed_categories', $restrictions[$role]);
}
}
}
/**
* 在用户资料页面添加分类访问字段
*/
public function add_user_category_access_fields($user) {
if (!current_user_can('edit_users')) {
return;
}
?>
<h3><?php _e('分类访问权限', 'textdomain'); ?></h3>
<table class="form-table">
<tr>
<th><label for="allowed_categories"><?php _e('允许访问的分类', 'textdomain'); ?></label></th>
<td>
<?php
$allowed_categories = get_user_meta($user->ID, '_allowed_categories', true);
$category_ids = is_array($allowed_categories) ? implode(',', $allowed_categories) : $allowed_categories;
?>
<input type="text"
name="allowed_categories"
id="allowed_categories"
value="<?php echo esc_attr($category_ids); ?>"
class="regular-text"
placeholder="<?php esc_attr_e('例如: 1,2,3 (空表示使用角色默认设置)', 'textdomain'); ?>">
<p class="description"><?php _e('分类ID,用逗号分隔。此设置会覆盖角色默认设置。', 'textdomain'); ?></p>
<div style="margin-top: 10px;">
<strong><?php _e('可用分类:', 'textdomain'); ?></strong><br>
<?php
$categories = get_categories(array('hide_empty' => false));
foreach ($categories as $category) {
echo '<span style="display: inline-block; margin: 5px; padding: 5px; background: #f5f5f5; border-radius: 3px;">';
echo esc_html($category->name) . ' (ID: ' . $category->term_id . ')';
echo '</span> ';
}
?>
</div>
</td>
</tr>
</table>
<?php
}
/**
* 保存用户分类访问设置
*/
public function save_user_category_access($user_id) {
if (!current_user_can('edit_user', $user_id)) {
return false;
}
if (isset($_POST['allowed_categories'])) {
$categories = sanitize_text_field($_POST['allowed_categories']);
$category_ids = array();
if (!empty($categories)) {
$category_ids = array_map('intval', explode(',', $categories));
$category_ids = array_unique(array_filter($category_ids));
}
update_user_meta($user_id, '_allowed_categories', $category_ids);
}
}
}
// 初始化
new WP_Category_Access_Control();
?>
2.2 基于用户元数据的细粒度控制
<?php
/**
* WordPress用户分类权限扩展功能
*/
class WP_User_Category_Permissions {
public function __construct() {
add_action('init', array($this, 'register_user_category_meta'));
add_action('pre_get_posts', array($this, 'filter_posts_by_user_categories'));
add_action('template_redirect', array($this, 'check_post_access'));
add_filter('get_terms_args', array($this, 'filter_user_categories'), 10, 2);
add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_scripts'));
}
/**
* 注册用户分类元数据
*/
public function register_user_category_meta() {
register_meta('user', '_user_allowed_categories', array(
'type' => 'array',
'description' => '用户允许访问的分类ID列表',
'single' => true,
'show_in_rest' => true,
'default' => array(),
));
}
/**
* 过滤用户可访问的分类
*/
public function filter_user_categories($args, $taxonomies) {
if (is_admin() || !in_array('category', $taxonomies) || current_user_can('manage_options')) {
return $args;
}
$user_id = get_current_user_id();
if (!$user_id) {
// 未登录用户可以看到所有分类
return $args;
}
$allowed_categories = get_user_meta($user_id, '_user_allowed_categories', true);
if (!empty($allowed_categories) && is_array($allowed_categories)) {
$args['include'] = $allowed_categories;
}
return $args;
}
/**
* 检查文章访问权限
*/
public function check_post_access() {
if (!is_single() && !is_singular()) {
return;
}
$post_id = get_the_ID();
if (!$post_id) {
return;
}
$user_id = get_current_user_id();
if (current_user_can('manage_options')) {
return; // 管理员可以访问所有内容
}
$post_categories = wp_get_post_categories($post_id, array('fields' => 'ids'));
if (empty($post_categories)) {
return; // 文章没有分类,允许访问
}
$allowed_categories = get_user_meta($user_id, '_user_allowed_categories', true);
if (empty($allowed_categories)) {
$this->redirect_no_access();
}
$has_access = false;
foreach ($post_categories as $category_id) {
if (in_array($category_id, $allowed_categories)) {
$has_access = true;
break;
}
}
if (!$has_access) {
$this->redirect_no_access();
}
}
/**
* 处理无权限访问的重定向
*/
private function redirect_no_access() {
$redirect_to = apply_filters('user_category_access_denied_redirect', home_url('/access-denied/'));
wp_redirect($redirect_to);
exit;
}
/**
* 前端脚本
*/
public function enqueue_frontend_scripts() {
if (is_user_logged_in() && !is_admin()) {
wp_enqueue_script(
'user-category-filter',
plugins_url('js/user-category-filter.js', __FILE__),
array('jquery'),
'1.0.0',
true
);
wp_localize_script('user-category-filter', 'categoryAccess', array(
'ajax_url' => admin_url('admin-ajax.php'),
'user_id' => get_current_user_id(),
'nonce' => wp_create_nonce('user_category_access_nonce')
));
}
}
}
三、用户界面实现
3.1 用户权限管理界面
<?php
/**
* WordPress用户分类权限管理界面
*/
class WP_User_Category_Admin {
public function __construct() {
add_action('admin_menu', array($this, 'add_admin_pages'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
add_action('wp_ajax_save_user_categories', array($this, 'ajax_save_user_categories'));
add_action('wp_ajax_get_user_categories', array($this, 'ajax_get_user_categories'));
}
/**
* 添加管理员页面
*/
public function add_admin_pages() {
add_users_page(
__('用户分类权限', 'textdomain'),
__('分类权限', 'textdomain'),
'manage_options',
'user-category-permissions',
array($this, 'render_admin_page')
);
add_submenu_page(
null,
__('用户分类权限设置', 'textdomain'),
'',
'manage_options',
'user-category-settings',
array($this, 'render_user_settings_page')
);
}
/**
* 管理员主页面
*/
public function render_admin_page() {
if (!current_user_can('manage_options')) {
wp_die(__('您没有权限访问此页面。', 'textdomain'));
}
$users = get_users(array(
'role__not_in' => array('administrator'),
'orderby' => 'display_name',
'order' => 'ASC',
));
$categories = get_categories(array(
'hide_empty' => false,
'orderby' => 'name',
'order' => 'ASC',
));
?>
<div class="wrap">
<h1><?php _e('用户分类权限管理', 'textdomain'); ?></h1>
<div class="user-category-permissions">
<div class="user-list-container">
<h2><?php _e('用户列表', 'textdomain'); ?></h2>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th><?php _e('用户', 'textdomain'); ?></th>
<th><?php _e('角色', 'textdomain'); ?></th>
<th><?php _e('允许的分类数', 'textdomain'); ?></th>
<th><?php _e('操作', 'textdomain'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<?php
$allowed_categories = get_user_meta($user->ID, '_user_allowed_categories', true);
$category_count = is_array($allowed_categories) ? count($allowed_categories) : 0;
$user_roles = implode(', ', $user->roles);
?>
<tr>
<td>
<strong>
<a href="<?php echo get_edit_user_link($user->ID); ?>">
<?php echo esc_html($user->display_name); ?>
</a>
</strong>
<br>
<small><?php echo esc_html($user->user_email); ?></small>
</td>
<td><?php echo esc_html($user_roles); ?></td>
<td>
<span class="category-count"><?php echo $category_count; ?></span>
<?php if ($category_count > 0): ?>
<div class="category-list-preview">
<?php
$category_names = array();
foreach ($allowed_categories as $cat_id) {
$cat = get_category($cat_id);
if ($cat) {
$category_names[] = $cat->name;
}
}
echo '<small>' . implode(', ', $category_names) . '</small>';
?>
</div>
<?php endif; ?>
</td>
<td>
<button class="button edit-user-categories"
data-user-id="<?php echo esc_attr($user->ID); ?>"
data-user-name="<?php echo esc_attr($user->display_name); ?>">
<?php _e('编辑权限', 'textdomain'); ?>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="category-list-container" style="margin-top: 30px;">
<h2><?php _e('可用分类', 'textdomain'); ?></h2>
<div class="category-tags">
<?php foreach ($categories as $category): ?>
<span class="category-tag" data-category-id="<?php echo esc_attr($category->term_id); ?>">
<?php echo esc_html($category->name); ?>
<small>(ID: <?php echo $category->term_id; ?>)</small>
</span>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<!-- 编辑模态框 -->
<div id="edit-categories-modal" class="hidden">
<div class="modal-content">
<h2><?php _e('编辑用户分类权限', 'textdomain'); ?></h2>
<p class="user-info"></p>
<div class="category-selector">
<h3><?php _e('选择允许访问的分类', 'textdomain'); ?></h3>
<div class="category-checkboxes">
<?php foreach ($categories as $category): ?>
<label class="category-checkbox-label">
<input type="checkbox"
name="allowed_categories[]"
value="<?php echo esc_attr($category->term_id); ?>"
class="category-checkbox">
<?php echo esc_html($category->name); ?>
<small>(ID: <?php echo $category->term_id; ?>)</small>
</label><br>
<?php endforeach; ?>
</div>
</div>
<div class="modal-actions">
<button type="button" class="button button-primary save-categories">
<?php _e('保存设置', 'textdomain'); ?>
</button>
<button type="button" class="button cancel-modal">
<?php _e('取消', 'textdomain'); ?>
</button>
<span class="spinner"></span>
</div>
</div>
</div>
<style>
.user-category-permissions {
margin-top: 20px;
}
.category-tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
}
.category-tag {
background: #f0f0f1;
border: 1px solid #c3c4c7;
border-radius: 3px;
padding: 5px 10px;
font-size: 12px;
}
.category-list-preview {
margin-top: 5px;
color: #666;
}
#edit-categories-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 99999;
}
#edit-categories-modal .modal-content {
background: white;
padding: 20px;
border-radius: 5px;
max-width: 600px;
max-height: 80vh;
overflow-y: auto;
}
.category-checkboxes {
max-height: 300px;
overflow-y: auto;
border: 1px solid #ddd;
padding: 10px;
margin: 10px 0;
}
.category-checkbox-label {
display: block;
padding: 5px 0;
}
.modal-actions {
margin-top: 20px;
display: flex;
gap: 10px;
align-items: center;
}
</style>
<script>
jQuery(document).ready(function($) {
var currentUserId = null;
// 打开编辑模态框
$('.edit-user-categories').on('click', function() {
var userId = $(this).data('user-id');
var userName = $(this).data('user-name');
currentUserId = userId;
// 显示用户信息
$('.user-info').text('用户: ' + userName);
// 获取用户当前的分类权限
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'get_user_categories',
user_id: userId,
nonce: '<?php echo wp_create_nonce('user_category_nonce'); ?>'
},
beforeSend: function() {
$('.spinner').addClass('is-active');
},
success: function(response) {
if (response.success) {
// 清空所有复选框
$('.category-checkbox').prop('checked', false);
// 设置已选中的分类
$.each(response.data.categories, function(index, categoryId) {
$('.category-checkbox[value="' + categoryId + '"]').prop('checked', true);
});
// 显示模态框
$('#edit-categories-modal').removeClass('hidden');
}
},
complete: function() {
$('.spinner').removeClass('is-active');
}
});
});
// 保存分类权限
$('.save-categories').on('click', function() {
var selectedCategories = [];
$('.category-checkbox:checked').each(function() {
selectedCategories.push($(this).val());
});
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'save_user_categories',
user_id: currentUserId,
categories: selectedCategories,
nonce: '<?php echo wp_create_nonce('user_category_nonce'); ?>'
},
beforeSend: function() {
$('.spinner').addClass('is-active');
},
success: function(response) {
if (response.success) {
alert('保存成功!');
location.reload();
} else {
alert('保存失败: ' + response.data.message);
}
},
complete: function() {
$('.spinner').removeClass('is-active');
}
});
});
// 关闭模态框
$('.cancel-modal').on('click', function() {
$('#edit-categories-modal').addClass('hidden');
});
// 点击背景关闭
$('#edit-categories-modal').on('click', function(e) {
if (e.target === this) {
$(this).addClass('hidden');
}
});
});
</script>
<?php
}
/**
* AJAX获取用户分类权限
*/
public function ajax_get_user_categories() {
check_ajax_referer('user_category_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('无权限操作');
}
$user_id = intval($_POST['user_id']);
$categories = get_user_meta($user_id, '_user_allowed_categories', true);
if (!is_array($categories)) {
$categories = array();
}
wp_send_json_success(array(
'categories' => $categories
));
}
/**
* AJAX保存用户分类权限
*/
public function ajax_save_user_categories() {
check_ajax_referer('user_category_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(array(
'message' => '无权限操作'
));
}
$user_id = intval($_POST['user_id']);
$categories = isset($_POST['categories']) ? array_map('intval', $_POST['categories']) : array();
update_user_meta($user_id, '_user_allowed_categories', $categories);
wp_send_json_success(array(
'message' => '保存成功'
));
}
}
四、短代码与小工具
4.1 用户专属分类列表短代码
<?php
/**
* 短代码和小工具扩展
*/
class WP_User_Category_Shortcodes {
public function __construct() {
add_shortcode('user_categories', array($this, 'user_categories_shortcode'));
add_action('widgets_init', array($this, 'register_widgets'));
}
/**
* 用户专属分类列表短代码
*/
public function user_categories_shortcode($atts) {
$atts = shortcode_atts(array(
'show_count' => true,
'show_empty' => false,
'orderby' => 'name',
'order' => 'ASC',
'style' => 'list', // list, dropdown, grid
'hide_if_empty' => false,
), $atts, 'user_categories');
$user_id = get_current_user_id();
if (!$user_id) {
return '<p>' . __('请登录查看分类。', 'textdomain') . '</p>';
}
$allowed_categories = get_user_meta($user_id, '_user_allowed_categories', true);
if (empty($allowed_categories) && $atts['hide_if_empty']) {
return '';
}
$args = array(
'taxonomy' => 'category',
'orderby' => $atts['orderby'],
'order' => $atts['order'],
'hide_empty' => !$atts['show_empty'],
);
if (!empty($allowed_categories)) {
$args['include'] = $allowed_categories;
}
$categories = get_categories($args);
if (empty($categories)) {
return '<p>' . __('没有可用的分类。', 'textdomain') . '</p>';
}
ob_start();
switch ($atts['style']) {
case 'list':
echo '<ul class="user-category-list">';
foreach ($categories as $category) {
$count = $atts['show_count'] ? ' (' . $category->count . ')' : '';
echo '<li>';
echo '<a href="' . esc_url(get_category_link($category)) . '">';
echo esc_html($category->name) . $count;
echo '</a>';
echo '</li>';
}
echo '</ul>';
break;
case 'dropdown':
echo '<select class="user-category-dropdown" onchange="if(this.value) window.location.href=this.value">';
echo '<option value="">' . __('选择分类', 'textdomain') . '</option>';
foreach ($categories as $category) {
$count = $atts['show_count'] ? ' (' . $category->count . ')' : '';
echo '<option value="' . esc_url(get_category_link($category)) . '">';
echo esc_html($category->name) . $count;
echo '</option>';
}
echo '</select>';
break;
case 'grid':
echo '<div class="user-category-grid">';
foreach ($categories as $category) {
echo '<div class="category-grid-item">';
echo '<a href="' . esc_url(get_category_link($category)) . '">';
echo '<h4>' . esc_html($category->name) . '</h4>';
if ($atts['show_count']) {
echo '<span class="category-count">' . $category->count . '篇文章</span>';
}
if ($category->description) {
echo '<p class="category-description">' . esc_html($category->description) . '</p>';
}
echo '</a>';
echo '</div>';
}
echo '</div>';
break;
}
return ob_get_clean();
}
/**
* 注册小工具
*/
public function register_widgets() {
register_widget('WP_User_Categories_Widget');
}
}
/**
* 用户分类小工具
*/
class WP_User_Categories_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'wp_user_categories',
__('用户分类列表', 'textdomain'),
array(
'description' => __('显示当前用户有权限访问的分类列表', 'textdomain')
)
);
}
public function widget($args, $instance) {
if (!is_user_logged_in()) {
return;
}
$title = !empty($instance['title']) ? apply_filters('widget_title', $instance['title']) : '';
$style = !empty($instance['style']) ? $instance['style'] : 'list';
$show_count = !empty($instance['show_count']);
$show_empty = !empty($instance['show_empty']);
$user_id = get_current_user_id();
$allowed_categories = get_user_meta($user_id, '_user_allowed_categories', true);
if (empty($allowed_categories)) {
return;
}
$categories = get_categories(array(
'include' => $allowed_categories,
'hide_empty' => !$show_empty,
));
if (empty($categories)) {
return;
}
echo $args['before_widget'];
if ($title) {
echo $args['before_title'] . $title . $args['after_title'];
}
switch ($style) {
case 'list':
echo '<ul>';
foreach ($categories as $category) {
$count = $show_count ? ' <span class="count">(' . $category->count . ')</span>' : '';
echo '<li><a href="' . get_category_link($category) . '">' .
esc_html($category->name) . $count . '</a></li>';
}
echo '</ul>';
break;
case 'dropdown':
echo '<select onchange="if(this.value) window.location.href=this.value">';
echo '<option value="">' . __('选择分类', 'textdomain') . '</option>';
foreach ($categories as $category) {
$count = $show_count ? ' (' . $category->count . ')' : '';
echo '<option value="' . get_category_link($category) . '">' .
esc_html($category->name) . $count . '</option>';
}
echo '</select>';
break;
}
echo $args['after_widget'];
}
public function form($instance) {
$title = !empty($instance['title']) ? $instance['title'] : __('我的分类', 'textdomain');
$style = !empty($instance['style']) ? $instance['style'] : 'list';
$show_count = isset($instance['show_count']) ? (bool) $instance['show_count'] : true;
$show_empty = isset($instance['show_empty']) ? (bool) $instance['show_empty'] : false;
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>">标题:</label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
name="<?php echo $this->get_field_name('title'); ?>"
type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('style'); ?>">显示样式:</label>
<select class="widefat" id="<?php echo $this->get_field_id('style'); ?>"
name="<?php echo $this->get_field_name('style'); ?>">
<option value="list" <?php selected($style, 'list'); ?>>列表</option>
<option value="dropdown" <?php selected($style, 'dropdown'); ?>>下拉菜单</option>
</select>
</p>
<p>
<input class="checkbox" type="checkbox"
id="<?php echo $this->get_field_id('show_count'); ?>"
name="<?php echo $this->get_field_name('show_count'); ?>"
<?php checked($show_count); ?>>
<label for="<?php echo $this->get_field_id('show_count'); ?>">显示文章数量</label>
</p>
<p>
<input class="checkbox" type="checkbox"
id="<?php echo $this->get_field_id('show_empty'); ?>"
name="<?php echo $this->get_field_name('show_empty'); ?>"
<?php checked($show_empty); ?>>
<label for="<?php echo $this->get_field_id('show_empty'); ?>">显示空分类</label>
</p>
<?php
}
public function update($new_instance, $old_instance) {
$instance = array();
$instance['title'] = !empty($new_instance['title']) ? strip_tags($new_instance['title']) : '';
$instance['style'] = !empty($new_instance['style']) ? $new_instance['style'] : 'list';
$instance['show_count'] = isset($new_instance['show_count']) ? (bool) $new_instance['show_count'] : false;
$instance['show_empty'] = isset($new_instance['show_empty']) ? (bool) $new_instance['show_empty'] : false;
return $instance;
}
}
五、高级功能扩展
5.1 分类权限的层级继承
<?php
/**
* 分类层级权限继承
*/
class WP_Category_Hierarchy_Access {
public function __construct() {
add_filter('user_allowed_categories', array($this, 'include_child_categories'), 10, 2);
add_action('save_post', array($this, 'check_post_category_access'), 10, 3);
}
/**
* 在用户允许的分类中包含子分类
*/
public function include_child_categories($category_ids, $user_id) {
if (empty($category_ids)) {
return $category_ids;
}
$all_categories = array();
foreach ($category_ids as $category_id) {
// 获取分类及其所有子分类
$children = get_term_children($category_id, 'category');
if (!is_wp_error($children)) {
$all_categories = array_merge($all_categories, $children);
}
$all_categories[] = $category_id;
}
return array_unique($all_categories);
}
/**
* 检查文章分类权限
*/
public function check_post_category_access($post_id, $post, $update) {
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
if (!current_user_can('edit_post', $post_id)) {
return;
}
// 只处理已发布文章
if ($post->post_status !== 'publish') {
return;
}
$post_categories = wp_get_post_categories($post_id, array('fields' => 'ids'));
if (empty($post_categories)) {
return;
}
$user_id = get_current_user_id();
$allowed_categories = get_user_meta($user_id, '_user_allowed_categories', true);
if (empty($allowed_categories)) {
return; // 用户没有限制
}
// 检查用户是否有权限发布到这些分类
$has_permission = false;
foreach ($post_categories as $category_id) {
if (in_array($category_id, $allowed_categories)) {
$has_permission = true;
break;
}
}
if (!$has_permission && !current_user_can('manage_options')) {
// 移除非权限分类
$allowed_post_categories = array_intersect($post_categories, $allowed_categories);
if (empty($allowed_post_categories)) {
// 如果没有权限的分类,设置默认分类
$default_category = get_option('default_category');
wp_set_post_categories($post_id, array($default_category));
// 添加管理员通知
update_post_meta($post_id, '_category_access_warning',
sprintf('文章已被移动到默认分类,您没有权限发布到选择的分类。'));
} else {
wp_set_post_categories($post_id, $allowed_post_categories);
}
}
}
}
5.2 REST API支持
<?php
/**
* WordPress分类权限REST API扩展
*/
class WP_Category_Access_REST_API {
public function __construct() {
add_action('rest_api_init', array($this, 'register_rest_routes'));
add_filter('rest_prepare_category', array($this, 'filter_rest_category_response'), 10, 3);
add_filter('rest_prepare_post', array($this, 'filter_rest_post_response'), 10, 3);
}
/**
* 注册REST API路由
*/
public function register_rest_routes() {
// 获取用户分类权限
register_rest_route('category-access/v1', '/user-categories/(?P<id>\d+)', array(
'methods' => 'GET',
'callback' => array($this, 'get_user_categories_rest'),
'permission_callback' => array($this, 'check_rest_permissions'),
));
// 设置用户分类权限
register_rest_route('category-access/v1', '/user-categories/(?P<id>\d+)', array(
'methods' => 'POST',
'callback' => array($this, 'set_user_categories_rest'),
'permission_callback' => array($this, 'check_rest_permissions'),
));
// 获取有权限的分类列表
register_rest_route('category-access/v1', '/allowed-categories', array(
'methods' => 'GET',
'callback' => array($this, 'get_allowed_categories_rest'),
'permission_callback' => function() {
return is_user_logged_in();
},
));
}
/**
* 权限检查
*/
public function check_rest_permissions($request) {
$user_id = $request->get_param('id');
$current_user_id = get_current_user_id();
// 用户只能查看/修改自己的权限,管理员可以查看/修改所有用户
if ($current_user_id == $user_id || current_user_can('manage_options')) {
return true;
}
return new WP_Error('rest_forbidden', __('您没有权限执行此操作。', 'textdomain'), array('status' => 403));
}
/**
* 获取用户分类权限
*/
public function get_user_categories_rest($request) {
$user_id = $request->get_param('id');
$categories = get_user_meta($user_id, '_user_allowed_categories', true);
if (!is_array($categories)) {
$categories = array();
}
return rest_ensure_response(array(
'user_id' => $user_id,
'allowed_categories' => $categories,
'categories_data' => $this->get_categories_data($categories),
));
}
/**
* 设置用户分类权限
*/
public function set_user_categories_rest($request) {
$user_id = $request->get_param('id');
$categories = $request->get_param('categories');
if (!is_array($categories)) {
return new WP_Error('invalid_categories', __('分类数据格式不正确。', 'textdomain'), array('status' => 400));
}
$categories = array_map('intval', $categories);
update_user_meta($user_id, '_user_allowed_categories', $categories);
return rest_ensure_response(array(
'success' => true,
'message' => __('分类权限已更新。', 'textdomain'),
'allowed_categories' => $categories,
));
}
/**
* 获取当前用户有权限的分类
*/
public function get_allowed_categories_rest() {
$user_id = get_current_user_id();
$category_ids = get_user_meta($user_id, '_user_allowed_categories', true);
if (empty($category_ids)) {
// 如果没有限制,返回所有分类
$categories = get_categories(array('hide_empty' => false));
} else {
$categories = get_categories(array(
'include' => $category_ids,
'hide_empty' => false,
));
}
$formatted_categories = array();
foreach ($categories as $category) {
$formatted_categories[] = array(
'id' => $category->term_id,
'name' => $category->name,
'slug' => $category->slug,
'count' => $category->count,
'description' => $category->description,
'link' => get_category_link($category),
);
}
return rest_ensure_response(array(
'user_id' => $user_id,
'categories' => $formatted_categories,
));
}
/**
* 过滤分类REST响应
*/
public function filter_rest_category_response($response, $category, $request) {
$user_id = get_current_user_id();
if (current_user_can('manage_options')) {
return $response;
}
$allowed_categories = get_user_meta($user_id, '_user_allowed_categories', true);
if (!empty($allowed_categories) && !in_array($category->term_id, $allowed_categories)) {
// 用户没有权限查看此分类
return new WP_Error('rest_forbidden', __('您没有权限查看此分类。', 'textdomain'), array('status' => 403));
}
return $response;
}
/**
* 过滤文章REST响应
*/
public function filter_rest_post_response($response, $post, $request) {
$user_id = get_current_user_id();
if (current_user_can('manage_options')) {
return $response;
}
$post_categories = wp_get_post_categories($post->ID, array('fields' => 'ids'));
$allowed_categories = get_user_meta($user_id, '_user_allowed_categories', true);
if (empty($allowed_categories)) {
return $response; // 用户没有限制
}
$has_access = false;
foreach ($post_categories as $category_id) {
if (in_array($category_id, $allowed_categories)) {
$has_access = true;
break;
}
}
if (!$has_access) {
return new WP_Error('rest_forbidden', __('您没有权限查看此文章。', 'textdomain'), array('status' => 403));
}
return $response;
}
/**
* 获取分类数据
*/
private function get_categories_data($category_ids) {
if (empty($category_ids)) {
return array();
}
$categories = get_categories(array(
'include' => $category_ids,
'hide_empty' => false,
));
$data = array();
foreach ($categories as $category) {
$data[] = array(
'id' => $category->term_id,
'name' => $category->name,
'slug' => $category->slug,
'description' => $category->description,
'count' => $category->count,
);
}
return $data;
}
}
六、性能优化与缓存策略
<?php
/**
* 分类权限缓存优化
*/
class WP_Category_Access_Cache {
private $cache_group = 'category_access';
public function __construct() {
add_action('set_user_role', array($this, 'clear_user_cache'), 10, 3);
add_action('profile_update', array($this, 'clear_user_cache_on_profile_update'), 10, 2);
add_action('updated_user_meta', array($this, 'clear_user_cache_on_meta_update'), 10, 4);
add_action('deleted_user_meta', array($this, 'clear_user_cache_on_meta_update'), 10, 4);
}
/**
* 获取缓存的用户分类权限
*/
public function get_cached_user_categories($user_id) {
$cache_key = "user_categories_{$user_id}";
$categories = wp_cache_get($cache_key, $this->cache_group);
if (false === $categories) {
$categories = get_user_meta($user_id, '_user_allowed_categories', true);
if (!is_array($categories)) {
$categories = array();
}
// 包括子分类
$categories = $this->include_child_categories($categories);
wp_cache_set($cache_key, $categories, $this->cache_group, 12 * HOUR_IN_SECONDS);
}
return $categories;
}
/**
* 获取缓存的用户可访问文章查询
*/
public function get_cached_user_posts_query($user_id, $query_args = array()) {
$cache_key = md5("user_posts_{$user_id}_" . serialize($query_args));
$posts = wp_cache_get($cache_key, $this->cache_group);
if (false === $posts) {
$allowed_categories = $this->get_cached_user_categories($user_id);
if (!empty($allowed_categories)) {
$query_args['tax_query'] = array(
array(
'taxonomy' => 'category',
'field' => 'term_id',
'terms' => $allowed_categories,
'operator' => 'IN',
),
);
}
$posts = new WP_Query($query_args);
wp_cache_set($cache_key, $posts, $this->cache_group, 1 * HOUR_IN_SECONDS);
}
return $posts;
}
/**
* 清理用户缓存
*/
public function clear_user_cache($user_id, $role, $old_roles) {
$this->clear_specific_user_cache($user_id);
}
public function clear_user_cache_on_profile_update($user_id, $old_user_data) {
$this->clear_specific_user_cache($user_id);
}
public function clear_user_cache_on_meta_update($meta_id, $user_id, $meta_key, $meta_value) {
if ($meta_key === '_user_allowed_categories') {
$this->clear_specific_user_cache($user_id);
}
}
/**
* 清理特定用户的缓存
*/
private function clear_specific_user_cache($user_id) {
$cache_key = "user_categories_{$user_id}";
wp_cache_delete($cache_key, $this->cache_group);
// 清理相关的文章查询缓存
$pattern = "user_posts_{$user_id}_*";
if (function_exists('wp_cache_delete_group')) {
wp_cache_delete_group($this->cache_group);
}
}
/**
* 包含子分类
*/
private function include_child_categories($category_ids) {
if (empty($category_ids)) {
return array();
}
$all_categories = array();
foreach ($category_ids as $category_id) {
$children = get_term_children($category_id, 'category');
if (!is_wp_error($children)) {
$all_categories = array_merge($all_categories, $children);
}
$all_categories[] = $category_id;
}
return array_unique($all_categories);
}
}
七、完整实现示例
<?php
/**
* WordPress分类访问限制插件主文件
* Plugin Name: User Category Access Control
* Plugin URI: https://example.com/user-category-access
* Description: 限制用户只能浏览指定分类的文章
* Version: 1.0.0
* Author: Your Name
* License: GPL v2 or later
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('UCAC_VERSION', '1.0.0');
define('UCAC_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('UCAC_PLUGIN_URL', plugin_dir_url(__FILE__));
// 自动加载类文件
spl_autoload_register(function($class) {
$prefix = 'UCAC_';
$base_dir = UCAC_PLUGIN_DIR . 'includes/';
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
$relative_class = substr($class, $len);
$file = $base_dir . str_replace('_', '/', $relative_class) . '.php';
if (file_exists($file)) {
require $file;
}
});
// 初始化插件
add_action('plugins_loaded', 'ucac_init');
function ucac_init() {
// 主控制器
$main = new UCAC_Main_Controller();
$main->init();
}
// 激活钩子
register_activation_hook(__FILE__, 'ucac_activate');
function ucac_activate() {
require_once UCAC_PLUGIN_DIR . 'includes/class-activator.php';
UCAC_Activator::activate();
}
// 停用钩子
register_deactivation_hook(__FILE__, 'ucac_deactivate');
function ucac_deactivate() {
require_once UCAC_PLUGIN_DIR . 'includes/class-deactivator.php';
UCAC_Deactivator::deactivate();
}
八、最佳实践与注意事项
8.1 安全考虑
- 输入验证:所有用户输入必须经过验证和清理
- 权限检查:确保只有授权用户可以修改设置
- SQL注入防护:使用WordPress提供的数据库函数
- XSS防护:输出时使用适当的转义函数
8.2 性能优化
- 缓存策略:合理使用对象缓存
- 数据库索引:确保相关字段有适当的索引
- 查询优化:避免N+1查询问题
- 懒加载:只在需要时加载用户权限数据
8.3 用户体验
- 清晰的错误提示:告诉用户为什么无法访问
- 友好的界面:管理员界面要直观易用
- 批量操作:支持批量设置用户权限
- 导入导出:支持权限设置的导入导出
8.4 兼容性考虑
- 多站点支持:确保在多站点环境下正常工作
- 缓存插件兼容:与常见缓存插件兼容
- 其他插件兼容:避免与其他插件冲突
- 主题兼容:确保与各种主题兼容
九、总结
通过本文的详细讲解,你应该已经掌握了在WordPress中实现用户分类访问限制的完整方案。从基础的用户角色权限控制,到细粒度的用户级别权限管理,再到高级功能如REST API支持、缓存优化等,这些技术可以灵活组合使用,满足不同场景的需求。
关键点总结:
- 核心逻辑:通过
pre_get_posts过滤查询,template_redirect检查单篇文章访问 - 权限存储:使用用户元数据存储分类权限
- 管理界面:提供友好的管理界面
- 性能优化:合理使用缓存机制
- 扩展性:支持REST API和短代码
根据你的具体需求,可以选择合适的实现方案,或者将这些技术组合使用,打造出符合你网站需求的用户分类访问控制系统。


湘公网安备43020002000238