Execução do saneamento de URLs no WordPress com WP-CLI

Depois de diagnosticar a poluição e definir a regra de reconstrução, chegou a parte que separa teoria de resultado: execução do saneamento de URLs no WordPress. Este artigo continua diretamente o case iniciado em Saneamento de URLs no WordPress, agora com foco total na Limpeza de URLs Hotmart com WP-CLI.

Aqui, o objetivo não é “rodar um comando e torcer”. É operar como engenharia: gerar comandos, auditar antes, executar em lote e só então validar. Isso reduz risco de corromper dados serializados e protege configurações de plugins como Rank Math e blocos do Gutenberg.

Você verá como usei scripts geradores para criar um arquivo .sh, executar centenas de substituições com WP-CLI e, depois, evoluir para uma reconstrução agressiva quando o banco mostrou anomalias reais de sintaxe.

E quando a limpeza termina, começa a parte mais importante: provar que o dado ficou limpo. Essa validação completa, com auditoria final, caches e impacto em BI e conversão, está em “Auditoria final e impacto“.

Execução da Limpeza (Round 1)

Com o diagnóstico feito e a lógica definida, chegamos à etapa de execução. No entanto, em operações de grande escala, não rodamos comandos destrutivos diretamente no banco de dados sem um plano de voo validado.

A estratégia adotada foi a criação de “Scripts Geradores”. Em vez de o PHP executar a limpeza, ele foi programado para escrever um arquivo de texto .sh (Shell Script) contendo a lista exata de comandos que deveriam ser rodados.

Essa camada intermediária é crucial para a segurança. Ela permite que revisemos cada linha de comando antes de autorizar a execução. O script PHP atua como o arquiteto que desenha a planta, e o script Bash é o engenheiro que executa a obra.

O coração dessa separação é a Expressão Regular (Regex) de captura. Precisávamos de uma instrução que dissesse ao computador: “Ignore o protocolo e o domínio, pegue apenas o ID do produto e isole todo o resto para análise”.

Utilizamos um padrão de regex agressivo para capturar a URL inteira, desde o http até encontrar um caractere delimitador (espaço, aspas ou sinal de maior/menor), garantindo que nada ficasse para trás.

Abaixo, o código do gerador que varre o banco e cria o arquivo de execução dinâmica:

<?php
/**
 * Gerador de Script de Limpeza (Round 1)
 * Gera um arquivo .sh com comandos WP-CLI para execução em lote.
 */

global $wpdb;

// Regex para capturar URL completa com sujeira
$regex = "/https?:\/\/[^\s\"'<]*hotmart\.com\/[a-zA-Z0-9]+[^\s\"'<]*/i";

$dirty_list = [];
$commands = [];

// Varredura nos posts
$posts = $wpdb->get_results("SELECT post_content FROM {$wpdb->posts} WHERE post_content LIKE '%hotmart.com%'");
foreach ($posts as $p) {
    if (preg_match_all($regex, $p->post_content, $matches)) {
        foreach ($matches[0] as $url) {
            $dirty_list[] = stripslashes($url); 
        }
    }
}

// Processamento da Lista
$dirty_list = array_unique($dirty_list);

foreach ($dirty_list as $dirty_url) {
    // Aplica o "Funil de Decisão" (Lógica descrita na Fase de Lógica do Algoritmo de Limpeza)
    // Se a URL suja for diferente da limpa, gera o comando
    if ($dirty_url !== $clean_url) {
        // Escapa aspas para o Bash e gera a linha de comando
        $safe_dirty = str_replace("'", "'\''", $dirty_url);
        $commands[] = "wp search-replace '$safe_dirty' '$clean_url' --all-tables";
    }
}

// Salva o arquivo final para auditoria e execução
file_put_contents('executar_limpeza.sh', implode("\n", $commands));
echo "Arquivo de lote gerado com " . count($commands) . " instruções.\n";

Após rodar este gerador, obtivemos o arquivo executar_limpeza.sh. Este arquivo serviu como um log prévio de alterações. Pude abrir o arquivo, ler as substituições propostas e confirmar se a lógica do checkoutMode estava sendo respeitada.

A execução final foi feita em lote via terminal (bash executar_limpeza.sh). O WP-CLI processou centenas de substituições sequenciais, tratando a serialização de dados do WordPress automaticamente e garantindo a integridade dos metadados.

Refinamento e Casos “Teimosos” (Round 2)

Mesmo após a execução em lote, a auditoria revelou que cerca de 30% das URLs ainda mantinham resíduos. A limpeza inicial, embora robusta, seguiu regras de sintaxe padrão. O problema é que o banco de dados continha erros de sintaxe.

Identificando resíduos persistentes

Encontramos o que chamo de “anomalias de concatenação”. Eram links onde o separador de parâmetros (o ponto de interrogação ?) estava ausente, fundindo o ID do produto com o rastreamento, gerando algo como ID123src=facebook.

Além disso, havia o problema do “Lixo HTML”. Em blocos do Gutenberg e Elementor, o final da URL frequentemente trazia códigos como u0022 ou u003c colados ao parâmetro de checkout, sobrevivendo ao primeiro filtro.

Mudança de Estratégia: Reconstrução Agressiva

Diante disso, mudamos a lógica. Paramos de tentar “limpar” a cauda da URL. Adotamos a “Reconstrução Agressiva”. O novo script ignora totalmente o que vem após o ID do produto.

A lógica passou a ser: capturar o ID alfanumérico e verificar se a string checkoutMode existe em qualquer lugar da sujeira subsequente.

Se existir, recriamos a URL do zero: Base + ID + ?checkoutMode=10. Se não existir, a URL morre no ID. Isso garante que erros de digitação, falta de separadores ou lixo de código sejam sumariamente eliminados.

Abaixo, a implementação dessa lógica de reconstrução baseada no ID:

// Trecho do Script de Reconstrução Agressiva
// Regex captura o ID (Grupo 1) e isola todo o resto (Grupo 2)
$regex_capture = "/https?:\/\/[^\/]*hotmart\.com\/([a-zA-Z0-9]+)(.*)/i";

if (preg_match($regex_capture, $dirty_url, $parts)) {
    $id = $parts[1];   // O ID puro (ex: Q92999519K)
    $tail = $parts[2]; // O lixo (ex: src=fb&checkoutMode=10u0022...)
    
    // Tratamento para IDs "contaminados" sem separador (ex: IDsrc=...)
    $stop_words = ['src', 'utm', 'sck', '?'];
    foreach ($stop_words as $word) {
        $pos = stripos($id, $word);
        if ($pos !== false) {
            $id = substr($id, 0, $pos); // Corta o ID antes da contaminação
        }
    }

    // Reconstrói a URL limpa baseada apenas na intenção
    $base = "https://go.hotmart.com/" . $id;

    // Se checkoutMode existir em qualquer lugar do lixo, ele é recriado limpo
    if (stripos($dirty_url, 'checkoutmode=10') !== false) {
        $clean_url = $base . "?checkoutMode=10";
    } else {
        $clean_url = $base;
    }
}

O Script Localizador: Rastreando o “Paciente Zero”

Para os casos onde nem a reconstrução automática funcionou, precisamos entender o contexto. Criamos um “Script Localizador” que não apenas listava o erro, mas apontava sua localização exata: ID do Post e Chave Meta.

Isso nos permitiu descobrir se o erro estava no conteúdo visível ou escondido em um campo de Schema (Rank Math) ou configuração de bloco (GenerateBlocks). Identificar o “Paciente Zero” é vital para decidir entre uma correção manual pontual ou um novo script de lote.

// Trecho do Localizador de Resíduos
// Varre o banco e aponta exatamente onde o link sujo está hospedado
foreach ($posts as $p) {
    if (preg_match_all($regex, $p->post_content, $matches)) {
        foreach ($matches[0] as $url) {
            if (is_dirty($url)) {
                echo "\nURL Suja: " . $url . "\n";
                echo "Local: Arquivo ID " . $p->ID . " (" . get_permalink($p->ID) . ")\n";
            }
        }
    }
}

Após a execução do saneamento de URLs no WordPress

Após a execução em lote, o resultado técnico mais importante não é “reduzir variações”. É garantir que o WordPress continue íntegro: metadados funcionais, blocos preservados e URLs padronizadas sem efeitos colaterais.

Mas execução não encerra o trabalho. Em engenharia de dados, o script que roda não é o fim, é o começo da pergunta: como provar que não sobrou resíduo?

No próximo artigo, eu mostro a auditoria pós-limpeza, o checklist de pós-operatório (cache, transients e CSS) e como traduzir esse saneamento em dados confiáveis para análise e decisão.

Deixe um comentário