<? phpukraine СПІВБЕСІДИ
⌕Пошук по платформі
WORDPRESS · JUNIOR

Як працюють ролі й можливості (capabilities) у WordPress і як додати власну?

Роль - це лише іменований набір capabilities у таблиці options; перевіряти завжди треба capability через current_user_can, meta-можливості типу edit_post - разом з ID обʼєкта, а власні ролі додавати в register_activation_hook і прибирати в uninstall.php.

Чому `current_user_can('administrator')` працює, якщо такої можливості немає?
Редактор бачить сторінку налаштувань плагіна, хоч я вказав capability в add_menu_page. Де дірка?
Я дописав ще одну capability в add_role, перевстановив плагін, а в ролі її немає. Чому?
Після видалення плагіна частина користувачів втратила доступ до адмінки. Що сталося?
capabilities ролі current_user_can map_meta_cap

Роль у WordPress не містить коду. Це рядок у wp_options під ключем wp_user_roles, де лежить масив виду ['editor' => ['name' => 'Editor', 'capabilities' => ['edit_posts' => true, ...]]]. Те, що роль призначена конкретній людині, зберігається окремо, в usermeta wp_capabilities, і теж масивом: ключ - слаг ролі або окрема capability, значення - true або false. Під час завантаження користувача WP_User::get_role_caps() зводить роль і персональні права в один плаский масив allcaps, і далі всі перевірки працюють уже з ним.

Звідси класична пастка. Слаг ролі лежить у тому самому масиві caps, тому current_user_can('editor') повертає true для редактора, хоч можливості з такою назвою не існує. Код «працює», поки роль призначена напряму, і тихо ламається, коли права видає інший плагін через фільтр user_has_cap або коли в користувача дві ролі. Перевіряти треба дію: current_user_can('manage_options'), current_user_can('edit_courses'). Назва ролі - організаційна зручність замовника, і список ролей на живому сайті перекроюють як завгодно.

Primitive capabilities (edit_posts, publish_posts, upload_files) лежать у ролях і перевіряються як є. Meta (edit_post, delete_post, read_post) у ролях відсутні: map_meta_cap() розкладає їх на primitive, дивлячись на конкретний обʼєкт - хто автор, чи запис опублікований, чи він приватний. Тому current_user_can('edit_post', $postId) і current_user_can('edit_posts') відповідають на різні питання, а meta cap без ID обʼєкта мапиться на do_not_allow. Для власного post type цю машинерію увімкніть явно: 'map_meta_cap' => true поряд з 'capability_type', інакше мапінг для нестандартного типу не відбудеться.

Власна роль додається функцією add_role, окрема можливість до наявної ролі - через get_role('editor')->add_cap(). Обидві пишуть в options, тому їх місце в register_activation_hook, а не в коді, що виконується на кожен запит. Другий нюанс: add_role повертає null, якщо роль із таким слагом уже є, і не оновлює нічого. Щоб дописувати capabilities у вже встановлений плагін, тримайте в options номер версії схеми прав і доганяйте різницю через add_cap/remove_cap на plugins_loaded: активаційний хук після звичайного оновлення плагіна не спрацьовує.

Прибирання йде в uninstall.php, не в деактивацію: вимкнути плагін на годину людина може будь-коли, і знищений при цьому розподіл прав на сайті нікого не порадує. Порядок дій тут важливий. remove_role видаляє роль із options, але слаг залишається в usermeta кожного, кому її призначили, і такий користувач входить в адмінку взагалі без можливостей. Спершу переводимо людей на безпечну роль через set_role, далі видаляємо роль і прибираємо ті capabilities, які плагін додавав у вбудовані ролі. Результат перевіряйте під реальною роллю, а не під адміністратором: адміністратор на одиночному сайті проходить майже будь-яку перевірку й приховує дірки, які під редактором видно одразу.

// Ролі пишемо в базу один раз - при активації, не на кожен запит
register_activation_hook(__FILE__, function (): void {
    add_role('course_manager', 'Менеджер курсів', [
        'read' => true,
        'edit_courses' => true,            // свої курси
        'edit_published_courses' => true,
        'publish_courses' => true,
        'upload_files' => true,
    ]); // поверне null, якщо роль уже існує - нічого не перезапише

    get_role('editor')?->add_cap('edit_others_courses'); // доповнюємо вбудовану роль
});

// Без map_meta_cap => true meta caps для власного типу не мапляться
register_post_type('course', [
    'public' => true,
    'capability_type' => ['course', 'courses'],
    'map_meta_cap' => true,
]);

// Правило на конкретний запис: закритий курс не редагує ніхто, крім адміна
add_filter('map_meta_cap', function (array $caps, string $cap, int $userId, array $args): array {
    if ($cap !== 'edit_post' || ! isset($args[0])) {
        return $caps;
    }
    $post = get_post($args[0]);
    if ($post?->post_type === 'course'
        && get_post_meta($post->ID, '_course_locked', true)
        && ! user_can($userId, 'manage_options')) {
        return ['do_not_allow'];
    }

    return $caps;
}, 10, 4);

// Перевірка там, де відбувається дія, а не лише там, де малюється кнопка
if (! current_user_can('edit_post', $courseId)) { // meta cap - разом з ID
    wp_die('Недостатньо прав', 403);
}
Що роль не має власної логіки: це запис у `wp_options` під ключем `wp_user_roles` зі списком capabilities, а призначення ролі користувачу лежить в usermeta `wp_capabilities`.
Що перевіряти треба можливість, а не назву ролі, бо список ролей змінюють інші плагіни й замовник.
Що `add_role` і `WP_Role::add_cap` пишуть у базу, тому їх місце в `register_activation_hook`, а не на `init`.
Розрізнення primitive і meta capabilities: `edit_posts` перевіряється сама собою, `edit_post` без ID обʼєкта безглузда й мапиться на `do_not_allow`.
Що для власного post type правила на окремий запис увімкнює `'map_meta_cap' => true` у `register_post_type`, а точні умови дописує фільтр `map_meta_cap`.
Що `remove_role` не прибирає роль з користувачів, тому перед видаленням їх треба перевести на іншу роль.
Писати `current_user_can('administrator')` або `current_user_can('editor')`. Це випадково працює, бо назва призначеної ролі лежить у масиві `caps` користувача, але ламається для прав, виданих фільтром, і для користувача з кількома ролями.
Покладатися на capability в `add_menu_page` і не перевіряти права в самому обробнику: прямий POST на `admin-post.php` чи `admin.php?page=...` меню не питає.
Викликати `add_role` на кожен запит (на `init` або в конструкторі плагіна): зайвий запис в options і сюрпризи після того, як адміністратор прибрав capability вручну.
Чекати, що `add_role` оновить наявну роль. Вона повертає `null`, якщо роль з таким слагом уже є, і не змінює нічого.
Викликати `current_user_can('edit_post')` без ID запису й думати, що це перевірка «чи може редагувати записи».
Прибирати ролі в `register_deactivation_hook` замість `uninstall.php`: тимчасове вимкнення плагіна знищує призначення ролей користувачам.
Перевіряти все під адміністратором, який проходить майже будь-яку перевірку, і не тестувати під реальною роллю.
ПОРАДА

Скажіть, що право на дію перевіряється в тому ж місці, де дія відбувається, а не там, де малюється кнопка. І згадайте версійність схеми прав: номер версії в options плюс міграція на `plugins_loaded` дають змогу дописувати capabilities у вже встановлений плагін, бо `add_role` існуючу роль не чіпає.

оновлено 3 жовтня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

Назва ролі справді лежить у масиві caps користувача, тому перевірка ролі працює випадково й ламається на правах від фільтрів; meta cap без ID обʼєкта мапиться на do_not_allow.