diff --git a/features/media-import.feature b/features/media-import.feature index 8e75fa95..1a8ced91 100644 --- a/features/media-import.feature +++ b/features/media-import.feature @@ -287,6 +287,51 @@ Feature: Manage WordPress attachments Error: Invalid value for : invalid. Expected flag or 'url'. """ + Scenario: Import media from STDIN + Given download: + | path | url | + | {CACHE_DIR}/codeispoetry.png | http://wp-cli.org/behat-data/codeispoetry.png | + + When I run `cat {CACHE_DIR}/codeispoetry.png | wp media import - --title="From STDIN" --porcelain` + Then save STDOUT as {ATTACHMENT_ID} + + When I run `wp post get {ATTACHMENT_ID} --field=title` + Then STDOUT should be: + """ + From STDIN + """ + + Scenario: Import media from STDIN with file_name + Given download: + | path | url | + | {CACHE_DIR}/codeispoetry.png | http://wp-cli.org/behat-data/codeispoetry.png | + + When I run `cat {CACHE_DIR}/codeispoetry.png | wp media import - --file_name=my-image.png --porcelain` + Then save STDOUT as {ATTACHMENT_ID} + + When I run `wp post get {ATTACHMENT_ID} --field=post_name` + Then STDOUT should be: + """ + my-image + """ + + When I run `wp post meta get {ATTACHMENT_ID} _wp_attached_file` + Then STDOUT should contain: + """ + my-image.png + """ + And STDOUT should not contain: + """ + my-image.png.png + """ + Scenario: Fail to import from STDIN when no input provided + When I try `wp media import - ] * : ID of the post to attach the imported files to. @@ -453,6 +459,11 @@ public function prune( $args, $assoc_args = array() ) { * $ wp media import http://s.wordpress.org/style/images/wp-header-logo.png --porcelain | xargs -I {} wp post list --post__in={} --field=url --post_type=attachment * http://wordpress-develop.dev/wp-header-logo/ * + * # Import an image from STDIN. + * $ curl http://example.com/image.jpg | wp media import - --title="From STDIN" + * Imported file 'STDIN' as attachment ID 1756. + * Success: Imported 1 of 1 items. + * * @param string[] $args Positional arguments. * @param array{post_id?: string, post_name?: string, file_name?: string, title?: string, caption?: string, alt?: string, desc?: string, 'skip-copy'?: bool, 'destination-dir'?: string, 'preserve-filetime'?: bool, featured_image?: bool, porcelain?: bool|string} $assoc_args Associative arguments. * @return void @@ -514,41 +525,109 @@ public function import( $args, $assoc_args = array() ) { Utils\wp_clear_object_cache(); } - // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- parse_url will only be used in absence of wp_parse_url. - $is_file_remote = function_exists( 'wp_parse_url' ) ? wp_parse_url( $file, PHP_URL_HOST ) : parse_url( $file, PHP_URL_HOST ); - $orig_filename = $file; - $file_time = ''; + // Handle STDIN input + if ( '-' === $file ) { + if ( ! Utils\has_stdin() ) { + WP_CLI::warning( 'Unable to import file from STDIN. Reason: No input provided.' ); + ++$errors; + continue; + } - if ( empty( $is_file_remote ) ) { - if ( ! file_exists( $file ) ) { - WP_CLI::warning( "Unable to import file '$file'. Reason: File doesn't exist." ); + // Read from STDIN and save to a temporary file + // Stream STDIN directly to temp file to avoid memory issues with large files + $stdin_handle = fopen( 'php://stdin', 'rb' ); + if ( false === $stdin_handle ) { + WP_CLI::warning( 'Unable to import file from STDIN. Reason: Could not open STDIN.' ); ++$errors; continue; } - if ( Utils\get_flag_value( $assoc_args, 'skip-copy' ) ) { - $tempfile = $file; - } else { - $tempfile = $this->make_copy( $file ); + + // Create a temporary file to store STDIN content + $tempfile = wp_tempnam( 'wp-media-import-' ); + if ( false === $tempfile ) { + fclose( $stdin_handle ); + WP_CLI::warning( 'Unable to import file from STDIN. Reason: Could not create temporary file.' ); + ++$errors; + continue; } - $name = Path::basename( $file ); - if ( Utils\get_flag_value( $assoc_args, 'preserve-filetime' ) ) { - $file_time = @filemtime( $file ); + $temp_handle = fopen( $tempfile, 'wb' ); + if ( false === $temp_handle ) { + fclose( $stdin_handle ); + WP_CLI::warning( 'Unable to import file from STDIN. Reason: Could not write to temporary file.' ); + ++$errors; + continue; } - } else { - $tempfile = download_url( $file ); - if ( is_wp_error( $tempfile ) ) { - WP_CLI::warning( - sprintf( - "Unable to import file '%s'. Reason: %s", - $file, - implode( ', ', $tempfile->get_error_messages() ) - ) - ); + + // Stream data from STDIN to temp file + $bytes_copied = stream_copy_to_stream( $stdin_handle, $temp_handle ); + fclose( $stdin_handle ); + fclose( $temp_handle ); + + if ( false === $bytes_copied || 0 === $bytes_copied ) { + WP_CLI::warning( 'Unable to import file from STDIN. Reason: No input provided.' ); ++$errors; continue; } - $name = (string) strtok( Path::basename( $file ), '?' ); + + // Determine file extension from content + $mimetype = mime_content_type( $tempfile ); + + // Map MIME type to extension + $ext = ''; + if ( $mimetype && function_exists( 'wp_get_mime_types' ) ) { + $mime_types = wp_get_mime_types(); + foreach ( $mime_types as $exts => $mime ) { + if ( $mime === $mimetype ) { + $ext_array = explode( '|', $exts ); + $ext = '.' . $ext_array[0]; + break; + } + } + } + + // Generate filename with proper extension + $name = 'stdin-' . time() . $ext; + + $orig_filename = 'STDIN'; + $file_time = ''; + } else { + // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- parse_url will only be used in absence of wp_parse_url. + $is_file_remote = function_exists( 'wp_parse_url' ) ? wp_parse_url( $file, PHP_URL_HOST ) : parse_url( $file, PHP_URL_HOST ); + $orig_filename = $file; + $file_time = ''; + + if ( empty( $is_file_remote ) ) { + if ( ! file_exists( $file ) ) { + WP_CLI::warning( "Unable to import file '$file'. Reason: File doesn't exist." ); + ++$errors; + continue; + } + if ( Utils\get_flag_value( $assoc_args, 'skip-copy' ) ) { + $tempfile = $file; + } else { + $tempfile = $this->make_copy( $file ); + } + $name = Path::basename( $file ); + + if ( Utils\get_flag_value( $assoc_args, 'preserve-filetime' ) ) { + $file_time = @filemtime( $file ); + } + } else { + $tempfile = download_url( $file ); + if ( is_wp_error( $tempfile ) ) { + WP_CLI::warning( + sprintf( + "Unable to import file '%s'. Reason: %s", + $file, + implode( ', ', $tempfile->get_error_messages() ) + ) + ); + ++$errors; + continue; + } + $name = (string) strtok( Path::basename( $file ), '?' ); + } } if ( ! empty( $assoc_args['file_name'] ) ) { @@ -594,7 +673,9 @@ public function import( $args, $assoc_args = array() ) { } if ( empty( $post_array['post_title'] ) ) { - $post_array['post_title'] = preg_replace( '/\.[^.]+$/', '', Path::basename( $file ) ); + // For STDIN imports, use the generated filename instead of the '-' argument + $title_source = ( '-' === $file ) ? $name : $file; + $post_array['post_title'] = preg_replace( '/\.[^.]+$/', '', Path::basename( $title_source ) ); } if ( Utils\get_flag_value( $assoc_args, 'skip-copy' ) ) { @@ -2005,7 +2086,10 @@ private function get_image_name( $basename, $slug ) { $extension = pathinfo( $basename, PATHINFO_EXTENSION ); - return $slug . '.' . $extension; + // Strip any extension from the slug to prevent double extensions + $slug_without_ext = preg_replace( '/\.[^.]+$/', '', $slug ); + + return $slug_without_ext . '.' . $extension; } /**