Skip to content

Commit f5bb17a

Browse files
committed
Simplification
1 parent 23d9bbe commit f5bb17a

1 file changed

Lines changed: 42 additions & 88 deletions

File tree

src/AI_Command.php

Lines changed: 42 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
* WordPress shines bright
3030
*
3131
* # Generate an image from a prompt
32-
* $ wp ai generate image "A futuristic WordPress logo" --output=logo.png
32+
* $ wp ai generate image "A futuristic WordPress logo" --destination-file=logo.png
3333
* Success: Image saved to logo.png
3434
*
3535
* # Check if a prompt is supported
@@ -40,46 +40,6 @@
4040
*/
4141
class AI_Command extends WP_CLI_Command {
4242

43-
/**
44-
* Maximum binary image size in bytes (50MB).
45-
*/
46-
const MAX_IMAGE_SIZE_BYTES = 52428800; // 50 * 1024 * 1024
47-
48-
/**
49-
* Dummy prompt used for capability checking.
50-
*
51-
* This constant provides a consistent prompt value when checking AI capabilities.
52-
* The specific content doesn't affect capability detection, which is based on
53-
* configured providers and their available features.
54-
*/
55-
const CAPABILITY_CHECK_PROMPT = 'capability-check';
56-
57-
/**
58-
* System directories that should be protected from file writes.
59-
*/
60-
const FORBIDDEN_PATHS = array(
61-
// Unix/Linux system directories
62-
'/etc',
63-
'/bin',
64-
'/usr/bin',
65-
'/sbin',
66-
'/usr/sbin',
67-
'/boot',
68-
'/sys',
69-
'/proc',
70-
// Windows system directories (case-insensitive)
71-
'C:\\Windows',
72-
'C:\\Program Files',
73-
'C:\\Program Files (x86)',
74-
);
75-
76-
/**
77-
* Maximum size for base64-encoded image data.
78-
* Base64 encoding increases size by ~33%, so 50MB binary = ~67MB base64.
79-
* Using 70MB as safe upper bound.
80-
*/
81-
const MAX_IMAGE_SIZE_BASE64 = 70000000;
82-
8343
/**
8444
* Generates AI content.
8545
*
@@ -117,9 +77,12 @@ class AI_Command extends WP_CLI_Command {
11777
* [--system-instruction=<instruction>]
11878
* : System instruction to guide the AI's behavior.
11979
*
120-
* [--output=<file>]
80+
* [--destination-file=<file>]
12181
* : For image generation, path to save the generated image.
12282
*
83+
* [--stdout]
84+
* Output the whole image using standard output (incompatible with --destination-file=)
85+
*
12386
* [--format=<format>]
12487
* : Output format for text.
12588
* ---
@@ -150,7 +113,7 @@ class AI_Command extends WP_CLI_Command {
150113
* $ wp ai generate image "A minimalist WordPress logo" --output=wp-logo.png
151114
*
152115
* @param array{0: string, 1: string} $args Positional arguments.
153-
* @param array{model: string, provider: string, temperature: float, 'top-p': float, 'top-k': int, 'max-tokens': int, 'system-instruction': string, output: string, format: string} $assoc_args Associative arguments.
116+
* @param array{model: string, provider: string, temperature: float, 'top-p': float, 'top-k': int, 'max-tokens': int, 'system-instruction': string, 'destination-file': string, stdout: bool, format: string} $assoc_args Associative arguments.
154117
* @return void
155118
*/
156119
public function generate( $args, $assoc_args ) {
@@ -322,7 +285,7 @@ public function status( $args, $assoc_args ) {
322285

323286
try {
324287
// Create a builder to check capabilities (using constant for consistency)
325-
$builder = AI_Client::prompt( self::CAPABILITY_CHECK_PROMPT );
288+
$builder = AI_Client::prompt();
326289

327290
// Check each capability
328291
$capabilities = array(
@@ -388,8 +351,8 @@ private function generate_text( $builder, $assoc_args ) {
388351
/**
389352
* Generates an image from the prompt builder.
390353
*
391-
* @param \WordPress\AI_Client\Builders\Prompt_Builder $builder The prompt builder.
392-
* @param array{output: string} $assoc_args Associative arguments.
354+
* @param \WordPress\AI_Client\Builders\Prompt_Builder $builder The prompt builder.
355+
* @param array{'destination-file': string, stdout: bool} $assoc_args Associative arguments.
393356
* @return void
394357
*/
395358
private function generate_image( $builder, $assoc_args ) {
@@ -398,75 +361,66 @@ private function generate_image( $builder, $assoc_args ) {
398361
WP_CLI::error( 'Image generation is not supported. Make sure AI provider credentials are configured.' );
399362
}
400363

401-
$image_file = $builder->generate_image();
402-
403-
if ( isset( $assoc_args['output'] ) ) {
404-
$output_path = $assoc_args['output'];
364+
if ( ! empty( $assoc_args['stdout'] ) && ! empty( $assoc_args['dir'] ) ) {
365+
WP_CLI::error( '--stdout and --destination-file cannot be used together.' );
366+
}
405367

406-
// Resolve the full real path
407-
$parent_dir = dirname( $output_path );
368+
if ( isset( $assoc_args['destination-file'] ) ) {
369+
$output_path = $assoc_args['destination-file'];
370+
$parent_dir = dirname( $output_path );
408371

409-
// Check if parent directory exists
410-
if ( ! file_exists( $parent_dir ) || ! is_dir( $parent_dir ) ) {
372+
if ( ! is_dir( $parent_dir ) ) {
411373
WP_CLI::error( 'Invalid output directory. Directory does not exist: ' . $parent_dir );
412374
}
375+
}
413376

414-
// Resolve the real path to prevent traversal attacks
415-
$real_parent_dir = realpath( $parent_dir );
416-
if ( false === $real_parent_dir ) {
417-
WP_CLI::error( 'Cannot resolve output directory path.' );
418-
}
419-
420-
// Reconstruct the output path with the resolved parent directory
421-
$safe_output_path = $real_parent_dir . DIRECTORY_SEPARATOR . basename( $output_path );
422-
423-
// Prevent writing to sensitive system directories
424-
foreach ( self::FORBIDDEN_PATHS as $forbidden ) {
425-
// Use case-sensitive check for Unix paths (start with /), case-insensitive for Windows (contain :\)
426-
$is_windows_path = ( false !== strpos( $forbidden, ':\\' ) );
427-
$matches = $is_windows_path
428-
? ( 0 === stripos( $real_parent_dir, $forbidden ) )
429-
: ( 0 === strpos( $real_parent_dir, $forbidden ) );
377+
$image_file = $builder->generate_image();
430378

431-
if ( $matches ) {
432-
WP_CLI::error( 'Cannot write to system directory: ' . $safe_output_path );
433-
}
434-
}
379+
if ( isset( $assoc_args['destination-file'] ) ) {
380+
$output_path = $assoc_args['destination-file'];
381+
$output_path = realpath( dirname( $output_path ) ) . DIRECTORY_SEPARATOR . basename( $output_path );
435382

436-
// Get the image content from data URI
437383
$data_uri = $image_file->getDataUri();
438384

439-
// Extract base64 data from data URI
440385
$data_parts = $data_uri ? explode( ',', $data_uri, 2 ) : [];
441386
if ( count( $data_parts ) !== 2 ) {
442387
WP_CLI::error( 'Invalid image data received.' );
443388
}
444389

445-
// Validate and decode base64 data
446390
$base64_data = $data_parts[1];
447391

448-
// Check reasonable size limit
449-
if ( strlen( $base64_data ) > self::MAX_IMAGE_SIZE_BASE64 ) {
450-
WP_CLI::error( 'Image data exceeds maximum size limit (50MB).' );
451-
}
452-
453-
// Try strict base64 decode - this validates format
454392
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
455393
$image_data = base64_decode( $base64_data, true );
456394
if ( false === $image_data ) {
457395
WP_CLI::error( 'Invalid base64 image data format.' );
458396
}
459397

460398
// Save to file
461-
$result = file_put_contents( $safe_output_path, $image_data );
399+
$result = file_put_contents( $output_path, $image_data );
462400
if ( false === $result ) {
463-
WP_CLI::error( 'Failed to save image to ' . $safe_output_path );
401+
WP_CLI::error( 'Failed to save image to ' . $output_path );
402+
}
403+
404+
WP_CLI::success( 'Image saved to ' . $output_path );
405+
} elseif ( $assoc_args['stdout'] ) {
406+
$data_uri = $image_file->getDataUri();
407+
408+
$data_parts = $data_uri ? explode( ',', $data_uri, 2 ) : [];
409+
if ( count( $data_parts ) !== 2 ) {
410+
WP_CLI::error( 'Invalid image data received.' );
411+
}
412+
413+
$base64_data = $data_parts[1];
414+
415+
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
416+
$image_data = base64_decode( $base64_data, true );
417+
if ( false === $image_data ) {
418+
WP_CLI::error( 'Invalid base64 image data format.' );
464419
}
465420

466-
WP_CLI::success( 'Image saved to ' . $safe_output_path );
421+
WP_CLI::log( $image_data );
467422
} else {
468-
// Output data URI
469-
WP_CLI::success( 'Image generated (data URI):' );
423+
WP_CLI::success( 'Image generated:' );
470424
WP_CLI::line( (string) $image_file->getDataUri() );
471425
}
472426
}

0 commit comments

Comments
 (0)