Files
KGV-Role-Manager/kgv-role-manager.php
2026-04-13 21:57:54 +02:00

449 lines
21 KiB
PHP
Executable File

<?php
/**
* Plugin Name: KGV Rollen Manager
* Description: Erstellt zusätzliche WordPress-Rollen und vergibt Rechte direkt im Backend.
* Version: 1.0.0
* Author: Ronny Grobel
* Text Domain: kgv-role-manager
* Plugin URI: https://apex-project.de/
* Update URI: https://git.apex-project.de/RonnyG/KGV-Role-Manager
* Gitea Plugin URI: https://git.apex-project.de/RonnyG/KGV-Role-Manager
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
final class KGV_Rollen_Manager {
private array $protected_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
public function __construct() {
add_action( 'admin_menu', array( $this, 'admin_menu' ) );
add_action( 'admin_post_kgv_rm_save_role', array( $this, 'handle_save_role' ) );
add_action( 'admin_post_kgv_rm_delete_role', array( $this, 'handle_delete_role' ) );
add_action( 'admin_notices', array( $this, 'admin_notices' ) );
}
public function admin_menu(): void {
add_users_page(
__( 'Rollen Manager', 'kgv-role-manager' ),
__( 'Rollen Manager', 'kgv-role-manager' ),
'promote_users',
'kgv-role-manager',
array( $this, 'render_page' )
);
}
public function admin_notices(): void {
if ( ! current_user_can( 'promote_users' ) ) {
return;
}
if ( empty( $_GET['page'] ) || 'kgv-role-manager' !== $_GET['page'] || empty( $_GET['kgv_rm_notice'] ) ) {
return;
}
$notice = sanitize_key( wp_unslash( $_GET['kgv_rm_notice'] ) );
$map = array(
'saved' => array( 'updated', __( 'Rolle gespeichert.', 'kgv-role-manager' ) ),
'deleted' => array( 'updated', __( 'Rolle gelöscht.', 'kgv-role-manager' ) ),
'invalid' => array( 'error', __( 'Ungültige Eingabe.', 'kgv-role-manager' ) ),
'exists' => array( 'error', __( 'Der Rollen-Slug existiert bereits.', 'kgv-role-manager' ) ),
'failed' => array( 'error', __( 'Aktion konnte nicht ausgeführt werden.', 'kgv-role-manager' ) ),
'core' => array( 'error', __( 'Standardrollen können hier nicht gelöscht werden.', 'kgv-role-manager' ) ),
);
if ( empty( $map[ $notice ] ) ) {
return;
}
list( $class, $message ) = $map[ $notice ];
printf( '<div class="notice notice-%1$s is-dismissible"><p>%2$s</p></div>', esc_attr( $class ), esc_html( $message ) );
}
private function redirect_with_notice( string $notice ): void {
$url = add_query_arg(
array(
'page' => 'kgv-role-manager',
'kgv_rm_notice' => $notice,
),
admin_url( 'users.php' )
);
wp_safe_redirect( $url );
exit;
}
private function get_all_capabilities(): array {
global $wp_post_types, $wp_taxonomies;
$caps = array(
'read',
'level_0',
'level_1',
'level_2',
'level_3',
'level_4',
'level_5',
'level_6',
'level_7',
'level_8',
'level_9',
'level_10',
'edit_posts',
'edit_others_posts',
'edit_published_posts',
'publish_posts',
'delete_posts',
'delete_others_posts',
'delete_published_posts',
'delete_private_posts',
'edit_private_posts',
'read_private_posts',
'upload_files',
'manage_categories',
'moderate_comments',
'edit_pages',
'edit_others_pages',
'edit_published_pages',
'publish_pages',
'delete_pages',
'delete_others_pages',
'delete_published_pages',
'delete_private_pages',
'edit_private_pages',
'read_private_pages',
'read_private_posts',
'list_users',
'create_users',
'edit_users',
'delete_users',
'promote_users',
'remove_users',
'manage_options',
'switch_themes',
'edit_theme_options',
'activate_plugins',
'edit_plugins',
'install_plugins',
'update_plugins',
'delete_plugins',
'install_themes',
'update_themes',
'delete_themes',
'customize',
'unfiltered_html',
'unfiltered_upload',
'edit_dashboard',
'import',
'export',
'manage_links',
'manage_privacy_options',
'edit_files',
'update_core',
'manage_network',
'setup_network',
'create_sites',
'delete_sites',
'manage_network_users',
'manage_network_plugins',
'manage_network_themes',
'manage_network_options',
);
$roles = wp_roles();
if ( $roles instanceof WP_Roles ) {
foreach ( $roles->roles as $role_data ) {
if ( empty( $role_data['capabilities'] ) || ! is_array( $role_data['capabilities'] ) ) {
continue;
}
$caps = array_merge( $caps, array_keys( $role_data['capabilities'] ) );
}
}
if ( is_array( $wp_post_types ) ) {
foreach ( $wp_post_types as $post_type ) {
if ( empty( $post_type->cap ) ) {
continue;
}
foreach ( (array) $post_type->cap as $cap_name ) {
if ( is_string( $cap_name ) ) {
$caps[] = $cap_name;
}
}
}
}
if ( is_array( $wp_taxonomies ) ) {
foreach ( $wp_taxonomies as $taxonomy ) {
if ( empty( $taxonomy->cap ) ) {
continue;
}
foreach ( (array) $taxonomy->cap as $cap_name ) {
if ( is_string( $cap_name ) ) {
$caps[] = $cap_name;
}
}
}
}
$caps = array_values( array_unique( array_filter( $caps, 'is_string' ) ) );
natcasesort( $caps );
return array_values( $caps );
}
private function group_capabilities( array $caps ): array {
$groups = array(
'Inhalt' => array(),
'Medien' => array(),
'Kommentare' => array(),
'Benutzer' => array(),
'Design' => array(),
'Plugins' => array(),
'Einstellungen' => array(),
'Netzwerk / Sonstiges' => array(),
'Weitere' => array(),
);
foreach ( $caps as $cap ) {
if ( preg_match( '/^(edit_|publish_|delete_|read_private_|manage_categories)/', $cap ) ) {
$groups['Inhalt'][] = $cap;
} elseif ( preg_match( '/^(upload_files|unfiltered_upload)/', $cap ) ) {
$groups['Medien'][] = $cap;
} elseif ( preg_match( '/^(moderate_comments)/', $cap ) ) {
$groups['Kommentare'][] = $cap;
} elseif ( preg_match( '/^(list_users|create_users|edit_users|delete_users|promote_users|remove_users)/', $cap ) ) {
$groups['Benutzer'][] = $cap;
} elseif ( preg_match( '/^(switch_themes|edit_theme_options|customize|edit_files)/', $cap ) ) {
$groups['Design'][] = $cap;
} elseif ( preg_match( '/^(activate_plugins|edit_plugins|install_plugins|update_plugins|delete_plugins)/', $cap ) ) {
$groups['Plugins'][] = $cap;
} elseif ( preg_match( '/^(manage_options|manage_privacy_options|import|export|update_core)/', $cap ) ) {
$groups['Einstellungen'][] = $cap;
} elseif ( preg_match( '/^(manage_network|setup_network|create_sites|delete_sites|manage_network_users|manage_network_plugins|manage_network_themes|manage_network_options)/', $cap ) ) {
$groups['Netzwerk / Sonstiges'][] = $cap;
} else {
$groups['Weitere'][] = $cap;
}
}
return array_filter( $groups );
}
public function handle_save_role(): void {
if ( ! current_user_can( 'promote_users' ) ) {
wp_die( esc_html__( 'Keine Berechtigung.', 'kgv-role-manager' ) );
}
check_admin_referer( 'kgv_rm_save_role' );
$mode = isset( $_POST['mode'] ) ? sanitize_key( wp_unslash( $_POST['mode'] ) ) : 'create';
$role_name = isset( $_POST['role_name'] ) ? sanitize_text_field( wp_unslash( $_POST['role_name'] ) ) : '';
$role_slug = isset( $_POST['role_slug'] ) ? sanitize_key( wp_unslash( $_POST['role_slug'] ) ) : '';
$source_role = isset( $_POST['source_role'] ) ? sanitize_key( wp_unslash( $_POST['source_role'] ) ) : '';
$caps_input = isset( $_POST['caps'] ) && is_array( $_POST['caps'] ) ? array_map( 'sanitize_key', wp_unslash( $_POST['caps'] ) ) : array();
if ( '' === $role_name ) {
$this->redirect_with_notice( 'invalid' );
}
$roles = wp_roles();
if ( ! ( $roles instanceof WP_Roles ) ) {
$this->redirect_with_notice( 'failed' );
}
$all_caps = $this->get_all_capabilities();
$caps = array_fill_keys( array_intersect( $all_caps, $caps_input ), true );
if ( 'edit' === $mode ) {
$edit_role = isset( $_POST['edit_role'] ) ? sanitize_key( wp_unslash( $_POST['edit_role'] ) ) : '';
if ( '' === $edit_role || empty( $roles->roles[ $edit_role ] ) ) {
$this->redirect_with_notice( 'invalid' );
}
remove_role( $edit_role );
$result = add_role( $edit_role, $role_name, $caps );
if ( ! $result ) {
$this->redirect_with_notice( 'failed' );
}
$this->redirect_with_notice( 'saved' );
}
if ( '' === $role_slug ) {
$role_slug = sanitize_key( str_replace( ' ', '_', strtolower( $role_name ) ) );
}
if ( '' === $role_slug ) {
$this->redirect_with_notice( 'invalid' );
}
if ( ! empty( $roles->roles[ $role_slug ] ) ) {
$this->redirect_with_notice( 'exists' );
}
if ( $source_role && ! empty( $roles->roles[ $source_role ]['capabilities'] ) && empty( $caps ) ) {
$caps = array_map( '__return_true', $roles->roles[ $source_role ]['capabilities'] );
}
$result = add_role( $role_slug, $role_name, $caps );
if ( ! $result ) {
$this->redirect_with_notice( 'failed' );
}
$this->redirect_with_notice( 'saved' );
}
public function handle_delete_role(): void {
if ( ! current_user_can( 'promote_users' ) ) {
wp_die( esc_html__( 'Keine Berechtigung.', 'kgv-role-manager' ) );
}
check_admin_referer( 'kgv_rm_delete_role' );
$role = isset( $_POST['role'] ) ? sanitize_key( wp_unslash( $_POST['role'] ) ) : '';
if ( '' === $role ) {
$this->redirect_with_notice( 'invalid' );
}
if ( in_array( $role, $this->protected_roles, true ) ) {
$this->redirect_with_notice( 'core' );
}
remove_role( $role );
$this->redirect_with_notice( 'deleted' );
}
public function render_page(): void {
if ( ! current_user_can( 'promote_users' ) ) {
wp_die( esc_html__( 'Keine Berechtigung.', 'kgv-role-manager' ) );
}
$roles = wp_roles();
$all_roles = $roles instanceof WP_Roles ? $roles->roles : array();
$edit_role = isset( $_GET['edit_role'] ) ? sanitize_key( wp_unslash( $_GET['edit_role'] ) ) : '';
$edit_data = ( $edit_role && ! empty( $all_roles[ $edit_role ] ) ) ? $all_roles[ $edit_role ] : null;
$all_caps = $this->get_all_capabilities();
$cap_groups = $this->group_capabilities( $all_caps );
$selected = $edit_data && ! empty( $edit_data['capabilities'] ) ? array_keys( array_filter( $edit_data['capabilities'] ) ) : array();
?>
<div class="wrap">
<h1><?php esc_html_e( 'Rollen Manager', 'kgv-role-manager' ); ?></h1>
<p><?php esc_html_e( 'Hier legst du eigene Rollen an und weist gezielt Rechte zu. Standardrollen können bearbeitet, aber hier nicht gelöscht werden.', 'kgv-role-manager' ); ?></p>
<div style="display:grid;grid-template-columns:minmax(340px,460px) minmax(0,1fr);gap:24px;align-items:start;">
<div style="background:#fff;border:1px solid #dcdcde;padding:20px;">
<h2 style="margin-top:0;"><?php echo $edit_data ? esc_html__( 'Rolle bearbeiten', 'kgv-role-manager' ) : esc_html__( 'Neue Rolle anlegen', 'kgv-role-manager' ); ?></h2>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<input type="hidden" name="action" value="kgv_rm_save_role">
<input type="hidden" name="mode" value="<?php echo $edit_data ? 'edit' : 'create'; ?>">
<?php if ( $edit_data ) : ?>
<input type="hidden" name="edit_role" value="<?php echo esc_attr( $edit_role ); ?>">
<?php endif; ?>
<?php wp_nonce_field( 'kgv_rm_save_role' ); ?>
<table class="form-table" role="presentation">
<tbody>
<tr>
<th scope="row"><label for="role_name"><?php esc_html_e( 'Rollenname', 'kgv-role-manager' ); ?></label></th>
<td><input name="role_name" id="role_name" type="text" class="regular-text" required value="<?php echo esc_attr( $edit_data['name'] ?? '' ); ?>"></td>
</tr>
<?php if ( ! $edit_data ) : ?>
<tr>
<th scope="row"><label for="role_slug"><?php esc_html_e( 'Rollen-Slug', 'kgv-role-manager' ); ?></label></th>
<td>
<input name="role_slug" id="role_slug" type="text" class="regular-text" value="">
<p class="description"><?php esc_html_e( 'Nur Kleinbuchstaben, Zahlen und Unterstriche. Leer lassen = automatisch aus dem Namen erzeugen.', 'kgv-role-manager' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><label for="source_role"><?php esc_html_e( 'Vorlage', 'kgv-role-manager' ); ?></label></th>
<td>
<select name="source_role" id="source_role">
<option value=""><?php esc_html_e( 'Keine Vorlage', 'kgv-role-manager' ); ?></option>
<?php foreach ( $all_roles as $role_key => $role_data ) : ?>
<option value="<?php echo esc_attr( $role_key ); ?>"><?php echo esc_html( translate_user_role( $role_data['name'] ) . ' (' . $role_key . ')' ); ?></option>
<?php endforeach; ?>
</select>
<p class="description"><?php esc_html_e( 'Wird nur verwendet, wenn du unten keine Rechte manuell auswählst.', 'kgv-role-manager' ); ?></p>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<h3><?php esc_html_e( 'Rechte', 'kgv-role-manager' ); ?></h3>
<p><?php esc_html_e( 'Setze nur die Rechte, die wirklich nötig sind. Zu viele Rechte machen die Rolle faktisch zum Administrator.', 'kgv-role-manager' ); ?></p>
<div style="max-height:520px;overflow:auto;border:1px solid #dcdcde;padding:16px;background:#f9f9f9;">
<?php foreach ( $cap_groups as $group_label => $group_caps ) : ?>
<div style="margin-bottom:18px;">
<h4 style="margin:0 0 10px;"><?php echo esc_html( $group_label ); ?></h4>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:8px 16px;">
<?php foreach ( $group_caps as $cap ) : ?>
<label style="display:flex;gap:8px;align-items:flex-start;">
<input type="checkbox" name="caps[]" value="<?php echo esc_attr( $cap ); ?>" <?php checked( in_array( $cap, $selected, true ) ); ?>>
<span><code><?php echo esc_html( $cap ); ?></code></span>
</label>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php submit_button( $edit_data ? __( 'Rolle speichern', 'kgv-role-manager' ) : __( 'Rolle anlegen', 'kgv-role-manager' ) ); ?>
</form>
</div>
<div style="background:#fff;border:1px solid #dcdcde;padding:20px;">
<h2 style="margin-top:0;"><?php esc_html_e( 'Vorhandene Rollen', 'kgv-role-manager' ); ?></h2>
<table class="widefat striped">
<thead>
<tr>
<th><?php esc_html_e( 'Name', 'kgv-role-manager' ); ?></th>
<th><?php esc_html_e( 'Slug', 'kgv-role-manager' ); ?></th>
<th><?php esc_html_e( 'Rechte', 'kgv-role-manager' ); ?></th>
<th><?php esc_html_e( 'Aktionen', 'kgv-role-manager' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( $all_roles as $role_key => $role_data ) : ?>
<?php $role_caps = ! empty( $role_data['capabilities'] ) ? count( array_filter( $role_data['capabilities'] ) ) : 0; ?>
<tr>
<td><?php echo esc_html( translate_user_role( $role_data['name'] ) ); ?></td>
<td><code><?php echo esc_html( $role_key ); ?></code></td>
<td><?php echo esc_html( (string) $role_caps ); ?></td>
<td>
<?php
$edit_url = add_query_arg(
array(
'page' => 'kgv-role-manager',
'edit_role' => $role_key,
),
admin_url( 'users.php' )
);
?>
<a class="button button-small" href="<?php echo esc_url( $edit_url ); ?>"><?php esc_html_e( 'Bearbeiten', 'kgv-role-manager' ); ?></a>
<?php if ( ! in_array( $role_key, $this->protected_roles, true ) ) : ?>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="display:inline-block;margin-left:6px;" onsubmit="return confirm('<?php echo esc_js( __( 'Rolle wirklich löschen?', 'kgv-role-manager' ) ); ?>');">
<input type="hidden" name="action" value="kgv_rm_delete_role">
<input type="hidden" name="role" value="<?php echo esc_attr( $role_key ); ?>">
<?php wp_nonce_field( 'kgv_rm_delete_role' ); ?>
<button type="submit" class="button button-small button-link-delete"><?php esc_html_e( 'Löschen', 'kgv-role-manager' ); ?></button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php
}
}
new KGV_Rollen_Manager();