Especificação funcional do plugin emcp-tools v3.12.1 (msrbuilds/elementor-mcp, GPL-2.0-or-later) via leitura directa do código-fonte em emanuelalmeida.pt. - 00: arquitectura (bootstrap, ability registrar, dispatcher, MCP adapter) - 01: Elementor classico (paginas, layout, widgets, templates, globals) - 02: Elementor Atomic v4 + Gutenberg - 03: WordPress core (conteudo, media, settings, temas) - 04: Themer (CPT, condicoes, render, PHP templates) - 05: Redirects + change ledger unificado (rollback) - 06: Sandbox PHP snippets + custom widgets - 07: Filesystem/DB/WP-CLI/Security/Performance (maior risco) - 08: Integracoes terceiros (ACF, Meta Box, forms, SEO) - 09: Stock images + Cloud + OAuth - 10: Sistema de modulos + inventario Pro-only (30 classes) - INDEX: sintese, sequencia de construcao, tabela de risco Produzido por 10 subagentes code-explorer em paralelo + revisao cruzada de consistencia. Cada doc inclui blueprint de replica (copiar/simplificar/omitir).
37 KiB
08 — Integrações com plugins de terceiros (ACF, Meta Box, Forms, SEO, WooCommerce)
Fonte: leitura directa do código-fonte emcp-tools v3.12.1 (build Free), instalado em
emanuelalmeida.pt (/home/ealmeida/emanuelalmeida.pt/wp-content/plugins/emcp-tools/),
19-08-2026. Cobre as classes que ligam o EMCP Tools a dados/estruturas geridas por outros
plugins: ACF, Meta Box, plugins de formulários (Contact Form 7 no Free) e plugins de SEO
(Slim SEO no Free), mais o inventário de integrações Pro-only que a árvore Free referencia
mas não contém (WooCommerce, 8 plugins de formulários adicionais, 6 plugins de SEO
adicionais, 2 pacotes de widgets Elementor, e a Ultimate Addons for Elementor).
Contexto arquitectural (ver docs/00-ARQUITECTURA.md): toda ability regista-se via
emcp_tools_register_ability() — nunca wp_register_ability() directamente —, que normaliza o
schema, envolve o execute_callback num veto de escrita (emcp_tools_before_write) e num
normalize_result() (garante retorno sempre objecto JSON), antes de delegar ao core. O gating
de cada grupo vem de includes/abilities/class-ability-registrar.php (lido na íntegra nesta
tarefa) — os excertos exactos são reproduzidos abaixo, não parafraseados.
0. Padrão comum às cinco integrações deste documento: o "dispatcher de dois braços"
Todas as cinco famílias (ACF, Meta Box, Forms, SEO — WooCommerce presumivelmente também,
mas Pro) seguem a MESMA forma: em vez de registar N abilities MCP individuais (uma por
operação), registam exactamente duas — um tool <domínio>-read e um tool <domínio>-write —
cada uma com o schema { operation?: string, arguments?: object }. Chamar sem operation
devolve um catálogo de descoberta ({ mode, operations: [{operation, description, ...}] });
chamar com operation despacha para um executor interno. Isto é o MESMO padrão de "compact tool
mode" que o dispatcher global de 3 tools usa ao nível do servidor inteiro (docs/00, §4) —
aqui aplicado ao nível de CADA integração de terceiros, mantendo o catálogo total de abilities
pequeno mesmo com dezenas de operações internas por plugin.
Duas implementações distintas deste padrão coexistem no código:
- ACF e Meta Box — cada classe implementa o dispatcher "à mão" (método
dispatch()privado +operations()que devolve o mapa nome→{mode,run,perm,desc,...}). Não há classe base partilhada entre ACF e Meta Box. - Forms e SEO — têm uma classe base abstracta partilhada por TODAS as integrações da
categoria (
EMCP_Tools_Form_Integration,EMCP_Tools_SEO_Integration), da qual CF7 e Slim SEO (Free) e os 8+6 plugins Pro (não presentes nesta árvore) herdam. A base implementaregister(),dispatch(), o catálogo de descoberta, o schema{operation,arguments}e uma gate de confirmação genérica (confirm: trueobrigatório emargumentspara operações marcadasconfirm=>trueno mapa de operações — mecanismo de "são irreversíveis" ainda por usar em CF7/Slim SEO Free, mas presumivelmente usado pelos plugins Pro com delete de entradas/redirects).
Nuance de segurança repetida nas 4 classes: o permission_callback registado no MCP (a
"porta grossa" que decide se o tool sequer aparece/corre) é sempre uma capacidade larga
(edit_posts para leitura; para escrita varia — ver tabelas abaixo). A gate fina e real por
operação corre DENTRO do dispatch(), comparando com o perm específico de cada operação do
mapa operations(). Um utilizador pode assim ver o tool acf-write listado mas ser recusado
(forbidden) ao tentar create-field-group se só tiver edit_posts e não manage_options.
1. ACF (Advanced Custom Fields / ACF PRO)
Ficheiro: includes/abilities/class-acf-abilities.php (1627 linhas — a maior classe de
abilities do plugin). Classe: EMCP_Tools_ACF_Abilities. Desde: 3.2.1.
Condição de registo (class-ability-registrar.php):
// ACF abilities — only when Advanced Custom Fields (free or Pro) is active.
if ( class_exists( 'EMCP_Tools_ACF_Abilities' ) && EMCP_Tools_ACF_Abilities::acf_active() ) {
$acf = new EMCP_Tools_ACF_Abilities();
$acf->register();
...
}
acf_active() = function_exists('acf_get_field_groups') (ACF free OU Pro, qualquer versão
recente). Sem gate de licença Pro do EMCP em si — funciona com ACF free.
1.1 Abilities MCP registadas (2)
| Ability | Input schema | Descrição | permission_callback |
readonly/destructive |
|---|---|---|---|---|
emcp-tools/acf-read |
{ operation?: string, arguments?: object } |
Lê dados ACF: field groups, valores de campos, options pages, e (ACF 6.1+) CPTs/taxonomias geridos por ACF. Sem operation → catálogo. |
check_read_permission() = current_user_can('edit_posts') |
readonly=true, destructive=false, idempotent=true |
emcp-tools/acf-write |
{ operation?: string, arguments?: object } |
Escreve dados ACF (valores, field groups, CPTs/taxonomias). Escritas desligadas por omissão (activar em Tools → Plugins → ACF). Sem operation → catálogo. |
check_read_permission() — nota: a gate registada no MCP é a mesma de leitura; a gate real de escrita (manage_options/edit_post) corre por operação dentro de dispatch() |
readonly=false, destructive=false, idempotent=false |
1.2 As 15 operações internas (via operation + arguments)
| operação | modo | perm (capability) |
requer ACF 6.1+? | O que faz |
|---|---|---|---|---|
list-field-groups |
read | check_read_permission (edit_posts) |
não | Lista field groups: key, id, title, active, local, field_count. Args opcionais: post_id (filtra por contexto), search, active_only (default true). |
get-field-group |
read | check_read_permission |
não | Devolve um field group completo: location rules + árvore recursiva de campos (sub_fields/layouts até profundidade 10). Args: { key } (key ou ID numérico). |
list-options-pages |
read | check_read_permission |
não | Lista options pages ACF (feature PRO; vazio em ACF free). Sem args. |
get-fields |
read | check_fields_permission (edit_post/manage_options conforme alvo) |
não | Lê valores de campos de um post ou options page. Args: { post_id } OU { options_page }; opcionais { fields: string[] } (filtra), { include_field_objects: bool } (envolve valor com type/label). |
list-post-types |
read | check_manage_permission (manage_options) |
sim | Lista CPTs geridos por ACF (acf-post-type CPT interno). |
get-post-type |
read | check_manage_permission |
sim | Devolve um CPT ACF completo por { key }. |
list-taxonomies |
read | check_manage_permission |
sim | Lista taxonomias geridas por ACF. |
get-taxonomy |
read | check_manage_permission |
sim | Devolve uma taxonomia ACF completa por { key }. |
update-fields |
write | check_fields_permission |
não | Escreve valores de campos (incl. linhas repeater/flexible/gallery) num post ou options page. Args: { post_id|options_page, fields: {name: value} }. Campos de tipo PRO (repeater/flexible_content/gallery/clone) recusados se ACF não for Pro. Linhas de flexible_content validadas contra acf_fc_layout conhecido. Regista before-image no Change Ledger (EMCP_Tools_Change_Recorder::record_acf_fields) se essa classe existir. |
create-field-group |
write | check_manage_permission |
não | Cria um field group com campos + location rules via acf_import_field_group() (persiste no CPT acf-field-group; NÃO usa acf_add_local_field_group(), que seria só memória). Args: { title, fields: [...], location?: [[...]] }. |
update-field-group |
write | check_manage_permission |
não | Edita um field group guardado em BD: título, location, active, position, adiciona novos campos (add_fields), altera settings mutáveis de campos existentes (update_fields). Recusa grupos local (registados por acf-json/PHP) e qualquer alteração a name/type de um campo existente (imutáveis — evitaria orfanar postmeta). Sem delete. |
create-acf-post-type |
write | check_manage_permission |
sim | Regista um CPT via ACF (dados, sem código) usando acf_import_post_type(). Args: { post_type, title, singular?, public?, hierarchical?, show_in_rest?, supports?, has_archive?, taxonomies? }. Slug validado contra 21 slugs reservados do WordPress (post, page, attachment, etc.). |
update-acf-post-type |
write | check_manage_permission |
sim | Edita um CPT ACF existente por { key }. Slug imutável (recusa se post_type no input difere do actual — orfanaria conteúdo). |
create-acf-taxonomy |
write | check_manage_permission |
sim | Regista uma taxonomia via ACF. Args: { taxonomy, title, object_type: string[] (post types), singular?, hierarchical?, public?, show_in_rest? }. |
update-acf-taxonomy |
write | check_manage_permission |
sim | Edita uma taxonomia ACF existente por { key }. Slug imutável (orfanaria termos). |
cpt_tax_supported() = function_exists('acf_get_acf_post_types') && acf_get_acf_taxonomies && acf_import_post_type && acf_import_taxonomy (ACF 6.1+). Operações CPT/tax são omitidas do catálogo de descoberta e recusam com acf_cpt_tax_unsupported se a versão for anterior.
1.3 Decisões de design não óbvias (ACF)
- Escrita sempre por
field_key, nunca porname: comentário no código —update_field()por nome falha silenciosamente em alvos que ainda não têm valor guardado para o campo. O resolvedor de campo (resolve_field()) tenta: (1) prefixofield_directo viaacf_get_field(), (2) índice por nome/key construído a partir dos field groups aplicáveis ao alvo, (3) fallbackget_field_object()para alvos options sem valor guardado ainda. update_field()não confia no valor de retorno: devolvefalsetambém num no-op (valor idêntico), por isso o sucesso é confirmado por uma releitura (get_field()) após cada escrita, não pelo booleano.- Índice de campos por request, invalidado após escrita:
field_index_cache(array associativo portarget) evita relistar field groups a cada operação, mas é explicitamente apagado (unset) apósupdate-fields/create-field-group/update-field-grouppara uma leitura subsequente no mesmo request ver o estado fresco. - Options pages sem lookup reverso: ACF não tem forma de mapear um alvo
optionsde volta aos seus field groups; o índice cai para (a) todo o field group com QUALQUER regralocationde tipooptions_page, mais (b)get_field_objects()para capturar campos já guardados mesmo sem regra de localização detectável. - Normalização de valores:
WP_Post/WP_User/WP_Termviram resumos compactos ({id, title/name, ...}); arrays de imagem/ficheiro ACF (return_format=array) reduzidos a{id, url, alt, mime}em vez do array completo (que incluisizescom dezenas de entradas). - Sem delete, em lado nenhum: nem field groups, nem campos, nem valores, nem CPTs/taxonomias — write-only-forward. Confirma o padrão documentado no cabeçalho do ficheiro: "there is deliberately NO delete tool".
2. Meta Box (metabox.io)
Ficheiro: includes/abilities/class-metabox-abilities.php. Classe:
EMCP_Tools_Meta_Box_Abilities. Desde: 3.4.2.
Condição de registo:
// Meta Box abilities — only when Meta Box (free or extensions) is active.
if ( class_exists( 'EMCP_Tools_Meta_Box_Abilities' ) && EMCP_Tools_Meta_Box_Abilities::metabox_active() ) {
$metabox = new EMCP_Tools_Meta_Box_Abilities();
$metabox->register();
...
}
metabox_active() = defined('RWMB_VER') && function_exists('rwmb_get_registry').
2.1 Abilities MCP registadas (2)
| Ability | Input schema | Descrição | permission_callback |
readonly/destructive |
|---|---|---|---|---|
emcp-tools/metabox-read |
{ operation?, arguments? } |
Lê field groups Meta Box registados, as suas definições de campo, e valores de campo num post/objecto. | check_read_permission() = edit_posts |
readonly=true, idempotent=true |
emcp-tools/metabox-write |
{ operation?, arguments? } |
Escreve valores de campo Meta Box. Desligado por omissão (Tools → Plugins → Meta Box). | check_read_permission() — mesma nota que ACF: gate real é por operação |
readonly=false, destructive=false, idempotent=false |
2.2 As 4 operações internas
| operação | modo | Descrição |
|---|---|---|
list-field-groups |
read | Lista meta boxes registados: id, title, object_type, post_types[], field_count. Args opcionais: search, object_type. |
get-field-group |
read | Um meta box completo por { id }: título, object_type, post_types, árvore de campos recursiva (campos clone/group aninhados até profundidade 10). |
get-fields |
read | Lê valores via rwmb_meta(). Args: { post_id } OU { object_type, object_id }; opcionais { fields: string[], include_field_settings: bool }. |
update-fields |
write | Escreve valores via rwmb_set_meta() (não devolve nada — confirmação por releitura). Args: { post_id|object_type+object_id, fields: {id: value} }. |
Ambas as operações de leitura/escrita têm o mesmo perm: check_read_permission/check_fields_permission — sem distinção read/write op-a-op como no ACF (só há uma operação de escrita).
2.3 Diferenças-chave face ao ACF
- Meta Box free core não tem UI de construção de campos — campos são declarados em PHP via o filtro
rwmb_meta_boxes. Por isso NÃO HÁ authoring de field groups/CPT/taxonomia aqui (ao contrário do ACF) — só leitura de definições e leitura/escrita de VALORES. - Nomenclatura invertida face ao ACF: no Meta Box, o
idde um campo É a meta key (equivalente aonamedo ACF), enameé o label humano (equivalente aolabeldo ACF) — nota explícita no cabeçalho do ficheiro por ser uma fonte comum de confusão ao portar lógica entre as duas integrações. applicable_fields(): para alvospost, só considera meta boxes cujopost_typesinclui o tipo do post concreto (Meta Box permite meta boxes restritos a post types específicos); para outrosobject_type, aceita todas as meta boxes desse tipo sem mais filtragem.- Normalização de valor: reconhece o formato específico de imagem/ficheiro do Meta Box — array associativo com
ID+urlE (full_urlOUpathOUmime_type) — distinto da assinatura ACF (ID+url+mime_type). resolve_target()tem um caso de normalização subtil:{ object_type:'post', object_id:N }sempost_idé reescrito internamente para o caminhopost_id(garante o mesmo 404-check e gateedit_postque{post_id:N}receberia diretamente).
3. Forms — infra-estrutura genérica (EMCP_Tools_Form_Integration)
Ficheiro: includes/abilities/forms/class-form-integration.php. Classe: classe base
abstracta EMCP_Tools_Form_Integration. Desde: 3.5.0. Directório forms/ no Free contém
apenas ESTE ficheiro + class-cf7-integration.php — nenhum outro ficheiro de plugin de
formulário existe nesta árvore (confirmado por listagem directa, secção 6).
3.1 Contrato abstracto
Cada integração concreta implementa:
id(): string— id curto, usado para construir os nomes dos tools (<id>-read/<id>-write).label(): string— label humano.is_active(): bool— se o plugin de formulários alvo está activo.operations(): array<string,array>— mapanome => { mode, run, perm, desc, confirm? }.
is_available() (usado pelo registrar para decidir se regista) = is_active() por omissão.
3.2 O que a base fornece a TODAS as integrações de formulários
register()— regista os dois tools<id>-read/<id>-writecom o schema partilhado{ operation?, arguments? }. Meta annotations do tool write:readonly=false, destructive=true, idempotent=false— nota: destructive=true por omissão na base, diferente de ACF/Meta Box/SEO (que marcamdestructive=false) — reflecte que integrações Pro de formulários provavelmente apagam entradas/submissões, uma operação genuinamente destrutiva que CF7 (sem armazenamento de submissões) nunca exerce.can_read()=edit_posts;can_write()=manage_options— ambas as gates coarse registadas no MCP; a gate real corre por operação via$op['perm'].dispatch()— resolveoperation, verificais_active()(senãoplugin_inactive, HTTP 409), valida a operação existe no modo certo (unknown_operation, HTTP 404), corre$op['perm'](forbidden, HTTP 403), e — gate de confirmação genérica: se$op['confirm']===true, exigearguments.confirm===true(senãoconfirmation_required, HTTP 400); o campoconfirmé removido de$argsantes de chamar o executor real (não polui o input do handler).- Sem mecanismo de change-ledger automático — ao contrário da base SEO (secção 4), a base Forms não regista snapshots before-image; qualquer undo teria de ser implementado por integração concreta.
4. Contact Form 7 (integração Free com ficheiro dedicado)
Ficheiro: includes/abilities/forms/class-cf7-integration.php. Classe:
EMCP_Tools_CF7_Integration extends EMCP_Tools_Form_Integration. Desde: 3.5.0.
Condição de registo:
if ( class_exists( 'EMCP_Tools_CF7_Integration' ) ) {
$form_integrations[] = new EMCP_Tools_CF7_Integration();
}
... // depois, para todos os $form_integrations:
foreach ( $form_integrations as $form_integration ) {
if ( $form_integration->is_available() ) {
$form_integration->register();
...
}
}
is_active() = class_exists('WPCF7_ContactForm') || defined('WPCF7_VERSION'). Nenhum gate de
licença Pro do EMCP — CF7 é a ÚNICA integração de formulários incluída no build Free.
Verificado contra Contact Form 7 6.1.6 (comentário no cabeçalho do ficheiro lista a superfície
API usada: WPCF7_ContactForm::find(), ::get_instance(), ->id()/->name()/->title(),
->scan_form_tags(), ->prop(), ->set_properties()+->save()).
4.1 Abilities MCP registadas (2, via a base)
| Ability | Descrição gerada (label().' Read/Write') |
permission_callback |
|---|---|---|
emcp-tools/cf7-read |
"Contact Form 7, read operations. Call with no operation to list them." | can_read() = edit_posts |
emcp-tools/cf7-write |
"Contact Form 7, write operations..." | can_write() = manage_options |
4.2 As 7 operações internas
| operação | modo | perm (capability CF7 real) |
Descrição |
|---|---|---|---|
list-forms |
read | wpcf7_read_contact_forms (≈edit_posts) |
Lista todos os formulários CF7: id, title, slug, field_count. |
get-form |
read | idem | Um formulário completo por { form_id }: fields[], mail, mail_2, messages, additional_settings. |
list-notifications |
read | idem | Os dois templates de email (mail, mail_2) por { form_id }. |
get-settings |
read | idem | messages + additional_settings por { form_id }. |
update-notification |
write | wpcf7_edit_contact_forms (≈publish_pages) |
Actualiza um template de email por merge (não substitui): { form_id, notification: "mail"|"mail_2", mail: {subject?, sender?, recipient?, body?, additional_headers?, attachments?, use_html?, active?} }. |
update-messages |
write | idem | Actualiza mensagens de validação/resposta por merge: { form_id, messages: {key: value} }. |
update-form-settings |
write | idem | Substitui por completo o bloco Additional Settings: { form_id, additional_settings: string }. |
Nota: os perm reais aqui são MAIS finos que a gate coarse da base (can_write()=manage_options)
— usam as capacidades nativas do CF7 (wpcf7_edit_contact_forms), que por omissão mapeiam para
publish_pages, não manage_options. Um editor sem manage_options mas com publish_pages é
assim recusado pela gate coarse do MCP (can_write) ANTES sequer de chegar à gate fina do CF7 —
uma limitação prática: a capability CF7 nativa nunca é realmente exercida nesta implementação
porque a gate MCP é mais restritiva.
CF7 não guarda submissões (sem addon Flamingo/DB próprio) — por isso não há operações de "entries" (ao contrário do que os 8 plugins Pro de formulários presumivelmente expõem, dado todos guardarem submissões nativamente).
5. SEO — infra-estrutura genérica (EMCP_Tools_SEO_Integration)
Ficheiro: includes/abilities/seo/class-seo-integration.php. Classe: base abstracta
EMCP_Tools_SEO_Integration. Desde: 3.5.0. Directório seo/ no Free contém apenas ESTE
ficheiro + class-slimseo-integration.php.
Distinção documentada no cabeçalho: isto é diferente do toolkit Pro SEO & Accessibility
(audit-page-seo/generate-meta-tags, ver doc 01/02), que ANALISA e GERA em vez de
ler/escrever os dados que um plugin de SEO já guarda.
5.1 Contrato abstracto
Mesma forma que Forms: id(), label(), is_active(), operations(). Diferenças de
comportamento da base face a Forms:
can_read()=edit_posts;can_write()=edit_posts(nãomanage_options— escrever SEO de um post é uma operação de editor normal, não administrativa).- Meta annotations do tool write:
readonly=false, destructive=false, idempotent=false— ao contrário de Forms, aquidestructive=falsepor omissão (escritas de metadados SEO nunca apagam dados irrecuperáveis por si mesmas). - Change-ledger automático (feature exclusiva da base SEO, ausente em Forms/ACF/Meta Box):
para qualquer operação de modo
writecujosargumentsincluampost_idouterm_id, a base chamarecordable_meta_keys($object)(que cada integração concreta sobrepõe — devolve as meta keys que ela escreve para esse tipo de objecto; vazio por omissão = sem registo automático), tira umsnapshot_meta()(before-image dessas keys) ANTES de correr o executor, e — se o resultado não forWP_ErroreEMCP_Tools_Change_Logexistir — grava uma entrada{domain:'seo', action:'update', target:'<id>:<object>:<obj_id>', rollback:{type:'meta-before-image', object, id, before}}. Isto dá rollback automático a TODAS as integrações SEO que declarem as suas meta keys, sem cada uma ter de implementar a lógica de undo.
5.2 Abilities MCP e a mesma gate de confirmação
A dispatch() reutiliza a MESMA forma de Forms: catálogo sem operation, plugin_inactive
(409) se is_active() falhar, unknown_operation (404), forbidden (403) por $op['perm'],
confirmation_required (400) se $op['confirm']===true e arguments.confirm não for true.
6. Slim SEO (integração Free com ficheiro dedicado)
Ficheiro: includes/abilities/seo/class-slimseo-integration.php. Classe:
EMCP_Tools_SlimSEO_Integration extends EMCP_Tools_SEO_Integration. Desde: 3.5.0.
Condição de registo:
if ( class_exists( 'EMCP_Tools_SlimSEO_Integration' ) ) {
$seo_integrations[] = new EMCP_Tools_SlimSEO_Integration();
}
... // depois, para todos os $seo_integrations, mesma forma que Forms:
foreach ( $seo_integrations as $seo_integration ) {
if ( $seo_integration->is_available() ) { $seo_integration->register(); ... }
}
is_active() = defined('SLIM_SEO_VER'). Slim SEO é a ÚNICA integração de SEO incluída no build
Free. Armazenamento confirmado ao vivo (comentário "Verified live" no cabeçalho): um único array
meta slim_seo por post/termo (chaves: title, description, canonical, noindex, nofollow, facebook_image, twitter_image), mais uma option slim_seo para as definições do site.
6.1 Abilities MCP registadas (2, via a base)
| Ability | permission_callback |
|---|---|
emcp-tools/slimseo-read |
can_read() = edit_posts |
emcp-tools/slimseo-write |
can_write() = edit_posts |
6.2 As 5 operações internas
| operação | modo | perm |
Descrição |
|---|---|---|---|
get-post-seo |
read | edit_posts |
Metadados SEO de um post por { post_id }: title, description, canonical, noindex, nofollow, og_image, twitter_image (view unificada — ver mapeamento abaixo). |
get-term-seo |
read | edit_posts |
Idem para { term_id }. |
get-settings |
read | manage_options |
Definições SEO do site (a option slim_seo). |
update-post-seo |
write | edit_posts |
Actualiza por merge campo-a-campo: { post_id, title?, description?, canonical?, noindex?, nofollow?, og_image?, twitter_image? }. |
update-term-seo |
write | edit_posts |
Idem para { term_id, ... }. |
recordable_meta_keys() sobreposto para devolver ['slim_seo'] (a única meta key, para
qualquer $object) — activa o registo automático no Change Ledger da base para AMBAS as
operações de escrita.
6.3 Camada de mapeamento field↔meta-key
A classe mantém um map(): array<string,string> que traduz o vocabulário unificado do EMCP
(title, description, canonical, noindex, nofollow, og_image, twitter_image) para as chaves
reais do array slim_seo do plugin (title, description, canonical, noindex, nofollow, **facebook_image**, twitter_image — nota: só og_image→facebook_image difere). read_view()
e apply() usam este mapa nos dois sentidos; noindex/nofollow são forçados a booleano
(!empty()), o resto passa por (string) quando escalar.
7. WooCommerce e demais integrações Pro-only — não presentes neste build
O class-ability-registrar.php referencia 18 classes de integração via class_exists() que
não existem em nenhum ficheiro desta árvore Free — confirmado por listagem directa de
includes/abilities/ (topo), includes/abilities/forms/, includes/abilities/seo/, e
includes/ (topo, todas as subpastas), sem grep recursivo disponível nesta sessão (as
ferramentas glob/grep não recursam paths ssh://; a confirmação foi feita listando cada
directório candidato e comparando os nomes de ficheiro presentes contra os nomes de classe
referenciados). Nenhum destes ficheiros existe nesta instalação — não há schemas nem
comportamento a documentar; só a metadata de gating, copiada verbatim do registrar.
| Classe (referência no registrar) | Plugin de terceiros integrado | Condição de gating exacta (copiada do registrar) |
|---|---|---|
EMCP_Tools_Woo_Integration |
WooCommerce | class_exists( 'EMCP_Tools_Woo_Integration' ) && EMCP_Tools_Woo_Integration::woo_active() — comentário no código: "WooCommerce abilities (Pro) — only when WooCommerce is active." |
EMCP_Tools_WPForms_Integration |
WPForms | function_exists('emcp_tools_fs') && emcp_tools_fs()->can_use_premium_code() (licença Pro) E class_exists($classe) E depois $integration->is_available() (= is_active(), plugin alvo activo) — comentário: "CF7 is free; the five [sic, na prática oito] entry-storing plugins are Pro. Each registers only when its plugin is active." |
EMCP_Tools_GravityForms_Integration |
Gravity Forms | idem (mesmo loop, mesma tripla gate: licença Pro + ficheiro presente + plugin activo) |
EMCP_Tools_FluentForms_Integration |
Fluent Forms | idem |
EMCP_Tools_NinjaForms_Integration |
Ninja Forms | idem |
EMCP_Tools_Formidable_Integration |
Formidable Forms | idem |
EMCP_Tools_MetForm_Integration |
MetForm | idem |
EMCP_Tools_SureForms_Integration |
SureForms | idem |
EMCP_Tools_Forminator_Integration |
Forminator | idem |
EMCP_Tools_Yoast_Integration |
Yoast SEO | function_exists('emcp_tools_fs') && emcp_tools_fs()->can_use_premium_code() E class_exists($classe) E $integration->is_available() — comentário: "Slim SEO is free; the other 6 are Pro. Each registers only when its SEO plugin is active." |
EMCP_Tools_RankMath_Integration |
Rank Math | idem |
EMCP_Tools_AIOSEO_Integration |
All in One SEO | idem |
EMCP_Tools_SeoPress_Integration |
SEOPress | idem |
EMCP_Tools_SEOFramework_Integration |
The SEO Framework | idem |
EMCP_Tools_SureRank_Integration |
SureRank | idem |
EMCP_Tools_EssentialAddons_Integration |
Essential Addons for Elementor | Só class_exists( 'EMCP_Tools_EssentialAddons_Integration' ) E $addon_pack->is_available() — sem o gate explícito emcp_tools_fs()->can_use_premium_code() no laço (ao contrário de Forms/SEO); comentário: "Elementor addon widget packs (Pro). Each pack contributes ONE read tool for discovery + curation; widgets are placed with the generic add-free-widget tool, so there is deliberately no write tool here." O ficheiro simplesmente não existe fora do build Pro, logo class_exists() é sempre falso no Free independentemente de licença. |
EMCP_Tools_PremiumAddons_Integration |
Premium Addons for Elementor | idem (mesmo laço, mesma ausência de gate de licença explícito no registrar) |
EMCP_Tools_UAE_Integration |
Ultimate Addons for Elementor (anteriormente Header Footer Elementor) | class_exists( 'EMCP_Tools_UAE_Integration' ) E $emcp_uae->is_available() — comentário: "Ultimate Addons for Elementor (Pro)... Both a widget pack AND a data plugin, so unlike the pure packs it keeps the house read/write dispatcher pair: discovery + templates on read, templates on write." |
Nota adicional confirmada pelo registrar (fora do escopo directo desta tarefa mas
adjacente): outras integrações de "Themes-tab" também Pro-only e ausentes desta árvore —
EMCP_Tools_GeneratePress_Integration, EMCP_Tools_GenerateBlocks_Integration,
EMCP_Tools_Blocksy_Blocks_Integration, EMCP_Tools_Blocksy_Extensions_Integration — todas só
class_exists() + is_available(), sem gate de licença explícito no laço (mesmo padrão de
EssentialAddons/PremiumAddons/UAE). Não fazem parte do escopo pedido (ACF/MetaBox/Forms/SEO/Woo)
mas são citadas aqui por confirmarem o padrão: todas as integrações Pro deste plugin usam
class_exists() simples como gate primário — a distinção Free/Pro é feita por PRESENÇA DE
FICHEIRO (a pasta emcp-pro/ inclui-os, emcp-tools/ não), não por uma verificação de licença
em runtime dentro de cada laço de registo individual (excepto forms/SEO, que adicionam
explicitamente emcp_tools_fs()->can_use_premium_code() como segunda camada — provavelmente
porque esses 14 ficheiros de facto existem no zip Pro mas o acesso à FUNCIONALIDADE ainda
depende de licença activa, ao contrário dos addon-packs/UAE, que talvez sejam gratuitos dentro do
próprio Pro sem sub-gate de licença adicional).
8. Blueprint para réplica
Copiar quase 1:1:
- O padrão de dispatcher de dois braços (
<domínio>-read/<domínio>-write, catálogo semoperation, despacho poroperation+arguments) — é a peça que mantém o catálogo total de tools pequeno com dezenas de integrações possíveis. Vale a pena ter DUAS variantes conforme a complexidade: dispatcher "à mão" para integrações com estado rico e reaproveitamento pesado (ACF, Meta Box — cada uma tem a sua própria noção de "target"/"field index"), e uma classe base abstracta partilhada para famílias homogéneas de integrações simples-mas-numerosas (Forms, SEO) onde cada concreta só defineoperations()+ osruncallables. - A gate de confirmação genérica da base (
confirm: trueobrigatório emargumentspara operações marcadasconfirm=>true) — mecanismo barato e reutilizável para qualquer operação irreversível futura (delete de entrada de formulário, delete de redirect, etc.), sem cada integração ter de reimplementar a checagem. - O change-ledger automático da base SEO (
recordable_meta_keys()+ snapshot before-image +EMCP_Tools_Change_Log::record()) é o exemplo mais elegante deste documento: dá rollback automático a qualquer integração que declare as suas meta keys, com ZERO código extra por integração concreta. Recomendação forte: subir este padrão para a base Forms também (hoje ausente lá) e generalizar para ACF/Meta Box (hoje cada uma implementa o seu próprio snapshot ad-hoc — ACF viaEMCP_Tools_Change_Recorder::record_acf_fields, Meta Box sem qualquer registo). Um único mecanismo de "snapshot de meta keys antes de escrever" partilhado por TODAS as integrações de terceiros pouparia manutenção e cobriria o gap do Meta Box. - Escrita sempre por identificador estável (ACF: sempre por
field_key, nunca porname) — é uma lição de bug real ("silently fails on targets that have no stored value yet"), replicar esta disciplina em qualquer integração própria que tenha um par nome-humano/chave-estável. - Imutabilidade de slug/tipo em updates (ACF post-type/taxonomy slug; CF7 sem rename de formulário) — recusar explicitamente mudanças que orfanariam dados existentes é barato e evita uma classe inteira de bugs de integridade referencial silenciosa.
Simplificar:
- O gate duplo de licença Pro em Forms/SEO (
can_use_premium_code()+class_exists()+is_available()) é redundante para uma réplica que não tem modelo de negócio Free/Pro — uma reescrita própria só precisa da terceira camada (is_active()/plugin alvo instalado). - A normalização de valores por tipo de objecto WP (
WP_Post/WP_User/WP_Term→ resumos compactos) está duplicada quase verbatim entre ACF e Meta Box (normalize_value()em cada classe, com pequenas variações de forma de imagem/ficheiro). Vale a pena extrair um helper único partilhado (EMCP_Tools_Value_Normalizer::normalize($value)) parametrizável por "assinatura de imagem/ficheiro" (ACF:ID+url+mime_type; Meta Box:ID+url+ uma defull_url/path/mime_type) em vez de reimplementar a árvore recursiva duas vezes.
Deixar de fora (ou adiar bastante):
- CPT/taxonomia "managed by ACF" (ACF 6.1+) é uma feature de nicho (a maioria dos sites regista CPTs por código, não pela UI do ACF) — só vale a pena replicar se o público-alvo da réplica usar isto activamente. É também a parte mais frágil (7 das 15 operações ACF, gate por versão, slugs reservados) — maior superfície de manutenção por unidade de valor entregue.
- Options pages ACF — feature exclusivamente Pro do plugin de terceiros; sem ACF Pro
instalado esta parte do código é sempre um array vazio. Só vale a pena manter o "stub" que
devolve
{pro: false, pages: []}graciosamente, não investir em lógica adicional.
Risco/gotcha não óbvio encontrado no código (citação directa):
class-ability-registrar.php, comentário notry/catchderegister_all(): "Ability registration runs on every admin page load and every REST request, so an exception here is a site-wide fatal: wp-admin becomes unreachable... (issue #100, where a host malware scanner had quarantined one of our class files, leavingrequire_oncesatisfied but the class undeclared). No single tool group is worth locking an admin out of their own site." — implicação directa para as 5 integrações deste documento: cada bloco de registo (ACF, Meta Box, cada form/SEO integration) corre dentro destetry/catchamplo a nível deregister_groups(); um erro fatal ao construir QUALQUER uma destas classes (ex. uma versão incompatível de ACF que remova uma função que o código assume existir) não derruba o site — simplesmente essa família de tools fica ausente do catálogo, silenciosamente, com log viaerror_log. Uma réplica DEVE envolver o registo de cada grupo de integração de terceiros no mesmo tipo de guarda — nunca deixar uma dependência externa instável poder tirar o wp-admin do ar.- ACF
execute_update_fields(): comentário explícito sobre a armadilha de escrever pornameem vez dekey— já citado acima, mas vale reforçar como o exemplo mais concreto de "testámos em produção e partiu" que este documento encontrou. - Meta Box
resolve_target(): a normalização silenciosa de{object_type:'post', object_id:N}para o caminhopost_idé subtil — sem ela, alguém podia contornar oedit_postgate passando o post porobject_type+object_idem vez depost_iddirecto (dois caminhos de input para o mesmo alvo, só um gated correctamente, se não fosse esta normalização).
Fonte
Leitura directa (19-08-2026) de:
includes/abilities/class-acf-abilities.php (1627 linhas, completo),
includes/abilities/class-metabox-abilities.php (completo),
includes/abilities/forms/class-form-integration.php (completo),
includes/abilities/forms/class-cf7-integration.php (completo),
includes/abilities/seo/class-seo-integration.php (completo),
includes/abilities/seo/class-slimseo-integration.php (completo),
includes/abilities/class-ability-registrar.php (completo, 480+ linhas — todo o register_groups()).
Listagem directa (não grep recursivo, indisponível para paths ssh:// nesta sessão) dos
directórios includes/abilities/, includes/abilities/forms/, includes/abilities/seo/ e
includes/ (topo, todas as subpastas) para confirmar a AUSÊNCIA física de qualquer ficheiro das
18 classes Pro-only listadas na secção 7. Cruzado com docs/00-ARQUITECTURA.md (mesma sessão de
documentação) para o contrato de emcp_tools_register_ability() e o padrão de dispatcher de
compact tool mode ao nível do servidor.