Skip to content

Instantly share code, notes, and snippets.

@JiveDig
Last active January 10, 2025 20:10
Show Gist options
  • Save JiveDig/9d570e5255ccaec132de573575945bb5 to your computer and use it in GitHub Desktop.
Save JiveDig/9d570e5255ccaec132de573575945bb5 to your computer and use it in GitHub Desktop.
Custom WP-CLI commands to export/import specific posts via CLI.

Custom WP-CLI commands to export/import specific posts via CLI.

How to Export

Export posts of a specific type with all post data.

wp jivedig export_posts_to_csv --post_type=post --posts_per_page=10 --offset=0

Export pages of a specific type by post ID with all post data.

wp jivedig export_posts_to_csv --post_type=page --post__in=17827,17754,17756

Export pages of a specific type by post ID and include specific post data.

wp jivedig export_posts_to_csv --post_type=page --post__in=17827,17754,17756 --post_data=ID,post_title,post_content,post_excerpt

Export posts of a specific type by post ID and include specific post and post meta data.

wp jivedig export_posts_to_csv --post_type=page --post__in=17827,17754,17756 --post_data=ID,post_title,post_content,post_excerpt --post_meta=meta_key_one,meta_key_two

How to Import

Import posts from a CSV file. Get the current directory with pwd and then use that path to update the file path. Get the file name with ls and then use that file location.

wp jivedig update_posts_from_csv --file="/Users/yourname/Local Sites/sandbox/app/public/exported-posts-Jan-10-2025-1104am.csv"
<?php
// Prevent direct file access.
defined( 'ABSPATH' ) || die;
/**
* Gets it started.
*
* @link https://docs.wpvip.com/how-tos/write-custom-wp-cli-commands/
* @link https://webdevstudios.com/2019/10/08/making-wp-cli-commands/
*
* @return void
*/
add_action( 'cli_init', function() {
WP_CLI::add_command( 'jivedig', 'JiveDig_CLI_CSV_Migrate_Posts' );
});
/**
* Main JiveDig_CLI_CSV_Migrate_Posts Class.
*
* @version 0.1.0
* @author @JiveDig
*/
class JiveDig_CLI_CSV_Migrate_Posts {
protected $post_keys;
/**
* Constructor to set vars.
*
* @return void
*/
function __construct() {
global $wpdb;
// Get all column names from the wp_posts table.
$columns = $wpdb->get_col( "DESC {$wpdb->posts}", 0 );
// Assign the columns to the protected property.
$this->post_keys = $columns;
}
/**
* Gets environment. Useful for testing if the command and this class are loaded and working.
*
* Usage:
* wp jivedig get_environment
*
* @return void
*/
function get_environment() {
WP_CLI::log( sprintf( 'Environment: %s', wp_get_environment_type() ) );
}
/**
* Exports posts to a CSV file.
*
* 'post_data' is comma separated list of post fields to export.
* 'post_meta' is comma separated list of post meta fields to export.
*
* Usage:
* wp jivedig export_posts_to_csv --post_type=post --posts_per_page=10 --offset=0
* wp jivedig export_posts_to_csv --post_type=page --post__in=17827,17754,17756,17772,17862,17760
* wp jivedig export_posts_to_csv --post_type=page --post__in=17827,17754,17756,17772,17862,17760 --post_data=ID,post_title,post_content,post_excerpt,post_status,comment_status,ping_status,post_password,post_name,to_ping,pinged,post_modified,post_modified_gmt,post_content_filtered,post_parent,guid,menu_order,post_type,post_mime_type,comment_count
*
* @return void
*/
function export_posts_to_csv( $args, $assoc_args ) {
WP_CLI::log( 'Exporting posts...' );
$assoc_args = wp_parse_args(
$assoc_args,
[
'post_type' => 'post',
'post_status' => 'any',
'posts_per_page' => 500,
'offset' => 0,
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'post_data' => 'ID,post_author,post_date_gmt,post_content,post_title,post_excerpt,post_status,comment_status,ping_status,post_password,post_name,to_ping,pinged,post_modified,post_modified_gmt,post_content_filtered,post_parent,guid,menu_order,post_type,post_mime_type,comment_count',
'post_meta' => '',
]
);
// Force array incase of comma separated list.
$assoc_args['post_type'] = explode( ',', $assoc_args['post_type'] );
// If post__in is set, explode it.
if ( isset( $assoc_args['post__in'] ) ) {
$assoc_args['post__in'] = explode( ',', $assoc_args['post__in'] );
$assoc_args['posts_per_page'] = count( $assoc_args['post__in'] );
}
// Build post and meta arrays.
$post_data = explode( ',', $assoc_args['post_data'] );
$post_meta = explode( ',', $assoc_args['post_meta'] );
// Initialize rows array with header.
$rows = [ array_merge( $post_data, $post_meta ) ];
// Remove post and meta from post data array.
unset( $assoc_args['post_data'] );
unset( $assoc_args['post_meta'] );
// Get posts.
$query = new WP_Query( $assoc_args );
// If posts found.
if ( $query->have_posts() ) {
WP_CLI::log( sprintf( 'Posts found: %s', $query->post_count ) );
// Loop through posts.
while ( $query->have_posts() ) : $query->the_post();
// Start row and get post as array.
$row = [];
$post = get_post( get_the_ID(), ARRAY_A );
// Loop through post data array.
foreach( $post_data as $key ) {
$row[ $key ] = get_post_field( $key, $post['ID'], 'row' );
}
// Loop through meta data array.
foreach ( $post_meta as $key ) {
$row[ $key ] = maybe_serialize( get_post_meta( $post['ID'], $key, true ) );
}
// Add row to rows array.
$rows[] = $row;
endwhile;
}
// Log no posts found.
else {
WP_CLI::success( 'No posts found.' );
}
// Write CSV file with date and time.
$name = 'exported-posts-' . date( 'M-d-Y-gia', current_time('timestamp') ) . '.csv';
$file = fopen( $name, 'w' );
// Write each row to the CSV file.
foreach ( $rows as $row ) {
fputcsv( $file, $row );
}
fclose( $file );
WP_CLI::success( "Posts exported to $name" );
}
/**
* Update existing posts from a CSV file.
*
* Usage:
* wp jivedig update_posts_from_csv --file="/Users/yourname/Local Sites/sandbox/app/public/exported-posts-Jan-10-2025-1104am.csv"
*
* @return void
*/
function update_posts_from_csv( $args, $assoc_args ) {
WP_CLI::log( 'Importing posts...' );
$assoc_args = wp_parse_args(
$assoc_args,
[
'file' => '',
]
);
// Bail if no file.
if ( ! $assoc_args['file'] ) {
WP_CLI::error( 'No file provided.' );
}
// Bail if file doesn't exist.
if ( ! file_exists( $assoc_args['file'] ) ) {
WP_CLI::error( 'File does not exist. ' . $assoc_args['file'] );
}
// Get file.
$file = fopen( $assoc_args['file'], 'r' );
// Bail if file can't be opened.
if ( ! $file ) {
WP_CLI::error( 'File can\'t be opened. ' . $assoc_args['file'] );
}
// Get all rows.
$rows = [];
while ( false !== ( $row = fgetcsv( $file, 1000, ',' ) ) ) {
$rows[] = $row;
}
// Get header from first row.
$header = array_shift( $rows );
// Create data array with header keys.
$data = [];
foreach ( $rows as $row ) {
$data[] = array_combine( $header, $row );
}
// Close the file.
fclose( $file );
// Bail if no data.
if ( empty( $data ) ) {
WP_CLI::error( 'No data found.' );
}
// Loop through data.
foreach ( $data as $row ) {
// Bail if no post ID.
if ( ! isset( $row['ID'] ) ) {
WP_CLI::log( 'No post ID found.' );
continue;
}
// Get existing post.
$existing = get_post( $row['ID'] );
// Bail if post not found.
if ( ! $existing ) {
WP_CLI::log( 'Post not found. . ' . $row['ID'] );
continue;
}
// Build post and meta arrays from row.
$post = array_intersect_key( $row, array_flip( $this->post_keys ) );
$meta = array_diff_key( $row, array_flip( $this->post_keys ) );
// Sanitize.
$post = array_map( 'wp_kses_post', $post );
$meta = array_map( 'wp_kses_post', $meta );
// If post content is set, slash it.
if ( isset( $post['post_content'] ) ) {
$post['post_content'] = wp_slash( $post['post_content'] );
}
// Maybe unserialize meta.
foreach ( $meta as $key => $value ) {
$meta[ $key ] = is_serialized( $value ) ? unserialize( $value ) : $value;
}
// Update post.
$post_id = wp_update_post( $post );
// Check for errors.
if ( is_wp_error( $post_id ) ) {
WP_CLI::log( 'Error updating post. ' . $post_id->get_error_message() . ' - ' . get_permalink( $existing->ID ) );
continue;
}
// Update meta.
foreach ( $meta as $key => $value ) {
// If no value, delete meta.
if ( empty( $value ) ) {
delete_post_meta( $post_id, $key );
} else {
update_post_meta( $post_id, $key, $value );
}
}
// Log success.
WP_CLI::log( 'Updated post. ' . $existing->ID . ' - ' . get_permalink( $existing->ID ) );
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment