Files
emcp-tools-mapping/docs/03-WORDPRESS-CORE-TEMAS.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

44 KiB
Raw Permalink Blame History

03 — WordPress core (conteúdo/media/settings/plugins/temas/users/menus) e integrações de temas/frameworks de blocos

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. Ver docs/00-ARQUITECTURA.md para o contrato emcp_tools_register_ability() e o padrão de arranque; este documento assume esse contexto e não o repete.

Este grupo cobre duas famílias distintas de abilities:

  1. WordPress core puro (content, media, settings, plugins, themes, users, nav menus) — sempre activas, sem dependência de Elementor nem de nenhum framework de terceiros.
  2. Integrações de temas/frameworks de blocos (Astra, Spectra, Kadence, Kadence Blocks) — o padrão "Themes tab" de dois dispatchers (<id>-read/<id>-write) por integração, cada uma condicional à presença do tema/plugin correspondente. Free tem 5 integrações concretas (Active Theme + Astra + Spectra + Kadence + Kadence Blocks); GeneratePress/GenerateBlocks/ Blocksy são referenciadas no registrar mas não existem como ficheiros no build Free (ver §16).

1. EMCP_Tools_Content_Abilities — includes/abilities/class-content-abilities.php

Condição de registo: sempre-on (register_groups() linha ~158, sem guarda). Deliberadamente agnóstico ao Elementor — opera sobre post_content (HTML clássico ou markup de blocos Gutenberg) e nunca toca em _elementor_data.

8 tools. check_read_permission=edit_posts; check_create_permission=edit_posts (mesmo cap que ler — mimetiza o core WP, onde publicar é gated à parte); check_edit_permission=edit_posts

  • edit_post por-post quando há post_id; check_delete_permission=delete_posts + delete_post por-post.
Tool Input (resumo) O que faz Permission RO/Destr.
list-post-types public_only?:bool (default true) Lista post types registados (nome, label, hierárquico, supports, taxonomias); filtra internos (revision, nav_menu_item, wp_template, etc.) quando public_only. check_read_permission RO
list-taxonomies post_type?, include_terms?:bool, terms_limit?:int (≤500, default 100) Lista taxonomias (opcionalmente filtradas por post type), com termos embutidos opcionais. check_read_permission RO
create-post post_type?(default post), title, content, excerpt, status?(enum draft/publish/pending/private/future), slug?, author?, date?, parent?, menu_order?, comment_status?, terms?:{tax:ids[]}, meta?:{}, featured_image?:{id|url}|null wp_insert_post(). Recusa post types internos/inexistentes; publish exige publish_posts; author diferente do actual exige edit_others_posts; meta protegida (_-prefix ou is_protected_meta) é sempre recusada salvo allowlist via filtro emcp_tools_content_allowed_protected_meta. featured_image.url faz sideload via EMCP_Tools_Url_Guard::safe_download() (SSRF-guarded, bloqueia hosts privados/loopback/metadata cloud e revalida cada redirect). Regista change (EMCP_Tools_Change_Recorder::record_post_create). check_create_permission write, não-destr.
get-post post_id Serialização completa: title/slug/status/content/excerpt/datas/parent/menu_order/comment_status/permalink/edit_link/author/terms/meta(filtrada)/featured_image/is_elementor (flag _elementor_edit_mode==='builder'). check_read_permission RO
update-post post_id, campos parciais iguais ao create + terms_mode?(replace/append) Update parcial via wp_update_post(). Captura before-image (campos + meta + terms) para rollback via Change_Recorder::record_post_fields. Se o slug muda num post publicado, captura o URL antigo e sugere um redirect (with_redirect_suggestion → EMCP_Tools_Redirect_Abilities::push_suggestion, só quando EMCP_Tools_Redirect_Module::is_enabled()). check_edit_permission write, não-destr.
delete-post post_id, force?:bool force=false (default): wp_trash_post(). force=true: snapshot completo (Change_Recorder::snapshot_post) antes de wp_delete_post($id,true) (reversível). Ambos os caminhos sugerem redirect para o URL morto. check_delete_permission write, destrutivo
list-posts post_type?(str|array), status?(str|array), search?, taxonomy?:{tax:terms[]}(AND), author?, parent?, per_page?(≤100), page?, orderby?, order? WP_Query paginado, linhas compactas (sem body de conteúdo — usar get-post para isso). check_read_permission RO
set-post-terms post_id, taxonomy, terms:(int|string)[], mode?(replace/append/remove), create_missing?:bool(default true) wp_set_object_terms/wp_remove_object_terms. Quando create_missing=false, resolve nomes para IDs existentes via get_term_by(name→slug) e descarta os que não existem (não cria). check_edit_permission write, não-destr.

Detalhe de implementação notável: apply_write_extras() é o método partilhado usado por create-post/update-post para aplicar terms/meta/featured_image — inclui o comentário explícito no código sobre porquê carrega wp-admin/includes/{file,media,image}.php on-demand: essas funções (media_handle_sideload) não estão carregadas em pedidos REST/WP-CLI (que é onde o servidor MCP corre).

2. EMCP_Tools_Media_Library_Abilities — includes/abilities/class-media-library-abilities.php

Condição: sempre-on (linha ~143). Nasceu para preencher a lacuna que as tools de stock-image não cobrem: encontrar as fotos próprias do cliente já na Media Library (issue #25 do repo upstream). Recebe EMCP_Tools_Data no construtor (data-access layer partilhada — mesma classe usada por várias famílias de abilities de conteúdo Elementor, não específica de media).

5 tools.

Tool Input (resumo) O que faz Permission RO/Destr.
list-media search?, mime_type?(default image; aceita "any"), page?, per_page?(≤100), orderby?(date/title), order? WP_Query sobre attachment. O search do WP_Query cobre title/caption/description mas não alt text (vive em postmeta) — por isso resolve IDs por texto e por alt em duas queries ids-only e faz a união via post__in, sem filtros globais de query. check_read_permission (edit_posts) RO
get-media id Detalhe completo de 1 attachment: todos os tamanhos de imagem registados (url+dimensões via wp_get_attachment_image_src por tamanho), mime, filesize, alt, caption, descrição, autor, post_parent, metadata bruta. check_read_permission RO
upload-media filename, data(base64, aceita prefixo data:...;base64,), alt?, title?, caption?, description?, post_id?, convert_webp?:bool Companheira de sideload-image: sideload-image busca um URL que o SERVIDOR alcança; upload-media recebe bytes RAW do CLIENTE (útil quando o utilizador quer enviar um ficheiro do seu próprio computador). Decodifica base64 (limite UPLOAD_MAX_BYTES=32MB, filtrável via emcp_tools_upload_media_max_bytes), escreve para wp_tempnam(), valida tipo com wp_check_filetype_and_ext() contra get_allowed_mime_types() (bloqueia executáveis mesmo que um filtro afrouxe depois), entrega a media_handle_sideload(). Suporta convert_webp:false via filtro emcp_tools_optimize_attachment (idêntico a sideload-image). check_upload_permission (upload_files) write, não-destr.
update-media id, title?, alt?, caption?, description? Update parcial. description mapeia para post_content e não é sanitize_text_field (permitiria HTML legítimo — wp_update_post já aplica wp_filter_post_kses para quem não tem unfiltered_html). Captura before-image para rollback. check_edit_permission (por-attachment) write, não-destr.
delete-media id, confirm:true(obrigatório), force?:bool Destrutivo e efectivamente permanente: WordPress ignora a Trash para media a menos que MEDIA_TRASH esteja definido — o próprio schema avisa disto na descrição. Snapshot completo antes de apagar (post + meta + cópia trashed de todos os ficheiros) via Change_Recorder::snapshot_attachment, só quando não vai para trash. check_delete_permission (por-attachment) write, destrutivo

3. EMCP_Tools_Settings_Abilities — includes/abilities/class-settings-abilities.php

Condição: sempre-on (linha ~224). Nome deliberadamente distinto de EMCP_Tools_Settings_Validator (que valida settings de widgets Elementor — classe não relacionada).

Decisão de design central: NÃO expõe get_option/update_option arbitrário. Só a allowlist tipada e curada em allowlist() — 32 chaves cobrindo os 6 ecrãs de Settings do core (General/Reading/Writing/Discussion/Media/Permalinks). Cada entrada tem {group, label, type (string|int|bool|enum), writable, options?, min?, max?, pattern?}. Notavelmente ausentes da allowlist: siteurl/home (lock-out do site), users_can_register/default_role (escalada de registo). admin_email está presente mas writable=false (só leitura).

2 tools, ambas check_manage_permission = manage_options.

Tool Input O que faz RO/Destr.
get-settings group?(enum dos 6 ecrãs), keys?:string[] Sem args devolve as 32; filtra por grupo/chaves. Cada linha inclui value (coagido ao tipo declarado), writable, e options[] quando é enum — duplica como discovery para update-settings. RO
update-settings settings:{key:value} Escreve só chaves allowlisted+writable; tudo o resto (chave desconhecida, read-only, valor inválido) cai em skipped[] com motivo — uma chave má nunca aborta o batch. Coerção por tipo com clamp min/max (int), regex pattern (string, ex. permalink_structure), validação de enum. Se qualquer permalink_structure/category_base/tag_base mudar, chama flush_rewrite_rules(false) automaticamente e reporta rewrite_flushed. Regista before-map para rollback. write, não-destr. (idempotente)

4. EMCP_Tools_Plugin_Abilities — includes/abilities/class-plugin-abilities.php

Condição: sempre-on (linha ~229). 7 tools: 2 leitura (sempre activas) + 5 mutação (instalação/activação/desactivação/update/delete), todas construídas sobre APIs core (Plugin_Upgrader, plugins_api, activate_plugin/deactivate_plugins/delete_plugins) e guardadas por EMCP_Tools_Package_Guard (classe partilhada com theme-abilities — ver comentário do ficheiro: "as 5 tools de mutação vêm desligadas por omissão, o admin liga-as na tab Tools").

Tool Input O que faz Permission RO/Destr.
list-plugins status?(all/active/inactive) get_plugins() + get_site_transient('update_plugins') para flag de update disponível; inclui is_protected (via Package_Guard::is_protected_plugin). activate_plugins RO
search-plugins search, per_page?(≤50) plugins_api('query_plugins', …) — pesquisa no directório wordpress.org. install_plugins RO
install-plugin slug, activate?:bool plugins_api('plugin_information') → Plugin_Upgrader::install(). Fonte sempre wordpress.org — URLs arbitrários nunca são aceites (explicitamente afirmado na descrição da tool). install_plugins write, não-destr.
activate-plugin plugin(file ou slug de pasta) activate_plugin(). Resolve referência via resolve_plugin_file() (tenta como file exacto, depois como slug de pasta). activate_plugins write, idempotente
deactivate-plugin plugin deactivate_plugins(). Recusa plugins protegidos (EMCP Tools e Elementor nunca podem ser desactivados via MCP — protege contra o agente cortar o próprio ramo em que está sentado). activate_plugins write, idempotente
update-plugin plugin wp_update_plugins() refresca transient; se não há update pendente devolve up_to_date:true sem chamar o upgrader. Recusa plugins protegidos (mesma lista de deactivate). update_plugins write, não-destr.
delete-plugin plugin delete_plugins(). Recusa protegidos E recusa qualquer plugin activo (obriga a desactivar primeiro). delete_plugins write, destrutivo

5. EMCP_Tools_Theme_Abilities — includes/abilities/class-theme-abilities.php

Condição: sempre-on (linha ~233). Espelho exacto do padrão de plugin-abilities mas para temas (Theme_Upgrader, themes_api, switch_theme/delete_theme), mesmas 5+1 = 6 tools (sem search+install+switch+update+delete+list = 6, plugins tem 7 porque tem activate+deactivate separados; temas só têm switch-theme).

Tool Input O que faz Permission RO/Destr.
list-themes (nenhum) wp_get_themes() + get_site_transient('update_themes'); inclui parent (para child themes) e is_active. switch_themes RO
search-themes search, per_page?(≤50) themes_api('query_themes', …) sobre wordpress.org. install_themes RO
install-theme slug, activate?:bool themes_api('theme_information') → Theme_Upgrader::install(). Fonte sempre wordpress.org. install_themes write, não-destr.
switch-theme stylesheet switch_theme(). Recusa temas com $theme->errors() (load errors). switch_themes write, idempotente
update-theme stylesheet wp_update_themes() refresca transient; up_to_date:true sem upgrade se nada pendente. update_themes write, não-destr.
delete-theme stylesheet delete_theme(). Recusa o tema activo e o parent do tema activo (Package_Guard::active_theme_stylesheets()). delete_themes write, destrutivo

6. EMCP_Tools_User_Abilities — includes/abilities/class-user-abilities.php

Condição: sempre-on (linha ~238). O comentário de cabeçalho do ficheiro resume a filosofia: "the security boundary is the design" — sem tool de delete, sem tool de mudança de role, password sempre auto-gerada e nunca devolvida.

4 tools. Guarda de privilégio central: protected_caps() = {manage_options, promote_users, delete_users, edit_users, manage_network}. user_has_admin_caps($id) verifica se um utilizador tem QUALQUER uma destas caps (via user_can) — se sim, é intocável por update-user. role_has_admin_caps($role) faz o mesmo a nível de role, para bloquear create-user de atribuir uma role admin-grade.

Tool Input O que faz Permission RO/Destr.
list-users role?, search?, per_page?(≤100), page?, orderby?(registered/display_name/ID), order? WP_User_Query. Linhas compactas (id, username, display_name, email, roles, registered, post_count) — nunca password/auth. list_users RO
get-user id Detalhe: username/email/display_name/first_name/last_name/nickname/url/description/roles/registered/post_count + flag is_admin (= user_has_admin_caps, sinaliza que update-user vai recusar). list_users RO
create-user username, email, role?(default subscriber), first_name?, last_name?, display_name?, url?, description? Password gerada com wp_generate_password(24, true, true) (nunca devolvida). Envia email de "definir password" via wp_send_new_user_notifications($id, 'user'). Recusa role inexistente ou admin-grade. Anti-enumeração: erros existing_user_login/existing_user_email são normalizados para uma mensagem genérica ("that username or email is not available") para que a tool não sirva para enumerar contas existentes. create_users write, não-destr.
update-user id, email?, first_name?, last_name?, display_name?, nickname?, url?, description? Recusa se o alvo tem admin-caps. Constrói o update só a partir dos campos permitidos — role/password nunca são sequer lidos do input, logo nunca podem mudar aqui (não é uma verificação em runtime, é ausência estrutural do campo). Captura before-image para rollback. edit_users write, não-destr.

7. EMCP_Tools_Nav_Menu_Abilities — includes/abilities/class-nav-menu-abilities.php

Condição: sempre-on (linha ~243). Padrão de dois dispatchers (menu-read/menu-write), o mesmo padrão que as integrações de tema (§9-13) e as abilities ACF/SEO/forms (doc 08) usam: cada tool aceita {operation, arguments}; chamar sem operation devolve o catálogo de operações disponíveis (nome + descrição) — auto-descoberta sem precisar de schemas JSON per-operation separados. Ambos os dispatchers usam a mesma permissão: edit_theme_options.

13 operações no total:

Operação Modo Argumentos O que faz
list-menus read {} Todos os menus: id, name, slug, item count, locations atribuídas.
get-menu read {menu: id|slug|name} Um menu + a sua árvore de items aninhada (build_item_tree, construída a partir da lista flat via menu_item_parent).
list-locations read {} Locations de menu registadas pelo tema + qual menu (se algum) está atribuído a cada uma.
render read {menu|location, depth?, container?, container_class?, menu_class?, menu_id?} wp_nav_menu(['echo'=>false]) → devolve HTML. Útil para embutir num header custom.
create-menu write {name} wp_create_nav_menu().
rename-menu write {menu, name} wp_update_nav_menu_object().
delete-menu write {menu} wp_delete_nav_menu().
assign-location write {menu, location} set_theme_mod('nav_menu_locations', …). Valida que a location está registada pelo tema.
unassign-location write {location} Remove a atribuição de uma location.
add-item write {menu, type, object_id?, object?, url?, title?, parent?, position?, target?, classes?, description?, xfn?} wp_update_nav_menu_item(). resolve_item_type() valida type (custom/page/post/CPT/category/taxonomy) contra objectos reais existentes — nunca cria um item apontando para um post/termo inexistente. Items custom exigem url+title.
update-item write {item, title?, url?, parent?, position?, target?, classes?, description?, xfn?} Update parcial que preserva todos os campos não especificados (merge_existing_item() lê o item actual via wp_setup_nav_menu_item() antes de mesclar as mudanças — evita a armadilha comum de wp_update_nav_menu_item de apagar campos omitidos).
delete-item write {item} wp_delete_post($id, true) (items de menu são posts nav_menu_item).
reorder-items write {menu, items: [{id, parent?, position}]} Reordena/reparenta múltiplos items numa chamada, preservando os restantes campos de cada um (reutiliza merge_existing_item). Ignora silenciosamente items inválidos/de outro menu/com parent inválido, e conta só os efectivamente actualizados.

Validações estruturais notáveis: validate_parent() garante que um parent proposto é um item de menu real e pertence ao mesmo menu (não permite cruzar menus). resolve_item_type() faz a mesma validação de existência para add-item que set-post-terms faz para termos — nunca cria referências soltas.

8. EMCP_Tools_Image_Resize_Abilities — includes/abilities/class-image-resize-abilities.php

Condição: condicional dupla (registrar linha ~149) — só regista quando class_exists( 'EMCP_Tools_Image_Resize_Abilities' ) E class_exists( 'EMCP_Tools_Image_Optimization_Module' ) E EMCP_Tools_Image_Optimization_Module::module_is_active(). Ou seja: depende do módulo "Image Optimization" (um dos 9 módulos opcionais documentados no doc 10) estar activo — reutiliza a maquinaria de backup+compressão+WebP desse módulo (EMCP_Tools_Image_Resizer::resize(), classe do módulo, não desta ability).

1 tool: resize-media. attachment_id, width?, height? (pelo menos um dos dois; escala mantendo aspect ratio), crop?:bool (hard-crop exacto width×height, requer ambos). Permissão: edit_post($attachment_id) se especificado, senão upload_files. O ID do attachment e as suas URLs mantêm-se os mesmos (resize in-place), o original é feito backup (reversível), e todos os sub-tamanhos + WebP são regenerados. Se o cap de dimensão máxima do módulo for menor que o alvo pedido, o cap aplica-se silenciosamente.

9. EMCP_Tools_Theme_Integration (abstract) — includes/abilities/class-theme-integration.php

Serviço de suporte, não regista abilities por si — é a classe base abstracta para todas as integrações de tema/framework (§10-13 + os Pro-only do §16). Implementa o padrão "dois dispatchers" de forma genérica e reutilizável.

Contrato abstracto que cada subclasse implementa: id() (usado para construir os nomes das tools emcp-tools/<id>-read/-write), label(), is_available() (gate de disponibilidade — tema activo ou plugin activo), operations() (o mapa name => {mode, run, perm, desc}).

A base fornece:

  • register() — regista os dois dispatchers via emcp_tools_register_ability(). Ambos usam o mesmo dispatch_schema() genérico ({operation?, arguments?}).
  • can_read()/can_write() — gate grosseiro da tool (edit_theme_options por omissão); cada subclasse pode sobrepor (Spectra e Kadence Blocks sobrepõem para edit_posts, porque constroem conteúdo, não opções de tema — ver §12/§13).
  • dispatch() — resolve a operação pelo nome; se operation vazio devolve o catálogo de descoberta; valida que o modo (read/write) bate; corre a permissão específica da operação ($op['perm']) — nunca confia só no gate grosseiro do tool, cada operação individual re-verifica. Um erro 404 unknown_operation ou 403 forbidden tem status no WP_Error data.
  • capabilities() — descritor normalizado (supports_patterns, supports_preview, styles_model: none/uniqueid/styles-object/attributes) que um agente lê para saber, sem hard-coding por-framework, se este pack tem uma rota de inserção "editor-válida" (patterns) ou preview, e como o pack se auto-estiliza. Default {false, false, 'none'}; cada subclasse sobrepõe.

Blueprint chave: este é o padrão a copiar quase 1:1 numa réplica própria — uma classe base abstracta de ~250 linhas que qualquer integração de tema/plugin de terceiros herda, ganhando discovery + dispatch + permission-per-operation de graça. Adicionar um framework novo (ex. GeneratePress) é só escrever a subclasse concreta com operations().

10. EMCP_Tools_Active_Theme_Integration — includes/abilities/class-active-theme-integration.php

id()='theme'. is_available() = sempre true (funciona com qualquer tema activo — é o pack agnóstico-de-framework). Descrição do ficheiro: "Building pages reuses the Gutenberg/Elementor tools; this integration supplies the context the agent reasons over."

4 operações:

Operação Modo Argumentos O que faz
get-theme-context read {} Identidade + capacidades do tema activo: stylesheet/parent, is_child, framework detectado (contra KNOWN_FRAMEWORKS = {astra, kadence, generatepress, oceanwp, blocksy, neve, hello-elementor}), is_block_theme (wp_is_block_theme()), nome/versão, template_dir, theme supports probed (PROBED_SUPPORTS: post-thumbnails, custom-logo, editor-styles, wp-block-styles, align-wide, responsive-embeds, custom-background, html5), menu locations registadas, has_child (Child_Theme_Builder::child_exists()). "Call this first" — é o ponto de entrada de contexto antes de qualquer outra operação de tema.
get-mods read {} Todos os theme_mods do tema activo (estado do customizer).
set-mods write {values: {key: value}} set_theme_mod() por chave, salvo REFUSED_MODS = {nav_menu_locations, sidebars_widgets, custom_css_post_id} — mods estruturais recusados (esses domínios têm as suas próprias tools: nav-menu, widgets não têm tool própria, custom CSS é módulo separado).
create-child-theme write, perm=can_manage_theme (switch_themes+edit_theme_options) {confirm:true}(obrigatório) Delega para EMCP_Tools_Child_Theme_Builder::create() (§14). Exige confirm:true porque muda o tema activo E liga escrita de ficheiros.

11. EMCP_Tools_Astra_Integration — includes/abilities/class-astra-integration.php

id()='astra'. is_available() = 'astra' === get_template(). Lê/escreve a única option astra-settings sobre uma allowlist curada de 17 chaves (ALLOWLIST const), agrupadas em colors/typography/layout/header-footer — a mesma forma genérica get/update que EMCP_Tools_Settings_Abilities usa para o core WordPress (§3), aplicada ao option próprio do Astra. Allowlist explicitamente marcada como "verified against Astra 4.13.4 on the dev site".

2 operações: get-settings ({group?, keys?}) e update-settings ({values:{key:value}}, chaves não-allowlisted em skipped[]). read_value() prefere o resolver nativo astra_get_option() quando disponível (respeita o default do tema quando a option está ausente); senão cai no valor bruto da option. Detalhe de invalidação de cache notável: Astra só refresca o seu CSS dinâmico em cache no hook customize_save_after, não numa escrita simples de option — por isso execute_update_settings() chama explicitamente astra_clear_all_assets_cache() após qualquer update, senão as mudanças ficariam invisíveis até o próximo save do Customizer.

12. EMCP_Tools_Spectra_Integration — includes/abilities/class-spectra-integration.php

id()='spectra'. is_available() = EMCP_Tools_Spectra_Catalog::is_active(). Sobrepõe can_read()/can_write() para edit_posts (constrói conteúdo, não opções de tema). capabilities(): {supports_patterns:false, supports_preview:false, styles_model:'attributes'}.

3 operações — o padrão discover→inspect→act espelhando o catálogo de widgets Elementor:

Operação Modo Argumentos O que faz
list-blocks read {category?, search?} Catálogo compacto (via Spectra_Catalog::blocks_index()).
get-block-schema read {name?|names?[], full?} Atributos reais + defaults (Spectra_Catalog::real_attributes()) + markup de exemplo gerado. Reporta também shared_attributes (ver §12.1) e um note quando trunca à DEFAULT_CAP=30.
add-block write {post_id, block, attributes?, position?} Constrói o array de bloco parseado (build_block()), insere-o via EMCP_Tools_Block_Tree::insert() (serviço partilhado com Gutenberg — doc 02) na árvore existente do post, serializa de volta com serialize_block(), grava com wp_update_post. Gera block_id via EMCP_Tools_Id_Generator::generate().

12.1 EMCP_Tools_Spectra_Catalog — includes/blocks-catalog/class-spectra-catalog.php

Serviço de suporte (não regista abilities). Nada é adivinhado — as duas metades vêm do próprio Spectra:

  • Lista de blocos — UAGB_Block_Module::get_blocks_info() (a própria API pública do Spectra).
  • Atributos por bloco — lidos directamente do ficheiro fonte do Spectra includes/blocks/<slug>/attributes.php (via require, não introspecção de registry) — porque Spectra não regista todos os atributos no WP_Block_Type_Registry core.

STRUCTURE const documenta hints estruturais que não podem vir de attributes.php: quais blocos precisam de innerBlocks template (uagb/buttons→uagb/buttons-child, etc., 8 pares documentados) e quais são dinâmicos/server-rendered (post-grid, post-carousel, google-map, forms, etc. — add-block emite markup self-closing para estes).

SHARED_ATTRS é o achado mais interessante desta classe: documenta atributos nativos e reais do uagb/container que são registados via um helper partilhado (UAGB_Block_Helper) em vez de chaves literais em attributes.php — por isso não aparecem em real_attributes(), mas existem e são editáveis (background image/video com overlay, border-radius por canto, box-shadow). O comentário no código é explícito: "so they DO NOT appear in real_attributes()/get-block-schema, yet they are real, editable attributes an agent should use instead of a core/html workaround" — inclui até a gotcha exacta de que o overlay em gradiente vem de gradientValue, não de gradientOverlayColor1/2 como seria intuitivo.

13. EMCP_Tools_Kadence_Integration (theme settings) — includes/abilities/class-kadence-integration.php

id()='kadence'. is_available() = 'kadence' === get_template(). Lê/escreve theme_mods do Kadence (não uma option única como Astra) sobre uma allowlist de 16 chaves agrupadas em palette/colors/typography/layout/buttons/header-footer. Cada entrada da allowlist carrega um shape (string descritiva da forma do objecto esperado) — porque, ao contrário de Astra, os valores de Kadence são objectos estruturados, não escalares (ex. link_color = { highlight: "palette1", "highlight-alt": "palette2", style: "standard" }), e referenciam uma paleta global de 9 slots (palette1..palette9) definida em global_palette.

2 operações: get-settings/update-settings, mesma forma que Astra. read_value() prefere \Kadence\kadence()->option($key) (resolver nativo com theme_mod+default embutido). Sem invalidação de cache explícita — comentário do ficheiro nota que "Kadence renders dynamic CSS inline per request, so there is no cache to invalidate" (contraste directo com o gotcha do Astra em §11).

14. EMCP_Tools_Kadence_Blocks_Integration — includes/abilities/class-kadence-blocks-integration.php

id()='kadence-blocks'. is_available() = EMCP_Tools_Kadence_Blocks_Catalog::is_active(). Independente do tema activo (tal como Spectra — Kadence Blocks é um plugin, não amarrado ao tema Kadence). can_read()/can_write() = edit_posts. capabilities(): {supports_patterns:true, supports_preview:true, styles_model:'uniqueid'}.

5 operações — a mais rica das integrações de tema, porque acrescenta acesso à Kadence Prebuilt Library (patterns prontos) por cima do padrão discover→inspect→act:

Operação Modo Argumentos O que faz
list-blocks read {category?, search?} Catálogo curado dos 32 blocos top-level (via Kadence_Blocks_Catalog).
get-block-schema read {name?|names?[], full?} Atributos reais do WP_Block_Type_Registry (Kadence, ao contrário do Spectra, regista schemas completos com defaults no core registry — não precisa de ler ficheiro fonte). Inclui inner_blocks hint para containers, content_attributes para campos RichText/HTML-source, e um editor_note fixo (ver abaixo).
add-block write {post_id, block, attributes?, position?} Gera uniqueID (chave CSS por-bloco do Kadence), escafolda inner blocks de container conforme STRUCTURE, renderiza campos RichText directamente no HTML guardado (não no JSON de atributos — ver render_source_fields() abaixo).
list-patterns read {category?, search?, include_pro?, categories?:bool} Lista patterns da Kadence Design Library (cache local em uploads/kadence_blocks_library/). categories:true devolve só os nomes de categoria.
insert-pattern write {post_id, pattern, position?, localize_images?:bool} Insere markup canónico de um pattern (busca via Kadence_Blocks_Prebuilt_Library_REST_Controller::get_pattern_content — o próprio controller REST do Kadence, chamado internamente sem HTTP). localize_images:true transfere as imagens do pattern para a Media Library via o próprio process_pattern do Kadence (requer upload_files).

Nota de robustez notável em add-block: o output devolve sempre um note explícito — "Kadence blocks use a static JS save(), so a headlessly-inserted block renders correctly on the front end but the block editor may show 'Attempt recovery' on this block — one click regenerates valid markup and preserves the content." Isto é honestidade de design: blocos Kadence construídos à mão (não via pattern) não batem byte-a-byte com o save() estático do JS do bloco, então o editor Gutenberg vai reclamar (recuperável, sem perda de dados) — só o caminho insert-pattern (markup canónico do próprio Kadence) evita esse aviso.

render_source_fields() é o mecanismo mais complexo desta classe: para cada atributo que o registry marca como source: html|rich-text (ex. o content do Advanced Heading), renderiza o valor directamente em HTML dentro do bloco serializado usando o selector real registado por esse atributo (tag+classe), em vez de o guardar no JSON de attrs — porque é assim que WordPress lê esses valores de volta (parseados do HTML, não do JSON). Trata o caso especial "Advanced Heading" (selector_element()): a sua tag vem de htmlTag (h1-h6/p/div/span), e a classe CSS correcta é kt-adv-heading{uniqueID} — não o que o selector bruto do registry sugeriria.

14.1 EMCP_Tools_Kadence_Blocks_Catalog — includes/blocks-catalog/class-kadence-blocks-catalog.php

Serviço de suporte. Diferente do Spectra: ambas as metades (lista + atributos) vêm do WP_Block_Type_Registry core — Kadence regista schemas de atributos completos com defaults, não precisa de ler ficheiro fonte. TOP_LEVEL const lista os 32 slugs "placeable" (exclui blocos filho/estruturais como column-child, singlebtn, listitem, table-row, que são inseridos via STRUCTURE). TITLES/CATEGORY/HIGHLIGHT/STRUCTURE são curadoria manual (explicitamente "live-verified on Kadence Blocks 3.7.9" contra uma spec própria referenciada no comentário).

STRUCTURE['kadence/rowlayout'] tem uma gotcha documentada no comentário: colLayout tem de ser não-vazio ("equal") ou o editor mostra o picker "Select Your Layout" do Kadence em vez das colunas — "the frontend renders regardless" mas a experiência de edição fica quebrada sem isto.

14.2 EMCP_Tools_Kadence_Pattern_Library — includes/blocks-catalog/class-kadence-pattern-library.php

Serviço de suporte. Lê o cache local de metadados (uploads/kadence_blocks_library/*.json, distingue o ficheiro de metadata do de preview-HTML pela presença de slug+name nas entradas) e delega a obtenção do markup real ao controller REST do próprio Kadence (Kadence_Blocks_Prebuilt_Library_REST_Controller), invocado internamente (constrói um WP_REST_Request e chama o método directamente — sem round-trip HTTP). Nota explícita no cabeçalho do ficheiro: se a cache local estiver vazia, o utilizador precisa de abrir a Kadence Design Library uma vez no editor de blocos para a popular — a tool list-patterns devolve esse aviso quando detecta catálogo vazio.

15. EMCP_Tools_Child_Theme_Builder — includes/class-child-theme-builder.php

Serviço de suporte, usado por Active_Theme_Integration::execute_create_child_theme (§10). Deliberadamente conservador, três garantias explícitas no comentário de cabeçalho:

  1. Só cria um child do parent actualmente activo — nunca toca noutro tema.
  2. Recusa quando o tema activo já é um child (is_active_a_child()) — nunca cria um "neto" (grandchild).
  3. Idempotente — um child existente é apenas activado (switch_theme), nunca sobrescrito.

create(): cria a pasta (wp_mkdir_p), gera style.css mínimo (cabeçalho Theme Name/Template/ Version/Description) e functions.php mínimo (enfileira o style.css do parent via wp_enqueue_scripts), depois switch_theme($slug). Escreve via $wp_filesystem->put_contents() quando inicializado, senão file_put_contents() directo — o mesmo padrão dual usado pelas Filesystem tools (doc 07). Devolve {child, parent, directory, activated, created}.

É o enabler estrutural para edição de ficheiros de tema pelo agente: depois de criar o child, o agente edita style.css/functions.php/templates via as tools de Filesystem (ABSPATH-confined, gated por edit_files, com backup e audit-log — doc 07), nunca directamente aqui.

16. Pro-only confirmados ausentes: GeneratePress, GenerateBlocks, Blocksy

class-ability-registrar.php linhas 342-355 referencia 4 classes adicionais de integração de tema, seguindo o comentário explícito no código: "GeneratePress + GenerateBlocks (Pro; classes only present when Pro loaded)" e "Blocksy (Pro): blocks + Companion extensions."

if ( class_exists( 'EMCP_Tools_GeneratePress_Integration' ) ) { … }
if ( class_exists( 'EMCP_Tools_GenerateBlocks_Integration' ) ) { … }
if ( class_exists( 'EMCP_Tools_Blocksy_Blocks_Integration' ) ) { … }
if ( class_exists( 'EMCP_Tools_Blocksy_Extensions_Integration' ) ) { … }

Confirmado por listagem directa do directório includes/abilities/ neste build Free (19-08-2026, 48 ficheiros class-*.php listados): nenhum ficheiro class-generatepress-integration.php, class-generateblocks-integration.php, class-blocksy-blocks-integration.php ou class-blocksy-extensions-integration.php existe. As 4 classes são referenciadas apenas pelo class_exists() gate — em produção Free, este gate é sempre false; as 4 integrações só passam a existir (ficheiro + classe) quando o build Pro (emcp-pro/) está instalado e activo.

Cada uma seguiria, por analogia estrutural às 5 integrações Free já lidas (§10-14), o mesmo padrão herdado de EMCP_Tools_Theme_Integration (§9):

Classe (Pro-only, código não acessível) Gating no registrar Padrão inferido por analogia (não confirmado)
EMCP_Tools_GeneratePress_Integration class_exists() id()='generatepress' provavelmente; is_available() provavelmente 'generatepress' === get_template() — tema settings via GP Premium/GenerateBlocks options, seguindo o molde Astra/Kadence (§11/§13).
EMCP_Tools_GenerateBlocks_Integration class_exists() Pack de blocos GenerateBlocks (catálogo + add-block), seguindo o molde Spectra/Kadence Blocks (§12/§14) — seria o "blocks/builder" companion do GeneratePress.
EMCP_Tools_Blocksy_Blocks_Integration class_exists() Pack de blocos Blocksy (a Blocksy tem os seus próprios blocos Gutenberg nativos).
EMCP_Tools_Blocksy_Extensions_Integration class_exists() Gestão de "Extensions" do Blocksy (o painel de extensões modulares do tema — ex. Content Blocks, Custom Fonts, Ecommerce, etc.), provavelmente um dispatcher settings-like análogo ao Astra.

Não inventar schemas nem comportamento além desta inferência estrutural — sem acesso ao código fonte destas 4 classes, este é o limite do que se pode documentar com honestidade.

Blueprint para réplica

Copiar quase 1:1:

  • EMCP_Tools_Theme_Integration (abstract, §9) — o padrão de 2 dispatchers + discovery + permission-per-operation é genérico, pequeno (~250 linhas), e escala bem para qualquer novo framework/plugin de terceiros. É o único ponto de extensão que vale a pena manter exactamente como está numa réplica.
  • EMCP_Tools_Settings_Abilities (§3) e o padrão allowlist tipada — a decisão de nunca expor get_option/update_option cru é a protecção certa; a allowlist com {type, writable, min, max, pattern, options} é suficientemente expressiva sem ser um mini-JSON-Schema paralelo. Reutilizar esta forma para settings de qualquer plugin de terceiros (Astra/Kadence já o fazem).
  • Guarda de privilégio de User_Abilities (§6) — protected_caps() + user_has_admin_caps/ role_has_admin_caps é uma barreira de segurança simples e correcta (nenhuma tool de delete, role/password nunca lidos do input de update). Copiar tal e qual.
  • Package_Guard (referenciado por Plugin/Theme abilities, §4/§5) — lista de protecção (nunca desactivar/apagar/actualizar o próprio plugin nem o Elementor) é um padrão de auto-preservação que qualquer plugin MCP-serving precisa de replicar (senão o agente pode cortar o próprio ramo em que está sentado).
  • Child_Theme_Builder (§15) — as 3 garantias conservadoras (só o parent activo, nunca um grandchild, idempotente) são exactamente as invariantes certas para este tipo de operação.

Simplificar:

  • Kadence Blocks render_source_fields()/selector_element() (§14) — é código correcto mas bastante intrincado (resolve selectors CSS de volta para tag+classe HTML por heurística). Numa réplica que não precise de suportar Kadence Blocks especificamente, este é o tipo de complexidade a NÃO reinventar — só vale a pena se formos mesmo integrar Kadence Blocks.
  • Spectra_Catalog::real_attributes() a fazer require de um ficheiro fonte de outro plugin (§12.1) é frágil a mudanças de versão do Spectra (o caminho do ficheiro é hard-coded); preferir, quando possível, ler do WP_Block_Type_Registry (como Kadence faz) em vez de abrir ficheiros fonte de terceiros directamente — só recorrer a isto quando o plugin de terceiros não regista todos os atributos no registry core (razão pela qual o Spectra precisou desta técnica).

Deixar de fora (a menos que haja procura real de clientes):

  • As 4 integrações Pro-only (§16) — só valem a pena reimplementar quando houver um cliente concreto a usar GeneratePress/GenerateBlocks/Blocksy. Sem acesso ao código nem à base de utilizadores, seria trabalho especulativo.
  • Plugin_Abilities/Theme_Abilities install/update/delete completos (§4/§5) — install/delete arbitrário de plugins/temas via MCP é uma superfície de risco elevada (mesmo restrita a wordpress.org); para um clone interno de uso próprio da Descomplicar, ponderar reduzir a read-only (list/search) + activate/deactivate, deixando install/update/delete fora do MCP por preferir esse fluxo pelo wp-admin/WP-CLI directamente.

Riscos/gotchas não óbvios encontrados no código (citações):

  • Astra: cache de CSS dinâmico só invalida em customize_save_after, não numa escrita de option simples — sem a chamada explícita a astra_clear_all_assets_cache(), updates via MCP ficariam invisíveis até o próximo save do Customizer (§11).
  • Spectra: overlay de imagem de fundo em gradiente vem de gradientValue, não de gradientOverlayColor1/2 como seria intuitivo — documentado explicitamente no SHARED_ATTRS do próprio código (§12.1).
  • Kadence Blocks rowlayout: colLayout vazio faz o editor mostrar um picker de layout em vez das colunas — "the frontend renders regardless" mas a experiência de edição fica quebrada; fix é forçar colLayout: 'equal' como default quando o caller não o especifica (§14.1).
  • Kadence Blocks construídos à mão (não via pattern) disparam "Attempt recovery" no editor porque não batem byte-a-byte com o save() estático JS — o front-end renderiza correctamente na mesma; só insert-pattern (markup canónico do próprio Kadence) evita o aviso (§14).
  • upload-media/create-post (featured_image sideload): as funções de sideload (media_handle_sideload e dependências) vivem em wp-admin/includes/* que não está carregado em pedidos REST/WP-CLI — têm de ser require_once'd on-demand a cada chamada (§1, §2).
  • create-user: erros de conta existente são normalizados para uma mensagem genérica precisamente para impedir que a tool sirva de oráculo de enumeração de contas (§6).

Fonte

Leitura directa (19-08-2026) de: includes/abilities/class-content-abilities.php, includes/abilities/class-media-library-abilities.php, includes/abilities/class-settings-abilities.php, includes/abilities/class-plugin-abilities.php, includes/abilities/class-theme-abilities.php, includes/abilities/class-user-abilities.php, includes/abilities/class-nav-menu-abilities.php, includes/abilities/class-image-resize-abilities.php, includes/abilities/class-theme-integration.php, includes/abilities/class-active-theme-integration.php, includes/abilities/class-astra-integration.php, includes/abilities/class-spectra-integration.php, includes/abilities/class-kadence-integration.php, includes/abilities/class-kadence-blocks-integration.php, includes/blocks-catalog/class-kadence-blocks-catalog.php, includes/blocks-catalog/class-kadence-pattern-library.php, includes/blocks-catalog/class-spectra-catalog.php, includes/class-child-theme-builder.php; grep de includes/abilities/class-ability-registrar.php (linhas 135-165, 220-250, 320-380) para as condições de registo exactas e a confirmação da lista de 4 classes Pro-only referenciadas mas não implementadas; listagem directa de includes/abilities/ (48 ficheiros) para confirmar a ausência física dos ficheiros GeneratePress/GenerateBlocks/Blocksy neste build Free. Cruzado com docs/00-ARQUITECTURA.md (mesma sessão) para o contrato emcp_tools_register_ability().