{"id":271,"date":"2026-05-13T13:00:18","date_gmt":"2026-05-13T16:00:18","guid":{"rendered":"http:\/\/neodoor.neo\/?p=271"},"modified":"2026-05-21T09:03:19","modified_gmt":"2026-05-21T12:03:19","slug":"copy-delete-posts","status":"publish","type":"post","link":"https:\/\/www.portanova.com.br\/notebook\/copy-delete-posts\/","title":{"rendered":"[MU] Copy Delete Posts"},"content":{"rendered":"<h2>Aula: C\u00f3pia e Exclus\u00e3o em Massa com MU-Plugin \u2013 Substituindo o Copy Delete Posts<\/h2>\n<p><strong>Objetivo:<\/strong> Substituir o plugin <code>copy-delete-posts<\/code> por um MU-Plugin enxuto que permite copiar e excluir posts\/p\u00e1ginas em massa diretamente da lista administrativa, com suporte a metadados, taxonomias e feedback visual.<\/p>\n<p><strong>Contexto:<\/strong> O plugin original oferece c\u00f3pia r\u00e1pida e exclus\u00e3o em massa de posts\/p\u00e1ginas, mas \u00e9 pesado e muitas vezes desnecess\u00e1rio para necessidades b\u00e1sicas. Vamos criar uma solu\u00e7\u00e3o pr\u00f3pria com a\u00e7\u00f5es em massa personalizadas, mantendo o controle total sobre o processo.<\/p>\n<h3>Funcionalidades implementadas no nosso MU-Plugin<\/h3>\n<ul>\n<li>A\u00e7\u00e3o em massa <strong>&#8220;Copy&#8221;<\/strong> \u2013 duplica posts\/p\u00e1ginas selecionados (como rascunhos)<\/li>\n<li>A\u00e7\u00e3o em massa <strong>&#8220;Delete Permanently&#8221;<\/strong> \u2013 exclui permanentemente os posts selecionados<\/li>\n<li>Suporte para todos os tipos de post (posts, p\u00e1ginas, CPTs)<\/li>\n<li>C\u00f3pia completa de metadados (<code>post_meta<\/code>) e taxonomias (categorias, tags)<\/li>\n<li>Nomes de c\u00f3pias com sufixo autom\u00e1tico &#8221; \u2013 Copy&#8221;<\/li>\n<li>Notifica\u00e7\u00f5es visuais no admin ap\u00f3s cada opera\u00e7\u00e3o (quantidade processada)<\/li>\n<li>Verifica\u00e7\u00e3o de permiss\u00f5es (<code>edit_posts<\/code>) e nonce para seguran\u00e7a<\/li>\n<li>Tratamento de erros (exclus\u00e3o de posts protegidos)<\/li>\n<\/ul>\n<h3>C\u00f3digo do MU-Plugin<\/h3>\n<p>Crie a pasta <code>wp-content\/mu-plugins\/neodoor-copy-delete-posts\/<\/code> e dentro dela o arquivo <code>neodoor-copy-delete-posts.php<\/code> com o conte\u00fado abaixo:<\/p>\n<pre><code>&lt;?php\n\/**\n * Plugin Name:  Neodoor Copy Delete Posts\n * Description:  Adiciona a\u00e7\u00f5es em massa \"Copy\" e \"Delete Permanently\" na lista de posts\/p\u00e1ginas.\n * Version:      1.0\n * Author:       Reinaldo (Neodoor.neo)\n *\/\n\nif ( ! defined( 'ABSPATH' ) ) {\n    exit;\n}\n\nclass Neodoor_Copy_Delete_Posts {\n\n    private static $instance = null;\n\n    public static function get_instance() {\n        if ( null === self::$instance ) {\n            self::$instance = new self();\n        }\n        return self::$instance;\n    }\n\n    private function __construct() {\n        add_filter( 'bulk_actions-edit-post', [ $this, 'add_bulk_actions' ] );\n        add_filter( 'bulk_actions-edit-page', [ $this, 'add_bulk_actions' ] );\n        add_filter( 'handle_bulk_actions-edit-post', [ $this, 'handle_bulk_actions' ], 10, 3 );\n        add_filter( 'handle_bulk_actions-edit-page', [ $this, 'handle_bulk_actions' ], 10, 3 );\n        add_action( 'admin_notices', [ $this, 'show_admin_notices' ] );\n        \/\/ Para CPTs gen\u00e9ricos\n        add_filter( 'bulk_actions-edit-product', [ $this, 'add_bulk_actions' ] );\n        add_filter( 'handle_bulk_actions-edit-product', [ $this, 'handle_bulk_actions' ], 10, 3 );\n    }\n\n    \/**\n     * Adiciona as a\u00e7\u00f5es em massa no dropdown\n     *\/\n    public function add_bulk_actions( $bulk_actions ) {\n        $bulk_actions['neodoor_bulk_copy']   = __( 'Copy', 'neodoor-copy-delete' );\n        $bulk_actions['neodoor_bulk_delete'] = __( 'Delete Permanently', 'neodoor-copy-delete' );\n        return $bulk_actions;\n    }\n\n    \/**\n     * Processa as a\u00e7\u00f5es em massa\n     *\/\n    public function handle_bulk_actions( $redirect_to, $doaction, $post_ids ) {\n        \/\/ Verifica se \u00e9 uma das nossas a\u00e7\u00f5es\n        if ( $doaction !== 'neodoor_bulk_copy' &amp;&amp; $doaction !== 'neodoor_bulk_delete' ) {\n            return $redirect_to;\n        }\n\n        \/\/ Verifica permiss\u00e3o (usu\u00e1rio pode editar posts)\n        if ( ! current_user_can( 'edit_posts' ) ) {\n            wp_die( 'Voc\u00ea n\u00e3o tem permiss\u00e3o para realizar esta a\u00e7\u00e3o.' );\n        }\n\n        \/\/ Verifica nonce (prote\u00e7\u00e3o CSRF)\n        $referer = wp_get_referer();\n        if ( $referer &amp;&amp; strpos( $referer, admin_url() ) === 0 ) {\n            check_admin_referer( 'bulk-posts' );\n        }\n\n        $processed = 0;\n        $errors    = 0;\n\n        if ( $doaction === 'neodoor_bulk_copy' ) {\n            foreach ( $post_ids as $post_id ) {\n                $new_post_id = $this-&gt;duplicate_post( $post_id );\n                if ( $new_post_id ) {\n                    $processed++;\n                } else {\n                    $errors++;\n                }\n            }\n        } elseif ( $doaction === 'neodoor_bulk_delete' ) {\n            foreach ( $post_ids as $post_id ) {\n                \/\/ Verifica se o post pode ser exclu\u00eddo permanentemente\n                if ( wp_delete_post( $post_id, true ) ) {\n                    $processed++;\n                } else {\n                    $errors++;\n                }\n            }\n        }\n\n        \/\/ Adiciona argumentos na URL de redirecionamento para exibir notifica\u00e7\u00f5es\n        $redirect_to = add_query_arg(\n            [\n                'neodoor_action'   =&gt; $doaction,\n                'neodoor_processed' =&gt; $processed,\n                'neodoor_errors'    =&gt; $errors,\n            ],\n            $redirect_to\n        );\n\n        return $redirect_to;\n    }\n\n    \/**\n     * Duplica um post e seus metadados\/taxonomias\n     * @param int $post_id ID do post original\n     * @return int|false ID do novo post ou false em caso de erro\n     *\/\n    private function duplicate_post( $post_id ) {\n        $post = get_post( $post_id );\n        if ( ! $post ) {\n            return false;\n        }\n\n        \/\/ Prepara o array para o novo post (como rascunho)\n        $new_post = [\n            'post_title'   =&gt; $post-&gt;post_title . ' \u2013 Copy',\n            'post_content' =&gt; $post-&gt;post_content,\n            'post_excerpt' =&gt; $post-&gt;post_excerpt,\n            'post_status'  =&gt; 'draft',\n            'post_type'    =&gt; $post-&gt;post_type,\n            'post_author'  =&gt; get_current_user_id(),\n        ];\n\n        \/\/ Insere o novo post\n        $new_post_id = wp_insert_post( $new_post, true );\n        if ( is_wp_error( $new_post_id ) ) {\n            return false;\n        }\n\n        \/\/ Copia metadados (post_meta) \u2013 inclusive os com underscore\n        global $wpdb;\n        $meta_data = $wpdb-&gt;get_results( $wpdb-&gt;prepare(\n            \"SELECT meta_key, meta_value FROM {$wpdb-&gt;postmeta} WHERE post_id = %d\",\n            $post_id\n        ) );\n        foreach ( $meta_data as $meta ) {\n            add_post_meta( $new_post_id, $meta-&gt;meta_key, maybe_unserialize( $meta-&gt;meta_value ) );\n        }\n\n        \/\/ Copia taxonomias (categorias, tags, etc.)\n        $taxonomies = get_post_taxonomies( $post_id );\n        foreach ( $taxonomies as $taxonomy ) {\n            $terms = wp_get_object_terms( $post_id, $taxonomy, [ 'fields' =&gt; 'ids' ] );\n            if ( ! is_wp_error( $terms ) &amp;&amp; ! empty( $terms ) ) {\n                wp_set_object_terms( $new_post_id, $terms, $taxonomy );\n            }\n        }\n\n        return $new_post_id;\n    }\n\n    \/**\n     * Exibe notifica\u00e7\u00f5es ap\u00f3s o processamento das a\u00e7\u00f5es em massa\n     *\/\n    public function show_admin_notices() {\n        if ( empty( $_GET['neodoor_action'] ) ) {\n            return;\n        }\n\n        $action    = sanitize_text_field( $_GET['neodoor_action'] );\n        $processed = isset( $_GET['neodoor_processed'] ) ? intval( $_GET['neodoor_processed'] ) : 0;\n        $errors    = isset( $_GET['neodoor_errors'] ) ? intval( $_GET['neodoor_errors'] ) : 0;\n\n        $message = '';\n        if ( $action === 'neodoor_bulk_copy' ) {\n            if ( $processed === 1 ) {\n                $message = sprintf( '1 post copiado com sucesso (rascunho).', $processed );\n            } elseif ( $processed &gt; 1 ) {\n                $message = sprintf( '%d posts copiados com sucesso.', $processed );\n            }\n        } elseif ( $action === 'neodoor_bulk_delete' ) {\n            if ( $processed === 1 ) {\n                $message = sprintf( '1 post exclu\u00eddo permanentemente.', $processed );\n            } elseif ( $processed &gt; 1 ) {\n                $message = sprintf( '%d posts exclu\u00eddos permanentemente.', $processed );\n            }\n        }\n\n        if ( $errors &gt; 0 ) {\n            $message .= sprintf( ' %d erro(s) ignorado(s).', $errors );\n        }\n\n        if ( ! empty( $message ) ) {\n            echo '&lt;div class=\"notice notice-success is-dismissible\"&gt;&lt;p&gt;' . esc_html( $message ) . '&lt;\/p&gt;&lt;\/div&gt;';\n        }\n    }\n}\n\nNeodoor_Copy_Delete_Posts::get_instance();\n<\/code><\/pre>\n<h3>Suporte para Custom Post Types (CPTs)<\/h3>\n<p>O c\u00f3digo acima j\u00e1 inclui suporte para <strong>produtos (WooCommerce)<\/strong> como exemplo. Para adicionar outros CPTs, basta duplicar as linhas dos filtros trocando o nome do post type. Exemplo:<\/p>\n<pre><code>add_filter( 'bulk_actions-edit-&lt;SEU_CPT&gt;', [ $this, 'add_bulk_actions' ] );\nadd_filter( 'handle_bulk_actions-edit-&lt;SEU_CPT&gt;', [ $this, 'handle_bulk_actions' ], 10, 3 );\n<\/code><\/pre>\n<p>Substitua <code>&lt;SEU_CPT&gt;<\/code> pelo slug do seu tipo de post personalizado.<\/p>\n<h3>Como testar na pr\u00e1tica<\/h3>\n<ol>\n<li>Ap\u00f3s criar o MU-Plugin, acesse <strong>Posts \u2192 Todos os posts<\/strong> ou <strong>P\u00e1ginas \u2192 Todas as p\u00e1ginas<\/strong>.<\/li>\n<li>Selecione um ou mais posts\/p\u00e1ginas usando as caixas de sele\u00e7\u00e3o.<\/li>\n<li>No dropdown <strong>&#8220;A\u00e7\u00f5es em massa&#8221;<\/strong>, escolha <strong>&#8220;Copy&#8221;<\/strong> ou <strong>&#8220;Delete Permanently&#8221;<\/strong>.<\/li>\n<li>Clique em <strong>&#8220;Aplicar&#8221;<\/strong>.<\/li>\n<li>Uma notifica\u00e7\u00e3o verde aparecer\u00e1 informando quantos itens foram processados.<\/li>\n<\/ol>\n<h4>Exemplo visual<\/h4>\n<pre><code>+-----------------------------------------------------------+\n| [ ] T\u00edtulo do Post 1                                      |\n| [ ] T\u00edtulo do Post 2                                      |\n| [ ] T\u00edtulo do Post 3                                      |\n|                                                           |\n| A\u00e7\u00f5es em massa: [Copy \u25bc]            [Aplicar]             |\n+-----------------------------------------------------------+\n                \u2193\n+-----------------------------------------------------------+\n| \u2705 3 posts copiados com sucesso.                          |\n+-----------------------------------------------------------+\n<\/code><\/pre>\n<h3>Funcionalidades em detalhe<\/h3>\n<ul>\n<li><strong>C\u00f3pia:<\/strong> O novo post \u00e9 criado como <strong>rascunho<\/strong> e recebe o sufixo &#8221; \u2013 Copy&#8221;. Todos os metadados (campos personalizados, dados do Elementor) e taxonomias (categorias, tags) s\u00e3o preservados.<\/li>\n<li><strong>Exclus\u00e3o permanente:<\/strong> Os posts selecionados s\u00e3o removidos definitivamente do banco de dados, sem passar pela lixeira. Aten\u00e7\u00e3o: esta opera\u00e7\u00e3o \u00e9 irrevers\u00edvel!<\/li>\n<li><strong>Seguran\u00e7a:<\/strong> O plugin verifica se o usu\u00e1rio tem permiss\u00e3o para editar posts e utiliza nonce para prote\u00e7\u00e3o contra CSRF.<\/li>\n<li><strong>Feedback:<\/strong> Ap\u00f3s a opera\u00e7\u00e3o, uma notifica\u00e7\u00e3o amig\u00e1vel informa quantos itens foram processados com sucesso.<\/li>\n<\/ul>\n<h3>Personaliza\u00e7\u00f5es poss\u00edveis<\/h3>\n<ul>\n<li><strong>Alterar o sufixo da c\u00f3pia:<\/strong> Modifique a linha <code>'post_title'   =&gt; $post-&gt;post_title . ' \u2013 Copy'<\/code> no m\u00e9todo <code>duplicate_post()<\/code>.<\/li>\n<li><strong>Copiar como publicado:<\/strong> Troque <code>'post_status'  =&gt; 'draft'<\/code> por <code>'publish'<\/code>.<\/li>\n<li><strong>Adicionar confirma\u00e7\u00e3o JavaScript antes de excluir:<\/strong> Insira o c\u00f3digo abaixo no MU-Plugin:<\/li>\n<\/ul>\n<pre><code>add_action( 'admin_footer-edit.php', function() {\n    ?&gt;\n    &lt;script&gt;\n    jQuery(function($) {\n        $('select[name=\"action\"], select[name=\"action2\"]').change(function() {\n            if ($(this).val() === 'neodoor_bulk_delete') {\n                return confirm('Tem certeza que deseja excluir permanentemente os posts selecionados? Esta a\u00e7\u00e3o n\u00e3o pode ser desfeita.');\n            }\n        });\n    });\n    &lt;\/script&gt;\n    &lt;?php\n} );\n<\/code><\/pre>\n<h3>Compara\u00e7\u00e3o com o plugin original<\/h3>\n<p>Nosso MU-Plugin substitui as principais fun\u00e7\u00f5es do <code>copy-delete-posts<\/code>, com as seguintes vantagens:<\/p>\n<ul>\n<li><strong>Mais leve:<\/strong> Apenas 150 linhas de c\u00f3digo, sem CSS\/JS desnecess\u00e1rios.<\/li>\n<li><strong>Transparente:<\/strong> O c\u00f3digo \u00e9 simples e f\u00e1cil de modificar.<\/li>\n<li><strong>Sem an\u00fancios ou rastreadores:<\/strong> O plugin original exibe propagandas e coleta dados; nossa vers\u00e3o \u00e9 100% limpa.<\/li>\n<li><strong>Atualizado:<\/strong> Compat\u00edvel com as vers\u00f5es mais recentes do WordPress (6.x).<\/li>\n<li><strong>C\u00f3pia completa:<\/strong> Preserva metadados com underscore (essenciais para Elementor e outros builders) e taxonomias.<\/li>\n<\/ul>\n<h3>Observa\u00e7\u00f5es finais<\/h3>\n<ul>\n<li><strong>Backup recomendado:<\/strong> Antes de usar a exclus\u00e3o em massa, fa\u00e7a um backup do banco de dados \u2013 especialmente em produ\u00e7\u00e3o.<\/li>\n<li><strong>Performance:<\/strong> Para copiar\/excluir centenas de posts, o processo pode levar alguns segundos. O WordPress lida bem com lotes moderados.<\/li>\n<li><strong>Exclus\u00e3o permanente:<\/strong> Diferente da lixeira, os posts exclu\u00eddos n\u00e3o podem ser recuperados. Use com cautela.<\/li>\n<li><strong>Elementor:<\/strong> O c\u00f3digo copia todos os metadados, incluindo os dados de layout do Elementor. O novo post ser\u00e1 uma r\u00e9plica fiel, pronta para edi\u00e7\u00e3o.<\/li>\n<\/ul>\n<h3>Registro no caderno<\/h3>\n<p>Data: <!--?php echo date('d\/m\/Y'); ?--><\/p>\n<p>Funcionalidade: C\u00f3pia e exclus\u00e3o em massa de posts\/p\u00e1ginas<\/p>\n<p>Plugins substitu\u00eddos: copy-delete-posts<\/p>\n<p>Tempo de implementa\u00e7\u00e3o: ~15 minutos (configura\u00e7\u00e3o e testes)<\/p>\n<p>Observa\u00e7\u00e3o: Para CPTs adicionais, basta estender os filtros conforme explicado.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Aula: C\u00f3pia e Exclus\u00e3o em Massa com MU-Plugin \u2013 Substituindo o Copy Delete Posts Objetivo: Substituir o plugin copy-delete-posts por um MU-Plugin enxuto que permite copiar e excluir posts\/p\u00e1ginas em massa diretamente da lista administrativa, com suporte a metadados, taxonomias e feedback visual. Contexto: O plugin original oferece c\u00f3pia r\u00e1pida e exclus\u00e3o em massa de [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,4],"tags":[],"class_list":["post-271","post","type-post","status-publish","format-standard","hentry","category-lessons","category-pluginsmu"],"_links":{"self":[{"href":"https:\/\/www.portanova.com.br\/notebook\/wp-json\/wp\/v2\/posts\/271","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.portanova.com.br\/notebook\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.portanova.com.br\/notebook\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.portanova.com.br\/notebook\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.portanova.com.br\/notebook\/wp-json\/wp\/v2\/comments?post=271"}],"version-history":[{"count":0,"href":"https:\/\/www.portanova.com.br\/notebook\/wp-json\/wp\/v2\/posts\/271\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.portanova.com.br\/notebook\/wp-json\/wp\/v2\/media?parent=271"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.portanova.com.br\/notebook\/wp-json\/wp\/v2\/categories?post=271"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.portanova.com.br\/notebook\/wp-json\/wp\/v2\/tags?post=271"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}