Files
emcp-tools-mapping/docs/08-INTEGRACOES-TERCEIROS.md
Claude Code 8ada367bd0 docs: mapeamento completo do EMCP Tools (11 docs, ~6070 linhas)
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).
2026-08-19 06:41:04 +01:00

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:

  1. 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.
  2. 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 implementa register(), dispatch(), o catálogo de descoberta, o schema {operation,arguments} e uma gate de confirmação genérica (confirm: true obrigatório em arguments para operações marcadas confirm=>true no 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 por name: 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) prefixo field_ directo via acf_get_field(), (2) índice por nome/key construído a partir dos field groups aplicáveis ao alvo, (3) fallback get_field_object() para alvos options sem valor guardado ainda.
  • update_field() não confia no valor de retorno: devolve false també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 por target) evita relistar field groups a cada operação, mas é explicitamente apagado (unset) após update-fields/create-field-group/update-field-group para uma leitura subsequente no mesmo request ver o estado fresco.
  • Options pages sem lookup reverso: ACF não tem forma de mapear um alvo options de volta aos seus field groups; o índice cai para (a) todo o field group com QUALQUER regra location de tipo options_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_Term viram 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 inclui sizes com 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 id de um campo É a meta key (equivalente ao name do ACF), e name é o label humano (equivalente ao label do 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 alvos post, só considera meta boxes cujo post_types inclui o tipo do post concreto (Meta Box permite meta boxes restritos a post types específicos); para outros object_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+url E (full_url OU path OU mime_type) — distinto da assinatura ACF (ID+url+mime_type).
  • resolve_target() tem um caso de normalização subtil: { object_type:'post', object_id:N } sem post_id é reescrito internamente para o caminho post_id (garante o mesmo 404-check e gate edit_post que {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> — mapa nome => { 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>-write com 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 marcam destructive=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() — resolve operation, verifica is_active() (senão plugin_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, exige arguments.confirm===true (senão confirmation_required, HTTP 400); o campo confirm é removido de $args antes 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ão manage_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, aqui destructive=false por 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 write cujos arguments incluam post_id ou term_id, a base chama recordable_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 um snapshot_meta() (before-image dessas keys) ANTES de correr o executor, e — se o resultado não for WP_Error e EMCP_Tools_Change_Log existir — 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 sem operation, despacho por operation+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ó define operations() + os run callables.
  • A gate de confirmação genérica da base (confirm: true obrigatório em arguments para operações marcadas confirm=>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 via EMCP_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 por name) — é 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 de full_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 no try/catch de register_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, leaving require_once satisfied 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 deste try/catch amplo a nível de register_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 via error_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 por name em vez de key — 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 caminho post_id é subtil — sem ela, alguém podia contornar o edit_post gate passando o post por object_type+object_id em vez de post_id directo (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.