Plugin Directory

Changeset 3424368


Ignore:
Timestamp:
12/20/2025 08:17:54 PM (6 weeks ago)
Author:
andremoura
Message:

Update to version 1.2 from GitHub

Location:
easy-bulk-date-editor
Files:
2 added
6 edited
1 copied

Legend:

Unmodified
Added
Removed
  • easy-bulk-date-editor/tags/1.2/bde.js

    r3415608 r3424368  
    11(function($){
     2    var EasyBDEState = {
     3        filter_by: 'category',
     4        filter_val: 0,
     5        paged: 1,
     6        max_num_pages: 1
     7    };
     8
    29    function renderTable(rows){
    310        if(!rows || rows.length === 0){
    411            $('#easybde_controls').hide();
    5             $('#easybde_table_wrap').html('<p>'+EasyBDEData.i18n.no_posts+'</p>');
     12            $('#easybde_table_wrap').html(renderPagination() + '<p>'+EasyBDEData.i18n.no_posts+'</p>' + renderPagination());
    613            return;
    714        }
    815        $('#easybde_controls').show();
    916        var html = [];
     17        html.push(renderPagination());
    1018        html.push('<form id="easybde_form"><table class="wp-list-table widefat fixed striped"><thead><tr>');
    1119        html.push('<th style="width:30px"><input type="checkbox" id="easybde_select_all" /></th>');
     
    2129        });
    2230        html.push('</tbody></table><p><button id="easybde_save" class="button button-primary">Save changes</button> <span id="easybde_result" style="margin-left:10px"></span></p></form>');
     31        html.push(renderPagination());
    2332        $('#easybde_table_wrap').html(html.join(''));
     33    }
     34
     35    function renderPagination(){
     36        var paged = EasyBDEState.paged || 1;
     37        var max   = EasyBDEState.max_num_pages || 1;
     38        if(max <= 1){
     39            return '';
     40        }
     41
     42        var html = [];
     43        html.push('<div class="easybde_pagination" style="margin:12px 0; display:flex; gap:8px; align-items:center; flex-wrap:wrap;">');
     44        html.push('<button type="button" class="button easybde_page" data-page="' + Math.max(1, paged - 1) + '" ' + (paged <= 1 ? 'disabled' : '') + '>&laquo; Prev</button>');
     45
     46        // Compact page range around current (max 7 buttons)
     47        var start = Math.max(1, paged - 3);
     48        var end   = Math.min(max, paged + 3);
     49        if(end - start < 6){
     50            start = Math.max(1, end - 6);
     51            end   = Math.min(max, start + 6);
     52        }
     53
     54        if(start > 1){
     55            html.push('<button type="button" class="button easybde_page" data-page="1">1</button>');
     56            if(start > 2){
     57                html.push('<span style="padding:0 4px;">…</span>');
     58            }
     59        }
     60
     61        for(var p = start; p <= end; p++){
     62            if(p === paged){
     63                html.push('<span class="button button-primary" style="cursor:default;">' + p + '</span>');
     64            } else {
     65                html.push('<button type="button" class="button easybde_page" data-page="' + p + '">' + p + '</button>');
     66            }
     67        }
     68
     69        if(end < max){
     70            if(end < max - 1){
     71                html.push('<span style="padding:0 4px;">…</span>');
     72            }
     73            html.push('<button type="button" class="button easybde_page" data-page="' + max + '">' + max + '</button>');
     74        }
     75
     76        html.push('<button type="button" class="button easybde_page" data-page="' + Math.min(max, paged + 1) + '" ' + (paged >= max ? 'disabled' : '') + '>Next &raquo;</button>');
     77        html.push('<span style="margin-left:6px; opacity:.8;">Page ' + paged + ' of ' + max + '</span>');
     78        html.push('</div>');
     79        return html.join('');
    2480    }
    2581
     
    52108    }
    53109
    54     $(document).on('click', '#easybde_load', function(e){
    55         e.preventDefault();
    56         var cat = $('#easybde_category').val();
     110    function getFilterValue(){
     111        if(EasyBDEState.filter_by === 'author'){
     112            return parseInt($('#easybde_author').val() || 0, 10);
     113        }
     114        return parseInt($('#easybde_category').val() || 0, 10);
     115    }
     116
     117    function loadPosts(page){
     118        EasyBDEState.paged = page || 1;
     119        EasyBDEState.filter_by  = $('#easybde_filter_by').val() || 'category';
     120        EasyBDEState.filter_val = getFilterValue();
     121
    57122        $('#easybde_status').text(EasyBDEData.i18n.loading);
    58         $.post(EasyBDEData.ajax_url, {action:'easybde_load_posts', cat:cat, nonce:EasyBDEData.nonce}, function(resp){
     123        $.post(EasyBDEData.ajax_url, {
     124            action: 'easybde_load_posts',
     125            filter_by: EasyBDEState.filter_by,
     126            filter_val: EasyBDEState.filter_val,
     127            paged: EasyBDEState.paged,
     128            nonce: EasyBDEData.nonce
     129        }, function(resp){
    59130            if(resp && resp.success){
    60                 $('#easybde_status').text('Loaded ' + resp.data.count + ' posts');
     131                var meta = (resp.data && resp.data.meta) ? resp.data.meta : {};
     132                EasyBDEState.paged = meta.paged || EasyBDEState.paged;
     133                EasyBDEState.max_num_pages = meta.max_num_pages || 1;
     134
     135                var total = meta.found_posts || 0;
     136                $('#easybde_status').text('Loaded ' + (resp.data.count || 0) + ' posts (page ' + EasyBDEState.paged + ' of ' + EasyBDEState.max_num_pages + ', total ' + total + ')');
    61137                renderTable(resp.data.rows);
    62138            } else {
     
    64140            }
    65141        });
     142    }
     143
     144    $(document).on('change', '#easybde_filter_by', function(){
     145        var by = $(this).val();
     146        if(by === 'author'){
     147            $('#easybde_filter_category_wrap').hide();
     148            $('#easybde_filter_author_wrap').show();
     149        } else {
     150            $('#easybde_filter_author_wrap').hide();
     151            $('#easybde_filter_category_wrap').show();
     152        }
     153    });
     154
     155    $(document).on('click', '#easybde_load', function(e){
     156        e.preventDefault();
     157        loadPosts(1);
     158    });
     159
     160    $(document).on('click', '.easybde_page', function(e){
     161        e.preventDefault();
     162        var page = parseInt($(this).data('page') || 1, 10);
     163        if(!page || page < 1){
     164            page = 1;
     165        }
     166        loadPosts(page);
    66167    });
    67168
  • easy-bulk-date-editor/tags/1.2/easy-bulk-date-editor.php

    r3421759 r3424368  
    33 * Plugin Name: Easy Bulk Date Editor
    44 * Description: Bulk edit post publish dates by category from a single admin screen, including an optional "shift by" tool.
    5  * Version: 1.1.1
     5 * Version: 1.2
    66 * Author: Andre Moura
    77 * Author URI: https://www.andremoura.com
     
    2727     * @var int
    2828     */
    29     protected $max_posts = 500;
     29    protected $max_posts = 25;
    3030
    3131    /**
     
    105105            <p><?php esc_html_e( 'Filter posts and edit their publish dates in bulk. Remember to back up your database before performing bulk changes.', 'easy-bulk-date-editor' ); ?></p>
    106106
    107             <div id="easybde_filter">
    108                 <label for="easybde_category">
    109                     <?php esc_html_e( 'Category:', 'easy-bulk-date-editor' ); ?>
    110                 </label>
    111                 <?php
    112                 wp_dropdown_categories(
    113                     array(
    114                         'show_option_all' => __( 'All categories', 'easy-bulk-date-editor' ),
    115                         'hide_empty'      => 0,
    116                         'name'            => 'easybde_category',
    117                         'id'              => 'easybde_category',
    118                     )
    119                 );
    120                 ?>
    121                 <button class="button button-secondary" id="easybde_load">
    122                     <?php esc_html_e( 'Load posts', 'easy-bulk-date-editor' ); ?>
    123                 </button>
    124                 <span id="easybde_status" style="margin-left:10px;"></span>
    125             </div>
     107<div id="easybde_filter">
     108    <label for="easybde_filter_by">
     109        <?php esc_html_e( 'Filter by:', 'easy-bulk-date-editor' ); ?>
     110    </label>
     111    <select id="easybde_filter_by" name="easybde_filter_by" style="min-width:160px;">
     112        <option value="category"><?php esc_html_e( 'Category', 'easy-bulk-date-editor' ); ?></option>
     113        <option value="author"><?php esc_html_e( 'Author', 'easy-bulk-date-editor' ); ?></option>
     114    </select>
     115
     116    <span id="easybde_filter_category_wrap" style="margin-left:10px;">
     117        <label for="easybde_category" class="screen-reader-text">
     118            <?php esc_html_e( 'Category', 'easy-bulk-date-editor' ); ?>
     119        </label>
     120        <?php
     121        wp_dropdown_categories(
     122            array(
     123                'show_option_all' => __( 'All categories', 'easy-bulk-date-editor' ),
     124                'hide_empty'      => 0,
     125                'name'            => 'easybde_category',
     126                'id'              => 'easybde_category',
     127            )
     128        );
     129        ?>
     130    </span>
     131
     132    <span id="easybde_filter_author_wrap" style="margin-left:10px; display:none;">
     133        <label for="easybde_author" class="screen-reader-text">
     134            <?php esc_html_e( 'Author', 'easy-bulk-date-editor' ); ?>
     135        </label>
     136        <?php
     137        wp_dropdown_users(
     138            array(
     139                'name'             => 'easybde_author',
     140                'id'               => 'easybde_author',
     141                'show_option_all'  => __( 'All authors', 'easy-bulk-date-editor' ),
     142                'role__in'         => array( 'administrator', 'editor', 'author', 'contributor' ),
     143                'orderby'          => 'display_name',
     144                'order'            => 'ASC',
     145                'include_selected' => true,
     146            )
     147        );
     148        ?>
     149    </span>
     150
     151    <button class="button button-secondary" id="easybde_load" style="margin-left:10px;">
     152        <?php esc_html_e( 'Load posts', 'easy-bulk-date-editor' ); ?>
     153    </button>
     154    <span id="easybde_status" style="margin-left:10px;"></span>
     155</div>
     156
    126157
    127158            <div id="easybde_controls" style="margin-top:15px; display:none;">
     
    154185     */
    155186    public function easybde_ajax_load_posts() {
     187    check_ajax_referer( 'easybde_nonce', 'nonce' );
     188
     189    if ( ! current_user_can( 'edit_posts' ) ) {
     190        wp_send_json_error(
     191            array(
     192                'message' => __( 'You do not have permission to edit posts.', 'easy-bulk-date-editor' ),
     193            )
     194        );
     195    }
     196
     197    $filter_by = filter_input( INPUT_POST, 'filter_by', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
     198    $filter_by = is_string( $filter_by ) ? sanitize_key( $filter_by ) : 'category';
     199    if ( ! in_array( $filter_by, array( 'category', 'author' ), true ) ) {
     200        $filter_by = 'category';
     201    }
     202
     203    $filter_val = filter_input( INPUT_POST, 'filter_val', FILTER_SANITIZE_NUMBER_INT );
     204    $filter_val = ( null === $filter_val ) ? 0 : (int) $filter_val;
     205
     206    $paged = filter_input( INPUT_POST, 'paged', FILTER_SANITIZE_NUMBER_INT );
     207    $paged = ( null === $paged || (int) $paged <= 0 ) ? 1 : (int) $paged;
     208
     209    $args = array(
     210        'post_type'      => 'post',
     211        'posts_per_page' => $this->per_page,
     212        'orderby'        => 'date',
     213        'order'          => 'DESC',
     214        'paged'          => $paged,
     215    );
     216
     217    if ( 'category' === $filter_by && $filter_val > 0 ) {
     218        $args['cat'] = $filter_val;
     219    }
     220
     221    if ( 'author' === $filter_by && $filter_val > 0 ) {
     222        $args['author'] = $filter_val;
     223    }
     224
     225    $query = new WP_Query( $args );
     226    $rows  = array();
     227
     228    if ( $query->have_posts() ) {
     229        foreach ( $query->posts as $post ) {
     230            $post_id = (int) $post->ID;
     231
     232            // Datetime-local expects "YYYY-MM-DDTHH:MM".
     233            $dt = get_post_datetime( $post );
     234            if ( $dt instanceof DateTimeInterface ) {
     235                $value = $dt->format( 'Y-m-d\TH:i' );
     236            } else {
     237                $value = '';
     238            }
     239
     240            $rows[] = array(
     241                'ID'                => $post_id,
     242                'title'             => get_the_title( $post_id ),
     243                'permalink'         => get_edit_post_link( $post_id, '' ),
     244                'post_date_display' => get_the_time( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $post_id ),
     245                'post_date'         => $value,
     246            );
     247        }
     248    }
     249
     250    wp_send_json_success(
     251        array(
     252            'count' => count( $rows ),
     253            'rows'  => $rows,
     254            'meta'  => array(
     255                'paged'         => (int) $paged,
     256                'per_page'      => (int) $this->per_page,
     257                'found_posts'   => (int) $query->found_posts,
     258                'max_num_pages' => (int) $query->max_num_pages,
     259                'filter_by'     => $filter_by,
     260                'filter_val'    => (int) $filter_val,
     261            ),
     262        )
     263    );
     264}
     265
     266
     267    /**
     268     * AJAX handler to save new dates.
     269     */
     270    public function easybde_ajax_save_dates() {
    156271        check_ajax_referer( 'easybde_nonce', 'nonce' );
    157272
     
    164279        }
    165280
    166         $cat = filter_input( INPUT_POST, 'cat', FILTER_SANITIZE_NUMBER_INT );
    167         if ( null === $cat ) {
    168             $cat = 0;
    169         }
    170 
    171         $args = array(
    172             'post_type'      => 'post',
    173             'posts_per_page' => $this->max_posts,
    174             'orderby'        => 'date',
    175             'order'          => 'DESC',
    176         );
    177 
    178         if ( $cat > 0 ) {
    179             $args['cat'] = (int) $cat;
    180         }
    181 
    182         $query = new WP_Query( $args );
    183         $rows  = array();
    184 
    185         if ( $query->have_posts() ) {
    186             foreach ( $query->posts as $post ) {
    187                 $post_id = (int) $post->ID;
    188 
    189                 // Datetime-local expects "YYYY-MM-DDTHH:MM".
    190                 $dt = get_post_datetime( $post );
    191                 if ( $dt instanceof DateTimeInterface ) {
    192                     $value = $dt->format( 'Y-m-d\TH:i' );
    193                 } else {
    194                     $value = '';
    195                 }
    196 
    197                 $rows[] = array(
    198                     'ID'                => $post_id,
    199                     'title'             => get_the_title( $post_id ),
    200                     'permalink'         => get_edit_post_link( $post_id, '' ),
    201                     'post_date_display' => get_the_time( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $post_id ),
    202                     'post_date'         => $value,
    203                 );
    204             }
    205         }
    206 
    207         wp_send_json_success(
    208             array(
    209                 'count' => count( $rows ),
    210                 'rows'  => $rows,
    211             )
    212         );
    213     }
    214 
    215     /**
    216      * AJAX handler to save new dates.
    217      */
    218     public function easybde_ajax_save_dates() {
    219         check_ajax_referer( 'easybde_nonce', 'nonce' );
    220 
    221         if ( ! current_user_can( 'edit_posts' ) ) {
    222             wp_send_json_error(
    223                 array(
    224                     'message' => __( 'You do not have permission to edit posts.', 'easy-bulk-date-editor' ),
    225                 )
    226             );
    227         }
    228 
    229281        // Get changes from POST as an array, without directly touching $_POST.
    230282        $raw_changes = filter_input(
  • easy-bulk-date-editor/tags/1.2/readme.txt

    r3421759 r3424368  
    11=== Easy Bulk Date Editor ===
    2 Contributors: andremoura
    3 Requires at least: 4.6
    4 Tested up to: 6.9
    5 Stable tag: 1.1.1
    6 Requires PHP: 7.2
     2Contributors: andreluizmoura
     3Tags: posts, bulk edit, dates, scheduling, wordpress admin
     4Requires at least: 5.8
     5Tested up to: 6.6
     6Requires PHP: 7.4
     7Author URI: https://www.andremoura.com
     8Plugin URI:  https://wordpress.andremoura.com
     9Stable tag: 1.2
    710License: GPLv2 or later
    8 License URI: http://www.gnu.org/licenses/gpl-2.0.html
     11License URI: https://www.gnu.org/licenses/gpl-2.0.html
    912
    10 Bulk edit post publish dates by author or category from a single screen.
     13Bulk edit WordPress post dates safely and efficiently, now with filtering by category or author and paginated loading for better performance.
    1114
    1215== Description ==
    1316
    14 Easy Bulk Date Editor lets you update post publish dates in bulk from a single, focused admin screen.
     17Easy Bulk Date Editor is a lightweight WordPress admin plugin that allows editors and administrators to bulk edit post publication dates with precision and control.
    1518
    16 You can:
    17 * Filter posts by category.
    18 * Load up to a few hundred posts at once.
    19 * Edit individual publish dates directly in a table.
    20 * Select posts and shift all their dates by a number of days or hours.
     19The plugin was designed to avoid common performance pitfalls of bulk actions by loading posts incrementally and applying changes only to the selected rows.
    2120
    22 This is especially useful when:
    23 * You are migrating content and need to adjust dates.
    24 * An author’s posts need to be moved forward or backward in time.
    25 * You want to clean up an old content calendar without opening each post one by one.
     21### Key Features
    2622
    27 The plugin works with standard WordPress posts and uses the built-in datetime picker from your browser.
     23* Bulk edit post publication dates
     24* Shift dates forward or backward by days and/or hours
     25* Filter posts before editing:
     26  * By category
     27  * By author
     28* Paginated loading (25 posts per page) for improved database performance
     29* Uses native WordPress date and time formats
     30* Safe AJAX-based operations with nonce validation
     31* No front-end impact — admin-only tool
     32
     33== How It Works ==
     34
     351. Choose how you want to filter posts (by category or author)
     362. Load posts in manageable pages of 25 items
     373. Select the posts you want to edit
     384. Adjust individual dates or apply bulk shifts
     395. Save changes securely
     40
     41Pagination ensures the plugin remains fast and reliable even on large sites with thousands of posts.
    2842
    2943== Installation ==
    3044
    31 1. Upload the plugin folder `easy-bulk-date-editor` to the `/wp-content/plugins/` directory, or install it via **Plugins → Add New → Upload Plugin**.
    32 2. Ensure the main plugin file is named `easy-bulk-date-editor.php` inside that folder.
    33 3. Activate the plugin through the **Plugins** menu in WordPress.
    34 4. In the admin menu, go to **Tools → Easy Bulk Date Editor**.
    35 5. Select a category and click **Load posts**.
    36 6. Edit the dates directly in the table or use **Shift selected by** to move dates in bulk, then click **Save changes**.
     451. Upload the plugin folder to `/wp-content/plugins/`
     462. Activate the plugin through the "Plugins" menu in WordPress
     473. Navigate to the plugin page under the WordPress admin menu
     484. Start editing post dates
     49
     50== Frequently Asked Questions ==
     51
     52= Does this plugin edit posts across multiple pages at once? =
     53
     54No. Changes apply only to the posts loaded on the current page. This is intentional to prevent accidental bulk changes and ensure data safety.
     55
     56= Can I filter by both category and author at the same time? =
     57
     58Not at this time. You can choose one filter type (category or author) before loading posts.
     59
     60= Why is pagination limited to 25 posts? =
     61
     62To protect WordPress performance and avoid large, expensive database queries. This limit can be adjusted by developers if needed.
     63
     64= Does this plugin modify post content or metadata? =
     65
     66No. It only updates the post publication date fields.
     67
     68== Screenshots ==
     69
     701. Filter selection (category or author)
     712. Paginated post list with editable dates
     723. Bulk date shift controls
    3773
    3874== Changelog ==
    3975
    40 = 1.1.1 (2025-12-17) =
    41 * Add plugin icons.
     76See changelog.md for detailed version history.
    4277
    43 = 1.1 (2025-11-30) =
    44 * Initial public release.
     78== Upgrade Notice ==
    4579
    46 == License ==
    47 
    48 This plugin is free for everyone! Since it's released under the GPL, you can use it free of charge on your personal or commercial site.
     80= 1.2 =
     81Introduces filtering by author and paginated post loading for better performance and scalability.
  • easy-bulk-date-editor/trunk/bde.js

    r3415608 r3424368  
    11(function($){
     2    var EasyBDEState = {
     3        filter_by: 'category',
     4        filter_val: 0,
     5        paged: 1,
     6        max_num_pages: 1
     7    };
     8
    29    function renderTable(rows){
    310        if(!rows || rows.length === 0){
    411            $('#easybde_controls').hide();
    5             $('#easybde_table_wrap').html('<p>'+EasyBDEData.i18n.no_posts+'</p>');
     12            $('#easybde_table_wrap').html(renderPagination() + '<p>'+EasyBDEData.i18n.no_posts+'</p>' + renderPagination());
    613            return;
    714        }
    815        $('#easybde_controls').show();
    916        var html = [];
     17        html.push(renderPagination());
    1018        html.push('<form id="easybde_form"><table class="wp-list-table widefat fixed striped"><thead><tr>');
    1119        html.push('<th style="width:30px"><input type="checkbox" id="easybde_select_all" /></th>');
     
    2129        });
    2230        html.push('</tbody></table><p><button id="easybde_save" class="button button-primary">Save changes</button> <span id="easybde_result" style="margin-left:10px"></span></p></form>');
     31        html.push(renderPagination());
    2332        $('#easybde_table_wrap').html(html.join(''));
     33    }
     34
     35    function renderPagination(){
     36        var paged = EasyBDEState.paged || 1;
     37        var max   = EasyBDEState.max_num_pages || 1;
     38        if(max <= 1){
     39            return '';
     40        }
     41
     42        var html = [];
     43        html.push('<div class="easybde_pagination" style="margin:12px 0; display:flex; gap:8px; align-items:center; flex-wrap:wrap;">');
     44        html.push('<button type="button" class="button easybde_page" data-page="' + Math.max(1, paged - 1) + '" ' + (paged <= 1 ? 'disabled' : '') + '>&laquo; Prev</button>');
     45
     46        // Compact page range around current (max 7 buttons)
     47        var start = Math.max(1, paged - 3);
     48        var end   = Math.min(max, paged + 3);
     49        if(end - start < 6){
     50            start = Math.max(1, end - 6);
     51            end   = Math.min(max, start + 6);
     52        }
     53
     54        if(start > 1){
     55            html.push('<button type="button" class="button easybde_page" data-page="1">1</button>');
     56            if(start > 2){
     57                html.push('<span style="padding:0 4px;">…</span>');
     58            }
     59        }
     60
     61        for(var p = start; p <= end; p++){
     62            if(p === paged){
     63                html.push('<span class="button button-primary" style="cursor:default;">' + p + '</span>');
     64            } else {
     65                html.push('<button type="button" class="button easybde_page" data-page="' + p + '">' + p + '</button>');
     66            }
     67        }
     68
     69        if(end < max){
     70            if(end < max - 1){
     71                html.push('<span style="padding:0 4px;">…</span>');
     72            }
     73            html.push('<button type="button" class="button easybde_page" data-page="' + max + '">' + max + '</button>');
     74        }
     75
     76        html.push('<button type="button" class="button easybde_page" data-page="' + Math.min(max, paged + 1) + '" ' + (paged >= max ? 'disabled' : '') + '>Next &raquo;</button>');
     77        html.push('<span style="margin-left:6px; opacity:.8;">Page ' + paged + ' of ' + max + '</span>');
     78        html.push('</div>');
     79        return html.join('');
    2480    }
    2581
     
    52108    }
    53109
    54     $(document).on('click', '#easybde_load', function(e){
    55         e.preventDefault();
    56         var cat = $('#easybde_category').val();
     110    function getFilterValue(){
     111        if(EasyBDEState.filter_by === 'author'){
     112            return parseInt($('#easybde_author').val() || 0, 10);
     113        }
     114        return parseInt($('#easybde_category').val() || 0, 10);
     115    }
     116
     117    function loadPosts(page){
     118        EasyBDEState.paged = page || 1;
     119        EasyBDEState.filter_by  = $('#easybde_filter_by').val() || 'category';
     120        EasyBDEState.filter_val = getFilterValue();
     121
    57122        $('#easybde_status').text(EasyBDEData.i18n.loading);
    58         $.post(EasyBDEData.ajax_url, {action:'easybde_load_posts', cat:cat, nonce:EasyBDEData.nonce}, function(resp){
     123        $.post(EasyBDEData.ajax_url, {
     124            action: 'easybde_load_posts',
     125            filter_by: EasyBDEState.filter_by,
     126            filter_val: EasyBDEState.filter_val,
     127            paged: EasyBDEState.paged,
     128            nonce: EasyBDEData.nonce
     129        }, function(resp){
    59130            if(resp && resp.success){
    60                 $('#easybde_status').text('Loaded ' + resp.data.count + ' posts');
     131                var meta = (resp.data && resp.data.meta) ? resp.data.meta : {};
     132                EasyBDEState.paged = meta.paged || EasyBDEState.paged;
     133                EasyBDEState.max_num_pages = meta.max_num_pages || 1;
     134
     135                var total = meta.found_posts || 0;
     136                $('#easybde_status').text('Loaded ' + (resp.data.count || 0) + ' posts (page ' + EasyBDEState.paged + ' of ' + EasyBDEState.max_num_pages + ', total ' + total + ')');
    61137                renderTable(resp.data.rows);
    62138            } else {
     
    64140            }
    65141        });
     142    }
     143
     144    $(document).on('change', '#easybde_filter_by', function(){
     145        var by = $(this).val();
     146        if(by === 'author'){
     147            $('#easybde_filter_category_wrap').hide();
     148            $('#easybde_filter_author_wrap').show();
     149        } else {
     150            $('#easybde_filter_author_wrap').hide();
     151            $('#easybde_filter_category_wrap').show();
     152        }
     153    });
     154
     155    $(document).on('click', '#easybde_load', function(e){
     156        e.preventDefault();
     157        loadPosts(1);
     158    });
     159
     160    $(document).on('click', '.easybde_page', function(e){
     161        e.preventDefault();
     162        var page = parseInt($(this).data('page') || 1, 10);
     163        if(!page || page < 1){
     164            page = 1;
     165        }
     166        loadPosts(page);
    66167    });
    67168
  • easy-bulk-date-editor/trunk/easy-bulk-date-editor.php

    r3421759 r3424368  
    33 * Plugin Name: Easy Bulk Date Editor
    44 * Description: Bulk edit post publish dates by category from a single admin screen, including an optional "shift by" tool.
    5  * Version: 1.1.1
     5 * Version: 1.2
    66 * Author: Andre Moura
    77 * Author URI: https://www.andremoura.com
     
    2727     * @var int
    2828     */
    29     protected $max_posts = 500;
     29    protected $max_posts = 25;
    3030
    3131    /**
     
    105105            <p><?php esc_html_e( 'Filter posts and edit their publish dates in bulk. Remember to back up your database before performing bulk changes.', 'easy-bulk-date-editor' ); ?></p>
    106106
    107             <div id="easybde_filter">
    108                 <label for="easybde_category">
    109                     <?php esc_html_e( 'Category:', 'easy-bulk-date-editor' ); ?>
    110                 </label>
    111                 <?php
    112                 wp_dropdown_categories(
    113                     array(
    114                         'show_option_all' => __( 'All categories', 'easy-bulk-date-editor' ),
    115                         'hide_empty'      => 0,
    116                         'name'            => 'easybde_category',
    117                         'id'              => 'easybde_category',
    118                     )
    119                 );
    120                 ?>
    121                 <button class="button button-secondary" id="easybde_load">
    122                     <?php esc_html_e( 'Load posts', 'easy-bulk-date-editor' ); ?>
    123                 </button>
    124                 <span id="easybde_status" style="margin-left:10px;"></span>
    125             </div>
     107<div id="easybde_filter">
     108    <label for="easybde_filter_by">
     109        <?php esc_html_e( 'Filter by:', 'easy-bulk-date-editor' ); ?>
     110    </label>
     111    <select id="easybde_filter_by" name="easybde_filter_by" style="min-width:160px;">
     112        <option value="category"><?php esc_html_e( 'Category', 'easy-bulk-date-editor' ); ?></option>
     113        <option value="author"><?php esc_html_e( 'Author', 'easy-bulk-date-editor' ); ?></option>
     114    </select>
     115
     116    <span id="easybde_filter_category_wrap" style="margin-left:10px;">
     117        <label for="easybde_category" class="screen-reader-text">
     118            <?php esc_html_e( 'Category', 'easy-bulk-date-editor' ); ?>
     119        </label>
     120        <?php
     121        wp_dropdown_categories(
     122            array(
     123                'show_option_all' => __( 'All categories', 'easy-bulk-date-editor' ),
     124                'hide_empty'      => 0,
     125                'name'            => 'easybde_category',
     126                'id'              => 'easybde_category',
     127            )
     128        );
     129        ?>
     130    </span>
     131
     132    <span id="easybde_filter_author_wrap" style="margin-left:10px; display:none;">
     133        <label for="easybde_author" class="screen-reader-text">
     134            <?php esc_html_e( 'Author', 'easy-bulk-date-editor' ); ?>
     135        </label>
     136        <?php
     137        wp_dropdown_users(
     138            array(
     139                'name'             => 'easybde_author',
     140                'id'               => 'easybde_author',
     141                'show_option_all'  => __( 'All authors', 'easy-bulk-date-editor' ),
     142                'role__in'         => array( 'administrator', 'editor', 'author', 'contributor' ),
     143                'orderby'          => 'display_name',
     144                'order'            => 'ASC',
     145                'include_selected' => true,
     146            )
     147        );
     148        ?>
     149    </span>
     150
     151    <button class="button button-secondary" id="easybde_load" style="margin-left:10px;">
     152        <?php esc_html_e( 'Load posts', 'easy-bulk-date-editor' ); ?>
     153    </button>
     154    <span id="easybde_status" style="margin-left:10px;"></span>
     155</div>
     156
    126157
    127158            <div id="easybde_controls" style="margin-top:15px; display:none;">
     
    154185     */
    155186    public function easybde_ajax_load_posts() {
     187    check_ajax_referer( 'easybde_nonce', 'nonce' );
     188
     189    if ( ! current_user_can( 'edit_posts' ) ) {
     190        wp_send_json_error(
     191            array(
     192                'message' => __( 'You do not have permission to edit posts.', 'easy-bulk-date-editor' ),
     193            )
     194        );
     195    }
     196
     197    $filter_by = filter_input( INPUT_POST, 'filter_by', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
     198    $filter_by = is_string( $filter_by ) ? sanitize_key( $filter_by ) : 'category';
     199    if ( ! in_array( $filter_by, array( 'category', 'author' ), true ) ) {
     200        $filter_by = 'category';
     201    }
     202
     203    $filter_val = filter_input( INPUT_POST, 'filter_val', FILTER_SANITIZE_NUMBER_INT );
     204    $filter_val = ( null === $filter_val ) ? 0 : (int) $filter_val;
     205
     206    $paged = filter_input( INPUT_POST, 'paged', FILTER_SANITIZE_NUMBER_INT );
     207    $paged = ( null === $paged || (int) $paged <= 0 ) ? 1 : (int) $paged;
     208
     209    $args = array(
     210        'post_type'      => 'post',
     211        'posts_per_page' => $this->per_page,
     212        'orderby'        => 'date',
     213        'order'          => 'DESC',
     214        'paged'          => $paged,
     215    );
     216
     217    if ( 'category' === $filter_by && $filter_val > 0 ) {
     218        $args['cat'] = $filter_val;
     219    }
     220
     221    if ( 'author' === $filter_by && $filter_val > 0 ) {
     222        $args['author'] = $filter_val;
     223    }
     224
     225    $query = new WP_Query( $args );
     226    $rows  = array();
     227
     228    if ( $query->have_posts() ) {
     229        foreach ( $query->posts as $post ) {
     230            $post_id = (int) $post->ID;
     231
     232            // Datetime-local expects "YYYY-MM-DDTHH:MM".
     233            $dt = get_post_datetime( $post );
     234            if ( $dt instanceof DateTimeInterface ) {
     235                $value = $dt->format( 'Y-m-d\TH:i' );
     236            } else {
     237                $value = '';
     238            }
     239
     240            $rows[] = array(
     241                'ID'                => $post_id,
     242                'title'             => get_the_title( $post_id ),
     243                'permalink'         => get_edit_post_link( $post_id, '' ),
     244                'post_date_display' => get_the_time( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $post_id ),
     245                'post_date'         => $value,
     246            );
     247        }
     248    }
     249
     250    wp_send_json_success(
     251        array(
     252            'count' => count( $rows ),
     253            'rows'  => $rows,
     254            'meta'  => array(
     255                'paged'         => (int) $paged,
     256                'per_page'      => (int) $this->per_page,
     257                'found_posts'   => (int) $query->found_posts,
     258                'max_num_pages' => (int) $query->max_num_pages,
     259                'filter_by'     => $filter_by,
     260                'filter_val'    => (int) $filter_val,
     261            ),
     262        )
     263    );
     264}
     265
     266
     267    /**
     268     * AJAX handler to save new dates.
     269     */
     270    public function easybde_ajax_save_dates() {
    156271        check_ajax_referer( 'easybde_nonce', 'nonce' );
    157272
     
    164279        }
    165280
    166         $cat = filter_input( INPUT_POST, 'cat', FILTER_SANITIZE_NUMBER_INT );
    167         if ( null === $cat ) {
    168             $cat = 0;
    169         }
    170 
    171         $args = array(
    172             'post_type'      => 'post',
    173             'posts_per_page' => $this->max_posts,
    174             'orderby'        => 'date',
    175             'order'          => 'DESC',
    176         );
    177 
    178         if ( $cat > 0 ) {
    179             $args['cat'] = (int) $cat;
    180         }
    181 
    182         $query = new WP_Query( $args );
    183         $rows  = array();
    184 
    185         if ( $query->have_posts() ) {
    186             foreach ( $query->posts as $post ) {
    187                 $post_id = (int) $post->ID;
    188 
    189                 // Datetime-local expects "YYYY-MM-DDTHH:MM".
    190                 $dt = get_post_datetime( $post );
    191                 if ( $dt instanceof DateTimeInterface ) {
    192                     $value = $dt->format( 'Y-m-d\TH:i' );
    193                 } else {
    194                     $value = '';
    195                 }
    196 
    197                 $rows[] = array(
    198                     'ID'                => $post_id,
    199                     'title'             => get_the_title( $post_id ),
    200                     'permalink'         => get_edit_post_link( $post_id, '' ),
    201                     'post_date_display' => get_the_time( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $post_id ),
    202                     'post_date'         => $value,
    203                 );
    204             }
    205         }
    206 
    207         wp_send_json_success(
    208             array(
    209                 'count' => count( $rows ),
    210                 'rows'  => $rows,
    211             )
    212         );
    213     }
    214 
    215     /**
    216      * AJAX handler to save new dates.
    217      */
    218     public function easybde_ajax_save_dates() {
    219         check_ajax_referer( 'easybde_nonce', 'nonce' );
    220 
    221         if ( ! current_user_can( 'edit_posts' ) ) {
    222             wp_send_json_error(
    223                 array(
    224                     'message' => __( 'You do not have permission to edit posts.', 'easy-bulk-date-editor' ),
    225                 )
    226             );
    227         }
    228 
    229281        // Get changes from POST as an array, without directly touching $_POST.
    230282        $raw_changes = filter_input(
  • easy-bulk-date-editor/trunk/readme.txt

    r3421759 r3424368  
    11=== Easy Bulk Date Editor ===
    2 Contributors: andremoura
    3 Requires at least: 4.6
    4 Tested up to: 6.9
    5 Stable tag: 1.1.1
    6 Requires PHP: 7.2
     2Contributors: andreluizmoura
     3Tags: posts, bulk edit, dates, scheduling, wordpress admin
     4Requires at least: 5.8
     5Tested up to: 6.6
     6Requires PHP: 7.4
     7Author URI: https://www.andremoura.com
     8Plugin URI:  https://wordpress.andremoura.com
     9Stable tag: 1.2
    710License: GPLv2 or later
    8 License URI: http://www.gnu.org/licenses/gpl-2.0.html
     11License URI: https://www.gnu.org/licenses/gpl-2.0.html
    912
    10 Bulk edit post publish dates by author or category from a single screen.
     13Bulk edit WordPress post dates safely and efficiently, now with filtering by category or author and paginated loading for better performance.
    1114
    1215== Description ==
    1316
    14 Easy Bulk Date Editor lets you update post publish dates in bulk from a single, focused admin screen.
     17Easy Bulk Date Editor is a lightweight WordPress admin plugin that allows editors and administrators to bulk edit post publication dates with precision and control.
    1518
    16 You can:
    17 * Filter posts by category.
    18 * Load up to a few hundred posts at once.
    19 * Edit individual publish dates directly in a table.
    20 * Select posts and shift all their dates by a number of days or hours.
     19The plugin was designed to avoid common performance pitfalls of bulk actions by loading posts incrementally and applying changes only to the selected rows.
    2120
    22 This is especially useful when:
    23 * You are migrating content and need to adjust dates.
    24 * An author’s posts need to be moved forward or backward in time.
    25 * You want to clean up an old content calendar without opening each post one by one.
     21### Key Features
    2622
    27 The plugin works with standard WordPress posts and uses the built-in datetime picker from your browser.
     23* Bulk edit post publication dates
     24* Shift dates forward or backward by days and/or hours
     25* Filter posts before editing:
     26  * By category
     27  * By author
     28* Paginated loading (25 posts per page) for improved database performance
     29* Uses native WordPress date and time formats
     30* Safe AJAX-based operations with nonce validation
     31* No front-end impact — admin-only tool
     32
     33== How It Works ==
     34
     351. Choose how you want to filter posts (by category or author)
     362. Load posts in manageable pages of 25 items
     373. Select the posts you want to edit
     384. Adjust individual dates or apply bulk shifts
     395. Save changes securely
     40
     41Pagination ensures the plugin remains fast and reliable even on large sites with thousands of posts.
    2842
    2943== Installation ==
    3044
    31 1. Upload the plugin folder `easy-bulk-date-editor` to the `/wp-content/plugins/` directory, or install it via **Plugins → Add New → Upload Plugin**.
    32 2. Ensure the main plugin file is named `easy-bulk-date-editor.php` inside that folder.
    33 3. Activate the plugin through the **Plugins** menu in WordPress.
    34 4. In the admin menu, go to **Tools → Easy Bulk Date Editor**.
    35 5. Select a category and click **Load posts**.
    36 6. Edit the dates directly in the table or use **Shift selected by** to move dates in bulk, then click **Save changes**.
     451. Upload the plugin folder to `/wp-content/plugins/`
     462. Activate the plugin through the "Plugins" menu in WordPress
     473. Navigate to the plugin page under the WordPress admin menu
     484. Start editing post dates
     49
     50== Frequently Asked Questions ==
     51
     52= Does this plugin edit posts across multiple pages at once? =
     53
     54No. Changes apply only to the posts loaded on the current page. This is intentional to prevent accidental bulk changes and ensure data safety.
     55
     56= Can I filter by both category and author at the same time? =
     57
     58Not at this time. You can choose one filter type (category or author) before loading posts.
     59
     60= Why is pagination limited to 25 posts? =
     61
     62To protect WordPress performance and avoid large, expensive database queries. This limit can be adjusted by developers if needed.
     63
     64= Does this plugin modify post content or metadata? =
     65
     66No. It only updates the post publication date fields.
     67
     68== Screenshots ==
     69
     701. Filter selection (category or author)
     712. Paginated post list with editable dates
     723. Bulk date shift controls
    3773
    3874== Changelog ==
    3975
    40 = 1.1.1 (2025-12-17) =
    41 * Add plugin icons.
     76See changelog.md for detailed version history.
    4277
    43 = 1.1 (2025-11-30) =
    44 * Initial public release.
     78== Upgrade Notice ==
    4579
    46 == License ==
    47 
    48 This plugin is free for everyone! Since it's released under the GPL, you can use it free of charge on your personal or commercial site.
     80= 1.2 =
     81Introduces filtering by author and paginated post loading for better performance and scalability.
Note: See TracChangeset for help on using the changeset viewer.