diff --git a/features/media-import.feature b/features/media-import.feature index aa8562fc..8e75fa95 100644 --- a/features/media-import.feature +++ b/features/media-import.feature @@ -286,3 +286,35 @@ Feature: Manage WordPress attachments """ Error: Invalid value for : invalid. Expected flag or 'url'. """ + + Scenario: Upload files into a custom directory, relative to ABSPATH, when --destination-dir flag is applied. + Given download: + | path | url | + | {CACHE_DIR}/large-image.jpg | http://wp-cli.org/behat-data/large-image.jpg | + When I run `wp media import --destination-dir="foo" {CACHE_DIR}/large-image.jpg --porcelain=url` + + Then STDOUT should not contain: + """ + https://example.com/wp-content/uploads/ + """ + + And STDOUT should contain: + """ + https://example.com/foo/large-image.jpg + """ + + Scenario: Upload files into a custom directory, not relative to ABSPATH, when --destination-dir flag is applied. + Given download: + | path | url | + | {CACHE_DIR}/large-image.jpg | http://wp-cli.org/behat-data/large-image.jpg | + When I run `wp media import --destination-dir="{RUN_DIR}/foo" {CACHE_DIR}/large-image.jpg --porcelain=url` + + Then STDOUT should not contain: + """ + https://example.com/wp-content/uploads/ + """ + + And STDOUT should contain: + """ + /foo/large-image.jpg + """ diff --git a/src/Media_Command.php b/src/Media_Command.php index 8275cb45..ef4a7d40 100644 --- a/src/Media_Command.php +++ b/src/Media_Command.php @@ -49,6 +49,11 @@ class Media_Command extends WP_CLI_Command { */ const WP_CLEAR_OBJECT_CACHE_INTERVAL = 500; + /** + * @var string|null + */ + private $destination_dir; + /** * Regenerates thumbnails for one or more attachments. * @@ -216,7 +221,13 @@ public function regenerate( $args, $assoc_args = array() ) { * * [--skip-copy] * : If set, media files (local only) are imported to the library but not moved on disk. - * File names will not be run through wp_unique_filename() with this set. + * File names will not be run through wp_unique_filename() with this set. When used, files + * will remain at their current location and will not be copied into any destination directory. + * + * [--destination-dir=] + * : Path to the destination directory for uploaded imported files. + * Can be absolute or relative to ABSPATH. Ignored when used together with --skip-copy, as + * files are not moved on disk in that case. * * [--preserve-filetime] * : Use the file modified time as the post published & modified dates. @@ -266,12 +277,13 @@ public function import( $args, $assoc_args = array() ) { $assoc_args = wp_parse_args( $assoc_args, array( - 'file_name' => '', - 'title' => '', - 'caption' => '', - 'alt' => '', - 'desc' => '', - 'post_name' => '', + 'file_name' => '', + 'title' => '', + 'caption' => '', + 'alt' => '', + 'desc' => '', + 'post_name' => '', + 'destination-dir' => '', ) ); @@ -411,6 +423,13 @@ public function import( $args, $assoc_args = array() ) { } wp_update_attachment_metadata( $success, wp_generate_attachment_metadata( $success, $file ) ); } else { + + $destdir = Utils\get_flag_value( $assoc_args, 'destination-dir' ); + if ( ! empty( $destdir ) ) { + $this->destination_dir = $destdir; + add_filter( 'upload_dir', [ $this, 'filter_upload_dir' ], PHP_INT_MAX ); + } + // Deletes the temporary file. $success = media_handle_sideload( $file_array, $assoc_args['post_id'], $assoc_args['title'], $post_array ); if ( is_wp_error( $success ) ) { @@ -468,6 +487,8 @@ public function import( $args, $assoc_args = array() ) { ++$successes; } + remove_filter( 'upload_dir', [ $this, 'filter_upload_dir' ], PHP_INT_MAX ); + // Report the result of the operation if ( ! Utils\get_flag_value( $assoc_args, 'porcelain' ) ) { Utils\report_batch_operation_results( $noun, 'import', count( $args ), $successes, $errors ); @@ -939,6 +960,35 @@ private function remove_image_size_filters( $image_size_filters ) { } } + public function filter_upload_dir( $uploads ) { + if ( ! $this->destination_dir ) { + return $uploads; + } + + $upload_dir = $this->destination_dir; + + if ( 0 !== strpos( $this->destination_dir, ABSPATH ) ) { + // $dir is absolute, $upload_dir is (maybe) relative to ABSPATH. + $dir = path_join( ABSPATH, $this->destination_dir ); + } else { + $dir = $this->destination_dir; + // normalize $upload_dir. + $upload_dir = substr( $this->destination_dir, strlen( ABSPATH ) ); + } + + $siteurl = get_option( 'siteurl' ); + $url = trailingslashit( $siteurl ) . $upload_dir; + + return [ + 'path' => $this->destination_dir, + 'url' => $url, + 'subdir' => '', + 'basedir' => $this->destination_dir, + 'baseurl' => $url, + 'error' => false, + ]; + } + // Update attachment sizes metadata just for a particular intermediate image size. private function update_attachment_metadata_for_image_size( $id, $new_metadata, $image_size, $metadata ) {