Un solo contrato para las extensiones del sitio.
Contrato de API
Plugin API v1
Compatibilidad de temas
Entorno de ejecución
eyivo-php-subset-1
Identidad, versiones y confianza
Cada plugin tiene un identificador namespace/name, una versión semántica de paquete independiente y exactamente la versión de API "1". El espacio de nombres eyivo está reservado. El tipo es system o standard; el origen es system, verified o third_party. En esta versión solo se aceptan paquetes de origen system aprobados por la plataforma. Los paquetes no pueden otorgarse a sí mismos el estado de obligatorio ni nuevas capacidades.
Instalación y configuración
El master asignado es dueño de los registros de instalación. Instalar, activar, desactivar, desinstalar y actualizar se ejecutan bajo un bloqueo del sitio y una transacción de base de datos. Las dependencias se instalan primero; se rechazan paquetes ausentes, ciclos, conflictos y bajadas de versión. Las dependencias activas y los paquetes que la plataforma exige no se pueden desactivar ni eliminar. Los hooks de actualización reciben previous_version y pueden migrar su configuración con espacio de nombres mediante la API. La configuración usa esquemas escalares/de objeto declarados y valores predeterminados; las claves desconocidas se rechazan. Los controladores del ciclo de vida deben devolver un array y no deben depender de efectos secundarios externos.
Ajustes con un alcance claro.
Cada sitio y cada plugin tienen su propia configuración en la base de datos master asignada. El panel de la aplicación lee y guarda mediante la Internal API autenticada sobre WireGuard; no guarda credenciales de bases de datos master. Los servicios compartidos de plugins.eyivo.com usan una base de datos independiente para el estado del servicio.
Campos declarados
50 como máximo
Los contenedores de objetos y las propiedades anidadas comparten el mismo total.
Configuración completa
32 KB como máximo
32.768 bytes de JSON UTF-8 compacto, incluidas claves, valores y valores predeterminados.
Profundidad de anidamiento
3 niveles como máximo
Los campos de nivel superior empiezan en el nivel uno.
El host valida los esquemas al registrar un paquete y comprueba la configuración completa en cada escritura. Las claves desconocidas se rechazan. Los controles del panel y la validación del navegador no sustituyen la aplicación de reglas en el servidor.
Los esquemas admiten cadenas, enteros, booleanos y objetos con propiedades explícitas. Los arrays y los mapas arbitrarios no son tipos de configuración. La longitud de las cadenas se mide en bytes. Los campos de nivel superior empiezan en el nivel uno; los contenedores de objetos cuentan tanto para el total de campos como para la profundidad de anidamiento.
Los guardados incluyen la revisión de plugins del sitio. Una revisión obsoleta devuelve un conflicto en lugar de sobrescribir ajustes más recientes. Recargue y revise los valores actuales antes de reintentar. Cualquier cambio en un plugin del sitio modifica esta revisión, incluidos desactivar, volver a activar, desinstalar y reinstalar.
Ver los límites del esquema de configuración y las reglas de codificación
{
"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."
}
Sus páginas. Componentes de Eyivo.
Un plugin es dueño de sus definiciones de página, diseño, contenido localizado, formularios y acciones declaradas. La aplicación central renderiza las definiciones con componentes de confianza dentro del panel de usuario existente. Al hacer clic en un plugin se abre su página de entrada declarada, como la configuración o su bandeja de envíos.
-
Declare la página
Registre definiciones de página y vínculos de configuración en el paquete. Los enlaces de menú opcionales apuntan a páginas del mismo plugin.
-
Renderice en la aplicación
La aplicación comprueba el acceso al sitio y recibe definiciones JSON validadas y datos del master asignado. Ningún PHP ni Blade del plugin se transfiere a la aplicación ni se ejecuta en ella.
-
Ejecute en el master
Las acciones autorizadas pasan por la Internal API a controladores restringidos. El master aplica el estado del plugin, las capacidades, los esquemas y los límites de configuración.
Un grupo de Plugins de un solo nivel
Los enlaces elegibles aparecen directamente bajo Plugins para el sitio seleccionado. No se admiten submenús anidados. El grupo permanece oculto cuando no hay enlaces visibles. Desactivar o desinstalar elimina los enlaces y bloquea el acceso directo a páginas y acciones, incluidas las solicitudes de pestañas ya abiertas. Volver a activar restaura los enlaces elegibles.
Los paquetes sin declaración de panel y los paquetes desactivados abren una vista informativa desde la lista de plugins. Los paquetes desactivados no exponen sus páginas ni acciones de panel registradas. La verificación humana ofrece una página de paquete de solo lectura; sus valores de seguridad predeterminados siguen bajo control de la plataforma.
Membresía del sitio y acciones declaradas
Las páginas y las acciones personalizadas declaran permiso de ver o de gestionar. Los miembros actuales del sitio pueden ver las páginas elegibles e invocar acciones de visualización de solo lectura; gestionar requiere el rol de propietario del sitio. La aplicación deriva el actor de confianza de la membresía con sesión iniciada. Las acciones integradas de guardar/restablecer la configuración requieren gestionar; los controladores personalizados usan esquemas declarados de parámetros y respuesta. Cada página solo permite los ID de acción que enumera.
Las declaraciones no otorgan autoridad
La carga de páginas es de solo lectura. Una página o un menú no puede otorgar PHP sin restricciones, acceso a bases de datos, rutas arbitrarias de la aplicación ni scripts dentro del panel autenticado. Los paquetes existentes sin declaración de panel siguen funcionando. La instalación de paquetes de terceros sigue desactivada.
Componentes compatibles
Componga la página con estos componentes de confianza. Los campos se vinculan a la configuración declarada o a las entradas de acción; las vistas previas muestran texto sin formato y las tablas son de solo lectura. No se admiten HTML sin procesar, scripts arbitrarios, expresiones ni renderizado condicional.
stackgridcardtextalertfieldbuttonpreviewtablesubmission-listsubmission-detail
Ver el contrato versionado de la Panel API y sus límites
{
"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."
}
}
Formularios e historial de solicitudes
Forms API 1 incluye siete procesadores aprobados. Los temas controlan diseño y estilo; los plugins definen esquema, validación y flujo. La plataforma guarda, verifica y notifica.
| Plugin del sistema | Tipo de formulario |
|---|---|
eyivo/contact | Contacto contact |
eyivo/quote-request | Solicitud de presupuesto quote_request |
eyivo/product-inquiry | Consulta de producto product_inquiry |
eyivo/appointment-request | Solicitud de cita appointment |
eyivo/course-application | Solicitud de curso course_application |
eyivo/reservation-request | Solicitud de reserva reservation |
eyivo/property-inquiry | Consulta inmobiliaria property_inquiry |
Un formulario, varias ubicaciones
La configuración crea un UUID predeterminado estable por sitio/plugin/tipo. Reutilizar una ubicación conserva esa identidad; un formulario nuevo con nombre recibe otro UUID. Cambiar de tema o reinstalar conserva el historial. Los temas usan alias portables y el editor permite seleccionar formularios guardados sin introducir UUIDs.
{
"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 en contacto y component.form en secciones de formulario. Sustituye form_preset por form_binding. Renderiza campos, ayuda, action, CSRF, form_context firmado y verification_html suministrados. Solo se aceptan cambios de presentación declarados y campos adicionales permitidos; no se debilita la validación obligatoria.
forms.contact · component.form · form_binding · form.fields · form.action · form.csrf_token · form.context · form.verification_html
Dos vistas, un registro
La bandeja compartida y las páginas de lista/detalle del plugin muestran los mismos registros. Cada formulario requiere Panel API 2, submission-list/submission-detail y un enlace de menú plano. Panel API 1 sigue admitido. Ver no marca como leído; lectura, archivo y flujo son independientes y usan record_revision al editar. Desactivar elimina las páginas del plugin, pero conserva el historial en la bandeja compartida.
{"components":[{"type":"submission-list","form_type":"contact","detail_page":"submission"}]}
{"components":[{"type":"submission-detail","form_type":"contact","list_page":"submissions"}]}
Datos y permisos
Los registros usan la base de contenido del sitio y el límite de paquete max_db_bytes existente. No hay cuotas adicionales de cantidad o retención. La configuración sigue en el master con 50 campos, 32 KB y tres niveles. Los miembros pueden ver; modificar requiere propietario/gestión. Los plugins no acceden directamente a la base y no envían PHP/Blade a la app.
Guardado verificado y notificación
La plataforma valida contexto, respuestas y Human Verification y guarda registro, uso y trabajo de notificación juntos. El mismo intento y contenido se recuperan durante 24 horas sin duplicados. SMTP se ejecuta después y puede reintentar; un fallo de correo no borra el registro. Sin destinatario del formulario se usa el correo de la empresa; si ninguna dirección es válida, se guarda sin correo.
Todos los temas incluidos usan este contrato. Los antiguos POST de contact devuelven 410; los enlaces GET de contacto funcionan. Las solicitudes de cita o reserva no confirman reservas. No se incluyen procesadores de terceros, archivos ni destinos externos arbitrarios.
POST /__eyivo/forms/{UUID}/submissionsGET /__eyivo/forms/{UUID}/challenge
Temas y ubicación de formularios
Los temas declaran dependencias aprobadas y alias de formularios portables en theme.json. Un vínculo identifica plugin, type, schema_version e instance; la configuración lo resuelve a un UUID de formulario del sitio que se conserva. Las plantillas de contacto usan forms.contact y los componentes de captación usan component.form con form_binding. Renderice los campos suministrados, action, el token CSRF, el form_context firmado y verification_html. Use solo cambios de presentación declarados o campos adicionales permitidos; la validación obligatoria del plugin no se puede debilitar. Todos los temas incluidos usan este contrato. Las acciones externas y el POST de contacto retirado no son vías de envío admitidas.
{"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"
]
}
}
Ejemplo de manifiesto 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 del 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": []
}
}
Capacidades declaradas
Declare las capacidades que necesita un paquete. El host las comprueba antes de permitir cada operación.
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 y eventos del ciclo de vida
Hooks de frontend
page.headpage.body.startpage.body.endform.startform.before_fieldsform.after_fieldsform.before_submitform.after_submitform.end
Hooks de backend
form.before_validateform.validateform.after_validateform.before_processform.after_process
Eventos del ciclo de vida
installenabledisableuninstallupgrade
Validación y orden de procesamiento
El motor de formularios compartido valida el inquilino, el contexto de ubicación firmado, el plugin/esquema actual y las respuestas declaradas antes de la verificación. Vincula la recuperación de la prueba al intento y a la carga canónica. Tras volver a comprobar el estado y la cuota, el procesador restringido responsable guarda a través de la API del host en una única transacción de contenido junto con el recibo del intento, el cambio de uso y la tarea de notificación. Los reintentos idénticos recuperan el recibo sin volver a guardar. El envío del correo se produce después y puede reintentarse; un fallo de correo nunca elimina un envío guardado.
Entorno de ejecución PHP restringido
eyivo-php-subset-1
Contexto y resultados
Los controladores reciben $context como datos compatibles con JSON: site_id, site_public_id, plugin_id, api, configuration, locale y los page/form_id/form_type/action/method/fields/validation_state que correspondan. La identidad del sitio siempre la proporciona el host. Los hooks de renderizado devuelven un campo html; los hooks de validación devuelven un campo booleano valid. Cada validador obligatorio debe devolver true de forma explícita. Los demás hooks de backend devuelven un array. Los campos enviados están acotados y solo contienen los campos públicos del formulario y la prueba. Nunca se exponen objetos del framework, secretos, tokens de usuario ni conexiones de base de datos.
Puente de API y permisos
$pluginApi expone get, post, put, patch y delete con un ID de operación literal y un array de parámetros. Cada operación se comprueba con el manifiesto en cuanto a método, capacidad, esquema de solicitud, esquema de respuesta y restricción de plugin opcional. Las URL no expuestas se deniegan. El alcance de sitio y de plugin no se puede sobrescribir. Las operaciones de ajustes comparten el mismo servicio de aplicación que la gestión de configuración de la Internal API autenticada. Las escrituras de ajustes se limitan a controladores del ciclo de vida autorizados y a acciones de gestión del panel autorizadas por el propietario. Los cargadores de páginas, las acciones de visualización, los endpoints públicos y los hooks de renderizado no pueden alterar la configuración de la instalación.
Ver la sintaxis compatible y los límites de ejecución
{
"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
}
Recursos y endpoints públicos
Los paquetes registran archivos locales de script, estilo e imagen. Los scripts/estilos se cargan una vez cuando se renderiza el plugin; solo se sirven los archivos registrados. El HTML en línea del plugin usa una lista de elementos/atributos permitidos. La Content Security Policy de los temas personalizados autoriza los hashes de script registrados con Subresource Integrity. Los endpoints usan /__eyivo/plugins/{namespace}/{name}/{endpoint}, métodos explícitos y campos de entrada declarados. La resolución del inquilino, los límites de frecuencia y la protección CSRF para métodos no seguros siguen bajo control del host. Los endpoints de plugins nunca añaden rutas arbitrarias a la aplicación.
Verificación humana
eyivo/human-verification es obligatorio en todos los sitios. El servicio compartido de plugins.eyivo.com/verification usa desafíos PNG cifrados de seis dígitos que caducan a los 180 segundos. Se aplican la identidad de cliente de confianza, los servicios concedidos y las IP de origen. Los UUID de formulario registrados y los ID de intento vinculan cada desafío; la verificación también vincula la carga canónica. Los intentos aceptados idénticos pueden recuperar su resultado durante 24 horas, así que conserve las claves anteriores durante ese periodo más un margen de reloj/red. Las pruebas recién caducadas fallan. La base de datos compartida de plugins guarda el estado de repetición/límite de frecuencia/recibos, nunca respuestas de formularios ni historial de la bandeja de entrada. Los paquetes no reciben acceso a la base de datos.
Accesibilidad y comportamiento del navegador
El control tiene etiquetas localizadas, orientación para la entrada, anuncios de estado y un botón para actualizar la imagen. JavaScript debe estar activado. Este modo inicial solo con imagen no lo pueden completar visitantes que no puedan percibir la imagen; un desafío alternativo accesible no forma parte de v1. Las páginas con formularios compatibles omiten la caché HTML compartida. Las vistas previas de temas mantienen su política sin scripts y no pueden enviar desafíos funcionales.
Operaciones de API expuestas
Use estos ID de operación a través de $pluginApi. Cada entrada procede del mismo manifiesto de exposición que se usa para aplicar los permisos en tiempo de ejecución.
site.settings.read
Read the current plugin's namespaced site configuration.
site.settings.read
$pluginApi->get('site.settings.read', []);
Ver parámetros, respuesta y restricciones
{
"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' => []]);
Ver parámetros, respuesta y restricciones
{
"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', []);
Ver parámetros, respuesta y restricciones
{
"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', []);
Ver parámetros, respuesta y restricciones
{
"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']]);
Ver parámetros, respuesta y restricciones
{
"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', []);
Ver parámetros, respuesta y restricciones
{
"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', []);
Ver parámetros, respuesta y restricciones
{
"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', []);
Ver parámetros, respuesta y restricciones
{
"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', []);
Ver parámetros, respuesta y restricciones
{
"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', []);
Ver parámetros, respuesta y restricciones
{
"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', []);
Ver parámetros, respuesta y restricciones
{
"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', []);
Ver parámetros, respuesta y restricciones
{
"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', []);
Ver parámetros, respuesta y restricciones
{
"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
}
Compatibilidad y despliegue
Plugin API 1 admite Forms API 1 y Panel API 1/2. Los paquetes de formularios requieren páginas de lista/detalle y declaraciones de menú de Panel API 2. Se rechazan los contratos no compatibles y la sintaxis insegura. Despliegue primero el servicio de recibos de verificación, después las migraciones de master/contenido, la conciliación de plugins, la migración heredada y la contabilidad de uso; despliegue después el renderizador compatible de la aplicación y la referencia de Apex. Conserve el historial y las claves antiguas durante una reversión. Todos los temas incluidos usan formularios registrados; los POST de contacto heredados devuelven 410. El manifiesto de exposición proporciona esta referencia y los permisos en tiempo de ejecución.