diff --git a/features/eval.feature b/features/eval.feature index ba4f2f29..b7e94d47 100644 --- a/features/eval.feature +++ b/features/eval.feature @@ -333,3 +333,25 @@ Feature: Evaluating PHP code and files. Args: arg1 arg2 """ + Scenario: Eval-file with STDIN should work with alias groups + Given a WP installation in 'site1' + And a WP installation in 'site2' + And a wp-cli.yml file: + """ + @group: + - @site1 + - @site2 + @site1: + path: site1 + @site2: + path: site2 + """ + And a stdin-test.php file: + """ + ' . file_get_contents( 'php://stdin' ) ); + // Cache STDIN contents to support alias groups. + // When using alias groups, each site runs in a separate process, + // but they all share the same STDIN. Once STDIN is read, it's consumed. + // We cache it in a temporary file that persists across processes. + if ( null === self::$stdin_cache ) { + // Get a unique identifier for the cache file + // Use parent PID on Unix-like systems for sharing across child processes + // On Windows, this feature may have limitations due to lack of posix_getppid() + if ( function_exists( 'posix_getppid' ) ) { + $cache_id = posix_getppid(); + } else { + // On Windows or systems without POSIX, use an environment variable if available + // or fall back to current PID (which won't share across processes) + $cache_id = getenv( 'WP_CLI_STDIN_CACHE_ID' ) ?: getmypid(); + } + self::$stdin_cache_file = sys_get_temp_dir() . '/wp-cli-eval-stdin-' . $cache_id . '.php'; + + // Check if cache file already exists (created by a previous subprocess) + if ( file_exists( self::$stdin_cache_file ) ) { + $stdin_contents = file_get_contents( self::$stdin_cache_file ); + if ( false === $stdin_contents ) { + WP_CLI::error( 'Failed to read from STDIN cache file.' ); + } + self::$stdin_cache = (string) $stdin_contents; + + // Clean up old cache files (older than 1 hour) to prevent accumulation + self::cleanup_old_cache_files(); + } else { + // First process: read from STDIN and cache it + $stdin_contents = file_get_contents( 'php://stdin' ); + if ( false === $stdin_contents ) { + WP_CLI::error( 'Failed to read from STDIN.' ); + } + self::$stdin_cache = (string) $stdin_contents; + + // Save to cache file for subsequent processes + $write_result = file_put_contents( self::$stdin_cache_file, self::$stdin_cache ); + if ( false === $write_result ) { + WP_CLI::error( 'Failed to write STDIN cache file.' ); + } + + // Clean up old cache files (older than 1 hour) to prevent accumulation + self::cleanup_old_cache_files(); + + // Note: We intentionally don't clean up the current cache file immediately. + // If we delete it in the shutdown function, subsequent child processes + // won't be able to read it. The cleanup_old_cache_files() method handles + // removal of stale cache files. + } + } + eval( '?>' . self::$stdin_cache ); } elseif ( $use_include ) { include $file; } else { @@ -113,4 +181,33 @@ private static function execute_eval( $file, $positional_args, $use_include ) { eval( '?>' . $file_contents ); } } + + /** + * Clean up old STDIN cache files to prevent accumulation. + * + * Removes cache files older than 1 hour from the system temp directory. + * This is called when creating or reading a cache file to ensure stale + * files don't accumulate if processes terminate unexpectedly. + */ + private static function cleanup_old_cache_files() { + $temp_dir = sys_get_temp_dir(); + $cache_pattern = $temp_dir . '/wp-cli-eval-stdin-*.php'; + $cache_files = glob( $cache_pattern ); + $one_hour_ago = time() - 3600; + + if ( ! empty( $cache_files ) ) { + foreach ( $cache_files as $cache_file ) { + // Skip the current cache file + if ( $cache_file === self::$stdin_cache_file ) { + continue; + } + + // Delete files older than 1 hour + $file_time = filemtime( $cache_file ); + if ( false !== $file_time && $file_time < $one_hour_ago ) { + @unlink( $cache_file ); + } + } + } + } }