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).
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êmis_active()===true.apply_defaults()— mecanismo de seeding. Optionemcp_tools_modules_seeded(constOPTION_SEEDED) guarda uma lista de ids já considerados para seeding (não um booleano único). Para cada módulo registado ainda não na listaseeded: marca-o como seeded e, sedefault_active()===truee ainda não estiver emactive, adiciona-o aactive. 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 eminit(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 emactive(): seis_available()===true, chamaregister().
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):
(new EMCP_Tools_Themer_CPT())->register()— regista o CPT.- 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. EMCP_Tools_Themer_Index::register_hooks()— hooks de rebuild do índice de condições.- Heal one-time: se
get_option('emcp_tools_themer_index_healed') !== '1', chamaEMCP_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. - Se
! is_admin():(new EMCP_Tools_Themer_Render_Controller())->init()(front-end). - Se
is_admin() && class_exists('EMCP_Tools_Themer_Metabox'):(new EMCP_Tools_Themer_Metabox())->init(). - Se
class_exists('EMCP_Tools_Themer_Blocks'):::init()— blocos Gutenberg dinâmicos. - Se
class_exists('EMCP_Tools_Themer_Widgets'):::init()— widgets clássicos dinâmicos. - 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 abilitiesEMCP_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():
class-ability-registrar.php— gate deEMCP_Tools_Skill_Abilities(confirmado ausente da árvore Free, ver Parte 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
compressOUwebp: instanciaEMCP_Tools_Image_Optimizer($settings), hook emwp_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 comImage_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 sobuploads/emcp-originals/.should_skip(optim): bool—trueseoptim['status']==='done'(idempotência).on_generate_metadata($metadata, $attachment_id)— o hook callback:- guarda:
$metadatatem de ser array; settings tem de tercompressOUwebp; apply_filters('emcp_tools_optimize_attachment', true, $attachment_id)— opt-out per-attachment. Usado porsideload-image/add-stock-imagequando o chamador passaconvert_webp:false(conversão a dar timeout em shared hosting) — é o ÚNICO ponto de extensibilidade externo deste pipeline;- mime tem de ser
image/jpegouimage/png(WebP e GIF não entram no pipeline de compressão — só os dois formatos de origem processados); - se já
status=doneno meta existente, salta; - resolve
wp_upload_dir(), calculasizes_to_process(), chamaprocess_files(), grava o resultado em_emcp_optim.
- guarda:
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 capmax_dimensionviaresize($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[].
- mede tamanho "antes"; se
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=truepor omissão; capturabasedir/baseurldewp_upload_dir(). register()— hookswp_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 toggleserve_frontende headerAccept: 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_REQUESTconst OUWP_CLIconst OUwp_doing_cron().url_to_path(url)— mapeia URL de uploads de volta a caminho absoluto, só dentro dobaseurl(scoping de segurança).maybe_rewrite(url)— decisão final por URL: só reescreve seallowed()e a URL webp difere e o ficheiro.webpexiste 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 handlerswp_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 deOPTION_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 quandoprogress.done; devolve JSON de progresso.ajax_restore()— nonce +manage_options; percorreuploads/emcp-originals/recursivamente (RecursiveIteratorIterator); para cada backup: copia de volta sobre o caminho vivo, apaga o sibling.webpdo destino, incrementarestored; 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:
upload_mimes→allow_svg_mime()— adiciona'svg'=>'image/svg+xml'só securrent_user_can(required_capability()).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 (viafinfo) frequentemente reportatext/plainouimage/svgpara SVGs e rejeita o upload; esta correcção resolve explicitamenteext/typepara ficheiros.svgquando o utilizador tem a capability — é o que faz uploads REST/sideload funcionarem, não só omedia-new.phpclássico.wp_handle_upload_prefilter+wp_handle_sideload_prefilter→ ambossanitize_upload().admin_head→media_thumbnail_css(), só seis_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:
- classe já carregada (
class_exists('\enshrined\svgSanitize\Sanitizer')); EMCP_Tools_Adapter_Bootstrap::ensure()(o mesmo mecanismo de preload do Jetpack Autoloader usado para o MCP adapter vendorizado, ver00-ARQUITECTURA.md§2.1) ou fallback directo avendor/autoload_packages.php;- fallback próprio:
register_fallback_autoloader()— regista um autoloader PSR-4 escopado directamente contravendor/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 (removexlink:hrefremoto,use@hrefremoto);falseem 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 existirassets/brand-kits/{slug}.svg(previews pré-renderizadas, fontes outlined), injectathumbnail_url+preview.thumbnail_urlcom a URL do plugin (o JSON não pode saber a URL do plugin). Nunca devolveWP_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
- Leitura integral de
includes/abilities/class-ability-registrar.php::register_groups()(627 linhas) — extracção de TODAS as chamadasclass_exists('EMCP_Tools_...')que condicionam o registo de um grupo de abilities ou de um pack de integração. - Cruzamento com listagens de directório completas e literais (equivalente a
ls) de:includes/abilities/(47 ficheiros.php+ subpastasforms/,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/— semclass-memory-module.phpnemclass-migrate-module.php).
- subpastas
- 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 emhead: impossível abrir '...' para leitura: No such file or directory(SSH remoto,heada 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
.phpdo 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_optimem 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_attachmentcomo 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
OptimizereResizervia 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_extnã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.phpincludes/modules/class-modules-registry.phpincludes/modules/class-prompts-module.phpincludes/modules/class-brand-kits-module.phpincludes/modules/class-templates-module.phpincludes/modules/class-redirect-module.phpincludes/modules/class-cloud-module.phpincludes/modules/class-themer-module.phpincludes/modules/class-agent-skills-module.phpincludes/modules/image-optimization/class-image-optimization-module.phpincludes/modules/image-optimization/class-image-optimizer.phpincludes/modules/image-optimization/class-image-resizer.phpincludes/modules/image-optimization/class-webp-generator.phpincludes/modules/image-optimization/class-webp-rewriter.phpincludes/modules/image-optimization/class-bulk-optimizer.phpincludes/modules/image-optimization/settings-fields.phpincludes/modules/svg-support/class-svg-support-module.phpincludes/modules/svg-support/class-svg-sanitizer.phpincludes/class-free-brand-kits.phpincludes/abilities/class-ability-registrar.php(627 linhas, integral — extracção de todas as chamadasclass_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/eincludes/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).