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).
This commit is contained in:
@@ -0,0 +1,661 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user