Zum Inhalt springen

Eyivo Plugin-API v1

Eyivo mit Plugins erweitern.

Kontrollierte Erweiterungen, registrierte Formulare und von Eyivo gerenderte Plugin-Seiten.

Öffentlicher v1-Vertrag

Der Vertrag ist öffentlich. Nur freigegebene Eyivo-Pakete sind installierbar; Drittanbieterinstallation bleibt deaktiviert.

Ein Vertrag für Website-Erweiterungen.

API-Vertrag

Plugin API v1

Theme-Kompatibilität

Theme API v1

Ausführungslaufzeit

eyivo-php-subset-1

Identität, Versionen und Vertrauen

Jedes Plugin hat eine namespace/name-Kennung, eine unabhängige semantische Paketversion und genau die API-Version "1". Der Namensraum eyivo ist reserviert. Der Typ ist system oder standard; die Herkunft ist system, verified oder third_party. In dieser Version werden nur von der Plattform genehmigte Pakete mit Herkunft system akzeptiert. Pakete können sich weder einen Pflichtstatus noch neue Fähigkeiten selbst zuweisen.

Installation und Konfiguration

Der zugewiesene Master besitzt die Installationsdatensätze. Installieren, Aktivieren, Deaktivieren, Deinstallieren und Aktualisieren laufen unter einer Website-Sperre und in einer Datenbanktransaktion. Abhängigkeiten werden zuerst installiert; fehlende Pakete, Zyklen, Konflikte und Downgrades werden abgelehnt. Aktive Abhängigkeiten und von der Plattform vorgeschriebene Pakete können nicht deaktiviert oder entfernt werden. Upgrade-Hooks erhalten previous_version und können ihre Konfiguration im eigenen Namensraum über die API migrieren. Die Konfiguration verwendet deklarierte Skalar-/Objektschemas und Standardwerte; unbekannte Schlüssel werden abgelehnt. Lebenszyklus-Handler müssen ein Array zurückgeben und dürfen nicht von externen Nebenwirkungen abhängen.

Einstellungen mit klarem Geltungsbereich.

Jede Website und jedes Plugin hat eine eigene Konfiguration in der zugewiesenen Master-Datenbank. Das App-Panel liest und speichert über die authentifizierte Internal API über WireGuard; es besitzt keine Zugangsdaten für Master-Datenbanken. Gemeinsame Dienste unter plugins.eyivo.com verwenden für ihren Dienstzustand eine separate Datenbank.

Deklarierte Felder

Höchstens 50

Objektcontainer und verschachtelte Eigenschaften teilen sich dieselbe Gesamtzahl.

Gesamte Konfiguration

Höchstens 32 KB

32.768 Bytes kompaktes UTF-8-JSON, einschließlich Schlüsseln, Werten und Standardwerten.

Verschachtelungstiefe

Höchstens 3 Ebenen

Felder der obersten Ebene beginnen auf Ebene eins.

Der Host validiert Schemas bei der Registrierung eines Pakets und prüft die gesamte Konfiguration bei jedem Schreibvorgang. Unbekannte Schlüssel werden abgelehnt. Panelsteuerelemente und Browservalidierung ersetzen nicht die serverseitige Durchsetzung.

Schemas unterstützen Zeichenketten, Ganzzahlen, boolesche Werte und Objekte mit expliziten Eigenschaften. Arrays und beliebige Maps sind keine Konfigurationstypen. Zeichenkettenlängen werden in Bytes gemessen. Felder der obersten Ebene beginnen auf Ebene eins; Objektcontainer zählen sowohl zur Gesamtzahl der Felder als auch zur Verschachtelungstiefe.

Speichervorgänge enthalten die Plugin-Revision der Website. Eine veraltete Revision liefert einen Konflikt, statt neuere Einstellungen zu überschreiben. Laden Sie neu und prüfen Sie die aktuellen Werte, bevor Sie es erneut versuchen. Änderungen an einem beliebigen Plugin der Website ändern diese Revision, auch Deaktivieren, erneutes Aktivieren, Deinstallieren und Neuinstallieren.

Grenzen des Konfigurationsschemas und Kodierungsregeln ansehen
{
    "fields": 50,
    "depth": 3,
    "bytes": 32768,
    "counting": "Every declared property counts, including object containers. Top-level fields are level one. JSON bytes include recursively merged defaults, keys and values, encoded compactly with UTF-8 characters and slashes unescaped.",
    "types": [
        "string",
        "integer",
        "boolean",
        "object"
    ],
    "policy": "Objects require explicit properties; unknown keys and arrays are rejected. Strings default to 4096 bytes and support min/max up to 32768 bytes. Integer min/max and scalar enum choices are supported. constructor, prototype and __proto__ are reserved keys."
}

Ihre Seiten. Eyivo-Komponenten.

Ein Plugin besitzt seine Seitendefinitionen, sein Layout, lokalisierte Inhalte, Formulare und deklarierten Aktionen. Die zentrale App stellt die Definitionen mit vertrauenswürdigen Komponenten im bestehenden Benutzerpanel dar. Ein Klick auf ein Plugin öffnet dessen deklarierte Einstiegsseite, etwa die Konfiguration oder den Posteingang für Einsendungen.

  1. Seite deklarieren

    Registrieren Sie Seitendefinitionen und Konfigurationsbindungen im Paket. Optionale Menülinks zeigen auf Seiten desselben Plugins.

  2. In der App darstellen

    Die App prüft den Website-Zugriff und erhält validierte JSON-Definitionen und Daten vom zugewiesenen Master. Plugin-PHP oder Blade wird weder an die App übertragen noch dort ausgeführt.

  3. Auf dem Master ausführen

    Autorisierte Aktionen laufen über die Internal API zu eingeschränkten Handlern. Der Master setzt Plugin-Zustand, Fähigkeiten, Schemas und Konfigurationsgrenzen durch.

Eine einstufige Plugins-Gruppe

Berechtigte Links erscheinen für die ausgewählte Website direkt unter Plugins. Verschachtelte Untermenüs werden nicht unterstützt. Die Gruppe bleibt ausgeblendet, wenn keine Links sichtbar sind. Deaktivieren oder Deinstallieren entfernt die Links und sperrt den direkten Seiten- und Aktionszugriff, auch für Anfragen aus bereits geöffneten Tabs. Erneutes Aktivieren stellt berechtigte Links wieder her.

Pakete ohne Paneldeklaration und deaktivierte Pakete öffnen aus der Plugin-Liste eine Informationsansicht. Deaktivierte Pakete stellen ihre registrierten Panelseiten oder Aktionen nicht bereit. Die menschliche Verifizierung liefert eine schreibgeschützte Paketseite; ihre Sicherheitsvorgaben bleiben plattformgesteuert.

Website-Mitgliedschaft und deklarierte Aktionen

Seiten und benutzerdefinierte Aktionen deklarieren die Berechtigung „Ansehen“ oder „Verwalten“. Aktuelle Website-Mitglieder können berechtigte Seiten ansehen und schreibgeschützte Ansichtsaktionen ausführen; „Verwalten“ erfordert die Rolle des Website-Inhabers. Die App leitet den vertrauenswürdigen Akteur aus der angemeldeten Mitgliedschaft ab. Die integrierten Aktionen zum Speichern/Zurücksetzen der Konfiguration erfordern „Verwalten“; benutzerdefinierte Handler verwenden deklarierte Parameter- und Antwortschemas. Jede Seite erlaubt nur ihre aufgeführten Aktions-IDs.

Deklarationen verleihen keine Befugnisse

Das Laden einer Seite ist schreibgeschützt. Eine Seite oder ein Menü kann kein uneingeschränktes PHP, keinen Datenbankzugriff, keine beliebigen Anwendungsrouten und keine Skripte im authentifizierten Panel gewähren. Bestehende Pakete ohne Paneldeklaration funktionieren weiter. Die Installation von Drittanbieterpaketen bleibt deaktiviert.

Unterstützte Komponenten

Stellen Sie die Seite aus diesen vertrauenswürdigen Komponenten zusammen. Felder sind an deklarierte Konfigurations- oder Aktionseingaben gebunden; Vorschauen zeigen reinen Text, und Tabellen sind schreibgeschützt. Rohes HTML, beliebige Skripte, Ausdrücke und bedingtes Rendern werden nicht unterstützt.

  • stack
  • grid
  • card
  • text
  • alert
  • field
  • button
  • preview
  • table
  • submission-list
  • submission-detail
Versionierten Panel-API-Vertrag und Grenzen ansehen
{
    "api": "2",
    "execution": "Plugins own JSON page definitions and restricted master-side handlers. App receives validated localized JSON only; plugin PHP, Blade, JavaScript, CSS, raw HTML and handler paths are not transferred or executed in the panel.",
    "capabilities": [
        "panel.render",
        "panel.action",
        "site.settings.read",
        "site.settings.write"
    ],
    "components": [
        "stack",
        "grid",
        "card",
        "text",
        "alert",
        "field",
        "button",
        "preview",
        "table",
        "submission-list",
        "submission-detail"
    ],
    "controls": [
        "text",
        "textarea",
        "number",
        "checkbox",
        "select"
    ],
    "action_types": [
        "configuration.save",
        "configuration.reset",
        "handler"
    ],
    "permissions": {
        "view": [
            "owner",
            "viewer"
        ],
        "manage": [
            "owner"
        ]
    },
    "limits": {
        "pages": 10,
        "menu_links": 10,
        "menu_depth": 1,
        "actions": 20,
        "action_input_bytes": 32768,
        "components_per_page": 100,
        "component_depth": 8,
        "page_file_bytes": 65536,
        "response_bytes": 262144,
        "table_columns": 10,
        "table_rows": 100
    },
    "configuration": "Configuration.save receives a complete configuration object as input; configuration.reset accepts empty input. Both require manage permission and site.settings.write. All panel actions require panel.action. Custom handlers declare view (read-only even for owners) or manage permission; their input/response use declared bounded schemas. Page loaders are read-only.",
    "concurrency": "Every action requires the page revision. The master compares site.plugin_revision under the existing site row lock; stale saves return HTTP 409. Deactivation or uninstall blocks pages/actions and removes derived menus.",
    "localization": "Definitions use locale maps with a required en fallback. The master localizes text before sending JSON to app. Field labels and input guidance are required.",
    "supported_versions": [
        "1",
        "2"
    ],
    "submissions": {
        "version": "2",
        "declaration": "submission-list declares form_type and detail_page; submission-detail declares the same form_type and list_page. Every registered form type requires mutually linked viewable pages and a flat sidebar menu entry to its list.",
        "execution": "The host resolves current site/plugin ownership and supplies paginated summaries or a stored historical detail snapshot. App constructs all navigation and action URLs. Package PHP never reaches app.",
        "contexts": "Page loaders receive authenticated actor and validated query/record context. Plugin operations cannot override the current site or plugin. Creating records requires an out-of-band host-issued FormProcessingContext unavailable to interpreted package code.",
        "state": "Reading, archive and workflow state are independent. Record mutations require the current record_revision; configuration still uses plugin_revision. Viewing never marks a submission read. Viewers may read; existing owner/manage permissions govern mutations."
    }
}

Formulare und Einsendungen

Forms API 1 bietet sieben freigegebene Verarbeiter. Themes gestalten Layout und Stil; Plugins definieren Schema, Validierung und Ablauf. Der Host übernimmt Speicherung, Verifizierung und Benachrichtigung.

System-PluginFormulartyp
eyivo/contactKontakt contact
eyivo/quote-requestAngebotsanfrage quote_request
eyivo/product-inquiryProduktanfrage product_inquiry
eyivo/appointment-requestTerminanfrage appointment
eyivo/course-applicationKursbewerbung course_application
eyivo/reservation-requestReservierungsanfrage reservation
eyivo/property-inquiryImmobilienanfrage property_inquiry

Ein Formular, mehrere Platzierungen

Die Einrichtung erzeugt eine beständige Standard-UUID je Website/Plugin/Typ. Weitere Platzierungen verwenden dieselbe Identität; benannte neue Formulare erhalten eigene UUIDs. Themewechsel und Neuinstallation erhalten den Verlauf. Themes verwenden portable Aliase; Kunden wählen gespeicherte Formulare statt UUIDs einzugeben.

{
  "plugins": ["eyivo/contact", "eyivo/quote-request"],
  "forms": {
    "contact": {"plugin":"eyivo/contact","type":"contact","schema_version":1,"instance":"default"},
    "quote": {"plugin":"eyivo/quote-request","type":"quote_request","schema_version":1,"instance":"default"}
  }
}

Kontaktvorlagen verwenden forms.contact, Formularabschnitte component.form. Ersetzen Sie form_preset durch form_binding. Rendern Sie die gelieferten Felder, Hinweise, action, CSRF, signierten form_context und verification_html. Nur erlaubte Darstellungsänderungen und Zusatzfelder sind zulässig; Pflichtvalidierung bleibt bestehen.

forms.contact · component.form · form_binding · form.fields · form.action · form.csrf_token · form.context · form.verification_html

Zwei Ansichten, ein Datensatz

Gemeinsamer Posteingang und Plugin-Listen/Details zeigen dieselben Datensätze. Formularpakete benötigen Panel API 2 mit submission-list/submission-detail und einem flachen Menülink. Panel API 1 bleibt unterstützt. Lesen markiert nicht automatisch als gelesen; Lese-, Archiv- und Ablaufstatus sind getrennt, Änderungen nutzen record_revision. Deaktivierung entfernt Plugin-Seiten; der gemeinsame Posteingang erhält historische Snapshots.

{"components":[{"type":"submission-list","form_type":"contact","detail_page":"submission"}]}
{"components":[{"type":"submission-detail","form_type":"contact","list_page":"submissions"}]}

Kundendaten und Berechtigungen

Formulardaten liegen in der Inhaltsdatenbank der Website und zählen zum vorhandenen max_db_bytes-Paketlimit. Keine zusätzliche Anzahl- oder Aufbewahrungsquote. Konfiguration bleibt in der Masterdatenbank mit 50 Feldern, 32 KB und drei Ebenen. Mitglieder lesen; Änderungen brauchen Eigentümer-/Verwaltungsrechte. Plugins erhalten keinen direkten Datenbankzugriff; PHP/Blade gelangt nicht zur App.

Geprüft speichern und benachrichtigen

Der Host prüft Kontext, Antworten und Human Verification und speichert Datensatz, Nutzung und Benachrichtigungsauftrag zusammen. Identische Versuche lassen sich 24 Stunden ohne Duplikate wiederholen. SMTP läuft anschließend mit Wiederholungen; Mailfehler löschen keine Einsendung. Ohne Formular-Empfänger gilt die Unternehmensadresse; ist keine der beiden Adressen gültig, wird ohne E-Mail gespeichert.

Alle ausgelieferten Themes verwenden diesen Vertrag. Alte contact-POSTs liefern 410; Kontaktseiten bleiben per GET erreichbar. Termin- und Reservierungsanfragen bestätigen keine Buchung. Drittanbieterprozessoren, Dateiuploads und beliebige externe Ziele gehören nicht dazu.

POST /__eyivo/forms/{UUID}/submissions
GET /__eyivo/forms/{UUID}/challenge

Themes und Formularplatzierung

Themes deklarieren genehmigte Abhängigkeiten und portable Formular-Aliasse in theme.json. Eine Bindung bezeichnet plugin, type, schema_version und instance; bei der Einrichtung wird sie zu einer dauerhaften Formular-UUID der Website aufgelöst. Kontaktvorlagen verwenden forms.contact, Lead-Komponenten verwenden component.form mit form_binding. Stellen Sie die gelieferten Felder, action, das CSRF-Token, den signierten form_context und verification_html dar. Verwenden Sie nur deklarierte Darstellungsänderungen oder erlaubte zusätzliche Felder; die verpflichtende Plugin-Validierung kann nicht abgeschwächt werden. Alle ausgelieferten Themes verwenden diesen Vertrag. Externe Aktionen und der eingestellte Kontakt-POST sind keine unterstützten Übermittlungswege.

{"component_type":"eyivo:lead_form","data":{"form_binding":"contact"}}
Forms API 1
{
    "api": "1",
    "approval": "Only configured, approved Eyivo system form packages may declare forms; they require Human Verification and mandatory Panel API 2 submission pages.",
    "schema": "forms.types maps stable identifiers to schema_version, localized label, fields, extensions, workflow and a restricted processor file. Fields declare scalar type, compatible control, required flag, localized label and guidance; strings require explicit max. No PHP, SQL, validation expressions or unknown fields are accepted.",
    "field_types": [
        "string",
        "integer",
        "boolean"
    ],
    "controls": [
        "text",
        "textarea",
        "email",
        "tel",
        "url",
        "date",
        "datetime-local",
        "select",
        "number",
        "checkbox"
    ],
    "presentation_extensions": [
        "label",
        "placeholder",
        "help",
        "order"
    ],
    "storage": "The host owns site content storage. Plugin packages receive no credentials, database connection, SQL or unrestricted internal services. Site ownership is injected; schema snapshots remain readable without installed plugin code.",
    "quotas": "Submission data consumes the existing package database allowance. Configuration limits of 50 fields / 32768 bytes / three levels do not cap form fields or accumulated submissions. Existing runtime, request and pagination bounds are technical transport protections.",
    "identity": "Each site/plugin/type has a retained default form UUID created during authorized setup. Themes resolve portable aliases; rendering does not create instances. Additional named instances receive their own UUID. Theme changes and plugin reinstall preserve history.",
    "transport": {
        "encoding": "Escaped JSON byte counts match the restricted plugin interpreter; these technical ceilings do not limit accumulated form or submission counts.",
        "schema_bytes": 49152,
        "schema_accounting": "Definition plus one additional workflow copy, reserving the repeated workflow information on detail responses.",
        "answers_bytes": 49152,
        "source_bytes": 4096,
        "processor_context_bytes": 114688,
        "maximum_api_page": 25,
        "instances_summary": [
            "id",
            "plugin_id",
            "form_type",
            "name",
            "schema_version_id",
            "record_revision",
            "is_default"
        ]
    }
}

Beispiel für ein Plugin-Manifest

{
    "id": "eyivo/human-verification",
    "name": "Human Verification",
    "version": "1.1.0",
    "api": "1",
    "type": "system",
    "origin": "system",
    "required": true,
    "capabilities": [
        "form.inject",
        "form.validate",
        "frontend.assets",
        "public.endpoint",
        "verification.challenge",
        "verification.verify",
        "panel.render"
    ],
    "panel": {
        "api": "1",
        "entry": "settings",
        "pages": {
            "settings": {
                "title": {
                    "en": "Human Verification",
                    "id": "Verifikasi Manusia",
                    "tr": "\u0130nsan Do\u011frulama",
                    "de": "Menschliche Verifizierung",
                    "es": "Verificaci\u00f3n humana",
                    "fr": "V\u00e9rification humaine",
                    "pt": "Verifica\u00e7\u00e3o humana",
                    "it": "Verifica umana",
                    "ar": "\u0627\u0644\u062a\u062d\u0642\u0642 \u0627\u0644\u0628\u0634\u0631\u064a"
                },
                "permission": "view",
                "view": "panel/settings.json",
                "actions": []
            }
        },
        "menu": [],
        "actions": []
    },
    "dependencies": {
        "plugins": []
    },
    "conflicts": [],
    "configuration": [],
    "defaults": [],
    "hooks": {
        "form.before_submit": "render.php",
        "form.validate": "validate.php"
    },
    "render_hook": "form.before_submit",
    "required_validators": [
        "contact"
    ],
    "lifecycle": [],
    "assets": [
        {
            "path": "assets/verification.css",
            "type": "style"
        },
        {
            "path": "assets/verification.js",
            "type": "script"
        }
    ],
    "endpoints": {
        "challenge": {
            "method": "GET",
            "handler": "challenge.php",
            "parameters": {
                "form_id": {
                    "type": "string",
                    "required": true,
                    "enum": [
                        "contact"
                    ]
                },
                "locale": {
                    "type": "string",
                    "max": 10
                }
            },
            "response": {
                "token": {
                    "type": "string",
                    "max": 4096
                },
                "image": {
                    "type": "string",
                    "max": 65536
                },
                "expires_in": {
                    "type": "integer"
                }
            }
        }
    }
}
System-Plugin · eyivo/contact · Forms API 1 / Panel API 2
{
    "id": "eyivo/contact",
    "name": "Contact",
    "version": "1.0.0",
    "api": "1",
    "type": "system",
    "origin": "system",
    "required": false,
    "capabilities": [
        "forms.submissions.create",
        "forms.submissions.read",
        "forms.submissions.manage",
        "forms.instances.read",
        "forms.instances.manage",
        "panel.render",
        "panel.action"
    ],
    "dependencies": {
        "plugins": [
            "eyivo/human-verification"
        ]
    },
    "conflicts": [],
    "configuration": [],
    "defaults": [],
    "hooks": [],
    "required_validators": [],
    "lifecycle": [],
    "assets": [],
    "endpoints": [],
    "forms": {
        "api": "1",
        "types": {
            "contact": {
                "schema_version": 1,
                "label": {
                    "en": "Contact",
                    "id": "Kontak",
                    "tr": "İletişim",
                    "de": "Kontakt",
                    "es": "Contacto",
                    "fr": "Contact",
                    "pt": "Contato",
                    "it": "Contatti",
                    "ar": "التواصل",
                    "ru": "Контакт",
                    "nl": "Contact",
                    "pl": "Kontakt",
                    "ja": "お問い合わせ",
                    "ko": "문의",
                    "zh": "联系"
                },
                "fields": {
                    "name": {
                        "type": "string",
                        "control": "text",
                        "required": true,
                        "label": {
                            "en": "Name",
                            "id": "Nama",
                            "tr": "Ad",
                            "de": "Name",
                            "es": "Nombre",
                            "fr": "Nom",
                            "pt": "Nome",
                            "it": "Nome",
                            "ar": "الاسم",
                            "ru": "Имя",
                            "nl": "Naam",
                            "pl": "Imię i nazwisko",
                            "ja": "氏名",
                            "ko": "이름",
                            "zh": "姓名"
                        },
                        "max": 255,
                        "placeholder": {
                            "en": "Your full name",
                            "id": "Nama lengkap Anda",
                            "tr": "Adınız ve soyadınız",
                            "de": "Ihr vollständiger Name",
                            "es": "Tu nombre completo",
                            "fr": "Votre nom complet",
                            "pt": "Seu nome completo",
                            "it": "Il tuo nome completo",
                            "ar": "اسمك الكامل",
                            "ru": "Ваше полное имя",
                            "nl": "Uw volledige naam",
                            "pl": "Twoje imię i nazwisko",
                            "ja": "氏名を入力してください",
                            "ko": "성명을 입력하세요",
                            "zh": "请输入您的姓名"
                        },
                        "autocomplete": "name"
                    },
                    "email": {
                        "type": "string",
                        "control": "email",
                        "required": true,
                        "label": {
                            "en": "Email",
                            "id": "Email",
                            "tr": "E-posta",
                            "de": "E-Mail",
                            "es": "Correo electrónico",
                            "fr": "E-mail",
                            "pt": "E-mail",
                            "it": "Email",
                            "ar": "البريد الإلكتروني",
                            "ru": "Электронная почта",
                            "nl": "E-mail",
                            "pl": "E-mail",
                            "ja": "メールアドレス",
                            "ko": "이메일",
                            "zh": "电子邮箱"
                        },
                        "max": 254,
                        "placeholder": {
                            "en": "Your email address",
                            "id": "Alamat email Anda",
                            "tr": "E-posta adresiniz",
                            "de": "Ihre E-Mail-Adresse",
                            "es": "Tu dirección de correo",
                            "fr": "Votre adresse e-mail",
                            "pt": "Seu endereço de e-mail",
                            "it": "Il tuo indirizzo email",
                            "ar": "عنوان بريدك الإلكتروني",
                            "ru": "Ваш адрес электронной почты",
                            "nl": "Uw e-mailadres",
                            "pl": "Twój adres e-mail",
                            "ja": "メールアドレスを入力してください",
                            "ko": "이메일 주소를 입력하세요",
                            "zh": "请输入您的电子邮箱地址"
                        },
                        "autocomplete": "email"
                    },
                    "phone": {
                        "type": "string",
                        "control": "tel",
                        "required": false,
                        "label": {
                            "en": "Phone",
                            "id": "Telepon",
                            "tr": "Telefon",
                            "de": "Telefon",
                            "es": "Teléfono",
                            "fr": "Téléphone",
                            "pt": "Telefone",
                            "it": "Telefono",
                            "ar": "الهاتف",
                            "ru": "Телефон",
                            "nl": "Telefoon",
                            "pl": "Telefon",
                            "ja": "電話番号",
                            "ko": "전화번호",
                            "zh": "电话"
                        },
                        "max": 50,
                        "placeholder": {
                            "en": "Country code and phone number",
                            "id": "Kode negara dan nomor telepon",
                            "tr": "Ülke kodu ve telefon numarası",
                            "de": "Ländervorwahl und Telefonnummer",
                            "es": "Código de país y teléfono",
                            "fr": "Indicatif du pays et numéro",
                            "pt": "Código do país e telefone",
                            "it": "Prefisso internazionale e numero",
                            "ar": "رمز الدولة ورقم الهاتف",
                            "ru": "Код страны и номер телефона",
                            "nl": "Landcode en telefoonnummer",
                            "pl": "Numer telefonu z kodem kraju",
                            "ja": "国番号と電話番号を入力してください",
                            "ko": "국가 코드와 전화번호를 입력하세요",
                            "zh": "请输入国家代码和电话号码"
                        },
                        "autocomplete": "tel"
                    },
                    "message": {
                        "type": "string",
                        "control": "textarea",
                        "required": true,
                        "label": {
                            "en": "Message",
                            "id": "Pesan",
                            "tr": "Mesaj",
                            "de": "Nachricht",
                            "es": "Mensaje",
                            "fr": "Message",
                            "pt": "Mensagem",
                            "it": "Messaggio",
                            "ar": "الرسالة",
                            "ru": "Сообщение",
                            "nl": "Bericht",
                            "pl": "Wiadomość",
                            "ja": "メッセージ",
                            "ko": "메시지",
                            "zh": "留言"
                        },
                        "max": 20000,
                        "placeholder": {
                            "en": "Describe your question or request",
                            "id": "Jelaskan pertanyaan atau permintaan Anda",
                            "tr": "Sorunuzu veya talebinizi açıklayın",
                            "de": "Beschreiben Sie Ihre Frage oder Anfrage",
                            "es": "Describe tu pregunta o solicitud",
                            "fr": "Décrivez votre question ou demande",
                            "pt": "Descreva sua dúvida ou solicitação",
                            "it": "Descrivi la tua domanda o richiesta",
                            "ar": "صف سؤالك أو طلبك",
                            "ru": "Опишите ваш вопрос или запрос",
                            "nl": "Beschrijf uw vraag of verzoek",
                            "pl": "Opisz swoje pytanie lub prośbę",
                            "ja": "ご質問やご要望を入力してください",
                            "ko": "질문이나 요청 내용을 설명하세요",
                            "zh": "请描述您的问题或需求"
                        }
                    }
                },
                "extensions": {
                    "presentation": [
                        "label",
                        "placeholder",
                        "help",
                        "order"
                    ],
                    "field_types": [
                        "string",
                        "integer",
                        "boolean"
                    ]
                },
                "workflow": {
                    "initial": "received",
                    "states": {
                        "received": {
                            "en": "Received",
                            "id": "Diterima",
                            "tr": "Alındı",
                            "de": "Eingegangen",
                            "es": "Recibido",
                            "fr": "Reçu",
                            "pt": "Recebido",
                            "it": "Ricevuto",
                            "ar": "تم الاستلام"
                        },
                        "in_progress": {
                            "en": "In progress",
                            "id": "Sedang diproses",
                            "tr": "İşlemde",
                            "de": "In Bearbeitung",
                            "es": "En curso",
                            "fr": "En cours",
                            "pt": "Em andamento",
                            "it": "In corso",
                            "ar": "قيد المعالجة"
                        },
                        "completed": {
                            "en": "Completed",
                            "id": "Selesai",
                            "tr": "Tamamlandı",
                            "de": "Abgeschlossen",
                            "es": "Completado",
                            "fr": "Terminé",
                            "pt": "Concluído",
                            "it": "Completato",
                            "ar": "مكتمل"
                        }
                    },
                    "transitions": {
                        "start": {
                            "from": [
                                "received"
                            ],
                            "to": "in_progress",
                            "label": {
                                "en": "Start processing",
                                "id": "Mulai proses",
                                "tr": "İşleme al",
                                "de": "Bearbeitung starten",
                                "es": "Iniciar gestión",
                                "fr": "Commencer le traitement",
                                "pt": "Iniciar atendimento",
                                "it": "Avvia elaborazione",
                                "ar": "بدء المعالجة"
                            }
                        },
                        "complete": {
                            "from": [
                                "received",
                                "in_progress"
                            ],
                            "to": "completed",
                            "label": {
                                "en": "Complete",
                                "id": "Selesaikan",
                                "tr": "Tamamla",
                                "de": "Abschließen",
                                "es": "Completar",
                                "fr": "Terminer",
                                "pt": "Concluir",
                                "it": "Completa",
                                "ar": "إكمال"
                            }
                        },
                        "reopen": {
                            "from": [
                                "completed"
                            ],
                            "to": "received",
                            "label": {
                                "en": "Reopen",
                                "id": "Buka kembali",
                                "tr": "Yeniden aç",
                                "de": "Erneut öffnen",
                                "es": "Reabrir",
                                "fr": "Rouvrir",
                                "pt": "Reabrir",
                                "it": "Riapri",
                                "ar": "إعادة الفتح"
                            }
                        }
                    }
                },
                "processor": "submit.php"
            }
        }
    },
    "panel": {
        "api": "2",
        "entry": "submissions",
        "pages": {
            "submissions": {
                "title": {
                    "en": "Contact",
                    "id": "Kontak",
                    "tr": "İletişim",
                    "de": "Kontakt",
                    "es": "Contacto",
                    "fr": "Contact",
                    "pt": "Contato",
                    "it": "Contatti",
                    "ar": "التواصل"
                },
                "permission": "view",
                "view": "panel/submissions.json",
                "actions": []
            },
            "submission": {
                "title": {
                    "en": "Submission",
                    "id": "Kiriman",
                    "tr": "Form kaydı",
                    "de": "Einsendung",
                    "es": "Solicitud",
                    "fr": "Demande",
                    "pt": "Solicitação",
                    "it": "Richiesta",
                    "ar": "الطلب"
                },
                "permission": "view",
                "view": "panel/submission.json",
                "actions": []
            }
        },
        "menu": [
            {
                "page": "submissions",
                "label": {
                    "en": "Contact",
                    "id": "Kontak",
                    "tr": "İletişim",
                    "de": "Kontakt",
                    "es": "Contacto",
                    "fr": "Contact",
                    "pt": "Contato",
                    "it": "Contatti",
                    "ar": "التواصل"
                },
                "icon": "list",
                "order": 100
            }
        ],
        "actions": []
    }
}

Deklarierte Fähigkeiten

Deklarieren Sie die Fähigkeiten, die ein Paket benötigt. Der Host prüft sie, bevor er eine Operation erlaubt.

  • frontend.render
  • frontend.assets
  • form.inject
  • form.validate
  • public.endpoint
  • site.settings.read
  • site.settings.write
  • verification.challenge
  • verification.verify
  • panel.render
  • panel.action
  • forms.submissions.create
  • forms.submissions.read
  • forms.submissions.manage
  • forms.instances.read
  • forms.instances.manage

Hooks und Lebenszyklusereignisse

Frontend-Hooks

  • page.head
  • page.body.start
  • page.body.end
  • form.start
  • form.before_fields
  • form.after_fields
  • form.before_submit
  • form.after_submit
  • form.end

Backend-Hooks

  • form.before_validate
  • form.validate
  • form.after_validate
  • form.before_process
  • form.after_process

Lebenszyklusereignisse

  • install
  • enable
  • disable
  • uninstall
  • upgrade

Validierung und Verarbeitungsreihenfolge

Die gemeinsame Formular-Engine validiert vor der Verifizierung Mandant, signierten Platzierungskontext, aktuelles Plugin/Schema und deklarierte Antworten. Sie bindet die Wiederherstellung des Nachweises an den Versuch und die kanonische Nutzlast. Nach erneuter Prüfung von Zustand und Kontingent speichert der zuständige eingeschränkte Prozessor über die Host-API in einer Inhaltstransaktion zusammen mit der Versuchsquittung, der Nutzungsänderung und der Benachrichtigungsaufgabe. Identische Wiederholungen stellen die Quittung wieder her, ohne erneut zu speichern. Der E-Mail-Versand erfolgt danach und kann wiederholt werden; ein Mailfehler entfernt nie eine gespeicherte Einsendung.

Eingeschränkte PHP-Laufzeit

eyivo-php-subset-1

Kontext und Ergebnisse

Handler erhalten $context als JSON-kompatible Daten: site_id, site_public_id, plugin_id, api, configuration, locale sowie die jeweils zutreffenden page/form_id/form_type/action/method/fields/validation_state. Die Website-Identität wird immer vom Host geliefert. Render-Hooks geben ein html-Feld zurück; Validierungs-Hooks geben ein boolesches valid-Feld zurück. Jeder verpflichtende Validator muss ausdrücklich true zurückgeben. Andere Backend-Hooks geben ein Array zurück. Übermittelte Felder sind begrenzt und enthalten nur die öffentlichen Formularfelder und den Nachweis. Framework-Objekte, Geheimnisse, Benutzer-Token und Datenbankverbindungen werden nie offengelegt.

API-Brücke und Berechtigungen

$pluginApi stellt get, post, put, patch und delete mit einer literalen Operations-ID und einem Parameter-Array bereit. Jede Operation wird anhand des Manifests auf Methode, Fähigkeit, Anfrageschema, Antwortschema und optionale Plugin-Einschränkung geprüft. Nicht freigegebene URLs werden verweigert. Website- und Plugin-Geltungsbereich können nicht überschrieben werden. Einstellungsoperationen nutzen denselben Anwendungsdienst wie die Konfigurationsverwaltung über die authentifizierte Internal API. Schreibzugriffe auf Einstellungen sind auf autorisierte Lebenszyklus-Handler und vom Inhaber autorisierte Verwaltungsaktionen im Panel beschränkt. Seitenlader, Ansichtsaktionen, öffentliche Endpunkte und Render-Hooks können die Installationskonfiguration nicht ändern.

Unterstützte Syntax und Ausführungsgrenzen ansehen
{
    "execution": "Interpreted PHP subset. Package code is never included, evaluated, or autoloaded by PHP.",
    "statements": [
        "local assignment",
        "return array",
        "if / else with braces"
    ],
    "expressions": [
        "single-quoted strings",
        "integers",
        "booleans",
        "null",
        "arrays",
        "array lookup",
        "concatenation",
        "strict equality",
        "strict inequality",
        "boolean operators",
        "null coalescing"
    ],
    "functions": [
        "trim",
        "strtolower",
        "strlen",
        "count",
        "htmlspecialchars"
    ],
    "forbidden": [
        "all undeclared functions",
        "superglobals",
        "objects and classes",
        "dynamic calls",
        "references",
        "loops",
        "includes",
        "eval",
        "filesystem access",
        "network access",
        "database access",
        "environment access",
        "configuration manipulation",
        "deserialization"
    ],
    "source_bytes": 32768,
    "tokens": 4096,
    "syntax_depth": 32,
    "evaluation_steps": 4096,
    "api_calls_per_handler": 16,
    "handler_wall_seconds": 10,
    "binary_expression_chain": 128,
    "forms_per_page": 32,
    "data_bytes": 131072
}

Assets und öffentliche Endpunkte

Pakete registrieren lokale Skript-, Stil- und Bilddateien. Skripte/Stile werden einmal geladen, wenn das Plugin dargestellt wird; nur registrierte Dateien werden ausgeliefert. Inline-Plugin-HTML verwendet eine Positivliste für Elemente/Attribute. Die Content Security Policy benutzerdefinierter Themes autorisiert die registrierten Skript-Hashes mit Subresource Integrity. Endpunkte verwenden /__eyivo/plugins/{namespace}/{name}/{endpoint}, explizite Methoden und deklarierte Eingabefelder. Mandantenauflösung, Ratenbegrenzungen und CSRF-Schutz für unsichere Methoden bleiben unter Kontrolle des Hosts. Plugin-Endpunkte fügen nie beliebige Anwendungsrouten hinzu.

Menschliche Verifizierung

eyivo/human-verification ist auf jeder Website erforderlich. Der gemeinsame Dienst unter plugins.eyivo.com/verification verwendet verschlüsselte sechsstellige PNG-Aufgaben mit 180 Sekunden Gültigkeit. Vertrauenswürdige Client-Identität, gewährte Dienste und Quell-IPs werden durchgesetzt. Registrierte Formular-UUIDs und Versuchs-IDs binden jede Aufgabe; die Verifizierung bindet zusätzlich die kanonische Nutzlast. Identische akzeptierte Versuche können ihr Ergebnis 24 Stunden lang wiederherstellen, bewahren Sie frühere Schlüssel daher für diesen Zeitraum plus eine Uhr-/Netzwerkreserve auf. Gerade abgelaufene Nachweise schlagen fehl. Die gemeinsame Plugin-Datenbank speichert Wiederholungs-/Ratenbegrenzungs-/Quittungszustand, nie Formularantworten oder Posteingangsverläufe. Pakete erhalten keinen Datenbankzugriff.

Barrierefreiheit und Browserverhalten

Das Steuerelement hat lokalisierte Beschriftungen, Eingabehilfen, Statusansagen und eine Schaltfläche zum Aktualisieren des Bildes. JavaScript muss aktiviert sein. Dieser erste reine Bildmodus kann von Besuchern, die das Bild nicht wahrnehmen können, nicht abgeschlossen werden; eine barrierefreie Alternativaufgabe ist nicht Teil von v1. Seiten mit unterstützten Formularen umgehen das gemeinsame HTML-Caching. Theme-Vorschauen behalten ihre bestehende No-Script-Richtlinie und können keine funktionierenden Aufgaben übermitteln.

Bereitgestellte API-Operationen

Verwenden Sie diese Operations-IDs über $pluginApi. Jeder Eintrag unten stammt aus demselben Freigabemanifest, mit dem die Laufzeitberechtigungen durchgesetzt werden.

GET Seit API 1

site.settings.read

Read the current plugin's namespaced site configuration.

site.settings.read

$pluginApi->get('site.settings.read', []);
Parameter, Antwort und Einschränkungen ansehen
{
    "parameters": [],
    "response": {
        "configuration": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
PUT Seit API 1

site.settings.update

Replace the current plugin's configuration using its declared schema.

site.settings.write

$pluginApi->put('site.settings.update', ['configuration' => []]);
Parameter, Antwort und Einschränkungen ansehen
{
    "parameters": {
        "configuration": {
            "type": "object",
            "required": true
        }
    },
    "response": {
        "configuration": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
GET Seit API 1

form.verification.widget

Render a localized verification control for a supported form.

form.inject

$pluginApi->get('form.verification.widget', []);
Parameter, Antwort und Einschränkungen ansehen
{
    "parameters": [],
    "response": {
        "html": {
            "type": "string",
            "max": 16384
        }
    },
    "restricted_to_plugins": [
        "eyivo/human-verification"
    ],
    "deprecated": false
}
GET Seit API 1

form.verification.challenge

Request a numeric image challenge bound to the current site and form.

verification.challenge

$pluginApi->get('form.verification.challenge', []);
Parameter, Antwort und Einschränkungen ansehen
{
    "parameters": [],
    "response": {
        "token": {
            "type": "string",
            "max": 4096
        },
        "image": {
            "type": "string",
            "max": 65536
        },
        "expires_in": {
            "type": "integer"
        }
    },
    "restricted_to_plugins": [
        "eyivo/human-verification"
    ],
    "deprecated": false
}
POST Seit API 1

form.verification.verify

Consume a proof once. Rejection never allows form processing.

verification.verify

$pluginApi->post('form.verification.verify', ['token' => $context['fields']['verification_token'], 'answer' => $context['fields']['verification_answer']]);
Parameter, Antwort und Einschränkungen ansehen
{
    "parameters": {
        "token": {
            "type": "string",
            "required": true,
            "max": 4096
        },
        "answer": {
            "type": "string",
            "required": true,
            "max": 12
        }
    },
    "response": {
        "valid": {
            "type": "boolean"
        }
    },
    "restricted_to_plugins": [
        "eyivo/human-verification"
    ],
    "deprecated": false
}
POST Seit API 1

forms.submissions.create

Persist only the host-validated and verified attempt supplied out-of-band to the owning processor; accepts no answers or ownership override.

forms.submissions.create

$pluginApi->post('forms.submissions.create', []);
Parameter, Antwort und Einschränkungen ansehen
{
    "parameters": [],
    "response": {
        "result": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
GET Seit API 1

forms.submissions.list

Read paginated submission summaries for the current site and plugin in an authenticated panel context.

forms.submissions.read

$pluginApi->get('forms.submissions.list', []);
Parameter, Antwort und Einschränkungen ansehen
{
    "parameters": {
        "form_type": {
            "type": "string",
            "max": 64
        },
        "form_id": {
            "type": "string",
            "max": 36
        },
        "cursor": {
            "type": "string",
            "max": 1024
        },
        "per_page": {
            "type": "integer",
            "min": 1,
            "max": 25
        },
        "read": {
            "type": "boolean"
        },
        "archived": {
            "type": "boolean"
        },
        "workflow_state": {
            "type": "string",
            "max": 64
        },
        "from": {
            "type": "string",
            "max": 10
        },
        "to": {
            "type": "string",
            "max": 10
        }
    },
    "response": {
        "result": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
GET Seit API 1

forms.submissions.read

Read a historical submission snapshot owned by the current site and plugin.

forms.submissions.read

$pluginApi->get('forms.submissions.read', []);
Parameter, Antwort und Einschränkungen ansehen
{
    "parameters": {
        "submission_id": {
            "type": "string",
            "max": 36,
            "required": true
        }
    },
    "response": {
        "result": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
PATCH Seit API 1

forms.submissions.update

Apply authorized reading/archive changes or a declared workflow transition using the current record revision.

forms.submissions.manage

$pluginApi->patch('forms.submissions.update', []);
Parameter, Antwort und Einschränkungen ansehen
{
    "parameters": {
        "submission_id": {
            "type": "string",
            "max": 36,
            "required": true
        },
        "record_revision": {
            "type": "integer",
            "min": 1,
            "required": true
        },
        "read": {
            "type": "boolean"
        },
        "archived": {
            "type": "boolean"
        },
        "transition": {
            "type": "string",
            "max": 64
        }
    },
    "response": {
        "result": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
DELETE Seit API 1

forms.submissions.delete

Explicitly delete an owned submission, cancel pending notifications and release its stored byte charge; preserve technical replay protection.

forms.submissions.manage

$pluginApi->delete('forms.submissions.delete', []);
Parameter, Antwort und Einschränkungen ansehen
{
    "parameters": {
        "submission_id": {
            "type": "string",
            "max": 36,
            "required": true
        },
        "record_revision": {
            "type": "integer",
            "min": 1,
            "required": true
        }
    },
    "response": {
        "result": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
GET Seit API 1

forms.instances.list

Read cursor-paginated compact form summaries belonging to the current site and plugin; settings are not included.

forms.instances.read

$pluginApi->get('forms.instances.list', []);
Parameter, Antwort und Einschränkungen ansehen
{
    "parameters": {
        "form_type": {
            "type": "string",
            "max": 64
        },
        "cursor": {
            "type": "string",
            "max": 1024
        },
        "per_page": {
            "type": "integer",
            "min": 1,
            "max": 25
        },
        "q": {
            "type": "string",
            "max": 160
        }
    },
    "response": {
        "result": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
POST Seit API 1

forms.instances.create

Create a separate named form instance with an automatically generated UUID.

forms.instances.manage

$pluginApi->post('forms.instances.create', []);
Parameter, Antwort und Einschränkungen ansehen
{
    "parameters": {
        "form_type": {
            "type": "string",
            "max": 64,
            "required": true
        },
        "name": {
            "type": "string",
            "max": 160,
            "required": true
        }
    },
    "response": {
        "result": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
PATCH Seit API 1

forms.instances.update

Update a current form instance name or schema-compatible settings under owner/manage authorization and record revision.

forms.instances.manage

$pluginApi->patch('forms.instances.update', []);
Parameter, Antwort und Einschränkungen ansehen
{
    "parameters": {
        "form_id": {
            "type": "string",
            "max": 36,
            "required": true
        },
        "record_revision": {
            "type": "integer",
            "min": 1,
            "required": true
        },
        "name": {
            "type": "string",
            "max": 160
        },
        "settings": {
            "type": "object"
        }
    },
    "response": {
        "result": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}

Kompatibilität und Bereitstellung

Plugin API 1 unterstützt Forms API 1 und Panel API 1/2. Formularpakete erfordern Listen-/Detailseiten und Menüdeklarationen der Panel API 2. Nicht unterstützte Verträge und unsichere Syntax werden abgelehnt. Stellen Sie zuerst den Quittungsdienst der Verifizierung bereit, dann Master-/Inhaltsmigrationen, Plugin-Abgleich, Altdatenmigration und Nutzungserfassung; stellen Sie danach den kompatiblen App-Renderer und die Apex-Referenz bereit. Bewahren Sie bei einem Rollback den Verlauf und alte Schlüssel auf. Alle ausgelieferten Themes verwenden registrierte Formulare; alte Kontakt-POSTs geben 410 zurück. Das Freigabemanifest liefert diese Referenz und die Laufzeitberechtigungen.