Vai al contenuto

API dei plugin Eyivo v1

Estendi Eyivo con i plugin.

Estensioni controllate, moduli registrati e pagine dei plugin renderizzate da Eyivo.

Contratto pubblico v1

Il contratto è pubblico. Sono installabili solo pacchetti Eyivo approvati; i pacchetti di terzi restano disabilitati.

Un unico contratto per le estensioni del sito.

Contratto API

Plugin API v1

Compatibilità dei temi

Theme API v1

Runtime di esecuzione

eyivo-php-subset-1

Identità, versioni e attendibilità

Ogni plugin ha un identificatore namespace/name, una versione semantica del pacchetto indipendente ed esattamente la versione API "1". Lo spazio dei nomi eyivo è riservato. Il tipo è system o standard; l’origine è system, verified o third_party. In questa versione sono accettati solo pacchetti di origine system approvati dalla piattaforma. I pacchetti non possono attribuirsi lo stato di obbligatorio né nuove capacità.

Installazione e configurazione

Il master assegnato possiede i record di installazione. Installazione, attivazione, disattivazione, disinstallazione e aggiornamento vengono eseguiti con un blocco del sito e in una transazione del database. Le dipendenze vengono installate per prime; pacchetti mancanti, cicli, conflitti e downgrade vengono rifiutati. Le dipendenze attive e i pacchetti richiesti dalla piattaforma non possono essere disattivati né rimossi. Gli hook di aggiornamento ricevono previous_version e possono migrare la configurazione nel proprio spazio dei nomi tramite l’API. La configurazione usa schemi scalari/oggetto dichiarati e valori predefiniti; le chiavi sconosciute vengono rifiutate. I gestori del ciclo di vita devono restituire un array e non devono dipendere da effetti collaterali esterni.

Impostazioni con un ambito chiaro.

Ogni sito e ogni plugin hanno una propria configurazione nel database master assegnato. Il pannello dell’app legge e salva tramite la Internal API autenticata su WireGuard; non conserva credenziali dei database master. I servizi condivisi su plugins.eyivo.com usano un database separato per lo stato del servizio.

Campi dichiarati

Massimo 50

Contenitori di oggetti e proprietà annidate condividono lo stesso totale.

Configurazione completa

Massimo 32 KB

32.768 byte di JSON UTF-8 compatto, inclusi chiavi, valori e valori predefiniti.

Profondità di annidamento

Massimo 3 livelli

I campi di primo livello partono dal livello uno.

L’host convalida gli schemi quando registra un pacchetto e controlla la configurazione completa a ogni scrittura. Le chiavi sconosciute vengono rifiutate. I controlli del pannello e la convalida del browser non sostituiscono l’applicazione lato server.

Gli schemi supportano stringhe, interi, booleani e oggetti con proprietà esplicite. Array e mappe arbitrarie non sono tipi di configurazione. La lunghezza delle stringhe è misurata in byte. I campi di primo livello partono dal livello uno; i contenitori di oggetti contano sia nel totale dei campi sia nella profondità di annidamento.

I salvataggi includono la revisione dei plugin del sito. Una revisione obsoleta restituisce un conflitto invece di sovrascrivere impostazioni più recenti. Ricarica e controlla i valori attuali prima di riprovare. Le modifiche a qualsiasi plugin del sito cambiano questa revisione, inclusi disattivazione, riattivazione, disinstallazione e reinstallazione.

Vedi i limiti dello schema di configurazione e le regole di codifica
{
    "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."
}

Le tue pagine. Componenti Eyivo.

Un plugin possiede le proprie definizioni di pagina, layout, contenuti localizzati, moduli e azioni dichiarate. L’app centrale visualizza le definizioni con componenti attendibili all’interno del pannello utente esistente. Facendo clic su un plugin si apre la sua pagina di ingresso dichiarata, come la configurazione o la casella degli invii.

  1. Dichiara la pagina

    Registra nel pacchetto le definizioni di pagina e i collegamenti alla configurazione. I link di menu facoltativi puntano a pagine dello stesso plugin.

  2. Visualizza nell’app

    L’app verifica l’accesso al sito e riceve dal master assegnato definizioni JSON convalidate e dati. Nessun PHP o Blade del plugin viene trasferito all’app o eseguito da essa.

  3. Esegui sul master

    Le azioni autorizzate passano attraverso la Internal API verso gestori limitati. Il master applica stato del plugin, capacità, schemi e limiti di configurazione.

Un gruppo Plugin a un solo livello

I link idonei compaiono direttamente sotto Plugin per il sito selezionato. I sottomenu annidati non sono supportati. Il gruppo resta nascosto quando non ci sono link visibili. Disattivare o disinstallare rimuove i link e blocca l’accesso diretto a pagine e azioni, comprese le richieste da schede già aperte. La riattivazione ripristina i link idonei.

I pacchetti senza dichiarazione del pannello e quelli disattivati aprono una vista informativa dall’elenco dei plugin. I pacchetti disattivati non espongono le pagine o le azioni del pannello registrate. La verifica umana fornisce una pagina del pacchetto di sola lettura; le sue impostazioni di sicurezza predefinite restano controllate dalla piattaforma.

Appartenenza al sito e azioni dichiarate

Pagine e azioni personalizzate dichiarano l’autorizzazione di visualizzazione o di gestione. I membri attuali del sito possono vedere le pagine idonee e avviare azioni di visualizzazione di sola lettura; la gestione richiede il ruolo di proprietario del sito. L’app ricava l’attore attendibile dall’appartenenza con accesso effettuato. Le azioni integrate di salvataggio/ripristino della configurazione richiedono la gestione; i gestori personalizzati usano schemi dichiarati di parametri e risposta. Ogni pagina consente solo gli ID di azione elencati.

Le dichiarazioni non concedono autorità

Il caricamento della pagina è di sola lettura. Una pagina o un menu non può concedere PHP senza restrizioni, accesso al database, rotte arbitrarie dell’applicazione o script all’interno del pannello autenticato. I pacchetti esistenti senza dichiarazione del pannello continuano a funzionare. L’installazione di pacchetti di terze parti resta disattivata.

Componenti supportati

Componi la pagina con questi componenti attendibili. I campi si collegano alla configurazione dichiarata o agli input delle azioni; le anteprime mostrano testo semplice e le tabelle sono di sola lettura. HTML grezzo, script arbitrari, espressioni e visualizzazione condizionale non sono supportati.

  • stack
  • grid
  • card
  • text
  • alert
  • field
  • button
  • preview
  • table
  • submission-list
  • submission-detail
Vedi il contratto versionato della Panel API e i suoi limiti
{
    "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."
    }
}

Moduli e storico delle richieste

Forms API 1 offre sette processori approvati. I temi gestiscono layout e stile; i plugin definiscono schema, validazione e flusso. La piattaforma gestisce persistenza, verifica e notifiche.

Plugin di sistemaTipo di modulo
eyivo/contactContatto contact
eyivo/quote-requestRichiesta di preventivo quote_request
eyivo/product-inquiryInformazioni prodotto product_inquiry
eyivo/appointment-requestRichiesta di appuntamento appointment
eyivo/course-applicationDomanda per un corso course_application
eyivo/reservation-requestRichiesta di prenotazione reservation
eyivo/property-inquiryInformazioni immobiliari property_inquiry

Un modulo, più posizioni

La configurazione crea un UUID predefinito stabile per sito/plugin/tipo. Riutilizzare una posizione conserva l’identità; creare un modulo con nome genera un altro UUID. Cambi di tema e reinstallazione conservano lo storico. I temi usano alias portabili; l’editor seleziona i moduli salvati senza inserire UUID.

{
  "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"}
  }
}

Usa forms.contact nei contatti e component.form nelle sezioni modulo. Sostituisci form_preset con form_binding. Renderizza campi, aiuti, action, CSRF, form_context firmato e verification_html forniti. Sono ammesse solo modifiche di presentazione dichiarate e campi aggiuntivi consentiti; la validazione obbligatoria resta attiva.

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

Due viste, un record

La casella condivisa e le pagine elenco/dettaglio del plugin mostrano gli stessi record. I moduli richiedono Panel API 2, submission-list/submission-detail e un collegamento di menu senza sottolivelli. Panel API 1 resta supportata. Aprire non segna come letto; lettura, archivio e flusso sono separati e le modifiche usano record_revision. Disabilitare rimuove le pagine del plugin; la casella condivisa conserva gli snapshot storici.

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

Dati e autorizzazioni

I record usano il database dei contenuti del sito e il limite max_db_bytes del pacchetto. Nessuna quota separata per numero o conservazione. La configurazione resta sul master con 50 campi, 32 KB e tre livelli. I membri visualizzano; modificare richiede proprietario/gestione. Nessun accesso diretto al database dei plugin o trasferimento di PHP/Blade all’app.

Salvataggio verificato e notifica

La piattaforma convalida contesto, risposte e Human Verification, poi salva record, utilizzo e notifica insieme. Lo stesso tentativo si recupera per 24 ore senza duplicati. SMTP interviene dopo con possibili tentativi successivi; un errore email non elimina il record. Senza destinatario del modulo si usa l’email aziendale; se nessun indirizzo è valido, il record viene salvato senza email.

Tutti i temi inclusi usano questo contratto. I vecchi POST contact restituiscono 410; le pagine GET dei contatti funzionano. Le richieste di appuntamento o prenotazione non confermano prenotazioni. Processori di terzi, caricamenti e destinazioni esterne arbitrarie sono esclusi.

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

Temi e posizionamento dei moduli

I temi dichiarano dipendenze approvate e alias di moduli portabili in theme.json. Un collegamento identifica plugin, type, schema_version e instance; la configurazione lo risolve in un UUID di modulo del sito che viene conservato. I modelli di contatto usano forms.contact e i componenti per i contatti commerciali usano component.form con form_binding. Visualizza i campi forniti, action, il token CSRF, il form_context firmato e verification_html. Usa solo modifiche di presentazione dichiarate o campi aggiuntivi consentiti; la convalida obbligatoria del plugin non può essere indebolita. Tutti i temi inclusi usano questo contratto. Le azioni esterne e il vecchio POST di contatto non sono percorsi di invio supportati.

{"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"
        ]
    }
}

Esempio di manifest del plugin

{
    "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"
                }
            }
        }
    }
}
Plugin di sistema · 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": []
    }
}

Capacità dichiarate

Dichiara le capacità di cui un pacchetto ha bisogno. L’host le verifica prima di consentire ogni operazione.

  • 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

Hook ed eventi del ciclo di vita

Hook frontend

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

Hook backend

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

Eventi del ciclo di vita

  • install
  • enable
  • disable
  • uninstall
  • upgrade

Convalida e ordine di elaborazione

Il motore dei moduli condiviso convalida tenant, contesto di posizionamento firmato, plugin/schema attuale e risposte dichiarate prima della verifica. Collega il recupero della prova al tentativo e al payload canonico. Dopo aver ricontrollato stato e quota, il processore limitato responsabile salva tramite l’API dell’host in un’unica transazione di contenuto insieme alla ricevuta del tentativo, alla variazione di utilizzo e all’attività di notifica. I tentativi identici recuperano la ricevuta senza salvare di nuovo. L’invio dell’email avviene in seguito e può essere ritentato; un errore di posta non rimuove mai un invio salvato.

Runtime PHP limitato

eyivo-php-subset-1

Contesto e risultati

I gestori ricevono $context come dati compatibili con JSON: site_id, site_public_id, plugin_id, api, configuration, locale e, se applicabili, page/form_id/form_type/action/method/fields/validation_state. L’identità del sito è sempre fornita dall’host. Gli hook di visualizzazione restituiscono un campo html; gli hook di convalida restituiscono un campo booleano valid. Ogni validatore obbligatorio deve restituire esplicitamente true. Gli altri hook backend restituiscono un array. I campi inviati sono limitati e contengono solo i campi pubblici del modulo e la prova. Oggetti del framework, segreti, token utente e connessioni al database non vengono mai esposti.

Bridge API e autorizzazioni

$pluginApi espone get, post, put, patch e delete con un ID di operazione letterale e un array di parametri. Ogni operazione viene verificata rispetto al manifest per metodo, capacità, schema della richiesta, schema della risposta ed eventuale restrizione del plugin. Gli URL non esposti vengono negati. L’ambito del sito e del plugin non può essere sovrascritto. Le operazioni sulle impostazioni condividono lo stesso servizio applicativo della gestione della configurazione della Internal API autenticata. Le scritture delle impostazioni sono limitate ai gestori del ciclo di vida autorizzati e alle azioni di gestione del pannello autorizzate dal proprietario. Caricatori di pagina, azioni di visualizzazione, endpoint pubblici e hook di visualizzazione non possono modificare la configurazione dell’installazione.

Vedi la sintassi supportata e i limiti di esecuzione
{
    "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
}

Risorse ed endpoint pubblici

I pacchetti registrano file locali di script, stile e immagine. Script/stili vengono caricati una volta quando il plugin viene visualizzato; vengono serviti solo i file registrati. L’HTML in linea dei plugin usa un elenco di elementi/attributi consentiti. La Content Security Policy dei temi personalizzati autorizza gli hash degli script registrati con Subresource Integrity. Gli endpoint usano /__eyivo/plugins/{namespace}/{name}/{endpoint}, metodi espliciti e campi di input dichiarati. Risoluzione del tenant, limiti di frequenza e protezione CSRF per i metodi non sicuri restano sotto il controllo dell’host. Gli endpoint dei plugin non aggiungono mai rotte arbitrarie all’applicazione.

Verifica umana

eyivo/human-verification è obbligatorio su ogni sito. Il servizio condiviso su plugins.eyivo.com/verification usa sfide PNG cifrate a sei cifre che scadono dopo 180 secondi. Vengono applicate l’identità del client attendibile, i servizi concessi e gli IP di origine. Gli UUID dei moduli registrati e gli ID dei tentativi collegano ogni sfida; la verifica collega anche il payload canonico. I tentativi accettati identici possono recuperare il risultato per 24 ore, quindi conserva le chiavi precedenti per questo periodo più un margine di orologio/rete. Le prove appena scadute non vengono accettate. Il database condiviso dei plugin memorizza lo stato di replay/limiti di frequenza/ricevute, mai le risposte ai moduli né la cronologia della casella di posta. I pacchetti non ricevono accesso al database.

Accessibilità e comportamento del browser

Il controllo ha etichette localizzate, indicazioni per l’input, annunci di stato e un pulsante per aggiornare l’immagine. JavaScript deve essere attivo. Questa prima modalità solo immagine non può essere completata da visitatori che non riescono a percepire l’immagine; una sfida alternativa accessibile non fa parte della v1. Le pagine con moduli supportati evitano la cache HTML condivisa. Le anteprime dei temi mantengono la loro politica senza script e non possono inviare sfide funzionanti.

Operazioni API esposte

Usa questi ID di operazione tramite $pluginApi. Ogni voce qui sotto proviene dallo stesso manifest di esposizione usato per applicare le autorizzazioni in fase di esecuzione.

GET Dall’API 1

site.settings.read

Read the current plugin's namespaced site configuration.

site.settings.read

$pluginApi->get('site.settings.read', []);
Vedi parametri, risposta e restrizioni
{
    "parameters": [],
    "response": {
        "configuration": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
PUT Dall’API 1

site.settings.update

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

site.settings.write

$pluginApi->put('site.settings.update', ['configuration' => []]);
Vedi parametri, risposta e restrizioni
{
    "parameters": {
        "configuration": {
            "type": "object",
            "required": true
        }
    },
    "response": {
        "configuration": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
GET Dall’API 1

form.verification.widget

Render a localized verification control for a supported form.

form.inject

$pluginApi->get('form.verification.widget', []);
Vedi parametri, risposta e restrizioni
{
    "parameters": [],
    "response": {
        "html": {
            "type": "string",
            "max": 16384
        }
    },
    "restricted_to_plugins": [
        "eyivo/human-verification"
    ],
    "deprecated": false
}
GET Dall’API 1

form.verification.challenge

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

verification.challenge

$pluginApi->get('form.verification.challenge', []);
Vedi parametri, risposta e restrizioni
{
    "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 Dall’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']]);
Vedi parametri, risposta e restrizioni
{
    "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 Dall’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', []);
Vedi parametri, risposta e restrizioni
{
    "parameters": [],
    "response": {
        "result": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
GET Dall’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', []);
Vedi parametri, risposta e restrizioni
{
    "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 Dall’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', []);
Vedi parametri, risposta e restrizioni
{
    "parameters": {
        "submission_id": {
            "type": "string",
            "max": 36,
            "required": true
        }
    },
    "response": {
        "result": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
PATCH Dall’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', []);
Vedi parametri, risposta e restrizioni
{
    "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 Dall’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', []);
Vedi parametri, risposta e restrizioni
{
    "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 Dall’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', []);
Vedi parametri, risposta e restrizioni
{
    "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 Dall’API 1

forms.instances.create

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

forms.instances.manage

$pluginApi->post('forms.instances.create', []);
Vedi parametri, risposta e restrizioni
{
    "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 Dall’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', []);
Vedi parametri, risposta e restrizioni
{
    "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
}

Compatibilità e distribuzione

Plugin API 1 supporta Forms API 1 e Panel API 1/2. I pacchetti di moduli richiedono pagine elenco/dettaglio e dichiarazioni di menu della Panel API 2. Contratti non supportati e sintassi non sicura vengono rifiutati. Distribuisci prima il servizio delle ricevute di verifica, poi le migrazioni master/contenuti, la riconciliazione dei plugin, la migrazione dei dati esistenti e la contabilizzazione dell’utilizzo; distribuisci infine il renderer compatibile dell’app e il riferimento Apex. Conserva la cronologia e le vecchie chiavi durante un rollback. Tutti i temi inclusi usano moduli registrati; i vecchi POST di contatto restituiscono 410. Il manifest di esposizione fornisce questo riferimento e le autorizzazioni in fase di esecuzione.