@@ -49,6 +49,11 @@ class Media_Command extends WP_CLI_Command {
4949 */
5050 const WP_CLEAR_OBJECT_CACHE_INTERVAL = 500 ;
5151
52+ /**
53+ * @var string|null
54+ */
55+ private $ destination_dir ;
56+
5257 /**
5358 * Regenerates thumbnails for one or more attachments.
5459 *
@@ -216,7 +221,13 @@ public function regenerate( $args, $assoc_args = array() ) {
216221 *
217222 * [--skip-copy]
218223 * : If set, media files (local only) are imported to the library but not moved on disk.
219- * File names will not be run through wp_unique_filename() with this set.
224+ * File names will not be run through wp_unique_filename() with this set. When used, files
225+ * will remain at their current location and will not be copied into any destination directory.
226+ *
227+ * [--destination-dir=<destination-dir>]
228+ * : Path to the destination directory for uploaded imported files.
229+ * Can be absolute or relative to ABSPATH. Ignored when used together with --skip-copy, as
230+ * files are not moved on disk in that case.
220231 *
221232 * [--preserve-filetime]
222233 * : Use the file modified time as the post published & modified dates.
@@ -266,12 +277,13 @@ public function import( $args, $assoc_args = array() ) {
266277 $ assoc_args = wp_parse_args (
267278 $ assoc_args ,
268279 array (
269- 'file_name ' => '' ,
270- 'title ' => '' ,
271- 'caption ' => '' ,
272- 'alt ' => '' ,
273- 'desc ' => '' ,
274- 'post_name ' => '' ,
280+ 'file_name ' => '' ,
281+ 'title ' => '' ,
282+ 'caption ' => '' ,
283+ 'alt ' => '' ,
284+ 'desc ' => '' ,
285+ 'post_name ' => '' ,
286+ 'destination-dir ' => '' ,
275287 )
276288 );
277289
@@ -411,6 +423,13 @@ public function import( $args, $assoc_args = array() ) {
411423 }
412424 wp_update_attachment_metadata ( $ success , wp_generate_attachment_metadata ( $ success , $ file ) );
413425 } else {
426+
427+ $ destdir = Utils \get_flag_value ( $ assoc_args , 'destination-dir ' );
428+ if ( ! empty ( $ destdir ) ) {
429+ $ this ->destination_dir = $ destdir ;
430+ add_filter ( 'upload_dir ' , [ $ this , 'filter_upload_dir ' ], PHP_INT_MAX );
431+ }
432+
414433 // Deletes the temporary file.
415434 $ success = media_handle_sideload ( $ file_array , $ assoc_args ['post_id ' ], $ assoc_args ['title ' ], $ post_array );
416435 if ( is_wp_error ( $ success ) ) {
@@ -468,6 +487,8 @@ public function import( $args, $assoc_args = array() ) {
468487 ++$ successes ;
469488 }
470489
490+ remove_filter ( 'upload_dir ' , [ $ this , 'filter_upload_dir ' ], PHP_INT_MAX );
491+
471492 // Report the result of the operation
472493 if ( ! Utils \get_flag_value ( $ assoc_args , 'porcelain ' ) ) {
473494 Utils \report_batch_operation_results ( $ noun , 'import ' , count ( $ args ), $ successes , $ errors );
@@ -939,6 +960,35 @@ private function remove_image_size_filters( $image_size_filters ) {
939960 }
940961 }
941962
963+ public function filter_upload_dir ( $ uploads ) {
964+ if ( ! $ this ->destination_dir ) {
965+ return $ uploads ;
966+ }
967+
968+ $ upload_dir = $ this ->destination_dir ;
969+
970+ if ( 0 !== strpos ( $ this ->destination_dir , ABSPATH ) ) {
971+ // $dir is absolute, $upload_dir is (maybe) relative to ABSPATH.
972+ $ dir = path_join ( ABSPATH , $ this ->destination_dir );
973+ } else {
974+ $ dir = $ this ->destination_dir ;
975+ // normalize $upload_dir.
976+ $ upload_dir = substr ( $ this ->destination_dir , strlen ( ABSPATH ) );
977+ }
978+
979+ $ siteurl = get_option ( 'siteurl ' );
980+ $ url = trailingslashit ( $ siteurl ) . $ upload_dir ;
981+
982+ return [
983+ 'path ' => $ this ->destination_dir ,
984+ 'url ' => $ url ,
985+ 'subdir ' => '' ,
986+ 'basedir ' => $ this ->destination_dir ,
987+ 'baseurl ' => $ url ,
988+ 'error ' => false ,
989+ ];
990+ }
991+
942992 // Update attachment sizes metadata just for a particular intermediate image size.
943993 private function update_attachment_metadata_for_image_size ( $ id , $ new_metadata , $ image_size , $ metadata ) {
944994
0 commit comments