--- name: branda-menu description: > Organiza o menu admin WordPress via Branda (branda-white-labeling) em seccoes Descomplicar standard. Computa Branda IDs a partir de slugs WP, constroi menu custom com itens nativos reordenados + section headers, e guarda programaticamente via WP-CLI. Use when "branda menu", "organizar menu admin", "menu wordpress", "admin sidebar", "reorganizar admin". author: Descomplicar Crescimento Digital version: 1.0.0 quality_score: 85 user_invocable: true category: wordpress tags: [branda, wordpress, admin-menu, white-labeling, wip] desk_project: 69 allowed-tools: Read, Write, Edit, Bash, mcp__ssh-unified, mcp__memory-supabase mcps: ssh-unified, memory-supabase dependencies: mcps: [ssh-unified] plugins: [branda-white-labeling] triggers: - "User mentions 'branda menu', 'organizar menu', 'admin sidebar'" - "User asks to reorganize WordPress admin menu" - "Setting up new WiP site admin customization" --- # /branda-menu - Organizar Menu Admin WordPress Skill para organizar o menu admin sidebar de qualquer site WordPress WiP usando o Branda (branda-white-labeling). --- ## Arquitectura Branda (Referencia Interna) ### Como o Branda gere menus O Branda usa um modulo `admin/menu.php` que intercepta o render do menu admin WordPress e substitui pela configuracao custom. **Options `wp_options` envolvidas:** | Option | Funcao | |--------|--------| | `ultimatebranding_activated_modules` | Modulos activos (precisa ter `admin/menu.php`) | | `ub_custom_admin_menu` | Configuracao do menu custom por role | | `ub_saved_admin_menus` | Captura do menu nativo WP (so para UI Branda, nao para render) | ### Algoritmo de ID (critico) O Branda mapeia slugs WordPress para IDs internos usando: ```php function branda_id($slug) { return "menu_item_" . substr(base_convert(md5($slug), 16, 32), 0, 12); } ``` **Exemplo:** `index.php` -> `menu_item_42ho017e7jo0` Este algoritmo e determinista. O mesmo slug gera sempre o mesmo ID, independente do site. ### Tipos de itens no menu | Tipo | Campos chave | Uso | |------|-------------|-----| | **Section header** | `link_type: "none"`, `css_classes: "menu-highlight"` | Separador visual com titulo | | **Custom link** | `link_type: "custom"`, `custom_url: "..."` | Link manual (ex: Descomplicar) | | **Native item** | `was_native: 1`, campos vazios | Item WP real, nome preenchido dinamicamente | | **Hidden item** | `is_hidden: "1"`, `was_native: 1` | Item WP escondido do menu | ### Mecanismo de merge (parse_args_deep) Na renderizacao, o Branda faz merge do menu configurado com o menu WP real: - Itens nativos no config: aparecem na posicao configurada - Itens nativos **nao** no config: inseridos na posicao default (aparecem misturados!) - Itens custom: aparecem na posicao configurada - Submenus vazios `[]`: auto-preenchidos com submenus reais do WP **Regra critica:** Incluir **todos** os itens nativos no config (visiveis na posicao desejada OU hidden). Se faltar algum, aparece na posicao default e quebra a organizacao. --- ## Seccoes Standard Descomplicar (9) Todo site WiP deve ter o menu organizado nestas 9 seccoes, nesta ordem: | # | Seccao | Dashicon | Conteudo tipico | |---|--------|----------|----------------| | 1 | **Suporte** | dashicons-admin-tools | Link Descomplicar (desk.descomplicar.pt) | | 2 | **Admin** | dashicons-admin-generic | Painel, Utilizadores, Opcoes, Plugins, Ferramentas | | 3 | **Conteudo** | dashicons-admin-page | Paginas, CPTs do projecto, Artigos, Multimedia, ACF | | 4 | **Design** | dashicons-admin-appearance | Elementor, Modelos, Jeg Kit, Apresentacao | | 5 | **Marketing** | dashicons-megaphone | FluentCRM, Fluent Forms, Rank Math, WhatsApp, etc. | | 6 | **Idiomas** | dashicons-translation | Polylang/WPML, Loco Translate (se multilingue) | | 7 | **Performance** | dashicons-performance | WP Fastest Cache, WebP Express | | 8 | **Seguranca** | dashicons-shield | Wordfence, Complianz | | 9 | **WebMaster** | dashicons-admin-settings | Branda, Code Snippets, Activity Log | **Notas:** - Seccao Idiomas so aparece se site for multilingue - CPTs especificos do projecto (Tours, Servicos, etc.) vao em Conteudo - Plugins sectoriais (KiviCare, WooCommerce, etc.) podem ter seccao propria entre Marketing e Idiomas --- ## Itens tipicamente hidden Estes itens nativos devem ser incluidos no config com `is_hidden: "1"`: | Slug | Motivo | |------|--------| | `separator1`, `separator2`, `separator-last` | Separadores nativos substituidos por section headers | | `edit-comments.php` | Comentarios raramente usados | | `hello-elementor` | Pagina tema redundante com themes.php | | `elementor` | Menu Elementor duplicado (ja existe elementor-home) | --- ## Procedimento ### Passo 1: Verificar pre-requisitos ```bash # Via MCP SSH - verificar Branda activo e modulo menu activo mcp__ssh-unified__ssh_execute server:"server" command:"wp plugin is-active branda-white-labeling --allow-root --path=/home/USER/SITE && echo ACTIVE || echo INACTIVE" # Verificar modulo menu activo mcp__ssh-unified__ssh_execute server:"server" command:"wp eval ' \$m = get_option(\"ultimatebranding_activated_modules\", array()); echo isset(\$m[\"admin/menu.php\"]) ? \"MENU MODULE ACTIVE\" : \"MENU MODULE INACTIVE\"; ' --allow-root --path=/home/USER/SITE" ``` Se modulo inactivo, activar: ```bash mcp__ssh-unified__ssh_execute server:"server" command:"wp eval ' \$m = get_option(\"ultimatebranding_activated_modules\", array()); \$m[\"admin/menu.php\"] = \"yes\"; update_option(\"ultimatebranding_activated_modules\", \$m); echo \"Activado\"; ' --allow-root --path=/home/USER/SITE" ``` ### Passo 2: Extrair menu nativo Obter lista de plugins activos e menu WP via WP-CLI. Como WP-CLI nao carrega todos os menus admin, usar este metodo combinado: **Opcao A (preferida): mu-plugin temporario** ```bash # Criar mu-plugin que captura menu no proximo page load mcp__ssh-unified__ssh_execute server:"server" command:"cat > /home/USER/SITE/wp-content/mu-plugins/capture-menu.php << 'PHPEOF' \$menu, 'submenu' => \$submenu, 'captured_at' => time() )); update_option('_branda_menu_captured', true); // Self-destruct @unlink(__FILE__); }, 999999); PHPEOF echo 'mu-plugin criado' " ``` Depois, gerar cookie de auth e disparar um page load admin: ```bash # Gerar cookie auth via WP-CLI mcp__ssh-unified__ssh_execute server:"server" command:"wp eval ' \$expiration = time() + 300; \$cookie = wp_generate_auth_cookie(1, \$expiration, \"logged_in\"); echo \$cookie; ' --allow-root --path=/home/USER/SITE" # Disparar page load admin com curl (substituir COOKIE e HASH) mcp__ssh-unified__ssh_execute server:"server" command:"curl -s -o /dev/null -w '%{http_code}' -b 'wordpress_logged_in_HASH=COOKIE' https://SITE/wp-admin/" ``` Depois ler a captura: ```bash mcp__ssh-unified__ssh_execute server:"server" command:"wp eval ' \$cap = get_option(\"_branda_menu_capture\"); if (\$cap) { foreach (\$cap[\"menu\"] as \$pos => \$item) { if (count(\$item) >= 3) { \$slug = \$item[2]; \$title = strip_tags(\$item[0]); echo \"\$pos. \$title => \$slug\n\"; } } } ' --allow-root --path=/home/USER/SITE" ``` **Opcao B (alternativa): Chrome DevTools** Se tiver acesso ao browser, navegar ao wp-admin e executar JS: ```javascript Array.from(document.querySelectorAll('#adminmenu > li')).forEach((li, i) => { const a = li.querySelector('a'); if (a) { const name = a.querySelector('.wp-menu-name')?.textContent?.trim() || a.textContent?.trim(); const href = a.getAttribute('href') || ''; console.log(i + '. ' + name + ' => ' + href); } }); ``` ### Passo 3: Mapear itens para seccoes Com a lista de itens nativos, mapear cada um para a seccao correcta. **Mapeamento standard por slug:** ``` # Admin index.php -> Admin users.php -> Admin options-general.php -> Admin plugins.php -> Admin tools.php -> Admin # Conteudo edit.php -> Conteudo (Artigos) edit.php?post_type=page -> Conteudo (Paginas) upload.php -> Conteudo (Multimedia) edit.php?post_type=acf-field-group -> Conteudo (ACF) edit.php?post_type=* -> Conteudo (CPTs) # Design elementor-home -> Design (Elementor) edit.php?post_type=elementor_library -> Design (Modelos) jkit -> Design (Jeg Kit) themes.php -> Design (Apresentacao) # Marketing fluentcrm-admin -> Marketing fluent_forms -> Marketing rank-math -> Marketing click-to-chat -> Marketing # Idiomas (se multilingue) mlang -> Idiomas (Polylang) loco -> Idiomas (Loco Translate) # Performance wpfastestcacheoptions -> Performance # Seguranca Wordfence -> Seguranca complianz -> Seguranca # WebMaster branding -> WebMaster (Branda) wpcode -> WebMaster (Code Snippets) # Hidden separator1, separator2, separator-last -> Hidden edit-comments.php -> Hidden hello-elementor -> Hidden elementor -> Hidden (duplicado de elementor-home) ``` **Itens que nao encaixam no standard:** Perguntar ao utilizador em que seccao colocar. Exemplos: WooCommerce, KiviCare, FareHarbor. ### Passo 4: Construir e guardar o menu Criar um PHP script no servidor e executar com `wp eval-file`: ```bash mcp__ssh-unified__ssh_execute server:"server" command:"cat > /tmp/branda-menu-SITE.php << 'PHPEOF' '', 'id_attribute' => '', 'css_classes' => '', 'icon_svg' => '', 'icon_url' => '', 'icon_image_id' => '', 'dashicon' => '', 'icon_type' => '', 'custom_url' => '', 'link_type' => '', 'link_target' => '', 'is_invisible' => '', 'is_hidden' => '', 'was_native' => 1, 'submenu' => \$subs ); } function hidden_item() { \$item = native_item(); \$item['is_hidden'] = '1'; return \$item; } function section_header(\$title, \$dashicon) { return array( 'icon_type' => 'dashicon', 'link_type' => 'none', 'submenu' => array(), 'title' => \$title, 'css_classes' => 'menu-highlight', 'dashicon' => \$dashicon ); } function custom_link(\$title, \$url, \$dashicon, \$target = '') { return array( 'icon_type' => 'dashicon', 'link_type' => 'custom', 'submenu' => array(), 'title' => \$title, 'dashicon' => \$dashicon, 'link_target' => \$target, 'custom_url' => \$url ); } // === BUILD MENU === \$menu = array(); // 1. SUPORTE \$menu['menu_item_sec_suporte'] = section_header('Suporte', 'dashicons-admin-tools'); \$menu['menu_item_desk_link'] = custom_link('Descomplicar', 'https://desk.descomplicar.pt/', 'dashicons-admin-comments', '_blank'); // 2. ADMIN \$menu['menu_item_sec_admin'] = section_header('Admin', 'dashicons-admin-generic'); \$menu[branda_id('index.php')] = native_item(); \$menu[branda_id('users.php')] = native_item(); \$menu[branda_id('options-general.php')] = native_item(); \$menu[branda_id('plugins.php')] = native_item(); \$menu[branda_id('tools.php')] = native_item(); // 3. CONTEUDO \$menu['menu_item_sec_conteudo'] = section_header('Conteudo', 'dashicons-admin-page'); \$menu[branda_id('edit.php?post_type=page')] = native_item(); // [INSERIR CPTs DO PROJECTO AQUI] \$menu[branda_id('edit.php')] = native_item(); \$menu[branda_id('upload.php')] = native_item(); \$menu[branda_id('edit.php?post_type=acf-field-group')] = native_item(); // 4. DESIGN \$menu['menu_item_sec_design'] = section_header('Design', 'dashicons-admin-appearance'); \$menu[branda_id('elementor-home')] = native_item(); \$menu[branda_id('edit.php?post_type=elementor_library')] = native_item(); // \$menu[branda_id('jkit')] = native_item(); // Se Jeg Kit activo \$menu[branda_id('themes.php')] = native_item(); // 5. MARKETING \$menu['menu_item_sec_marketing'] = section_header('Marketing', 'dashicons-megaphone'); \$menu[branda_id('fluentcrm-admin')] = native_item(); \$menu[branda_id('fluent_forms')] = native_item(); \$menu[branda_id('rank-math')] = native_item(); \$menu[branda_id('click-to-chat')] = native_item(); // 6. IDIOMAS (se multilingue) // \$menu['menu_item_sec_idiomas'] = section_header('Idiomas', 'dashicons-translation'); // \$menu[branda_id('mlang')] = native_item(); // \$menu[branda_id('loco')] = native_item(); // 7. PERFORMANCE \$menu['menu_item_sec_performance'] = section_header('Performance', 'dashicons-performance'); \$menu[branda_id('wpfastestcacheoptions')] = native_item(); // 8. SEGURANCA \$menu['menu_item_sec_seguranca'] = section_header('Seguranca', 'dashicons-shield'); \$menu[branda_id('Wordfence')] = native_item(); \$menu[branda_id('complianz')] = native_item(); // 9. WEBMASTER \$menu['menu_item_sec_webmaster'] = section_header('WebMaster', 'dashicons-admin-settings'); \$menu[branda_id('branding')] = native_item(); \$menu[branda_id('wpcode')] = native_item(); // HIDDEN \$menu[branda_id('separator1')] = hidden_item(); \$menu[branda_id('separator2')] = hidden_item(); \$menu[branda_id('separator-last')] = hidden_item(); \$menu[branda_id('edit-comments.php')] = hidden_item(); \$menu[branda_id('hello-elementor')] = hidden_item(); \$menu[branda_id('elementor')] = hidden_item(); // SAVE update_option('ub_custom_admin_menu', array('administrator' => \$menu)); wp_cache_flush(); \$visible = count(array_filter(\$menu, function(\$i) { return empty(\$i['is_hidden']); })); \$hidden = count(\$menu) - \$visible; echo \"Menu guardado: \$visible visiveis + \$hidden hidden = \" . count(\$menu) . \" total\n\"; PHPEOF echo 'Script criado' " # Executar mcp__ssh-unified__ssh_execute server:"server" command:"wp eval-file /tmp/branda-menu-SITE.php --allow-root --path=/home/USER/SITE" ``` ### Passo 5: Verificar ```bash # Contar itens no config mcp__ssh-unified__ssh_execute server:"server" command:"wp eval ' \$m = get_option(\"ub_custom_admin_menu\"); \$items = \$m[\"administrator\"]; echo \"Total: \" . count(\$items) . \"\n\"; foreach (\$items as \$key => \$item) { \$hidden = !empty(\$item[\"is_hidden\"]) ? \" [HIDDEN]\" : \"\"; \$type = !empty(\$item[\"was_native\"]) ? \"native\" : (!empty(\$item[\"link_type\"]) && \$item[\"link_type\"] == \"none\" ? \"header\" : \"custom\"); \$title = !empty(\$item[\"title\"]) ? \$item[\"title\"] : \"(native)\"; echo \"\$key => \$type: \$title\$hidden\n\"; } ' --allow-root --path=/home/USER/SITE" ``` Verificar visualmente no browser: navegar ao wp-admin e confirmar que o menu esta organizado nas 9 seccoes. ### Passo 6: Limpar ```bash # Remover script temporario mcp__ssh-unified__ssh_execute server:"server" command:"rm -f /tmp/branda-menu-SITE.php" # Limpar captura temporaria (se usou mu-plugin) mcp__ssh-unified__ssh_execute server:"server" command:"wp eval ' delete_option(\"_branda_menu_capture\"); delete_option(\"_branda_menu_captured\"); echo \"Limpo\"; ' --allow-root --path=/home/USER/SITE" # Garantir mu-plugin removido mcp__ssh-unified__ssh_execute server:"server" command:"rm -f /home/USER/SITE/wp-content/mu-plugins/capture-menu.php 2>/dev/null; echo ok" ``` --- ## Adaptar para sites especificos ### Sites multilingue (Polylang/WPML) Descomentar seccao Idiomas no script. Adicionar: ```php $menu['menu_item_sec_idiomas'] = section_header('Idiomas', 'dashicons-translation'); $menu[branda_id('mlang')] = native_item(); // Polylang $menu[branda_id('loco')] = native_item(); // Loco Translate ``` ### Sites WooCommerce Adicionar seccao e-Commerce entre Marketing e Idiomas: ```php $menu['menu_item_sec_ecommerce'] = section_header('e-Commerce', 'dashicons-cart'); $menu[branda_id('woocommerce')] = native_item(); // $menu[branda_id('wc-admin&path=/analytics/overview')] = native_item(); // Analytics WC ``` ### Sites Care (KiviCare) Adicionar seccao Clinica entre Marketing e Idiomas: ```php $menu['menu_item_sec_clinica'] = section_header('Clinica', 'dashicons-heart'); $menu[branda_id('kivicare-...')] = native_item(); // Verificar slug exacto ``` ### CPTs customizados Adicionar na seccao Conteudo, antes de Artigos: ```php $menu[branda_id('edit.php?post_type=SLUG_CPT')] = native_item(); ``` ### Itens desconhecidos Se o menu nativo tiver itens que nao encaixam no mapeamento standard: 1. Perguntar ao utilizador onde colocar 2. Se plugin temporario/teste: adicionar ao Hidden 3. Se plugin permanente: criar seccao propria ou adicionar a seccao existente --- ## Troubleshooting ### Menu nao muda apos guardar 1. Limpar cache: `wp cache flush --allow-root` 2. Verificar modulo activo: `admin/menu.php` em `ultimatebranding_activated_modules` 3. Verificar role: config usa key `administrator` (nao `admin`) ### Itens nativos aparecem fora das seccoes Causa: item nativo nao incluido no config (inserido na posicao default pelo parse_args_deep). Fix: Identificar o slug do item, computar o Branda ID, adicionar ao config (visivel ou hidden). ### Separadores nativos aparecem Causa: separadores com slugs diferentes dos standard (`separator1`, `separator2`, `separator-last`). Fix: Verificar slugs via captura do menu nativo, adicionar como hidden. ### Menu items custom aparecem em duplicado Causa: o mesmo item existe como custom link E como native item. Fix: Remover o custom link, manter apenas o native item na posicao correcta. ### CSS para esconder separadores em falta Adicionar ao CSS custom do Branda (ub_admin_css) como fallback: ```css #adminmenu li.wp-menu-separator { display: none !important; } ``` --- ## IDs de slugs comuns (referencia rapida) | Slug | Branda ID | |------|-----------| | `index.php` | `menu_item_42ho017e7jo0` | | `edit.php` | `menu_item_6lk5pbiakha0` | | `upload.php` | `menu_item_1jkdde99dfd0` | | `edit.php?post_type=page` | `menu_item_774p5endtlu0` | | `themes.php` | `menu_item_7jgmlsspgv60` | | `plugins.php` | `menu_item_5g2kqk93qi30` | | `users.php` | `menu_item_gajld83c8es0` | | `tools.php` | `menu_item_3t0no8pv5bfg` | | `options-general.php` | `menu_item_d1a8rsor9700` | | `edit-comments.php` | `menu_item_252pn6seih20` | | `separator1` | `menu_item_3u7nva84d1i0` | | `separator2` | `menu_item_6lm7mo14a4r0` | | `separator-last` | `menu_item_74g99t5jejn0` | | `elementor-home` | `menu_item_2kehh8g6nop0` | | `elementor` | `menu_item_27qkhd7iqao0` | | `edit.php?post_type=elementor_library` | `menu_item_3rubghs8krfg` | | `hello-elementor` | `menu_item_3q7v6ask7gpg` | | `fluentcrm-admin` | `menu_item_4a7t8bi9mt30` | | `fluent_forms` | `menu_item_ebai6etubd00` | | `rank-math` | `menu_item_148bl1t91os0` | | `click-to-chat` | `menu_item_1b65lubbpnd8` | | `Wordfence` | `menu_item_1077vi8mf9b0` | | `complianz` | `menu_item_5etmjgu9lnk0` | | `wpfastestcacheoptions` | `menu_item_4hqsn1kbum10` | | `branding` | `menu_item_7qde2b2f7670` | | `wpcode` | `menu_item_69igp9fj4tl0` | | `loco` | `menu_item_2c34vb7r1csg` | | `mlang` | `menu_item_24um676vv08g` | | `woocommerce` | Computar: `branda_id('woocommerce')` | | `edit.php?post_type=acf-field-group` | `menu_item_70ga2p32mc70` | Para slugs nao listados, computar com: ```bash wp eval 'echo "menu_item_" . substr(base_convert(md5("SLUG"), 16, 32), 0, 12);' --allow-root --path=/home/USER/SITE ``` --- ## Checklist - [ ] Branda activo e modulo `admin/menu.php` activo - [ ] Menu nativo extraido (todos os itens identificados) - [ ] Todos os itens nativos incluidos no config (visiveis ou hidden) - [ ] 9 seccoes criadas na ordem standard - [ ] CPTs do projecto na seccao Conteudo - [ ] Verificado visualmente no browser - [ ] Script temporario e capturas removidos - [ ] Permissoes corrigidas: `chown -R USER:USER /home/USER/SITE/` --- *Skill v1.0.0 | 26-02-2026 | Descomplicar*