Plugin Directory

Changeset 3367871


Ignore:
Timestamp:
09/25/2025 02:05:08 PM (6 months ago)
Author:
silversh
Message:

Release V2.1.0

Location:
btw-importer
Files:
18 added
5 edited

Legend:

Unmodified
Added
Removed
  • btw-importer/trunk/btw-importer.php

    r3359514 r3367871  
    11<?php
    22/*
    3 Plugin Name:        BtW Importer
     3Plugin Name:        BtW Importer - Blogger to WordPress Importer
    44Plugin URI:         https://github.com/mnasikin/btw-importer
    55Description:        Simple yet powerful plugin to Migrate Blogger to WordPress in one click. Import .atom from Google Takeout and the plugin will scan & download first image, replace URLs, set featured image, show live progress.
    6 Version:            2.0.0
     6Version:            2.1.0
    77Author:             Nasikin
    88Author URI:         https://github.com/mnasikin/
     
    1515*/
    1616
    17 class Btw_Importer {
    18     private $downloaded_images = []; // cache
    19 
    20     public function __construct() {
    21         add_action('admin_menu', [$this, 'add_menu']);
    22         add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']);
    23         add_action('wp_ajax_btw_importer_prepare_import', [$this, 'ajax_prepare_import']);
    24         add_action('wp_ajax_btw_importer_import_single_post', [$this, 'ajax_import_single_post']);
    25     }
    26 
    27     public function add_menu() { // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
    28         add_menu_page( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
    29             'BtW Importer', 'BtW Importer', 'manage_options', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
    30             'btw-importer', [$this, 'import_page'], 'dashicons-upload' // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
    31         );
    32     }
    33 
    34     public function enqueue_scripts($hook) {
    35         if ($hook !== 'toplevel_page_btw-importer') return;
    36         wp_enqueue_script('btw-importer', plugin_dir_url(__FILE__).'btw-importer.js', ['jquery'], '1.2.2', true);
    37         wp_localize_script('btw-importer', 'btwImporter', [
    38             'ajaxUrl' => admin_url('admin-ajax.php'),
    39             'nonce'   => wp_create_nonce('btw_importer_importer_nonce')
    40         ]);
    41     }
    42 
    43     public function import_page() {
    44         echo '<div class="wrap">
    45             <h1>BtW Importer</h1>
    46             <p>A powerful yet simple migration tool, BtW Importer helps you seamlessly transfer posts, images, and formatting from Blogger (Blogspot) to WordPress. Don&apos;t forget to share this plugin if you found it&apos;s usefull</p>
    47             <div id="importNotice" style="margin:20px;">
    48             <h2>⚠️ Please Read Before Importing ⚠️</h2>
    49             <ul>
    50                 <li>🛑 ️This plugin doesn&apos;t overwrite existing posts with the same name. If you&apos;ve previously used an importer, it&apos;s recommended to manually delete the previously imported content.</li>
    51                 <li>🛑 301 redirects only work if you previously used a custom domain on Blogspot and you&apos;re moving that domain to WordPress.</li>
    52                 <li>🛑 Make sure not to leave this page while the process is underway, or the import will stop, and you&apos;ll need to start from the beginning.</li>
    53                 <li>🛑 301 redirects work if this plugin is active and you have already run the importer.</li>
    54                 <li>🛑 Only image from Google/Blogspot will be downloaded.</li>
    55                 <li>🛑 Be sure to manually check your content after the import process is complete.</li>
    56             </ul>
    57               <input type="checkbox" id="agreeNotice">
    58               <label for="agreeNotice">
    59                 I&apos;ve read all of them and I want to start the importer.
    60               </label>
    61             </div>
    62             <input type="file" id="atomFile" accept=".xml,.atom" />
    63             <button id="startImport" class="button button-primary" disabled>Start Import</button><br>
    64             <label for="atomFile">Accepted File: .xml,.atom</label>
    65             <hr>
    66             <div id="importOverlay" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.7); color: #fff; font-size: 20px; z-index: 9999; text-align: center; padding-top: 20%;">
    67                 ⚠ Import in progress... Please don’t close, reload, or navigate away.
    68             </div>
    69             <div id="progress" style="margin-top:20px; max-height:100vh; max-width;100%; overflow:auto; background:#fff; padding:10px; border:1px solid #ddd;"></div>
    70         </div>';
    71     }
    72 
    73     public function ajax_prepare_import() {
    74         check_ajax_referer('btw_importer_importer_nonce', 'nonce');
    75         $atom_content = isset($_POST['atom_content']) ? wp_unslash($_POST['atom_content']) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    76         if (!$atom_content) wp_send_json_error('No data received.');
    77 
    78         libxml_use_internal_errors(true);
    79         $xml = simplexml_load_string($atom_content);
    80         if (!$xml) wp_send_json_error('Failed to parse XML.');
    81 
    82         $posts = [];
    83         foreach ($xml->entry as $entry) {
    84             $bloggerType = strtolower((string)$entry->children('blogger', true)->type);
    85             $post_type = ($bloggerType === 'page') ? 'page' : 'post';
    86 
    87             $title = sanitize_text_field((string)$entry->title);
    88             $content = (string)$entry->content;
    89             $author = isset($entry->author->name) ? sanitize_text_field((string)$entry->author->name) : '';
    90 
    91             $published_raw = (string)$entry->published;
    92             $date_gmt = gmdate('Y-m-d H:i:s', strtotime($published_raw));
    93             $date_local = get_date_from_gmt($date_gmt, 'Y-m-d H:i:s');
    94 
    95             // get categories
    96             $categories = [];
    97             foreach ($entry->category as $cat) {
    98                 $term = (string)$cat['term'];
    99                 if ($term && strpos($term, '#') !== 0) {
    100                     $categories[] = sanitize_text_field($term);
    101                 }
    102             }
    103 
    104             // get old permalink from <blogger:filename>
    105             $filename = (string)$entry->children('blogger', true)->filename;
    106             $filename = trim($filename);
    107 
    108             $posts[] = [
    109                 'title'      => $title,
    110                 'content'    => $content,
    111                 'author'     => $author,
    112                 'post_type'  => $post_type,
    113                 'date'       => $date_local,
    114                 'date_gmt'   => $date_gmt,
    115                 'categories' => $categories,
    116                 'filename'   => $filename
    117             ];
    118         }
    119 
    120         wp_send_json_success(['posts' => $posts]);
    121     }
    122 
    123     public function ajax_import_single_post() {
    124         check_ajax_referer('btw_importer_importer_nonce', 'nonce');
    125         $raw_post = isset($_POST['post']) ? wp_unslash($_POST['post']) : []; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    126         if (!$raw_post) wp_send_json_error('Missing post data.');
    127 
    128         $title = sanitize_text_field($raw_post['title'] ?? '');
    129         $author = sanitize_text_field($raw_post['author'] ?? '');
    130         $post_type = in_array($raw_post['post_type'], ['post','page']) ? $raw_post['post_type'] : 'post';
    131         $date = sanitize_text_field($raw_post['date'] ?? '');
    132         $date_gmt = sanitize_text_field($raw_post['date_gmt'] ?? '');
    133         $categories = $raw_post['categories'] ?? [];
    134         $filename = sanitize_text_field($raw_post['filename'] ?? '');
    135         $allowed_tags = wp_kses_allowed_html('post');
    136         $allowed_tags['iframe'] = ['src'=>true,'width'=>true,'height'=>true,'frameborder'=>true,'allowfullscreen'=>true,'class'=>true,'youtube-src-id'=>true];
    137         $content = wp_kses($raw_post['content'] ?? '', $allowed_tags);
    138 
    139         $msgs = [];
    140 
    141         $author_id = 1;
    142         if ($author) {
    143             $user = get_user_by('login', sanitize_user($author, true));
    144             if ($user) $author_id = $user->ID;
    145         }
    146 
    147         require_once ABSPATH.'wp-admin/includes/image.php';
    148         require_once ABSPATH.'wp-admin/includes/file.php';
    149         require_once ABSPATH.'wp-admin/includes/media.php';
    150 
    151         $post_id = wp_insert_post([
    152             'post_title'    => $title,
    153             'post_content'  => $content,
    154             'post_status'   => 'publish',
    155             'post_date'     => $date,
    156             'post_date_gmt' => $date_gmt,
    157             'post_author'   => $author_id,
    158             'post_type'     => $post_type
    159         ]);
    160 
    161         if (is_wp_error($post_id)) wp_send_json_error('❌ Failed to insert: '.$title);
    162 
    163         // add redirect meta & log redirect creation
    164         if ($filename) {
    165             if ($filename[0] !== '/') $filename = '/' . $filename;
    166             add_post_meta($post_id, '_btw_importer_old_permalink', $filename, true);
    167             $new_url = get_permalink($post_id);
    168             $msgs[] = '✅ Finished create 301 redirect: '.$filename.' → '.$new_url;
    169         }
    170 
    171         // create categories
    172         if (!empty($categories) && $post_type === 'post') {
    173             $cat_ids = [];
    174             foreach ($categories as $cat_name) {
    175                 $term = term_exists($cat_name, 'category');
    176                 if (!$term) {
    177                     $new_term = wp_create_category($cat_name);
    178                     if (!is_wp_error($new_term)) {
    179                         $cat_ids[] = $new_term;
    180                         $msgs[] = '✅ Created category: '.$cat_name;
    181                     }
    182                 } else {
    183                     $cat_ids[] = $term['term_id'];
    184                     $msgs[] = '✅ Using category: '.$cat_name;
    185                 }
    186             }
    187             if (!empty($cat_ids)) wp_set_post_categories($post_id, $cat_ids);
    188         }
    189 
    190         // find unique blogger/googleusercontent images by basename (after /sXXX/)
    191         preg_match_all('/https?:\/\/[^"\']+\.(jpg|jpeg|png|gif|webp|bmp|svg)/i', $content, $matches);
    192         $image_by_basename = [];
    193         foreach (array_unique($matches[0]) as $img_url) {
    194             if (!preg_match('/(blogspot|googleusercontent)/i', $img_url)) continue;
    195 
    196             if (preg_match('#/s\d+/(.+)$#', $img_url, $m)) {
    197                 $basename = $m[1];
    198             } else {
    199                 $basename = basename(wp_parse_url($img_url, PHP_URL_PATH));
    200             }
    201 
    202             if (!isset($image_by_basename[$basename])) {
    203                 $image_by_basename[$basename] = $img_url;
    204             } else {
    205                 // prefer bigger /sXXX/ number
    206                 if (preg_match('#/s(\d+)/#', $img_url, $m1) && preg_match('#/s(\d+)/#', $image_by_basename[$basename], $m2)) {
    207                     if ((int)$m1[1] > (int)$m2[1]) {
    208                         $image_by_basename[$basename] = $img_url;
    209                     }
    210                 }
    211             }
    212         }
    213 
    214         $first_media_id = null;
    215         foreach ($image_by_basename as $img_url) {
    216             if (isset($this->downloaded_images[$img_url])) {
    217                 $new_url = $this->downloaded_images[$img_url];
    218                 $content = str_replace($img_url, $new_url, $content);
    219                 $msgs[]='✅ Used cached: '.$new_url;
    220                 continue;
    221             }
    222 
    223             $msgs[]='⏳ Downloading: '.$img_url;
    224             $tmp = download_url($img_url);
    225             if (is_wp_error($tmp)) { $msgs[]='⚠ Failed to download'; continue; }
    226 
    227             $file = ['name'=>basename(wp_parse_url($img_url, PHP_URL_PATH)),'tmp_name'=>$tmp];
    228             $media_id = media_handle_sideload($file,$post_id);
    229             if (is_wp_error($media_id)) { wp_delete_file($tmp); $msgs[]='⚠ Failed to attach'; continue; }
    230 
    231             $new_url = wp_get_attachment_url($media_id);
    232             if ($new_url) {
    233                 $this->downloaded_images[$img_url] = $new_url;
    234                 $content = str_replace($img_url, $new_url, $content);
    235                 $msgs[]='✅ Replaced: '.$img_url.' → '.$new_url;
    236                 if (!$first_media_id) $first_media_id = $media_id;
    237             }
    238         }
    239 
    240         wp_update_post(['ID'=>$post_id,'post_content'=>$content]);
    241         if ($first_media_id) {
    242             set_post_thumbnail($post_id, $first_media_id);
    243             $msgs[]='⭐ Successfully Set featured image';
    244         }
    245 
    246         $msgs[]='✅ Finished '.$post_type.': '.$title;
    247         wp_send_json_success($msgs);
    248     }
     17function btw_importer_include_files() {
     18    require_once plugin_dir_path(__FILE__) . 'importer.php';
     19    require_once plugin_dir_path(__FILE__) . 'redirect.php';
     20    require_once plugin_dir_path(__FILE__) . 'redirect-log.php';
    24921}
    250 
    251 new Btw_Importer();
    252 require_once plugin_dir_path(__FILE__) . 'redirect.php';
    253 require_once plugin_dir_path(__FILE__) . 'redirect-log.php';
     22add_action('plugins_loaded', 'btw_importer_include_files');
  • btw-importer/trunk/changelog.md

    r3359514 r3367871  
    77
    88## 🧾 Changelog
     9
     10### 2.1.0
     11- Fix draft and deleted content on .atom imported as published in WordPress
    912
    1013### 2.0.0
  • btw-importer/trunk/readme.md

    r3359514 r3367871  
    1 [![Download Plugin](https://img.shields.io/badge/download_plugin-000?style=for-the-badge&logo=download&logoColor=white)](https://github.com/mnasikin/btw-importer/releases/tag/v2.0.0)
     1[![Download Plugin](https://img.shields.io/badge/download_plugin-000?style=for-the-badge&logo=download&logoColor=white)](https://wordpress.org/plugins/btw-importer)
    22
    33# BtW Importer
     
    6363## 🧾 Changelog
    6464
     65### 2.1.0
     66- Draft, Published, Trash Post in Blogspot now keep as Draft, Published, Trash in WordPress
     67
    6568### 2.0.0
    6669🔥 Major Update 🔥
     
    8588
    8689### 2.0.0
    87 
    88 Major Update! This release adds many features for your import process including add notice before import, add warning on leaving page while import in process, add redirect 301 from old blogspot permalink, add redirect log and clear redirect log, sync post and page published date, add or use category based on .atom file, only download image hosted on blogspot/google, only download original image to avoid duplicated image, security update, and some UI change.
     90 Please check the changelog tab to check what's new.
  • btw-importer/trunk/readme.txt

    r3359514 r3367871  
    1 === BtW Importer ===
     1=== BtW Importer - Blogger to WordPress Importer ===
    22Contributors: silversh 
    33Tags: blogger, blogspot, blogger importer, blogspot importer, import blogspot 
    4 Requires at least: 6.8.1 
     4Requires at least: 6.8.0 
    55Tested up to: 6.8 
    6 Stable tag: 2.0.0 
     6Stable tag: 2.1.0 
    77Requires PHP: 7.4 
    88License: MIT 
     
    5151* PHP 7.4 or later 
    5252* cURL PHP Extension 
     53* `SimpleXML` PHP Extension
    5354* `allow_url_fopen` enabled 
    5455* Writable `wp-content/uploads` folder (default setting already meets this)
     
    6061
    6162== Screenshots ==
    62 1. Preview of the import process interface
     631. Importer Page
     642. Import Process
     653. Done Importing
     664. Redirect Log
    6367
    6468== Changelog ==
     69= 2.1.0 =
     70* Draft, Published, Trash Post in Blogspot now keep as Draft, Published, Trash in WordPress
     71
     72
    6573= 2.0.0 =
    6674🔥 Major Update 🔥
     
    7785== Upgrade Notice ==
    7886= 2.0.0 =
    79  Major Update! Please check the Changelog for more information
     87 Please check the changelog tab to check what's new.
  • btw-importer/trunk/redirect-log.php

    r3359514 r3367871  
    22if (!defined('ABSPATH')) exit;
    33
    4 class Btw_Importer_Redirect_Log {
     4class btw_importer_Redirect_Log {
    55    public function __construct() {
    6         add_action('admin_menu', [$this, 'add_redirect_log_menu']);
    7         add_action('admin_init', [$this, 'handle_clear_log']);
     6        add_action('admin_menu', [$this, 'btw_importer_add_redirect_log_menu']);
     7        add_action('admin_init', [$this, 'btw_importer_handle_clear_log']);
    88    }
    99
    10     public function add_redirect_log_menu() {
     10    public function btw_importer_add_redirect_log_menu() {
    1111        add_submenu_page(
    1212            'btw-importer',
     
    1515            'manage_options',
    1616            'btw-redirect-log',
    17             [$this, 'render_redirect_log_page']
     17            [$this, 'btw_importer_render_redirect_log_page']
    1818        );
    1919    }
    2020
    21     public function handle_clear_log() {
     21    public function btw_importer_handle_clear_log() {
    2222        if (!current_user_can('manage_options')) return;
    2323
     
    3535    }
    3636
    37     public function render_redirect_log_page() {
     37    public function btw_importer_render_redirect_log_page() {
    3838    global $wpdb;
    3939
     
    4343    $orderby = sanitize_sql_orderby((string) filter_input(INPUT_GET, 'orderby'));
    4444    $order   = (strtoupper((string) filter_input(INPUT_GET, 'order')) === 'ASC') ? 'ASC' : 'DESC';
    45     // Cache removed to fix SQL preparation error
     45    $post_type_filter = sanitize_text_field((string) filter_input(INPUT_GET, 'post_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
    4646
    47 
    48     $allowed_orderby = ['p.post_date', 'p.post_type'];
     47    $allowed_orderby = ['post_date', 'post_type'];
    4948    if (!in_array($orderby, $allowed_orderby, true)) {
    50         $orderby = 'p.post_date';
     49        $orderby = 'post_date';
    5150    }
    5251
    5352    $per_page = 25;
    5453    $offset   = ($paged - 1) * $per_page;
     54
     55    // Get distinct post types for filter dropdown
     56    $post_types = $wpdb->get_col( $wpdb->prepare("SELECT DISTINCT p.post_type FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = %s ORDER BY p.post_type", '_btw_importer_old_permalink') );
    5557
    5658    echo '<div class="wrap">';
     
    6062    $clear_nonce = wp_create_nonce('btw_importer_clear_log');
    6163
    62     // Search + clear form
     64    // Search + filter form
    6365    echo '<form method="get" style="margin-bottom:10px; display:inline-block; margin-right:10px;">
    6466            <input type="hidden" name="page" value="btw-redirect-log" />
    6567            <input type="search" name="s" placeholder="Search slug..." value="' . esc_attr($search) . '" />
    66             <input type="submit" class="button" value="Search" />
     68            <select name="post_type">
     69                <option value="">All Post Types</option>';
     70    foreach ($post_types as $type) {
     71        echo '<option value="' . esc_attr($type) . '" ' . selected($post_type_filter, $type, false) . '>' . esc_html($type) . '</option>';
     72    }
     73    echo '</select>
     74            <input type="submit" class="button" value="Filter" />
    6775          </form>';
    6876
     
    7280          </form>';
    7381
    74     // Build query safely
    75     $params   = ['_btw_importer_old_permalink'];
    76     if ($search) {
    77         if ($orderby === 'p.post_date' && $order === 'ASC') {
    78             $results = $wpdb->get_results( $wpdb->prepare("SELECT SQL_CALC_FOUND_ROWS p.ID, p.post_type, p.post_date, pm.meta_value as old_slug FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = %s AND pm.meta_value LIKE %s ORDER BY p.post_date ASC LIMIT %d OFFSET %d", ['_btw_importer_old_permalink', '%' . $wpdb->esc_like($search) . '%', $per_page, $offset]) );
    79         } elseif ($orderby === 'p.post_date' && $order === 'DESC') {
    80             $results = $wpdb->get_results( $wpdb->prepare("SELECT SQL_CALC_FOUND_ROWS p.ID, p.post_type, p.post_date, pm.meta_value as old_slug FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = %s AND pm.meta_value LIKE %s ORDER BY p.post_date DESC LIMIT %d OFFSET %d", ['_btw_importer_old_permalink', '%' . $wpdb->esc_like($search) . '%', $per_page, $offset]) );
    81         } elseif ($orderby === 'p.post_type' && $order === 'ASC') {
    82             $results = $wpdb->get_results( $wpdb->prepare("SELECT SQL_CALC_FOUND_ROWS p.ID, p.post_type, p.post_date, pm.meta_value as old_slug FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = %s AND pm.meta_value LIKE %s ORDER BY p.post_type ASC LIMIT %d OFFSET %d", ['_btw_importer_old_permalink', '%' . $wpdb->esc_like($search) . '%', $per_page, $offset]) );
    83         } elseif ($orderby === 'p.post_type' && $order === 'DESC') {
    84             $results = $wpdb->get_results( $wpdb->prepare("SELECT SQL_CALC_FOUND_ROWS p.ID, p.post_type, p.post_date, pm.meta_value as old_slug FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = %s AND pm.meta_value LIKE %s ORDER BY p.post_type DESC LIMIT %d OFFSET %d", ['_btw_importer_old_permalink', '%' . $wpdb->esc_like($search) . '%', $per_page, $offset]) );
    85         } else {
    86             $results = $wpdb->get_results( $wpdb->prepare("SELECT SQL_CALC_FOUND_ROWS p.ID, p.post_type, p.post_date, pm.meta_value as old_slug FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = %s AND pm.meta_value LIKE %s ORDER BY p.post_date DESC LIMIT %d OFFSET %d", ['_btw_importer_old_permalink', '%' . $wpdb->esc_like($search) . '%', $per_page, $offset]) );
    87         }
    88     } else {
    89         if ($orderby === 'p.post_date' && $order === 'ASC') {
    90             $results = $wpdb->get_results( $wpdb->prepare("SELECT SQL_CALC_FOUND_ROWS p.ID, p.post_type, p.post_date, pm.meta_value as old_slug FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = %s ORDER BY p.post_date ASC LIMIT %d OFFSET %d", ['_btw_importer_old_permalink', $per_page, $offset]) );
    91         } elseif ($orderby === 'p.post_date' && $order === 'DESC') {
    92             $results = $wpdb->get_results( $wpdb->prepare("SELECT SQL_CALC_FOUND_ROWS p.ID, p.post_type, p.post_date, pm.meta_value as old_slug FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = %s ORDER BY p.post_date DESC LIMIT %d OFFSET %d", ['_btw_importer_old_permalink', $per_page, $offset]) );
    93         } elseif ($orderby === 'p.post_type' && $order === 'ASC') {
    94             $results = $wpdb->get_results( $wpdb->prepare("SELECT SQL_CALC_FOUND_ROWS p.ID, p.post_type, p.post_date, pm.meta_value as old_slug FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = %s ORDER BY p.post_type ASC LIMIT %d OFFSET %d", ['_btw_importer_old_permalink', $per_page, $offset]) );
    95         } elseif ($orderby === 'p.post_type' && $order === 'DESC') {
    96             $results = $wpdb->get_results( $wpdb->prepare("SELECT SQL_CALC_FOUND_ROWS p.ID, p.post_type, p.post_date, pm.meta_value as old_slug FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = %s ORDER BY p.post_type DESC LIMIT %d OFFSET %d", ['_btw_importer_old_permalink', $per_page, $offset]) );
    97         } else {
    98             $results = $wpdb->get_results( $wpdb->prepare("SELECT SQL_CALC_FOUND_ROWS p.ID, p.post_type, p.post_date, pm.meta_value as old_slug FROM {$wpdb->postmeta} pm JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = %s ORDER BY p.post_date DESC LIMIT %d OFFSET %d", ['_btw_importer_old_permalink', $per_page, $offset]) );
    99         }
     82    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $orderby and $order are whitelisted (ASC/DESC, allowed columns only)
     83    $allowed_orderby = ['post_date', 'post_type'];
     84    if ( ! in_array( $orderby, $allowed_orderby, true ) ) {
     85        $orderby = 'post_date';
    10086    }
    10187
    102     $total_items = (int) $wpdb->get_var( "SELECT FOUND_ROWS()" );
    103 
    104 
     88    $order = ( 'ASC' === strtoupper( $order ) ) ? 'ASC' : 'DESC';
     89    $results = $wpdb->get_results(
     90        $wpdb->prepare(
     91            "
     92            SELECT SQL_CALC_FOUND_ROWS p.ID, p.post_type, p.post_date, pm.meta_value as old_slug
     93            FROM {$wpdb->postmeta} pm
     94            JOIN {$wpdb->posts} p ON p.ID = pm.post_id
     95            WHERE pm.meta_key = %s
     96            ORDER BY p.{$orderby} {$order}
     97            LIMIT %d OFFSET %d
     98            ",
     99            '_btw_importer_old_permalink',
     100            $per_page,
     101            $offset
     102        )
     103    );
    105104
    106105    if (!$results) {
     
    112111            $base_url = add_query_arg('s', urlencode($search), $base_url);
    113112        }
     113        if ($post_type_filter) {
     114            $base_url = add_query_arg('post_type', urlencode($post_type_filter), $base_url);
     115        }
    114116
    115117        $columns = [
    116             'p.post_date' => 'Date',
    117             'p.post_type' => 'Post Type',
     118            'post_date' => 'Date',
     119            'post_type' => 'Post Type',
    118120        ];
    119121
     
    160162                    'orderby' => $orderby,
    161163                    'order'   => $order,
     164                    'post_type' => $post_type_filter,
    162165                ],
    163166                'prev_text' => esc_html__('« Prev', 'btw-importer'),
Note: See TracChangeset for help on using the changeset viewer.