WordPress bietet einige APIs, um über die Core-Funktionalitäten Posts, Pages, Kategorien, Tags usw. hinaus flexible und umfangreiche, maßgeschneiderte Lösungen zu integrieren. Allein, das User-Management stößt schnell an seine Grenzen, sodass trotz vorhandener API zur Deklarierung neuer Rollen und Berechtigungen es manchmal unnötig umständlich ist, den richtigen Weg zu finden.

Ein Beispiel aus der Praxis: Ich wurde für eine Webseite beauftragt, die neben einem Blog, Agenturbereich und anderen Funktionen auch ein ausgedehntes Influencer-Verzeichnis enthalten sollte. Dazu sollten die Influencer Profile erstellen und diese mit Webseiten oder anderen Medien verknüpfen können, welche ebenfalls ihre eigenen Profile haben sollten. Dabei wurde gewünscht, dass die Medienprofile nur von Admins und jenen Personen bearbeitet werden dürfen, welche dafür auch verantwortlich waren.

Custom Taxonomy deklarieren

Dafür bot es sich an, eine Custom Taxonomy „Media “ per register_taxonomy zu definieren. Dabei kann auch eine Reihe von Berechtigungen definiert und später den Rollen Admin, Redakteur usw. oder auch nur einzelnen Nutzern zugeordnet werden:

$media = array(
			'capabilities' => array(
				'manage_terms' => 'manage_media',
				'edit_terms'   => 'edit_media',
				'delete_terms' => 'delete_media',
				'assign_terms' => 'assign_media',
			),
			…
	);
register_taxonomy('media', array('post'), $media);

$admins = get_role('administrator');
$editors = get_role('editor');
$authors = get_role('author');
$admins->add_cap('manage_media');
$editors->add_cap('manage_media');
…

Ein kurzer Blick in den WordPress-Codex bestätigte den Verdacht: Custom Taxonomies sind von Haus aus nur mit den vier oben genannten Capabilities ausgestattet. Eine Berechtigung wie edit_others_posts oder delete_others_posts vermisst man, zumal Taxonomien nicht von sich aus ihrem Ersteller zugeordnet sind. Immerhin, ab WordPress-Version 4.7 gibt es dafür einen Workaround.

Metafeld Owner einführen

Also bauen wir uns diese Funktion eben selbst. Dazu wird für die neue Custom Taxonomy „Media“ ein Metafeld definiert, das bei der Erstellung eines Terms mit der ID des jeweiligen Besitzers, sprich: des aktuellen Nutzers befüllt wird. Dazu wird am besten der Hook create_term angesprochen:

add_action('create_term', 'mp_add_media_owner', 10, 3);
function mp_add_media_owner($term_id, $tt_id, $taxonomy) {
	if ($taxonomy == 'media') {
		$creator = get_current_user_id();
		add_term_meta($term_id, 'owner', $creator);
	}
}

Capabilities überprüfen und einschränken

Wenn nun ein Nutzer versucht, diesen Term zu bearbeiten, soll eine Abfrage erfolgen, welche seine User-ID mit der hinterlegte Owner-ID abgeglichen wird. Dazu bietet es sich an, den Low-Level-Hook user_has_cap anzusprechen. Praktischerweise bringt user_has_cap in den Variablen $cap und $args bereits alle weiteren Informationen mit, die wir benötigen. Der Codex weiß dazu:

@param array $allcaps	All the capabilities of the user
@param array $cap			[0] Required capability
@param array $args		[0] Requested capability
								[1] User ID
								[2] Associated object ID

Da dieser Hook bei jeder erdenklichen Nutzerberechtigungsabfrage, also auch für Posts, Pages, Einstellungen usw. feuert, müssen wir zuerst einschränken, ob es auch tatsächlich um einen Term geht. Falls nicht, können wir das Array $allcaps, welches alle Berechtigungen des aktuellen Users enthält, unverändert zurückgeben.

Andernfalls wird geprüft, ob für den aktuellen Term das Metafeld „owner“ vergeben wurde

add_filter('user_has_cap', 'mp_check_media_owner_caps', 10, 3);
function mp_check_media_owner_caps($allcaps, $cap, $args) {
	$requested_cap = $args[0];

	// Wenn es nicht um Terms (einzelne Tags, Custom Taxonomies usw.) geht,
	// $allcaps unverändert zurückgeben.

	if ('edit_term' !== $requested_cap && 'delete_term' !== $requested_cap)
		return $allcaps;

	$user = $args[1];
	$object = isset($args[2]) ? $args[2] : null;
	$owner = get_term_meta($object, 'owner');

	if (!empty($owner)) {
		if ((int) $owner[0] !== $user && !current_user_can('edit_others_posts'))
			$allcaps[$cap[0]] = false;
	}

	return $allcaps;
}

Ein Wermutstropfen: Versionskompatibilität

Dreh- und Angelpunkt dieser Anwendung sind die Berechtigungen edit_term und delete_term. Diese sind noch recht neu, erst seit der WordPress-Version 4.7 integriert und noch nicht einmal im Codex dokumentiert. Vorsicht ist also geboten, auf welchem System diese Lösung zum Einsatz kommt.

Die obige Lösung bietet sich also an, wenn von einer aktuellen WordPress-Version ausgegangen werden kann, etwa bei Aufträgen für eine neue Webseite. In der Plugin-Entwicklung für die breite Masse bleibt allerdings leider nichts übrig, als all jene User außen vor zu lassen, die ihr letztes WordPress-Update vor Dezember 2016 durchführten.

Daher sollte ein Fallback bspw. auf Basis von get_bloginfo('version') angeboten werden. Dies geschieht am einfachsten bei der Registrierung des Filter-Hooks:

$version = get_bloginfo('version');
if ((float) $version >= 4.7) {
	add_filter('user_has_cap', 'mp_check_media_owner_caps', 10, 3);
}

In älteren Versionen werden also zwar die Metafelder ausgefüllt, allerdings kann jeder Nutzer, dessen Nutzerrolle einer der eingangs definierten entspricht, auch jedes andere Medium bearbeiten.

Kennt ihr eine Lösung auch für ältere Versionen? Dann lasst es mich wissen!