Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"require": {},
"require-dev": {
"behat/behat": "~2.5",
"wp-cli/wp-cli": "*"
"wp-cli/wp-cli": "^1.5"
},
"extra": {
"branch-alias": {
Expand Down
59 changes: 49 additions & 10 deletions features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,9 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface {
private $running_procs = array();

/**
* Array of variables available as {VARIABLE_NAME}. Some are always set: CORE_CONFIG_SETTINGS, SRC_DIR, CACHE_DIR, WP_VERSION-version-latest. Some are step-dependent:
* RUN_DIR, SUITE_CACHE_DIR, COMPOSER_LOCAL_REPOSITORY, PHAR_PATH. Scenarios can define their own variables using "Given save" steps. Variables are reset for each scenario.
* Array of variables available as {VARIABLE_NAME}. Some are always set: CORE_CONFIG_SETTINGS, SRC_DIR, CACHE_DIR, WP_VERSION-version-latest.
* Some are step-dependent: RUN_DIR, SUITE_CACHE_DIR, COMPOSER_LOCAL_REPOSITORY, PHAR_PATH. One is set on use: INVOKE_WP_CLI_WITH_PHP_ARGS-args.
* Scenarios can define their own variables using "Given save" steps. Variables are reset for each scenario.
*/
public $variables = array();

Expand Down Expand Up @@ -117,8 +118,9 @@ private static function get_process_env_variables() {
// Ensure we're using the expected `wp` binary
$bin_dir = getenv( 'WP_CLI_BIN_DIR' ) ?: realpath( __DIR__ . '/../../bin' );
$vendor_dir = realpath( __DIR__ . '/../../vendor/bin' );
$path_separator = Utils\is_windows() ? ';' : ':';
$env = array(
'PATH' => $bin_dir . ':' . $vendor_dir . ':' . getenv( 'PATH' ),
'PATH' => $bin_dir . $path_separator . $vendor_dir . $path_separator . getenv( 'PATH' ),
'BEHAT_RUN' => 1,
'HOME' => sys_get_temp_dir() . '/wp-cli-home',
);
Expand Down Expand Up @@ -328,20 +330,57 @@ public function getHookDefinitionResources() {
}

/**
* Replace {VARIABLE_NAME}. Note that variable names can only contain uppercase letters and underscores (no numbers).
* Replace standard {VARIABLE_NAME} variables and the special {INVOKE_WP_CLI_WITH_PHP_ARGS-args} and {WP_VERSION-version-latest} variables.
* Note that standard variable names can only contain uppercase letters, digits and underscores and cannot begin with a digit.
*/
public function replace_variables( $str ) {
$ret = preg_replace_callback( '/\{([A-Z_]+)\}/', array( $this, '_replace_var' ), $str );
if ( false !== strpos( $str, '{INVOKE_WP_CLI_WITH_PHP_ARGS-' ) ) {
$str = $this->replace_invoke_wp_cli_with_php_args( $str );
}
$str = preg_replace_callback( '/\{([A-Z_][A-Z_0-9]*)\}/', array( $this, 'replace_var' ), $str );
if ( false !== strpos( $str, '{WP_VERSION-' ) ) {
$ret = $this->_replace_wp_versions( $ret );
$str = $this->replace_wp_versions( $str );
}
return $str;
}

/**
* Substitute {INVOKE_WP_CLI_WITH_PHP_ARGS-args} variables.
*/
private function replace_invoke_wp_cli_with_php_args( $str ) {
static $phar_path = null, $shell_path = null;

if ( null === $phar_path ) {
$phar_path = false;
$phar_begin = '#!/usr/bin/env php';
$phar_begin_len = strlen( $phar_begin );
if ( ( $bin_dir = getenv( 'WP_CLI_BIN_DIR' ) ) && file_exists( $bin_dir . '/wp' ) && $phar_begin === file_get_contents( $bin_dir . '/wp', false, null, 0, $phar_begin_len ) ) {
$phar_path = $bin_dir . '/wp';
} else {
$src_dir = dirname( dirname( __DIR__ ) );
$bin_path = $src_dir . '/bin/wp';
$vendor_bin_path = $src_dir . '/vendor/bin/wp';
if ( file_exists( $bin_path ) && is_executable( $bin_path ) ) {
$shell_path = $bin_path;
} elseif ( file_exists( $vendor_bin_path ) && is_executable( $vendor_bin_path ) ) {
$shell_path = $vendor_bin_path;
} else {
$shell_path = 'wp';
}
}
}
return $ret;

$str = preg_replace_callback( '/{INVOKE_WP_CLI_WITH_PHP_ARGS-([^}]*)}/', function ( $matches ) use ( $phar_path, $shell_path ) {
return $phar_path ? "php {$matches[1]} {$phar_path}" : ( 'WP_CLI_PHP_ARGS=' . escapeshellarg( $matches[1] ) . ' ' . $shell_path );
}, $str );

return $str;
}

/**
* Replace variables callback.
*/
private function _replace_var( $matches ) {
private function replace_var( $matches ) {
$cmd = $matches[0];

foreach ( array_slice( $matches, 1 ) as $key ) {
Expand All @@ -352,9 +391,9 @@ private function _replace_var( $matches ) {
}

/**
* Substitute "{WP_VERSION-version-latest}" variables.
* Substitute {WP_VERSION-version-latest} variables.
*/
private function _replace_wp_versions( $str ) {
private function replace_wp_versions( $str ) {
static $wp_versions = null;
if ( null === $wp_versions ) {
$wp_versions = array();
Expand Down
4 changes: 3 additions & 1 deletion features/bootstrap/Process.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace WP_CLI;

use WP_CLI\Utils;

/**
* Run a system process, and learn what happened.
*/
Expand Down Expand Up @@ -67,7 +69,7 @@ private function __construct() {}
public function run() {
$start_time = microtime( true );

$proc = proc_open( $this->command, self::$descriptors, $pipes, $this->cwd, $this->env );
$proc = Utils\proc_open_compat( $this->command, self::$descriptors, $pipes, $this->cwd, $this->env );

$stdout = stream_get_contents( $pipes[1] );
fclose( $pipes[1] );
Expand Down
169 changes: 151 additions & 18 deletions features/bootstrap/utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -383,15 +383,11 @@ function launch_editor_for_input( $input, $filename = 'WP-CLI' ) {

$editor = getenv( 'EDITOR' );
if ( ! $editor ) {
$editor = 'vi';

if ( isset( $_SERVER['OS'] ) && false !== strpos( $_SERVER['OS'], 'indows' ) ) {
$editor = 'notepad';
}
$editor = is_windows() ? 'notepad' : 'vi';
}

$descriptorspec = array( STDIN, STDOUT, STDERR );
$process = proc_open( "$editor " . escapeshellarg( $tmpfile ), $descriptorspec, $pipes );
$process = proc_open_compat( "$editor " . escapeshellarg( $tmpfile ), $descriptorspec, $pipes );
$r = proc_close( $process );
if ( $r ) {
exit( $r );
Expand Down Expand Up @@ -453,7 +449,7 @@ function run_mysql_command( $cmd, $assoc_args, $descriptors = null ) {

$final_cmd = force_env_on_nix_systems( $cmd ) . assoc_args_to_str( $assoc_args );

$proc = proc_open( $final_cmd, $descriptors, $pipes );
$proc = proc_open_compat( $final_cmd, $descriptors, $pipes );
if ( ! $proc ) {
exit( 1 );
}
Expand Down Expand Up @@ -515,14 +511,15 @@ function mustache_render( $template_name, $data = array() ) {
*
* @param string $message Text to display before the progress bar.
* @param integer $count Total number of ticks to be performed.
* @param int $interval Optional. The interval in milliseconds between updates. Default 100.
* @return cli\progress\Bar|WP_CLI\NoOp
*/
function make_progress_bar( $message, $count ) {
function make_progress_bar( $message, $count, $interval = 100 ) {
if ( \cli\Shell::isPiped() ) {
return new \WP_CLI\NoOp;
}

return new \cli\progress\Bar( $message, $count );
return new \cli\progress\Bar( $message, $count, $interval );
}

function parse_url( $url ) {
Expand Down Expand Up @@ -775,6 +772,16 @@ function trailingslashit( $string ) {
return rtrim( $string, '/\\' ) . '/';
}

/**
* Convert Windows EOLs to *nix.
*
* @param string $str String to convert.
* @return string String with carriage return / newline pairs reduced to newlines.
*/
function normalize_eols( $str ) {
return str_replace( "\r\n", "\n", $str );
}

/**
* Get the system's temp directory. Warns user if it isn't writable.
*
Expand All @@ -790,14 +797,8 @@ function get_temp_dir() {
return $temp;
}

$temp = '/tmp/';

// `sys_get_temp_dir()` introduced PHP 5.2.1.
if ( $try = sys_get_temp_dir() ) {
$temp = trailingslashit( $try );
} elseif ( $try = ini_get( 'upload_tmp_dir' ) ) {
$temp = trailingslashit( $try );
}
// `sys_get_temp_dir()` introduced PHP 5.2.1. Will always return something.
$temp = trailingslashit( sys_get_temp_dir() );

if ( ! is_writable( $temp ) ) {
\WP_CLI::warning( "Temp directory isn't writable: {$temp}" );
Expand Down Expand Up @@ -1110,7 +1111,9 @@ function glob_brace( $pattern, $dummy_flags = null ) {
function get_suggestion( $target, array $options, $threshold = 2 ) {

$suggestion_map = array(
'add' => 'create',
'check' => 'check-update',
'capability' => 'cap',
'clear' => 'flush',
'decrement' => 'decr',
'del' => 'delete',
Expand All @@ -1126,10 +1129,11 @@ function get_suggestion( $target, array $options, $threshold = 2 ) {
'regen' => 'regenerate',
'rep' => 'replace',
'repl' => 'replace',
'trash' => 'delete',
'v' => 'version',
);

if ( array_key_exists( $target, $suggestion_map ) ) {
if ( array_key_exists( $target, $suggestion_map ) && in_array( $suggestion_map[ $target ], $options, true ) ) {
return $suggestion_map[ $target ];
}

Expand Down Expand Up @@ -1318,3 +1322,132 @@ function get_php_binary() {

return 'php';
}

/**
* Windows compatible `proc_open()`.
* Works around bug in PHP, and also deals with *nix-like `ENV_VAR=blah cmd` environment variable prefixes.
*
* @access public
*
* @param string $command Command to execute.
* @param array $descriptorspec Indexed array of descriptor numbers and their values.
* @param array &$pipes Indexed array of file pointers that correspond to PHP's end of any pipes that are created.
* @param string $cwd Initial working directory for the command.
* @param array $env Array of environment variables.
* @param array $other_options Array of additional options (Windows only).
*
* @return string Command stripped of any environment variable settings.
*/
function proc_open_compat( $cmd, $descriptorspec, &$pipes, $cwd = null, $env = null, $other_options = null ) {
if ( is_windows() ) {
// Need to encompass the whole command in double quotes - PHP bug https://bugs.php.net/bug.php?id=49139
$cmd = '"' . _proc_open_compat_win_env( $cmd, $env ) . '"';
}
return proc_open( $cmd, $descriptorspec, $pipes, $cwd, $env, $other_options );
}

/**
* For use by `proc_open_compat()` only. Separated out for ease of testing. Windows only.
* Turns *nix-like `ENV_VAR=blah command` environment variable prefixes into stripped `cmd` with prefixed environment variables added to passed in environment array.
*
* @access private
*
* @param string $command Command to execute.
* @param array &$env Array of existing environment variables. Will be modified if any settings in command.
*
* @return string Command stripped of any environment variable settings.
*/
function _proc_open_compat_win_env( $cmd, &$env ) {
if ( false !== strpos( $cmd, '=' ) ) {
while ( preg_match( '/^([A-Za-z_][A-Za-z0-9_]*)=("[^"]*"|[^ ]*) /', $cmd, $matches ) ) {
$cmd = substr( $cmd, strlen( $matches[0] ) );
if ( null === $env ) {
$env = array();
}
$env[ $matches[1] ] = isset( $matches[2][0] ) && '"' === $matches[2][0] ? substr( $matches[2], 1, -1 ) : $matches[2];
}
}
return $cmd;
}

/**
* First half of escaping for LIKE special characters % and _ before preparing for MySQL.
*
* Use this only before wpdb::prepare() or esc_sql(). Reversing the order is very bad for security.
*
* Copied from core "wp-includes/wp-db.php". Avoids dependency on WP 4.4 wpdb.
*
* @access public
*
* @param string $text The raw text to be escaped. The input typed by the user should have no
* extra or deleted slashes.
* @return string Text in the form of a LIKE phrase. The output is not SQL safe. Call $wpdb::prepare()
* or real_escape next.
*/
function esc_like( $text ) {
return addcslashes( $text, '_%\\' );
}

/**
* Escapes (backticks) MySQL identifiers (aka schema object names) - i.e. column names, table names, and database/index/alias/view etc names.
* See https://dev.mysql.com/doc/refman/5.5/en/identifiers.html
*
* @param string|array $idents A single identifier or an array of identifiers.
* @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings.
*/
function esc_sql_ident( $idents ) {
$backtick = function ( $v ) {
// Escape any backticks in the identifier by doubling.
return '`' . str_replace( '`', '``', $v ) . '`';
};
if ( is_string( $idents ) ) {
return $backtick( $idents );
}
return array_map( $backtick, $idents );
}

/**
* Check whether a given string is a valid JSON representation.
*
* @param string $argument String to evaluate.
* @param bool $ignore_scalars Optional. Whether to ignore scalar values.
* Defaults to true.
*
* @return bool Whether the provided string is a valid JSON representation.
*/
function is_json( $argument, $ignore_scalars = true ) {
if ( ! is_string( $argument ) || '' === $argument ) {
return false;
}

if ( $ignore_scalars && ! in_array( $argument[0], array( '{', '[' ), true ) ) {
return false;
}

json_decode( $argument, $assoc = true );

return json_last_error() === JSON_ERROR_NONE;
}

/**
* Parse known shell arrays included in the $assoc_args array.
*
* @param array $assoc_args Associative array of arguments.
* @param array $array_arguments Array of argument keys that should receive an
* array through the shell.
*
* @return array
*/
function parse_shell_arrays( $assoc_args, $array_arguments ) {
if ( empty( $assoc_args ) || empty( $array_arguments ) ) {
return $assoc_args;
}

foreach ( $array_arguments as $key ) {
if ( array_key_exists( $key, $assoc_args ) && is_json( $assoc_args[ $key ] ) ) {
$assoc_args[ $key ] = json_decode( $assoc_args[ $key ], $assoc = true );
}
}

return $assoc_args;
}
17 changes: 12 additions & 5 deletions utils/behat-tags.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,24 @@ function version_tags( $prefix, $current, $operator = '<' ) {
return $skip_tags;
}

$wp_version = getenv( 'WP_VERSION' );
$wp_version_reqs = array();
// Only apply @require-wp tags when WP_VERSION isn't 'latest' or 'nightly'
// 'latest' and 'nightly' are expected to work with all features
if ( ! in_array( getenv( 'WP_VERSION' ), array( 'latest', 'nightly', 'trunk' ), true ) ) {
$wp_version_reqs = version_tags( 'require-wp', getenv( 'WP_VERSION' ), '<' );
// Only apply @require-wp tags when WP_VERSION isn't 'latest', 'nightly' or 'trunk'.
// 'latest', 'nightly' and 'trunk' are expected to work with all features.
if ( $wp_version && ! in_array( $wp_version, array( 'latest', 'nightly', 'trunk' ), true ) ) {
$wp_version_reqs = array_merge(
version_tags( 'require-wp', $wp_version, '<' ),
version_tags( 'less-than-wp', $wp_version, '>=' )
);
} else {
// But make sure @less-than-wp tags always exist for those special cases. (Note: @less-than-wp-latest etc won't work and shouldn't be used).
$wp_version_reqs = array_merge( $wp_version_reqs, version_tags( 'less-than-wp', '9999', '>=' ) );
}

$skip_tags = array_merge(
$wp_version_reqs,
version_tags( 'require-php', PHP_VERSION, '<' ),
version_tags( 'less-than-php', PHP_VERSION, '>' )
version_tags( 'less-than-php', PHP_VERSION, '>=' ) // Note: this was '>' prior to WP-CLI 1.5.0 but the change is unlikely to cause BC issues as usually compared against major.minor only.
);

# Skip Github API tests if `GITHUB_TOKEN` not available because of rate limiting. See https://github.com/wp-cli/wp-cli/issues/1612
Expand Down