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).
56 KiB
01 — Elementor Clássico (não-atómico): páginas, layout, widgets, templates, globals, custom code, composite
Fonte: leitura directa do código-fonte emcp-tools v3.12.1 (build Free), instalado em
emanuelalmeida.pt (/home/ealmeida/emanuelalmeida.pt/wp-content/plugins/emcp-tools/),
19-08-2026. Cobre as classes de abilities que operam sobre o modelo de dados Elementor
clássico (_elementor_data, árvore container/widget/section/column), incluindo os
grupos de leitura/escrita das Global Classes do Elementor 4.0+ (que já não são "clássicas"
mas vivem no mesmo _elementor_data/kit e não fazem parte do sistema de props atómicas
tratado no doc 02). Todas as classes aqui descritas registam-se dentro do bloco
if ( $elementor_active ) de class-ability-registrar.php (linha ~452), sem nenhum outro gate
de módulo — só o Elementor (free) precisa de estar activo.
⚠️ Nota importante para a batch — onde vive o CRUD de widgets custom
class-widget-abilities.php (EMCP_Tools_Widget_Abilities) NÃO contém nenhuma tool de
criação/edição/eliminação de widgets custom (create-custom-widget, update-custom-widget,
get-custom-widget, list-custom-widgets, set-widget-status, delete-custom-widget — a
lista de 16 tools "Widget/Block Builder" identificada em skill://emcp-tools §2.14). Esta
classe cobre só colocação/actualização de instâncias de widget numa página — três tools:
add-free-widget, add-pro-widget (ambas catalog-backed, inserem um widget já existente no
registo do Elementor) e update-widget (edita definições de uma instância já colocada). O CRUD
de definição de widgets custom (a "fábrica" que cria um NOVO tipo de widget PHP/JS a partir de
um spec) vive noutro módulo — confirmado por grep ao class-ability-registrar.php, não faz
parte de nenhuma das 10 classes desta tarefa — quase certamente no sandbox de widgets/blocos
custom tratado no doc 06 (EMCP_Tools_Sandbox_*). Doc06 deve confirmar isto ao ler o registrar
completo; aqui fica o achado negativo registado para não haver dupla cobertura nem lacuna.
1. EMCP_Tools_Page_Abilities — includes/abilities/class-page-abilities.php
Condição de registo: dentro de if ( $elementor_active ), sempre (sem gate adicional).
Construtor recebe EMCP_Tools_Data $data e EMCP_Tools_Element_Factory $factory (injectados
pelo registrar). 5 tools, todas prefixadas emcp-tools/.
| Tool | Input (resumo) | O que faz | permission_callback |
readonly / destructive |
|---|---|---|---|---|
create-page |
title* (string), status (draft|publish, default draft), post_type (page|post, default page), template (slug), content (array de elementos, opcional) |
wp_insert_post() com _elementor_edit_mode=builder + _elementor_template_type=wp-{post_type}; grava content (ou [] se omitido) via EMCP_Tools_Data::save_page_data(); devolve post_id, edit_url, preview_url. |
check_create_permission: publish_pages || edit_pages |
não-readonly, não-destructive, não-idempotente |
update-page-settings |
post_id*, settings* (objecto livre) |
Delegado 1:1 a EMCP_Tools_Data::save_page_settings() — grava definições ao nível de página (background, padding, custom CSS, layout) via Document::save(['settings'=>...]) com fallback a merge em _elementor_page_settings. |
check_edit_permission: edit_posts + (se post_id) edit_post desse post |
não-readonly, não-destructive, idempotente |
delete-page-content |
post_id* |
save_page_data($post_id, []) — limpa TODO o conteúdo Elementor da página, mantendo a página em si (post continua a existir). |
check_delete_permission: edit_posts E delete_posts, mais (se post_id) edit_post E delete_post desse post — a única classe do doc que exige capability de eliminação para uma operação que tecnicamente só edita meta, porque o efeito é irreversível sem o change-ledger |
não-readonly, destructive, idempotente |
import-template |
post_id*, template_json* (array de elementos Elementor), position (default -1 = append) |
Lê a página actual, reassign_ids() a todo o template_json (evita colisão de IDs), insere no array (append ou array_splice na posição) e grava. Devolve elements_count (contagem recursiva via count_elements()). |
check_edit_permission |
não-readonly, não-destructive, não-idempotente |
export-page |
post_id* |
Devolve get_page_data($post_id) verbatim como json — export completo da árvore Elementor da página, reimportável via import-template/apply-template. |
check_edit_permission |
readonly, idempotente |
Achado de design: create-page grava sempre _elementor_data mesmo quando content é
omitido (save_page_data($post_id, [])) — isto inicializa a meta em vez de a deixar por
criar, o que é relevante porque EMCP_Tools_Data::get_page_data() trata "meta ausente" e "meta
[]" da mesma forma (array vazio), mas só a segunda garante que o Elementor reconhece a página
como "Editada com Elementor" desde o primeiro save.
2. EMCP_Tools_Layout_Abilities — includes/abilities/class-layout-abilities.php
Condição de registo: dentro de if ( $elementor_active ), sempre. Mesmo par de
dependências injectadas ($data, $factory). 9 tools — o grupo com mais ferramentas do
Elementor clássico, cobrindo toda a manipulação estrutural da árvore de elementos.
| Tool | Input (resumo) | O que faz | permission_callback |
readonly / destructive |
|---|---|---|---|---|
add-container |
post_id*, parent_id (vazio = topo), position (-1=append), settings (flex/grid completo), full_bleed (bool) |
Cria um container via EMCP_Tools_Element_Factory::create_container() e insere na árvore. full_bleed=true faz merge do preset full_bleed_preset() (content_width=full, width 100%, padding/gap zero, column+stretch) antes de aplicar settings do chamador (que sempre ganham). Bloqueia com erro accionável se EMCP_Tools_Atomic_Props::is_container_supported() for falso (ver "Gotchas" abaixo). |
check_edit_permission |
não-readonly, não-destructive, não-idempotente |
update-container |
post_id*, element_id*, settings* (merge parcial) |
Valida que o elemento alvo é mesmo um container (is_container_type()) antes de aplicar update_element_settings() — devolve erro not_container se for widget (aponta para update-widget). |
check_edit_permission |
não-readonly, não-destructive, idempotente |
update-element |
post_id*, element_id*, settings* |
Versão universal de update — funciona em qualquer elType, container ou widget, sem o caller precisar de saber qual é. Aceita também styles/editor_settings no payload (roteados para a raiz do elemento pela camada de dados — ver §Elementor_Data). Recomendado como default em vez de update-container/update-widget separados. |
check_edit_permission |
não-readonly, não-destructive, idempotente |
batch-update |
post_id*, operations* (array de {element_id, settings}) |
Aplica múltiplos updates num único save (uma leitura + uma escrita da página inteira) — muito mais eficiente que N chamadas a update-element. Continua a processar mesmo com falhas parciais; devolve {success, updated, failed:[{element_id,reason}]}. |
check_edit_permission |
não-readonly, não-destructive, idempotente |
set-element-label |
post_id*, element_id*, title* |
Wrapper de conveniência sobre update_element_settings() com editor_settings.title — define só o rótulo do Navigator (útil sobretudo em elementos atómicos v4, mas funciona em qualquer elType). |
check_edit_permission |
não-readonly, não-destructive, idempotente |
reorder-elements |
post_id*, container_id*, element_ids* (ordem desejada) |
Reordena os filhos DIRECTOS de um container. Valida que todos os element_ids são de facto filhos directos (erro se não). Filhos existentes não mencionados na lista são acrescentados no fim (preservados, não perdidos). |
check_edit_permission |
não-readonly, não-destructive, idempotente |
move-element |
post_id*, element_id*, target_parent_id* (vazio=topo), position* |
Remove o elemento da posição actual e reinsere no destino — implementado como remove_element() + insert_element() sequenciais sobre a mesma árvore em memória, um único save no fim. |
check_edit_permission |
não-readonly, não-destructive, idempotente |
remove-element |
post_id*, element_id* |
Remove o elemento e todos os filhos da árvore. | check_edit_permission |
não-readonly, destructive, idempotente |
duplicate-element |
post_id*, element_id* |
Clona profundamente o elemento (reassign_element_ids() — novos IDs em toda a subárvore, incluindo remapeamento de classes de estilo locais v4 se aplicável) e insere logo a seguir ao original. |
check_edit_permission |
não-readonly, não-destructive, não-idempotente |
Gotchas de design documentados no código (Layout)
is_container_type()inclui os tipos atómicos (container,e-flexbox,e-div-block) — não é só legado;update-container/reorder-elementsreconhecem containers v4 também (comentário no código refere issues #104/#72 como o mesmo problema raiz).add-containerrecusa-se a criar umcontainerlegado se a experiência "Flexbox Container" do Elementor estiver desligada (EMCP_Tools_Atomic_Props::is_container_supported()). Antes desta guarda (issue #111), o elemento era gravado com sucesso mas o Elementor simplesmente não o renderiza em runtime — página fica vazia sem qualquer erro visível ao agente. Este é o tipo de falha silenciosa mais perigosa do plugin: a tool "funciona" (devolvesuccess:true) mas o resultado visual é nada. A mesma guarda está embuild-page(ver §10).full_bleedpreset (#83): em páginas com template Canvas, os defaults "boxed" do Elementor deixam faixas brancas nas margens de secções full-width (headers/footers). O preset resolve isto de forma reutilizável em vez de o agente ter de descobrir os 6 campos certos por tentativa e erro.
3. EMCP_Tools_Widget_Abilities — includes/abilities/class-widget-abilities.php
Condição de registo: dentro de if ( $elementor_active ). add-pro-widget só regista se
defined('ELEMENTOR_PRO_VERSION') (gate interno na própria classe, não no registrar). 3 tools.
Construtor recebe $data, $factory, $schema_generator (EMCP_Tools_Schema_Generator,
usado por get-widget-schema noutra classe P0, não aqui) e $validator
(EMCP_Tools_Settings_Validator, usado para validar settings contra o schema do widget alvo).
| Tool | Input (resumo) | O que faz | permission_callback |
readonly / destructive |
|---|---|---|---|---|
add-free-widget |
post_id*, parent_id*, widget_type*, position, settings |
Valida tier via EMCP_Tools_Widget_Catalog::is_pro($widget_type) — rejeita (wrong_tier) se o tipo pedido for Pro/Woo. Faz merge dos defaults do catálogo (entry['defaults']) por baixo do settings do chamador (chamador sempre ganha). Delega ao motor comum execute_add_widget(). |
check_edit_permission |
não-readonly, não-destructive, não-idempotente |
add-pro-widget |
idem add-free-widget |
Espelho exacto, mas com o tier invertido — rejeita widgets free (aponta para add-free-widget). Só registada quando Elementor Pro está activo (gate natural: não faz sentido oferecer a tool se não há widgets Pro para colocar). |
check_edit_permission |
não-readonly, não-destructive, não-idempotente |
update-widget |
post_id*, element_id*, settings* |
Universal para instâncias de widget já colocadas: encontra o elemento, valida elType==='widget' (erro not_a_widget caso contrário), faz merge parcial de settings. |
check_edit_permission |
não-readonly, não-destructive, idempotente |
Motor comum: execute_add_widget() (privado, partilhado pelas duas tools de inserção)
Passos: (1) valida que widget_type existe de facto no registo Elementor
(Plugin::$instance->widgets_manager->get_widget_types($widget_type) — erro
invalid_widget_type se não), (2) se settings não vazio, chama
EMCP_Tools_Settings_Validator::validate($widget_type, $settings) (mesmo validador de
includes/validators/, mas para controls de widget — distinto do Element_Validator que
valida a forma estrutural do elemento, ver §Elementor_Data), (3) factory->create_widget(),
(4) data->insert_element(), (5) data->save_page_data().
Achado de design: o catálogo (EMCP_Tools_Widget_Catalog, não lido nesta tarefa — pertence
provavelmente ao doc 02 ou doc 10) é a fonte de verdade de tier E de defaults por widget — as
duas tools de inserção são finas camadas de gate+merge sobre um motor único; não há lógica de
posicionamento/inserção duplicada entre free e pro.
4. EMCP_Tools_Template_Abilities — includes/abilities/class-template-abilities.php
Condição de registo: dentro de if ( $elementor_active ). save-as-template e
apply-template sempre registadas; as outras 6 só se defined('ELEMENTOR_PRO_VERSION')
(gate interno, get_ability_names() e register() espelham a mesma condição). 8 tools no
total (2 free + 6 Pro).
| Tool | Tier | Input (resumo) | O que faz | permission_callback |
readonly / destructive |
|---|---|---|---|---|---|
save-as-template |
free | post_id*, element_id (omitir=página inteira), title*, template_type (page|section|container, default page) |
Cria um post elementor_library com _elementor_template_type, define a taxonomia elementor_library_type, grava os elementos (página inteira ou só o elemento indicado) como _elementor_data desse novo post-template. |
check_edit_permission |
não-readonly, não-destructive, não-idempotente |
apply-template |
free | post_id*, template_id*, parent_id, position |
Lê o template, reassign_ids(), insere na página alvo (dentro de parent_id ou ao nível de topo). Devolve elements_added (contagem via count_elements()). |
check_edit_permission |
não-readonly, não-destructive, não-idempotente |
create-elementor-theme-template |
Pro | title*, template_type* (enum: header/footer/single/single-post/single-page/archive/search-results/error-404/loop-item) |
Cria post elementor_library do tipo indicado, inicializa com _elementor_data=[]. |
check_edit_permission |
não-readonly, não-destructive, não-idempotente |
set-elementor-template-conditions |
Pro | post_id*, conditions* (array de arrays de partes, ex. ["include","singular","post"]) |
Ver "Gotcha crítico #38" abaixo — usa save_elementor_conditions(). |
check_edit_permission |
não-readonly, não-destructive, idempotente |
list-dynamic-tags |
Pro | group (filtro opcional) |
Enumera Plugin::instance()->dynamic_tags->get_tags(), filtra por grupo se indicado, devolve {name, title, group, categories} por tag. |
check_edit_permission |
readonly, idempotente |
set-dynamic-tag |
Pro | post_id*, element_id*, setting_key*, tag_name*, tag_settings |
Constrói o valor [elementor-tag id="…" name="…" settings="…"] (formato interno do Elementor, settings urlencoded como JSON) e escreve-o em element.settings.__dynamic__[setting_key], tornando essa definição dinâmica. |
check_edit_permission |
não-readonly, não-destructive, idempotente |
create-popup |
Pro | title* |
Cria post elementor_library tipo popup. |
check_edit_permission |
não-readonly, não-destructive, não-idempotente |
set-popup-settings |
Pro | post_id*, triggers, conditions, timing |
Grava _elementor_popup_triggers/_elementor_popup_timing em post meta directamente; conditions reutiliza o MESMO save_elementor_conditions() seguro que o template (não é um caminho separado). |
check_edit_permission |
não-readonly, não-destructive, idempotente |
Gotcha crítico documentado (#38) — save_elementor_conditions()
Método privado partilhado por set-elementor-template-conditions e set-popup-settings.
A abordagem anterior (update_post_meta() + delete_option() na cache global
elementor_pro_theme_builder_conditions) invalidava a localização de TODOS os templates
sem os reconstruir — definir condições num template partia silenciosamente headers/footers não
relacionados até um rebuild completo. A abordagem correcta passa pelo conditions manager do
próprio Elementor Pro (ThemeBuilder::get_conditions_manager()->save_conditions()), que
regenera a cache correctamente; só cai para escrita directa de meta (sem tocar na cache global)
se o gestor Pro não estiver disponível. Blueprint: nunca fazer bypass da API de alto nível
de um plugin de terceiros para "poupar uma chamada" quando essa API mantém uma cache
side-effectful — o preço é corromper estado partilhado fora do escopo da própria operação.
5. EMCP_Tools_Global_Abilities — includes/abilities/class-global-abilities.php
Condição de registo: dentro de if ( $elementor_active ), sempre. Construtor só recebe
$data (não usa $factory). 2 tools — actuam sobre o kit activo do Elementor
(Plugin::$instance->kits_manager->get_active_kit()), que é o post que guarda paleta global de
cores e tipografia site-wide.
| Tool | Input (resumo) | O que faz | permission_callback |
readonly / destructive |
|---|---|---|---|---|
update-global-colors |
colors* (array de {_id*, title*, color*} hex) |
Lê kit_settings['custom_colors'], faz merge por _id (actualiza existentes, acrescenta novos), kit->update_settings(['custom_colors'=>...]). Regista snapshot no change-ledger ANTES de escrever (snapshot_kit_settings()/record_kit_change() — captura _elementor_page_settings do kit para rollback). |
check_manage_permission: manage_options |
não-readonly, não-destructive, idempotente |
update-global-typography |
typography* (array de {_id*, title*, typography_font_family, typography_font_size, typography_font_weight, typography_line_height, typography_letter_spacing}) |
Mesmo padrão merge-por-_id sobre kit_settings['custom_typography'], mas com allowlist explícita de chaves (allowed_keys) — qualquer campo fora dessa lista é descartado silenciosamente. Força sempre typography_typography='custom' (activa o override; sem isto o Elementor ignora os campos custom). |
check_manage_permission |
não-readonly, não-destructive, idempotente |
Achado de design — a única classe do doc com change-ledger integrado directamente no fluxo de
escrita (não via o hook central de save_page_data(), porque estas duas tools não passam por
_elementor_data nenhuma — escrevem _elementor_page_settings do post do kit). snapshot_kit_settings()
record_kit_change()chamamEMCP_Tools_Change_Recorder::record_meta()explicitamente antes doupdate_settings(), porque de outra forma uma mudança de cor/tipografia global — que afecta todas as páginas do site simultaneamente — não teria rollback nenhum via o ledger genérico (esse só cobre_elementor_datade um post individual, verclass-elementor-data.php). Blueprint: qualquer mutação "global" que não passe pelo caminho de escrita comum de página precisa do seu PRÓPRIO ponto de integração com o change-ledger — não é automático.
6. EMCP_Tools_Global_Classes_Abilities — includes/abilities/class-global-classes-abilities.php
Condição de registo: classe auto-gated — is_available() verifica
class_exists('\Elementor\Modules\GlobalClasses\Global_Classes_Repository') (Elementor 4.0+).
O registrar só instancia se class_exists('EMCP_Tools_Global_Classes_Abilities') E chama
sempre register(), que internamente re-verifica is_available(). 1 tool, read-only.
Resolve a peça mais opaca do sistema de design do Elementor 4.0+: elementos referenciam classes
CSS globais só pelo ID opaco g-xxxxxxx; sem esta tool, um agente que lê um elemento vê o ID
mas não sabe o que ele estiliza.
| Tool | Input (resumo) | O que faz | permission_callback |
readonly / destructive |
|---|---|---|---|---|
list-global-classes |
class_ids (array opcional; omitir = todas) |
Global_Classes_Repository::make()->all() → normaliza Collection/array, para cada item resolve {id, label, css} onde css é flatten_variants() — um mapa breakpoint[:state] → {prop:valor} com os $$type-wrapped props desembrulhados via EMCP_Tools_Atomic_Props::unwrap(). |
check_read_permission: edit_posts |
readonly, idempotente |
Gotcha documentado (#57) — resolução defensiva por item
Cada item é resolvido dentro do seu próprio try/catch. Antes desta guarda, uma única
classe malformada fazia a enumeração inteira (resolve-all, sem class_ids) falhar por
completo, enquanto pedidos com class_ids explícitos (que saltam a entrada má) continuavam a
funcionar — inconsistência confusa de diagnosticar ("porque é que só falha quando não passo
IDs?"). A correcção devolve a classe problemática mesmo assim, com css:[] e um campo error
explicativo, em vez de a fazer desaparecer da lista ou abortar tudo. Blueprint: ao
enumerar uma colecção de N itens onde cada um pode ter forma inesperada, isolar cada resolução —
nunca deixar um item mau abortar os outros N-1.
7. EMCP_Tools_Global_Classes_Write_Abilities — includes/abilities/class-global-classes-write-abilities.php
Condição de registo: mesmo padrão auto-gated (is_available() delega para a classe de
leitura se existir, senão verifica a mesma constante REPOSITORY directamente). 4 tools —
todas em emcp_tools_disabled_tools por omissão (mutação de CSS partilhado entre todas as
páginas do site é tratada como categoria de alto risco, ver skill://emcp-tools §2.9/§5).
Permissão de escrita: elementor_global_classes_update_class (capability própria do Elementor,
tipicamente só admin) OU manage_options.
| Tool | Input (resumo) | O que faz | Destructive |
|---|---|---|---|
create-global-class |
label*, styles (mapa amigável), props (escape-hatch raw $$type), breakpoint (enum de 7 valores, default desktop), state (opcional: hover/focus/…) |
Lê o estado actual (items, order) via read_state(), gera um novo ID (mint_id() — g- + 4 bytes hex aleatórios, sem colisão), constrói o objecto {id, type:class, label, variants:[{meta, props}]} combinando styles (traduzido via EMCP_Tools_Atomic_Styles::build_common_props()+build_flex_props()) com props raw por cima, escreve com write_state(). |
não |
update-global-class |
id*, label, styles, props, breakpoint, state, replace_variant (bool) |
Localiza a variante pelo par (breakpoint, state) (find_variant_index()); se não existir, acrescenta uma nova variante; se existir e replace_variant=false (default), faz merge dos props na variante existente; se true, substitui-a inteira. |
não |
delete-global-class |
id*, confirm* (deve ser true) |
Remove do mapa items e da lista order; exige confirm:true explicitamente — é a única das 4 a ter esse requisito extra, porque apaga a classe de TODOS os elementos que a usam. |
sim |
reorder-global-classes |
order* (array de IDs g-) |
A ordem do Class Manager É a ordem de saída CSS — decide qual classe ganha quando duas se aplicam ao mesmo elemento com a mesma especificidade. IDs omitidos em order são acrescentados no fim, na ordem actual — nenhuma classe pode desaparecer por um reorder parcial (a "baseline order" é a união de current_order + array_keys(items), nunca só o que o chamador mandou). |
não |
Como as escritas persistem — o padrão read_state()/write_state()
Todas as 4 tools passam pelo repositório oficial do Elementor
(Global_Classes_Repository::make()), nunca por meta directa: lê o mapa completo id => item
order[], muta em memória, chamaput($items, $order)— o Elementor calcula o diff add/modify/delete internamente e trata relações + limpeza de uso.write_state()faz best-effort de espelhar também para o contexto de preview (set_preview(true)->put(...)) — se esse segundo write falhar, é tolerado silenciosamente (só logado comWP_DEBUG) porque o write de frontend é a fonte de verdade; o preview só afecta o que o editor mostra até recarregar.
Achado de design — reutilização directa dos tijolos atómicos v4: build_variant_props()
chama EMCP_Tools_Atomic_Styles::build_common_props()/build_flex_props() — as MESMAS classes
de suporte que o sistema de widgets atómicos (doc 02) usa para construir estilos locais por
elemento. Isto significa que "escrever uma Global Class" e "aplicar um estilo local a um
elemento atómico" partilham o mesmo motor de tradução styles amigável → props $$type-wrapped
— não há dois formatos de estilo diferentes no plugin, só dois destinos de armazenamento
(classe global partilhada vs. classe local de um elemento).
8. EMCP_Tools_Custom_Code_Abilities — includes/abilities/class-custom-code-abilities.php
Condição de registo: dentro de if ( $elementor_active ). add-custom-js sempre regista
(funciona com Elementor free, via widget HTML); as outras 3 só se defined('ELEMENTOR_PRO_VERSION').
4 tools no total (1 free + 3 Pro). Injecção de código executável — a classe com o perfil de
risco mais alto deste doc, com o maior número de comentários de segurança no código-fonte.
| Tool | Tier | Input (resumo) | O que faz | permission_callback |
Destructive |
|---|---|---|---|---|---|
add-custom-css |
Pro | post_id*, element_id (omitir=nível de página), css*, replace (bool) |
CSS por elemento usa o placeholder selector como wrapper (substituído pelo Elementor no seu gerador de CSS); grava em settings.custom_css do elemento ou em page_settings.custom_css. Por omissão faz append; replace=true sobrescreve. Sanitização: remove tags PHP e <script>, e neutraliza </style> em loop até fixpoint (ver F-004 abaixo). |
check_edit_permission |
não |
add-custom-js |
free | post_id*, parent_id*, js*, position, wrap_dom_ready (bool) |
Insere um widget HTML contendo <script>{js}</script> na árvore da página (não é injecção site-wide, é conteúdo normal da página). Remove qualquer <script>/</script> que o chamador já tenha incluído (evita duplo-wrap); opcionalmente envolve em DOMContentLoaded. |
check_js_permission: edit_posts+per-post E unfiltered_html — a única tool do grupo a exigir unfiltered_html além da capability de edição normal, porque injecta um <script> executável que o WordPress tiraria a um utilizador sem essa capability (ex. não-super-admin em multisite) |
não |
add-code-snippet |
Pro | title*, code*, location (head|body_start|body_end, default head), priority (1-10, clamp), status (publish|draft), ensure_jquery (bool) |
Cria um post CPT elementor_snippet com meta _elementor_location/_elementor_priority/_elementor_code/_elementor_template_type=code_snippet — injecção site-wide, em TODAS as páginas, ao contrário de add-custom-js (só naquela página). |
check_snippet_permission: manage_options E unfiltered_html |
não |
list-code-snippets |
Pro | location (filtro), status (default any) |
Lista posts elementor_snippet (até 100), devolve {id, title, location, priority, status, code, edit_url} por snippet. |
check_manage_permission: manage_options |
readonly |
Gotchas de segurança documentados no código (Custom Code)
- F-004 — bypass de
</style>neutralizado em loop até fixpoint: o CSS deadd-custom-cssé emitido dentro de um bloco<style>, que o parser HTML trata como texto bruto — a ÚNICA forma de escapar para HTML vivo (vector XSS, ex.</style><img onerror=...>) é a tag literal</style>;<,>isolados ou até<img>sozinhos são inertes sem ela. O código remove</\s*stylenumwhileaté$previous === $css, especificamente para impedir que remover UMA ocorrência reconstrua outra por concatenação adjacente. Importante: preserva TODA a CSS válida — combinadores>/~/+, media queries com</>, strings de conteúdo — só a sequência exacta</styledesaparece. - F-008 — regex de handlers
on*=precisa da flag/s(DOTALL): emsanitize_svg_content()(classe SVG, não esta, mas o padrão de regex é idêntico e vale a pena registar aqui porqueadd-custom-cssfaz sanitização de string semelhante) — sem/s, um handler cujo VALOR contém uma quebra de linha (onclick="alert(1)\n") escapa ao match porque.por omissão não cruza linhas. - Distinção free vs Pro não é arbitrária:
add-custom-js(free) é sempre por-página (um widget HTML normal, mesma superfície de risco que qualquer conteúdo de página); os 3 Pro operam site-wide —add-custom-cssa nível de página TAMBÉM é possível masadd-code-snippetinjecta sempre em todas as páginas. É essa amplitude, não a linguagem em si, que justifica o gatemanage_options(site-wide) vsedit_posts(por-página).
9. EMCP_Tools_Svg_Icon_Abilities — includes/abilities/class-svg-icon-abilities.php
Condição de registo: dentro de if ( $elementor_active ), sempre — mas na prática a tool
não depende de nada específico do Elementor além do formato de saída (o objecto ícone). 1 tool.
| Tool | Input (resumo) | O que faz | permission_callback |
readonly / destructive |
|---|---|---|---|---|
upload-svg-icon |
svg_url OU svg_content (mutuamente exclusivos), title |
Faz upload/sideload de um SVG para a Media Library e devolve o objecto de ícone Elementor pronto a usar: {value:{id,url}, library:'svg'} — directamente atribuível a selected_icon em qualquer widget icon/icon-box/button. |
check_upload_permission: upload_files |
não-readonly, não-destructive, não-idempotente |
Pipeline de segurança (fail-closed em 3 camadas)
- Download (
svg_url): viaEMCP_Tools_Url_Guard::safe_download()— guarda SSRF que bloqueia hosts privados/reservados/loopback e revalida cada hop de redirect (não é lido nesta tarefa, mas o nome da classe/comportamento fica documentado como dependência crítica). - Bypass temporário do filtro de MIME do WordPress: dois filtros temporários
(
upload_mimes,wp_check_filetype_and_ext) permitem o.svgsó durante a chamada e são removidos logo a seguir — não altera a política global de uploads do site. - Sanitização fail-closed, dupla camada:
- Verificação superficial: rejeita se contiver
<script(regra própria, antes de chamar o sanitizador). - Sanitizador real:
\Elementor\Core\Utils\Svg\Svg_Sanitizer(classe do PRÓPRIO Elementor, reaproveitada — não é uma dependência própria do EMCP Tools). Se a classe do sanitizador não existir, a tool recusa o upload por completo (no_svg_sanitizer) em vez de aceitar markup minimamente verificado — fail-closed genuíno, não um "melhor esforço". - Para
svg_content: além do sanitizador, regex adicionais removem handlerson*=(com/s— ver F-008 acima) e URLsjavascript:.
- Verificação superficial: rejeita se contiver
Blueprint: este é o padrão de referência para QUALQUER tool de upload de conteúdo potencialmente executável (SVG pode conter script) — nunca confiar só na extensão de ficheiro nem só num sanitizador; usar o MELHOR sanitizador disponível (aqui, reaproveitar o do Elementor em vez de reescrever um) e recusar em vez de degradar quando ele não está presente.
10. EMCP_Tools_Composite_Abilities — includes/abilities/class-composite-abilities.php
Condição de registo: dentro de if ( $elementor_active ), sempre. 1 tool — mas é a mais
sofisticada do doc: constrói uma página inteira a partir de uma especificação declarativa
aninhada, numa única chamada MCP.
| Tool | Input (resumo) | O que faz | permission_callback |
readonly / destructive |
|---|---|---|---|---|
build-page |
title*, status (default draft), post_type (default page), page_settings, dry_run (bool), structure* (árvore declarativa: {type, widget_type?, settings?, children?}[]) |
Constrói recursivamente a árvore Elementor a partir de structure (em memória, sem write ainda), cria o post e grava só se dry_run não estiver activo. |
check_create_permission: publish_pages || edit_pages |
não-readonly, não-destructive, não-idempotente |
Passos de execução (execute_build_page)
- Guarda de suporte de container (idêntica à de
add-container, ver §2) — recusa antecipadamente se a experiência Flexbox Container estiver desligada, porquebuild-pageemitecontainers legados que renderizariam vazios (#111). build_elements()recursivo — percorrestructure, normaliza cada nó (normalize_node(), ver abaixo), constróicontainers ou widgets, acumula$this->elements_createde$this->warnings.- Se
elements_created > 150(SOFT_ELEMENT_LIMIT), acrescenta um aviso sobre risco de timeout num conector MCP remoto — sugeredry_runprimeiro ou dividir em várias chamadas. dry_run=true: devolve{dry_run:true, would_create:N, warnings:[...]}sem tocar na base de dados — nenhum post é criado.- Caso contrário:
wp_insert_post(),save_page_data(),save_page_settings()(se fornecido), devolvepost_id/edit_url/preview_url/elements_created/warnings.
normalize_node() — coerção tolerante de shorthand de modelos fracos
Modelos de IA mais fracos escrevem rotineiramente {"type":"heading", ...} em vez da forma
canónica {"type":"widget","widget_type":"heading"}, ou dão a um container um type diferente
de "container" mas ainda com children. Em vez de descartar silenciosamente estes nós (o que
faria o pedido "ter sucesso" com colunas vazias no resultado — o pior tipo de falha, porque
parece funcionar), normalize_node():
- Se o nó tem
childrennão-vazio → tratado como container, independentemente dotypedeclarado; se otypeoriginal não era"container", regista um warning. - Se não tem
childrene temtypenão-vazio → interpretado como shorthand de widget: o própriotypetorna-sewidget_type(ex."heading"→ widget heading); warning explica a forma preferida. - Devolve sempre o nó com
typecanónico (container/widget), nunca lança erro — a filosofia é "aceitar o que o modelo quis dizer, mas dizer exactamente o que foi assumido".
Layout automático em containers flex_direction=row
Quando um container pai tem flex_direction=row (ou row-reverse) e mais de 1 filho:
- Containers filhos SEM largura explícita (
width/_flex_size/_flex_growjá definidos) recebemcontent_width=full+width={size: 100/N, unit:'%'}automaticamente — replica o padrão nativo de colunas do Elementor sem o agente ter de calcular percentagens. - Widgets colocados directamente como filho de um row (sem container intermédio) são
automaticamente envolvidos num container-coluna com a mesma largura calculada — porque o
modelo flex do Elementor exige um container como flex-item; um widget "nu" não tem
flex-basis e simplesmente esticaria para preencher a row em vez de formar uma coluna própria.
Isto acrescenta um elemento extra à árvore (contabilizado em
elements_created) que o chamador não pediu explicitamente, mas sem o qual o layout pedido (colunas lado-a-lado) nem sequer seria possível. - Explicitamente PROIBIDO no schema (bloco
descriptionda tool): nunca definirflex_wrapou_flex_sizemanualmente — a tool já trata disto e sobreposições manuais causam overflow de layout.
build_widget() — ponte para widgets atómicos v4 dentro de build-page
Se EMCP_Tools_Atomic_Widget_Map::is_atomic($widget_type) for verdadeiro, build-page não
usa factory->create_widget() legado — usa factory->create_atomic_widget() com os settings
mapeados por EMCP_Tools_Atomic_Widget_Map::settings() (o mesmo mapeamento que as tools
add-atomic-* individuais usam, doc 02), e aplica os parâmetros de estilo restantes como uma
classe local via EMCP_Tools_Atomic_Styles::create_local_class(). Isto significa que
build-page é atomic-aware por composição, não por duplicação — reaproveita inteiramente o
motor do doc 02 em vez de ter a sua própria lógica de tradução de props atómicas.
Blueprint: build-page é o exemplo mais claro no plugin de "tool de alto nível como
orquestrador fino sobre primitivas de baixo nível" — não introduz nenhuma capacidade de
persistência nova, só composição declarativa + tolerância a input ambíguo em cima de
Element_Factory+Data(+Atomic_* quando aplicável). Uma réplica deveria construir esta tool
POR ÚLTIMO, depois de todas as primitivas (container/widget/atomic) já funcionarem
individualmente.
Serviços de suporte
EMCP_Tools_Element_Factory — includes/class-element-factory.php
Fábrica pura (sem I/O, sem WordPress DB) que constrói arrays PHP no formato exacto que o
Elementor espera para cada tipo de elemento. Métodos: create_container(), create_widget(),
create_section()/create_column() (legado pré-Container, usado só residualmente — nenhuma
tool destas 10 classes os invoca directamente, mantidos por compatibilidade), e os 3 atómicos
create_atomic_widget()/create_flexbox()/create_div_block() (Elementor 4.0+, cobertos em
detalhe no doc 02 mas usados aqui indirectamente por build-page).
Duas normalizações estáticas reutilizadas em toda a base de código (chamadas não só pela
factory mas também por EMCP_Tools_Data::update_element_settings()):
normalize_container_settings()— remapeia os atalhos sem prefixojustify_content/align_items/align_contentpara as chaves prefixadasflex_justify_content/flex_align_items/flex_align_contentque o schema de container do Elementor realmente lê. Gotcha crítico (#32): sem este remap, os valores eram persistidos sob nomes que o gerador de CSS do Elementor nunca consulta — as custom properties CSS (--justify-content,--align-items) nunca são emitidas e o container renderiza com alinhamento default no frontend, apesar dos dados estarem "correctos" na base de dados. Chaves prefixadas fornecidas pelo chamador sempre ganham sobre o atalho, se ambas aparecerem no mesmo payload.normalize_background_settings()— corrige 3 formas erradas-mas-intuitivas de background que modelos fracos emitem: (1) um grupo aninhadobackground: {background_image, size, ...}é achatado para chavesbackground_*de topo (o Elementor não tem control de grupobackground, um objecto aninhado é simplesmente ignorado); (2)background_imagedado como array de objectos[{id,url}](o modelo espelha a forma de um media-repeater) é desembrulhado para o objecto único{id,url}esperado; (3) quando existe imagem OU cor mas falta o activadorbackground_background, injectaclassicautomaticamente — sem o activador o Elementor nunca renderiza background nenhum. Idempotente e não-destrutivo: chaves planas já fornecidas pelo chamador sempre ganham sobre o que é elevado do grupo aninhado.
Container create_container() também aplica um default de UX: auto-centra
flex_align_items='center' em containers coluna não-grid quando o chamador não especificou
alinhamento — só linhas (row) ficam com o comportamento default do Elementor.
EMCP_Tools_Data — includes/class-elementor-data.php
A camada de leitura/escrita real do _elementor_data — usada por praticamente todas as 10
classes deste doc (excepto Global Classes, que usa o repositório próprio do Elementor
directamente, e Global_Abilities, que usa o kit manager). ~700 linhas; o ficheiro mais denso em
comentários de bug-fix real de todo o doc. Métodos-chave:
get_document()— obtém o\Elementor\Core\Base\Documentpara um post viaPlugin::$instance->documents->get($post_id). Guardado porelementor_documents_ready(): o gestor de documentos do Elementor só existe depois do seu próprio hookinitcorrer; durante a ACTIVAÇÃO do Elementor (que insere o kit por omissão viasave_post, o que dispara o indexador do EMCP Tools) essa dependência ainda não existe — sem a guarda seria um fatal error por null-deref (#105).get_page_data()— tenta primeiro$document->get_elements_data(); se vazio, cai para leitura directa de_elementor_data(post meta bruto,json_decode). O fallback existe porque em contexto CLI/proxy (sem browser, sessão de editor) o API do documento por vezes devolve vazio mesmo com dados presentes na base de dados.save_page_data()— o método mais complexo de todo o ficheiro (~140 linhas). Fluxo completo:EMCP_Tools_Atomic_Props::coerce_tree($data)— varre a árvore INTEIRA (não só o elemento a alterar) antes de gravar. Gotcha #102: uma versão anterior só coagia o elemento sendo escrito; como o Elementor 4.x valida a ÁRVORE COMPLETA no save, um único widget com um valor de prop bruto (não$$type-wrapped) noutro sítio da página bloqueava TODOS os saves futuros — incluindo o save destinado a reparar esse mesmo widget. Fazer a coerção ser sempre sobre a árvore inteira é um no-op para páginas saudáveis e uma rede de segurança universal para páginas com dados legados/corrompidos.- Preserva
_elementor_datacorrupto antes de sobrescrever — se a meta actual for uma string não-vazia que não fazjson_decodeválido, é copiada para_elementor_data_emcp_corruptantes do save prosseguir. Sem isto,get_page_data()trata "corrupto" como "vazio" e um save subsequente apagaria os dados originais para sempre. try { $document->save(...) } catch (\Throwable $e)— o Elementor 4.x atómico lança excepção (não devolvefalse) quando a validação de settings/estilos falha.is_atomic_validation_rejection()distingue uma rejeição de validação legítima (mensagem contém "validation failed" ou "invalid_value") de um erro fatal genuíno. Gotcha #112: um prop{$$type:'dynamic'}gravado pelo editor ao vivo pode referenciar uma dynamic tag que o registo atómico em contexto CLI/REST não consegue resolver — e como a validação é sobre a árvore inteira, isso bloquearia QUALQUER save da página, incluindo edições a elementos totalmente não relacionados. Uma rejeição de validação é tratada como "dados legítimos que este contexto não sabe verificar" e roteada para o fallback de meta directa em vez de reprovada.- Verificação pós-save (#98): mesmo quando
$document->save()devolve verdadeiro sem excepção, relê_elementor_datae confirma que os dados enviados realmente persistiram — em certos contextos 4.x/atómicos/REST o save pode devolver "sucesso" e ainda assim esvaziar_elementor_data. Se detectado, força o mesmo fallback de escrita directa em vez de reportar um sucesso fantasma ao chamador. - Fallback de meta directa:
update_post_meta('_elementor_data', wp_slash(json_encode($data)))- garante
_elementor_edit_mode=builder+_elementor_version+ invalida cache CSS (delete_post_meta('_elementor_css')+ apaga o ficheiro físicouploads/elementor/css/post-{id}.cssse existir) + invalida a cache de elemento renderizado do Elementor 4.2 (_elementor_element_cache, ver hookinit()abaixo).
- garante
- Regista no change-ledger —
EMCP_Tools_Change_Recorder::record_elementor()(ou fallback directo aEMCP_Tools_Change_Log::record()), capturando o_elementor_dataANTERIOR completo para permitir rollback viarollback-change(doc 05).
init()(hook estático global) — regista emadded_post_meta/updated_post_meta: sempre que_elementor_dataé escrito, por QUALQUER caminho (as nossas tools, o editor, um import), apaga_elementor_element_cache. Motivo (#111 revisitado): o Elementor 4.2 introduziu uma cache de HTML renderizado nessa meta key; o próprio Elementor limpa-a emDocument::save(), mas o fallback de meta directa do EMCP Tools bypassa isso. Numa instalação com object cache persistente (ex. WP Engine), uma entrada vazia/obsoleta sobrevivia a QUALQUER escrita de conteúdo subsequente — uma página criada via MCP (escrita enquanto os dados ainda eram[], renderizada [caching vazio], depois preenchida) servia o render vazio em cache para sempre. Este hook restaura a invalidação universalmente para qualquer caminho de escrita.insert_element()/remove_element()/reassign_ids()/reassign_element_ids()/count_elements()/find_element_by_id()— utilitários recursivos puros sobre a árvore em memória (todos operam por referência&$dataonde relevante para evitar cópias de arrays grandes a cada nível de recursão).reassign_element_ids()também chamaEMCP_Tools_Atomic_Styles::remap_local_classes()— gotcha #97: classes de estilo locais v4 (e-<id>-<hash>) pertencem a UM elemento; duplicar um elemento sem remapear as suas classes locais fazia o duplicado partilhar as classes do original — vazamento de estilo entre elementos e duplicação da "Origem de Estilo" no editor.update_element_settings()— o segundo método mais complexo. Além do merge óbvio desettings, faz:- Hoist de chaves-irmãs da raiz (
styles,editor_settings) — em elementos atómicos v4, o mapastyleslocal eeditor_settings(rótulo Navigator) vivem na RAIZ do elemento, como irmãos desettings, não dentro dele. Um agente naturalmente aninha-os sobsettings;update_element_settings()intercepta essas duas chaves ANTES do merge normal, remove-as do payload de settings, e fazdeep_merge()para a raiz do elemento (gotcha #72/#73 — sem isto, eram gravadas emsettings.styles, uma chave morta que o Elementor nunca lê). - Normalização condicional por tipo: containers passam por
normalize_container_settings(); qualquer outro elType passa só pornormalize_background_settings()(mesma correcção de background, sem o remap de flex que só faz sentido em containers). EMCP_Tools_Atomic_Props::coerce_settings()no settings MERGED (não só no incoming) para widgets — gotcha #101: um valor bruto ('Hello'em vez de{$$type:'html-v3',value:'Hello'}) em prop atómico não é simplesmente ignorado — "envenena" o elemento: o Elementor cai para o default do prop (renderiza texto placeholder) E todo o save subsequente da página lança "Settings validation failed", trancando a página fora tanto da API como do próprio editor visual. Correr a coerção sobre o resultado do merge aceita valores simples que um agente naturalmente envia E repara qualquer coisa que uma versão anterior já tenha gravado incorrectamente.sync_local_class_refs()quandostylesfoi tocado — gotcha #92: uma classe de estilo local só renderiza sesettings.classes(o prop$$type:'classes'que lista IDs aplicados) referenciar o seu ID. Um agente que escreve um mapastylesmas esquece de acrescentar o ID aclassesobtém um no-op silencioso — o estilo persiste na base de dados mas nunca se aplica visualmente. Este método varreitem['styles'](só entradastype==='class') e garante que todos os IDs aparecem emsettings.classes.value, idempotente.deep_merge()— merge recursivo próprio: mapas associativos fazem merge chave-a-chave; listas (arrays numéricos sequenciais, ex. um arrayvariants) e escalares são substituídos por inteiro pelo valor incoming. Permite que um update parcial destyles/editor_settingstoque só uma classe/chave sem apagar as irmãs, mantendo ao mesmo tempo a substituição total quando o chamador manda de facto uma lista nova completa.
- Hoist de chaves-irmãs da raiz (
EMCP_Tools_Element_Validator — includes/validators/class-element-validator.php
Classe pequena e propositadamente simples (~55 linhas) — valida só a forma estrutural de um
elemento isolado antes de ser gravado: id presente, elType presente e num allowlist fixo
(container, widget, section, column, mais os tipos atómicos e-div-block, e-flexbox,
mais os tipos de estrutura de formulário atómico e-tabs*/e-form*), e widgetType presente
quando elType==='widget'. Não é chamada por nenhuma das 10 classes deste doc directamente
(não aparece em nenhuma das leituras de add-container/add-*-widget/build-page) — é
provavelmente invocada num caminho de import/validação mais genérico não coberto por esta
tarefa (possivelmente import-sandbox-artifact ou o dispatcher de validação de widgets custom
do doc 06, dado o allowlist incluir tipos e-form-* que não aparecem em mais nenhum ficheiro
lido nesta tarefa). Não confundir com EMCP_Tools_Settings_Validator (validador de VALORES de
settings contra o schema de controls de um widget — usado por Widget_Abilities::execute_add_widget(),
ver §3) nem com EMCP_Tools_Atomic_Props (coerção/validação de tipos de prop atómico v4 — usado
extensivamente por EMCP_Tools_Data, coberto em detalhe no doc 02).
Blueprint para réplica
Copiar quase 1:1 (baixo risco de reescrever pior)
EMCP_Tools_Element_Factory— fábrica pura, sem I/O; as duas normalizações estáticas (normalize_container_settings,normalize_background_settings) codificam conhecimento tácito real sobre onde o Elementor lê cada chave (não documentado nem no Elementor nem em lado nenhum público) — recriá-las do zero significa redescobrir os mesmos 3-4 bugs (#32 em particular) por tentativa e erro num site de produção.- O padrão try/save/verify/fallback de
save_page_data()— é o resultado de pelo menos 5 issues reais numeradas (#98, #101, #102, #105, #111, #112) resolvidas ao longo de várias versões. Uma réplica que escreva_elementor_datasó comupdate_post_meta()direto (sem passar primeiro peloDocument::save()nativo) perde a regeneração de CSS automática do Elementor; uma que só useDocument::save()sem fallback nem verificação pós-save vai falhar silenciosamente em contexto CLI/REST exactamente como a v1.0.0 original deste plugin presumivelmente fazia antes destas correcções serem adicionadas. - O hook
init()de invalidação de_elementor_element_cache— 4 linhas de código que previnem uma classe inteira de bugs "página fica vazia depois de criada via MCP" em sites com object cache persistente. Trivial de replicar, caro de não ter. - O padrão de
Global_Classes_Write_Abilities: read-mutate-put via o repositório oficial do Elementor em vez de escrita directa de meta — delega o cálculo de diff e limpeza de relações ao próprio Elementor. Reescrever isto por fora (calcular o diff manualmente) só faz sentido se se estiver a substituir inteiramente o sistema de Class Manager, não a interagir com ele.
Simplificar na reescrita
- Os 3 tools Pro de
Custom_Code_Abilities(add-custom-css,add-code-snippet,list-code-snippets) dependem de comportamento interno específico do Elementor Pro (CPTelementor_snippet, meta keys_elementor_location/_elementor_priority/_elementor_code). Numa réplica sem o objectivo de espelhar exactamente o Elementor Pro, um sistema de snippets site-wide próprio (CPT nosso, sem tentar imitar o formato do Elementor Pro) é mais simples de manter e não fica preso a mudanças não documentadas do formato interno de um plugin de terceiros. - Os 6 tools Pro de
Template_Abilities(theme templates, dynamic tags, popups) só fazem sentido se se estiver mesmo a espelhar o Elementor Pro Theme Builder; se a réplica não visa paridade total com Elementor Pro, este bloco inteiro pode ficar de fora sem perda de valor para o caso de uso "gerar/editar páginas com Elementor free". normalize_node()(shorthand coercion embuild-page) — é uma correcção pragmática para modelos de IA fracos, não uma necessidade estrutural. Numa réplica visada a modelos fortes (ex. só Claude/GPT-4 classe), pode-se optar por rejeitar shorthand com erro claro em vez de coagir silenciosamente — troca tolerância por previsibilidade; ambas são escolhas válidas, mas a escolha deve ser deliberada, não copiada por omissão.
Riscos/gotchas a não esquecer (lista consolidada, por nº de issue)
| # | Onde | Risco se ignorado numa réplica |
|---|---|---|
| #32 | normalize_container_settings |
Atalhos justify_content/align_items gravados mas nunca lidos pelo gerador CSS — alinhamento nunca aplica no frontend |
| #38 | save_elementor_conditions |
Bypass da API de condições do Theme Builder invalida location cache de TODOS os templates, não só o alterado |
| #72/#73/#92 | update_element_settings (styles/editor_settings hoist + sync_local_class_refs) |
Estilos locais v4 gravados mas nunca aplicados; rótulo Navigator gravado em chave morta |
| #83 | full_bleed_preset |
Faixas brancas em templates Canvas com secções full-width |
| #97 | reassign_element_ids |
Duplicar um elemento atómico faz o duplicado herdar (e poluir) as classes locais do original |
| #98 | save_page_data verificação pós-save |
Document::save() pode devolver sucesso e mesmo assim não persistir nada em contexto 4.x/REST |
| #101/#102 | coerce_settings/coerce_tree |
Um valor bruto num prop atómico tranca TODA a página fora de futuros saves, não só o elemento afectado |
| #104/#72(layout) | is_container_type |
Ferramentas de layout que só reconhecem container legado ignoram containers atómicos v4 (e-flexbox/e-div-block) |
| #105 | elementor_documents_ready |
Fatal error por null-deref se o Elementor ainda não completou o próprio boot |
| #108 | Global_Classes_Write_Abilities (comentário de cabeçalho) |
Tools de escrita de Global Classes são recentes (3.9.0) — API do Elementor para isto é jovem, sujeita a mudança |
| #111 | is_container_supported (2 sítios: add-container e build-page) |
Container gravado com sucesso mas invisível no frontend se a experiência Flexbox Container estiver desligada — falha totalmente silenciosa |
| #112 | is_atomic_validation_rejection |
Uma dynamic tag não resolvível em contexto CLI bloqueia save de página inteira, incluindo edições não relacionadas |
| F-004 | add-custom-css sanitização |
Bypass de </style> como vector XSS se a remoção não for feita em loop até fixpoint |
| F-008 | Regex de handlers on*= (SVG + custom CSS) |
Handler com valor multi-linha escapa à sanitização sem a flag /s |
Fonte
Leitura directa (19-08-2026) de, sob
/home/ealmeida/emanuelalmeida.pt/wp-content/plugins/emcp-tools/:
includes/abilities/class-page-abilities.phpincludes/abilities/class-layout-abilities.phpincludes/abilities/class-widget-abilities.phpincludes/abilities/class-template-abilities.phpincludes/abilities/class-global-abilities.phpincludes/abilities/class-global-classes-abilities.phpincludes/abilities/class-global-classes-write-abilities.phpincludes/abilities/class-custom-code-abilities.phpincludes/abilities/class-svg-icon-abilities.phpincludes/abilities/class-composite-abilities.phpincludes/class-element-factory.phpincludes/class-elementor-data.phpincludes/validators/class-element-validator.phpincludes/abilities/class-ability-registrar.php(linhas 430-530, só para confirmar a condição de registo —if ( $elementor_active )— e a ausência de qualquer classe de CRUD de widget custom neste bloco)
Cruzado com docs/00-ARQUITECTURA.md (mesma tarefa, doc irmão) para o contrato de
emcp_tools_register_ability() e a ordem de arranque, e com skill://emcp-tools (auditoria de
segurança, 16-08-2026) para a lista de tools deste grupo presentes no deny-list por omissão
(§2.6/§2.9/§2.10 desse documento).