Das Löschen von eingebundenen Post-Type-Einträgen verhindern

Dies ist sicher ein Problem, in das einige von euch auch bereits gerannt sind. Ihr habt einen Post-Type-Eintrag in eine Seite oder einen Beitrag eingefügt und diesen dann später gelöscht, ohne zu wissen, dass dieser noch irgendwo eingebunden war. In diesen Beitrag will ich am beliebten Kontakt-Formular-Plugin „Contact Form 7“ demonstrieren, wie das verhindert werden kann. Es funktioniert aber natürlich auch mit jedem anderen Inhaltstyp.

Vorbereitung

Wir installieren das Contact Form 7 Plugin, erstellen ein Formular und fügen dieses in eine Seite mit dem mitgelieferten Gutenberg Block ein. Der Inhalt der Seite sieht dann in etwa wie folgt aus:

<!-- wp:contact-form-7/contact-form-selector {"id":39,"title":"Contact form 1"} -->
<div class="wp-block-contact-form-7-contact-form-selector">[contact-form-7 id="39" title="Contact form 1"]</div>
<!-- /wp:contact-form-7/contact-form-selector -->

Wir sehen hier die ID des Post-Types wpcf7_contact_form, die einmal in den Block-Attributen und einmal im inneren Shortcode verwendet wird, den das Plugin noch immer verwendet.

Löschen eines Post-Type-Eintrags

Wenn wir nun zu „Formulare -> Kontaktformulare“ navigieren können wir über die „Mehrfachaktionen“ das Kontaktformular löschen. Öffnen wir im Anschluss die Seite, auf der wir das Formular eingebunden haben im Frontend, erhalten wir folgendes, ziemlich witzige, Resultat:

[contact-form-7 404 "Nicht gefunden"]

Anstelle des Kontakformulars wird also ein „defekter Shortcode“ angezeigt. Für andere Post-Types wird die „Fehlerbehandlung“ eventuell anders aussehen oder im schlimmsten falsche eure Seite zerschießen.

Post-Type-Einträge vor einer Löschung schützen

Wie können wir also nun vermeiden, dass ein Eintrag gelöscht wird, der noch wo anders eingebunden ist? Nun, hierzu müssten wir alle andern Post-Types nach einem solchen Eintrag durchsuchen. Wenn wir hierzu den „Post-Content“ nach Markup durchsuchen, wird das eine recht teure Operation sein. Für manche Einbindungen wird es wohl auch nicht so einfach funktionieren. Wir holen uns daher ein wenig Hilfe, um die Operation schneller und zuverlässiger zu machen.

Verwendung einer „Hilfstaxonomy“

Wir führen eine Taxonomie ein, die uns beim Verhindern einer Löschung hilft. Diese Taxonomie benötigt kein UI und muss auch nicht öffentlich sein. Daher reichen wenige Zeilen aus, um sie zu registrieren:

function deletion_prevention_register_taxonomy() {
	register_taxonomy(
		'deletion_prevention_tax',
		array( 'post' ),
		array(
			'hierarchical' => false,
			'public'       => false,
		)
	);
}
add_action( 'init', 'deletion_prevention_register_taxonomy' );

Wie müssen sie auch nicht für jeden Post-Type registrieren, mit dem wir sei verwenden wollen, die Registrierung für Beiträge reicht aus. Nun können wir diese Taxonomie hinzufügen.

Hilfstaxonomie zu Seiten und Beiträgen hinzufügen

Für unser Beispiel parsen wir jedes Mal, wenn ein Post-Type gespeichert wird alle Blöcke. Finden wir hierbei einen Kontaktformular Block, dann verwenden wir dessen ID als der Namen des Terms:

function deletion_prevention_save_post( $post_ID, $post, $update ) {
	if ( ! in_array( $post->post_type, array( 'post', 'page', true ) ) ) {
		return;
	}

	$blocks = parse_blocks( $post->post_content );

	$forms = array();
	foreach ( $blocks as $block ) {
		if ( 'contact-form-7/contact-form-selector' === $block['blockName'] ) {
			$forms[] = (string) $block['attrs']['id'];
		}
	}

	wp_set_object_terms( $post_ID, $forms, 'deletion_prevention_tax' );
}
add_action( 'save_post', 'deletion_prevention_save_post', 10, 3 );

In der Callback-Funktion limitieren wir die Überprüfung auf Beiträge und Seiten und suchen nach dem contact-form-7/contact-form-selector Block. Wenn ihr das Löschen mehrerer eingebundener Post-Types verhindern wollt, könnten ihr zusätzliche Taxonomien verwenden, einen anderen Namen für den Term oder Metadaten.

Indem wir nun diese Taxonomie speichern, können wir sehr einfach und genau feststellen, wo Kontaktformulare eingebunden wurden. Wollen wir ein solches Formular löschen, können wir dann diese Seiten und Beiträge sehr schnell finden.

Verhinderung der Löschung

Jedes Mal, wenn ein Post-Type-Eintrag gelöscht wird, werden eine Reihe von Actions ausgelöst. Eine davon wird kurz vor der eigentlichen Löschung ausgeführt und hier können wir uns einhängen:

function deletion_prevention_delete_check( $delete, $post, $force_delete ) {
	deletion_prevention_check( $post );
}
add_action( 'pre_delete_post', 'deletion_prevention_delete_check', 10, 3 );

Einige Inhaltstypen unterstützen das Verschieben von Einträgen in den Papierkorb (Contact Form 7 unterstützt dies nicht), es kann also hilfreich sein, auch dies abzufangen:

function deletion_prevention_trash_check( $trash, $post ) {
	deletion_prevention_check( $post );
}
add_action( 'pre_trash_post', 'deletion_prevention_trash_check', 10, 2 );

Jetzt seht ihr auch, weshalb ich eine weiter Funktion für die Überprüfung verwendet habe.

Überprüfung auch aktuelle Einbindungen eines Eintrags

Jetzt sind wir im letzten Schritt. Wir suchen mit Hilfe einer Taxonomy-Query nach Einbettungen des zu löschenden Eintrags in Seiten und Beiträgen, indem wir dessen ID als Term-Slug verwenden:

function deletion_prevention_check( $post ) {
	$args = array(
		'post_type'      => array( 'post', 'page' ),
		'post_status'    => array(
			'publish',
			'pending',
			'draft',
			'future',
			'private',
		),
		'tax_query'      => array(
			array(
				'taxonomy' => 'deletion_prevention_tax',
				'field'    => 'slug',
				'terms'    => (string) $post->ID,
			),
		),
		'posts_per_page' => 1,
		'fields'         => 'ids',

	);
	$posts = get_posts( $args );

	if ( ! empty( $posts ) ) {
		wp_die(
			wp_kses_post(
				sprintf(
					__(
						/* translators: %1$s: link to a filtered posts list, %2$s: link to a filtered pages list */
						'This item as it is still used in <a href="%1$s">posts</a> or <a href="%2$s">pages</a>.',
						'deletion-prevention'
					),
					admin_url( 'edit.php?post_type=post&taxonomy=deletion_prevention_tax&term=' . $post->ID ),
					admin_url( 'edit.php?taxonomy=deletion_prevention_tax&term=' . $post->ID )
				)
			)
		);
	}
}

Wenn diese Query mindestens einen Beitrag oder eine Seite zurück liefert, dann wissen wird, dass der Post-Type-Eintrag noch immer verwendet wird. In diesem Fall unterbrechen wir den aktuellen Request mit einem wp_die, was auch die Löschung unterbindet. In der Nachricht sind zwei Links enthalten, die auf die Listen von Beiträgen bzw. Seiten verweisen, in denen der Eintrag eingebunden ist. Hiermit kann man ihn dann schnell dort entfernen und anschließend gefahrlos löschen.

Fazit

Es passiert sehr leicht, dass man aus Versehen einen Eintrag eines Post-Types löscht, der noch an anderer Stelle verwendet wird. Im Core gibt es leider keine Möglichkeit, so etwas einfach zu verhindern. Durch unsere Hilfstaxonomie können wir dies nun sehr einfach absichern. Der gleiche Ansatz würde auch funktionieren, um das Ändern des Status eines Eintrags in einen „nicht öffentlichen“ Status zu verhindern. Das Löschen von Medieninhalten lässt sich hiermit leider nicht direkt abfangen (obwohl diese auch ein Post-Type sind). Ich hoffe dieser letzte Beitrag für 2020 hilft euch ebenfalls in einem Projekt weiter. Den Quellcode dieses Beitrags findet ihr als Plugin in einem GIST, welches ihr gerne ausprobieren oder für eure Anforderungen anpassen könnt.

Veröffentlicht von

Bernhard ist fest angestellter Webentwickler, entwickelt in seiner Freizeit Plugins, schreibt in seinem Blog über WordPress und andere Themen, treibt sich gerne bei den WP Meetups in Berlin und Potsdam herum und läuft nach Feierabend den ein oder anderen Halbmarathon.

Schreibe einen Kommentar

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