Ir para o conteúdo

API de plugins Eyivo v1

Amplie o Eyivo com plugins.

Extensões controladas, formulários registados e páginas de plugins renderizadas pelo Eyivo.

Contrato público v1

O contrato é público. Só podem ser instalados pacotes Eyivo aprovados; a instalação de terceiros permanece desativada.

Um único contrato para extensões de site.

Contrato de API

Plugin API v1

Compatibilidade de temas

Theme API v1

Ambiente de execução

eyivo-php-subset-1

Identidade, versões e confiança

Cada plugin tem um identificador namespace/name, uma versão semântica de pacote independente e exatamente a versão de API "1". O namespace eyivo é reservado. O tipo é system ou standard; a origem é system, verified ou third_party. Nesta versão, apenas pacotes de origem system aprovados pela plataforma são aceitos. Os pacotes não podem atribuir a si mesmos status obrigatório nem novas capacidades.

Instalação e configuração

O master atribuído é dono dos registros de instalação. Instalar, ativar, desativar, desinstalar e atualizar são executados sob um bloqueio do site e uma transação de banco de dados. As dependências são instaladas primeiro; pacotes ausentes, ciclos, conflitos e rebaixamentos de versão são rejeitados. Dependências ativas e pacotes exigidos pela plataforma não podem ser desativados nem removidos. Os hooks de atualização recebem previous_version e podem migrar a configuração no seu namespace pela API. A configuração usa esquemas escalares/de objeto declarados e padrões; chaves desconhecidas são rejeitadas. Os manipuladores do ciclo de vida devem retornar um array e não devem depender de efeitos colaterais externos.

Configurações com um escopo claro.

Cada site e cada plugin têm sua própria configuração no banco de dados master atribuído. O painel do aplicativo lê e salva por meio da Internal API autenticada sobre WireGuard; ele não guarda credenciais de bancos de dados master. Os serviços compartilhados em plugins.eyivo.com usam um banco de dados separado para o estado do serviço.

Campos declarados

No máximo 50

Contêineres de objetos e propriedades aninhadas compartilham o mesmo total.

Configuração completa

No máximo 32 KB

32.768 bytes de JSON UTF-8 compacto, incluindo chaves, valores e padrões.

Profundidade de aninhamento

No máximo 3 níveis

Os campos de nível superior começam no nível um.

O host valida os esquemas ao registrar um pacote e verifica a configuração completa a cada gravação. Chaves desconhecidas são rejeitadas. Os controles do painel e a validação do navegador não substituem a aplicação no servidor.

Os esquemas suportam strings, inteiros, booleanos e objetos com propriedades explícitas. Arrays e mapas arbitrários não são tipos de configuração. O comprimento das strings é medido em bytes. Os campos de nível superior começam no nível um; os contêineres de objetos contam tanto para o total de campos quanto para a profundidade de aninhamento.

Os salvamentos carregam a revisão de plugins do site. Uma revisão desatualizada retorna um conflito em vez de sobrescrever configurações mais recentes. Recarregue e revise os valores atuais antes de tentar novamente. Alterações em qualquer plugin do site mudam esta revisão, incluindo desativar, reativar, desinstalar e reinstalar.

Ver os limites do esquema de configuração e as regras de codificação
{
    "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."
}

Suas páginas. Componentes da Eyivo.

Um plugin é dono das suas definições de página, layout, conteúdo localizado, formulários e ações declaradas. O aplicativo central renderiza as definições com componentes confiáveis dentro do painel de usuário existente. Clicar em um plugin abre a página de entrada declarada, como a configuração ou a caixa de envios.

  1. Declare a página

    Registre definições de página e vínculos de configuração no pacote. Links de menu opcionais apontam para páginas do mesmo plugin.

  2. Renderize no aplicativo

    O aplicativo verifica o acesso ao site e recebe definições JSON validadas e dados do master atribuído. Nenhum PHP ou Blade do plugin é transferido para o aplicativo nem executado por ele.

  3. Execute no master

    Ações autorizadas passam pela Internal API até manipuladores restritos. O master aplica o estado do plugin, as capacidades, os esquemas e os limites de configuração.

Um grupo de Plugins de nível único

Os links elegíveis aparecem diretamente em Plugins para o site selecionado. Submenus aninhados não são suportados. O grupo fica oculto quando não há links visíveis. Desativar ou desinstalar remove os links e bloqueia o acesso direto a páginas e ações, incluindo solicitações de abas já abertas. Reativar restaura os links elegíveis.

Pacotes sem declaração de painel e pacotes desativados abrem uma visualização informativa a partir da lista de plugins. Pacotes desativados não expõem as páginas nem as ações de painel registradas. A verificação humana fornece uma página de pacote somente leitura; seus padrões de segurança continuam controlados pela plataforma.

Associação ao site e ações declaradas

Páginas e ações personalizadas declaram permissão de visualizar ou gerenciar. Os membros atuais do site podem ver as páginas elegíveis e acionar ações de visualização somente leitura; gerenciar exige a função de proprietário do site. O aplicativo deriva o ator confiável da associação com sessão iniciada. As ações integradas de salvar/redefinir a configuração exigem gerenciar; manipuladores personalizados usam esquemas declarados de parâmetros e resposta. Cada página permite apenas os IDs de ação que lista.

Declarações não concedem autoridade

O carregamento de páginas é somente leitura. Uma página ou menu não pode conceder PHP irrestrito, acesso ao banco de dados, rotas arbitrárias do aplicativo nem scripts dentro do painel autenticado. Pacotes existentes sem declaração de painel continuam funcionando. A instalação de pacotes de terceiros continua desativada.

Componentes suportados

Monte a página com estes componentes confiáveis. Os campos se vinculam à configuração declarada ou às entradas de ação; as pré-visualizações mostram texto simples e as tabelas são somente leitura. HTML bruto, scripts arbitrários, expressões e renderização condicional não são suportados.

  • stack
  • grid
  • card
  • text
  • alert
  • field
  • button
  • preview
  • table
  • submission-list
  • submission-detail
Ver o contrato versionado da Panel API e seus 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."
    }
}

Formulários e histórico de pedidos

Forms API 1 fornece sete processadores aprovados. Os temas controlam disposição e estilo; os plugins definem esquema, validação e fluxo. A plataforma guarda, verifica e notifica.

Plugin do sistemaTipo de formulário
eyivo/contactContacto contact
eyivo/quote-requestPedido de orçamento quote_request
eyivo/product-inquiryConsulta de produto product_inquiry
eyivo/appointment-requestPedido de marcação appointment
eyivo/course-applicationCandidatura a curso course_application
eyivo/reservation-requestPedido de reserva reservation
eyivo/property-inquiryConsulta imobiliária property_inquiry

Um formulário, várias posições

A configuração cria um UUID predefinido permanente por site/plugin/tipo. Reutilizar uma posição conserva a identidade; criar um formulário com nome gera outro UUID. Mudar de tema ou reinstalar preserva o histórico. Os temas usam nomes portáteis; o editor seleciona formulários guardados sem introduzir 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"}
  }
}

Use forms.contact nos contactos e component.form nas secções de formulário. Substitua form_preset por form_binding. Renderize campos, ajuda, action, CSRF, form_context assinado e verification_html fornecidos. Só são permitidas alterações de apresentação declaradas e campos adicionais autorizados; a validação obrigatória mantém-se.

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

Duas vistas, um registo

A caixa partilhada e as páginas de lista/detalhe do plugin mostram os mesmos registos. Formulários exigem Panel API 2, submission-list/submission-detail e um link de menu sem subníveis. Panel API 1 continua suportada. Ver não marca como lido; leitura, arquivo e fluxo são separados e alterações usam record_revision. Desativar remove as páginas do plugin; a caixa partilhada mantém o histórico.

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

Dados e permissões

Os registos usam a base de conteúdo do site e o limite max_db_bytes do pacote existente. Não há quota adicional de quantidade ou retenção. A configuração continua no master com 50 campos, 32 KB e três níveis. Membros podem ver; alterações exigem proprietário/gestão. Plugins não têm acesso direto à base nem enviam PHP/Blade para a app.

Gravação verificada e notificação

A plataforma valida contexto, respostas e Human Verification e guarda registo, utilização e trabalho de notificação juntos. A mesma tentativa pode ser recuperada durante 24 horas sem duplicação. SMTP executa depois e pode repetir; uma falha de email não apaga o registo. Sem destinatário no formulário, usa-se o email da empresa; se nenhum endereço for válido, o registo é guardado sem email.

Todos os temas incluídos usam este contrato. POST contact antigos devolvem 410; as páginas GET de contacto funcionam. Pedidos de marcação ou reserva não confirmam reservas. Processadores de terceiros, ficheiros e destinos externos arbitrários estão excluídos.

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

Temas e posicionamento de formulários

Os temas declaram dependências aprovadas e aliases de formulários portáteis em theme.json. Um vínculo identifica plugin, type, schema_version e instance; a configuração o resolve para um UUID de formulário do site que é mantido. Modelos de contato usam forms.contact e componentes de captação usam component.form com form_binding. Renderize os campos fornecidos, action, o token CSRF, o form_context assinado e verification_html. Use apenas mudanças de apresentação declaradas ou campos adicionais permitidos; a validação obrigatória do plugin não pode ser enfraquecida. Todos os temas incluídos usam este contrato. Ações externas e o antigo POST de contato não são caminhos de envio suportados.

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

Exemplo de manifesto 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 do 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 as capacidades de que um pacote precisa. O host as verifica antes de permitir cada operação.

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

Hooks e eventos do ciclo de vida

Hooks de 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

Hooks de backend

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

Eventos do ciclo de vida

  • install
  • enable
  • disable
  • uninstall
  • upgrade

Validação e ordem de processamento

O mecanismo de formulários compartilhado valida o inquilino, o contexto de posicionamento assinado, o plugin/esquema atual e as respostas declaradas antes da verificação. Ele vincula a recuperação da prova à tentativa e à carga canônica. Depois de verificar novamente o estado e a cota, o processador restrito responsável salva pela API do host em uma única transação de conteúdo, junto com o recibo da tentativa, a mudança de uso e a tarefa de notificação. Novas tentativas idênticas recuperam o recibo sem salvar de novo. O envio do e-mail acontece depois e pode ser repetido; uma falha de e-mail nunca remove um envio salvo.

Ambiente de execução PHP restrito

eyivo-php-subset-1

Contexto e resultados

Os manipuladores recebem $context como dados compatíveis com JSON: site_id, site_public_id, plugin_id, api, configuration, locale e os page/form_id/form_type/action/method/fields/validation_state aplicáveis. A identidade do site é sempre fornecida pelo host. Hooks de renderização retornam um campo html; hooks de validação retornam um campo booleano valid. Cada validador obrigatório deve retornar true explicitamente. Os demais hooks de backend retornam um array. Os campos enviados são limitados e contêm apenas os campos públicos do formulário e a prova. Objetos do framework, segredos, tokens de usuário e conexões de banco de dados nunca são expostos.

Ponte de API e permissões

$pluginApi expõe get, post, put, patch e delete com um ID de operação literal e um array de parâmetros. Cada operação é verificada no manifesto quanto a método, capacidade, esquema de solicitação, esquema de resposta e restrição opcional de plugin. URLs não expostas são negadas. O escopo de site e de plugin não pode ser substituído. As operações de configuração compartilham o mesmo serviço de aplicação que o gerenciamento de configuração da Internal API autenticada. As gravações de configuração são limitadas a manipuladores do ciclo de vida autorizados e a ações de gerenciamento do painel autorizadas pelo proprietário. Carregadores de página, ações de visualização, endpoints públicos e hooks de renderização não podem alterar a configuração da instalação.

Ver a sintaxe suportada e os limites de execução
{
    "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 e endpoints públicos

Os pacotes registram arquivos locais de script, estilo e imagem. Scripts/estilos são carregados uma vez quando o plugin é renderizado; apenas os arquivos registrados são servidos. O HTML embutido do plugin usa uma lista de elementos/atributos permitidos. A Content Security Policy de temas personalizados autoriza os hashes dos scripts registrados com Subresource Integrity. Os endpoints usam /__eyivo/plugins/{namespace}/{name}/{endpoint}, métodos explícitos e campos de entrada declarados. A resolução do inquilino, os limites de taxa e a proteção CSRF para métodos inseguros continuam sob controle do host. Endpoints de plugins nunca adicionam rotas arbitrárias ao aplicativo.

Verificação humana

eyivo/human-verification é obrigatório em todos os sites. O serviço compartilhado em plugins.eyivo.com/verification usa desafios PNG criptografados de seis dígitos que expiram em 180 segundos. A identidade de cliente confiável, os serviços concedidos e os IPs de origem são aplicados. UUIDs de formulário registrados e IDs de tentativa vinculam cada desafio; a verificação também vincula a carga canônica. Tentativas aceitas idênticas podem recuperar o resultado por 24 horas, portanto mantenha as chaves anteriores durante esse período mais uma margem de relógio/rede. Provas que acabaram de expirar falham. O banco de dados compartilhado de plugins armazena o estado de repetição/limite de taxa/recibos, nunca respostas de formulários nem histórico da caixa de entrada. Os pacotes não recebem acesso ao banco de dados.

Acessibilidade e comportamento do navegador

O controle tem rótulos localizados, orientação de preenchimento, anúncios de status e um botão para atualizar a imagem. O JavaScript deve estar ativado. Este modo inicial apenas com imagem não pode ser concluído por visitantes que não conseguem perceber a imagem; um desafio alternativo acessível não faz parte da v1. Páginas com formulários suportados ignoram o cache HTML compartilhado. As pré-visualizações de temas mantêm a política sem scripts e não podem enviar desafios funcionais.

Operações de API expostas

Use estes IDs de operação por meio de $pluginApi. Cada entrada abaixo vem do mesmo manifesto de exposição usado para aplicar as permissões em tempo de execução.

GET Desde a API 1

site.settings.read

Read the current plugin's namespaced site configuration.

site.settings.read

$pluginApi->get('site.settings.read', []);
Ver parâmetros, resposta e restrições
{
    "parameters": [],
    "response": {
        "configuration": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
PUT Desde a API 1

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, resposta e restrições
{
    "parameters": {
        "configuration": {
            "type": "object",
            "required": true
        }
    },
    "response": {
        "configuration": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
GET Desde a API 1

form.verification.widget

Render a localized verification control for a supported form.

form.inject

$pluginApi->get('form.verification.widget', []);
Ver parâmetros, resposta e restrições
{
    "parameters": [],
    "response": {
        "html": {
            "type": "string",
            "max": 16384
        }
    },
    "restricted_to_plugins": [
        "eyivo/human-verification"
    ],
    "deprecated": false
}
GET Desde a API 1

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, resposta e restrições
{
    "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 Desde a 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']]);
Ver parâmetros, resposta e restrições
{
    "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 Desde a 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', []);
Ver parâmetros, resposta e restrições
{
    "parameters": [],
    "response": {
        "result": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
GET Desde a 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', []);
Ver parâmetros, resposta e restrições
{
    "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 Desde a 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', []);
Ver parâmetros, resposta e restrições
{
    "parameters": {
        "submission_id": {
            "type": "string",
            "max": 36,
            "required": true
        }
    },
    "response": {
        "result": {
            "type": "object"
        }
    },
    "restricted_to_plugins": null,
    "deprecated": false
}
PATCH Desde a 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', []);
Ver parâmetros, resposta e restrições
{
    "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 Desde a 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', []);
Ver parâmetros, resposta e restrições
{
    "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 Desde a 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', []);
Ver parâmetros, resposta e restrições
{
    "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 Desde a API 1

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, resposta e restrições
{
    "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 Desde a 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', []);
Ver parâmetros, resposta e restrições
{
    "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
}

Compatibilidade e implantação

Plugin API 1 suporta Forms API 1 e Panel API 1/2. Pacotes de formulários exigem páginas de lista/detalhe e declarações de menu da Panel API 2. Contratos não suportados e sintaxe insegura são rejeitados. Implante primeiro o serviço de recibos de verificação, depois as migrações de master/conteúdo, a reconciliação de plugins, a migração legada e a contabilização de uso; implante em seguida o renderizador compatível do aplicativo e a referência do Apex. Preserve o histórico e as chaves antigas durante uma reversão. Todos os temas incluídos usam formulários registrados; POSTs de contato legados retornam 410. O manifesto de exposição fornece esta referência e as permissões em tempo de execução.