Files
emcp-tools-mapping/docs/10-MODULOS-E-INVENTARIO-PRO.md
T
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

10 — Sistema de módulos (Modules tab) e inventário Pro-only (metadata)

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. Cruzado com docs/00-ARQUITECTURA.md (mesma batch) e skill://emcp-tools.

Este documento tem duas partes independentes: Parte 1 documenta o sistema de "módulos" — a camada de toggles on/off do Modules tab, que é ortogonal ao sistema de abilities/MCP tools (uma ability pode existir sempre, só um módulo; um módulo pode não ter nenhuma ability associada; ver Themer/Redirect para o padrão onde módulo E abilities coexistem com gating cruzado). Parte 2 é o inventário definitivo — para cada classe referenciada com class_exists() em class-ability-registrar.php::register_groups() que não seja confirmada Free por outro documento desta batch, confirma-se aqui, por leitura directa (listagem de directório + tentativa de leitura de ficheiro), se existe ou não na árvore Free instalada.


PARTE 1 — Sistema de módulos

1.1 Classe base — EMCP_Tools_Module

Ficheiro: includes/modules/class-module.php (~100 linhas). Classe abstracta; contrato mínimo que todo módulo tem de implementar:

Método Abstracto? Contrato
id(): string sim id estável (a-z0-9-) — usado como valor no array emcp_tools_active_modules e como infixo das option keys próprias do módulo
title(): string sim título humano para o card no Modules tab
description(): string sim descrição de uma linha para o card
tier(): string sim 'free' | 'pro' — controla o badge de tier na UI
default_active(): bool sim se o módulo arranca activo por omissão (seeded uma única vez, ver §1.2)
register(): void sim liga os hooks do módulo. Só chamado pela registry quando activo e disponível
render_settings(): void sim desenha os knobs do módulo dentro do seu card (mostrado quando activo)
is_available(): bool não (default true) sonda de dependência/capacidade — override para gate em features do servidor (ex.: suporte WebP) ou em licença Pro
settings_fields(): array não (default []) mapa option_key => ['type','default','sanitize_callback'] para registo sanitizado no grupo de settings do módulo
is_active(): bool concreto in_array($this->id(), get_option('emcp_tools_active_modules', []), true)
settings_group(): string concreto 'emcp_tools_module_' . str_replace('-','_',$this->id()) . '_settings' — grupo Settings API próprio, para o form do módulo gravar independentemente dos toggles de outros módulos
has_settings(): bool concreto [] !== $this->settings_fields()
settings_url(): string não (default '') quando definido, o card mostra um link "Configure →" para uma página admin dedicada, em vez de um overlay inline

Constante: OPTION_ACTIVE = 'emcp_tools_active_modules' — a ÚNICA option que guarda quais módulos estão activos (array de ids).

1.2 Registry — EMCP_Tools_Modules_Registry

Ficheiro: includes/modules/class-modules-registry.php (~115 linhas). Singleton (instance() / reset_for_tests() para testes).

  • register(EMCP_Tools_Module $module) — idempotente por id ($this->modules[$module->id()] = $module).
  • all() / get($id) / active() — leitura simples; active() filtra pelos que têm is_active()===true.
  • apply_defaults() — mecanismo de seeding. Option emcp_tools_modules_seeded (const OPTION_SEEDED) guarda uma lista de ids já considerados para seeding (não um booleano único). Para cada módulo registado ainda não na lista seeded: marca-o como seeded e, se default_active()===true e ainda não estiver em active, adiciona-o a active. Grava as duas options só se algo mudou. Porquê lista por-módulo e não um marcador booleano: um módulo novo introduzido numa versão posterior do plugin é seeded no load seguinte sem re-seedar — ou remover — o que o utilizador já tinha alterado manualmente nos módulos existentes. É o desenho correcto para migração aditiva sem tocar em estado do utilizador.
  • boot_active() — chamado em init (presume-se prioridade 5, confirmado pelos comentários nos módulos Redirect/Themer/Agent-Skills que dizem "abilities register on wp_abilities_api_init, antes do módulo arrancar em init:5"). Para cada módulo em active(): se is_available()===true, chama register().

Consequência de desenho importante: os grupos de abilities MCP de um módulo (quando existem) não podem depender de register() ter corrido, porque wp_abilities_api_init dispara ANTES de init:5. Por isso todo módulo com abilities associadas expõe um helper estático is_enabled() (lê get_option(OPTION_ACTIVE) directamente, sem depender da instância da registry) que o class-ability-registrar.php chama em vez de $module->is_active(). Ver Redirect, Cloud, Themer, Agent-Skills, Image-Optimization (module_is_active()), Memory, Migrate — todos seguem este padrão.

1.3 Os 7 módulos "tab-only" / leves

Todos vivem em includes/modules/class-{id}-module.php. Cinco são efectivamente free (Prompts, Brand Kits, Redirect, Cloud, Themer); dois têm o ficheiro de metadata presente na árvore Free mas tier()==='pro' (Templates, Agent Skills) — um padrão deliberado do autor, explicitado no comentário do código de Agent Skills: "This class carries no Pro logic and lives safely in the free tree, like EMCP_Tools_Templates_Module."

Prompts (id: 'prompts')

Campo Valor
tier() free
default_active() true
is_available() não sobreposto (sempre true)
settings_url() admin.php?page={PAGE_SLUG}-prompts
register() no-op

Feature "tab-only": o módulo só controla se a tab admin Prompts (e o respectivo stat card) aparece; quem lê is_active() é a classe de admin para mostrar/esconder a tab. O conteúdo free/Pro DENTRO da tab (amostras bundled vs biblioteca premium) é inalterado por este toggle — é outra camada de gating, não deste módulo.

Brand Kits (id: 'brand-kits')

Idêntico em estrutura ao Prompts: tier()=free, default_active()=true, register() no-op, settings_url() aponta para -brand-kits. Controla apenas a visibilidade da tab; os 10 kits bundled free (EMCP_Tools_Free_Brand_Kits, §1.6) vs 50+ kits Pro por licença são geridos por outro mecanismo, não por este toggle.

Templates (id: 'templates') — tier Pro, ficheiro em Free

Campo Valor
tier() pro
default_active() true
is_available() sobreposto: function_exists('emcp_tools_fs') && emcp_tools_fs()->can_use_premium_code()
settings_url() admin.php?page={PAGE_SLUG}-templates
register() no-op

Comentário do autor no ficheiro: "Pro tier — free users see a locked card in Modules and the standalone tab is hidden. [...] This class is metadata only (no Pro logic), so it lives safely in the free tree." Ou seja: o ficheiro .php desta classe existe fisicamente no build Free (por isso não aparece na Parte 2 como "ausente"), mas is_available() bloqueia sempre que não há licença activa — o resultado prático é indistinguível de um módulo Pro-only, só a UI "locked card" difere.

Redirect Manager (id: 'redirects', const ID)

Campo Valor
tier() free
default_active() true
settings_url() admin.php?page={PAGE_SLUG}-redirects
is_enabled(): bool (estático) lê OPTION_ACTIVE directamente — usado pelo registrar
register() EMCP_Tools_Redirect_Store::init() (instala tabela em init:20) + EMCP_Tools_Redirect_Handler::init() (handler 301/302 no front-end), ambos condicionais a class_exists()

Comentário do autor: desactivar este módulo é um verdadeiro kill switch — o handler de redirect pára, as MCP tools caem (gated no registrar via is_enabled()), a tab admin esconde-se, e delete/rename deixa de emitir sugestões de redirect. Ver Doc05 para Redirect_Abilities/ Redirect_Store/Redirect_Handler em detalhe.

EMCP Cloud (id: 'cloud')

Campo Valor
tier() free
default_active() true
settings_url() admin.php?page=emcp-tools-connection#emcp-conn-main
is_enabled(): bool (estático) idem padrão Redirect
register() EMCP_Tools_Cloud_Connect::init() (arranca o fluxo admin do cliente OAuth)

O grupo EMCP_Tools_Cloud_Abilities (ficheiro includes/abilities/class-cloud-abilities.php, confirmado presente na árvore Free) só regista quando ambas as condições são verdadeiras: EMCP_Tools_Cloud_Module::is_enabled() e EMCP_Tools_Cloud::is_connected() — isto é, o gate não é licença, é estado de ligação (o site tem de estar ligado a uma conta EMCP Cloud). Toda a infra-estrutura de suporte (includes/cloud/: class-cloud.php, class-cloud-client.php, class-cloud-connect.php, class-cloud-http.php, class-cloud-sync.php, class-gateway-credential.php, class-settings-sync.php) está presente e é free. Ver Doc09.

Themer (id: 'themer')

Campo Valor
tier() free
default_active() true
settings_url() edit.php?post_type={Themer_CPT::POST_TYPE}
is_enabled(): bool (estático) idem padrão Redirect/Cloud
register() ver abaixo — o mais rico dos 7 módulos leves

register() (chamado só em init:5, activo+disponível):

  1. (new EMCP_Tools_Themer_CPT())->register() — regista o CPT.
  2. Se class_exists('EMCP_Tools_Themer_HFE_Conflict'): ::init() — aviso de conflito com o Header Footer Elementor (que constrói os mesmos slots header/footer); até o admin escolher um sistema, Themer ganha deterministicamente.
  3. EMCP_Tools_Themer_Index::register_hooks() — hooks de rebuild do índice de condições.
  4. Heal one-time: se get_option('emcp_tools_themer_index_healed') !== '1', chama EMCP_Tools_Themer_Index::rebuild() e grava o marcador. Comentário do autor, citado: "a prior build could leave the condition index empty (the rebuild raced the metabox meta writes), so existing templates silently stopped applying." É a reparação de uma race condition real de dados já corrompidos em sites existentes — o marcador de option garante que corre exactamente uma vez por upgrade, sem o admin ter de voltar a gravar cada template manualmente.
  5. Se ! is_admin(): (new EMCP_Tools_Themer_Render_Controller())->init() (front-end).
  6. Se is_admin() && class_exists('EMCP_Tools_Themer_Metabox'): (new EMCP_Tools_Themer_Metabox())->init().
  7. Se class_exists('EMCP_Tools_Themer_Blocks'): ::init() — blocos Gutenberg dinâmicos.
  8. Se class_exists('EMCP_Tools_Themer_Widgets'): ::init() — widgets clássicos dinâmicos.
  9. Se class_exists('EMCP_Tools_Themer_PHP'): (new EMCP_Tools_Themer_PHP())->init() — feature de templates PHP puro (confirmado presente: includes/themer/php/class-themer-php.php). Nota: o grupo de abilities EMCP_Tools_Themer_PHP_Abilities (também confirmado presente) tem o SEU PRÓPRIO toggle independente (EMCP_Tools_Themer_PHP::enabled()), separado do toggle base do Themer — ver Doc04.

Comentário do autor: desactivar o módulo pára o CPT, a tomada de controlo do front-end e a tab; o registrar omite as tools também — kill switch total. Ver Doc04 para detalhe completo do subsistema includes/themer/.

Agent Skills (id: 'agent-skills') — tier Pro, ficheiro em Free

Campo Valor
tier() pro
default_active() true
is_available() sobreposto: function_exists('emcp_tools_fs') && emcp_tools_fs()->can_use_premium_code()
settings_url() admin.php?page={PAGE_SLUG}-skills
register() no-op
is_enabled(): bool (estático) idem padrão Redirect/Cloud/Themer, usado por DOIS consumidores

Controla a exposição em runtime das skills bundled a agentes de IA ligados: as tools MCP list-skills / get-skill e o catálogo ## Skills injectado no contexto de discovery. Desligar remove ambos (e o footprint de ~900 tokens da injecção) sem tocar no caminho de instalação-local na tab Skills (i.e. skills já descarregadas para disco continuam lá, só deixam de ser expostas a agentes MCP).

Dois consumidores estáticos de is_enabled():

  1. class-ability-registrar.php — gate de EMCP_Tools_Skill_Abilities (confirmado ausente da árvore Free, ver Parte 2).
  2. EMCP_Tools_Skill_Catalog::discovery_catalog() — gate da injecção do catálogo no contexto MCP.

Nuance importante: o ficheiro desta classe existe no build Free instalado (contraste com Memory/Migrate, cujos módulos nem sequer têm ficheiro na árvore Free — ver Parte 2 §2.2). É por isso que EMCP_Tools_Agent_Skills_Module NÃO aparece na tabela final de "classes ausentes" — mas funcionalmente comporta-se como Pro, porque is_available() exige licença e a única ability que depende dela (Skill_Abilities) está de qualquer forma ausente do ficheiro-sistema Free.

1.4 Image Optimization — o módulo opt-in mais substancial (deep dive)

Ficheiros em includes/modules/image-optimization/: class-image-optimization-module.php, class-image-optimizer.php, class-image-resizer.php, class-webp-generator.php, class-webp-rewriter.php, class-bulk-optimizer.php, settings-fields.php.

EMCP_Tools_Image_Optimization_Module (id 'image-optimization', const ID; option prefix PREFIX = 'emcp_tools_module_image_optimization_')

Campo Valor
tier() free
default_active() false — único dos 9 módulos (contando os 2 de deep-dive) que é opt-in por omissão, além do SVG Support
is_available() sobreposto: (new EMCP_Tools_Webp_Generator(82))->is_available() — delega para wp_image_editor_supports(['mime_type'=>'image/webp']); se o editor de imagem do servidor (GD/Imagick) não sabe exportar WebP, o módulo não fica disponível de todo
module_is_active(): bool (estático) usado pelo registrar para condicionar a ability resize-media

settings_fields() — 6 option keys, todas sob PREFIX:

Key Tipo Default
compress bool ('1'/'0') 1
webp bool 1
webp_serve bool 1
quality int 60
max_dimension int 0 (0 = sem cap)
keep_originals bool 1

current_settings() resolve estas options num array tipado (quality passa por EMCP_Tools_Image_Optimizer::clamp_quality() — clamp 1-100 partilhado, single source of truth).

register() (chamado só activo+disponível):

  • Se compress OU webp: instancia EMCP_Tools_Image_Optimizer($settings), hook em wp_generate_attachment_metadata (prioridade 20) → on_generate_metadata().
  • Se webp: (new EMCP_Tools_Webp_Rewriter($settings['webp_serve']))->register().
  • Se is_admin(): (new EMCP_Tools_Bulk_Optimizer($settings))->register().

render_settings() faz include de settings-fields.php com $settings em scope (view partial pura: switches para compress/webp/webp_serve, slider 1-100 para quality, number input para max_dimension, switch para keep_originals; usa um closure local $emcp_io_toggle para DRY do HTML).

EMCP_Tools_Image_Optimizer — pipeline compress-on-upload

  • Const META_KEY = '_emcp_optim' — meta do post attachment onde fica o resultado.
  • Construtor normaliza settings (bool/bool/int-clamp/int/bool).
  • clamp_quality(int): int (estático) — clamp 1-100, partilhado com Image_Resizer.
  • sizes_to_process(metadata, basedir): string[] — caminhos absolutos do full-size + todos os sub-sizes gerados.
  • backup_path(file, upload): string (estático) — espelha o caminho relativo às uploads sob uploads/emcp-originals/.
  • should_skip(optim): bool — true se optim['status']==='done' (idempotência).
  • on_generate_metadata($metadata, $attachment_id) — o hook callback:
    1. guarda: $metadata tem de ser array; settings tem de ter compress OU webp;
    2. apply_filters('emcp_tools_optimize_attachment', true, $attachment_id) — opt-out per-attachment. Usado por sideload-image/add-stock-image quando o chamador passa convert_webp:false (conversão a dar timeout em shared hosting) — é o ÚNICO ponto de extensibilidade externo deste pipeline;
    3. mime tem de ser image/jpeg ou image/png (WebP e GIF não entram no pipeline de compressão — só os dois formatos de origem processados);
    4. se já status=done no meta existente, salta;
    5. resolve wp_upload_dir(), calcula sizes_to_process(), chama process_files(), grava o resultado em _emcp_optim.
  • process_files($files, $upload, $full_rel, $basedir): array — o loop real por ficheiro:
    • mede tamanho "antes"; se keep_originals, faz backup (skip se já existir — idempotente);
    • wp_get_image_editor(); set_quality(); só o full-size ($file === $full_abs) recebe o cap max_dimension via resize($max,$max,false) (scale-to-fit, nunca crop) — os sub-sizes NUNCA são redimensionados por este cap;
    • se compress: editor->save($file) (re-encode IN PLACE, mesmo caminho);
    • mede tamanho "depois";
    • se webp_ok: generator->generate($file) → sibling;
    • devolve agregado: status=done, original_bytes, optimized_bytes, webp_bytes, backups[], webps[].

EMCP_Tools_Webp_Generator

  • sibling_path(file): string (estático) — "$file.webp" (ex.: name-800x600.jpg.webp — a extensão original é preservada, .webp é anexado como extensão composta, para o rewriter encontrar deterministicamente sem precisar de índice).
  • is_available() — wp_image_editor_supports(['mime_type'=>'image/webp']).
  • generate(file) — skip se sibling já existe (idempotente); wp_get_image_editor(); set_quality(); save($sibling, 'image/webp').

EMCP_Tools_Webp_Rewriter

  • Construtor: serve_frontend=true por omissão; captura basedir/baseurl de wp_upload_dir().
  • register() — hooks wp_get_attachment_url, wp_get_attachment_image_src, wp_calculate_image_srcset (todos prioridade 20).
  • should_rewrite($accept, $is_rest, $serve_frontend): bool (estático) — REST/CLI/cron qualifica-se SEMPRE (as MCP media tools resolvem sempre para WebP, independentemente do toggle frontend); frontend requer adicionalmente o toggle serve_frontend e header Accept: image/webp.
  • webp_url(url): string (estático) — regex troca .jpg/.jpeg/.png (antes de qualquer query string) pelo sibling .webp; outras extensões passam inalteradas.
  • is_rest_context() — REST_REQUEST const OU WP_CLI const OU wp_doing_cron().
  • url_to_path(url) — mapeia URL de uploads de volta a caminho absoluto, só dentro do baseurl (scoping de segurança).
  • maybe_rewrite(url) — decisão final por URL: só reescreve se allowed() e a URL webp difere e o ficheiro .webp existe mesmo em disco (nunca devolve uma URL para um ficheiro inexistente).

EMCP_Tools_Bulk_Optimizer — processador resumível da biblioteca existente

  • Consts: ACTION_BATCH='emcp_tools_optimize_batch', ACTION_RESTORE='emcp_tools_optimize_restore', NONCE='emcp_tools_modules', OPTION_CURSOR='emcp_tools_module_image_optimization_bulk_cursor'.
  • register() — 2 handlers wp_ajax_*.
  • batch_size(n): int (estático) — clamp 1-50, default 10 se <=0.
  • progress(total, processed): array (estático, pura) — {total,processed,remaining,percent,done}.
  • ajax_batch() — nonce + manage_options; query de TODOS os attachment IDs jpeg/png ordenados por ID ASC; lê cursor de OPTION_CURSOR; fatia o batch a partir do cursor; para cada ID: metadata → sizes_to_process() → process_files() → grava _emcp_optim; avança o cursor; reset do cursor a 0 quando progress.done; devolve JSON de progresso.
  • ajax_restore() — nonce + manage_options; percorre uploads/emcp-originals/ recursivamente (RecursiveIteratorIterator); para cada backup: copia de volta sobre o caminho vivo, apaga o sibling .webp do destino, incrementa restored; reset do cursor; devolve {restored}.

1.5 SVG Support — o módulo com maior superfície de segurança (deep dive)

Ficheiros: includes/modules/svg-support/class-svg-support-module.php, includes/modules/svg-support/class-svg-sanitizer.php.

EMCP_Tools_SVG_Support_Module (id 'svg-support', const ID; prefix PREFIX = 'emcp_tools_module_svg_support_')

Campo Valor
tier() free
default_active() false — opt-in, comentário explícito "a security surface"
is_available() sobreposto: EMCP_Tools_SVG_Sanitizer::library_available() — fail-closed se a biblioteca de sanitização não estiver disponível

WordPress bloqueia uploads SVG por omissão (SVG é XML e pode transportar scripts). Elementor já permite SVG para utilizadores autorizados via o seu próprio handling de unfiltered-upload — este módulo é dirigido a sites sem Elementor (ou onde o mime svg não está registado por outra via).

svg_already_supported(): bool (estático) — verifica get_allowed_mime_types() por 'svg' ou 'svg|svgz' — se já suportado por outro plugin/tema, mostra uma nota informativa em render_settings() (o módulo continua a sanitizar de qualquer forma quando activo).

settings_fields() — UMA option: admin_only (bool, default '0').

required_capability(): string — resolve para manage_options se admin_only estiver ligado, senão upload_files; filtrável via emcp_tools_svg_upload_capability.

register() liga:

  1. upload_mimes → allow_svg_mime() — adiciona 'svg'=>'image/svg+xml' só se current_user_can(required_capability()).
  2. wp_check_filetype_and_ext (prioridade 10, 4 args) → fix_svg_filetype() — "the piece most SVG plugins miss" (comentário do autor, citado): a sniff real-content de mime type do WordPress (via finfo) frequentemente reporta text/plain ou image/svg para SVGs e rejeita o upload; esta correcção resolve explicitamente ext/type para ficheiros .svg quando o utilizador tem a capability — é o que faz uploads REST/sideload funcionarem, não só o media-new.php clássico.
  3. wp_handle_upload_prefilter + wp_handle_sideload_prefilter → ambos sanitize_upload().
  4. admin_head → media_thumbnail_css(), só se is_admin().

sanitize_upload($file) — o gate real: se a extensão não for svg, passa; se o utilizador não tem a capability, rejeita com mensagem de erro; senão corre (new EMCP_Tools_SVG_Sanitizer()) ->sanitize_file($tmp_name) — se falhar, rejeita fail-closed ("could not be sanitized and was rejected for security").

media_thumbnail_css() — injecção CSS mínima para as thumbnails SVG renderizarem corretamente dimensionadas na grelha/lista da Media Library.

EMCP_Tools_SVG_Sanitizer — wrapper fino sobre enshrined/svg-sanitize (vendorizada)

Mesma biblioteca que o plugin "Safe SVG" usa. SVG é XML — pode transportar script, event handlers, referências externas e payloads XXE; permitir upload SVG cru sem sanitizar é um vector de stored-XSS.

library_available(): bool — 3 estratégias de carregamento em cascata:

  1. classe já carregada (class_exists('\enshrined\svgSanitize\Sanitizer'));
  2. EMCP_Tools_Adapter_Bootstrap::ensure() (o mesmo mecanismo de preload do Jetpack Autoloader usado para o MCP adapter vendorizado, ver 00-ARQUITECTURA.md §2.1) ou fallback directo a vendor/autoload_packages.php;
  3. fallback próprio: register_fallback_autoloader() — regista um autoloader PSR-4 escopado directamente contra vendor/enshrined/svg-sanitize/src/, para a sanitização SVG continuar a funcionar "even if the Jetpack classmap wasn't regenerated" (comentário do autor) — redundância defensiva deliberada contra um modo de falha real de geração de classmap Composer/Jetpack.

sanitize(string $svg): string|false:

  • instancia \enshrined\svgSanitize\Sanitizer;
  • minify(false);
  • removeRemoteReferences(true) — endurecimento SSRF/XSS explícito para lá da configuração por omissão da biblioteca (remove xlink:href remoto, use@href remoto);
  • false em vazio/falha.

sanitize_file(path): bool — ler → sanitizar → sobrescrever in-place; false se ilegível ou sanitização falhar.

1.6 EMCP_Tools_Free_Brand_Kits — serviço de suporte (não é módulo)

Ficheiro: includes/class-free-brand-kits.php (nível topo de includes/, não em modules/).

Contraparte free do serviço EMCP_Tools_Pro_Brand_Kits. Onde o Pro busca 50+ kits de emcptools.com atrás de licença, este lê um conjunto pequeno e curado embutido no plugin (assets/brand-kits/free-brand-kits.json) — disponível a todos, sem licença, o mesmo modelo dos 5 prompts de amostra bundled.

  • get_bundle(): array — parse cacheado em memória (self::$bundle) do JSON; forma: ['categories' => [['slug','label','kits' => [{kit}, ...]]]]. Para cada kit, se existir assets/brand-kits/{slug}.svg (previews pré-renderizadas, fontes outlined), injecta thumbnail_url + preview.thumbnail_url com a URL do plugin (o JSON não pode saber a URL do plugin). Nunca devolve WP_Error — os dados estão bundled, por isso estão sempre disponíveis (devolve bundle vazio se o ficheiro faltar).
  • find_kit(kit_slug, category_slug='') — procura linear.
  • count_kits(): int — total, para a barra de stats do admin.

Facto crucial para o blueprint: esta classe só PROVIDENCIA os dados do kit. A aplicação (escrever cores/tipografia no Elementor kit activo) passa sempre pelo EMCP_Tools_System_Kit_Writer PARTILHADO (confirmado presente em includes/class-system-kit-writer.php) e pelo EMCP_Tools_Kit_Backup_Store (backups reversíveis, confirmado presente em includes/class-kit-backup-store.php) — exactamente o mesmo caminho usado pelo Pro. Ou seja: a funcionalidade de "aplicar um kit" via UI admin funciona sem licença nenhuma (kits free + writer partilhado); é só a ferramenta MCP para o fazer programaticamente (EMCP_Tools_System_Kit_Abilities) que está totalmente ausente do build Free (ver Parte 2).


PARTE 2 — Inventário Pro definitivo

2.1 Metodologia

  1. Leitura integral de includes/abilities/class-ability-registrar.php::register_groups() (627 linhas) — extracção de TODAS as chamadas class_exists('EMCP_Tools_...') que condicionam o registo de um grupo de abilities ou de um pack de integração.
  2. Cruzamento com listagens de directório completas e literais (equivalente a ls) de:
    • includes/abilities/ (47 ficheiros .php + subpastas forms/, seo/);
    • includes/abilities/forms/ (2 ficheiros: class-cf7-integration.php, class-form-integration.php — só a base + CF7);
    • includes/abilities/seo/ (2 ficheiros: class-seo-integration.php, class-slimseo-integration.php — só a base + SlimSEO);
    • includes/modules/ (7 ficheiros de módulo + class-module.php + class-modules-registry.php
      • subpastas image-optimization/, svg-support/ — sem class-memory-module.php nem class-migrate-module.php).
  3. Para cada classe candidata a Pro-only (i.e. referenciada no registrar mas ausente das listagens acima), tentativa directa de leitura do ficheiro esperado (equivalente a test -f) — 20 tentativas, cada uma resultando em head: impossível abrir '...' para leitura: No such file or directory (SSH remoto, head a falhar por ausência do ficheiro). Nenhuma presunção — cada linha da tabela abaixo tem confirmação de ausência por, no mínimo, listagem de directório completa, e a maioria tem confirmação dupla (listagem + tentativa de leitura directa).

Classes confirmadas presentes (portanto free, excluídas desta tabela por já estarem cobertas noutros documentos da batch): EMCP_Tools_Image_Resize_Abilities, EMCP_Tools_ACF_Abilities, EMCP_Tools_Meta_Box_Abilities, EMCP_Tools_CF7_Integration, EMCP_Tools_SlimSEO_Integration, EMCP_Tools_Active_Theme_Integration, EMCP_Tools_Astra_Integration, EMCP_Tools_Spectra_Integration, EMCP_Tools_Kadence_Integration, EMCP_Tools_Kadence_Blocks_Integration, EMCP_Tools_PHP_Snippet_Abilities, EMCP_Tools_Sandbox_Cloud_Abilities, EMCP_Tools_Cloud_Abilities, EMCP_Tools_Cloud_Module, EMCP_Tools_Global_Classes_Abilities, EMCP_Tools_Global_Classes_Write_Abilities, EMCP_Tools_Themer_Abilities, EMCP_Tools_Themer_Module, EMCP_Tools_Themer_PHP_Abilities, EMCP_Tools_Themer_PHP, EMCP_Tools_Redirect_Module, EMCP_Tools_Redirect_Abilities, EMCP_Tools_Image_Optimization_Module, EMCP_Tools_Agent_Skills_Module (presente mas tier=pro, ver §1.3).

2.2 Tabela final — Classes referenciadas no registrar mas ausentes do build Free (Pro-only)

30 classes, agrupadas por família funcional. Coluna "Condição de gating" reproduz literalmente a lógica de register_groups() (nomes de variáveis simplificados por clareza).

Classe Grupo funcional Condição de gating (registrar) Módulo Pro associado
EMCP_Tools_Woo_Integration Integração WooCommerce (CRUD produtos/encomendas/etc.) class_exists(...) && EMCP_Tools_Woo_Integration::woo_active() — (gate próprio: plugin WooCommerce activo; sem module toggle dedicado)
EMCP_Tools_WPForms_Integration Integração de formulários — leitura de entries (Pro) emcp_tools_fs()->can_use_premium_code() && class_exists(...), depois $integration->is_available() — (tab "Forms"; CF7 é a única integração free)
EMCP_Tools_GravityForms_Integration idem idem —
EMCP_Tools_FluentForms_Integration idem idem —
EMCP_Tools_NinjaForms_Integration idem idem —
EMCP_Tools_Formidable_Integration idem idem —
EMCP_Tools_MetForm_Integration idem idem —
EMCP_Tools_SureForms_Integration idem idem —
EMCP_Tools_Forminator_Integration idem idem —
EMCP_Tools_Yoast_Integration Integração SEO — leitura/escrita meta SEO (Pro) emcp_tools_fs()->can_use_premium_code() && class_exists(...), depois $integration->is_available() — (tab "SEO"; SlimSEO é a única integração free)
EMCP_Tools_RankMath_Integration idem idem —
EMCP_Tools_AIOSEO_Integration idem idem —
EMCP_Tools_SeoPress_Integration idem idem —
EMCP_Tools_SEOFramework_Integration idem idem —
EMCP_Tools_SureRank_Integration idem idem —
EMCP_Tools_GeneratePress_Integration Integração de tema/framework (Pro) class_exists(...) — comentário do código: "classes only present when Pro loaded" — depois $integration->is_available() —
EMCP_Tools_GenerateBlocks_Integration idem idem —
EMCP_Tools_Blocksy_Blocks_Integration Integração Blocksy — blocos class_exists(...), depois is_available() —
EMCP_Tools_Blocksy_Extensions_Integration Integração Blocksy — Companion extensions class_exists(...), depois is_available() —
EMCP_Tools_EssentialAddons_Integration Pack de widgets Elementor de terceiros — Essential Addons (Pro) class_exists(...), depois is_available() ("contributes ONE read tool for discovery + curation") —
EMCP_Tools_PremiumAddons_Integration Pack de widgets Elementor — Premium Addons (Pro) idem —
EMCP_Tools_UAE_Integration Ultimate Addons for Elementor (ex-Header Footer Elementor) — widget pack + data plugin class_exists(...), depois is_available() — único pack que mantém o par read/write dispatcher (discovery+templates na leitura, templates na escrita) —
EMCP_Tools_Block_Builder_Abilities Construtor de blocos Gutenberg via MCP (Pro) class_exists(...) — comentário: "self-guards on license" — Gutenberg, NÃO gated por Elementor activo —
EMCP_Tools_System_Kit_Abilities Brand Kit / System Kit — ferramenta MCP (Pro) class_exists(...) dentro do bloco if ($elementor_active) — "self-guards on license" Módulos "Brand Kits" (free) e "Templates" (Pro) são metadata-only; a ferramenta MCP para aplicar kits é sempre Pro, mesmo com os 10 kits free disponíveis via UI (§1.6)
EMCP_Tools_Seo_Abilities Toolkit SEO on-page — ferramenta MCP (Pro) idem, dentro de $elementor_active —
EMCP_Tools_A11y_Abilities Toolkit de acessibilidade — ferramenta MCP (Pro) idem —
EMCP_Tools_Widget_Builder_Abilities Construtor de widgets Elementor custom — ferramenta MCP (Pro) idem —
EMCP_Tools_Skill_Abilities Ferramentas MCP list-skills/get-skill (Pro) class_exists('EMCP_Tools_Skill_Abilities') && class_exists('EMCP_Tools_Agent_Skills_Module') && EMCP_Tools_Agent_Skills_Module::is_enabled() Agent Skills — módulo PRESENTE na árvore Free (tier()='pro', ver §1.3); a ability em si está sempre ausente independentemente do toggle
EMCP_Tools_Memory_Abilities Project Memory — ferramenta MCP (Pro) class_exists('EMCP_Tools_Memory_Abilities') && class_exists('EMCP_Tools_Memory_Module') && EMCP_Tools_Memory_Module::is_enabled() Memory — módulo TAMBÉM ausente da árvore Free (ver linha seguinte)
EMCP_Tools_Memory_Module Módulo "Memory" (toggle no Modules tab) class_exists('EMCP_Tools_Memory_Module'), usado em conjunto com Memory_Abilities acima Pro — ao contrário de Agent Skills/Templates, nem o ficheiro de metadata do módulo está na árvore Free; não há card "Memory" a mostrar-se bloqueado na Modules tab de um site Free
EMCP_Tools_Migrate_Abilities Backup / Migrate / Sync — ferramenta MCP (Pro) class_exists('EMCP_Tools_Migrate_Abilities') && class_exists('EMCP_Tools_Migrate_Module') && EMCP_Tools_Migrate_Module::is_enabled(). As duas tools destrutivas deste grupo vêm desactivadas por omissão mesmo quando disponíveis. Migrate — módulo TAMBÉM ausente (linha seguinte)
EMCP_Tools_Migrate_Module Módulo "Migrate" (toggle no Modules tab) idem padrão de Memory_Module Pro — mesmo padrão: nem o ficheiro de metadata existe na árvore Free

Nota sobre Memory_Module/Migrate_Module vs Templates_Module/Agent_Skills_Module: o código tem DOIS padrões distintos para features Pro sem equivalente free algum:

  • Padrão "card bloqueado" (Templates, Agent Skills): o ficheiro .php do módulo VIVE na árvore Free, tier()==='pro', is_available() exige licença — o utilizador Free VÊ o card na Modules tab, mas bloqueado/locked, como incentivo de upsell visível.
  • Padrão "invisível" (Memory, Migrate): nem o ficheiro do módulo existe no build Free — não há card nenhum a mostrar-se, a feature é completamente invisível a um utilizador Free até subir para o build Pro. Só as próprias abilities (também ausentes) e o módulo referenciam estas classes; sem card na Modules tab não há sequer superfície de descoberta da feature no admin.

Blueprint para réplica

Sistema de módulos (class-module.php + class-modules-registry.php)

Copiar quase verbatim — é uma pequena máquina de estado limpa (~215 linhas as duas classes juntas), sem lógica Pro nenhuma. O único detalhe de desenho que vale a pena preservar deliberadamente é a lista seeded por-módulo (em vez de um marcador booleano único) em apply_defaults() — é o que permite adicionar um módulo novo numa versão futura sem re-seedar nem tocar no estado que o utilizador já ajustou nos módulos existentes. E o padrão do helper estático is_enabled() em todo módulo com abilities associadas — necessário porque wp_abilities_api_init dispara antes de init:5, então o gate das MCP tools nunca pode depender de register() já ter corrido.

Prompts / Brand Kits (módulos "tab-only")

Sem lógica nenhuma (register() no-op) — só interessam se formos replicar a UI de tabs correspondente. Podem ser omitidos por completo numa primeira réplica sem perda funcional MCP nenhuma.

Redirect / Cloud / Themer (módulos-wrapper)

Vale a pena adoptar o padrão inteiro, não só o wrapper: um módulo que arranca a infra-estrutura pesada (store, handler, CPT) em init:5 E gate as MCP tools associadas via um helper estático separado é o mecanismo real de "kill switch" — desliga tudo (runtime + tools) a partir de UMA option, sem desregistar código morto por todo o lado. O heal-on-upgrade do Themer (emcp_tools_themer_index_healed) é um padrão geral a reter: sempre que se corrige um bug de corrupção de dados, adicionar também um marcador de reparação única para sites JÁ afectados — corrigir só o código novo deixa instalações existentes permanentemente partidas.

Templates / Agent Skills (módulos "metadata Pro")

Ignorar numa réplica 100% free/open — só fazem sentido se construirmos o nosso próprio sistema de licenciamento/tiering. Se algum dia quisermos essa separação, o padrão exacto a copiar é: tier() + is_available() a checar um SDK de licença (equivalente Freemius), com a classe de metadata do módulo a viver sempre no build "free" para mostrar um card bloqueado como incentivo de upsell — decisão de UX deliberada, não acidental (o próprio comentário do autor cita este padrão explicitamente ao comparar Agent Skills com Templates).

Image Optimization

Copiar de perto — é a feature opt-in mais bem desenhada e mais valiosa de toda a árvore Free, sem nenhuma dependência Pro. Decisões de desenho a preservar:

  • Idempotência via marcador de estado _emcp_optim em post meta — evita reprocessar em cada regeneração de metadata.
  • Reversibilidade via espelho de backup em uploads/emcp-originals/ — nunca destruir sem saída de emergência.
  • O filtro emcp_tools_optimize_attachment como seam de extensibilidade explícito — manter um ponto de opt-out per-attachment para outras ferramentas (sideload/stock-image) é um padrão a reter mesmo fora deste contexto específico.
  • REST/CLI sempre-WebP vs frontend condicional ao header Accept é um comportamento subtil mas importante — as MCP tools devem sempre receber o asset optimizado, independentemente da política pública de servir WebP do site.
  • O processador em lote resumível (cursor em option + batch pequeno, 1-50) é o padrão standard WP-admin-ajax para evitar timeout em bibliotecas grandes — sem surpresas, replicar tal-e-qual.
  • Gotcha a preservar: o clamp de quality (1-100) é partilhado entre Optimizer e Resizer via método estático único — evitar duplicar essa lógica de clamp em dois sítios.

SVG Support

Copiar de perto, com cuidado redobrado — é a feature com maior superfície de segurança de toda a árvore Free. Coisas que NÃO se podem saltar:

  • Sanitização fail-closed: um SVG que não consiga ser limpo tem de ser rejeitado, nunca deixado passar silenciosamente.
  • A correcção de wp_check_filetype_and_ext não é opcional — a maioria das implementações ingénuas de "basta adicionar svg a upload_mimes" esquecem-se disto e o upload SVG falha silenciosamente no caminho REST/sideload (comentário explícito do autor: "the piece most SVG plugins miss" — é exactamente o tipo de gotcha não óbvio que vale a pena citar e replicar).
  • removeRemoteReferences(true) explícito — não confiar nos defaults da biblioteca de sanitização, configurar o endurecimento SSRF/XSS deliberadamente.
  • O autoloader PSR-4 escopado como fallback próprio para a biblioteca vendorizada — um padrão de redundância defensiva a reter sempre que se vendoriza um pacote Composer dentro de um plugin WordPress (geração de classmap Jetpack/Composer é um footgun real e conhecido).

Free Brand Kits

Vale a pena copiar o padrão (bundle de um dataset JSON+SVG pequeno e gratuito, partilhando o caminho de escrita/aplicação com qualquer tier pago) mesmo que não cheguemos a construir kits Pro nós próprios — significa que a UX de "aplicar um kit inicial" via admin funciona sem infra-estrutura de licenciamento nenhuma.

Inventário Pro (Parte 2)

Para uma réplica 100% free/aberta, estas 30 classes simplesmente não se constroem — representam ~30 dos ~165 tools totais (~18%) e estão inteiramente ausentes do que seria preciso reimplementar para um clone só-free. Se algum dia quisermos uma separação de monetização própria, o mecanismo exacto a copiar é: (a) gate class_exists() no ponto de chamada do registrar (o ficheiro da classe Pro literalmente não existe a menos que a pasta do plugin pago esteja activa — mesmo modelo de Free/Pro como duas pastas de plugin distintas usado por este vendor, ver 00-ARQUITECTURA.md §7); (b) um toggle de módulo SECUNDÁRIO e opcional (is_enabled()) para o punhado de tools que também querem um interruptor admin independente da licença (Skill/Memory/Migrate); (c) is_available() na própria classe de módulo a verificar um SDK de licença, para os módulos "metadata-only" com card bloqueado (Templates/Agent-Skills).


Fonte

Leitura directa (19-08-2026) de:

  • includes/modules/class-module.php
  • includes/modules/class-modules-registry.php
  • includes/modules/class-prompts-module.php
  • includes/modules/class-brand-kits-module.php
  • includes/modules/class-templates-module.php
  • includes/modules/class-redirect-module.php
  • includes/modules/class-cloud-module.php
  • includes/modules/class-themer-module.php
  • includes/modules/class-agent-skills-module.php
  • includes/modules/image-optimization/class-image-optimization-module.php
  • includes/modules/image-optimization/class-image-optimizer.php
  • includes/modules/image-optimization/class-image-resizer.php
  • includes/modules/image-optimization/class-webp-generator.php
  • includes/modules/image-optimization/class-webp-rewriter.php
  • includes/modules/image-optimization/class-bulk-optimizer.php
  • includes/modules/image-optimization/settings-fields.php
  • includes/modules/svg-support/class-svg-support-module.php
  • includes/modules/svg-support/class-svg-sanitizer.php
  • includes/class-free-brand-kits.php
  • includes/abilities/class-ability-registrar.php (627 linhas, integral — extracção de todas as chamadas class_exists())

Listagens de directório completas (equivalente a ls), 19-08-2026:

  • includes/abilities/ (47 ficheiros + forms/, seo/)
  • includes/abilities/forms/
  • includes/abilities/seo/
  • includes/modules/ (7 módulos + base + registry + image-optimization/, svg-support/)
  • includes/modules/image-optimization/
  • includes/modules/svg-support/
  • includes/themer/ e includes/themer/php/
  • includes/cloud/
  • includes/ (nível topo)

Tentativas directas de leitura de ficheiro (equivalente a test -f), todas devolvendo "No such file or directory" — confirmação de ausência, 19-08-2026, para: class-woo-integration.php, class-memory-module.php, class-migrate-module.php, class-skill-abilities.php, class-memory-abilities.php, class-migrate-abilities.php, class-system-kit-abilities.php, class-seo-abilities.php, class-a11y-abilities.php, class-widget-builder-abilities.php, class-block-builder-abilities.php, class-essential-addons-integration.php, class-premium-addons-integration.php, class-uae-integration.php, class-generatepress-integration.php, class-generateblocks-integration.php, class-blocksy-blocks-integration.php, class-blocksy-extensions-integration.php, forms/class-wpforms-integration.php, seo/class-yoast-integration.php (spot-checks representativos das famílias Forms/SEO Pro, cuja ausência integral é também confirmada pela listagem completa dos respectivos subdirectórios).

Cruzado com docs/00-ARQUITECTURA.md (mesma batch, 19-08-2026) e skill://emcp-tools (auditoria de postura de segurança, 16-08-2026).