Как создать пользовательскую роль с определенными возможностями?
Я хочу создать пользовательскую возможность для доступа к интерфейсу моего плагина.
- Должен ли плагин управлять добавлением этой возможности ко всем учетным записям администратора при активации?
- Если да: Управляет ли WordPress добавлением возможности всем администраторам дочерних блогов и суперадминистраторам в мультисайтовых установках, или эта функция должна обрабатываться плагином?
Для плагина, над которым я сейчас работаю, мне нужно было предоставить/ограничить доступ к настройкам плагина (т.е. к соответствующим страницам меню админки) на основе ролей пользователей.
Для этого мне потребовалось добавить новую плагино-специфичную capability
(возможность) к user roles
(ролям пользователей).
К сожалению, ответ kaiser больше не работает, поэтому я потратил время на поиск решения, позволяющего реализовать описанную функциональность.
План действий
Прежде чем поделиться кодом, вот краткое описание того, что он делает:
- При активации плагина добавляем новую возможность
THE_NEW_CAP
для ролей, у которых уже есть встроенная возможностьBUILT_IN_CAP
(в моем случае —edit_pages
). - При каждой загрузке страницы повторяем пункт 1 (т.е. снова добавляем возможность). Это необходимо только в том случае, если нужно учитывать новые роли, созданные после активации плагина. Такие роли не будут иметь плагино-специфичной возможности, даже если у них есть требуемая встроенная.
- Используем новую возможность по своему усмотрению. В моём случае — для управления доступом к страницам меню плагина в админке. Пример кода ниже.
- При деактивации плагина удаляем эту возможность. Конечно, можно сделать это и при удалении плагина. В любом случае, это нужно сделать.
Код
А вот реализация описанного выше:
» Настройка
class WPSE35165Plugin {
public function __construct() {
// Регистрируем хуки
register_activation_hook(__FILE__, array(__CLASS__, 'activation'));
register_deactivation_hook(__FILE__, array(__CLASS__, 'deactivation'));
// Добавляем действия
add_action('admin_menu', array(__CLASS__, 'admin_menu'));
}
public function activation() {
self::add_cap();
}
// Добавляем новую возможность всем ролям, у которых есть определённая встроенная возможность
private static function add_cap() {
$roles = get_editable_roles();
foreach ($GLOBALS['wp_roles']->role_objects as $key => $role) {
if (isset($roles[$key]) && $role->has_cap('BUILT_IN_CAP')) {
$role->add_cap('THE_NEW_CAP');
}
}
}
» Использование
// Добавляем страницы плагина в меню админки
public function admin_menu() {
// Удалите следующую строку, если вас не интересуют новые роли,
// созданные после активации плагина
self::add_cap();
// Настраиваем меню админки плагина
add_menu_page('Меню', 'Меню', 'THE_NEW_CAP', …);
add_submenu_page('wpse35165', 'Подменю', 'Подменю', 'THE_NEW_CAP', ...);
}
» Очистка
public function deactivation() {
self::remove_cap();
}
// Удаляем плагино-специфичную возможность
private static function remove_cap() {
$roles = get_editable_roles();
foreach ($GLOBALS['wp_roles']->role_objects as $key => $role) {
if (isset($roles[$key]) && $role->has_cap('THE_NEW_CAP')) {
$role->remove_cap('THE_NEW_CAP');
}
}
}
}
Примечание: Пожалуйста, не используйте возможности в верхнем регистре. Это сделано только для удобства чтения.

Всегда используйте get_editable_roles()
для получения ролей, которые вы хотите редактировать. В противном случае вы можете сломать работу плагинов.

@toscho Ну ладно, видимо, это одна из тех функций, о которых даже Кодекс не знает... ;) Конечно, эта функция имеет право на существование, но в моем случае я не вижу, как использование глобального массива WP_Roles может сломать какие-либо плагины.

Удаляйте то, что добавляете
Во-первых, убедитесь, что всё, что вы добавляете при активации, также удаляется при деактивации. У меня есть краткое руководство с примером кода для вас.
Тестируем с небольшим плагином:
Я не очень разбираюсь в MU, но насколько я понимаю, объект ролей является глобальным для всех блогов. Просто попробуйте этот небольшой плагин и посмотрите, что получится:
<?php
/*
Plugin Name: MU Roles check
Plugin URI: https://github.com/franz-josef-kaiser/
Description: Проверка ролей при просмотре блога
Author: Franz Josef Kaiser
Author URI: https://plus.google.com/u/0/107110219316412982437
Version: 0.1
Text Domain: murc
License: GPL v2 - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
/**
* Показывает данные блога и названия ролей в этом блоге
* Также показывает, была ли кастомная возможность успешно добавлена, или отображает n/a для роли
*
* @return void
*/
function wpse35165_role_check()
{
$blog = get_current_site();
$custom_cap = 'name_of_your_custom_capability';
$html = "<hr /><table>";
$html .= "<caption>Список ролей в (Блоге) {$blog->site_name} / ID#{$blog->id}</caption>"
$html .= "<thead><tr><th>Название роли</th><th>Возможности</th></tr></thead><tbody>";
foreach ( $GLOBALS['wp_roles'] as $name => $role_obj )
{
$cap = in_array( $custom_cap, $role_obj->caps ) ? $custom_cap : 'n/a';
$cap = $cap OR in_array( $custom_cap, $role_obj->allcaps ) ? $custom_cap : 'n/a';
$html .= "<tr><td>{$name}</td><td>{$cap}</td></tr>";
}
$html .= '</tbody></table>';
print $html;
}
add_action( 'shutdown', 'wpse35165_role_check' );
Добавление возможностей
/**
* Добавляет возможность к объектам ролей
* Должно быть в вашей функции активации и выполнено до проверки вашим плагином
*
* @return void
*/
function wpse35165_add_cap()
{
$custom_cap = 'name_of_your_custom_capability';
$min_cap = 'the_minimum_required_built_in_cap'; // Проверьте "Таблица ролей и объектов" в кодексе!
$grant = true;
foreach ( $GLOBALS['wp_roles'] as $role_obj )
{
if (
! $role_obj->has_cap( $custom_cap )
AND $role_obj->has_cap( $min_cap )
)
$role_obj->add_cap( $custom_cap, $grant );
}
}
Примечание: Вы можете добавить возможность к роли без предоставления доступа к ней — просто установите второй аргумент $grant = false;
. Это позволяет вручную добавлять возможность отдельным пользователям, установив последний аргумент в true.

Протестировано с WordPress 6.0 и работает:
add_action( 'activated_plugin',function( $plugin,$network_activation ){
add_role( 'your_custom_role_slug', __( 'Название вашей кастомной роли','your-plugin-domain' ),
array(
'read' => true,
'view_admin_dashboard' => true,
'activate_plugins' => false,
'deactivate_plugins' => false,
'your_custom_capability' => true
)
);
}, 10, 2 );
В массиве приведены лишь некоторые примеры возможностей (capabilities). Вы можете без проблем добавить свою собственную возможность. Эта кастомная возможность будет сохранена вместе с остальными.
С этим методом вы не сможете указать, что именно можно делать с "your_custom_capability", но в большинстве случаев, как в данном вопросе, вам нужно лишь проверить, имеет ли текущий пользователь эту возможность. Если вы хотите показывать страницы настроек вашего плагина только пользователям с возможностью "your_custom_capability", вы можете сделать это без проблем. Используйте встроенные возможности при создании роли, чтобы определить, что ещё могут видеть и делать пользователи с ролью "your_custom_role_slug".
Это вернёт true, если пользователь имеет роль "your_custom_role_slug":
current_user_can( 'your_custom_capability' );
А это покажет страницу настроек только если пользователь имеет возможность "your_custom_capability":
add_menu_page( __( 'Ваш заголовок','eos-dp' ),__( 'Ваш заголовок','your-domain' ),'your_custom_capability,'your_page_slug','your_function_callback','dashicons-your-icon',20 );
Затем при удалении плагина я бы удалил пользовательскую роль с помощью:
remove_role( 'your_custom_role_slug' );

Это работает для меня:
add_action('admin_init', 'add_custom_cap');
function add_custom_cap()
{
$custom_cap = 'test_cap';
$min_cap = 'read';
$grant = true;
$to_role = 'your_user_role';
$role = 'user_role';
foreach ( $GLOBALS['wp_roles'] as $role_obj )
{
if (is_object($role_obj[$role])) {
if (!$role_obj[$role]->has_cap( $custom_cap ) && $role_obj[$role]->has_cap( $min_cap )) {
$role_obj[$role]->add_cap( $custom_cap, $grant );
}
}
}
}

Никогда не изменяйте глобальные роли! Никогда. Не делайте этого! Вы не сработаете ни на какие хуки, отключите фильтры и сделаете ваш код подвижной мишенью. Никто никогда не узнает, когда и где вы зарегистрировали эту роль (вы не регистрировали её, вы просто затолкали её туда где-то, когда-то, как-то). Пожалуйста: никогда так не делайте. Особенно с ролями.
