Un seul contrat pour les extensions de site.
Contrat d’API
Plugin API v1
Compatibilité des thèmes
Environnement d’exécution
eyivo-php-subset-1
Identité, versions et confiance
Chaque plugin possède un identifiant namespace/name, une version sémantique de paquet indépendante et exactement la version d’API "1". L’espace de noms eyivo est réservé. Le type est system ou standard ; l’origine est system, verified ou third_party. Seuls les paquets d’origine system approuvés par la plateforme sont acceptés dans cette version. Les paquets ne peuvent pas s’attribuer un statut obligatoire ni de nouvelles capacités.
Installation et configuration
Le master attribué possède les enregistrements d’installation. L’installation, l’activation, la désactivation, la désinstallation et la mise à niveau s’exécutent sous un verrou de site et dans une transaction de base de données. Les dépendances sont installées en premier ; les paquets manquants, les cycles, les conflits et les rétrogradations sont refusés. Les dépendances actives et les paquets exigés par la plateforme ne peuvent pas être désactivés ni supprimés. Les hooks de mise à niveau reçoivent previous_version et peuvent migrer leur configuration dans leur espace de noms via l’API. La configuration utilise des schémas scalaires/objets déclarés et des valeurs par défaut ; les clés inconnues sont refusées. Les gestionnaires du cycle de vie doivent renvoyer un tableau et ne doivent pas dépendre d’effets de bord externes.
Des réglages au périmètre clair.
Chaque site et chaque plugin ont leur propre configuration dans la base de données master attribuée. Le panneau de l’application lit et enregistre via l’Internal API authentifiée sur WireGuard ; il ne détient aucun identifiant de base de données master. Les services partagés de plugins.eyivo.com utilisent une base de données distincte pour l’état du service.
Champs déclarés
50 au maximum
Les conteneurs d’objets et les propriétés imbriquées partagent le même total.
Configuration complète
32 Ko au maximum
32 768 octets de JSON UTF-8 compact, clés, valeurs et valeurs par défaut comprises.
Profondeur d’imbrication
3 niveaux au maximum
Les champs de premier niveau commencent au niveau un.
L’hôte valide les schémas lors de l’enregistrement d’un paquet et vérifie la configuration complète à chaque écriture. Les clés inconnues sont refusées. Les contrôles du panneau et la validation du navigateur ne remplacent pas l’application côté serveur.
Les schémas prennent en charge les chaînes, les entiers, les booléens et les objets aux propriétés explicites. Les tableaux et les maps arbitraires ne sont pas des types de configuration. La longueur des chaînes est mesurée en octets. Les champs de premier niveau commencent au niveau un ; les conteneurs d’objets comptent à la fois dans le total des champs et dans la profondeur d’imbrication.
Les enregistrements portent la révision des plugins du site. Une révision obsolète renvoie un conflit au lieu d’écraser des réglages plus récents. Rechargez et vérifiez les valeurs actuelles avant de réessayer. Toute modification d’un plugin du site change cette révision, y compris la désactivation, la réactivation, la désinstallation et la réinstallation.
Voir les limites du schéma de configuration et les règles d’encodage
{
"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."
}
Vos pages. Des composants Eyivo.
Un plugin possède ses définitions de pages, sa mise en page, son contenu localisé, ses formulaires et ses actions déclarées. L’application centrale rend ces définitions avec des composants de confiance dans le panneau utilisateur existant. Cliquer sur un plugin ouvre sa page d’entrée déclarée, comme la configuration ou sa boîte de réception des envois.
-
Déclarer la page
Enregistrez les définitions de pages et les liaisons de configuration dans le paquet. Les liens de menu facultatifs ciblent des pages appartenant à ce même plugin.
-
Rendre dans l’application
L’application vérifie l’accès au site et reçoit du master attribué des définitions JSON validées et des données. Aucun PHP ni Blade du plugin n’est transféré à l’application ni exécuté par elle.
-
Exécuter sur le master
Les actions autorisées passent par l’Internal API vers des gestionnaires restreints. Le master applique l’état du plugin, les capacités, les schémas et les limites de configuration.
Un groupe Plugins à un seul niveau
Les liens éligibles apparaissent directement sous Plugins pour le site sélectionné. Les sous-menus imbriqués ne sont pas pris en charge. Le groupe reste masqué lorsqu’aucun lien n’est visible. La désactivation ou la désinstallation supprime les liens et bloque l’accès direct aux pages et aux actions, y compris pour les requêtes d’onglets déjà ouverts. La réactivation rétablit les liens éligibles.
Les paquets sans déclaration de panneau et les paquets désactivés ouvrent une vue d’information depuis la liste des plugins. Les paquets désactivés n’exposent pas leurs pages ni leurs actions de panneau enregistrées. La vérification humaine fournit une page de paquet en lecture seule ; ses paramètres de sécurité par défaut restent contrôlés par la plateforme.
Appartenance au site et actions déclarées
Les pages et les actions personnalisées déclarent une autorisation de consultation ou de gestion. Les membres actuels du site peuvent consulter les pages éligibles et lancer des actions de consultation en lecture seule ; la gestion requiert le rôle de propriétaire du site. L’application déduit l’acteur de confiance de l’appartenance connectée. Les actions intégrées d’enregistrement/réinitialisation de la configuration requièrent la gestion ; les gestionnaires personnalisés utilisent des schémas de paramètres et de réponse déclarés. Chaque page n’autorise que les identifiants d’action qu’elle liste.
Les déclarations n’accordent aucune autorité
Le chargement d’une page est en lecture seule. Une page ou un menu ne peut accorder ni PHP sans restriction, ni accès à la base de données, ni routes arbitraires de l’application, ni scripts dans le panneau authentifié. Les paquets existants sans déclaration de panneau continuent de fonctionner. L’installation de paquets tiers reste désactivée.
Composants pris en charge
Composez la page avec ces composants de confiance. Les champs se lient à la configuration déclarée ou aux entrées d’action ; les aperçus affichent du texte brut et les tableaux sont en lecture seule. Le HTML brut, les scripts arbitraires, les expressions et le rendu conditionnel ne sont pas pris en charge.
stackgridcardtextalertfieldbuttonpreviewtablesubmission-listsubmission-detail
Voir le contrat versionné de la Panel API et ses limites
{
"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."
}
}
Formulaires et historique des demandes
Forms API 1 propose sept processeurs approuvés. Les thèmes définissent présentation et style ; les plugins définissent schéma, validation et traitement. La plateforme assure stockage, vérification et notifications.
| Plugin système | Type de formulaire |
|---|---|
eyivo/contact | Contact contact |
eyivo/quote-request | Demande de devis quote_request |
eyivo/product-inquiry | Question produit product_inquiry |
eyivo/appointment-request | Demande de rendez-vous appointment |
eyivo/course-application | Candidature à un cours course_application |
eyivo/reservation-request | Demande de réservation reservation |
eyivo/property-inquiry | Demande immobilière property_inquiry |
Un formulaire, plusieurs emplacements
La configuration crée un UUID par défaut durable par site/plugin/type. Réutiliser un emplacement conserve cette identité ; créer un formulaire nommé génère un autre UUID. Changer de thème ou réinstaller préserve l’historique. Les thèmes utilisent des alias portables ; le client sélectionne un formulaire enregistré sans saisir d’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"}
}
}
Utilisez forms.contact pour le contact et component.form pour les sections de formulaire. Remplacez form_preset par form_binding. Affichez les champs, aides, action, CSRF, form_context signé et verification_html fournis. Seuls les changements de présentation déclarés et les champs supplémentaires autorisés sont acceptés ; la validation obligatoire reste intacte.
forms.contact · component.form · form_binding · form.fields · form.action · form.csrf_token · form.context · form.verification_html
Deux vues, un enregistrement
La boîte commune et les pages liste/détail du plugin affichent les mêmes enregistrements. Les formulaires exigent Panel API 2, submission-list/submission-detail et un lien de menu sans sous-menu. Panel API 1 reste compatible. Consulter ne marque pas comme lu ; lecture, archive et traitement sont distincts, avec record_revision pour les modifications. La désactivation retire les pages du plugin, tandis que la boîte commune conserve les instantanés historiques.
{"components":[{"type":"submission-list","form_type":"contact","detail_page":"submission"}]}
{"components":[{"type":"submission-detail","form_type":"contact","list_page":"submissions"}]}
Données et permissions
Les données utilisent la base de contenu du site et son quota max_db_bytes existant. Aucun quota supplémentaire de nombre ou de conservation. La configuration reste sur le master avec 50 champs, 32 Ko et trois niveaux. Les membres consultent ; modifier exige propriétaire/gestion. Aucun accès direct à la base pour les plugins, ni transfert PHP/Blade vers l’app.
Enregistrement vérifié et notification
La plateforme valide contexte, réponses et Human Verification, puis enregistre demande, utilisation et notification ensemble. Une tentative identique se récupère pendant 24 heures sans doublon. SMTP intervient ensuite avec des reprises possibles ; une erreur mail ne supprime pas la demande. Sans destinataire du formulaire, l’adresse de l’entreprise est utilisée ; si aucune adresse n’est valide, la demande est conservée sans email.
Tous les thèmes fournis suivent ce contrat. Les anciens POST contact renvoient 410 ; les pages de contact GET restent accessibles. Les demandes de rendez-vous ou réservation ne confirment aucune réservation. Processeurs tiers, fichiers et destinations externes arbitraires sont exclus.
POST /__eyivo/forms/{UUID}/submissionsGET /__eyivo/forms/{UUID}/challenge
Thèmes et placement des formulaires
Les thèmes déclarent leurs dépendances approuvées et des alias de formulaires portables dans theme.json. Une liaison identifie plugin, type, schema_version et instance ; la configuration la résout en un UUID de formulaire de site conservé. Les modèles de contact utilisent forms.contact et les composants de prospection utilisent component.form avec form_binding. Rendez les champs fournis, action, le jeton CSRF, le form_context signé et verification_html. N’utilisez que les modifications de présentation déclarées ou les champs supplémentaires autorisés ; la validation obligatoire du plugin ne peut pas être affaiblie. Tous les thèmes fournis utilisent ce contrat. Les actions externes et l’ancien POST de contact ne sont pas des voies d’envoi prises en charge.
{"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"
]
}
}
Exemple de manifeste de 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 système · 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és déclarées
Déclarez les capacités dont un paquet a besoin. L’hôte les vérifie avant d’autoriser chaque opération.
frontend.renderfrontend.assetsform.injectform.validatepublic.endpointsite.settings.readsite.settings.writeverification.challengeverification.verifypanel.renderpanel.actionforms.submissions.createforms.submissions.readforms.submissions.manageforms.instances.readforms.instances.manage
Hooks et événements du cycle de vie
Hooks frontend
page.headpage.body.startpage.body.endform.startform.before_fieldsform.after_fieldsform.before_submitform.after_submitform.end
Hooks backend
form.before_validateform.validateform.after_validateform.before_processform.after_process
Événements du cycle de vie
installenabledisableuninstallupgrade
Validation et ordre de traitement
Le moteur de formulaires partagé valide le locataire, le contexte de placement signé, le plugin/schéma actuel et les réponses déclarées avant la vérification. Il lie la récupération de la preuve à la tentative et à la charge utile canonique. Après une nouvelle vérification de l’état et du quota, le processeur restreint responsable enregistre via l’API de l’hôte dans une seule transaction de contenu avec le reçu de tentative, la variation d’usage et la tâche de notification. Les nouvelles tentatives identiques récupèrent le reçu sans enregistrer à nouveau. L’envoi de l’e-mail a lieu ensuite et peut être réessayé ; un échec d’e-mail ne supprime jamais un envoi enregistré.
Environnement d’exécution PHP restreint
eyivo-php-subset-1
Contexte et résultats
Les gestionnaires reçoivent $context sous forme de données compatibles JSON : site_id, site_public_id, plugin_id, api, configuration, locale et, selon le cas, page/form_id/form_type/action/method/fields/validation_state. L’identité du site est toujours fournie par l’hôte. Les hooks de rendu renvoient un champ html ; les hooks de validation renvoient un champ booléen valid. Chaque validateur obligatoire doit renvoyer explicitement true. Les autres hooks backend renvoient un tableau. Les champs envoyés sont bornés et ne contiennent que les champs publics du formulaire et la preuve. Les objets du framework, les secrets, les jetons utilisateur et les connexions à la base de données ne sont jamais exposés.
Passerelle d’API et autorisations
$pluginApi expose get, post, put, patch et delete avec un identifiant d’opération littéral et un tableau de paramètres. Chaque opération est vérifiée par rapport au manifeste pour la méthode, la capacité, le schéma de requête, le schéma de réponse et une éventuelle restriction de plugin. Les URL non exposées sont refusées. Le périmètre du site et du plugin ne peut pas être remplacé. Les opérations de réglages partagent le même service applicatif que la gestion de configuration de l’Internal API authentifiée. Les écritures de réglages sont limitées aux gestionnaires du cycle de vie autorisés et aux actions de gestion du panneau autorisées par le propriétaire. Les chargeurs de pages, les actions de consultation, les points de terminaison publics et les hooks de rendu ne peuvent pas modifier la configuration de l’installation.
Voir la syntaxe prise en charge et les limites d’exécution
{
"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
}
Ressources et points de terminaison publics
Les paquets enregistrent des fichiers locaux de script, de style et d’image. Les scripts/styles se chargent une fois lorsque le plugin est rendu ; seuls les fichiers enregistrés sont servis. Le HTML en ligne des plugins utilise une liste blanche d’éléments/attributs. La Content Security Policy des thèmes personnalisés autorise les empreintes des scripts enregistrés avec Subresource Integrity. Les points de terminaison utilisent /__eyivo/plugins/{namespace}/{name}/{endpoint}, des méthodes explicites et des champs d’entrée déclarés. La résolution du locataire, les limites de débit et la protection CSRF des méthodes non sûres restent contrôlées par l’hôte. Les points de terminaison de plugins n’ajoutent jamais de routes arbitraires à l’application.
Vérification humaine
eyivo/human-verification est obligatoire sur chaque site. Le service partagé de plugins.eyivo.com/verification utilise des défis PNG chiffrés à six chiffres qui expirent après 180 secondes. L’identité du client de confiance, les services accordés et les IP sources sont appliqués. Les UUID de formulaires enregistrés et les identifiants de tentative lient chaque défi ; la vérification lie également la charge utile canonique. Les tentatives acceptées identiques peuvent récupérer leur résultat pendant 24 heures ; conservez donc les clés précédentes pendant cette période plus une marge d’horloge/réseau. Les preuves tout juste expirées échouent. La base de données partagée des plugins stocke l’état de rejeu/limite de débit/reçus, jamais les réponses aux formulaires ni l’historique de la boîte de réception. Les paquets n’ont aucun accès à la base de données.
Accessibilité et comportement du navigateur
Le contrôle dispose de libellés localisés, d’indications de saisie, d’annonces d’état et d’un bouton d’actualisation de l’image. JavaScript doit être activé. Ce premier mode uniquement visuel ne peut pas être complété par les visiteurs qui ne peuvent pas percevoir l’image ; un défi alternatif accessible ne fait pas partie de la v1. Les pages contenant des formulaires pris en charge contournent le cache HTML partagé. Les aperçus de thèmes conservent leur politique sans script et ne peuvent pas envoyer de défis fonctionnels.
Opérations d’API exposées
Utilisez ces identifiants d’opération via $pluginApi. Chaque entrée ci-dessous provient du même manifeste d’exposition que celui qui applique les autorisations à l’exécution.
site.settings.read
Read the current plugin's namespaced site configuration.
site.settings.read
$pluginApi->get('site.settings.read', []);
Voir les paramètres, la réponse et les restrictions
{
"parameters": [],
"response": {
"configuration": {
"type": "object"
}
},
"restricted_to_plugins": null,
"deprecated": false
}
site.settings.update
Replace the current plugin's configuration using its declared schema.
site.settings.write
$pluginApi->put('site.settings.update', ['configuration' => []]);
Voir les paramètres, la réponse et les restrictions
{
"parameters": {
"configuration": {
"type": "object",
"required": true
}
},
"response": {
"configuration": {
"type": "object"
}
},
"restricted_to_plugins": null,
"deprecated": false
}
form.verification.widget
Render a localized verification control for a supported form.
form.inject
$pluginApi->get('form.verification.widget', []);
Voir les paramètres, la réponse et les restrictions
{
"parameters": [],
"response": {
"html": {
"type": "string",
"max": 16384
}
},
"restricted_to_plugins": [
"eyivo/human-verification"
],
"deprecated": false
}
form.verification.challenge
Request a numeric image challenge bound to the current site and form.
verification.challenge
$pluginApi->get('form.verification.challenge', []);
Voir les paramètres, la réponse et les restrictions
{
"parameters": [],
"response": {
"token": {
"type": "string",
"max": 4096
},
"image": {
"type": "string",
"max": 65536
},
"expires_in": {
"type": "integer"
}
},
"restricted_to_plugins": [
"eyivo/human-verification"
],
"deprecated": false
}
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']]);
Voir les paramètres, la réponse et les restrictions
{
"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
}
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', []);
Voir les paramètres, la réponse et les restrictions
{
"parameters": [],
"response": {
"result": {
"type": "object"
}
},
"restricted_to_plugins": null,
"deprecated": false
}
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', []);
Voir les paramètres, la réponse et les restrictions
{
"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
}
forms.submissions.read
Read a historical submission snapshot owned by the current site and plugin.
forms.submissions.read
$pluginApi->get('forms.submissions.read', []);
Voir les paramètres, la réponse et les restrictions
{
"parameters": {
"submission_id": {
"type": "string",
"max": 36,
"required": true
}
},
"response": {
"result": {
"type": "object"
}
},
"restricted_to_plugins": null,
"deprecated": false
}
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', []);
Voir les paramètres, la réponse et les restrictions
{
"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
}
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', []);
Voir les paramètres, la réponse et les restrictions
{
"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
}
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', []);
Voir les paramètres, la réponse et les restrictions
{
"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
}
forms.instances.create
Create a separate named form instance with an automatically generated UUID.
forms.instances.manage
$pluginApi->post('forms.instances.create', []);
Voir les paramètres, la réponse et les restrictions
{
"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
}
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', []);
Voir les paramètres, la réponse et les restrictions
{
"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é et déploiement
Plugin API 1 prend en charge Forms API 1 et Panel API 1/2. Les paquets de formulaires requièrent les pages de liste/détail et les déclarations de menu de Panel API 2. Les contrats non pris en charge et la syntaxe dangereuse sont refusés. Déployez d’abord le service de reçus de vérification, puis les migrations master/contenu, la réconciliation des plugins, la migration de l’existant et la comptabilisation de l’usage ; déployez ensuite le moteur de rendu compatible de l’application et la référence Apex. Conservez l’historique et les anciennes clés lors d’un retour arrière. Tous les thèmes fournis utilisent des formulaires enregistrés ; les anciens POST de contact renvoient 410. Le manifeste d’exposition fournit cette référence et les autorisations d’exécution.