Zum Inhalt springen

Eigene Inhaltstypen

Ein Verein braucht manchmal mehr, als das Plugin mitbringt: Fotoalben, Ausrüstung, Fahrzeuge einer Feuerwehr. Dafür gibt es Add-ons: ein eigenes Plugin, das WP Club Elements voraussetzt und ihm einen Post Type mit Feldern hinzufügt.

Das Add-on schreibt nur die Deklaration. Registrierung, Meta-Boxen, register_post_meta mit REST-Schema, die Felder für Herkunft und Kennung und der Feldwähler der Feld-Blöcke kommen aus der Basis. Dein Typ verhält sich danach wie die eigenen Typen des Plugins.

beispiel-album/
├─ beispiel-album.php Plugin-Kopf (Requires Plugins: wp-club-elements), Autoloader
├─ includes/
│ ├─ Plugin.php hängt sich in wpce_register_post_types ein
│ ├─ AlbumCPT.php extends WPCE\Plugin\CustomPostTypes\BaseCPT
│ └─ AlbumCategoryTaxonomy extends WPCE\Plugin\CustomTaxonomies\BaseTaxonomy
├─ src/blocks/… eigene Blöcke, falls die Feld-Blöcke der Basis nicht reichen
└─ templates/, patterns Fallback-Templates und Patterns des Add-ons

Das Beispiel hier ist ein Fotoalbum, erfunden für diese Seite. Ein fertiges Add-on dafür gibt es nicht; die Basis testet denselben Weg mit einem Album-Typ (siehe ganz unten).

Eine Klasse, zwei Methoden. schema() beschreibt die Felder, get_post_type_args() die Argumente für register_post_type():

namespace Beispiel\Album;
use WPCE\Plugin\CustomPostTypes\BaseCPT;
use WPCE\Plugin\Schema\FieldDefinition;
use WPCE\Plugin\Schema\FieldType;
use WPCE\Plugin\Schema\MetaBoxDefinition;
use WPCE\Plugin\Schema\PostTypeSchema;
final class AlbumCPT extends BaseCPT {
public static function schema(): PostTypeSchema {
return new PostTypeSchema(
'wpce_album',
array(
new MetaBoxDefinition(
'wpce_album_details',
__( 'Album', 'beispiel-album' ),
array(
new FieldDefinition( '_wpce_album_photos', FieldType::Gallery, __( 'Photos', 'beispiel-album' ), '' ),
new FieldDefinition( '_wpce_album_description', FieldType::RichText, __( 'Description', 'beispiel-album' ), '' ),
new FieldDefinition( '_wpce_album_sections', FieldType::Reference, __( 'Departments', 'beispiel-album' ), null, array( 'post_type' => 'wpce_section', 'multiple' => true ) ),
)
),
)
);
}
protected function get_post_type_args(): array {
return array(
'labels' => array( 'name' => 'Alben', 'singular_name' => 'Album' ),
'public' => true,
'has_archive' => true,
'show_in_rest' => true,
'supports' => array( 'title', 'thumbnail' ),
'rewrite' => array( 'slug' => 'album' ),
);
}
}

FieldDefinition( key, type, label, admin_label, options ):

Argument Bedeutung
key Meta-Key, mit _wpce_-Präfix und dem Präfix des Add-ons dahinter (_wpce_album_…)
type ein FieldType (siehe unten)
label Beschriftung im Feldwähler der Blöcke
admin_label Beschriftung in der Meta-Box: null = wie label, '' = keine (der Titel der Meta-Box trägt sie)
options typabhängig: post_type (Relation, Reference), multiple, choices (Select), allowed_types (Contact), min und max (Number)

Die Feldtypen (WPCE\Plugin\Schema\FieldType):

Typ Für
Text, Textarea, Number, Select einfache Werte
RichText formatierter Text
Image, Gallery, Files ein Bild, mehrere Bilder, Dateien
Contact, Address, Bank, OpeningHours Kontakte, Adresse, Bankverbindung, Öffnungszeiten
Relation Verweis auf Einträge, mit Beschriftung je Verweis (die Leitung eines Kurses)
Reference Verweis auf Einträge, eine Meta-Zeile je ID
Place ein Ort: Standort oder freier Text
ScheduleRecurring, ScheduleEvent wiederkehrende Zeiten, datierte Tage
Rooms Räume

Jeder Typ bringt ein Admin-Feld, einen Sanitizer, ein REST-Schema und, außer bei Bank, einen Feld-Block mit.

In der Hauptdatei des Add-ons, sobald WP Club Elements da ist:

add_action( 'wpce_register_post_types', static function (): void {
\WPCE\Plugin\Schema\SchemaRegistry::register_post_type( \Beispiel\Album\AlbumCPT::class );
} );

Die Basis feuert diese Action auf init (Priorität 10), registriert danach alle Post Types, erst die der Basis, dann die der Add-ons, auf init:20 und das Meta auf init:21.

Geprüft an der Testwebsite mit dem Demoverein, mit genau dieser Klasse, ohne etwas zu speichern:

  • post_type_exists( 'wpce_album' ) ist wahr.
  • GET /wpce/v1/fields/wpce_album liefert _wpce_album_photos, _wpce_album_description und _wpce_album_sections.
  • get_registered_meta_keys( 'post', 'wpce_album' ) nennt diese drei Felder und dazu _wpce_source und _wpce_external_id, die Felder für Herkunft und Kennung.
  • Der Listen-Block hat für den neuen Typ einen eigenen Eintrag im Block-Einfüger. Seine Vorbelegung ist cards, das Bild das Titelbild, der Kurztext das Feld _wpce_album_description. Das Plugin leitet sie aus dem Schema ab. Willst du sie ändern, nutze den Filter wpce_entry_types.

Ab da gilt für das Add-on dasselbe wie für die Basis:

  • GET /wp/v2/wpce_album/<id> liefert die Felder unter meta als typisierte JSON-Strukturen. Das Schreiben läuft durch denselben Sanitizer wie die Meta-Box.
  • Identity::upsert() funktioniert, die Herkunft eines Eintrags steht in _wpce_source und _wpce_external_id.
  • Die Feld-Blöcke (wpce/field-gallery, wpce/field-richtext, wpce/field-relation und die anderen) rendern die Felder in der Abfrage und auf Einzelseiten.
  • FieldRegistry::get_value() liest ein Feld so, wie sein Typ es speichert.

Genauso wie in der Basis. Eine Klasse extends BaseTaxonomy mit get_taxonomy_args(), im Konstruktor parent::__construct( 'wpce_album_category', array( 'wpce_album' ) ):

namespace Beispiel\Album;
use WPCE\Plugin\CustomTaxonomies\BaseTaxonomy;
final class AlbumCategoryTaxonomy extends BaseTaxonomy {
public function __construct() {
parent::__construct( 'wpce_album_category', array( 'wpce_album' ) );
}
protected function get_taxonomy_args(): array {
return array(
'hierarchical' => true,
'labels' => array( 'name' => 'Albumarten', 'singular_name' => 'Albumart' ),
'show_ui' => true,
'show_admin_column' => true,
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'albumart' ),
);
}
}

Registriert wird sie in derselben Action. Sie hängt sich selbst auf init:25:

add_action( 'wpce_register_post_types', static function (): void {
\WPCE\Plugin\Schema\SchemaRegistry::register_post_type( \Beispiel\Album\AlbumCPT::class );
( new \Beispiel\Album\AlbumCategoryTaxonomy() )->register();
} );

Geprüft: Danach gibt es wpce_album_category und sie hängt an wpce_album.

Was die Basis für ihre Typen im Backend tut, bekommt ein Add-on-Typ aus seinem Schema, ohne eigenen Code:

  • Prüfungen nach dem Speichern. Jedes Feld wird nach seinem Typ geprüft (Zeit endet vor Beginn, Zeile ohne Uhrzeit, E-Mail, Telefonnummer, IBAN, BIC). Das Ergebnis steht als Hinweis oben auf der Maske, als Markierung „Bitte prüfen“ in der Liste und im Dashboard. Gespeichert wird immer. Drei Optionen einer FieldDefinition ergänzen, was der Typ allein nicht wissen kann:

    Option Wert Wirkung
    needed warning oder hint meldet das leere Feld
    format amount Textfeld, das einen Geldbetrag enthält
    at_most Meta-Key die Zahl darf das andere Feld nicht übersteigen (Mindestalter, Höchstalter)
    hide_when_empty true nur bei Textfeldern: Die Website lässt einen Eintrag ohne diesen Wert aus allen Listen und Seiten aus, so wie sie einen Mitgliedsbeitrag ohne Betrag auslässt (0 ist ein Wert). Das Backend, WP-CLI und Cron sehen den Eintrag weiter. Der Filter wpce_hide_incomplete_entries schaltet es ab. Nur gegen den Quelltext gelesen. Damit der Redakteur es erfährt, setz zusätzlich needed => warning

    Geprüft mit einem Feld needed => warning, einem Zahlenfeld needed => hint und einem Textfeld format => amount mit dem Inhalt „abc“. Das Ergebnis: eine Warnung „Noch nicht ausgefüllt.“, ein Hinweis „Noch nicht ausgefüllt.“ und eine Warnung „„abc“ ist kein Betrag.“

    Eigene Prüfungen hängt der Filter wpce_entry_problems an, siehe Filter und Hooks.

  • Hilfetext am Feld. Die Option help einer FieldDefinition: ein Satz unter der Beschriftung.

  • Listenspalten. Abteilungen (mit Filter), Standort, Personen, Dateizahl und Betrag folgen aus den Feldtypen. Jede Taxonomie mit show_admin_column bekommt einen Filter. Ein Typ mit datierten Tagen (FieldType::ScheduleEvent) wird nach Termin sortiert, Vergangenes ist ausgegraut. Ist das Feld needed => warning, bleiben Einträge ohne Tag aus den Listen der Website.

  • Leere Liste. Der Satz aus description der Post-Type-Argumente und ein Knopf zum Anlegen.

  • Link zur Doku. Liste, Maske und Kategorien eines Typs zeigen oben rechts „Hilfe zu dieser Seite“, sobald es eine Adresse dafür gibt. Für die Typen der Basis kennt das Plugin sie. Ein Add-on nennt seine über den Filter wpce_doc_url mit dem Schlüssel post_type:wpce_album, siehe Filter und Hooks. Derselbe Filter gibt dem Listen-Block, der den Typ zeigt (block:wpce/entries:wpce_album), und jedem eigenen Block des Add-ons (block:beispiel-album/album) den Link „Hilfe zu diesem Block“. Das sind je eine Zeile Code.

  • Templates und Patterns. Das Add-on registriert eigene Fallback-Templates (register_block_template( 'beispiel-album//single-wpce_album', … )) und Patterns. Das Theme darf sie überschreiben.
  • Eigene Blöcke. Nur, wenn die Feld-Blöcke nicht reichen. Der Namensraum ist beispiel-album/.
  • Migrationen. Eigene Änderungen am Speicherformat versioniert das Add-on selbst, mit eigener Option und eigenem Upgrader nach dem Muster der Basis, siehe Datenmigrationen. Eine Vorlage dafür kommt, sobald das erste Add-on sie braucht.
  • Übersetzungen. Eigene Text Domain (beispiel-album).

tests/integration/AddonApiTest.php in der Basis registriert einen Album-Typ auf genau diesem Weg und prüft Registrierung, Meta, Kennung, Feldwähler und Block-Rendering. Ein Add-on kann ihn als Vorlage für seine eigene Integrationssuite nehmen.

  • Die Registrierung außerhalb der Action. wpce_register_post_types feuert auf init:10. Wer den Typ erst nach init:10 meldet, verpasst die Runde, in der die Basis alle Typen registriert. Melde ihn darum in der Action.
  • Das Add-on lädt vor der Basis. Dann feuert die Action nie. Trag im Kopf Requires Plugins: wp-club-elements ein und prüfe im Zweifel mit class_exists( '\WPCE\Plugin\Schema\SchemaRegistry' ).
  • Ein Meta-Key ohne die beiden Präfixe. So ist es Konvention: _wpce_, dann das Präfix des Add-ons. Ohne sie kann dein Key mit dem Key eines Typs der Basis oder eines anderen Add-ons zusammenfallen.
  • Ein Typ ohne eigene Seite (public => false). Er hat keine Seite im Netz, und der Listen-Block bietet ihn nur an, wenn jemand ihn über wpce_entry_types definiert.
  • Eine Taxonomie ohne show_in_rest. Der Block-Editor findet sie nicht.