Cobre: auditoria de ligacao/licencas por site, ligar sites a conta FULL, reactivacao de plugins premium, e diagnostico de licencas fora da FULL (fabricante directo, CodeCanyon). Baseada em leitura directa do codigo do plugin full-customer v4.1.3 durante sessao real de auditoria da frota WP.
192 lines
9.3 KiB
Markdown
192 lines
9.3 KiB
Markdown
---
|
|
name: full-cliente
|
|
description: Gestão do plugin FULL.Cliente (broker de licenças WordPress premium, api.full.services) via WP-CLI no servidor CWP. Cobre auditoria de ligação e licenças por site, ligar/desligar sites à conta FULL, reactivação de plugins premium (Elementor Pro, Elements Kit, Rank Math Pro, etc.), e diagnóstico de licenças que NÃO passam pela FULL (fabricante directo, CodeCanyon). Usar quando "full cliente", "full.services", "plugin premium sem licença", "elementor pro expired", "ligar site à full", "reactivar licença", "quantos sites têm licença full", "quem consome esta licença".
|
|
---
|
|
|
|
# /full-cliente — Broker de licenças WordPress premium
|
|
|
|
O plugin `full-customer` (nome visível "FULL.Cliente") está instalado em cada
|
|
site WP da frota. Substitui o updater nativo de cada plugin premium que gere
|
|
por um Plugin Update Checker apontado ao servidor da FULL, e serve como
|
|
corretor de licenças compradas pela Descomplicar® — uma conta partilhada
|
|
(`it@descomplicar.pt`) por toda a frota.
|
|
|
|
**Fonte:** código lido directamente em `wp-content/plugins/full-customer/`
|
|
(v4.1.3), servidor `server.descomplicar.pt` (SSH porta 9443). Namespace REST
|
|
local: `fc/v1`. API externa: `https://api.full.services/wp-json/fc-ai`.
|
|
|
|
---
|
|
|
|
## Regra de ouro: "geridos" ≠ "comprado"
|
|
|
|
Duas fontes dizem coisas diferentes, e as duas são reais:
|
|
|
|
| Endpoint | O que mostra | Quando usar |
|
|
|---|---|---|
|
|
| `plugin-repository/updates` | Plugins com o updater ACTUALMENTE substituído pela FULL neste site | "Este plugin está a receber updates pela FULL agora?" |
|
|
| `account/licenses-extract` | Ficha real de licenças da conta: comprado/disponível/domínios em uso, por produto | "Esta licença é da FULL?" — **fonte de verdade** |
|
|
|
|
**Nunca concluir "não é FULL" só pela ausência em `updates`.** Um produto pode
|
|
estar comprado e a consumir licença em vários domínios (`licenses-extract`) e
|
|
mesmo assim estar fora do catálogo activo (`plugin-repository/all`) — o mesmo
|
|
padrão do `elementor-pro` `expired`/"retirado dos geridos". Verificar sempre
|
|
as duas antes de classificar algo como "fora da FULL". (Erro cometido e
|
|
corrigido em sessão real: "Elements Kit" tinha 13 licenças compradas, 2
|
|
disponíveis, mas estava fora do catálogo activo — parecia "sem FULL" olhando
|
|
só para `updates`.)
|
|
|
|
---
|
|
|
|
## Consultas (leitura livre, sem GATE) — sempre via `wp eval-file` seguro
|
|
|
|
Aspas aninhadas partem o PHP através de ssh. Usar sempre base64 no stdin:
|
|
|
|
```bash
|
|
B64=$(printf '%s' '<?php
|
|
// codigo PHP aqui, so aspas simples PHP normais
|
|
' | base64 -w0)
|
|
timeout 200 ssh -p 9443 -o BatchMode=yes root@server.descomplicar.pt \
|
|
"P=/opt/alt/php-fpm82/usr/bin/php; W=/usr/local/bin/wp
|
|
echo $B64 | base64 -d | \$P -d memory_limit=512M -d error_reporting=0 \$W eval-file - --allow-root --path=/home/USER/SITE 2>&1"
|
|
```
|
|
|
|
### O site está ligado?
|
|
|
|
```php
|
|
<?php
|
|
global $wpdb;
|
|
$r = $wpdb->get_row("SELECT user_id, meta_value FROM {$wpdb->usermeta} WHERE meta_key='fc/connection-email' AND meta_value!='' LIMIT 1", ARRAY_A);
|
|
echo $r ? "ligado: {$r['meta_value']} (user {$r['user_id']})" : "NAO LIGADO";
|
|
```
|
|
|
|
Atalho sem PHP: `wp option get _full_customer-connection_email --allow-root --path=$S`
|
|
— só devolve valor se o site ainda não migrou (ver secção "Porque é que um
|
|
site pode não estar ligado").
|
|
|
|
### Extracto de licenças da conta (fonte de verdade)
|
|
|
|
```php
|
|
<?php
|
|
$x = fcDashboardAPI("GET", "account/licenses-extract");
|
|
$items = isset($x["data"]["name"]) ? [$x["data"]] : ($x["data"] ?? []);
|
|
foreach ($items as $v) {
|
|
echo $v["name"] . " | comprado=" . $v["purchased"] . " | disponivel=" . $v["available"]
|
|
. " | dominios=" . implode(", ", $v["domainsInUse"] ?? []) . "\n";
|
|
}
|
|
```
|
|
|
|
Requer o site já estar ligado (usa o token do site). Corre em qualquer site
|
|
ligado, não só na conta de referência.
|
|
|
|
Para contar sites únicos com licença activa: agregar `domainsInUse` de todos
|
|
os produtos, ignorar entradas `"Site excluído"`, desduplicar por domínio.
|
|
|
|
### Catálogo activo vs geridos neste site
|
|
|
|
```php
|
|
<?php
|
|
$catalogo = fcDashboardAPI("GET", "plugin-repository/all")["data"] ?? [];
|
|
$geridos = fcDashboardAPI("GET", "plugin-repository/updates")["data"] ?? [];
|
|
echo "catalogo activo: " . count($catalogo) . " | geridos neste site: " . count($geridos) . "\n";
|
|
```
|
|
|
|
---
|
|
|
|
## Ligar um site à conta FULL (grátis — não consome licença)
|
|
|
|
Mecanismo real (`Actions/ConnectAccount.php::handleConnection`): regista o par
|
|
(email, site) no servidor FULL via `account/connect`; só se aceite grava
|
|
`fc/connection-email` no usermeta do utilizador WP que executa a acção.
|
|
|
|
**Pré-requisitos:**
|
|
- GATE 5.1 — mutação em produção. Pedir autorização
|
|
(`~/.claude/hooks/infra-auth.sh server`, válida 60 min) antes de escrever.
|
|
- Um utilizador admin local (`manage_options`). Preferir o `it@descomplicar.pt`
|
|
local se existir (fica auditável).
|
|
|
|
```bash
|
|
B64=$(printf '%s' '<?php
|
|
$ca = new \FC\Actions\ConnectAccount();
|
|
$result = $ca->handleConnection("it@descomplicar.pt");
|
|
echo json_encode($result) . "\n";
|
|
echo "isConnected: " . (\FC\User::instance()->isConnected() ? "yes" : "no") . "\n";
|
|
' | base64 -w0)
|
|
# --user=<ID do admin> e OBRIGATORIO: sem ele User::instance() nao esta
|
|
# "logged in" e o setMeta() silencia sem gravar nada (ver Erros Comuns).
|
|
echo $B64 | base64 -d | php -d memory_limit=512M wp eval-file - --user=<ID> --allow-root --path=$S
|
|
```
|
|
|
|
**Verificar sempre de forma independente**, fora da sessão do eval:
|
|
`wp user meta get <ID> fc/connection-email --allow-root --path=$S`. Não
|
|
confiar só no `success:true` da resposta.
|
|
|
|
### Porque é que um site "v4.1.3" pode não estar ligado
|
|
|
|
`Services/Connection.php::autoConnection()` corre em cada `admin_init` e tenta
|
|
migrar sozinho: lê o option legado `_full_customer-connection_email`; se tiver
|
|
valor, liga e apaga o legado (`connectionMode = 'upgrade'`). **Se esse option
|
|
já estava vazio antes da actualização do plugin, nunca houve nada para
|
|
migrar** — o site nunca esteve ligado, nem na geração antiga. Confirmar com
|
|
`wp option get _full_customer-connection_email` (vazio = nunca ligado).
|
|
|
|
---
|
|
|
|
## Reactivar a licença de um plugin (CUSTA — consome licença e abre ticket)
|
|
|
|
**Nunca fazer sem autorização explícita.** Cada `reactivate` consome uma
|
|
licença disponível e abre um ticket Zendesk do lado da FULL, que fica em fila
|
|
— não é instantâneo.
|
|
|
|
Fluxo (`Actions/PluginReactivate.php`):
|
|
1. `plugin-repository/<slug>/reactivate` — consome a licença, abre ticket;
|
|
2. `plugin-repository/<slug>/license` — busca a chave;
|
|
3. `LocalLicenseProcessor::process($slug, $license, $step, $state)` — aplica
|
|
localmente (multi-step para alguns);
|
|
4. `plugin-repository/<slug>/license/confirm` + `do_action('fc/updates/invalidate')`.
|
|
|
|
**Processador por produto (`Services/LocalLicenseProcessor.php`) — nem todos
|
|
são one-shot:**
|
|
|
|
| Produto (slug) | Fluxo |
|
|
|---|---|
|
|
| `elementorPro` | **OAuth multi-step** com a Elementor: `fetch_consent` → `submit_consent` → `finalize`, cookies de sessão entre passos. Não dá por WP-CLI puro. |
|
|
| `essentialAddons` | OTP (`check_otp`) |
|
|
| `astra`, `rankMath`, `seoPress`, `perfmatters`, `acfPRO`, `wpRocket`, `jetThemeCore`, `ultimateAddons` | Uma chamada, sem steps |
|
|
|
|
---
|
|
|
|
## Não existe forma de libertar uma licença de um domínio
|
|
|
|
Procurado exaustivamente no código (`grep -rniE "release|remove.?domain|deactivate.?domain|revoke|free.?license"`)
|
|
— **zero mecanismo**. `Actions/DisconnectAccount.php` só desliga a ligação
|
|
LOCAL (`setConnectionEmail('')`); o próprio texto confirma: "suas ativações e
|
|
licenças seguem funcionando normalmente". As únicas acções por produto
|
|
(`PluginActivationManager.php`) são "Reativar", "Renovar" e "Solicitar ajuda"
|
|
— e "ajuda" é acção de frontend, sem rota REST própria. Para libertar um
|
|
domínio (ex.: site descomissionado que ainda consome uma licença comprada), a
|
|
única via é abrir um pedido de suporte à FULL — não há atalho técnico.
|
|
|
|
---
|
|
|
|
## Licenças que NÃO são da FULL (não confundir)
|
|
|
|
| Fonte | Como identificar | Exemplo real |
|
|
|---|---|---|
|
|
| **Fabricante directo** | Option própria com `license_key`, formato do produto (não FULL) | `elementskit_options.license_key` (WPMET), `_ff_fluentform_pro_license_key` (WPManageNinja) |
|
|
| **CodeCanyon (compra única)** | Zero option de licença, zero `envato`/`purchase_code` no código — ficheiro entregue sem phone-home, por desenho | `kivicare-pro` |
|
|
|
|
Confirmar sempre as duas possibilidades (FULL via `licenses-extract` E licença
|
|
própria do fabricante) antes de classificar um plugin como "sem licença" — um
|
|
produto pode estar em ambas, nenhuma, ou só numa.
|
|
|
|
---
|
|
|
|
## Erros comuns
|
|
|
|
| Sintoma | Causa | Solução |
|
|
|---|---|---|
|
|
| "X não é FULL" (conclusão errada) | Só verificado em `plugin-repository/updates`, faltou `account/licenses-extract` | Verificar sempre as duas fontes (ver "Regra de ouro") |
|
|
| `ConnectAccount` devolve `success:true` mas usermeta continua vazio | Faltou `--user=<ID>` no `wp eval-file` — `User::instance()` não estava "logged in", `setMeta()` silencia | Sempre passar `--user=<ID admin>` |
|
|
| Erro de sintaxe PHP no `eval-file` | Aspas duplas aninhadas via ssh | Base64 no stdin (ver topo desta skill) |
|
|
| Domínio "morto" que na verdade está vivo | DNS aponta para o servidor mas é conta partilhada com outros sites | Confirmar `siteurl` real (`wp option get siteurl`) antes de declarar morto — nunca só presença/ausência de pasta |
|