Skip to content

Eigene Feldtypen ​

Ein Feldtyp beschreibt sich selbst: Er liefert seinen Eintrag für die Builder-Palette, sein Inspector-Schema, sein Frontend-Markup und die Regeln zum Sanitizing und Validieren des übermittelten Werts. Wer FieldInterface implementiert und sich registriert, erscheint automatisch im Builder, im REST-Katalog und im Renderer.

Das Interface ​

Dingfelder\AdForm\Fields\FieldInterface verlangt 14 Methoden.

Identität und Palette ​

php
public function type(): string;
public function label(): string;
public function default_label(): string;
public function description(): string;
public function icon(): string;
public function category(): string;
public function supports_required(): bool;
MethodeBedeutung
type()Stabiler Typ-Schlüssel, z. B. text. Zugleich der Schlüssel in der Registry – ein zweiter Feldtyp mit demselben Wert überschreibt den ersten.
label()Lesbares Label für die Builder-Palette.
default_label()Label, das ein neu eingefügtes Feld auf dem Canvas bekommt.
description()Kurzbeschreibung in der Palette.
icon()Dashicon-Slug ohne das Präfix dashicons-, z. B. editor-textcolor.
category()Palette-Kategorie: basic, advanced, business, wordpress oder layout.
supports_required()Ob der Inspector einen Pflichtfeld-Schalter anzeigt.

Einstellungen und Inspector ​

php
/** @return array<string, mixed> */
public function default_settings(): array;

/** @return list<array<string, mixed>> */
public function inspector_fields(): array;

/**
 * @param array<string, mixed> $settings Raw settings.
 * @return array<string, mixed>
 */
public function sanitize_settings( array $settings ): array;

/** @return array<string, mixed> */
public function to_rest_array(): array;

default_settings() wird beim Einfügen eines Feldes in die Definition kopiert. inspector_fields() beschreibt die Steuerelemente im Inspector; jeder Eintrag hat mindestens name, path, type und label, optional help und options. Der path zeigt auf die Stelle in der Feldinstanz (label, required, key, settings.placeholder, …).

to_rest_array() liefert die Palette-/REST-Nutzlast. AbstractField baut sie bereits aus den übrigen Methoden zusammen – ergänzt um required_entitlements – sodass sie in der Regel nicht überschrieben werden muss.

Rendering und Übermittlung ​

php
/**
 * @param array<string, mixed> $field   Sanitized field from the definition.
 * @param array<string, mixed> $context Renderer context (values, errors, form_id).
 */
public function render_html( array $field, array $context = array() ): string;

public function sanitize_submitted_value( mixed $value ): mixed;

/**
 * @param array<string, mixed> $field Field instance.
 */
public function validate_submitted_value( mixed $value, array $field ): ?string;

validate_submitted_value() gibt null zurück, wenn der Wert in Ordnung ist, und sonst die übersetzte Fehlermeldung.

AbstractField als Basis ​

Dingfelder\AdForm\Fields\AbstractField implementiert alles außer type() und label(). In der Praxis wird immer davon abgeleitet – eine direkte FieldInterface-Implementierung lohnt sich nur für vollständig eigenes Markup.

Kategorie-Konstanten:

php
AbstractField::CATEGORY_BASIC;      // 'basic'
AbstractField::CATEGORY_ADVANCED;   // 'advanced'
AbstractField::CATEGORY_BUSINESS;   // 'business'
AbstractField::CATEGORY_WORDPRESS;  // 'wordpress'
AbstractField::CATEGORY_LAYOUT;     // 'layout'

Was die Basisklasse übernimmt ​

render_html() baut den kompletten Wrapper: CSS-Klassen (ad-form__field, ad-form__field--{type}, ad-form__field--w{width}), die Data-Attribute data-field-id, data-field-key, data-required und data-conditions, den Sichtbarkeitszustand aus dem Renderer-Kontext sowie Label, Beschreibung und Fehlermeldung mit den passenden aria-describedby-Verknüpfungen.

sanitize_settings() startet von default_settings() und schickt jeden Wert durch sanitize_setting(). sanitize_submitted_value() ruft sanitize_text_field() auf – bei Arrays für jeden Eintrag. validate_submitted_value() prüft nur die Pflichtfeld-Regel.

Erweiterungspunkte für Unterklassen ​

MethodeZweck
control_html( array $field, string $html_id, string $name, mixed $value, string $error, bool $required, array $context ): stringKompletter Inhalt des Wrappers inklusive Label und Fehlern
input_html( array $field, string $html_id, string $name, mixed $value, bool $required, array $described ): stringNur das Eingabe-Element – der übliche Einstiegspunkt
label_html( string $html_id, string $label, bool $required ): stringLabel-Markup
common_settings(): arrayBasis-Settings: admin_label, placeholder, default_value, description, css_class, width
identity_inspector(): arrayLabel, Required, Field key, Admin label
extra_inspector(): arrayTypspezifische Steuerelemente – hier wird meist ergänzt
style_inspector(): arrayBeschreibung, CSS-Klasse, Breite
width_options(): arrayAuswahl 100 / 75 / 66 / 50 / 33 / 25
extra_wrap_attributes( array $field, array $context ): arrayZusätzliche Attribute am Wrapper, z. B. data-formula
sanitize_setting( string $key, mixed $value ): mixedSanitizing eines einzelnen Settings-Werts
sanitize_options( mixed $value ): arrayNormalisiert {label, value}-Paare
is_empty_value( mixed $value ): boolLeer-Prüfung für die Pflichtfeld-Validierung
has_error_id( array $described ): boolOb unter den aria-describedby-IDs eine Fehler-ID ist

required_entitlements() ​

php
/** @return list<string> */
public function required_entitlements(): array;

Liefert die Feature-Schlüssel, die für diesen Feldtyp freigeschaltet sein müssen. AbstractField gibt eine leere Liste zurück – ein eigener Feldtyp ist also standardmäßig uneingeschränkt nutzbar. Die Methode steht bewusst auf der abstrakten Klasse und nicht auf FieldInterface, damit bestehende Interface-Implementierungen unverändert weiterlaufen. Der Wert erscheint zusätzlich als required_entitlements in to_rest_array().

Registrierung ​

Der Hook ad_form_register_fields feuert innerhalb von FieldRegistry::boot(), nachdem die Kern-Feldtypen registriert wurden, und bekommt die Registry als Parameter.

php
use Dingfelder\AdForm\Fields\FieldRegistry;

add_action(
	'ad_form_register_fields',
	static function ( FieldRegistry $registry ): void {
		$registry->register( new Acme\AdFormFields\PhoneField() );
	}
);

Direkt danach läuft der Filter ad_form_fields über die fertige Map type => FieldInterface. Er eignet sich zum Entfernen oder Ersetzen von Feldtypen:

php
add_filter(
	'ad_form_fields',
	static function ( array $fields ): array {
		unset( $fields['calculation'] );

		return $fields;
	}
);

Nur einmal booten

FieldRegistry::boot() läuft genau einmal pro Request und wird beim ersten has(), get(), all() oder to_rest_array() ausgelöst. Wer sich zu spät einhängt – etwa erst auf wp –, verpasst die Registrierung. ad_form_register_fields und ad_form_fields sind die einzigen verlässlichen Zeitpunkte.

Die Kern-Feldtypen sind text, email, number, textarea, select, checkbox, calculation und submit.

Vollständiges Beispiel: Telefonnummer-Feld ​

php
<?php
/**
 * Phone number field for AD Form.
 *
 * @package Acme\AdFormFields
 */

declare(strict_types=1);

namespace Acme\AdFormFields;

use Dingfelder\AdForm\Fields\AbstractField;
use Dingfelder\AdForm\Support\Html;

final class PhoneField extends AbstractField {

	public const TYPE = 'phone';

	public function type(): string {
		return self::TYPE;
	}

	public function label(): string {
		return __( 'Phone', 'acme-ad-form-fields' );
	}

	public function description(): string {
		return __( 'Telephone number with optional pattern check.', 'acme-ad-form-fields' );
	}

	public function icon(): string {
		return 'phone';
	}

	public function category(): string {
		return self::CATEGORY_ADVANCED;
	}

	/**
	 * @return array<string, mixed>
	 */
	public function default_settings(): array {
		$settings              = $this->common_settings();
		$settings['min_digits'] = '6';
		$settings['autocomplete_tel'] = true;

		return $settings;
	}

	/**
	 * @return list<array<string, mixed>>
	 */
	protected function extra_inspector(): array {
		$fields   = parent::extra_inspector();
		$fields[] = array(
			'name'  => 'min_digits',
			'path'  => 'settings.min_digits',
			'type'  => 'text',
			'label' => __( 'Minimum digits', 'acme-ad-form-fields' ),
			'help'  => __( 'Digits are counted after stripping spaces, slashes and dashes.', 'acme-ad-form-fields' ),
		);
		$fields[] = array(
			'name'  => 'autocomplete_tel',
			'path'  => 'settings.autocomplete_tel',
			'type'  => 'toggle',
			'label' => __( 'Enable browser autocomplete', 'acme-ad-form-fields' ),
		);

		return $fields;
	}

	/**
	 * @param array<string, mixed> $field     Field instance.
	 * @param list<string>         $described Description/error ids.
	 */
	protected function input_html( array $field, string $html_id, string $name, mixed $value, bool $required, array $described ): string {
		$settings    = is_array( $field['settings'] ?? null ) ? $field['settings'] : array();
		$placeholder = (string) ( $settings['placeholder'] ?? '' );

		return '<input' . Html::attributes(
			array(
				'type'             => 'tel',
				'id'               => $html_id,
				'name'             => $name,
				'value'            => (string) $value,
				'placeholder'      => '' !== $placeholder ? $placeholder : null,
				'class'            => 'ad-form__control',
				'required'         => $required,
				'inputmode'        => 'tel',
				'autocomplete'     => empty( $settings['autocomplete_tel'] ) ? null : 'tel',
				'aria-invalid'     => $this->has_error_id( $described ) ? 'true' : null,
				'aria-required'    => $required ? 'true' : null,
				'aria-describedby' => array() !== $described ? implode( ' ', $described ) : null,
			)
		) . ' />';
	}

	public function sanitize_submitted_value( mixed $value ): mixed {
		$text = trim( sanitize_text_field( (string) $value ) );

		return preg_replace( '/[^0-9+()\/\s-]/', '', $text ) ?? '';
	}

	/**
	 * @param array<string, mixed> $field Field instance.
	 */
	public function validate_submitted_value( mixed $value, array $field ): ?string {
		$required = parent::validate_submitted_value( $value, $field );

		if ( null !== $required ) {
			return $required;
		}

		$text = trim( (string) $value );

		if ( '' === $text ) {
			return null;
		}

		$settings = is_array( $field['settings'] ?? null ) ? $field['settings'] : array();
		$minimum  = max( 0, (int) ( $settings['min_digits'] ?? 0 ) );
		$digits   = preg_replace( '/\D/', '', $text ) ?? '';

		if ( strlen( $digits ) < $minimum ) {
			return __( 'Please enter a valid phone number.', 'acme-ad-form-fields' );
		}

		return null;
	}

	protected function sanitize_setting( string $key, mixed $value ): mixed {
		return match ( $key ) {
			'min_digits'       => (string) max( 0, min( 20, (int) $value ) ),
			'autocomplete_tel' => ! empty( $value ),
			default            => parent::sanitize_setting( $key, $value ),
		};
	}
}

Registrierung in der Add-on-Haupt-Datei:

php
<?php
declare(strict_types=1);

use Dingfelder\AdForm\Fields\FieldRegistry;

add_action(
	'ad_form_register_fields',
	static function ( FieldRegistry $registry ): void {
		$registry->register( new Acme\AdFormFields\PhoneField() );
	}
);

Was danach automatisch funktioniert ​

Sobald der Feldtyp registriert ist:

  • Er erscheint in der Builder-Palette unter seiner Kategorie und im REST-Katalog (GET /wp-json/ad-form/v1/fields).
  • FieldRegistry::create_instance( 'phone', $existing_keys ) erzeugt eine neue Feldinstanz mit eindeutigem Key, stabiler ID, default_label(), default_settings() und leerer Bedingungsgruppe.
  • Der FormRenderer ruft render_html() mit dem Renderer-Kontext auf.
  • Der SubmitService ruft erst sanitize_submitted_value(), dann validate_submitted_value().
  • Bedingte Logik und Multi-Step funktionieren ohne Zutun, weil sie am Wrapper und an der Definition ansetzen, nicht am Feldtyp.

Verwandte Referenzen ​

Digitale Lösungen. Persönlich. Zukunftssicher.