WordPress ma wbudowaną listę kontroli dostępu, a prawie nikt z niej nie korzysta. Najczęstszy błąd uprawnień na stronach klientów to nie niezałatana wtyczka, tylko redaktor treści z rolą Administrator, bo ktoś raz potrzebował przestawić menu. Ten przewodnik pokazuje, co API ról i uprawnień robi na poziomie źródeł: gdzie leżą dane, dlaczego add_role() milcząco ignoruje drugie wywołanie, do czego naprawdę sprowadza się sprawdzenie nazwy roli i które capabilities są szersze, niż sugeruje nazwa.
Jedna zmiana robi większość roboty. Przestań nazywać role w warunkach i nazwij capability, której kod naprawdę potrzebuje. Reszta przewodnika to koszt tej decyzji w praktyce.
1. Rola a uprawnienie (capability) w WordPress
Capability to pojedynczy ciąg uprawnienia: edit_posts, publish_pages, install_plugins. Rola to nazwany worek tych ciągów. WordPress przechowuje worek, a ewaluuje ciąg.
Domyślne worki są mniejsze, niż się zakłada. Według dokumentacji ról i uprawnień WordPress Editor ma 26 capabilities, a Author siedem: read, upload_files, edit_posts, edit_published_posts, publish_posts, delete_posts, delete_published_posts. Contributor ma trzy: read, edit_posts, delete_posts. Contributor nie może wgrać pliku, stąd ticket o bibliotece mediów po „dajcie mu Contributor”.
Editor niesie unfiltered_html na single-site (surowy <script> w treści). W multisite map_meta_cap() odmawia go wszystkim poza super adminami:
if ( defined( 'DISALLOW_UNFILTERED_HTML' ) && DISALLOW_UNFILTERED_HTML ) {
$caps[] = 'do_not_allow';
} elseif ( is_multisite() && ! is_super_admin( $user_id ) ) {
$caps[] = 'do_not_allow';
} else {
$caps[] = 'unfiltered_html';
}Czego Editor nie ma: edit_theme_options, list_users, edit_users, manage_options, niczego związanego z wtyczkami.
Dlaczego sprawdzenie nazwy roli wydaje się działać
Sprawdzanie nazwy roli to nie błąd składni. Jest gorzej: zwraca poprawną odpowiedź w jedynym przypadku, który przetestowałeś.
// Źle
if ( current_user_can( 'administrator' ) ) { ... }
// Dobrze
if ( current_user_can( 'manage_options' ) ) { ... }Pierwsza linia zwraca true dla administratora przez merge na końcu WP_User::get_role_caps():
$this->allcaps = array();
foreach ( (array) $this->roles as $role ) {
$the_role = $wp_roles->get_role( $role );
$this->allcaps = array_merge( (array) $this->allcaps, (array) $the_role->capabilities );
}
$this->allcaps = array_merge( (array) $this->allcaps, (array) $this->caps );$this->caps to surowa meta wp_capabilities, a jej kluczami są nazwy ról. Stąd administrator => true ląduje w allcaps obok prawdziwych uprawnień i przechodzi sprawdzenie przypadkiem. Dokumentacja current_user_can() mówi, że sprawdzanie ról zamiast capabilities jest częściowo wspierane, ale odradzane, bo daje niepewne wyniki.
Trzy scenariusze z żywych wdrożeń:
- Budujesz własną rolę z
manage_options, żeby klient wszedł na stronę ustawień.current_user_can('administrator')jestfalse. Twoja własna strona admina zwraca 403 użytkownikowi, dla którego ją zrobiłeś. - Capability nadane bezpośrednio na obiekcie użytkownika, bez roli, przechodzi każde sprawdzenie capability i pada przy każdym sprawdzeniu roli.
- W multisite
WP_User::has_cap()dla super adminów kończy się wcześniej, zanim zajrzy doallcaps, i zwracatruedla wszystkiego pozado_not_allow. Sprawdzenie roli nic tam nie rozróżnia.
Sprawdzenia capability przeżywają wszystkie trzy, bo capability to to, czego kod naprawdę potrzebuje.
2. Jak dodać własną rolę w WordPress
Zanim napiszesz własną, sprawdź, czy platforma już ją dostarcza. WooCommerce przy instalacji dodaje shop_manager i customer. Według dokumentacji ról WooCommerce Shop Manager i tak zarządza produktami, zamówieniami, kuponami i kontami klientów. Równoległy „manager sklepu” dubluje rolę aktualizowaną w cudzym cyklu wydań.
Gdy naprawdę potrzebujesz własnej:
add_role(
'editorial_lead',
'Szef redakcji',
[
'read' => true,
'upload_files' => true,
'edit_posts' => true,
'edit_others_posts' => true,
'edit_published_posts' => true,
'publish_posts' => true,
'delete_posts' => true,
'moderate_comments' => true,
]
);Dwie rzeczy z tej sygnatury nie wynikają z dokumentacji API.
Zapisuje raz i potem Cię ignoruje. Handbook wtyczek mówi, że po pierwszym wywołaniu rola i capabilities są w bazie, a „kolejne wywołania nic nie robią: także przy zmianie listy uprawnień” (tłumaczenie własne). Edytujesz tablicę, wdrażasz, i nic się nie zmienia. Każda zmiana capabilities wymaga migracji:
const EDITORIAL_LEAD_VERSION = 3;
function wppoland_sync_editorial_lead() {
if ( (int) get_option( 'wppoland_editorial_lead_version' ) === EDITORIAL_LEAD_VERSION ) {
return;
}
remove_role( 'editorial_lead' );
add_role( 'editorial_lead', 'Szef redakcji', [ /* ... */ ] );
update_option( 'wppoland_editorial_lead_version', EDITORIAL_LEAD_VERSION );
}
register_activation_hook( __FILE__, 'wppoland_sync_editorial_lead' );
add_action( 'admin_init', 'wppoland_sync_editorial_lead' );Kopia na admin_init ma znaczenie w multisite i przy deployach, które nie odpalają activation hooka. Bramka wersji trzyma to poza ścieżką krytyczną; handbook ostrzega, że bezwarunkowe usuwanie i ponowne dodawanie „mocno degraduje wydajność” (tłumaczenie własne).
remove_role() nie rusza użytkowników. Usuwa klucz w wierszu options. Każdy użytkownik, którego meta wp_capabilities nadal mówi editorial_lead => true, zachowuje ten klucz, WP_Roles::is_role() zwraca już dla niego false, więc nigdy nie zamienia się on w uprawnienia. Użytkownikowi zostaje ciąg, który niczego nie nadaje. Przypisz użytkowników przed usunięciem albo zaraz po.
Gdzie leży wiersz. WP_Roles::for_site() buduje klucz $wpdb->get_blog_prefix( $this->site_id ) . 'user_roles'. Na single-site z domyślnym prefiksem to wp_user_roles. Na subsite 3 sieci to wp_3_user_roles, i dlatego rola dodana na jednej podstronie sieci nie istnieje na następnej. Jeśli chcesz mieć role zdefiniowane w kodzie i nigdy niezapisywane w bazie, ustaw global $wp_user_roles w wp-config.php: WordPress używa wtedy jego, a opcja ról, jak pisze referencja core, nie jest ani aktualizowana, ani używana.
Etykiety ról na stronach wielojęzycznych
Drugi argument add_role() to etykieta widoczna w panelu, a WP_Roles::add_role() zapisuje ją w wierszu options dokładnie w tej postaci, w jakiej przyszła. Jeśli owiniesz ją w __(), w bazie ląduje ciąg w języku aktywnym w chwili aktywacji, a nie w języku osoby, która później otwiera ekran użytkowników. Przy wyświetlaniu wp_dropdown_roles() przepuszcza etykietę przez translate_user_role(), która tłumaczy z kontekstem User role w domenie tekstowej default. To obejmuje role core, których ciągi są w katalogu WordPressa, i nie obejmuje etykiety Twojej wtyczki. Na stronie wielojęzycznej trzymaj slug i etykietę po angielsku i nie opieraj logiki na etykiecie: stabilnym identyfikatorem jest slug.
3. Jak dodać uprawnienie do istniejącej roli
get_role() plus add_cap() to mniejszy diff, gdy domyślna rola jest prawie dobra:
$role = get_role( 'editor' );
if ( $role ) {
$role->add_cap( 'edit_theme_options' );
}Ta sama bramka wersji z sekcji 2 obowiązuje. WP_Role::add_cap() przyjmuje dokładnie dwa parametry, add_cap( $cap, $grant = true ), a drugi decyduje, czy uprawnienie jest nadane, czy jawnie odebrane, a nie o tym, czy wiersz zostanie zapisany. Trzeciego parametru tu nie ma. Forma trzyargumentowa należy do WP_Roles::add_cap( $role, $cap, $grant ), innej klasy, i to do tej metody deleguje WP_Role::add_cap(). Ta metoda przypisuje uprawnienie i woła update_option() zawsze, gdy use_db jest true, przy każdym wywołaniu. Żaden argument nie wyłącza zapisu, więc add_cap() na init bez bramki oznacza próbę zapisu opcji przy każdym żądaniu.
Zakres edit_theme_options w motywach blokowych
Większy problem tej konkretnej linii to zakres. edit_theme_options to klasyczna poprawka typu „niech Editor zarządza menu” i przy motywie klasycznym robi mniej więcej to: Wygląd dla widgetów, menu, Customizera i nagłówka. Przy motywie blokowym to inne uprawnienie. Dokumentacja core nazywa je teraz głównym uprawnieniem, które WordPress sprawdza, gdy decyduje, czy użytkownik może wejść do szablonów i nimi zarządzać w Site Editorze, i ostrzega, że nie ogranicza się wyłącznie do Site Editora i może dać dostęp do innych funkcji administracyjnych związanych z motywem.
Editor, któremu chciałeś pozwolić przestawić menu, ma więc teraz Site Editor: szablony, części szablonów, style globalne i nawigację. To szerszy grant niż switch_themes, który odsłania tylko Wygląd i Wygląd > Motywy.
Jeśli naprawdę chcesz roli do Site Editora, dokumentacja wymienia, czego potrzebuje: edit_theme_options do dostępu i menu nawigacji, do tego edit_posts, edit_pages i edit_others_posts do zarządzania szablonami, read do podglądu i upload_files do mediów.
Błąd jest tu cichy, bo Site Editor rozmawia z REST API. Endpointy szablonów i części szablonów sprawdzają edit_theme_options, endpointy wpisów i stron robią własne sprawdzenia. Rola, która otwiera Site Editor, ale nie ma edit_others_posts, dostaje panel, który się ładuje, a potem nie zapisuje zmian. Zanim zaczniesz dokładać uprawnienia na ślepo, przejrzyj zakładkę sieci w poszukiwaniu odpowiedzi 401 i 403: kod błędu REST nazywa uprawnienie, którego zabrakło.
Uprawnienia warunkowe przez user_has_cap
Niektóre reguły nie mieszczą się w roli, bo zależą od obiektu: na przykład Editor, który nie może ruszać wpisów z jednej konkretnej kategorii. Do tego służy filtr user_has_cap. WP_User::has_cap() stosuje go po map_meta_cap(), z czterema argumentami: $allcaps (co użytkownik ma), $caps (prymitywy wymagane przez sprawdzenie), $args (żądane uprawnienie w $args[0], ID użytkownika w $args[1] i zwykle ID obiektu w $args[2]) oraz obiekt WP_User.
add_filter( 'user_has_cap', function ( $allcaps, $caps, $args, $user ) {
if ( 'edit_post' !== ( $args[0] ?? '' ) || empty( $args[2] ) ) {
return $allcaps;
}
if ( has_term( 'legal', 'category', $args[2] ) && empty( $allcaps['manage_options'] ) ) {
foreach ( $caps as $cap ) {
$allcaps[ $cap ] = false;
}
}
return $allcaps;
}, 10, 4 );Trzy szczegóły w źródłach decydują, czy to działa. Filtr widzi tylko sprawdzenia, które niosą ID obiektu, więc current_user_can( 'edit_posts' ) przechodzi przez niego bez uruchomienia reguły. Działa przy każdym sprawdzeniu uprawnień, więc wczesny return w pierwszej linii nie jest opcjonalny. A w multisite nie dociera do super adminów: has_cap() zwraca dla nich true, zanim zastosuje filtr, co pokazuje sekcja 6.
4. Jak zresetować role WordPress do domyślnych
Skrypt resetu z parametrem GET, który krążył też we wcześniejszych edycjach tego artykułu, to zdalne podniesienie uprawnień:
// Nie wdrażaj tego.
if ( ! isset( $_GET['reset_roles_secret_key'] ) ) return;
require_once( ABSPATH . 'wp-admin/includes/schema.php' );
populate_roles();isset() sprawdza obecność parametru, nie poprawność. Każdy niezalogowany gość z ?reset_roles_secret_key na dowolnym URL przywraca domyślne capabilities, w tym to, co celowo ściąłeś z Editor lub Author.
Użyj WP-CLI. wp role reset korzysta z tej samej funkcji core i jest uwierzytelniony dostępem do shella:
wp role reset --all
wp role reset editorRobi też więcej niż samo populate_roles(), i różnica ma znaczenie przy debugowaniu. populate_roles() woła populate_roles_160() przez populate_roles_300(), a te funkcje tylko add_role() i add_cap(). Nie ma w nich remove_cap(). Wywołane bezpośrednio przywracają usunięte uprawnienia i zostawiają każde dodane przez wtyczkę. WP-CLI robi na tym diff względem czystej instalacji, stąd output w stylu:
Restored 1 capability to and removed 0 capabilities from 'administrator' role.Własne role żaden z tych ścieżek nie rusza. Jeśli problemem jest popsuta własna rola, reset jej nie naprawi.
Zanim zresetujesz, podejrzyj stan. To jeden wiersz options:
wp option get wp_user_roles --format=json | jq '.editor.capabilities'
wp cap list editor
wp user list --field=user_login --role=administratorOstatnie polecenie warto odpalić na każdej stronie, którą dziedziczysz. Liczba administratorów, która Cię zaskakuje, jest już diagnozą.
5. Jak zabezpieczyć role i uprawnienia WordPress
Jak sprawdzić konta administratorów w WordPress
Najmniejsze uprawnienie to nie nowa rola, tylko krótsza lista administratorów. Odpal wp user list --role=administrator na stronach, które utrzymujesz. Konta agencji, odejście freelancera i login wsparcia wtyczki sprzed dwóch lat to typowi „lokatorzy”.
Nazwa użytkownika admin i enumeracja użytkowników przez REST API
Rada „nigdy nie nazywaj użytkownika admin” jest starsza niż instalator pytający o login, a robi to od WordPress 3.0. Zostaje enumeracja: endpoint REST użytkowników zwraca bez uwierzytelnienia id, name, url, description, link, slug i avatar_urls. roles i capabilities należą do kontekstu edit i pozostają ukryte, ale slug to user_nicename, które WordPress wyprowadza z loginu, o ile nikt go nie zmienił. To lista loginów, a nie wyciek, i limit prób na formularzu logowania waży tu więcej.
Jak wyłączyć edycję plików przez DISALLOW_FILE_EDIT
Usunięcie edit_themes z Administratora to zmiana capability, którą ktoś może cofnąć. Stała w wp-config.php jest sprawdzana w map_meta_cap() i nie da się jej obejść grantem:
define( 'DISALLOW_FILE_EDIT', true );DISALLOW_FILE_EDIT mapuje edit_files, edit_plugins i edit_themes na do_not_allow. DISALLOW_FILE_MODS idzie dalej: blokuje instalację i aktualizację wtyczek oraz motywów z panelu i wyłącza edytor plików jako efekt uboczny. Na stronie wdrażanej z Gita DISALLOW_FILE_MODS jest uczciwym ustawieniem, bo aktualizacje i tak idą pipeline’em.
DISALLOW_UNFILTERED_HTML działa tak samo i warto ją ustawić na stronie redakcyjnej, gdzie Editorzy nie muszą wklejać skryptów.
Mapuj meta capabilities na własnych typach treści
register_post_type() domyślnie ustawia map_meta_cap na null, nie false. WP_Post_Type::set_props() potem zamienia null na true, gdy capabilities jest puste i capability_type to post lub page (kompatybilność wsteczna z 3.0, ticket #14122), a w pozostałych przypadkach spada do false. Własny capability_type jak poniżej ląduje więc na false, o ile tego nie powiesz. Ustaw oba argumenty:
register_post_type( 'book', [
'capability_type' => [ 'book', 'books' ],
'map_meta_cap' => true,
] );Forma tablicowa nie jest ozdobą. Przy zwykłym stringu WordPress dokleja s do liczby mnogiej, stąd storys dla story. Referencja register_post_type() wskazuje tablicę jako sposób podania alternatywnej liczby mnogiej.
capability_type => 'book' generuje meta uprawnienia edit_book, read_book, delete_book oraz prymitywy edit_books, edit_others_books, delete_books, publish_books, read_private_books. Kolejne sześć, read, delete_private_books, delete_published_books, delete_others_books, edit_private_books i edit_published_books, powstaje tylko przy map_meta_cap ustawionym na true, bo konsumuje je wyłącznie map_meta_cap(). To jedyny powód istnienia tej flagi. Bez niej map_meta_cap() zamienia meta uprawnienie edit_book na dosłowny ciąg edit_book, bez sprawdzania autora i statusu publikacji, więc kto ma to uprawnienie, edytuje książki wszystkich.
Jeszcze jedna wartość domyślna: create_posts mapuje się na edit_books, o ile nie ustawisz go jawnie. Rola, która ma edytować istniejące książki, ale nigdy nie tworzyć nowych, wymaga osobnego wpisu w tablicy capabilities.
Gdzie sprawdzać current_user_can w handlerach i trasach REST
Sprawdzenie należy obok operacji, nie przy menu. add_menu_page() z capability chowa link. Nie chroni handlera. Każdy handler admin-post, callback AJAX i trasa REST potrzebuje własnego current_user_can(), a trasy REST w permission_callback, nie wewnątrz handlera. Od WordPress 5.5 register_rest_route() zgłasza _doing_it_wrong(), gdy permission_callback brakuje, a dla trasy naprawdę publicznej udokumentowaną wartością jest __return_true.
Dla operacji na konkretnym obiekcie przekaż obiekt: current_user_can( 'edit_post', $post_id ) idzie przez map_meta_cap() i rozstrzyga własność. current_user_can( 'edit_posts' ) pyta o typ treści, nie o ten wpis.
Log audytu dostępu i rozdzielenie środowisk
Log audytu, który śledzi logowania, zmiany ról i wrażliwe akcje, mówi, kto komu co nadał i kiedy. Tę funkcję pokrywają wtyczki takie jak WP Activity Log, a można ją też zbudować na hookach WordPressa.
Role w środowisku deweloperskim, na stagingu i na produkcji przegląda się osobno. Rola rozsądna na dev, z dodatkowymi uprawnieniami do debugowania, może być niebezpieczna na produkcji.
6. Uprawnienia WordPress w multisite i we wtyczkach
Uprawnienia sieci w multisite
W WordPress Multisite uprawnienia działają na dwóch poziomach: pojedynczej strony i sieci. Według dokumentacji ról i uprawnień tych dziesięć istnieje tylko dla super admina: create_sites, delete_sites, manage_network, manage_sites, manage_network_users, manage_network_plugins, manage_network_themes, manage_network_options, upgrade_network i setup_network.
Zaskakująca część działa w drugą stronę. Administrator podstrony sieci traci uprawnienia, które ma na single-site. Ta sama dokumentacja wymienia update_core, update_plugins, update_themes, install_plugins, install_themes, delete_themes, delete_plugins, edit_plugins, edit_themes, edit_files, edit_users, add_users, create_users, delete_users i unfiltered_html jako uprawnienia, które w multisite ma tylko super admin. Nie znikają z wiersza roli: map_meta_cap() zamienia je na do_not_allow pod warunkiem is_multisite() && ! is_super_admin( $user_id ), tym samym, który w sekcji 1 widziałeś przy unfiltered_html. Dlatego dodanie install_plugins roli Administrator na podstronie niczego nie zmienia.
WP_User::has_cap() obsługuje super admina, zanim zajrzy do allcaps i zanim zastosuje filtr user_has_cap: jeśli wynik map_meta_cap() nie zawiera do_not_allow, zwraca true. Żeby coś super adminowi odebrać, filtrujesz map_meta_cap i dodajesz do_not_allow, a nie user_has_cap.
Do sprawdzenia uprawnienia na innej stronie sieci WordPress 6.7 wprowadził current_user_can_for_site( $site_id, $capability ), która zastępuje current_user_can_for_blog(), przeniesioną do deprecated.php. Handbook wtyczek wciąż pokazuje starą nazwę.
Uprawnienia WooCommerce, BuddyPress i bbPress
Wtyczki takie jak WooCommerce, BuddyPress i bbPress dodają do systemu własne uprawnienia. Planując architekturę ról dla strony z tymi wtyczkami, uwzględnij zarówno natywne uprawnienia WordPressa, jak i te dodane przez wtyczki.
Members i User Role Editor do zarządzania rolami
Wtyczki Members i User Role Editor dają graficzny interfejs do ról i uprawnień. W profesjonalnym developmencie lepiej jednak zarządzać rolami w wersjonowanym kodzie, żeby zmiany uprawnień przechodziły ten sam review co każda inna zmiana.
Podsumowanie
Role to magazyn. Capabilities to decyzja. Cały przewodnik wynika z tego podziału:
- Sprawdzaj capabilities, nigdy nazw ról, bo
current_user_can('administrator')przechodzi przez merge kluczy meta użytkownika i zwraca false dla każdej własnej roli, którą zbudujesz. - Traktuj
add_role()iadd_cap()jak migracje wiersza w bazie, z bramką wersji, a nie jak konfigurację, którą edytujesz i wdrażasz. - Czytaj realny zakres capability przed grantem.
edit_theme_optionsprzy motywie blokowym to Site Editor, nie tylko ekran menu. - Odzyskuj przez
wp role reset --all, nigdy przez nieuwierzytelniony parametr w URL, i pamiętaj, że własne role zostawia w spokoju. - Ustaw
map_meta_cap => truena każdym własnym typie treści z więcej niż jednym autorem.
Audyt listy administratorów i mapy uprawnień: audyt bezpieczeństwa WordPress albo kontakt.







