Skip to content

Instantly share code, notes, and snippets.

@seanlanglands
Last active April 6, 2023 19:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seanlanglands/9eff5531fbcf5f6a356d343a3da9074e to your computer and use it in GitHub Desktop.
Save seanlanglands/9eff5531fbcf5f6a356d343a3da9074e to your computer and use it in GitHub Desktop.
Custom WP-CLI command to import a CSV file into a post type
<?php
/**
* ExamplePackage\CLI_Import_CSV Class.
*
* @package ExamplePackage
*/
namespace ExamplePackage;
if ( ! defined( '\WP_CLI' ) ) {
return;
}
use WP_CLI;
use WPCOM_VIP_CLI_Command;
/**
* Adds import-csv-example CLI command.
*/
class CLI_Import_CSV {
/**
* Default associative args.
*
* @var array
*/
public array $defaults = array();
/**
* List of items used for the named args.
*
* This will be added to the assoc_args using the provided keys.
*
* @var array
*/
public array $args = array();
/**
* The combined associative array of args.
*
* @var array
*/
public array $assoc_args = array();
/**
* Rows from the CSV file.
*
* @var array
*/
public array $rows = array();
/**
* The current row.
*
* @var array
*/
public array $row = array();
/**
* Posts to be added.
*
* @var array
*/
public array $posts = array();
/**
* Post IDs that have been inserted.
*
* @var array
*/
public array $post_ids = array();
/**
* The number of posts that have been inserted.
*
* @var int
*/
public int $count = 0;
/**
* CSV constructor.
*/
public function __construct() {
$this->defaults = array(
'site-id' => 0,
'post-type' => null,
'dry-run' => ''
);
$this->args = array(
'data-source'
);
}
/**
* Sets the $assoc_args property.
*
* Uses the $args parameter to set the named args in the $assoc_args property
*
* @param array $args The arguments.
* @param array $assoc_args Associative array of args.
*/
public function set_args( array $args, array $assoc_args ): void {
$this->assoc_args = array_merge( $this->defaults, $assoc_args );
$count = 0;
if ( ! empty( $this->args ) ) {
foreach ( $this->args as $name ) {
$this->assoc_args[ $name ] = empty( $args[ $count ] ) ? '' : $args[ $count ++ ];
}
}
}
/**
* Checks to see if a flag is set and returns true if so otherwise false.
*
* @param string $flag The item to check.
*
* @return bool
*/
public function get_flag_value( string $flag ): bool {
return ! empty( $this->assoc_args[ $flag ] );
}
/**
* Imports a CSV file into a post type.
*
* Only supports attachment post type. Useful when orphan media files don't have
* associated attachment post type data in WordPress.
*
* Important – File URLs must use the "/uploads/YYYY/MM/" directory structure.
*
* ## OPTIONS
*
* <data-source>
* : Local server absolute path to the csv file to import.
*
* [--site-id=<id>]
* : Network site ID.
*
* [--post-type=<slug>]
* : Post type slug (e.g., attachment).
*
* [--dry-run]
* : Indicates to test the import instead of doing the import.
*
* ## EXAMPLES
*
* // VIP Local Development Environment
* wp import-csv-example /wp/wp-content/themes/theme/attachments.csv --site-id=2
* --post-type=attachment
*
* // VIP Environment
* wp import-csv-example /var/www/wp-content/themes/theme/attachments.csv --site-id=2
* --post-type=attachment
*
* @param array $args The arguments.
* @param array $assoc_args Associative array of args.
*/
public function __invoke( array $args, array $assoc_args ): void {
if ( empty( $args[0] ) ) {
WP_CLI::error( __( 'The file is required.' ) );
}
$this->set_args( $args, $assoc_args );
if ( ! $this->get_flag_value( 'site-id' ) ) {
WP_CLI::error( __( 'The site ID is required.' ) );
}
if ( ! $this->get_flag_value( 'post-type' ) ) {
WP_CLI::error( __( 'The post type is required.' ) );
}
if ( ! $this->assoc_args['dry-run'] ) {
WP_CLI::confirm( 'Are you sure you want to import into ' . get_site_url( $this->assoc_args['site-id'] ) . '?' );
}
if ( ! defined( 'WP_IMPORTING' ) ) {
define( 'WP_IMPORTING', true );
}
if ( class_exists( 'WPCOM_VIP_CLI_Command' ) ) {
WPCOM_VIP_CLI_Command::start_bulk_operation();
}
switch_to_blog( $this->assoc_args['site-id'] );
WP_CLI::log( WP_CLI::colorize( '%bParsing CSV file:%n ' ) . $this->assoc_args['data-source'] );
$this->open_file();
if ( 'attachment' === $this->assoc_args['post-type'] ) {
$this->prepare_attachment_posts();
}
WP_CLI::log( WP_CLI::colorize( '%bInserting ' . count( $this->posts ) . ' post(s)%n' ) . '...' );
$this->insert_posts();
$this->finish();
if ( class_exists( 'WPCOM_VIP_CLI_Command' ) ) {
WPCOM_VIP_CLI_Command::end_bulk_operation();
}
restore_current_blog();
}
/**
* Adds attachment posts from the import to the posts' property.
*/
public function prepare_attachment_posts(): void {
foreach ( $this->rows as $this->row ) {
$guid = $this->row[0];
$filename = basename( $guid );
$wp_filetype = wp_check_filetype( $filename, null );
$wp_upload_dir = wp_upload_dir();
preg_match( '(\d{4}\/\d\d)', $guid, $retro_post_date_matches );
$filepath = trailingslashit( $retro_post_date_matches[0] ) . $filename;
/**
* Post date should match the initial /YYYY/MM/ directory structure.
*/
$retro_post_date = explode( '/', $retro_post_date_matches[0] );
$retro_post_date = strtotime( $retro_post_date[0] . '-' . $retro_post_date[1] . '-01' );
$retro_post_date = gmdate( 'Y-m-d H:i:s', $retro_post_date );
$this->posts[] = array(
'data' => array(
'guid' => $guid,
'post_mime_type' => $wp_filetype['type'],
'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $filename ) ),
'post_content' => '',
'post_status' => 'inherit',
'post_date' => $retro_post_date,
'post_author' => 1,
'post_type' => 'attachment'
),
'meta' => array(
'file_path' => trailingslashit( $wp_upload_dir['basedir'] ) . $filepath,
'attached_file' => $filepath
)
);
}
}
/**
* Opens the CSV file and sets the rows' property.
*/
public function open_file(): void {
$content = file( $this->assoc_args['data-source'] );
if ( $content ) {
$this->rows = array_map( 'str_getcsv', $content );
} else {
WP_CLI::error( __( 'The file was not found on the server.' ) );
}
}
/**
* Inserts the posts.
*
* If dry run is enabled the post data is output instead.
*/
public function insert_posts(): void {
foreach ( $this->posts as $post ) {
if ( $this->assoc_args['dry-run'] ) {
sprintf(
'%1$s%3$s%2$s%3$s',
esc_html__( 'Prepared Post Data:' ),
print_r( $post, false ), // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
PHP_EOL
);
} else {
$post_id = wp_insert_attachment( $post['data'] );
$this->post_ids[] = $post_id;
$attach_data = wp_generate_attachment_metadata( $post_id, $post['meta']['file_path'] );
wp_update_attachment_metadata( $post_id, $attach_data );
add_post_meta( $post_id, '_wp_attached_file', $post['meta']['attached_file'], true );
if ( is_wp_error( $post_id ) ) {
WP_CLI::warning(
sprintf(
esc_html__( 'Failed to import %s' ),
print_r( $post, false ) // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
)
);
} else {
WP_CLI::success(
sprintf(
esc_html__( 'Imported post %d' ),
$post_id
)
);
}
}
$this->count ++;
}
}
/**
* Outputs the success message.
*/
public function finish(): void {
if ( $this->assoc_args['dry-run'] ) {
WP_CLI::success(
sprintf(
esc_html__( '%d post(s) would have been imported' ),
$this->count
)
);
} else {
WP_CLI::success(
sprintf(
esc_html__( '%d post(s) were imported' ),
$this->count
)
);
WP_CLI::log(
WP_CLI::colorize( '%bOptionally validate counts with WP-CLI command: %n' ) .
'wp post list --post__in=' . implode( ',', $this->post_ids ) . ' --post_type=' . $this->assoc_args['post-type'] . ' --format=count'
);
}
}
}
$command = new CLI_Import_CSV();
WP_CLI::add_command( 'import-csv-example', $command );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment