Last active
April 6, 2023 19:01
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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