Nutzer Berechtigungen auf ausgewählte Kategorie beschränken

In einem meiner vergangenen Projekte hatte ich die Anforderung, dass Nutzern eine Taxonomy-Term zugewiesen werden soll und die Nutzer nur Beiträge dieser Kategorie bearbeiten dürfen und neu erstellte Beiträge automatisch dieser Kategorie zugewiesen werden sollten.

Der Anwendungfall dafür war, dass eine Unternehmenswebsite ca. 60 Redakteure hat, diese aber immer nur für eine Abteilung Inhalte veröffentlichen/bearbeiten sollen.

Es gibt zwar einige Plugins die ähnliche Funktionen bereitstellen, da es sich aber um eine mehrsprachige Website die per MultilingualPress als Multisite-Installation aufgebaut ist handelt, kam keines der Plugins in Frage.

Auf den genauen Aufbau der Multisite Funktionalität werde ich in diesem Beitrag aber nicht eingehen, da ich dafür einige Funktionen des kostenpflichtigen MultilingualPress Plugins verwendet habe. Diese wären ohne weitere Kenntnisse des Plugins nicht nachvollziehbar.

Mein Vorgehen

Als erstes Teilte ich die Anforderungen in einzelne Tasks auf. Folgende Dinge sind zu tun:

  1. Benutzerdefinierte Taxonomie „Department“ anlegen
  2. Den Nutzern muss ein Abteilung (Benutzerdefinierte Taxonomie) zugewiesen werden
  3. Beitrag-Bearbeiten Berechtigung auf Basis der zugewiesenen Departments filtern.
  4. Nutzern Capabilities auf Basis der Departments zuweisen
  5. Beim Anlegen eines Beitrags sollte die dem Nutzer zugewiesene Abteilung automatisch dem Beitrag zugewiesen werden
  1. Ergebnis
  2. Weitere Anpassungen
  3. Plugin

1. Benutzerdefinierte Taxonomie „Department“ anlegen

Die „Department“ Taxonomie können wir über die Funktion register_taxonomy() registieren.

$labels = array(
	'name'              => _x( 'Departments', 'taxonomy general name', 'user-departments' ),
	'singular_name'     => _x( 'Department', 'taxonomy singular name', 'user-departments' ),
	'search_items'      => __( 'Search Departments', 'user-departments' ),
	'all_items'         => __( 'All Departments', 'user-departments' ),
	'parent_item'       => __( 'Parent Department', 'user-departments' ),
	'parent_item_colon' => __( 'Parent Department:', 'user-departments' ),
	'edit_item'         => __( 'Edit Department', 'user-departments' ),
	'update_item'       => __( 'Update Department', 'user-departments' ),
	'add_new_item'      => __( 'Add New Department', 'user-departments' ),
	'new_item_name'     => __( 'New Department Name', 'user-departments' ),
	'menu_name'         => __( 'Department', 'user-departments' ),
);

$args = array(
	'hierarchical'      => true,
	'labels'            => $labels,
	'show_ui'           => true,
	'show_admin_column' => true,
	'query_var'         => false,
	'public'            => false,
	'show_in_rest'      => true,
	'rewrite'           => array( 'slug' => 'department' )
);

register_taxonomy( 'derweili-department', 'post', $args );
Code-Sprache: PHP (php)

2. Verknüpfen der Benutzer mit der Taxonomie

Da Taxonomien eigenlich nur mit Posts, nicht aber mit Nutzern verknüpft werden können, müssen wir diese als Workaround als User Meta speichern.

Dafür müssen wir zuerst die Terms als Checkboxen im „Profil bearbeiten“ Screen einbauen und anschließend beim Speichern des Profils die Terms als User meta speichern

Einbauen des User Interface

Über die Hooks show_user_profile und edit_user_profile können wir die Nutzer Editieren Formulare bearbeiten. Hier bauen wir einfach das benötigte HTML ein.

Die dem Departments holen wir uns über die get_terms() Funktion. Wichtig hier ist dass wir hide_empty auf false setzten. Andernfalls würden nur Departments ausgegeben, denen Bereits ein Post zugewiesen ist.

Die dem Nutzer zugewiesenen Departments (Term IDs) holen wir uns über die get_user_meta() Funktion. Hier ist zu beachten dass wir diese als Array zurückbekommen, da der 3. Parameter auf false gestellt ist. Letzteres ist zwar der Standardwert, ich gebe diesen aber doch gerne hier mit, damit direkt sichtbar ist, dass wir ein Array zurück bekommen.

Anschließend geben wir die Departments als Checkboxen aus. Dabei überprüfen wir ob das jeweilige Department bereits dem Nutzer zugewiesen ist. Falls dies der Fall ist, bekommt die Checkbox das checked Attribut.

add_action( 'show_user_profile', 'add_user_department_form' );
add_action( 'edit_user_profile', 'add_user_department_form' );

add_user_department_form( $user ) {
		$departments = $terms = get_terms( array(
			'taxonomy' => 'derweili-department',
			'hide_empty' => false,
		) );
		$users_department_ids = get_user_meta( $user->ID, 'derweili_users_department', false );
		?>
			<h3><?php _e("Departments", "bluser-departmentsank"); ?></h3>

			<table class="form-table">
			<tr>
					<th><?php _e("Users Departments", "bluser-departmentsank"); ?></th>
					<td>
						<?php
							foreach ($departments as $department) :
						?>
							<label>
								<input
									type="checkbox"
									name="departments[]"
									value="<?php echo $department->term_id; ?>"
									<?php if( in_array( $department->term_id, $users_department_ids ) ) echo "checked"; ?>
									>
								<span class="description"><?php echo $department->name; ?></span>
							</label>
							<br />
						<?php endforeach; ?>
					</td>
			</tr>
			</table>
	<?php 
}
Code-Sprache: PHP (php)

Speichern der Departments als User Meta

Über die Hooks personal_options_update und edit_user_profile_update können wir uns in den „Profil speichern“ Prozess einklinken.

Zu beachten ist hier, dass wir hier über den ersten Funktionsparameter nur die User ID erhalten, während wir bei den vorherigen Hooks das WP_User Objekt erhalten haben.

Wir überprüfen zuerst ob der aktuelle Nutzer den zu bearbeitenden Nutzer überhaupt bearbeiten darf. Anschließend entfernen wir alle bisher dem Nutzer zugewiesenen Departments indem wir über delete_user_meta( $user_id, 'derweili_users_department' ); die User Metas löschen (Zeile 11).

Anschließend speichern wir die neuen Departments als User Meta. (Zeile 17)

add_action( 'personal_options_update', 'save_users_departments' );
add_action( 'edit_user_profile_update', 'save_users_departments' );

function save_users_departments( $user_id ) {
	if ( !current_user_can( 'edit_user', $user_id ) ) { 
		return false; 
	}

	if( ! isset( $_POST["departments"] ) ) return;

	delete_user_meta( $user_id, 'derweili_users_department' );
	
	if( ! is_array( $_POST["departments"] ) ) wp_die('Invalid Departments');
	
	foreach ($_POST["departments"] as $department_id) {
		$_department_id = intval( $id );
		add_user_meta( $user_id, 'derweili_users_department', $_department_id, false );
	}
}
Code-Sprache: PHP (php)

Dadurch dass wir die Departments nicht als „single“ User meta in einem serialisierten Array speichern sondern jedes Department als einzelne Zeile in der User Metas Tabelle könnten wir die Nutzer später per get_users() und einer Meta Query nach Department gefiltert ausgeben. Das machen wir aktuell zwar nicht, es ist aber sinnvoll für solche Fälle schonmal die Datenstruktur vorzubereiten.

3. Beitrag-Bearbeiten Berechtigung auf Basis der zugewiesenen Departments filtern.

Um die für die Bearbeitung der Posts benötigten Capabilities dynamisch auf Basis der dem Post zugewiesenen Capabilities zu ändern, bietet sich der map_meta_cap Filter an.

Der map_meta_cap Filter besitzt 4 Arguments:
function( $required_caps, $cap, $user_id, $args )

Die $required_caps sind ein Array aller Capabilities, die ein Nutzer haben muss, um die aktuelle Aktion durchführen zu können. Sofern wir nichts ändern möchten, gibt unsere Funktion genau dieses Array zurück.

Über $cap erhalten wir die aktuell abgefragte Meta Capability, als `edit_post, publish_post, delete_post, etc.

Über $user_id erhalten wir die $user_id des Nutzers für den die Capabilities abgerufen werden.

Über $args erhalten wir in unserem Fall die ID des Posts, der bearbeit werden soll. Je nach Wert der $cap Variable ist $args Variable leer oder enthält andere Daten.

Unser Code sieht dann aus:

add_filter( 'map_meta_cap', 'filter_post_edit_caps', 10, 4 );

function filter_post_edit_caps( $required_caps, $cap, $user_id, $args ) {
	if( empty( $args ) ) return $required_caps;

	$capabilities_to_filter = [
		'edit_post',
		'delete_post',
		'publish_post'
	]
		
	// wir möchten unsere Anpassungen auf die `edit_post`, `delete_post` und `publish_post` Aktionen anwenden. 
	if( ! in_array($cap, $capabilities_to_filter ) ) return $required_caps;

	$post_departments = get_the_terms( $post, 'derweili-department' );

	if( false === $post_departments) $post_departments = [];

	foreach ($post_departments as $department) {
		$required_caps[] = 'manage_department_' . $department->term_id;
	}

	return $required_caps;
}
Code-Sprache: PHP (php)

Zuerst überprüfen wir, ob $args einen Wert enthält (Zeile 4). Wenn $args leer ist, können wir davon ausgehen, dass keine Berechtigung zu einem einzelnen Post abgefragt wird. Diese Berechtigungen sind für uns nicht wichtig.

Anschließend überprüfen wir ob die Berechtigungen für eine der von uns festgelegten Aktionen (edit, publish, delete) abgefragt wird (Zeile 13).

Wenn das der Fall ist, holen wir uns die dem Post zugewiesenen Departments und ergänzen diese als $required_cap.

Wenn wir uns jetzt die Posts im Backend ansehen, sollten alle Posts die ein Departments zugewiesen haben ausgegraut sein, da wir bisher nicht die Berechtigung haben, diese Posts zu bearbeiten. Diese muss den Nutzer erst noch gegeben werden.

4. Nutzern Capabilities auf Basis der Departments zuweisen

Um jetzt wieder Zugriff auf die Beiträge zu erhalten, müssen wir den Nutzern auf Basis der ihnen Zugewiesenen Departments die jeweiligen Capabilities zuweisen.

Hierfür eignet sich der user_has_cap Filter.

Hier checken wir erst ob der Nutzer angemeldet ist, wenn nicht geben wir direkt wieder $all_caps zurück.

Anschließend holen wir uns die Departments des Nutzers und fügen diese dem $all_caps Array hinzu. Hier muss die jeweilige Capability als Array Key verwendet werden, der Wert (true oder false) gibt an, ob der Nutzer die jeweilige Capability besitzt.

add_filter( 'user_has_cap', 'filter_has_cap', 10, 4 );

function filter_has_cap( $all_caps, $caps, $args, $user ) {
	if( ! taxonomy_exists( 'derweili-department' ) ) return $all_caps;

	if( ! is_user_logged_in() ) return $all_caps;

	$users_department_ids = get_user_meta( $user->ID, 'derweili_users_department', false );

	foreach ($users_department_ids as $department_id) {
		# code...
		$department_capability = 'manage_department_' . $department_id;
		$all_caps[ $department_capability ] = true;
	}

	return $all_caps;
}
Code-Sprache: PHP (php)

Sofern der Nutzer ein Department zugewiesen hat, sind die jeweiligen Posts im der Post Übersicht wieder klickbar.

5. Beiträgen automatisch ein Department zuweisen.

Zuletzt sollen Beiträge automatisch das Department des Nutzers zugewiesen bekommen. Dies lösen wir einfach über den save_post Hook.

add_action( 'save_post', 'auto_assign_departments', 10, 3 );

public static function auto_assign_departments( $post_id, $post, $is_update ) {
	if ( $is_update ) return;

	if ( wp_is_post_revision( $post_id ) ) return;

	$user = wp_get_current_user();

	$users_department_ids = get_user_meta( $user->ID, 'derweili_users_department', false );

	wp_set_post_terms($post_id, $users_department_ids, 'derweili-department');
}
Code-Sprache: PHP (php)

Mit den hier vorgenommenen Anpassungen an den Berechtigungen werden Folgende Funktionen abgedeckt:

Ergebnis

  • Es können Departments erstellt werden
  • Jedem Nutzer können keine, ein oder mehrere Departments zugewiesen werden
  • Jedem Beitrag können keine, ein oder mehrere Departments zugewiesen werden.
  • Nutzer denen Departments zugewiesen sind, können nur Beiträge „ihrer“ Departments bearbeiten.
  • Beiträge denen Departments zugewiesen sind, können nur von Nutzern der jeweiligen Departments bearbeitet / veröffentlicht / gelöscht werden.

Weitere Anpassungen

Auch wenn die oben beschriebenen Funktionen implementiert sind, gibt es noch einige Dinge die nicht optimal sind und für einen Produktiven Einsatz dringend angepasst werden.

Darunter fallen unter anderem folgende Probleme:

  • Aktuell können Nutzer Departments auch manuell zuweisen. Dadurch können Sie sich selbst von der Bearbeitung des Beitrags ausschließend und dies nicht selbst Rückgängig machen. Hier müssen die Capabilities für das Managemnt der Department Taxonomie angepasst werden.
  • Aktuell ist das Verhalten, sofern mehrere Departments einem Nutzer oder Post zugewiesen sind nicht optimal. Sofern einem Beitrag und einem Nutzer sowieso nur ein Department zugewiesen werden soll ist das kein Problem. Ggfls. sollte dann aber das UI von Checkboxen zu Radioboxen gewechselt werden.
  • Auch Administratoren müssen die Departments zugewiesen bekommen um Beiträge bearbeiten zu können. Ggfls. wäre es sinnvoll diesen automatisch die Capabilities für alle Departments zuzuweisen.

Ein halbfertiges Plugin.

Den gesamten Code und ein bisschen extra Zeugs habe ich als Plugin auf Github geladen:

https://github.com/derweili/user-departments


Beitrag veröffentlicht

in

von

Schlagwörter:

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert