[MU] Duplicate Page

Aula: Duplicar Páginas/Posts com um Clique – Substituindo o Plugin Duplicate Page

Objetivo: Substituir o plugin duplicate-page por um MU-Plugin leve, que adiciona um link “Duplicate” (ou “Clonar”) ao lado de cada post, página ou custom post type, permitindo duplicar o conteúdo com um único clique, mantendo metadados, taxonomias e status de rascunho.

Contexto: O plugin original é útil para criar cópias rápidas sem precisar de ações em massa. No entanto, é mais um plugin externo que pode ser substituído por poucas linhas de código, ganhando performance e reduzindo dependências. Nosso MU-Plugin fará exatamente isso: adicionar uma ação customizada na linha de cada post.

Funcionalidades implementadas no nosso MU-Plugin

  • Link “Duplicate” (Clonar) nos posts, páginas e CPTs configuráveis.
  • Redirecionamento automático para a lista de posts após a duplicação, com mensagem de sucesso.
  • Cópia completa de todos os metadados (post_meta), inclusive os que começam com underscore (usados por Elementor, ACF, etc.).
  • Cópia de taxonomias (categorias, tags, termos personalizados).
  • Novo post criado como rascunho, com título original acrescido de ” – Cópia”.
  • Verificação de permissão (usuário precisa ter capacidade de editar posts).
  • Proteção contra ataques CSRF (nonce).
  • Notificação verde exibida no admin após a duplicação.

Código do MU-Plugin

Crie a pasta wp-content/mu-plugins/neodoor-duplicate-page/ e dentro dela o arquivo neodoor-duplicate-page.php com o conteúdo abaixo:

<?php
/**
 * Plugin Name:  Neodoor Duplicate Page
 * Description:  Adiciona link "Duplicate" (Clonar) na lista de posts, páginas e CPTs para duplicar conteúdo rapidamente.
 * Version:      1.0
 * Author:       Reinaldo (Neodoor.neo)
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Neodoor_Duplicate_Page {

    private static $instance = null;

    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        add_action( 'admin_action_neodoor_duplicate_post', [ $this, 'handle_duplicate_action' ] );
        add_filter( 'post_row_actions', [ $this, 'add_duplicate_link' ], 10, 2 );
        add_filter( 'page_row_actions', [ $this, 'add_duplicate_link' ], 10, 2 );
        add_action( 'admin_notices', [ $this, 'show_duplicate_notice' ] );
        // Para CPTs, o mesmo filtro funciona (post_row_actions cobre todos os tipos)
    }

    /**
     * Adiciona o link "Duplicate" na coluna de ações da lista administrativa
     */
    public function add_duplicate_link( $actions, $post ) {
        // Verifica se o usuário tem permissão para editar posts
        if ( ! current_user_can( 'edit_posts' ) ) {
            return $actions;
        }

        // Gera URL de duplicação com nonce para segurança
        $url = wp_nonce_url(
            admin_url( 'admin.php?action=neodoor_duplicate_post&post=' . $post->ID ),
            'neodoor_duplicate_post_' . $post->ID,
            'neodoor_nonce'
        );

        $actions['neodoor_duplicate'] = '<a href="' . esc_url( $url ) . '" title="Clonar este item">Duplicar</a>';
        return $actions;
    }

    /**
     * Processa a ação de duplicação
     */
    public function handle_duplicate_action() {
        // Verifica se o post ID foi informado
        if ( empty( $_GET['post'] ) ) {
            wp_die( 'ID do post não informado.' );
        }

        $post_id = intval( $_GET['post'] );
        $nonce   = isset( $_GET['neodoor_nonce'] ) ? sanitize_text_field( $_GET['neodoor_nonce'] ) : '';

        // Verifica nonce
        if ( ! wp_verify_nonce( $nonce, 'neodoor_duplicate_post_' . $post_id ) ) {
            wp_die( 'Erro de segurança: nonce inválido. Recarregue a página e tente novamente.' );
        }

        // Verifica permissão
        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_die( 'Você não tem permissão para duplicar conteúdos.' );
        }

        // Executa a duplicação
        $new_post_id = $this->duplicate_post( $post_id );
        if ( ! $new_post_id || is_wp_error( $new_post_id ) ) {
            wp_die( 'Erro ao duplicar o post. Verifique o log de erros.' );
        }

        // Redireciona de volta para a lista de posts do tipo correspondente, com mensagem de sucesso
        $post_type = get_post_type( $post_id );
        $redirect_url = add_query_arg(
            [ 'post_type' => $post_type, 'neodoor_duplicated' => $new_post_id ],
            admin_url( 'edit.php' )
        );
        wp_safe_redirect( $redirect_url );
        exit;
    }

    /**
     * Função principal de duplicação
     * @param int $post_id ID do post original
     * @return int|false ID do novo post ou false em caso de erro
     */
    private function duplicate_post( $post_id ) {
        $post = get_post( $post_id );
        if ( ! $post ) {
            return false;
        }

        // Prepara o array para o novo post (como rascunho)
        $new_post = [
            'post_title'   => $post->post_title . ' – Cópia',
            'post_content' => $post->post_content,
            'post_excerpt' => $post->post_excerpt,
            'post_status'  => 'draft',
            'post_type'    => $post->post_type,
            'post_author'  => get_current_user_id(),
        ];

        // Insere o novo post
        $new_post_id = wp_insert_post( $new_post, true );
        if ( is_wp_error( $new_post_id ) ) {
            return false;
        }

        // Copia metadados (post_meta) – inclusive os com underscore
        global $wpdb;
        $meta_data = $wpdb->get_results( $wpdb->prepare(
            "SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d",
            $post_id
        ) );
        foreach ( $meta_data as $meta ) {
            add_post_meta( $new_post_id, $meta->meta_key, maybe_unserialize( $meta->meta_value ) );
        }

        // Copia taxonomias (categorias, tags, etc.)
        $taxonomies = get_post_taxonomies( $post_id );
        foreach ( $taxonomies as $taxonomy ) {
            $terms = wp_get_object_terms( $post_id, $taxonomy, [ 'fields' => 'ids' ] );
            if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
                wp_set_object_terms( $new_post_id, $terms, $taxonomy );
            }
        }

        return $new_post_id;
    }

    /**
     * Exibe notificação de sucesso após duplicação
     */
    public function show_duplicate_notice() {
        if ( isset( $_GET['neodoor_duplicated'] ) ) {
            $post_id = intval( $_GET['neodoor_duplicated'] );
            $post_title = get_the_title( $post_id );
            echo '<div class="notice notice-success is-dismissible"><p>' .
                 esc_html( sprintf( 'Item duplicado com sucesso: "%s" (rascunho).', $post_title ) ) .
                 '</p></div>';
        }
    }
}

Neodoor_Duplicate_Page::get_instance();

Como testar na prática

  1. Após criar o MU-Plugin, acesse Posts → Todos os posts ou Páginas → Todas as páginas.
  2. Passe o mouse sobre qualquer título de post/página.
  3. Você verá o link “Duplicar” ao lado de Editar, Lixeira, Visualizar, etc.
  4. Clique em “Duplicar”.
  5. Uma nova página será recarregada e você verá uma notificação verde: “Item duplicado com sucesso: ‘Título do Post – Cópia’ (rascunho)”.
  6. O novo post aparecerá na lista com status Rascunho e título original acrescido de ” – Cópia”.

Exemplo visual (antes/depois)

Lista de posts (antes):
+------------------------------------------------+
| Título Exemplo          | Editar | Lixeira   |
+------------------------------------------------+

Após passar o mouse:
+------------------------------------------------+
| Título Exemplo          | Editar | Duplicar | Lixeira |
+------------------------------------------------+

Clique em Duplicar → notificação verde:
✅ Item duplicado com sucesso: "Título Exemplo – Cópia" (rascunho).

Suporte para Custom Post Types (CPTs)

O filtro post_row_actions funciona automaticamente para todos os tipos de post que usam a lista padrão do WordPress (incluindo CPTs como produto, aula, etc.). Nenhuma configuração extra é necessária. Se quiser restringir a apenas alguns tipos, adicione uma verificação no método add_duplicate_link:

$allowed_types = [ 'post', 'page', 'meu_cpt' ];
if ( ! in_array( $post->post_type, $allowed_types ) ) {
    return $actions;
}

Personalizações possíveis

  • Alterar o texto do link: Mude 'Duplicar' no array de actions para o texto desejado (ex: “Clonar”, “Copiar”).
  • Alterar o sufixo do título: Na linha 'post_title' => $post->post_title . ' – Cópia', modifique o sufixo.
  • Copiar como publicado: Troque 'post_status' => 'draft' por 'publish'. Cuidado: isso tornará a cópia visível imediatamente.
  • Adicionar confirmação antes de duplicar: Inclua um pequeno trecho JavaScript no admin:
add_action( 'admin_footer', function() {
    ?>
    <script>
    jQuery(document).ready(function($) {
        $('a[href*="action=neodoor_duplicate_post"]').click(function(e) {
            if ( ! confirm('Tem certeza que deseja duplicar este item?') ) {
                e.preventDefault();
            }
        });
    });
    </script>
    <?php
} );

Comparação com o plugin original

  • Sem configurações desnecessárias: O plugin original tem telas de opções, versão pro, anúncios. Nosso MU-Plugin é direto ao ponto.
  • Leve e rápido: Cerca de 120 linhas de código, sem CSS ou JS extras.
  • Sem rastreadores ou chamadas externas: Código 100% limpo, sem conexões a terceiros.
  • Totalmente compatível com Elementor, ACF, Yoast e outros: Copia todos os metadados, incluindo os que começam com underscore.

Observações finais

  • Permissões: O link só aparece para usuários que podem editar posts (normalmente administradores, editores e autores).
  • Segurança: O nonce garante que a ação só possa ser executada uma vez por requisição legítima.
  • Performance: Duplicar um post com muitos metadados (ex: páginas do Elementor) pode levar alguns segundos, mas funciona bem.
  • Redirecionamento inteligente: Após duplicar, você é levado de volta à lista de posts do mesmo tipo (post, página, CPT), mantendo o contexto.

Possíveis problemas e soluções

  • O link “Duplicar” não aparece: Verifique se você está logado com um papel que tenha permissão edit_posts (administrador ou editor).
  • Erro de nonce inválido: Tente recarregar a página de listagem e clique novamente. Nonces expiram após 12-24 horas.
  • Não copia imagens destacadas: O código copia todos os metadados, incluindo _thumbnail_id, portanto a imagem destacada é preservada. Se não estiver funcionando, verifique se o campo _thumbnail_id foi copiado corretamente.

Registro no caderno

Data:

Funcionalidade: Duplicação com um clique na lista administrativa

Plugins substituídos: duplicate-page (também conhecido como “Duplicate Page” de mndpsingh287)

Tempo de implementação: ~10 minutos (criação do arquivo e testes)

Observação: Para remover o link de tipos específicos, utilize a verificação com in_array conforme exemplo acima.

✅ Voto computado! Obrigado.

Média: 3.1 (43 votos)