От автора: управление пользователями в WordPress основано на механизме ролей и возможностей пользователей. Роли (или группе) задается уникальное имя с набором определенных возможностей. Каждая возможность определяет, есть ли у роли доступ к определенному свойству платформы. Разберем механизм роли и возможности пользователей в WordPress более подробно.
Что внутри. Хранение ролей
На странице WordPress Codex можно посмотреть список ролей и возможностей по умолчанию. В базе данных данный список хранится в таблице wp_options. Таблица использует серийный ключ wp_user_roles.
Несериализованные данные выглядят вот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
array( 'administrator' => array( 'name' => 'Administrator', 'capabilities' => array( 'switch_themes' => true, 'edit_themes' => true, 'activate_plugins' => true, 'edit_plugins' => true, 'edit_users' => true, // [...] ) ), 'contributor' => array( 'name' => 'Contributor', 'capabilities' => array( 'delete_pages' => true, 'delete_others_pages' => true, 'delete_published_pages' => true, 'delete_posts' => true, // [...] ) ), // [...] ); |
Эти метаданные задаются при установке WordPress с нуля. При первом входе на сайт класс WP_Roles загружает список ролей и возможностей из базы данных. Операция происходит между методами plugins_loaded и init.
Привязка ролей пользователям
Для привязки пользователю определенной роли в WordPress используется значение meta_key, которое хранится в таблице wp_usermeta.
После конвертации это выглядит так:
1 2 3 |
array( 'administrator' => true ) |
Заметьте, что WordPress почему-то использует массив, хотя на данный момент у нас всего одна роль. Это мы разберем немного позже. Также помните, что wp_ это префикс текущего блога. (Его можно получить с помощью $GLOBALS[‘wpdb’]->get_blog_prefix()). Если WordPress установлен на несколько сайтов, то пользователи могут иметь различные роли на этих сайтах:
wp_capabilities => a:1:{s:13:»administrator»;b:1;}
wp_10_capabilities => a:1:{s:11:»contributor»;b:1;}
wp_15_capabilities => a:1:{s:10:»subscriber»;b:1;}
[…]
Такое же правило применяется и к wp_user_roles в таблице wp_options, о котором мы говорили выше. И наконец, мы можем получить значение wp_user_level вместе с ролью. Такой подход использовался для управления ролями в старых версиях WordPress, сейчас он упразднен.
Работа с возможностями на уровне ядра
Мы уже видели, как загружаются роли, и как они прикрепляются к пользователям; и теперь WordPress может предоставлять различные возможности для пользователей по необходимости. Парочка возможностей по умолчанию жестко зашиты в ядре WordPress. К примеру, во время загрузки экрана плагинов, проверяется, а может ли пользователь работать с плагинами. Проверка выполняется с помощью следующего кода:
1 2 3 4 |
if (!current_user_can('activate_plugins')) { wp_die(__('You do not have sufficient permissions to manage plugins for this site.')); } |
Роли же никогда жестко не запрограммированы; роль это всего лишь оболочка для возможностей и существует только в БД.
Роли и возможности: WordPress API. Доступ к API
Для облегчения работы с ролями в WordPress есть следующие глобальные функции.
current_user_can() — Проверяет, есть ли у текущего пользователя требуемые права.
1 2 3 4 5 6 7 8 9 10 11 |
add_action('init', function() { if (current_user_can('install_plugins')) { echo 'you can install plugins'; } else { echo 'You cannot install plugins'; } }); |
WP_User::has_cap — Проверяет права заданного пользователя.
1 2 3 4 5 6 7 8 9 10 11 12 |
add_action('init', function() { $user = get_user_by('slug', 'admin'); if ($user->has_cap('install_plugins')) { echo 'Admin can install plugins'; } else { echo 'Admin cannot install plugins'; } }); |
Также данная функция используется в current_user_can.
get_editable_roles() — Возвращает доступные роли.
1 2 3 4 5 |
add_action('admin_init', function() { $roles = get_editable_roles(); var_dump($roles); }); |
Данный список можно переопределить с помощью фильтра editable_roles. Так что не стоит полностью полагаться на данную функцию, список ролей может быть неполным. Обратите внимание, что хук admin_init загружается еще до выполнения метода init.
get_role() — Получает объект WP_Role.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
add_action('init', function() { $role = get_role('administrator'); var_dump($role); }); // This will print: // WP_Role Object // ( // [name] => administrator // [capabilities] => Array // ( // [switch_themes] => 1 // [edit_themes] => 1 // [activate_plugins] => 1 // [edit_plugins] => 1 // [...] |
WP_Role::has_cap() — Проверяет, обладает ли роль требуемыми возможностями.
1 2 3 4 5 |
add_action('init', function() { $role = get_role('administrator'); var_dump($role->has_cap('install_plugins')); // Распечатает TRUE }); |
Настройка API
В WordPress API помимо всего прочего можно также настроить роли и их возможности.
add_role() — Регистрирует новую роль в базе данных.
1 2 3 4 5 6 7 8 |
add_action('init', function() { add_role('plugins_manager', 'Plugins Manager', array( 'install_plugins', 'activate_plugins', 'edit_plugins' )); }); |
remove_role() — Удаляет роль из базы данных, если она существует.
1 2 3 4 |
add_action('init', function() { remove_role('plugins_manager'); }); |
WP_Role::add_cap() — Добавляет роли возможность.
1 2 3 4 5 |
add_action('init', function() { $role = get_role('contributor'); $role->add_cap('install_plugins'); }); |
Значением может быть как основные возможности (install_plugins, edit_posts, …), так и свои собственные (my_awesome_plugin_cap). Для наших плагинов можно зарегистрировать сколько угодно много возможностей.
WP_Role::remove_cap() — Удаляет возможность из роли, если таковая существует.
1 2 3 4 5 |
add_action('init', function() { $role = get_role('contributor'); $role->remove_cap('install_plugins'); }); |
WP_User::add_role() — Добавляет роль заданному пользователю.
1 2 3 4 5 |
add_action('init', function() { $user = get_user_by('slug', 'admin'); $user->add_role('contributor'); }); |
Данная функция, теоретически, способна добавить одному пользователю несколько ролей. Так как в backend WordPress заложено показывать одну роль для одного пользователя, то нам не следует добавлять пользователю несколько ролей. Перед использованием данной функции всегда также запускайте WP_User::remove_role().
WP_User::remove_role() — Удаляет роль у заданного пользователя.
1 2 3 4 5 |
add_action('init', function() { $user = get_user_by('slug', 'admin'); $user->remove_role('administrator'); }); |
WP_User::add_cap() — Добавляет возможность заданному пользователю.
1 2 3 4 5 |
add_action('init', function() { $user = get_user_by('slug', 'admin'); $user->add_cap('my_custom_cap'); }); |
Функция полезна, если нам необходимо добавить всего одну возможность пользователю, а не создавать целую роль.
WP_User::remove_cap() — Удаляет возможность у заданного пользователя.
1 2 3 4 5 |
add_action('init', function() { $user = get_user_by('slug', 'admin'); $user->remove_cap('my_custom_cap'); }); |
Пара проблем с WordPress API
В рассмотренных выше функциях все, вроде бы, выглядит лучше некуда. Все кроме производительности и доступа к базе данных. Главная проблема при работе с ролями и возможностями заключается в том, как определить момент, когда необходимо запустить наш код на выполнение. Чтобы разобраться, взглянем на код ядра WordPress. Сперва, нам необходимо добавить новую пустую роль:
1 2 3 4 |
add_action('init', function() { add_role('plugins_manager', 'Plugins Manager', array()); }); |
Функция add_role на самом деле перенаправляет нас на WP_Roles::add_role:
1 2 3 |
public function add_role( $role, $display_name, $capabilities = array() ) { if ( isset( $this->roles[$role] ) ) return; |
Если добавить новую роль, функция add_role отработает всего раз и больше запускаться не будет. Теперь, скажем, нам нужно добавить возможность к свежесозданной роли:
1 2 3 4 5 |
add_action('init', function() { $role = get_role('plugins_manager'); $role->add_cap('install_plugins'); }); |
В WordPress 4.2.2 функция WP_Role::add_cap() выглядит так:
1 2 3 4 5 6 7 8 |
public function add_cap( $role, $cap, $grant = true ) { if ( ! isset( $this->roles[$role] ) ) return; $this->roles[$role]['capabilities'][$cap] = $grant; if ( $this->use_db ) update_option( $this->role_key, $this->roles ); } |
Данная функция обновляет объект $this->roles. Но еще она каждый раз при выполнении обновляет базу данных, даже если возможность уже зарегистрирована! То есть, если мы хотим позаботиться о производительности, не следует запускать наш код на каждой странице.
Способы обхода
Чтобы избежать проблем с базой данных, существует несколько вариантов.
Настройка запуска плагинов
С помощью функции register_activation_hook() в WordPress авторам плагинов можно настроить их запуск. Создадим простой плагин:
1 2 3 4 5 6 7 8 |
/* Plugin Name: Our sample role plugin */ register_activation_hook(__FILE__, function() { $role = add_role('plugins_manager', 'Plugins Manager', array()); $role->add_cap('install_plugins'); }); |
Данный код выполнится всего раз во время активации плагина. Стоит помнить, что данный метод зависит от запуска и приостановки плагинов. Что будет, если плагин уже запущен? Или отключится ли плагин, если началось его обновление? По факту, данный метод также зависит от базы данных.
Обход базы данных WordPress
Есть и второй, незадокументированный способ. В некоторых случаях он довольно неплохо работает. Еще раз рассмотрим момент загрузки объектом WP_Roles ролей из базы данных при старте WordPress:
1 2 3 4 5 6 7 8 9 |
protected function _init() { global $wpdb, $wp_user_roles; $this->role_key = $wpdb->get_blog_prefix() . 'user_roles'; if ( ! empty( $wp_user_roles ) ) { $this->roles = $wp_user_roles; $this->use_db = false; } else { $this->roles = get_option( $this->role_key ); } |
Перед тем, как вытянуть данные из БД, WordPress проверяет глобальную переменную $wp_user_roles. Если она не пуста, WordPress берет данные из нее и блокирует доступ к БД, установив значения переменной $this->use_db на false. Опробуем данный метод на закрытой роли administrator:
1 2 3 4 5 6 7 8 9 10 11 12 |
/* Plugin Name: Our sample role plugin */ $GLOBALS['wp_user_roles'] = array( 'administrator' => array( 'name' => 'Administrator', 'capabilities' => array( 'activate_plugins' => true, 'read' => true, ) ) ); |
Обновив страницу панели администратора замечаем, что наша роль сохранилась:
Такой метод решает проблему с БД, но добавляет других:
Плагины использующие родное API могут работать неправильно.
Каждую роль приходится прописывать вручную, даже те, которые не хотим менять.
Но, если мы создаем пользовательское WordPress веб-приложение, настроенное вручную со статичным списком ролей, то данный метод, в принципе, подходит:
Значение ролей можно изменить.
Вставка своего кода автоматически обновляет значение роли.
С базами данных больше никаких проблем.
Заключение
В этой статье я рассказал вам о ролях и возможностях в WordPress. Несмотря на то, что в API предоставлены все рычаги для любых мыслимых и немыслимых задач, все же основная проблема, связь с БД, остается. Придется помнить об этом во время разработки плагинов и тем.
Автор: Johan Satgé
Источник: //www.sitepoint.com/
Редакция: Команда webformyself.