harshadyeola
9 years ago
31 changed files with 5407 additions and 0 deletions
@ -0,0 +1 @@ |
|||
0.1-alpha |
@ -0,0 +1,46 @@ |
|||
#!/usr/bin/env sh |
|||
# |
|||
# This wrapper script has been adapted from the equivalent drush wrapper |
|||
# and 99.9% of all credit should go to the authors of that project: |
|||
# http://drupal.org/project/drush |
|||
# And 0.09% to the author of this project: |
|||
# https://github.com/88mph/wpadmin/blob/master/wpadmin.php |
|||
|
|||
# Get the absolute path of this executable |
|||
ORIGDIR="$(pwd)" |
|||
SELF_PATH="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" && SELF_PATH="$SELF_PATH/$(basename -- "$0")" |
|||
|
|||
# Resolve symlinks - this is the equivalent of "readlink -f", but also works with non-standard OS X readlink. |
|||
while [ -h "$SELF_PATH" ]; do |
|||
# 1) cd to directory of the symlink |
|||
# 2) cd to the directory of where the symlink points |
|||
# 3) Get the pwd |
|||
# 4) Append the basename |
|||
DIR="$(dirname -- "$SELF_PATH")" |
|||
SYM="$(readlink "$SELF_PATH")" |
|||
SELF_PATH="$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")" |
|||
done |
|||
cd "$ORIGDIR" |
|||
|
|||
# Build the path to the root PHP file |
|||
SCRIPT_PATH="$(dirname "$SELF_PATH")/../php/boot-fs.php" |
|||
|
|||
case $(uname -a) in |
|||
CYGWIN*) |
|||
SCRIPT_PATH="$(cygpath -w -a -- "$SCRIPT_PATH")" ;; |
|||
esac |
|||
|
|||
if [ ! -z "$EE_CLI_PHP" ] ; then |
|||
# Use the EE_CLI_PHP environment variable if it is available. |
|||
php="$EE_CLI_PHP" |
|||
else |
|||
# Default to using the php that we find on the PATH. |
|||
# Note that we need the full path to php here for Dreamhost, which behaves oddly. See http://drupal.org/node/662926 |
|||
php="`which php`" |
|||
fi |
|||
|
|||
# Pass in the path to php so that wp-cli knows which one |
|||
# to use if it re-launches itself to run other commands. |
|||
export EE_CLI_PHP_USED="$php" |
|||
|
|||
exec "$php" $EE_CLI_PHP_ARGS "$SCRIPT_PATH" "$@" |
@ -0,0 +1,32 @@ |
|||
{ |
|||
"name": "rtCamp/easyengine", |
|||
"description": "A command line interface for EasyEngine", |
|||
"keywords": [ "cli", "EasyEngine" ], |
|||
"homepage": "http://easyengine.io", |
|||
"license": "MIT", |
|||
"bin": [ |
|||
"bin/ee" |
|||
], |
|||
"require": { |
|||
"php": ">=5.3.2", |
|||
"wp-cli/php-cli-tools": "0.10.5", |
|||
"mustache/mustache": "~2.4", |
|||
"composer/semver": "1.0.0", |
|||
"ramsey/array_column": "~1.1", |
|||
"rmccue/requests": "~1.6", |
|||
"symfony/finder": "~2.3", |
|||
"nb/oxymel": "0.1.0" |
|||
}, |
|||
"require-dev": { |
|||
"phpunit/phpunit": "3.7.*", |
|||
"behat/behat": "2.5.*" |
|||
}, |
|||
"suggest": { |
|||
"psy/psysh": "Enhanced `wp shell` functionality" |
|||
}, |
|||
"autoload": { |
|||
"psr-0": { "EE_CLI": "php" }, |
|||
"files": [ "php/Spyc.php" ], |
|||
"classmap": [ "php/export" ] |
|||
} |
|||
} |
@ -0,0 +1,223 @@ |
|||
<?php |
|||
|
|||
namespace EE_CLI; |
|||
|
|||
/** |
|||
* Handles file- and runtime-based configuration values. |
|||
* |
|||
* @package EE_CLI |
|||
*/ |
|||
class Configurator { |
|||
|
|||
/** |
|||
* @var array $spec Configurator argument specification. |
|||
*/ |
|||
private $spec; |
|||
|
|||
/** |
|||
* @var array $config Values for keys defined in Configurator spec. |
|||
*/ |
|||
private $config = array(); |
|||
|
|||
/** |
|||
* @var array $extra_config Extra config values not specified in spec. |
|||
*/ |
|||
private $extra_config = array(); |
|||
|
|||
/** |
|||
* @param string $path Path to config spec file. |
|||
*/ |
|||
function __construct( $path ) { |
|||
$this->spec = include $path; |
|||
|
|||
$defaults = array( |
|||
'runtime' => false, |
|||
'file' => false, |
|||
'synopsis' => '', |
|||
'default' => null, |
|||
'multiple' => false, |
|||
); |
|||
|
|||
foreach ( $this->spec as $key => &$details ) { |
|||
$details = array_merge( $defaults, $details ); |
|||
|
|||
$this->config[ $key ] = $details['default']; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Get declared configuration values as an array. |
|||
* |
|||
* @return array |
|||
*/ |
|||
function to_array() { |
|||
return array( $this->config, $this->extra_config ); |
|||
} |
|||
|
|||
/** |
|||
* Get configuration specification, i.e. list of accepted keys. |
|||
* |
|||
* @return array |
|||
*/ |
|||
function get_spec() { |
|||
return $this->spec; |
|||
} |
|||
|
|||
/** |
|||
* Splits a list of arguments into positional, associative and config. |
|||
* |
|||
* @param array(string) |
|||
* @return array(array) |
|||
*/ |
|||
public function parse_args( $arguments ) { |
|||
list( $positional_args, $mixed_args ) = self::extract_assoc( $arguments ); |
|||
list( $assoc_args, $runtime_config ) = $this->unmix_assoc_args( $mixed_args ); |
|||
return array( $positional_args, $assoc_args, $runtime_config ); |
|||
} |
|||
|
|||
/** |
|||
* Splits positional args from associative args. |
|||
* |
|||
* @param array |
|||
* @return array(array) |
|||
*/ |
|||
public static function extract_assoc( $arguments ) { |
|||
$positional_args = $assoc_args = array(); |
|||
|
|||
foreach ( $arguments as $arg ) { |
|||
if ( preg_match( '|^--no-([^=]+)$|', $arg, $matches ) ) { |
|||
$assoc_args[] = array( $matches[1], false ); |
|||
} elseif ( preg_match( '|^--([^=]+)$|', $arg, $matches ) ) { |
|||
$assoc_args[] = array( $matches[1], true ); |
|||
} elseif ( preg_match( '|^--([^=]+)=(.*)|s', $arg, $matches ) ) { |
|||
$assoc_args[] = array( $matches[1], $matches[2] ); |
|||
} else { |
|||
$positional_args[] = $arg; |
|||
} |
|||
} |
|||
|
|||
return array( $positional_args, $assoc_args ); |
|||
} |
|||
|
|||
/** |
|||
* Separate runtime parameters from command-specific parameters. |
|||
* |
|||
* @param array $mixed_args |
|||
* @return array |
|||
*/ |
|||
private function unmix_assoc_args( $mixed_args ) { |
|||
$assoc_args = $runtime_config = array(); |
|||
|
|||
foreach ( $mixed_args as $tmp ) { |
|||
list( $key, $value ) = $tmp; |
|||
|
|||
if ( isset( $this->spec[ $key ] ) && $this->spec[ $key ]['runtime'] !== false ) { |
|||
$details = $this->spec[ $key ]; |
|||
|
|||
if ( isset( $details['deprecated'] ) ) { |
|||
fwrite( STDERR, "WP-CLI: The --{$key} global parameter is deprecated. {$details['deprecated']}\n" ); |
|||
} |
|||
|
|||
if ( $details['multiple'] ) { |
|||
$runtime_config[ $key ][] = $value; |
|||
} else { |
|||
$runtime_config[ $key ] = $value; |
|||
} |
|||
} else { |
|||
$assoc_args[ $key ] = $value; |
|||
} |
|||
} |
|||
|
|||
return array( $assoc_args, $runtime_config ); |
|||
} |
|||
|
|||
/** |
|||
* Load a YAML file of parameters into scope. |
|||
* |
|||
* @param string $path Path to YAML file. |
|||
*/ |
|||
public function merge_yml( $path ) { |
|||
foreach ( self::load_yml( $path ) as $key => $value ) { |
|||
if ( !isset( $this->spec[ $key ] ) || false === $this->spec[ $key ]['file'] ) { |
|||
$this->extra_config[ $key ] = $value; |
|||
} elseif ( $this->spec[ $key ]['multiple'] ) { |
|||
self::arrayify( $value ); |
|||
$this->config[ $key ] = array_merge( $this->config[ $key ], $value ); |
|||
} else { |
|||
$this->config[ $key ] = $value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Merge an array of values into the configurator config. |
|||
* |
|||
* @param array $config |
|||
*/ |
|||
public function merge_array( $config ) { |
|||
foreach ( $this->spec as $key => $details ) { |
|||
if ( false !== $details['runtime'] && isset( $config[ $key ] ) ) { |
|||
$value = $config[ $key ]; |
|||
|
|||
if ( $details['multiple'] ) { |
|||
self::arrayify( $value ); |
|||
$this->config[ $key ] = array_merge( $this->config[ $key ], $value ); |
|||
} else { |
|||
$this->config[ $key ] = $value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Load values from a YAML file. |
|||
* |
|||
* @param string $yml_file Path to the YAML file |
|||
* @return array $config Declared configuration values |
|||
*/ |
|||
private static function load_yml( $yml_file ) { |
|||
if ( !$yml_file ) |
|||
return array(); |
|||
|
|||
$config = spyc_load_file( $yml_file ); |
|||
|
|||
// Make sure config-file-relative paths are made absolute. |
|||
$yml_file_dir = dirname( $yml_file ); |
|||
|
|||
if ( isset( $config['path'] ) ) |
|||
self::absolutize( $config['path'], $yml_file_dir ); |
|||
|
|||
if ( isset( $config['require'] ) ) { |
|||
self::arrayify( $config['require'] ); |
|||
foreach ( $config['require'] as &$path ) { |
|||
self::absolutize( $path, $yml_file_dir ); |
|||
} |
|||
} |
|||
|
|||
return $config; |
|||
} |
|||
|
|||
/** |
|||
* Conform a variable to an array. |
|||
* |
|||
* @param mixed $val A string or an array |
|||
*/ |
|||
private static function arrayify( &$val ) { |
|||
if ( !is_array( $val ) ) { |
|||
$val = array( $val ); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Make a path absolute. |
|||
* |
|||
* @param string $path Path to file. |
|||
* @param string $base Base path to prepend. |
|||
*/ |
|||
private static function absolutize( &$path, $base ) { |
|||
if ( !empty( $path ) && !\EE_CLI\Utils\is_path_absolute( $path ) ) { |
|||
$path = $base . DIRECTORY_SEPARATOR . $path; |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,93 @@ |
|||
<?php |
|||
|
|||
namespace EE_CLI\Dispatcher; |
|||
|
|||
/** |
|||
* Creates CompositeCommand or Subcommand instances. |
|||
* |
|||
* @package EE_CLI |
|||
*/ |
|||
class CommandFactory { |
|||
|
|||
/** |
|||
* Create a new CompositeCommand (or Subcommand if class has __invoke()) |
|||
* |
|||
* @param string $name Represents how the command should be invoked |
|||
* @param string $class A subclass of EE_CLI_Command |
|||
* @param mixed $parent The new command's parent Composite (or Root) command |
|||
*/ |
|||
public static function create( $name, $class, $parent ) { |
|||
$reflection = new \ReflectionClass( $class ); |
|||
|
|||
if ( $reflection->hasMethod( '__invoke' ) ) { |
|||
$command = self::create_subcommand( $parent, $name, $reflection->name, |
|||
$reflection->getMethod( '__invoke' ) ); |
|||
} else { |
|||
$command = self::create_composite_command( $parent, $name, $reflection ); |
|||
} |
|||
|
|||
return $command; |
|||
} |
|||
|
|||
/** |
|||
* Create a new Subcommand instance. |
|||
* |
|||
* @param mixed $parent The new command's parent Composite command |
|||
* @param string $name Represents how the command should be invoked |
|||
* @param string $class A subclass of EE_CLI_Command |
|||
* @param string $method Class method to be called upon invocation. |
|||
*/ |
|||
private static function create_subcommand( $parent, $name, $class_name, $method ) { |
|||
$docparser = new \EE_CLI\DocParser( $method->getDocComment() ); |
|||
|
|||
if ( !$name ) |
|||
$name = $docparser->get_tag( 'subcommand' ); |
|||
|
|||
if ( !$name ) |
|||
$name = $method->name; |
|||
|
|||
$method_name = $method->name; |
|||
|
|||
$when_invoked = function ( $args, $assoc_args ) use ( $class_name, $method_name ) { |
|||
call_user_func( array( new $class_name, $method_name ), $args, $assoc_args ); |
|||
}; |
|||
|
|||
return new Subcommand( $parent, $name, $docparser, $when_invoked ); |
|||
} |
|||
|
|||
/** |
|||
* Create a new Composite command instance. |
|||
* |
|||
* @param mixed $parent The new command's parent Root or Composite command |
|||
* @param string $name Represents how the command should be invoked |
|||
* @param ReflectionClass $reflection |
|||
*/ |
|||
private static function create_composite_command( $parent, $name, $reflection ) { |
|||
$docparser = new \EE_CLI\DocParser( $reflection->getDocComment() ); |
|||
|
|||
$container = new CompositeCommand( $parent, $name, $docparser ); |
|||
|
|||
foreach ( $reflection->getMethods() as $method ) { |
|||
if ( !self::is_good_method( $method ) ) |
|||
continue; |
|||
|
|||
$subcommand = self::create_subcommand( $container, false, $reflection->name, $method ); |
|||
|
|||
$subcommand_name = $subcommand->get_name(); |
|||
|
|||
$container->add_subcommand( $subcommand_name, $subcommand ); |
|||
} |
|||
|
|||
return $container; |
|||
} |
|||
|
|||
/** |
|||
* Check whether a method is actually callable. |
|||
* |
|||
* @param ReflectionMethod $method |
|||
* @return bool |
|||
*/ |
|||
private static function is_good_method( $method ) { |
|||
return $method->isPublic() && !$method->isStatic() && 0 !== strpos( $method->getName(), '__' ); |
|||
} |
|||
} |
@ -0,0 +1,261 @@ |
|||
<?php |
|||
|
|||
namespace EE_CLI\Dispatcher; |
|||
|
|||
use \EE_CLI\Utils; |
|||
|
|||
/** |
|||
* A non-leaf node in the command tree. |
|||
* Contains one or more Subcommands. |
|||
* |
|||
* @package EE_CLI |
|||
*/ |
|||
class CompositeCommand { |
|||
|
|||
protected $name, $shortdesc, $synopsis, $docparser; |
|||
|
|||
protected $parent, $subcommands = array(); |
|||
|
|||
/** |
|||
* Instantiate a new CompositeCommand |
|||
* |
|||
* @param mixed $parent Parent command (either Root or Composite) |
|||
* @param string $name Represents how command should be invoked |
|||
* @param \EE_CLI\DocParser |
|||
*/ |
|||
public function __construct( $parent, $name, $docparser ) { |
|||
$this->parent = $parent; |
|||
|
|||
$this->name = $name; |
|||
|
|||
$this->shortdesc = $docparser->get_shortdesc(); |
|||
$this->longdesc = $docparser->get_longdesc(); |
|||
$this->longdesc .= $this->get_global_params(); |
|||
$this->docparser = $docparser; |
|||
|
|||
$when_to_invoke = $docparser->get_tag( 'when' ); |
|||
if ( $when_to_invoke ) { |
|||
\EE_CLI::get_runner()->register_early_invoke( $when_to_invoke, $this ); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Get the parent composite (or root) command |
|||
* |
|||
* @return mixed |
|||
*/ |
|||
public function get_parent() { |
|||
return $this->parent; |
|||
} |
|||
|
|||
/** |
|||
* Add a named subcommand to this composite command's |
|||
* set of contained subcommands. |
|||
* |
|||
* @param string $name Represents how subcommand should be invoked |
|||
* @param \EE_CLI\Dispatcher\Subcommand |
|||
*/ |
|||
public function add_subcommand( $name, $command ) { |
|||
$this->subcommands[ $name ] = $command; |
|||
} |
|||
|
|||
/** |
|||
* Composite commands always contain subcommands. |
|||
* |
|||
* @return true |
|||
*/ |
|||
public function can_have_subcommands() { |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Get the subcommands contained by this composite |
|||
* command. |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function get_subcommands() { |
|||
ksort( $this->subcommands ); |
|||
|
|||
return $this->subcommands; |
|||
} |
|||
|
|||
/** |
|||
* Get the name of this composite command. |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function get_name() { |
|||
return $this->name; |
|||
} |
|||
|
|||
/** |
|||
* Get the short description for this composite |
|||
* command. |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function get_shortdesc() { |
|||
return $this->shortdesc; |
|||
} |
|||
|
|||
/** |
|||
* Get the long description for this composite |
|||
* command. |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function get_longdesc() { |
|||
return $this->longdesc; |
|||
} |
|||
|
|||
/** |
|||
* Get the synopsis for this composite command. |
|||
* As a collection of subcommands, the composite |
|||
* command is only intended to invoke those |
|||
* subcommands. |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function get_synopsis() { |
|||
return '<command>'; |
|||
} |
|||
|
|||
/** |
|||
* Get the usage for this composite command. |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function get_usage( $prefix ) { |
|||
return sprintf( "%s%s %s", |
|||
$prefix, |
|||
implode( ' ', get_path( $this ) ), |
|||
$this->get_synopsis() |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Show the usage for all subcommands contained |
|||
* by the composite command. |
|||
*/ |
|||
public function show_usage() { |
|||
$methods = $this->get_subcommands(); |
|||
|
|||
$i = 0; |
|||
|
|||
foreach ( $methods as $name => $subcommand ) { |
|||
$prefix = ( 0 == $i++ ) ? 'usage: ' : ' or: '; |
|||
|
|||
if ( \EE_CLI::get_runner()->is_command_disabled( $subcommand ) ) { |
|||
continue; |
|||
} |
|||
|
|||
\EE_CLI::line( $subcommand->get_usage( $prefix ) ); |
|||
} |
|||
|
|||
$cmd_name = implode( ' ', array_slice( get_path( $this ), 1 ) ); |
|||
|
|||
\EE_CLI::line(); |
|||
\EE_CLI::line( "See 'wp help $cmd_name <command>' for more information on a specific command." ); |
|||
} |
|||
|
|||
/** |
|||
* When a composite command is invoked, it shows usage |
|||
* docs for its subcommands. |
|||
* |
|||
* @param array $args |
|||
* @param array $assoc_args |
|||
* @param array $extra_args |
|||
*/ |
|||
public function invoke( $args, $assoc_args, $extra_args ) { |
|||
$this->show_usage(); |
|||
} |
|||
|
|||
/** |
|||
* Given supplied arguments, find a contained |
|||
* subcommand |
|||
* |
|||
* @param array $args |
|||
* @return \EE_CLI\Dispatcher\Subcommand|false |
|||
*/ |
|||
public function find_subcommand( &$args ) { |
|||
$name = array_shift( $args ); |
|||
|
|||
$subcommands = $this->get_subcommands(); |
|||
|
|||
if ( !isset( $subcommands[ $name ] ) ) { |
|||
$aliases = self::get_aliases( $subcommands ); |
|||
|
|||
if ( isset( $aliases[ $name ] ) ) { |
|||
$name = $aliases[ $name ]; |
|||
} |
|||
} |
|||
|
|||
if ( !isset( $subcommands[ $name ] ) ) |
|||
return false; |
|||
|
|||
return $subcommands[ $name ]; |
|||
} |
|||
|
|||
/** |
|||
* Get any registered aliases for this composite command's |
|||
* subcommands. |
|||
* |
|||
* @param array $subcommands |
|||
* @return array |
|||
*/ |
|||
private static function get_aliases( $subcommands ) { |
|||
$aliases = array(); |
|||
|
|||
foreach ( $subcommands as $name => $subcommand ) { |
|||
$alias = $subcommand->get_alias(); |
|||
if ( $alias ) |
|||
$aliases[ $alias ] = $name; |
|||
} |
|||
|
|||
return $aliases; |
|||
} |
|||
|
|||
/** |
|||
* Composite commands can only be known by one name. |
|||
* |
|||
* @return false |
|||
*/ |
|||
public function get_alias() { |
|||
return false; |
|||
} |
|||
|
|||
/*** |
|||
* Get the list of global parameters |
|||
* |
|||
* @param string $root_command whether to include or not root command specific description |
|||
* @return string |
|||
*/ |
|||
protected function get_global_params( $root_command = false ) { |
|||
$binding = array(); |
|||
$binding['root_command'] = $root_command; |
|||
|
|||
foreach ( \EE_CLI::get_configurator()->get_spec() as $key => $details ) { |
|||
if ( false === $details['runtime'] ) |
|||
continue; |
|||
|
|||
if ( isset( $details['deprecated'] ) ) |
|||
continue; |
|||
|
|||
if ( isset( $details['hidden'] ) ) |
|||
continue; |
|||
|
|||
if ( true === $details['runtime'] ) |
|||
$synopsis = "--[no-]$key"; |
|||
else |
|||
$synopsis = "--$key" . $details['runtime']; |
|||
|
|||
$binding['parameters'][] = array( |
|||
'synopsis' => $synopsis, |
|||
'desc' => $details['desc'] |
|||
); |
|||
} |
|||
|
|||
return Utils\mustache_render( 'man-params.mustache', $binding ); |
|||
} |
|||
} |
@ -0,0 +1,60 @@ |
|||
<?php |
|||
|
|||
namespace EE_CLI\Dispatcher; |
|||
|
|||
use \EE_CLI\Utils; |
|||
|
|||
/** |
|||
* The root node in the command tree. |
|||
* |
|||
* @package EE_CLI |
|||
*/ |
|||
class RootCommand extends CompositeCommand { |
|||
|
|||
public function __construct() { |
|||
$this->parent = false; |
|||
|
|||
$this->name = 'wp'; |
|||
|
|||
$this->shortdesc = 'Manage WordPress through the command-line.'; |
|||
} |
|||
|
|||
/** |
|||
* Get the human-readable long description. |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function get_longdesc() { |
|||
return $this->get_global_params( true ); |
|||
} |
|||
|
|||
/** |
|||
* Find a subcommand registered on the root |
|||
* command. |
|||
* |
|||
* @param array $args |
|||
* @return \EE_CLI\Dispatcher\Subcommand|false |
|||
*/ |
|||
public function find_subcommand( &$args ) { |
|||
$command = array_shift( $args ); |
|||
|
|||
Utils\load_command( $command ); |
|||
|
|||
if ( !isset( $this->subcommands[ $command ] ) ) { |
|||
return false; |
|||
} |
|||
|
|||
return $this->subcommands[ $command ]; |
|||
} |
|||
|
|||
/** |
|||
* Get all registered subcommands. |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function get_subcommands() { |
|||
Utils\load_all_commands(); |
|||
|
|||
return parent::get_subcommands(); |
|||
} |
|||
} |
@ -0,0 +1,299 @@ |
|||
<?php |
|||
|
|||
namespace EE_CLI\Dispatcher; |
|||
|
|||
/** |
|||
* A leaf node in the command tree. |
|||
* |
|||
* @package EE_CLI |
|||
*/ |
|||
class Subcommand extends CompositeCommand { |
|||
|
|||
private $alias; |
|||
|
|||
private $when_invoked; |
|||
|
|||
function __construct( $parent, $name, $docparser, $when_invoked ) { |
|||
parent::__construct( $parent, $name, $docparser ); |
|||
|
|||
$this->when_invoked = $when_invoked; |
|||
|
|||
$this->alias = $docparser->get_tag( 'alias' ); |
|||
|
|||
$this->synopsis = $docparser->get_synopsis(); |
|||
if ( !$this->synopsis && $this->longdesc ) { |
|||
$this->synopsis = self::extract_synopsis( $this->longdesc ); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Extract the synopsis from PHPdoc string. |
|||
* |
|||
* @param string $longdesc Command docs via PHPdoc |
|||
* @return string |
|||
*/ |
|||
private static function extract_synopsis( $longdesc ) { |
|||
preg_match_all( '/(.+?)[\r\n]+:/', $longdesc, $matches ); |
|||
return implode( ' ', $matches[1] ); |
|||
} |
|||
|
|||
/** |
|||
* Subcommands can't have subcommands because they |
|||
* represent code to be executed. |
|||
* |
|||
* @return bool |
|||
*/ |
|||
function can_have_subcommands() { |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Get the synopsis string for this subcommand. |
|||
* A synopsis defines what runtime arguments are |
|||
* expected, useful to humans and argument validation. |
|||
* |
|||
* @return string |
|||
*/ |
|||
function get_synopsis() { |
|||
return $this->synopsis; |
|||
} |
|||
|
|||
/** |
|||
* If an alias is set, grant access to it. |
|||
* Aliases permit subcommands to be instantiated |
|||
* with a secondary identity. |
|||
* |
|||
* @return string |
|||
*/ |
|||
function get_alias() { |
|||
return $this->alias; |
|||
} |
|||
|
|||
/** |
|||
* Print the usage details to the end user. |
|||
* |
|||
* @param string $prefix |
|||
*/ |
|||
function show_usage( $prefix = 'usage: ' ) { |
|||
\EE_CLI::line( $this->get_usage( $prefix ) ); |
|||
} |
|||
|
|||
/** |
|||
* Get the usage of the subcommand as a formatted string. |
|||
* |
|||
* @param string $prefix |
|||
* @return string |
|||
*/ |
|||
function get_usage( $prefix ) { |
|||
return sprintf( "%s%s %s", |
|||
$prefix, |
|||
implode( ' ', get_path( $this ) ), |
|||
$this->get_synopsis() |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Wrapper for CLI Tools' prompt() method. |
|||
* |
|||
* @param string $question |
|||
* @param string $default |
|||
* @return string|false |
|||
*/ |
|||
private function prompt( $question, $default ) { |
|||
|
|||
try { |
|||
$response = \cli\prompt( $question, $default ); |
|||
} catch( \Exception $e ) { |
|||
\EE_CLI::line(); |
|||
return false; |
|||
} |
|||
|
|||
return $response; |
|||
} |
|||
|
|||
/** |
|||
* Interactively prompt the user for input |
|||
* based on defined synopsis and passed arguments. |
|||
* |
|||
* @param array $args |
|||
* @param array $assoc_args |
|||
* @return array |
|||
*/ |
|||
private function prompt_args( $args, $assoc_args ) { |
|||
|
|||
$synopsis = $this->get_synopsis(); |
|||
|
|||
if ( ! $synopsis ) |
|||
return array( $args, $assoc_args ); |
|||
|
|||
$spec = array_filter( \EE_CLI\SynopsisParser::parse( $synopsis ), function( $spec_arg ) { |
|||
return in_array( $spec_arg['type'], array( 'generic', 'positional', 'assoc', 'flag' ) ); |
|||
}); |
|||
|
|||
$spec = array_values( $spec ); |
|||
|
|||
// 'positional' arguments are positional (aka zero-indexed) |
|||
// so $args needs to be reset before prompting for new arguments |
|||
$args = array(); |
|||
foreach( $spec as $key => $spec_arg ) { |
|||
|
|||
$current_prompt = ( $key + 1 ) . '/' . count( $spec ) . ' '; |
|||
$default = ( $spec_arg['optional'] ) ? '' : false; |
|||
|
|||
// 'generic' permits arbitrary key=value (e.g. [--<field>=<value>] ) |
|||
if ( 'generic' == $spec_arg['type'] ) { |
|||
|
|||
list( $key_token, $value_token ) = explode( '=', $spec_arg['token'] ); |
|||
|
|||
$repeat = false; |
|||
do { |
|||
if ( ! $repeat ) |
|||
$key_prompt = $current_prompt . $key_token; |
|||
else |
|||
$key_prompt = str_repeat( " ", strlen( $current_prompt ) ) . $key_token; |
|||
|
|||
$key = $this->prompt( $key_prompt, $default ); |
|||
if ( false === $key ) |
|||
return array( $args, $assoc_args ); |
|||
|
|||
if ( $key ) { |
|||
$key_prompt_count = strlen( $key_prompt ) - strlen( $value_token ) - 1; |
|||
$value_prompt = str_repeat( " ", $key_prompt_count ) . '=' . $value_token; |
|||
|
|||
$value = $this->prompt( $value_prompt, $default ); |
|||
if ( false === $value ) |
|||
return array( $args, $assoc_args ); |
|||
|
|||
$assoc_args[$key] = $value; |
|||
|
|||
$repeat = true; |
|||
$required = false; |
|||
} else { |
|||
$repeat = false; |
|||
} |
|||
|
|||
} while( $required || $repeat ); |
|||
|
|||
} else { |
|||
|
|||
$prompt = $current_prompt . $spec_arg['token']; |
|||
if ( 'flag' == $spec_arg['type'] ) |
|||
$prompt .= ' (Y/n)'; |
|||
|
|||
$response = $this->prompt( $prompt, $default ); |
|||
if ( false === $response ) |
|||
return array( $args, $assoc_args ); |
|||
|
|||
if ( $response ) { |
|||
switch ( $spec_arg['type'] ) { |
|||
case 'positional': |
|||
if ( $spec_arg['repeating'] ) |
|||
$response = explode( ' ', $response ); |
|||
else |
|||
$response = array( $response ); |
|||
$args = array_merge( $args, $response ); |
|||
break; |
|||
case 'assoc': |
|||
$assoc_args[$spec_arg['name']] = $response; |
|||
break; |
|||
case 'flag': |
|||
if ( 'Y' == $response ) |
|||
$assoc_args[$spec_arg['name']] = true; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return array( $args, $assoc_args ); |
|||
} |
|||
|
|||
/** |
|||
* Validate the supplied arguments to the command. |
|||
* Throws warnings or errors if arguments are missing |
|||
* or invalid. |
|||
* |
|||
* @param array $args |
|||
* @param array $assoc_args |
|||
* @param array $extra_args |
|||
* @return array list of invalid $assoc_args keys to unset |
|||
*/ |
|||
private function validate_args( $args, $assoc_args, $extra_args ) { |
|||
$synopsis = $this->get_synopsis(); |
|||
if ( !$synopsis ) |
|||
return array(); |
|||
|
|||
$validator = new \EE_CLI\SynopsisValidator( $synopsis ); |
|||
|
|||
$cmd_path = implode( ' ', get_path( $this ) ); |
|||
foreach ( $validator->get_unknown() as $token ) { |
|||
\EE_CLI::warning( sprintf( |
|||
"The `%s` command has an invalid synopsis part: %s", |
|||
$cmd_path, $token |
|||
) ); |
|||
} |
|||
|
|||
if ( !$validator->enough_positionals( $args ) ) { |
|||
$this->show_usage(); |
|||
exit(1); |
|||
} |
|||
|
|||
$unknown_positionals = $validator->unknown_positionals( $args ); |
|||
if ( !empty( $unknown_positionals ) ) { |
|||
\EE_CLI::error( 'Too many positional arguments: ' . |
|||
implode( ' ', $unknown_positionals ) ); |
|||
} |
|||
|
|||
list( $errors, $to_unset ) = $validator->validate_assoc( |
|||
array_merge( \EE_CLI::get_config(), $extra_args, $assoc_args ) |
|||
); |
|||
|
|||
if ( $this->name != 'help' ) { |
|||
foreach ( $validator->unknown_assoc( $assoc_args ) as $key ) { |
|||
$errors['fatal'][] = "unknown --$key parameter"; |
|||
} |
|||
} |
|||
|
|||
if ( !empty( $errors['fatal'] ) ) { |
|||
$out = 'Parameter errors:'; |
|||
foreach ( $errors['fatal'] as $key => $error ) { |
|||
$out .= "\n {$error}"; |
|||
if ( $desc = $this->docparser->get_param_desc( $key ) ) { |
|||
$out .= " ({$desc})"; |
|||
} |
|||
} |
|||
|
|||
\EE_CLI::error( $out ); |
|||
} |
|||
|
|||
array_map( '\\EE_CLI::warning', $errors['warning'] ); |
|||
|
|||
return $to_unset; |
|||
} |
|||
|
|||
/** |
|||
* Invoke the subcommand with the supplied arguments. |
|||
* Given a --prompt argument, interactively request input |
|||
* from the end user. |
|||
* |
|||
* @param array $args |
|||
* @param array $assoc_args |
|||
*/ |
|||
public function invoke( $args, $assoc_args, $extra_args ) { |
|||
if ( \EE_CLI::get_config( 'prompt' ) ) |
|||
list( $args, $assoc_args ) = $this->prompt_args( $args, $assoc_args ); |
|||
|
|||
$to_unset = $this->validate_args( $args, $assoc_args, $extra_args ); |
|||
|
|||
foreach ( $to_unset as $key ) { |
|||
unset( $assoc_args[ $key ] ); |
|||
} |
|||
|
|||
$path = get_path( $this->get_parent() ); |
|||
\EE_CLI::do_hook( 'before_invoke:' . implode( ' ', array_slice( $path, 1 ) ) ); |
|||
|
|||
call_user_func( $this->when_invoked, $args, array_merge( $extra_args, $assoc_args ) ); |
|||
|
|||
\EE_CLI::do_hook( 'after_invoke:' . implode( ' ', array_slice( $path, 1 ) ) ); |
|||
} |
|||
} |
@ -0,0 +1,129 @@ |
|||
<?php |
|||
|
|||
namespace EE_CLI; |
|||
|
|||
/** |
|||
* Parse command attributes from its PHPdoc. |
|||
* Used to determine execution characteristics (arguments, etc.). |
|||
*/ |
|||
class DocParser { |
|||
|
|||
/** |
|||
* @var string $docComment PHPdoc command for the command. |
|||
*/ |
|||
protected $docComment; |
|||
|
|||
/** |
|||
* @param string $docComment |
|||
*/ |
|||
public function __construct( $docComment ) { |
|||
$this->docComment = self::remove_decorations( $docComment ); |
|||
} |
|||
|
|||
/** |
|||
* Remove unused cruft from PHPdoc comment. |
|||
* |
|||
* @param string $comment PHPdoc comment. |
|||
* @return string |
|||
*/ |
|||
private static function remove_decorations( $comment ) { |
|||
$comment = preg_replace( '|^/\*\*[\r\n]+|', '', $comment ); |
|||
$comment = preg_replace( '|\n[\t ]*\*/$|', '', $comment ); |
|||
$comment = preg_replace( '|^[\t ]*\* ?|m', '', $comment ); |
|||
|
|||
return $comment; |
|||
} |
|||
|
|||
/** |
|||
* Get the command's short description (e.g. summary). |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function get_shortdesc() { |
|||
if ( !preg_match( '|^([^@][^\n]+)\n*|', $this->docComment, $matches ) ) |
|||
return ''; |
|||
|
|||
return $matches[1]; |
|||
} |
|||
|
|||
/** |
|||
* Get the command's full description |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function get_longdesc() { |
|||
$shortdesc = $this->get_shortdesc(); |
|||
if ( !$shortdesc ) |
|||
return ''; |
|||
|
|||
$longdesc = substr( $this->docComment, strlen( $shortdesc ) ); |
|||
|
|||
$lines = array(); |
|||
foreach ( explode( "\n", $longdesc ) as $line ) { |
|||
if ( 0 === strpos( $line, '@' ) ) |
|||
break; |
|||
|
|||
$lines[] = $line; |
|||
} |
|||
$longdesc = trim( implode( $lines, "\n" ) ); |
|||
|
|||
return $longdesc; |
|||
} |
|||
|
|||
/** |
|||
* Get the value for a given tag (e.g. "@alias" or "@subcommand") |
|||
* |
|||
* @param string $name Name for the tag, without '@' |
|||
* @return string |
|||
*/ |
|||
public function get_tag( $name ) { |
|||
if ( preg_match( '|^@' . $name . '\s+([a-z-_]+)|m', $this->docComment, $matches ) ) |
|||
return $matches[1]; |
|||
|
|||
return ''; |
|||
} |
|||
|
|||
/** |
|||
* Get the command's synopsis. |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function get_synopsis() { |
|||
if ( !preg_match( '|^@synopsis\s+(.+)|m', $this->docComment, $matches ) ) |
|||
return ''; |
|||
|
|||
return $matches[1]; |
|||
} |
|||
|
|||
/** |
|||
* Get the description for a given argument. |
|||
* |
|||
* @param string $name Argument's doc name. |
|||
* @return string |
|||
*/ |
|||
public function get_arg_desc( $name ) { |
|||
|
|||
if ( preg_match( "/\[?<{$name}>.+\n: (.+?)(\n|$)/", $this->docComment, $matches ) ) { |
|||
return $matches[1]; |
|||
} |
|||
|
|||
return ''; |
|||
|
|||
} |
|||
|
|||
/** |
|||
* Get the description for a given parameter. |
|||
* |
|||
* @param string $key Parameter's key. |
|||
* @return string |
|||
*/ |
|||
public function get_param_desc( $key ) { |
|||
|
|||
if ( preg_match( "/\[?--{$key}=.+\n: (.+?)(\n|$)/", $this->docComment, $matches ) ) { |
|||
return $matches[1]; |
|||
} |
|||
|
|||
return ''; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,51 @@ |
|||
<?php |
|||
|
|||
namespace EE_CLI\Loggers; |
|||
|
|||
/** |
|||
* Base logger class |
|||
*/ |
|||
abstract class Base { |
|||
|
|||
abstract public function info( $message ); |
|||
|
|||
abstract public function success( $message ); |
|||
|
|||
abstract public function warning( $message ); |
|||
|
|||
/** |
|||
* Write a message to STDERR, prefixed with "Debug: ". |
|||
* |
|||
* @param string $message Message to write. |
|||
*/ |
|||
public function debug( $message ) { |
|||
if ( \EE_CLI::get_runner()->config['debug'] ) { |
|||
$time = round( microtime( true ) - EE_CLI_START_MICROTIME, 3 ); |
|||
$this->_line( "$message ({$time}s)", 'Debug', '%B', STDERR ); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Write a string to a resource. |
|||
* |
|||
* @param resource $handle Commonly STDOUT or STDERR. |
|||
* @param string $str Message to write. |
|||
*/ |
|||
protected function write( $handle, $str ) { |
|||
fwrite( $handle, $str ); |
|||
} |
|||
|
|||
/** |
|||
* Output one line of message to a resource. |
|||
* |
|||
* @param string $message Message to write. |
|||
* @param string $label Prefix message with a label. |
|||
* @param string $color Colorize label with a given color. |
|||
* @param resource $handle Resource to write to. Defaults to STDOUT. |
|||
*/ |
|||
protected function _line( $message, $label, $color, $handle = STDOUT ) { |
|||
$label = \cli\Colors::colorize( "$color$label:%n", $this->in_color ); |
|||
$this->write( $handle, "$label $message\n" ); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,57 @@ |
|||
<?php |
|||
|
|||
namespace EE_CLI\Loggers; |
|||
|
|||
/** |
|||
* Quiet logger only logs errors. |
|||
*/ |
|||
class Quiet extends Base { |
|||
|
|||
/** |
|||
* Informational messages aren't logged. |
|||
* |
|||
* @param string $message Message to write. |
|||
*/ |
|||
public function info( $message ) { |
|||
// nothing |
|||
} |
|||
|
|||
/** |
|||
* Success messages aren't logged. |
|||
* |
|||
* @param string $message Message to write. |
|||
*/ |
|||
public function success( $message ) { |
|||
// nothing |
|||
} |
|||
|
|||
/** |
|||
* Warning messages aren't logged. |
|||
* |
|||
* @param string $message Message to write. |
|||
*/ |
|||
public function warning( $message ) { |
|||
// nothing |
|||
} |
|||
|
|||
/** |
|||
* Write an error message to STDERR, prefixed with "Error: ". |
|||
* |
|||
* @param string $message Message to write. |
|||
*/ |
|||
public function error( $message ) { |
|||
$this->write( STDERR, \EE_CLI::colorize( "%RError:%n $message\n" ) ); |
|||
} |
|||
|
|||
/** |
|||
* Similar to error( $message ), but outputs $message in a red box |
|||
* |
|||
* @param array $message Message to write. |
|||
*/ |
|||
public function error_multi_line( $message_lines ) { |
|||
$message = implode( "\n", $message_lines ); |
|||
|
|||
$this->write( STDERR, \EE_CLI::colorize( "%RError:%n\n$message\n" ) ); |
|||
$this->write( STDERR, \EE_CLI::colorize( "%R---------%n\n\n" ) ); |
|||
} |
|||
} |
@ -0,0 +1,79 @@ |
|||
<?php |
|||
|
|||
namespace EE_CLI\Loggers; |
|||
|
|||
/** |
|||
* Default logger for success, warning, error, and standard messages. |
|||
*/ |
|||
class Regular extends Base { |
|||
|
|||
/** |
|||
* @param bool $in_color Whether or not to Colorize strings. |
|||
*/ |
|||
function __construct( $in_color ) { |
|||
$this->in_color = $in_color; |
|||
} |
|||
|
|||
/** |
|||
* Write an informational message to STDOUT. |
|||
* |
|||
* @param string $message Message to write. |
|||
*/ |
|||
public function info( $message ) { |
|||
$this->write( STDOUT, $message . "\n" ); |
|||
} |
|||
|
|||
/** |
|||
* Write a success message, prefixed with "Success: ". |
|||
* |
|||
* @param string $message Message to write. |
|||
*/ |
|||
public function success( $message ) { |
|||
$this->_line( $message, 'Success', '%G' ); |
|||
} |
|||
|
|||
/** |
|||
* Write a warning message to STDERR, prefixed with "Warning: ". |
|||
* |
|||
* @param string $message Message to write. |
|||
*/ |
|||
public function warning( $message ) { |
|||
$this->_line( $message, 'Warning', '%C', STDERR ); |
|||
} |
|||
|
|||
/** |
|||
* Write an message to STDERR, prefixed with "Error: ". |
|||
* |
|||
* @param string $message Message to write. |
|||
*/ |
|||
public function error( $message ) { |
|||
$this->_line( $message, 'Error', '%R', STDERR ); |
|||
} |
|||
|
|||
/** |
|||
* Similar to error( $message ), but outputs $message in a red box |
|||
* |
|||
* @param array $message Message to write. |
|||
*/ |
|||
public function error_multi_line( $message_lines ) { |
|||
// convert tabs to four spaces, as some shells will output the tabs as variable-length |
|||
$message_lines = array_map( function( $line ) { |
|||
return str_replace( "\t", ' ', $line ); |
|||
} , $message_lines ); |
|||
|
|||
$longest = max( array_map( 'strlen', $message_lines ) ); |
|||
|
|||
// write an empty line before the message |
|||
$empty_line = \cli\Colors::colorize( '%w%1 ' . str_repeat( ' ', $longest ) . ' %n' ); |
|||
$this->write( STDERR, "\n\t$empty_line\n" ); |
|||
|
|||
foreach ( $message_lines as $line ) { |
|||
$padding = str_repeat( ' ', $longest - strlen( $line ) ); |
|||
$line = \cli\Colors::colorize( "%w%1 $line $padding%n" ); |
|||
$this->write( STDERR, "\t$line\n" ); |
|||
} |
|||
|
|||
// write an empty line after the message |
|||
$this->write( STDERR, "\t$empty_line\n\n" ); |
|||
} |
|||
} |
@ -0,0 +1,511 @@ |
|||
<?php |
|||
|
|||
namespace EE_CLI; |
|||
|
|||
use EE_CLI; |
|||
use EE_CLI\Utils; |
|||
use EE_CLI\Dispatcher; |
|||
|
|||
/** |
|||
* Performs the execution of a command. |
|||
* |
|||
* @package EE_CLI |
|||
*/ |
|||
class Runner { |
|||
|
|||
private $global_config_path, $project_config_path; |
|||
|
|||
private $config, $extra_config; |
|||
|
|||
private $arguments, $assoc_args; |
|||
|
|||
private $_early_invoke = array(); |
|||
|
|||
private $_global_config_path_debug; |
|||
|
|||
private $_project_config_path_debug; |
|||
|
|||
public function __get( $key ) { |
|||
if ( '_' === $key[0] ) |
|||
return null; |
|||
|
|||
return $this->$key; |
|||
} |
|||
|
|||
/** |
|||
* Register a command for early invocation, generally before WordPress loads. |
|||
* |
|||
* @param string $when Named execution hook |
|||
* @param EE_CLI\Dispatcher\Subcommand $command |
|||
*/ |
|||
public function register_early_invoke( $when, $command ) { |
|||
$this->_early_invoke[ $when ][] = array_slice( Dispatcher\get_path( $command ), 1 ); |
|||
} |
|||
|
|||
/** |
|||
* Perform the early invocation of a command. |
|||
* |
|||
* @param string $when Named execution hook |
|||
*/ |
|||
private function do_early_invoke( $when ) { |
|||
if ( !isset( $this->_early_invoke[ $when ] ) ) |
|||
return; |
|||
|
|||
foreach ( $this->_early_invoke[ $when ] as $path ) { |
|||
if ( $this->cmd_starts_with( $path ) ) { |
|||
$this->_run_command(); |
|||
exit; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Get the path to the global configuration YAML file. |
|||
* |
|||
* @return string|false |
|||
*/ |
|||
private function get_global_config_path() { |
|||
|
|||
if ( isset( $runtime_config['config'] ) ) { |
|||
$config_path = $runtime_config['config']; |
|||
$this->_global_config_path_debug = 'Using global config from config runtime arg: ' . $config_path; |
|||
} else if ( getenv( 'EE_CLI_CONFIG_PATH' ) ) { |
|||
$config_path = getenv( 'EE_CLI_CONFIG_PATH' ); |
|||
$this->_global_config_path_debug = 'Using global config from EE_CLI_CONFIG_PATH env var: ' . $config_path; |
|||
} else { |
|||
$config_path = getenv( 'HOME' ) . '/.wp-cli/config.yml'; |
|||
$this->_global_config_path_debug = 'Using default global config: ' . $config_path; |
|||
} |
|||
|
|||
if ( is_readable( $config_path ) ) { |
|||
return $config_path; |
|||
} else { |
|||
$this->_global_config_path_debug = 'No readable global config found'; |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Get the path to the project-specific configuration |
|||
* YAML file. |
|||
* wp-cli.local.yml takes priority over wp-cli.yml. |
|||
* |
|||
* @return string|false |
|||
*/ |
|||
private function get_project_config_path() { |
|||
$config_files = array( |
|||
'wp-cli.local.yml', |
|||
'wp-cli.yml' |
|||
); |
|||
|
|||
// Stop looking upward when we find we have emerged from a subdirectory |
|||
// install into a parent install |
|||
$project_config_path = Utils\find_file_upward( $config_files, getcwd(), function ( $dir ) { |
|||
static $wp_load_count = 0; |
|||
$wp_load_path = $dir . DIRECTORY_SEPARATOR . 'wp-load.php'; |
|||
if ( file_exists( $wp_load_path ) ) { |
|||
$wp_load_count += 1; |
|||
} |
|||
return $wp_load_count > 1; |
|||
} ); |
|||
if ( ! empty( $project_config_path ) ) { |
|||
$this->_project_config_path_debug = 'Using project config: ' . $project_config_path; |
|||
} else { |
|||
$this->_project_config_path_debug = 'No project config found'; |
|||
} |
|||
return $project_config_path; |
|||
} |
|||
|
|||
/** |
|||
* Attempts to find the path to the WP install inside index.php |
|||
* |
|||
* @param string $index_path |
|||
* @return string|false |
|||
*/ |
|||
private static function extract_subdir_path( $index_path ) { |
|||
$index_code = file_get_contents( $index_path ); |
|||
|
|||
if ( !preg_match( '|^\s*require\s*\(?\s*(.+?)/wp-blog-header\.php([\'"])|m', $index_code, $matches ) ) { |
|||
return false; |
|||
} |
|||
|
|||
$wp_path_src = $matches[1] . $matches[2]; |
|||
$wp_path_src = Utils\replace_path_consts( $wp_path_src, $index_path ); |
|||
$wp_path = eval( "return $wp_path_src;" ); |
|||
|
|||
if ( !Utils\is_path_absolute( $wp_path ) ) { |
|||
$wp_path = dirname( $index_path ) . "/$wp_path"; |
|||
} |
|||
|
|||
return $wp_path; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Guess which URL context WP-CLI has been invoked under. |
|||
* |
|||
* @param array $assoc_args |
|||
* @return string|false |
|||
*/ |
|||
private static function guess_url( $assoc_args ) { |
|||
if ( isset( $assoc_args['blog'] ) ) { |
|||
$assoc_args['url'] = $assoc_args['blog']; |
|||
} |
|||
|
|||
if ( isset( $assoc_args['url'] ) ) { |
|||
$url = $assoc_args['url']; |
|||
if ( true === $url ) { |
|||
EE_CLI::warning( 'The --url parameter expects a value.' ); |
|||
} |
|||
} |
|||
|
|||
if ( isset( $url ) ) { |
|||
return $url; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
private function cmd_starts_with( $prefix ) { |
|||
return $prefix == array_slice( $this->arguments, 0, count( $prefix ) ); |
|||
} |
|||
|
|||
/** |
|||
* Given positional arguments, find the command to execute. |
|||
* |
|||
* @param array $args |
|||
* @return array|string Command, args, and path on success; error message on failure |
|||
*/ |
|||
public function find_command_to_run( $args ) { |
|||
$command = \EE_CLI::get_root_command(); |
|||
|
|||
$cmd_path = array(); |
|||
|
|||
while ( !empty( $args ) && $command->can_have_subcommands() ) { |
|||
$cmd_path[] = $args[0]; |
|||
$full_name = implode( ' ', $cmd_path ); |
|||
|
|||
$subcommand = $command->find_subcommand( $args ); |
|||
|
|||
if ( !$subcommand ) { |
|||
if ( count( $cmd_path ) > 1 ) { |
|||
$child = array_pop( $cmd_path ); |
|||
$parent_name = implode( ' ', $cmd_path ); |
|||
return sprintf( |
|||
"'%s' is not a registered subcommand of '%s'. See 'wp help %s'.", |
|||
$child, |
|||
$parent_name, |
|||
$parent_name |
|||
); |
|||
} else { |
|||
return sprintf( |
|||
"'%s' is not a registered wp command. See 'wp help'.", |
|||
$full_name |
|||
); |
|||
} |
|||
} |
|||
|
|||
/* if ( $this->is_command_disabled( $subcommand ) ) { |
|||
return sprintf( |
|||
"The '%s' command has been disabled from the config file.", |
|||
$full_name |
|||
); |
|||
} |
|||
*/ |
|||
|
|||
$command = $subcommand; |
|||
} |
|||
|
|||
return array( $command, $args, $cmd_path ); |
|||
} |
|||
|
|||
/** |
|||
* Find the WP-CLI command to run given arguments, |
|||
* and invoke it. |
|||
* |
|||
* @param array $args Positional arguments including command name |
|||
* @param array $assoc_args |
|||
*/ |
|||
public function run_command( $args, $assoc_args = array() ) { |
|||
$r = $this->find_command_to_run( $args ); |
|||
if ( is_string( $r ) ) { |
|||
EE_CLI::error( $r ); |
|||
} |
|||
|
|||
list( $command, $final_args, $cmd_path ) = $r; |
|||
|
|||
$name = implode( ' ', $cmd_path ); |
|||
|
|||
if ( isset( $this->extra_config[ $name ] ) ) { |
|||
$extra_args = $this->extra_config[ $name ]; |
|||
} else { |
|||
$extra_args = array(); |
|||
} |
|||
|
|||
EE_CLI::debug( 'Running command: ' . $name ); |
|||
try { |
|||
$command->invoke( $final_args, $assoc_args, $extra_args ); |
|||
} catch ( EE_CLI\Iterators\Exception $e ) { |
|||
EE_CLI::error( $e->getMessage() ); |
|||
} |
|||
} |
|||
|
|||
private function _run_command() { |
|||
$this->run_command( $this->arguments, $this->assoc_args ); |
|||
} |
|||
|
|||
/** |
|||
* Check whether a given command is disabled by the config |
|||
* |
|||
* @return bool |
|||
*/ |
|||
/*public function is_command_disabled( $command ) { |
|||
$path = implode( ' ', array_slice( \EE_CLI\Dispatcher\get_path( $command ), 1 ) ); |
|||
return in_array( $path, $this->config['disabled_commands'] ); |
|||
}*/ |
|||
|
|||
/** |
|||
* Returns wp-config.php code, skipping the loading of wp-settings.php |
|||
* |
|||
* @return string |
|||
*/ |
|||
|
|||
|
|||
public function get_wp_config_code() { |
|||
$wp_config_path = Utils\locate_wp_config(); |
|||
|
|||
// $wp_config_code = explode( "\n", file_get_contents( $wp_config_path ) ); |
|||
|
|||
$found_wp_settings = false; |
|||
|
|||
// $lines_to_run = array(); |
|||
|
|||
// foreach ( $wp_config_code as $line ) { |
|||
// if ( preg_match( '/^\s*require.+wp-settings\.php/', $line ) ) { |
|||
// $found_wp_settings = true; |
|||
// continue; |
|||
// } |
|||
|
|||
$lines_to_run[] = $line; |
|||
} |
|||
|
|||
// if ( !$found_wp_settings ) { |
|||
// EE_CLI::error( 'Strange wp-config.php file: wp-settings.php is not loaded directly.' ); |
|||
// } |
|||
|
|||
// $source = implode( "\n", $lines_to_run ); |
|||
// $source = Utils\replace_path_consts( $source, $wp_config_path ); |
|||
// return preg_replace( '|^\s*\<\?php\s*|', '', $source ); |
|||
//} |
|||
|
|||
/** |
|||
* Transparently convert deprecated syntaxes |
|||
* |
|||
* @param array $args |
|||
* @param array $assoc_args |
|||
* @return array |
|||
*/ |
|||
private static function back_compat_conversions( $args, $assoc_args ) { |
|||
$top_level_aliases = array( |
|||
'sql' => 'db', |
|||
'blog' => 'site' |
|||
); |
|||
if ( count( $args ) > 0 ) { |
|||
foreach ( $top_level_aliases as $old => $new ) { |
|||
if ( $old == $args[0] ) { |
|||
$args[0] = $new; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// --json -> --format=json |
|||
if ( isset( $assoc_args['json'] ) ) { |
|||
$assoc_args['format'] = 'json'; |
|||
unset( $assoc_args['json'] ); |
|||
} |
|||
|
|||
// --{version|info} -> cli {version|info} |
|||
if ( empty( $args ) ) { |
|||
$special_flags = array( 'version', 'info' ); |
|||
foreach ( $special_flags as $key ) { |
|||
if ( isset( $assoc_args[ $key ] ) ) { |
|||
$args = array( 'cli', $key ); |
|||
unset( $assoc_args[ $key ] ); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return array( $args, $assoc_args ); |
|||
} |
|||
|
|||
/** |
|||
* Whether or not the output should be rendered in color |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function in_color() { |
|||
return $this->colorize; |
|||
} |
|||
|
|||
private function init_colorization() { |
|||
if ( 'auto' === $this->config['color'] ) { |
|||
$this->colorize = ( !\cli\Shell::isPiped() && !\EE_CLI\Utils\is_windows() ); |
|||
} else { |
|||
$this->colorize = $this->config['color']; |
|||
} |
|||
} |
|||
|
|||
private function init_logger() { |
|||
if ( $this->config['quiet'] ) |
|||
$logger = new \EE_CLI\Loggers\Quiet; |
|||
else |
|||
$logger = new \EE_CLI\Loggers\Regular( $this->in_color() ); |
|||
|
|||
EE_CLI::set_logger( $logger ); |
|||
} |
|||
|
|||
|
|||
private function init_config() { |
|||
$configurator = \EE_CLI::get_configurator(); |
|||
|
|||
// File config |
|||
{ |
|||
$this->global_config_path = $this->get_global_config_path(); |
|||
$this->project_config_path = $this->get_project_config_path(); |
|||
|
|||
$configurator->merge_yml( $this->global_config_path ); |
|||
$configurator->merge_yml( $this->project_config_path ); |
|||
} |
|||
|
|||
// Runtime config and args |
|||
{ |
|||
list( $args, $assoc_args, $runtime_config ) = $configurator->parse_args( |
|||
array_slice( $GLOBALS['argv'], 1 ) ); |
|||
|
|||
list( $this->arguments, $this->assoc_args ) = self::back_compat_conversions( |
|||
$args, $assoc_args ); |
|||
|
|||
$configurator->merge_array( $runtime_config ); |
|||
} |
|||
|
|||
list( $this->config, $this->extra_config ) = $configurator->to_array(); |
|||
} |
|||
|
|||
public function start() { |
|||
$this->init_config(); |
|||
$this->init_colorization(); |
|||
$this->init_logger(); |
|||
|
|||
EE_CLI::debug( $this->_global_config_path_debug ); |
|||
EE_CLI::debug( $this->_project_config_path_debug ); |
|||
|
|||
// $this->check_root(); |
|||
|
|||
if ( empty( $this->arguments ) ) |
|||
$this->arguments[] = 'help'; |
|||
|
|||
// Protect 'cli info' from most of the runtime |
|||
if ( 'cli' === $this->arguments[0] && ! empty( $this->arguments[1] ) && 'info' === $this->arguments[1] ) { |
|||
$this->_run_command(); |
|||
exit; |
|||
} |
|||
|
|||
// Load bundled commands early, so that they're forced to use the same |
|||
// APIs as non-bundled commands. |
|||
Utils\load_command( $this->arguments[0] ); |
|||
|
|||
if ( isset( $this->config['require'] ) ) { |
|||
foreach ( $this->config['require'] as $path ) { |
|||
if ( ! file_exists( $path ) ) { |
|||
EE_CLI::error( sprintf( "Required file '%s' doesn't exist", basename( $path ) ) ); |
|||
} |
|||
Utils\load_file( $path ); |
|||
EE_CLI::debug( 'Required file from config: ' . $path ); |
|||
} |
|||
} |
|||
|
|||
// Show synopsis if it's a composite command. |
|||
$r = $this->find_command_to_run( $this->arguments ); |
|||
if ( is_array( $r ) ) { |
|||
list( $command ) = $r; |
|||
|
|||
if ( $command->can_have_subcommands() ) { |
|||
$command->show_usage(); |
|||
exit; |
|||
} |
|||
} |
|||
|
|||
// First try at showing man page |
|||
if ( 'help' === $this->arguments[0] && ! $this->wp_exists() ) { |
|||
$this->_run_command(); |
|||
} |
|||
|
|||
// Handle --url parameter |
|||
$url = self::guess_url( $this->config ); |
|||
if ( $url ) |
|||
\EE_CLI::set_url( $url ); |
|||
|
|||
$this->do_early_invoke( 'before_wp_load' ); |
|||
|
|||
//$this->check_wp_version(); |
|||
|
|||
if ( $this->cmd_starts_with( array( 'core', 'config' ) ) ) { |
|||
$this->_run_command(); |
|||
exit; |
|||
} |
|||
|
|||
//if ( !Utils\locate_wp_config() ) { |
|||
// EE_CLI::error( |
|||
// "wp-config.php not found.\n" . |
|||
// "Either create one manually or use `wp core config`." ); |
|||
//} |
|||
|
|||
if ( $this->cmd_starts_with( array( 'db' ) ) && !$this->cmd_starts_with( array( 'db', 'tables' ) ) ) { |
|||
eval( $this->get_wp_config_code() ); |
|||
$this->_run_command(); |
|||
exit; |
|||
} |
|||
|
|||
if ( $this->cmd_starts_with( array( 'core', 'is-installed' ) ) ) { |
|||
define( 'WP_INSTALLING', true ); |
|||
} |
|||
|
|||
if ( |
|||
count( $this->arguments ) >= 2 && |
|||
$this->arguments[0] == 'core' && |
|||
in_array( $this->arguments[1], array( 'install', 'multisite-install' ) ) |
|||
) { |
|||
define( 'WP_INSTALLING', true ); |
|||
|
|||
// We really need a URL here |
|||
if ( !isset( $_SERVER['HTTP_HOST'] ) ) { |
|||
$url = 'http://example.com'; |
|||
\EE_CLI::set_url( $url ); |
|||
} |
|||
|
|||
if ( 'multisite-install' == $this->arguments[1] ) { |
|||
// need to fake some globals to skip the checks in wp-includes/ms-settings.php |
|||
$url_parts = Utils\parse_url( $url ); |
|||
self::fake_current_site_blog( $url_parts ); |
|||
|
|||
if ( !defined( 'COOKIEHASH' ) ) { |
|||
define( 'COOKIEHASH', md5( $url_parts['host'] ) ); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if ( $this->cmd_starts_with( array( 'import') ) ) { |
|||
define( 'WP_LOAD_IMPORTERS', true ); |
|||
define( 'WP_IMPORTING', true ); |
|||
} |
|||
|
|||
if ( $this->cmd_starts_with( array( 'plugin' ) ) ) { |
|||
$GLOBALS['pagenow'] = 'plugins.php'; |
|||
} |
|||
|
|||
$this->_run_command(); |
|||
|
|||
} |
|||
} |
@ -0,0 +1,110 @@ |
|||
<?php |
|||
|
|||
namespace EE_CLI; |
|||
|
|||
/** |
|||
* Generate a synopsis from a command's PHPdoc arguments. |
|||
* Turns something like "<object-id>..." |
|||
* into [ optional=>false, type=>positional, repeating=>true, name=>object-id ] |
|||
*/ |
|||
class SynopsisParser { |
|||
|
|||
/** |
|||
* @param string A synopsis |
|||
* @return array List of parameters |
|||
*/ |
|||
public static function parse( $synopsis ) { |
|||
$tokens = array_filter( preg_split( '/[\s\t]+/', $synopsis ) ); |
|||
|
|||
$params = array(); |
|||
foreach ( $tokens as $token ) { |
|||
$param = self::classify_token( $token ); |
|||
|
|||
// Some types of parameters shouldn't be mandatory |
|||
if ( isset( $param['optional'] ) && !$param['optional'] ) { |
|||
if ( 'flag' === $param['type'] || ( 'assoc' === $param['type'] && $param['value']['optional'] ) ) { |
|||
$param['type'] = 'unknown'; |
|||
} |
|||
} |
|||
|
|||
$param['token'] = $token; |
|||
$params[] = $param; |
|||
} |
|||
|
|||
return $params; |
|||
} |
|||
|
|||
/** |
|||
* Classify argument attributes based on its syntax. |
|||
* |
|||
* @param string $token |
|||
* @return array $param |
|||
*/ |
|||
private static function classify_token( $token ) { |
|||
$param = array(); |
|||
|
|||
list( $param['optional'], $token ) = self::is_optional( $token ); |
|||
list( $param['repeating'], $token ) = self::is_repeating( $token ); |
|||
|
|||
$p_name = '([a-z-_]+)'; |
|||
$p_value = '([a-zA-Z-_|,]+)'; |
|||
|
|||
if ( '--<field>=<value>' === $token ) { |
|||
$param['type'] = 'generic'; |
|||
} elseif ( preg_match( "/^<($p_value)>$/", $token, $matches ) ) { |
|||
$param['type'] = 'positional'; |
|||
$param['name'] = $matches[1]; |
|||
} elseif ( preg_match( "/^--(?:\\[no-\\])?$p_name/", $token, $matches ) ) { |
|||
$param['name'] = $matches[1]; |
|||
|
|||
$value = substr( $token, strlen( $matches[0] ) ); |
|||
|
|||
// substr returns false <= PHP 5.6, and '' PHP 7+ |
|||
if ( false === $value || '' === $value ) { |
|||
$param['type'] = 'flag'; |
|||
} else { |
|||
$param['type'] = 'assoc'; |
|||
|
|||
list( $param['value']['optional'], $value ) = self::is_optional( $value ); |
|||
|
|||
if ( preg_match( "/^=<$p_value>$/", $value, $matches ) ) { |
|||
$param['value']['name'] = $matches[1]; |
|||
} else { |
|||
$param = array( 'type' => 'unknown' ); |
|||
} |
|||
} |
|||
} else { |
|||
$param['type'] = 'unknown'; |
|||
} |
|||
|
|||
return $param; |
|||
} |
|||
|
|||
/** |
|||
* An optional parameter is surrounded by square brackets. |
|||
* |
|||
* @param string $token |
|||
* @return array |
|||
*/ |
|||
private static function is_optional( $token ) { |
|||
if ( '[' == substr( $token, 0, 1 ) && ']' == substr( $token, -1 ) ) { |
|||
return array( true, substr( $token, 1, -1 ) ); |
|||
} else { |
|||
return array( false, $token ); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* A repeating parameter is followed by an ellipsis. |
|||
* |
|||
* @param string $token |
|||
* @return array |
|||
*/ |
|||
private static function is_repeating( $token ) { |
|||
if ( '...' === substr( $token, -3 ) ) { |
|||
return array( true, substr( $token, 0, -3 ) ); |
|||
} else { |
|||
return array( false, $token ); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,163 @@ |
|||
<?php |
|||
|
|||
namespace EE_CLI; |
|||
|
|||
/** |
|||
* Checks if the list of parameters matches the specification defined in the synopsis. |
|||
*/ |
|||
class SynopsisValidator { |
|||
|
|||
/** |
|||
* @var array $spec Structured representation of command synopsis. |
|||
*/ |
|||
private $spec = array(); |
|||
|
|||
/** |
|||
* @param string $synopsis Command's synopsis. |
|||
*/ |
|||
public function __construct( $synopsis ) { |
|||
$this->spec = SynopsisParser::parse( $synopsis ); |
|||
} |
|||
|
|||
/** |
|||
* Get any unknown arugments. |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function get_unknown() { |
|||
return array_column( $this->query_spec( array( |
|||
'type' => 'unknown', |
|||
) ), 'token' ); |
|||
} |
|||
|
|||
/** |
|||
* Check whether there are enough positional arguments. |
|||
* |
|||
* @param array $args Positional arguments. |
|||
* @return bool |
|||
*/ |
|||
public function enough_positionals( $args ) { |
|||
$positional = $this->query_spec( array( |
|||
'type' => 'positional', |
|||
'optional' => false, |
|||
) ); |
|||
|
|||
return count( $args ) >= count( $positional ); |
|||
} |
|||
|
|||
/** |
|||
* Check for any unknown positionals. |
|||
* |
|||
* @param array $args Positional arguments. |
|||
* @return array |
|||
*/ |
|||
public function unknown_positionals( $args ) { |
|||
$positional_repeating = $this->query_spec( array( |
|||
'type' => 'positional', |
|||
'repeating' => true, |
|||
) ); |
|||
|
|||
// At least one positional supports as many as possible. |
|||
if ( !empty( $positional_repeating ) ) |
|||
return array(); |
|||
|
|||
$positional = $this->query_spec( array( |
|||
'type' => 'positional', |
|||
'repeating' => false, |
|||
) ); |
|||
|
|||
return array_slice( $args, count( $positional ) ); |
|||
} |
|||
|
|||
/** |
|||
* Check that all required keys are present and that they have values. |
|||
* |
|||
* @param array $assoc_args Parameters passed to command. |
|||
* @return array |
|||
*/ |
|||
public function validate_assoc( $assoc_args ) { |
|||
$assoc_spec = $this->query_spec( array( |
|||
'type' => 'assoc', |
|||
) ); |
|||
|
|||
$errors = array( |
|||
'fatal' => array(), |
|||
'warning' => array() |
|||
); |
|||
|
|||
$to_unset = array(); |
|||
|
|||
foreach ( $assoc_spec as $param ) { |
|||
$key = $param['name']; |
|||
|
|||
if ( !isset( $assoc_args[ $key ] ) ) { |
|||
if ( !$param['optional'] ) { |
|||
$errors['fatal'][$key] = "missing --$key parameter"; |
|||
} |
|||
} else { |
|||
if ( true === $assoc_args[ $key ] && !$param['value']['optional'] ) { |
|||
$error_type = ( !$param['optional'] ) ? 'fatal' : 'warning'; |
|||
$errors[ $error_type ][$key] = "--$key parameter needs a value"; |
|||
|
|||
$to_unset[] = $key; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return array( $errors, $to_unset ); |
|||
} |
|||
|
|||
/** |
|||
* Check whether there are unknown parameters supplied. |
|||
* |
|||
* @param array $assoc_args Parameters passed to command. |
|||
* @return array|false |
|||
*/ |
|||
public function unknown_assoc( $assoc_args ) { |
|||
$generic = $this->query_spec( array( |
|||
'type' => 'generic', |
|||
) ); |
|||
|
|||
if ( count( $generic ) ) |
|||
return array(); |
|||
|
|||
$known_assoc = array(); |
|||
|
|||
foreach ( $this->spec as $param ) { |
|||
if ( in_array( $param['type'], array( 'assoc', 'flag' ) ) ) |
|||
$known_assoc[] = $param['name']; |
|||
} |
|||
|
|||
return array_diff( array_keys( $assoc_args ), $known_assoc ); |
|||
} |
|||
|
|||
/** |
|||
* Filters a list of associative arrays, based on a set of key => value arguments. |
|||
* |
|||
* @param array $args An array of key => value arguments to match against |
|||
* @param string $operator |
|||
* @return array |
|||
*/ |
|||
private function query_spec( $args, $operator = 'AND' ) { |
|||
$operator = strtoupper( $operator ); |
|||
$count = count( $args ); |
|||
$filtered = array(); |
|||
|
|||
foreach ( $this->spec as $key => $to_match ) { |
|||
$matched = 0; |
|||
foreach ( $args as $m_key => $m_value ) { |
|||
if ( array_key_exists( $m_key, $to_match ) && $m_value == $to_match[ $m_key ] ) |
|||
$matched++; |
|||
} |
|||
|
|||
if ( ( 'AND' == $operator && $matched == $count ) |
|||
|| ( 'OR' == $operator && $matched > 0 ) |
|||
|| ( 'NOT' == $operator && 0 == $matched ) ) { |
|||
$filtered[$key] = $to_match; |
|||
} |
|||
} |
|||
|
|||
return $filtered; |
|||
} |
|||
|
|||
} |
File diff suppressed because it is too large
@ -0,0 +1,17 @@ |
|||
<?php |
|||
|
|||
// This file needs to parse without error in PHP < 5.3 |
|||
|
|||
if ( 'cli' !== PHP_SAPI ) { |
|||
echo "Only CLI access.\n"; |
|||
die(-1); |
|||
} |
|||
|
|||
if ( version_compare( PHP_VERSION, '5.3.0', '<' ) ) { |
|||
printf( "Error: ee-cli requires PHP %s or newer. You are running version %s.\n", '5.3.0', PHP_VERSION ); |
|||
die(-1); |
|||
} |
|||
|
|||
define( 'EE_CLI_ROOT', dirname( __DIR__ ) ); |
|||
|
|||
include EE_CLI_ROOT . '/php/ee-cli.php'; |
@ -0,0 +1,11 @@ |
|||
<?php |
|||
|
|||
/** |
|||
* Base class for WP-CLI commands |
|||
* |
|||
* @package wp-cli |
|||
*/ |
|||
abstract class EE_CLI_Command { |
|||
|
|||
public function __construct() {} |
|||
} |
@ -0,0 +1,505 @@ |
|||
<?php |
|||
|
|||
use \EE_CLI\Utils; |
|||
use \EE_CLI\Dispatcher; |
|||
use \EE_CLI\FileCache; |
|||
use \EE_CLI\Process; |
|||
use \EE_CLI\WpHttpCacheManager; |
|||
|
|||
/** |
|||
* Various utilities for EE-CLI commands. |
|||
*/ |
|||
class EE_CLI { |
|||
|
|||
private static $configurator; |
|||
|
|||
private static $logger; |
|||
|
|||
private static $hooks = array(), $hooks_passed = array(); |
|||
|
|||
/** |
|||
* Set the logger instance. |
|||
* |
|||
* @param object $logger |
|||
*/ |
|||
public static function set_logger( $logger ) { |
|||
self::$logger = $logger; |
|||
} |
|||
|
|||
/** |
|||
* Get the Configurator instance |
|||
* |
|||
* @return \EE_CLI\Configurator |
|||
*/ |
|||
public static function get_configurator() { |
|||
static $configurator; |
|||
|
|||
if ( !$configurator ) { |
|||
$configurator = new EE_CLI\Configurator( EE_CLI_ROOT . '/php/config-spec.php' ); |
|||
} |
|||
|
|||
return $configurator; |
|||
} |
|||
|
|||
public static function get_root_command() { |
|||
static $root; |
|||
|
|||
if ( !$root ) { |
|||
$root = new Dispatcher\RootCommand; |
|||
} |
|||
|
|||
return $root; |
|||
} |
|||
|
|||
public static function get_runner() { |
|||
static $runner; |
|||
|
|||
if ( !$runner ) { |
|||
$runner = new EE_CLI\Runner; |
|||
} |
|||
|
|||
return $runner; |
|||
} |
|||
|
|||
/** |
|||
* @return FileCache |
|||
*/ |
|||
public static function get_cache() { |
|||
static $cache; |
|||
|
|||
if ( !$cache ) { |
|||
$home = getenv( 'HOME' ); |
|||
if ( !$home ) { |
|||
// sometime in windows $HOME is not defined |
|||
$home = getenv( 'HOMEDRIVE' ) . getenv( 'HOMEPATH' ); |
|||
} |
|||
$dir = getenv( 'EE_CLI_CACHE_DIR' ) ? : "$home/.EE-CLI/cache"; |
|||
|
|||
// 6 months, 300mb |
|||
$cache = new FileCache( $dir, 15552000, 314572800 ); |
|||
|
|||
// clean older files on shutdown with 1/50 probability |
|||
if ( 0 === mt_rand( 0, 50 ) ) { |
|||
register_shutdown_function( function () use ( $cache ) { |
|||
$cache->clean(); |
|||
} ); |
|||
} |
|||
} |
|||
|
|||
return $cache; |
|||
} |
|||
|
|||
/** |
|||
* Set the context in which EE-CLI should be run |
|||
*/ |
|||
public static function set_url( $url ) { |
|||
EE_CLI::debug( 'Set URL: ' . $url ); |
|||
$url_parts = Utils\parse_url( $url ); |
|||
self::set_url_params( $url_parts ); |
|||
} |
|||
|
|||
private static function set_url_params( $url_parts ) { |
|||
$f = function( $key ) use ( $url_parts ) { |
|||
return \EE_CLI\Utils\get_flag_value( $url_parts, $key, '' ); |
|||
}; |
|||
|
|||
if ( isset( $url_parts['host'] ) ) { |
|||
if ( isset( $url_parts['scheme'] ) && 'https' === strtolower( $url_parts['scheme'] ) ) { |
|||
$_SERVER['HTTPS'] = 'on'; |
|||
} |
|||
|
|||
$_SERVER['HTTP_HOST'] = $url_parts['host']; |
|||
if ( isset( $url_parts['port'] ) ) { |
|||
$_SERVER['HTTP_HOST'] .= ':' . $url_parts['port']; |
|||
} |
|||
|
|||
$_SERVER['SERVER_NAME'] = $url_parts['host']; |
|||
} |
|||
|
|||
$_SERVER['REQUEST_URI'] = $f('path') . ( isset( $url_parts['query'] ) ? '?' . $url_parts['query'] : '' ); |
|||
$_SERVER['SERVER_PORT'] = \EE_CLI\Utils\get_flag_value( $url_parts, 'port', '80' ); |
|||
$_SERVER['QUERY_STRING'] = $f('query'); |
|||
} |
|||
|
|||
/** |
|||
* @return WpHttpCacheManager |
|||
*/ |
|||
public static function get_http_cache_manager() { |
|||
static $http_cacher; |
|||
|
|||
if ( !$http_cacher ) { |
|||
$http_cacher = new WpHttpCacheManager( self::get_cache() ); |
|||
} |
|||
|
|||
return $http_cacher; |
|||
} |
|||
|
|||
public static function colorize( $string ) { |
|||
return \cli\Colors::colorize( $string, self::get_runner()->in_color() ); |
|||
} |
|||
|
|||
/** |
|||
* Schedule a callback to be executed at a certain point (before WP is loaded). |
|||
*/ |
|||
public static function add_hook( $when, $callback ) { |
|||
if ( in_array( $when, self::$hooks_passed ) ) |
|||
call_user_func( $callback ); |
|||
|
|||
self::$hooks[ $when ][] = $callback; |
|||
} |
|||
|
|||
/** |
|||
* Execute registered callbacks. |
|||
*/ |
|||
public static function do_hook( $when ) { |
|||
self::$hooks_passed[] = $when; |
|||
|
|||
if ( !isset( self::$hooks[ $when ] ) ) |
|||
return; |
|||
|
|||
foreach ( self::$hooks[ $when ] as $callback ) { |
|||
call_user_func( $callback ); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Add a command to the EE-CLI list of commands |
|||
* |
|||
* @param string $name The name of the command that will be used in the CLI |
|||
* @param string $class The command implementation |
|||
* @param array $args An associative array with additional parameters: |
|||
* 'before_invoke' => callback to execute before invoking the command |
|||
*/ |
|||
public static function add_command( $name, $class, $args = array() ) { |
|||
if ( is_string( $class ) && ! class_exists( (string) $class ) ) { |
|||
EE_CLI::error( sprintf( "Class '%s' does not exist.", $class ) ); |
|||
} |
|||
|
|||
if ( isset( $args['before_invoke'] ) ) { |
|||
self::add_hook( "before_invoke:$name", $args['before_invoke'] ); |
|||
} |
|||
|
|||
$path = preg_split( '/\s+/', $name ); |
|||
|
|||
$leaf_name = array_pop( $path ); |
|||
$full_path = $path; |
|||
|
|||
$command = self::get_root_command(); |
|||
|
|||
while ( !empty( $path ) ) { |
|||
$subcommand_name = $path[0]; |
|||
$subcommand = $command->find_subcommand( $path ); |
|||
|
|||
// create an empty container |
|||
if ( !$subcommand ) { |
|||
$subcommand = new Dispatcher\CompositeCommand( $command, $subcommand_name, |
|||
new \EE_CLI\DocParser( '' ) ); |
|||
$command->add_subcommand( $subcommand_name, $subcommand ); |
|||
} |
|||
|
|||
$command = $subcommand; |
|||
} |
|||
|
|||
$leaf_command = Dispatcher\CommandFactory::create( $leaf_name, $class, $command ); |
|||
|
|||
if ( ! $command->can_have_subcommands() ) { |
|||
throw new Exception( sprintf( "'%s' can't have subcommands.", |
|||
implode( ' ' , Dispatcher\get_path( $command ) ) ) ); |
|||
} |
|||
|
|||
$command->add_subcommand( $leaf_name, $leaf_command ); |
|||
} |
|||
|
|||
/** |
|||
* Display a message in the CLI and end with a newline |
|||
* |
|||
* @param string $message |
|||
*/ |
|||
public static function line( $message = '' ) { |
|||
echo $message . "\n"; |
|||
} |
|||
|
|||
/** |
|||
* Log an informational message. |
|||
* |
|||
* @param string $message |
|||
*/ |
|||
public static function log( $message ) { |
|||
self::$logger->info( $message ); |
|||
} |
|||
|
|||
/** |
|||
* Display a success in the CLI and end with a newline |
|||
* |
|||
* @param string $message |
|||
*/ |
|||
public static function success( $message ) { |
|||
self::$logger->success( $message ); |
|||
} |
|||
|
|||
/** |
|||
* Log debug information |
|||
* |
|||
* @param string $message |
|||
*/ |
|||
public static function debug( $message ) { |
|||
self::$logger->debug( self::error_to_string( $message ) ); |
|||
} |
|||
|
|||
/** |
|||
* Display a warning in the CLI and end with a newline |
|||
* |
|||
* @param string $message |
|||
*/ |
|||
public static function warning( $message ) { |
|||
self::$logger->warning( self::error_to_string( $message ) ); |
|||
} |
|||
|
|||
/** |
|||
* Display an error in the CLI and end with a newline |
|||
* |
|||
* @param string|EE_Error $message |
|||
* @param bool $exit if true, the script will exit() |
|||
*/ |
|||
public static function error( $message, $exit = true ) { |
|||
if ( ! isset( self::get_runner()->assoc_args[ 'completions' ] ) ) { |
|||
self::$logger->error( self::error_to_string( $message ) ); |
|||
} |
|||
|
|||
if ( $exit ) { |
|||
exit(1); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Display an error in the CLI and end with a newline |
|||
* |
|||
* @param array $message each element from the array will be printed on its own line |
|||
*/ |
|||
public static function error_multi_line( $message_lines ) { |
|||
if ( ! isset( self::get_runner()->assoc_args[ 'completions' ] ) && is_array( $message_lines ) ) { |
|||
self::$logger->error_multi_line( array_map( array( __CLASS__, 'error_to_string' ), $message_lines ) ); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Ask for confirmation before running a destructive operation. |
|||
*/ |
|||
public static function confirm( $question, $assoc_args = array() ) { |
|||
if ( ! \EE_CLI\Utils\get_flag_value( $assoc_args, 'yes' ) ) { |
|||
fwrite( STDOUT, $question . " [y/n] " ); |
|||
|
|||
$answer = trim( fgets( STDIN ) ); |
|||
|
|||
if ( 'y' != $answer ) |
|||
exit; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Read value from a positional argument or from STDIN. |
|||
* |
|||
* @param array $args The list of positional arguments. |
|||
* @param int $index At which position to check for the value. |
|||
* |
|||
* @return string |
|||
*/ |
|||
public static function get_value_from_arg_or_stdin( $args, $index ) { |
|||
if ( isset( $args[ $index ] ) ) { |
|||
$raw_value = $args[ $index ]; |
|||
} else { |
|||
// We don't use file_get_contents() here because it doesn't handle |
|||
// Ctrl-D properly, when typing in the value interactively. |
|||
$raw_value = ''; |
|||
while ( ( $line = fgets( STDIN ) ) !== false ) { |
|||
$raw_value .= $line; |
|||
} |
|||
} |
|||
|
|||
return $raw_value; |
|||
} |
|||
|
|||
/** |
|||
* Read a value, from various formats. |
|||
* |
|||
* @param mixed $value |
|||
* @param array $assoc_args |
|||
*/ |
|||
public static function read_value( $raw_value, $assoc_args = array() ) { |
|||
if ( \EE_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) { |
|||
$value = json_decode( $raw_value, true ); |
|||
if ( null === $value ) { |
|||
EE_CLI::error( sprintf( 'Invalid JSON: %s', $raw_value ) ); |
|||
} |
|||
} else { |
|||
$value = $raw_value; |
|||
} |
|||
|
|||
return $value; |
|||
} |
|||
|
|||
/** |
|||
* Display a value, in various formats |
|||
* |
|||
* @param mixed $value |
|||
* @param array $assoc_args |
|||
*/ |
|||
public static function print_value( $value, $assoc_args = array() ) { |
|||
if ( \EE_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) { |
|||
$value = json_encode( $value ); |
|||
} elseif ( is_array( $value ) || is_object( $value ) ) { |
|||
$value = var_export( $value ); |
|||
} |
|||
|
|||
echo $value . "\n"; |
|||
} |
|||
|
|||
/** |
|||
* Convert a ee_error into a string |
|||
* |
|||
* @param mixed $errors |
|||
* @return string |
|||
*/ |
|||
public static function error_to_string( $errors ) { |
|||
if ( is_string( $errors ) ) { |
|||
return $errors; |
|||
} |
|||
|
|||
if ( is_object( $errors ) && is_a( $errors, 'EE_Error' ) ) { |
|||
foreach ( $errors->get_error_messages() as $message ) { |
|||
if ( $errors->get_error_data() ) |
|||
return $message . ' ' . $errors->get_error_data(); |
|||
else |
|||
return $message; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Launch an external process that takes over I/O. |
|||
* |
|||
* @param string Command to call |
|||
* @param bool Whether to exit if the command returns an error status |
|||
* @param bool Whether to return an exit status (default) or detailed execution results |
|||
* |
|||
* @return int|ProcessRun The command exit status, or a ProcessRun instance |
|||
*/ |
|||
public static function launch( $command, $exit_on_error = true, $return_detailed = false ) { |
|||
|
|||
$proc = Process::create( $command ); |
|||
$results = $proc->run(); |
|||
|
|||
if ( $results->return_code && $exit_on_error ) |
|||
exit( $results->return_code ); |
|||
|
|||
if ( $return_detailed ) { |
|||
return $results; |
|||
} else { |
|||
return $results->return_code; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Launch another EE-CLI command using the runtime arguments for the current process |
|||
* |
|||
* @param string Command to call |
|||
* @param array $args Positional arguments to use |
|||
* @param array $assoc_args Associative arguments to use |
|||
* @param bool Whether to exit if the command returns an error status |
|||
* @param bool Whether to return an exit status (default) or detailed execution results |
|||
* @param array $runtime_args Override one or more global args (path,url,user,allow-root) |
|||
* |
|||
* @return int|ProcessRun The command exit status, or a ProcessRun instance |
|||
*/ |
|||
public static function launch_self( $command, $args = array(), $assoc_args = array(), $exit_on_error = true, $return_detailed = false, $runtime_args = array() ) { |
|||
$reused_runtime_args = array( |
|||
'path', |
|||
'url', |
|||
'user', |
|||
'allow-root', |
|||
); |
|||
|
|||
foreach ( $reused_runtime_args as $key ) { |
|||
if ( isset( $runtime_args[ $key ] ) ) { |
|||
$assoc_args[ $key ] = $runtime_args[ $key ]; |
|||
} else if ( $value = self::get_runner()->config[ $key ] ) |
|||
$assoc_args[ $key ] = $value; |
|||
} |
|||
|
|||
$php_bin = self::get_php_binary(); |
|||
|
|||
$script_path = $GLOBALS['argv'][0]; |
|||
|
|||
$args = implode( ' ', array_map( 'escapeshellarg', $args ) ); |
|||
$assoc_args = \EE_CLI\Utils\assoc_args_to_str( $assoc_args ); |
|||
|
|||
$full_command = "{$php_bin} {$script_path} {$command} {$args} {$assoc_args}"; |
|||
|
|||
return self::launch( $full_command, $exit_on_error, $return_detailed ); |
|||
} |
|||
|
|||
/** |
|||
* Get the path to the PHP binary used when executing EE-CLI. |
|||
* Environment values permit specific binaries to be indicated. |
|||
* |
|||
* @return string |
|||
*/ |
|||
public static function get_php_binary() { |
|||
if ( defined( 'PHP_BINARY' ) ) |
|||
return PHP_BINARY; |
|||
|
|||
if ( getenv( 'EE_CLI_PHP_USED' ) ) |
|||
return getenv( 'EE_CLI_PHP_USED' ); |
|||
|
|||
if ( getenv( 'EE_CLI_PHP' ) ) |
|||
return getenv( 'EE_CLI_PHP' ); |
|||
|
|||
return 'php'; |
|||
} |
|||
|
|||
public static function get_config( $key = null ) { |
|||
if ( null === $key ) { |
|||
return self::get_runner()->config; |
|||
} |
|||
|
|||
if ( !isset( self::get_runner()->config[ $key ] ) ) { |
|||
self::warning( "Unknown config option '$key'." ); |
|||
return null; |
|||
} |
|||
|
|||
return self::get_runner()->config[ $key ]; |
|||
} |
|||
|
|||
/** |
|||
* Run a given command within the current process using the same global parameters. |
|||
* |
|||
* To run a command using a new process with the same global parameters, use EE_CLI::launch_self() |
|||
* To run a command using a new process with different global parameters, use EE_CLI::launch() |
|||
* |
|||
* @param array |
|||
* @param array |
|||
*/ |
|||
public static function run_command( $args, $assoc_args = array() ) { |
|||
self::get_runner()->run_command( $args, $assoc_args ); |
|||
} |
|||
|
|||
|
|||
|
|||
// DEPRECATED STUFF |
|||
|
|||
public static function add_man_dir() { |
|||
trigger_error( 'EE_CLI::add_man_dir() is deprecated. Add docs inline.', E_USER_WARNING ); |
|||
} |
|||
|
|||
// back-compat |
|||
public static function out( $str ) { |
|||
fwrite( STDOUT, $str ); |
|||
} |
|||
|
|||
// back-compat |
|||
public static function addCommand( $name, $class ) { |
|||
trigger_error( sprintf( 'ee %s: %s is deprecated. use EE_CLI::add_command() instead.', |
|||
$name, __FUNCTION__ ), E_USER_WARNING ); |
|||
self::add_command( $name, $class ); |
|||
} |
|||
} |
@ -0,0 +1,30 @@ |
|||
<?php |
|||
|
|||
/** |
|||
* Manage EasyEngine sites. |
|||
* |
|||
* ## EXAMPLES |
|||
* |
|||
* ee site create my_domain |
|||
* |
|||
* ee site delete my_domain |
|||
*/ |
|||
class Site_Command extends EE_CLI_Command { |
|||
|
|||
/** |
|||
* Remove a value from the object cache. |
|||
* |
|||
* <sitename> |
|||
* : Cache key. |
|||
*[--files] |
|||
* : Webroot |
|||
* [--db=<some_value>] |
|||
* : Method for grouping data within the cache which allows the same key to be used across groups. |
|||
*/ |
|||
public function delete( $args, $assoc_args ) { |
|||
EE_CLI::success( 'Object deleted.' ); |
|||
} |
|||
|
|||
} |
|||
|
|||
EE_CLI::add_command( 'site', 'Site_Command' ); |
@ -0,0 +1,102 @@ |
|||
<?php |
|||
|
|||
return array( |
|||
'path' => array( |
|||
'runtime' => '=<path>', |
|||
'file' => '<path>', |
|||
'desc' => 'Path to the WordPress files', |
|||
), |
|||
|
|||
'url' => array( |
|||
'runtime' => '=<url>', |
|||
'file' => '<url>', |
|||
'desc' => 'Pretend request came from given URL. In multisite, this argument is how the target site is specified.', |
|||
), |
|||
'blog' => array( |
|||
'deprecated' => 'Use --url instead.', |
|||
'runtime' => '=<url>', |
|||
), |
|||
|
|||
'config' => array( |
|||
'deprecated' => 'Use the WP_CLI_CONFIG_PATH environment variable instead.', |
|||
'runtime' => '=<path>', |
|||
), |
|||
|
|||
'user' => array( |
|||
'runtime' => '=<id|login|email>', |
|||
'file' => '<id|login|email>', |
|||
'desc' => 'Set the WordPress user', |
|||
), |
|||
|
|||
'skip-plugins' => array( |
|||
'runtime' => '[=<plugin>]', |
|||
'file' => '<list>', |
|||
'desc' => 'Skip loading all or some plugins', |
|||
'default' => '', |
|||
), |
|||
|
|||
'skip-themes' => array( |
|||
'runtime' => '[=<theme>]', |
|||
'file' => '<list>', |
|||
'desc' => 'Skip loading all or some themes', |
|||
'default' => '', |
|||
), |
|||
|
|||
'require' => array( |
|||
'runtime' => '=<path>', |
|||
'file' => '<path>', |
|||
'desc' => 'Load PHP file before running the command (may be used more than once)', |
|||
'multiple' => true, |
|||
'default' => array(), |
|||
), |
|||
|
|||
'disabled_commands' => array( |
|||
'file' => '<list>', |
|||
'default' => array(), |
|||
'desc' => '(Sub)commands to disable', |
|||
), |
|||
|
|||
'color' => array( |
|||
'runtime' => true, |
|||
'file' => '<bool>', |
|||
'default' => 'auto', |
|||
'desc' => 'Whether to colorize the output', |
|||
), |
|||
|
|||
'debug' => array( |
|||
'runtime' => '', |
|||
'file' => '<bool>', |
|||
'default' => false, |
|||
'desc' => 'Show all PHP errors; add verbosity to WP-CLI bootstrap', |
|||
), |
|||
|
|||
'prompt' => array( |
|||
'runtime' => '', |
|||
'file' => false, |
|||
'default' => false, |
|||
'desc' => 'Prompt the user to enter values for all command arguments', |
|||
), |
|||
|
|||
'quiet' => array( |
|||
'runtime' => '', |
|||
'file' => '<bool>', |
|||
'default' => false, |
|||
'desc' => 'Suppress informational messages', |
|||
), |
|||
|
|||
'apache_modules' => array( |
|||
'file' => '<list>', |
|||
'desc' => 'List of Apache Modules that are to be reported as loaded', |
|||
'multiple' => true, |
|||
'default' => array(), |
|||
), |
|||
|
|||
# --allow-root => (NOT RECOMMENDED) Allow wp-cli to run as root. This poses |
|||
# a security risk, so you probably do not want to do this. |
|||
'allow-root' => array( |
|||
'file' => false, # Explicit. Just in case the default changes. |
|||
'runtime' => '', |
|||
'hidden' => true, |
|||
), |
|||
|
|||
); |
@ -0,0 +1,19 @@ |
|||
<?php |
|||
|
|||
namespace EE_CLI\Dispatcher; |
|||
|
|||
/** |
|||
* Get the path to a command, e.g. "core download" |
|||
* |
|||
* @param EE_CLI\Dispatcher\Subcommand $command |
|||
* @return string |
|||
*/ |
|||
function get_path( $command ) { |
|||
$path = array(); |
|||
|
|||
do { |
|||
array_unshift( $path, $command->get_name() ); |
|||
} while ( $command = $command->get_parent() ); |
|||
|
|||
return $path; |
|||
} |
@ -0,0 +1,21 @@ |
|||
<?php |
|||
|
|||
// Can be used by plugins/themes to check if WP-CLI is running or not |
|||
define( 'EE_CLI', true ); |
|||
define( 'EE_CLI_VERSION', trim( file_get_contents( EE_CLI_ROOT . '/VERSION' ) ) ); |
|||
define( 'EE_CLI_START_MICROTIME', microtime( true ) ); |
|||
|
|||
// Set common headers, to prevent warnings from plugins |
|||
$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.0'; |
|||
$_SERVER['HTTP_USER_AGENT'] = ''; |
|||
$_SERVER['REQUEST_METHOD'] = 'GET'; |
|||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1'; |
|||
|
|||
include EE_CLI_ROOT . '/php/utils.php'; |
|||
include EE_CLI_ROOT . '/php/dispatcher.php'; |
|||
include EE_CLI_ROOT . '/php/class-ee-cli.php'; |
|||
include EE_CLI_ROOT . '/php/class-ee-cli-command.php'; |
|||
|
|||
\EE_CLI\Utils\load_dependencies(); |
|||
|
|||
EE_CLI::get_runner()->start(); |
@ -0,0 +1,25 @@ |
|||
<?php |
|||
|
|||
class WP_Export_Oxymel extends Oxymel { |
|||
public function optional( $tag_name, $contents ) { |
|||
if ( $contents ) { |
|||
$this->$tag_name( $contents ); |
|||
} |
|||
return $this; |
|||
} |
|||
|
|||
public function optional_cdata( $tag_name, $contents ) { |
|||
if ( $contents ) { |
|||
$this->$tag_name->contains->cdata( $contents )->end; |
|||
} |
|||
return $this; |
|||
} |
|||
|
|||
public function cdata( $text ) { |
|||
if ( !seems_utf8( $text ) ) { |
|||
$text = utf8_encode( $text ); |
|||
} |
|||
return parent::cdata( $text ); |
|||
} |
|||
} |
|||
|
@ -0,0 +1,372 @@ |
|||
<?php |
|||
/** |
|||
* Represents a set of posts and other site data to be exported. |
|||
* |
|||
* An immutable object, which gathers all data needed for the export. |
|||
*/ |
|||
class WP_Export_Query { |
|||
const QUERY_CHUNK = 100; |
|||
|
|||
private static $defaults = array( |
|||
'post_ids' => null, |
|||
'post_type' => null, |
|||
'status' => null, |
|||
'author' => null, |
|||
'start_date' => null, |
|||
'end_date' => null, |
|||
'start_id' => null, |
|||
'category' => null, |
|||
); |
|||
|
|||
private $post_ids; |
|||
private $filters; |
|||
private $xml_gen; |
|||
|
|||
private $wheres = array(); |
|||
private $joins = array(); |
|||
|
|||
private $author; |
|||
private $category; |
|||
|
|||
public $missing_parents = false; |
|||
|
|||
public function __construct( $filters = array() ) { |
|||
$this->filters = wp_parse_args( $filters, self::$defaults ); |
|||
$this->post_ids = $this->calculate_post_ids(); |
|||
} |
|||
|
|||
public function post_ids() { |
|||
return $this->post_ids; |
|||
} |
|||
|
|||
public function charset() { |
|||
return get_bloginfo( 'charset' ); |
|||
} |
|||
|
|||
public function site_metadata() { |
|||
$metadata = array( |
|||
'name' => $this->bloginfo_rss( 'name' ), |
|||
'url' => $this->bloginfo_rss( 'url' ), |
|||
'language' => $this->bloginfo_rss( 'language' ), |
|||
'description' => $this->bloginfo_rss( 'description' ), |
|||
'pubDate' => date( 'D, d M Y H:i:s +0000' ), |
|||
'site_url' => is_multisite()? network_home_url() : $this->bloginfo_rss( 'url' ), |
|||
'blog_url' => $this->bloginfo_rss( 'url' ), |
|||
); |
|||
return $metadata; |
|||
} |
|||
|
|||
public function wp_generator_tag() { |
|||
return apply_filters( 'the_generator', get_the_generator( 'export' ), 'export' ); |
|||
} |
|||
|
|||
public function authors() { |
|||
global $wpdb; |
|||
$authors = array(); |
|||
$author_ids = $wpdb->get_col( "SELECT DISTINCT post_author FROM $wpdb->posts WHERE post_status != 'auto-draft'" ); |
|||
foreach ( (array) $author_ids as $author_id ) { |
|||
$authors[] = get_userdata( $author_id ); |
|||
} |
|||
$authors = array_filter( $authors ); |
|||
return $authors; |
|||
} |
|||
|
|||
public function categories() { |
|||
if ( $this->category ) { |
|||
return array( $this->category ); |
|||
} |
|||
if ( $this->filters['post_type'] ) { |
|||
return array(); |
|||
} |
|||
$categories = (array) get_categories( array( 'get' => 'all' ) ); |
|||
|
|||
$this->check_for_orphaned_terms( $categories ); |
|||
|
|||
$categories = self::topologically_sort_terms( $categories ); |
|||
|
|||
return $categories; |
|||
} |
|||
|
|||
public function tags() { |
|||
if ( $this->filters['post_type'] ) { |
|||
return array(); |
|||
} |
|||
$tags = (array) get_tags( array( 'get' => 'all' ) ); |
|||
|
|||
$this->check_for_orphaned_terms( $tags ); |
|||
|
|||
return $tags; |
|||
} |
|||
|
|||
public function custom_taxonomies_terms() { |
|||
if ( $this->filters['post_type'] ) { |
|||
return array(); |
|||
} |
|||
$custom_taxonomies = get_taxonomies( array( '_builtin' => false ) ); |
|||
$custom_terms = (array) get_terms( $custom_taxonomies, array( 'get' => 'all' ) ); |
|||
$this->check_for_orphaned_terms( $custom_terms ); |
|||
$custom_terms = self::topologically_sort_terms( $custom_terms ); |
|||
return $custom_terms; |
|||
} |
|||
|
|||
public function nav_menu_terms() { |
|||
$nav_menus = wp_get_nav_menus(); |
|||
foreach( $nav_menus as &$term ) { |
|||
$term->description = ''; |
|||
} |
|||
return $nav_menus; |
|||
} |
|||
|
|||
public function exportify_post( $post ) { |
|||
$GLOBALS['wp_query']->in_the_loop = true; |
|||
$previous_global_post = \WP_CLI\Utils\get_flag_value( $GLOBALS, 'post' ); |
|||
$GLOBALS['post'] = $post; |
|||
setup_postdata( $post ); |
|||
$post->post_content = apply_filters( 'the_content_export', $post->post_content ); |
|||
$post->post_excerpt = apply_filters( 'the_excerpt_export', $post->post_excerpt ); |
|||
$post->is_sticky = is_sticky( $post->ID ) ? 1 : 0; |
|||
$post->terms = self::get_terms_for_post( $post ); |
|||
$post->meta = self::get_meta_for_post( $post ); |
|||
$post->comments = self::get_comments_for_post( $post ); |
|||
$GLOBALS['post'] = $previous_global_post; |
|||
return $post; |
|||
} |
|||
|
|||
public function posts() { |
|||
$posts_iterator = new WP_Post_IDs_Iterator( $this->post_ids, self::QUERY_CHUNK ); |
|||
return new WP_Map_Iterator( $posts_iterator, array( $this, 'exportify_post' ) ); |
|||
} |
|||
|
|||
private function calculate_post_ids() { |
|||
global $wpdb; |
|||
if ( is_array( $this->filters['post_ids'] ) ) { |
|||
return $this->filters['post_ids']; |
|||
} |
|||
$this->post_type_where(); |
|||
$this->status_where(); |
|||
$this->author_where(); |
|||
$this->start_date_where(); |
|||
$this->end_date_where(); |
|||
$this->start_id_where(); |
|||
$this->category_where(); |
|||
|
|||
$where = implode( ' AND ', array_filter( $this->wheres ) ); |
|||
if ( $where ) $where = "WHERE $where"; |
|||
$join = implode( ' ', array_filter( $this->joins ) ); |
|||
|
|||
$post_ids = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} AS p $join $where" ); |
|||
$post_ids = array_merge( $post_ids, $this->attachments_for_specific_post_types( $post_ids ) ); |
|||
return $post_ids; |
|||
} |
|||
|
|||
private function post_type_where() { |
|||
global $wpdb; |
|||
$post_types_filters = array( 'can_export' => true ); |
|||
if ( $this->filters['post_type'] ) { |
|||
$post_types = $this->filters['post_type']; |
|||
|
|||
// Flatten single post types |
|||
if ( is_array( $post_types ) && 1 === count( $post_types ) ) { |
|||
$post_types = array_shift( $post_types ); |
|||
} |
|||
$post_types_filters = array_merge( $post_types_filters, array( 'name' => $post_types ) ); |
|||
} |
|||
|
|||
// Multiple post types |
|||
if ( is_array( $post_types_filters['name'] ) ) { |
|||
$post_types = array(); |
|||
foreach ( $post_types_filters['name'] as $post_type ) { |
|||
if ( post_type_exists( $post_type ) ) { |
|||
$post_types[] = $post_type; |
|||
} |
|||
} |
|||
} else { |
|||
$post_types = get_post_types( $post_types_filters ); |
|||
} |
|||
|
|||
if ( ! $post_types ) { |
|||
$this->wheres[] = 'p.post_type IS NULL'; |
|||
return; |
|||
} |
|||
$this->wheres[] = _wp_export_build_IN_condition( 'p.post_type', $post_types ); |
|||
} |
|||
|
|||
private function status_where() { |
|||
global $wpdb; |
|||
if ( !$this->filters['status'] ) { |
|||
$this->wheres[] = "p.post_status != 'auto-draft'"; |
|||
return; |
|||
} |
|||
$this->wheres[] = $wpdb->prepare( 'p.post_status = %s', $this->filters['status'] ); |
|||
} |
|||
|
|||
private function author_where() { |
|||
global $wpdb; |
|||
$user = $this->find_user_from_any_object( $this->filters['author'] ); |
|||
if ( !$user || is_wp_error( $user ) ) { |
|||
return; |
|||
} |
|||
$this->author = $user; |
|||
$this->wheres[] = $wpdb->prepare( 'p.post_author = %d', $user->ID ); |
|||
} |
|||
|
|||
private function start_date_where() { |
|||
global $wpdb; |
|||
$timestamp = strtotime( $this->filters['start_date'] ); |
|||
if ( !$timestamp ) { |
|||
return; |
|||
} |
|||
$this->wheres[] = $wpdb->prepare( 'p.post_date >= %s', date( 'Y-m-d 00:00:00', $timestamp ) ); |
|||
} |
|||
|
|||
private function end_date_where() { |
|||
global $wpdb; |
|||
if ( preg_match( '/^\d{4}-\d{2}$/', $this->filters['end_date'] ) ) { |
|||
$timestamp = $this->get_timestamp_for_the_last_day_of_a_month( $this->filters['end_date'] ); |
|||
} else { |
|||
$timestamp = strtotime( $this->filters['end_date'] ); |
|||
} |
|||
if ( !$timestamp ) { |
|||
return; |
|||
} |
|||
$this->wheres[] = $wpdb->prepare( 'p.post_date <= %s', date( 'Y-m-d 23:59:59', $timestamp ) ); |
|||
} |
|||
|
|||
private function start_id_where() { |
|||
global $wpdb; |
|||
|
|||
$start_id = absint( $this->filters['start_id'] ); |
|||
if ( 0 === $start_id ) { |
|||
return; |
|||
} |
|||
$this->wheres[] = $wpdb->prepare( 'p.ID >= %d', $start_id ); |
|||
} |
|||
|
|||
private function get_timestamp_for_the_last_day_of_a_month( $yyyy_mm ) { |
|||
return strtotime( "$yyyy_mm +1month -1day" ); |
|||
} |
|||
|
|||
private function category_where() { |
|||
global $wpdb; |
|||
if ( 'post' != $this->filters['post_type'] && ! in_array( 'post', (array) $this->filters['post_type'] ) ) { |
|||
return; |
|||
} |
|||
$category = $this->find_category_from_any_object( $this->filters['category'] ); |
|||
if ( !$category ) { |
|||
return; |
|||
} |
|||
$this->category = $category; |
|||
$this->joins[] = "INNER JOIN {$wpdb->term_relationships} AS tr ON (p.ID = tr.object_id)"; |
|||
$this->wheres[] = $wpdb->prepare( 'tr.term_taxonomy_id = %d', $category->term_taxonomy_id ); |
|||
} |
|||
|
|||
private function attachments_for_specific_post_types( $post_ids ) { |
|||
global $wpdb; |
|||
if ( !$this->filters['post_type'] ) { |
|||
return array(); |
|||
} |
|||
$attachment_ids = array(); |
|||
while ( $batch_of_post_ids = array_splice( $post_ids, 0, self::QUERY_CHUNK ) ) { |
|||
$post_parent_condition = _wp_export_build_IN_condition( 'post_parent', $batch_of_post_ids ); |
|||
$attachment_ids = array_merge( $attachment_ids, (array)$wpdb->get_col( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'attachment' AND $post_parent_condition" ) ); |
|||
} |
|||
return array_map( 'intval', $attachment_ids ); |
|||
} |
|||
|
|||
private function bloginfo_rss( $section ) { |
|||
return apply_filters( 'bloginfo_rss', get_bloginfo_rss( $section ), $section ); |
|||
} |
|||
|
|||
private function find_user_from_any_object( $user ) { |
|||
if ( is_numeric( $user ) ) { |
|||
return get_user_by( 'id', $user ); |
|||
} elseif ( is_string( $user ) ) { |
|||
return get_user_by( 'login', $user ); |
|||
} elseif ( isset( $user->ID ) ) { |
|||
return get_user_by( 'id', $user->ID ); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private function find_category_from_any_object( $category ) { |
|||
if ( is_numeric( $category ) ) { |
|||
return get_term( $category, 'category' ); |
|||
} elseif ( is_string( $category ) ) { |
|||
$term = term_exists( $category, 'category' ); |
|||
return isset( $term['term_id'] )? get_term( $term['term_id'], 'category' ) : false; |
|||
} elseif ( isset( $category->term_id ) ) { |
|||
return get_term( $category->term_id, 'category' ); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private static function topologically_sort_terms( $terms ) { |
|||
$sorted = array(); |
|||
while ( $term = array_shift( $terms ) ) { |
|||
if ( $term->parent == 0 || isset( $sorted[$term->parent] ) ) |
|||
$sorted[$term->term_id] = $term; |
|||
else |
|||
$terms[] = $term; |
|||
} |
|||
return $sorted; |
|||
} |
|||
|
|||
private function check_for_orphaned_terms( $terms ) { |
|||
$term_ids = array(); |
|||
$have_parent = array(); |
|||
|
|||
foreach ( $terms as $term ) { |
|||
$term_ids[ $term->term_id ] = true; |
|||
if ( $term->parent != 0 ) |
|||
$have_parent[] = $term; |
|||
} |
|||
|
|||
foreach ( $have_parent as $has_parent ) { |
|||
if ( ! isset( $term_ids[ $has_parent->parent ] ) ) { |
|||
$this->missing_parents = $has_parent; |
|||
throw new WP_Export_Term_Exception( sprintf( __( 'Term is missing a parent: %s (%d)' ), $has_parent->slug, $has_parent->term_taxonomy_id ) ); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static function get_terms_for_post( $post ) { |
|||
$taxonomies = get_object_taxonomies( $post->post_type ); |
|||
if ( empty( $taxonomies ) ) |
|||
return array(); |
|||
$terms = wp_get_object_terms( $post->ID, $taxonomies ); |
|||
$terms = $terms? $terms : array(); |
|||
return $terms; |
|||
} |
|||
|
|||
private static function get_meta_for_post( $post ) { |
|||
global $wpdb; |
|||
$meta_for_export = array(); |
|||
$meta_from_db = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->postmeta WHERE post_id = %d", $post->ID ) ); |
|||
foreach ( $meta_from_db as $meta ) { |
|||
if ( apply_filters( 'wxr_export_skip_postmeta', false, $meta->meta_key, $meta ) ) |
|||
continue; |
|||
if ( in_array( $meta->meta_key, array( '_edit_lock', '_wp_attachment_metadata', '_wp_attached_file' ) ) ) { |
|||
continue; |
|||
} |
|||
$meta_for_export[] = $meta; |
|||
} |
|||
return $meta_for_export; |
|||
} |
|||
|
|||
private static function get_comments_for_post( $post ) { |
|||
global $wpdb; |
|||
$comments = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved <> 'spam'", $post->ID ) ); |
|||
foreach( $comments as $comment ) { |
|||
$meta = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->commentmeta WHERE comment_id = %d", $comment->comment_ID ) ); |
|||
$meta = $meta? $meta : array(); |
|||
$comment->meta = $meta; |
|||
} |
|||
return $comments; |
|||
} |
|||
} |
|||
|
|||
class WP_Export_Exception extends RuntimeException { |
|||
} |
|||
|
|||
class WP_Export_Term_Exception extends RuntimeException { |
|||
} |
@ -0,0 +1,261 @@ |
|||
<?php |
|||
/** |
|||
* Version number for the export format. |
|||
* |
|||
* Bump this when something changes that might affect compatibility. |
|||
* |
|||
* @since 2.5.0 |
|||
*/ |
|||
define( 'WXR_VERSION', '1.2' ); |
|||
|
|||
/** |
|||
* Responsible for formatting the data in WP_Export_Query to WXR |
|||
*/ |
|||
class WP_Export_WXR_Formatter { |
|||
public function __construct( $export ) { |
|||
$this->export = $export; |
|||
$this->wxr_version = WXR_VERSION; |
|||
} |
|||
|
|||
public function before_posts() { |
|||
$before_posts_xml = ''; |
|||
$before_posts_xml .= $this->header(); |
|||
$before_posts_xml .= $this->site_metadata(); |
|||
$before_posts_xml .= $this->authors(); |
|||
$before_posts_xml .= $this->categories(); |
|||
$before_posts_xml .= $this->tags(); |
|||
$before_posts_xml .= $this->nav_menu_terms(); |
|||
$before_posts_xml .= $this->custom_taxonomies_terms(); |
|||
$before_posts_xml .= $this->rss2_head_action(); |
|||
return $before_posts_xml; |
|||
} |
|||
|
|||
public function posts() { |
|||
return new WP_Map_Iterator( $this->export->posts(), array( $this, 'post' ) ); |
|||
} |
|||
|
|||
public function after_posts() { |
|||
return $this->footer(); |
|||
} |
|||
|
|||
public function header() { |
|||
$oxymel = new Oxymel; |
|||
$charset = $this->export->charset(); |
|||
$wp_generator_tag = $this->export->wp_generator_tag(); |
|||
$comment = <<<COMMENT |
|||
|
|||
This is a WordPress eXtended RSS file generated by WordPress as an export of your site. |
|||
It contains information about your site's posts, pages, comments, categories, and other content. |
|||
You may use this file to transfer that content from one site to another. |
|||
This file is not intended to serve as a complete backup of your site. |
|||
|
|||
To import this information into a WordPress site follow these steps: |
|||
1. Log in to that site as an administrator. |
|||
2. Go to Tools: Import in the WordPress admin panel. |
|||
3. Install the "WordPress" importer from the list. |
|||
4. Activate & Run Importer. |
|||
5. Upload this file using the form provided on that page. |
|||
6. You will first be asked to map the authors in this export file to users |
|||
on the site. For each author, you may choose to map to an |
|||
existing user on the site or to create a new user. |
|||
7. WordPress will then import each of the posts, pages, comments, categories, etc. |
|||
contained in this file into your site. |
|||
|
|||
COMMENT; |
|||
return $oxymel |
|||
->xml |
|||
->comment( $comment ) |
|||
->raw( $wp_generator_tag ) |
|||
->open_rss( array( |
|||
'version' => '2.0', |
|||
'xmlns:excerpt' => "http://wordpress.org/export/{$this->wxr_version}/excerpt/", |
|||
'xmlns:content' => "http://purl.org/rss/1.0/modules/content/", |
|||
'xmlns:wfw' => "http://wellformedweb.org/CommentAPI/", |
|||
'xmlns:dc' => "http://purl.org/dc/elements/1.1/", |
|||
'xmlns:wp' => "http://wordpress.org/export/{$this->wxr_version}/", |
|||
) ) |
|||
->open_channel |
|||
->to_string(); |
|||
|
|||
} |
|||
|
|||
public function site_metadata() { |
|||
$oxymel = new Oxymel; |
|||
$metadata = $this->export->site_metadata(); |
|||
return $oxymel |
|||
->title( $metadata['name'] ) |
|||
->link( $metadata['url'] ) |
|||
->description( $metadata['description'] ) |
|||
->pubDate( $metadata['pubDate'] ) |
|||
->language( $metadata['language'] ) |
|||
->tag( 'wp:wxr_version', $this->wxr_version ) |
|||
->tag( 'wp:base_site_url', $metadata['site_url'] ) |
|||
->tag( 'wp:base_blog_url', $metadata['blog_url'] ) |
|||
->to_string(); |
|||
} |
|||
|
|||
public function authors() { |
|||
$oxymel = new Oxymel; |
|||
$authors = $this->export->authors(); |
|||
foreach ( $authors as $author ) { |
|||
$oxymel |
|||
->tag( 'wp:author' )->contains |
|||
->tag( 'wp:author_id', $author->ID ) |
|||
->tag( 'wp:author_login', $author->user_login ) |
|||
->tag( 'wp:author_email', $author->user_email ) |
|||
->tag( 'wp:author_display_name' )->contains->cdata( $author->display_name )->end |
|||
->tag( 'wp:author_first_name' )->contains->cdata( $author->user_firstname )->end |
|||
->tag( 'wp:author_last_name' )->contains->cdata( $author->user_lastname )->end |
|||
->end; |
|||
} |
|||
return $oxymel->to_string(); |
|||
} |
|||
|
|||
public function categories() { |
|||
$oxymel = new WP_Export_Oxymel; |
|||
$categories = $this->export->categories(); |
|||
foreach( $categories as $term_id => $category ) { |
|||
$category->parent_slug = $category->parent? $categories[$category->parent]->slug : ''; |
|||
$oxymel->tag( 'wp:category' )->contains |
|||
->tag( 'wp:term_id', $category->term_id ) |
|||
->tag( 'wp:category_nicename', $category->slug ) |
|||
->tag( 'wp:category_parent', $category->parent_slug ) |
|||
->optional_cdata( 'wp:cat_name', $category->name ) |
|||
->optional_cdata( 'wp:category_description', $category->description ) |
|||
->end; |
|||
} |
|||
return $oxymel->to_string(); |
|||
} |
|||
|
|||
public function tags() { |
|||
$oxymel = new WP_Export_Oxymel; |
|||
$tags = $this->export->tags(); |
|||
foreach( $tags as $tag ) { |
|||
$oxymel->tag( 'wp:tag' )->contains |
|||
->tag( 'wp:term_id', $tag->term_id ) |
|||
->tag( 'wp:tag_slug', $tag->slug ) |
|||
->optional_cdata( 'wp:tag_name', $tag->name ) |
|||
->optional_cdata( 'wp:tag_description', $tag->description ) |
|||
->end; |
|||
} |
|||
return $oxymel->to_string(); |
|||
} |
|||
|
|||
public function nav_menu_terms() { |
|||
return $this->terms( $this->export->nav_menu_terms() ); |
|||
} |
|||
|
|||
public function custom_taxonomies_terms() { |
|||
return $this->terms( $this->export->custom_taxonomies_terms() ); |
|||
} |
|||
|
|||
public function rss2_head_action() { |
|||
ob_start(); |
|||
do_action( 'rss2_head' ); |
|||
$action_output = ob_get_clean(); |
|||
return $action_output; |
|||
} |
|||
|
|||
public function post( $post ) { |
|||
$oxymel = new WP_Export_Oxymel; |
|||
$GLOBALS['wp_query']->in_the_loop = true; |
|||
$GLOBALS['post'] = $post; |
|||
setup_postdata( $post ); |
|||
|
|||
$oxymel->item->contains |
|||
->title( apply_filters( 'the_title_rss', $post->post_title ) ) |
|||
->link( esc_url( apply_filters('the_permalink_rss', get_permalink() ) ) ) |
|||
->pubDate( mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true ), false ) ) |
|||
->tag( 'dc:creator', get_the_author_meta( 'login' ) ) |
|||
->guid( esc_url( get_the_guid() ), array( 'isPermaLink' => 'false' ) ) |
|||
->description( '' ) |
|||
->tag( 'content:encoded' )->contains->cdata( $post->post_content )->end |
|||
->tag( 'excerpt:encoded' )->contains->cdata( $post->post_excerpt )->end |
|||
->tag( 'wp:post_id', $post->ID ) |
|||
->tag( 'wp:post_date', $post->post_date ) |
|||
->tag( 'wp:post_date_gmt', $post->post_date_gmt ) |
|||
->tag( 'wp:comment_status', $post->comment_status ) |
|||
->tag( 'wp:ping_status', $post->ping_status ) |
|||
->tag( 'wp:post_name', $post->post_name ) |
|||
->tag( 'wp:status', $post->post_status ) |
|||
->tag( 'wp:post_parent', $post->post_parent ) |
|||
->tag( 'wp:menu_order', $post->menu_order ) |
|||
->tag( 'wp:post_type', $post->post_type ) |
|||
->tag( 'wp:post_password', $post->post_password ) |
|||
->tag( 'wp:is_sticky', $post->is_sticky ) |
|||
->optional( 'wp:attachment_url', wp_get_attachment_url( $post->ID ) ); |
|||
foreach( $post->terms as $term ) { |
|||
$oxymel |
|||
->category( array( 'domain' => $term->taxonomy, 'nicename' => $term->slug ) )->contains->cdata( $term->name )->end; |
|||
} |
|||
foreach( $post->meta as $meta ) { |
|||
$oxymel |
|||
->tag( 'wp:postmeta' )->contains |
|||
->tag( 'wp:meta_key', $meta->meta_key ) |
|||
->tag( 'wp:meta_value' )->contains->cdata( $meta->meta_value )->end |
|||
->end; |
|||
} |
|||
foreach( $post->comments as $comment ) { |
|||
$oxymel |
|||
->tag( 'wp:comment' )->contains |
|||
->tag( 'wp:comment_id', $comment->comment_ID ) |
|||
->tag( 'wp:comment_author' )->contains->cdata( $comment->comment_author )->end |
|||
->tag( 'wp:comment_author_email', $comment->comment_author_email ) |
|||
->tag( 'wp:comment_author_url', esc_url( $comment->comment_author_url ) ) |
|||
->tag( 'wp:comment_author_IP', $comment->comment_author_IP ) |
|||
->tag( 'wp:comment_date', $comment->comment_date ) |
|||
->tag( 'wp:comment_date_gmt', $comment->comment_date_gmt ) |
|||
->tag( 'wp:comment_content' )->contains->cdata( $comment->comment_content )->end |
|||
->tag( 'wp:comment_approved', $comment->comment_approved ) |
|||
->tag( 'wp:comment_type', $comment->comment_type ) |
|||
->tag( 'wp:comment_parent', $comment->comment_parent ) |
|||
->tag( 'wp:comment_user_id', $comment->user_id ) |
|||
->oxymel( $this->comment_meta( $comment ) ) |
|||
->end; |
|||
} |
|||
$oxymel |
|||
->end; |
|||
return $oxymel->to_string(); |
|||
} |
|||
|
|||
public function footer() { |
|||
$oxymel = new Oxymel; |
|||
return $oxymel->close_channel->close_rss->to_string(); |
|||
} |
|||
|
|||
protected function terms( $terms ) { |
|||
$oxymel = new WP_Export_Oxymel; |
|||
foreach( $terms as $term ) { |
|||
$term->parent_slug = $term->parent? $terms[$term->parent]->slug : ''; |
|||
$oxymel->tag( 'wp:term' )->contains |
|||
->tag( 'wp:term_id', $term->term_id ) |
|||
->tag( 'wp:term_taxonomy', $term->taxonomy ) |
|||
->tag( 'wp:term_slug', $term->slug ); |
|||
if ( 'nav_menu' != $term->taxonomy ) { |
|||
$oxymel |
|||
->tag( 'wp:term_parent', $term->parent_slug ); |
|||
} |
|||
$oxymel |
|||
->optional_cdata( 'wp:term_name', $term->name ) |
|||
->optional_cdata( 'wp:term_description', $term->description ) |
|||
->end; |
|||
} |
|||
return $oxymel->to_string(); |
|||
} |
|||
|
|||
protected function comment_meta( $comment ) { |
|||
global $wpdb; |
|||
$metas = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->commentmeta WHERE comment_id = %d", $comment->comment_ID ) ); |
|||
if ( !$metas ) { |
|||
return new Oxymel; |
|||
} |
|||
$oxymel = new WP_Export_Oxymel; |
|||
foreach( $metas as $meta ) { |
|||
$oxymel->tag( 'wp:commentmeta' )->contains |
|||
->tag( 'wp:meta_key', $meta->meta_key ) |
|||
->tag( 'wp:meta_value' )->contains->cdata( $meta->meta_value )->end |
|||
->end; |
|||
} |
|||
return $oxymel; |
|||
} |
|||
} |
@ -0,0 +1,41 @@ |
|||
<?php |
|||
|
|||
function wp_export( $args = array() ) { |
|||
$defaults = array( |
|||
'filters' => array(), |
|||
'format' => 'WP_Export_WXR_Formatter', |
|||
'writer' => 'WP_Export_Returner', |
|||
'writer_args' => null, |
|||
); |
|||
$args = wp_parse_args( $args, $defaults ); |
|||
$export_query = new WP_Export_Query( $args['filters'] ); |
|||
$formatter = new $args['format']( $export_query ); |
|||
$writer = new $args['writer']( $formatter, $args['writer_args'] ); |
|||
try { |
|||
return $writer->export(); |
|||
} catch ( WP_Export_Exception $e ) { |
|||
return new WP_Error( 'wp-export-error', $e->getMessage() ); |
|||
} |
|||
} |
|||
|
|||
function wp_export_new_style_args_from_old_style_args( $args ) { |
|||
if ( isset( $args['content'] ) ) { |
|||
if ( 'all' == $args['content'] ) { |
|||
unset( $args['content'] ); |
|||
} else { |
|||
$args['post_type'] = $args['content']; |
|||
} |
|||
} |
|||
return $args; |
|||
} |
|||
|
|||
// TEMPORARY |
|||
function _wp_export_build_IN_condition( $column_name, $values, $format = '%s' ) { |
|||
global $wpdb; |
|||
|
|||
if ( !is_array( $values ) || empty( $values ) ) { |
|||
return ''; |
|||
} |
|||
$formats = implode( ', ', array_fill( 0, count( $values ), $format ) ); |
|||
return $wpdb->prepare( "$column_name IN ($formats)", $values ); |
|||
} |
@ -0,0 +1,80 @@ |
|||
<?php |
|||
class WP_Map_Iterator extends IteratorIterator { |
|||
function __construct( $iterator, $callback ) { |
|||
$this->callback = $callback; |
|||
parent::__construct( $iterator ); |
|||
} |
|||
|
|||
function current() { |
|||
$original_current = parent::current(); |
|||
return call_user_func( $this->callback, $original_current ); |
|||
} |
|||
} |
|||
|
|||
class WP_Post_IDs_Iterator implements Iterator { |
|||
private $limit = 100; |
|||
private $post_ids; |
|||
private $ids_left; |
|||
private $results = array(); |
|||
|
|||
public function __construct( $post_ids, $limit = null ) { |
|||
$this->db = $GLOBALS['wpdb']; |
|||
$this->post_ids = $post_ids; |
|||
$this->ids_left = $post_ids; |
|||
if ( !is_null( $limit ) ) { |
|||
$this->limit = $limit; |
|||
} |
|||
} |
|||
|
|||
public function current() { |
|||
return $this->results[$this->index_in_results]; |
|||
} |
|||
|
|||
public function key() { |
|||
return $this->global_index; |
|||
} |
|||
|
|||
public function next() { |
|||
$this->index_in_results++; |
|||
$this->global_index++; |
|||
} |
|||
|
|||
public function rewind() { |
|||
$this->results = array(); |
|||
$this->global_index = 0; |
|||
$this->index_in_results = 0; |
|||
$this->ids_left = $this->post_ids; |
|||
} |
|||
|
|||
public function valid() { |
|||
if ( isset( $this->results[$this->index_in_results] ) ) { |
|||
return true; |
|||
} |
|||
if ( empty( $this->ids_left ) ) { |
|||
return false; |
|||
} |
|||
$has_posts = $this->load_next_posts_from_db(); |
|||
if ( !$has_posts ) { |
|||
return false; |
|||
} |
|||
$this->index_in_results = 0; |
|||
return true; |
|||
} |
|||
|
|||
private function load_next_posts_from_db() { |
|||
$next_batch_post_ids = array_splice( $this->ids_left, 0, $this->limit ); |
|||
$in_post_ids_sql = _wp_export_build_IN_condition( 'ID', $next_batch_post_ids ); |
|||
$this->results = $this->db->get_results( "SELECT * FROM {$this->db->posts} WHERE $in_post_ids_sql" ); |
|||
if ( !$this->results ) { |
|||
if ( $this->db->last_error ) { |
|||
throw new WP_Iterator_Exception( 'Database error: ' . $this->db->last_error ); |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
class WP_Iterator_Exception extends Exception { |
|||
} |
@ -0,0 +1,184 @@ |
|||
<?php |
|||
abstract class WP_Export_Base_Writer { |
|||
protected $formatter; |
|||
|
|||
function __construct( $formatter ) { |
|||
$this->formatter = $formatter; |
|||
} |
|||
|
|||
public function export() { |
|||
$this->write( $this->formatter->before_posts() ); |
|||
foreach( $this->formatter->posts() as $post_in_wxr ) { |
|||
$this->write( $post_in_wxr ); |
|||
} |
|||
$this->write( $this->formatter->after_posts() ); |
|||
} |
|||
|
|||
abstract protected function write( $xml ); |
|||
} |
|||
|
|||
class WP_Export_XML_Over_HTTP extends WP_Export_Base_Writer { |
|||
private $file_name; |
|||
|
|||
function __construct( $formatter, $file_name ) { |
|||
parent::__construct( $formatter ); |
|||
$this->file_name = $file_name; |
|||
} |
|||
|
|||
public function export() { |
|||
try { |
|||
$export = $this->get_export(); |
|||
$this->send_headers(); |
|||
echo $export; |
|||
} catch ( WP_Export_Exception $e ) { |
|||
$message = apply_filters( 'export_error_message', $e->getMessage() ); |
|||
wp_die( $message, __( 'Export Error' ), array( 'back_link' => true ) ); |
|||
} catch ( WP_Export_Term_Exception $e ) { |
|||
do_action( 'export_term_orphaned', $this->formatter->export->missing_parents ); |
|||
$message = apply_filters( 'export_term_error_message', $e->getMessage() ); |
|||
wp_die( $message, __( 'Export Error' ), array( 'back_link' => true ) ); |
|||
} |
|||
} |
|||
|
|||
protected function write( $xml ) { |
|||
$this->result .= $xml; |
|||
} |
|||
|
|||
protected function get_export() { |
|||
$this->result = ''; |
|||
parent::export(); |
|||
return $this->result; |
|||
} |
|||
|
|||
protected function send_headers() { |
|||
header( 'Content-Description: File Transfer' ); |
|||
header( 'Content-Disposition: attachment; filename=' . $this->file_name ); |
|||
header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true ); |
|||
} |
|||
} |
|||
|
|||
class WP_Export_Returner extends WP_Export_Base_Writer { |
|||
private $result = ''; |
|||
|
|||
public function export() { |
|||
$this->private = ''; |
|||
try { |
|||
parent::export(); |
|||
} catch ( WP_Export_Exception $e ) { |
|||
$message = apply_filters( 'export_error_message', $e->getMessage() ); |
|||
return new WP_Error( 'wp-export-error', $message ); |
|||
|
|||
} catch ( WP_Export_Term_Exception $e ) { |
|||
do_action( 'export_term_orphaned', $this->formatter->export->missing_parents ); |
|||
$message = apply_filters( 'export_term_error_message', $e->getMessage() ); |
|||
return new WP_Error( 'wp-export-error', $message ); |
|||
} |
|||
return $this->result; |
|||
} |
|||
protected function write( $xml ) { |
|||
$this->result .= $xml; |
|||
} |
|||
} |
|||
|
|||
class WP_Export_File_Writer extends WP_Export_Base_Writer { |
|||
private $f; |
|||
private $file_name; |
|||
|
|||
public function __construct( $formatter, $file_name ) { |
|||
parent::__construct( $formatter ); |
|||
$this->file_name = $file_name; |
|||
} |
|||
|
|||
public function export() { |
|||
$this->f = fopen( $this->file_name, 'w' ); |
|||
if ( !$this->f ) { |
|||
throw new WP_Export_Exception( sprintf( __( 'WP Export: error opening %s for writing.' ), $this->file_name ) ); |
|||
} |
|||
|
|||
try { |
|||
parent::export(); |
|||
} catch ( WP_Export_Exception $e ) { |
|||
throw $e; |
|||
} catch ( WP_Export_Term_Exception $e ) { |
|||
throw $e; |
|||
} |
|||
|
|||
fclose( $this->f ); |
|||
} |
|||
|
|||
protected function write( $xml ) { |
|||
$res = fwrite( $this->f, $xml); |
|||
if ( false === $res ) { |
|||
throw new WP_Export_Exception( __( 'WP Export: error writing to export file.' ) ); |
|||
} |
|||
} |
|||
} |
|||
|
|||
class WP_Export_Split_Files_Writer extends WP_Export_Base_Writer { |
|||
private $result = ''; |
|||
private $f; |
|||
private $next_file_number = 0; |
|||
private $current_file_size = 0; |
|||
|
|||
function __construct( $formatter, $writer_args = array() ) { |
|||
parent::__construct( $formatter ); |
|||
//TODO: check if args are not missing |
|||
$this->max_file_size = is_null( $writer_args['max_file_size'] ) ? 15 * MB_IN_BYTES : $writer_args['max_file_size']; |
|||
$this->destination_directory = $writer_args['destination_directory']; |
|||
$this->filename_template = $writer_args['filename_template']; |
|||
$this->before_posts_xml = $this->formatter->before_posts(); |
|||
$this->after_posts_xml = $this->formatter->after_posts(); |
|||
} |
|||
|
|||
public function export() { |
|||
$this->start_new_file(); |
|||
foreach( $this->formatter->posts() as $post_xml ) { |
|||
if ( $this->current_file_size + strlen( $post_xml ) > $this->max_file_size ) { |
|||
$this->start_new_file(); |
|||
} |
|||
$this->write( $post_xml ); |
|||
} |
|||
$this->close_current_file(); |
|||
} |
|||
|
|||
protected function write( $xml ) { |
|||
$res = fwrite( $this->f, $xml); |
|||
if ( false === $res ) { |
|||
throw new WP_Export_Exception( __( 'WP Export: error writing to export file.' ) ); |
|||
} |
|||
$this->current_file_size += strlen( $xml ); |
|||
} |
|||
|
|||
private function start_new_file() { |
|||
if ( $this->f ) { |
|||
$this->close_current_file(); |
|||
} |
|||
$file_path = $this->next_file_path(); |
|||
$this->f = fopen( $file_path, 'w' ); |
|||
if ( !$this->f ) { |
|||
throw new WP_Export_Exception( sprintf( __( 'WP Export: error opening %s for writing.' ), $file_path ) ); |
|||
} |
|||
do_action( 'wp_export_new_file', $file_path ); |
|||
$this->current_file_size = 0; |
|||
$this->write( $this->before_posts_xml ); |
|||
} |
|||
|
|||
private function close_current_file() { |
|||
if ( !$this->f ) { |
|||
return; |
|||
} |
|||
$this->write( $this->after_posts_xml ); |
|||
fclose( $this->f ); |
|||
} |
|||
|
|||
private function next_file_name() { |
|||
$next_file_name = sprintf( $this->filename_template, $this->next_file_number ); |
|||
$this->next_file_number++; |
|||
return $next_file_name; |
|||
} |
|||
|
|||
private function next_file_path() { |
|||
return untrailingslashit( $this->destination_directory ) . DIRECTORY_SEPARATOR . $this->next_file_name(); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,575 @@ |
|||
<?php |
|||
|
|||
// Utilities that do NOT depend on WordPress code. |
|||
|
|||
namespace EE_CLI\Utils; |
|||
|
|||
use \Composer\Semver\Comparator; |
|||
use \Composer\Semver\Semver; |
|||
use \EE_CLI\Dispatcher; |
|||
use \EE_CLI\Iterators\Transform; |
|||
|
|||
function inside_phar() { |
|||
return 0 === strpos( EE_CLI_ROOT, 'phar://' ); |
|||
} |
|||
|
|||
// Files that need to be read by external programs have to be extracted from the Phar archive. |
|||
function extract_from_phar( $path ) { |
|||
if ( ! inside_phar() ) { |
|||
return $path; |
|||
} |
|||
|
|||
$fname = basename( $path ); |
|||
|
|||
$tmp_path = sys_get_temp_dir() . "/ee-cli-$fname"; |
|||
|
|||
copy( $path, $tmp_path ); |
|||
|
|||
register_shutdown_function( function() use ( $tmp_path ) { |
|||
@unlink( $tmp_path ); |
|||
} ); |
|||
|
|||
return $tmp_path; |
|||
} |
|||
|
|||
function load_dependencies() { |
|||
if ( inside_phar() ) { |
|||
require EE_CLI_ROOT . '/vendor/autoload.php'; |
|||
return; |
|||
} |
|||
|
|||
$has_autoload = false; |
|||
|
|||
foreach ( get_vendor_paths() as $vendor_path ) { |
|||
if ( file_exists( $vendor_path . '/autoload.php' ) ) { |
|||
require $vendor_path . '/autoload.php'; |
|||
$has_autoload = true; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if ( !$has_autoload ) { |
|||
fputs( STDERR, "Internal error: Can't find Composer autoloader.\nTry running: composer install\n" ); |
|||
exit(3); |
|||
} |
|||
} |
|||
|
|||
function get_vendor_paths() { |
|||
$vendor_paths = array( |
|||
EE_CLI_ROOT . '/../../../vendor', // part of a larger project / installed via Composer (preferred) |
|||
EE_CLI_ROOT . '/vendor', // top-level project / installed as Git clone |
|||
); |
|||
$maybe_composer_json = EE_CLI_ROOT . '/../../../composer.json'; |
|||
if ( file_exists( $maybe_composer_json ) && is_readable( $maybe_composer_json ) ) { |
|||
$composer = json_decode( file_get_contents( $maybe_composer_json ) ); |
|||
if ( ! empty( $composer->{'vendor-dir'} ) ) { |
|||
array_unshift( $vendor_paths, EE_CLI_ROOT . '/../../../' . $composer->{'vendor-dir'} ); |
|||
} |
|||
} |
|||
return $vendor_paths; |
|||
} |
|||
|
|||
// Using require() directly inside a class grants access to private methods to the loaded code |
|||
function load_file( $path ) { |
|||
require_once $path; |
|||
} |
|||
|
|||
function load_command( $name ) { |
|||
$path = EE_CLI_ROOT . "/php/commands/$name.php"; |
|||
|
|||
if ( is_readable( $path ) ) { |
|||
include_once $path; |
|||
} |
|||
} |
|||
|
|||
function load_all_commands() { |
|||
$cmd_dir = EE_CLI_ROOT . '/php/commands'; |
|||
|
|||
$iterator = new \DirectoryIterator( $cmd_dir ); |
|||
|
|||
foreach ( $iterator as $filename ) { |
|||
if ( '.php' != substr( $filename, -4 ) ) |
|||
continue; |
|||
|
|||
include_once "$cmd_dir/$filename"; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Like array_map(), except it returns a new iterator, instead of a modified array. |
|||
* |
|||
* Example: |
|||
* |
|||
* $arr = array('Football', 'Socker'); |
|||
* |
|||
* $it = iterator_map($arr, 'strtolower', function($val) { |
|||
* return str_replace('foo', 'bar', $val); |
|||
* }); |
|||
* |
|||
* foreach ( $it as $val ) { |
|||
* var_dump($val); |
|||
* } |
|||
* |
|||
* @param array|object Either a plain array or another iterator |
|||
* @param callback The function to apply to an element |
|||
* @return object An iterator that applies the given callback(s) |
|||
*/ |
|||
function iterator_map( $it, $fn ) { |
|||
if ( is_array( $it ) ) { |
|||
$it = new \ArrayIterator( $it ); |
|||
} |
|||
|
|||
if ( !method_exists( $it, 'add_transform' ) ) { |
|||
$it = new Transform( $it ); |
|||
} |
|||
|
|||
foreach ( array_slice( func_get_args(), 1 ) as $fn ) { |
|||
$it->add_transform( $fn ); |
|||
} |
|||
|
|||
return $it; |
|||
} |
|||
|
|||
/** |
|||
* Search for file by walking up the directory tree until the first file is found or until $stop_check($dir) returns true |
|||
* @param string|array The files (or file) to search for |
|||
* @param string|null The directory to start searching from; defaults to CWD |
|||
* @param callable Function which is passed the current dir each time a directory level is traversed |
|||
* @return null|string Null if the file was not found |
|||
*/ |
|||
function find_file_upward( $files, $dir = null, $stop_check = null ) { |
|||
$files = (array) $files; |
|||
if ( is_null( $dir ) ) { |
|||
$dir = getcwd(); |
|||
} |
|||
while ( is_readable( $dir ) ) { |
|||
// Stop walking up when the supplied callable returns true being passed the $dir |
|||
if ( is_callable( $stop_check ) && call_user_func( $stop_check, $dir ) ) { |
|||
return null; |
|||
} |
|||
|
|||
foreach ( $files as $file ) { |
|||
$path = $dir . DIRECTORY_SEPARATOR . $file; |
|||
if ( file_exists( $path ) ) { |
|||
return $path; |
|||
} |
|||
} |
|||
|
|||
$parent_dir = dirname( $dir ); |
|||
if ( empty($parent_dir) || $parent_dir === $dir ) { |
|||
break; |
|||
} |
|||
$dir = $parent_dir; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
function is_path_absolute( $path ) { |
|||
// Windows |
|||
if ( isset($path[1]) && ':' === $path[1] ) |
|||
return true; |
|||
|
|||
return $path[0] === '/'; |
|||
} |
|||
|
|||
/** |
|||
* Composes positional arguments into a command string. |
|||
* |
|||
* @param array |
|||
* @return string |
|||
*/ |
|||
function args_to_str( $args ) { |
|||
return ' ' . implode( ' ', array_map( 'escapeshellarg', $args ) ); |
|||
} |
|||
|
|||
/** |
|||
* Composes associative arguments into a command string. |
|||
* |
|||
* @param array |
|||
* @return string |
|||
*/ |
|||
function assoc_args_to_str( $assoc_args ) { |
|||
$str = ''; |
|||
|
|||
foreach ( $assoc_args as $key => $value ) { |
|||
if ( true === $value ) |
|||
$str .= " --$key"; |
|||
else |
|||
$str .= " --$key=" . escapeshellarg( $value ); |
|||
} |
|||
|
|||
return $str; |
|||
} |
|||
|
|||
/** |
|||
* Given a template string and an arbitrary number of arguments, |
|||
* returns the final command, with the parameters escaped. |
|||
*/ |
|||
function esc_cmd( $cmd ) { |
|||
if ( func_num_args() < 2 ) |
|||
trigger_error( 'esc_cmd() requires at least two arguments.', E_USER_WARNING ); |
|||
|
|||
$args = func_get_args(); |
|||
|
|||
$cmd = array_shift( $args ); |
|||
|
|||
return vsprintf( $cmd, array_map( 'escapeshellarg', $args ) ); |
|||
} |
|||
|
|||
function locate_wp_config() { |
|||
static $path; |
|||
|
|||
if ( null === $path ) { |
|||
if ( file_exists( ABSPATH . 'wp-config.php' ) ) |
|||
$path = ABSPATH . 'wp-config.php'; |
|||
elseif ( file_exists( ABSPATH . '../wp-config.php' ) && ! file_exists( ABSPATH . '/../wp-settings.php' ) ) |
|||
$path = ABSPATH . '../wp-config.php'; |
|||
else |
|||
$path = false; |
|||
|
|||
if ( $path ) |
|||
$path = realpath( $path ); |
|||
} |
|||
|
|||
return $path; |
|||
} |
|||
|
|||
/** |
|||
* Output items in a table, JSON, CSV, ids, or the total count |
|||
* |
|||
* @param string $format Format to use: 'table', 'json', 'csv', 'ids', 'count' |
|||
* @param array $items Data to output |
|||
* @param array|string $fields Named fields for each item of data. Can be array or comma-separated list |
|||
*/ |
|||
function format_items( $format, $items, $fields ) { |
|||
$assoc_args = compact( 'format', 'fields' ); |
|||
$formatter = new \EE_CLI\Formatter( $assoc_args ); |
|||
$formatter->display_items( $items ); |
|||
} |
|||
|
|||
/** |
|||
* Write data as CSV to a given file. |
|||
* |
|||
* @param resource $fd File descriptor |
|||
* @param array $rows Array of rows to output |
|||
* @param array $headers List of CSV columns (optional) |
|||
*/ |
|||
function write_csv( $fd, $rows, $headers = array() ) { |
|||
if ( ! empty( $headers ) ) { |
|||
fputcsv( $fd, $headers ); |
|||
} |
|||
|
|||
foreach ( $rows as $row ) { |
|||
if ( ! empty( $headers ) ) { |
|||
$row = pick_fields( $row, $headers ); |
|||
} |
|||
|
|||
fputcsv( $fd, array_values( $row ) ); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Pick fields from an associative array or object. |
|||
* |
|||
* @param array|object Associative array or object to pick fields from |
|||
* @param array List of fields to pick |
|||
* @return array |
|||
*/ |
|||
function pick_fields( $item, $fields ) { |
|||
$item = (object) $item; |
|||
|
|||
$values = array(); |
|||
|
|||
foreach ( $fields as $field ) { |
|||
$values[ $field ] = isset( $item->$field ) ? $item->$field : null; |
|||
} |
|||
|
|||
return $values; |
|||
} |
|||
|
|||
/** |
|||
* Launch system's $EDITOR to edit text |
|||
* |
|||
* @param str $content Text to edit (eg post content) |
|||
* @return str|bool Edited text, if file is saved from editor |
|||
* False, if no change to file |
|||
*/ |
|||
function launch_editor_for_input( $input, $title = 'ee-cli' ) { |
|||
|
|||
$tmpfile = wp_tempnam( $title ); |
|||
|
|||
if ( !$tmpfile ) |
|||
\EE_CLI::error( 'Error creating temporary file.' ); |
|||
|
|||
$output = ''; |
|||
file_put_contents( $tmpfile, $input ); |
|||
|
|||
$editor = getenv( 'EDITOR' ); |
|||
if ( !$editor ) { |
|||
if ( isset( $_SERVER['OS'] ) && false !== strpos( $_SERVER['OS'], 'indows' ) ) |
|||
$editor = 'notepad'; |
|||
else |
|||
$editor = 'vi'; |
|||
} |
|||
|
|||
$descriptorspec = array( STDIN, STDOUT, STDERR ); |
|||
$process = proc_open( "$editor " . escapeshellarg( $tmpfile ), $descriptorspec, $pipes ); |
|||
$r = proc_close( $process ); |
|||
if ( $r ) { |
|||
exit( $r ); |
|||
} |
|||
|
|||
$output = file_get_contents( $tmpfile ); |
|||
|
|||
unlink( $tmpfile ); |
|||
|
|||
if ( $output === $input ) |
|||
return false; |
|||
|
|||
return $output; |
|||
} |
|||
|
|||
/** |
|||
* @param string MySQL host string, as defined in wp-config.php |
|||
* @return array |
|||
*/ |
|||
function mysql_host_to_cli_args( $raw_host ) { |
|||
$assoc_args = array(); |
|||
|
|||
$host_parts = explode( ':', $raw_host ); |
|||
if ( count( $host_parts ) == 2 ) { |
|||
list( $assoc_args['host'], $extra ) = $host_parts; |
|||
$extra = trim( $extra ); |
|||
if ( is_numeric( $extra ) ) { |
|||
$assoc_args['port'] = intval( $extra ); |
|||
$assoc_args['protocol'] = 'tcp'; |
|||
} else if ( $extra !== '' ) { |
|||
$assoc_args['socket'] = $extra; |
|||
} |
|||
} else { |
|||
$assoc_args['host'] = $raw_host; |
|||
} |
|||
|
|||
return $assoc_args; |
|||
} |
|||
|
|||
function run_mysql_command( $cmd, $assoc_args, $descriptors = null ) { |
|||
if ( !$descriptors ) |
|||
$descriptors = array( STDIN, STDOUT, STDERR ); |
|||
|
|||
if ( isset( $assoc_args['host'] ) ) { |
|||
$assoc_args = array_merge( $assoc_args, mysql_host_to_cli_args( $assoc_args['host'] ) ); |
|||
} |
|||
|
|||
$pass = $assoc_args['pass']; |
|||
unset( $assoc_args['pass'] ); |
|||
|
|||
$old_pass = getenv( 'MYSQL_PWD' ); |
|||
putenv( 'MYSQL_PWD=' . $pass ); |
|||
|
|||
$final_cmd = $cmd . assoc_args_to_str( $assoc_args ); |
|||
|
|||
$proc = proc_open( $final_cmd, $descriptors, $pipes ); |
|||
if ( !$proc ) |
|||
exit(1); |
|||
|
|||
$r = proc_close( $proc ); |
|||
|
|||
putenv( 'MYSQL_PWD=' . $old_pass ); |
|||
|
|||
if ( $r ) exit( $r ); |
|||
} |
|||
|
|||
/** |
|||
* Render PHP or other types of files using Mustache templates. |
|||
* |
|||
* IMPORTANT: Automatic HTML escaping is disabled! |
|||
*/ |
|||
function mustache_render( $template_name, $data ) { |
|||
if ( ! file_exists( $template_name ) ) |
|||
$template_name = EE_CLI_ROOT . "/templates/$template_name"; |
|||
|
|||
$template = file_get_contents( $template_name ); |
|||
|
|||
$m = new \Mustache_Engine( array( |
|||
'escape' => function ( $val ) { return $val; } |
|||
) ); |
|||
|
|||
return $m->render( $template, $data ); |
|||
} |
|||
|
|||
function make_progress_bar( $message, $count ) { |
|||
if ( \cli\Shell::isPiped() ) |
|||
return new \EE_CLI\NoOp; |
|||
|
|||
return new \cli\progress\Bar( $message, $count ); |
|||
} |
|||
|
|||
function parse_url( $url ) { |
|||
$url_parts = \parse_url( $url ); |
|||
|
|||
if ( !isset( $url_parts['scheme'] ) ) { |
|||
$url_parts = parse_url( 'http://' . $url ); |
|||
} |
|||
|
|||
return $url_parts; |
|||
} |
|||
|
|||
/** |
|||
* Check if we're running in a Windows environment (cmd.exe). |
|||
*/ |
|||
function is_windows() { |
|||
return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; |
|||
} |
|||
|
|||
/** |
|||
* Replace magic constants in some PHP source code. |
|||
* |
|||
* @param string $source The PHP code to manipulate. |
|||
* @param string $path The path to use instead of the magic constants |
|||
*/ |
|||
function replace_path_consts( $source, $path ) { |
|||
$replacements = array( |
|||
'__FILE__' => "'$path'", |
|||
'__DIR__' => "'" . dirname( $path ) . "'" |
|||
); |
|||
|
|||
$old = array_keys( $replacements ); |
|||
$new = array_values( $replacements ); |
|||
|
|||
return str_replace( $old, $new, $source ); |
|||
} |
|||
|
|||
/** |
|||
* Make a HTTP request to a remote URL |
|||
* |
|||
* @param string $method |
|||
* @param string $url |
|||
* @param array $headers |
|||
* @param array $options |
|||
* @return object |
|||
*/ |
|||
function http_request( $method, $url, $data = null, $headers = array(), $options = array() ) { |
|||
|
|||
$cert_path = '/rmccue/requests/library/Requests/Transport/cacert.pem'; |
|||
if ( inside_phar() ) { |
|||
// cURL can't read Phar archives |
|||
$options['verify'] = extract_from_phar( |
|||
EE_CLI_ROOT . '/vendor' . $cert_path ); |
|||
} else { |
|||
foreach( get_vendor_paths() as $vendor_path ) { |
|||
if ( file_exists( $vendor_path . $cert_path ) ) { |
|||
$options['verify'] = $vendor_path . $cert_path; |
|||
break; |
|||
} |
|||
} |
|||
if ( empty( $options['verify'] ) ){ |
|||
EE_CLI::error_log( "Cannot find SSL certificate." ); |
|||
} |
|||
} |
|||
|
|||
try { |
|||
$request = \Requests::request( $url, $headers, $data, $method, $options ); |
|||
return $request; |
|||
} catch( \Requests_Exception $ex ) { |
|||
// Handle SSL certificate issues gracefully |
|||
\EE_CLI::warning( $ex->getMessage() ); |
|||
$options['verify'] = false; |
|||
try { |
|||
return \Requests::request( $url, $headers, $data, $method, $options ); |
|||
} catch( \Requests_Exception $ex ) { |
|||
\EE_CLI::error( $ex->getMessage() ); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Increments a version string using the "x.y.z-pre" format |
|||
* |
|||
* Can increment the major, minor or patch number by one |
|||
* If $new_version == "same" the version string is not changed |
|||
* If $new_version is not a known keyword, it will be used as the new version string directly |
|||
* |
|||
* @param string $current_version |
|||
* @param string $new_version |
|||
* @return string |
|||
*/ |
|||
function increment_version( $current_version, $new_version ) { |
|||
// split version assuming the format is x.y.z-pre |
|||
$current_version = explode( '-', $current_version, 2 ); |
|||
$current_version[0] = explode( '.', $current_version[0] ); |
|||
|
|||
switch ( $new_version ) { |
|||
case 'same': |
|||
// do nothing |
|||
break; |
|||
|
|||
case 'patch': |
|||
$current_version[0][2]++; |
|||
|
|||
$current_version = array( $current_version[0] ); // drop possible pre-release info |
|||
break; |
|||
|
|||
case 'minor': |
|||
$current_version[0][1]++; |
|||
$current_version[0][2] = 0; |
|||
|
|||
$current_version = array( $current_version[0] ); // drop possible pre-release info |
|||
break; |
|||
|
|||
case 'major': |
|||
$current_version[0][0]++; |
|||
$current_version[0][1] = 0; |
|||
$current_version[0][2] = 0; |
|||
|
|||
$current_version = array( $current_version[0] ); // drop possible pre-release info |
|||
break; |
|||
|
|||
default: // not a keyword |
|||
$current_version = array( array( $new_version ) ); |
|||
break; |
|||
} |
|||
|
|||
// reconstruct version string |
|||
$current_version[0] = implode( '.', $current_version[0] ); |
|||
$current_version = implode( '-', $current_version ); |
|||
|
|||
return $current_version; |
|||
} |
|||
|
|||
/** |
|||
* Compare two version strings to get the named semantic version |
|||
* |
|||
* @param string $new_version |
|||
* @param string $original_version |
|||
* @return string $name 'major', 'minor', 'patch' |
|||
*/ |
|||
function get_named_sem_ver( $new_version, $original_version ) { |
|||
|
|||
if ( ! Comparator::greaterThan( $new_version, $original_version ) ) { |
|||
return ''; |
|||
} |
|||
|
|||
$parts = explode( '-', $original_version ); |
|||
list( $major, $minor, $patch ) = explode( '.', $parts[0] ); |
|||
|
|||
if ( Semver::satisfies( $new_version, "{$major}.{$minor}.x" ) ) { |
|||
return 'patch'; |
|||
} else if ( Semver::satisfies( $new_version, "{$major}.x.x" ) ) { |
|||
return 'minor'; |
|||
} else { |
|||
return 'major'; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Return the flag value or, if it's not set, the $default value. |
|||
* |
|||
* @param array $args Arguments array. |
|||
* @param string $flag Flag to get the value. |
|||
* @param mixed $default Default value for the flag. Default: NULL |
|||
* @return mixed |
|||
*/ |
|||
function get_flag_value( $args, $flag, $default = null ) { |
|||
return isset( $args[ $flag ] ) ? $args[ $flag ] : $default; |
|||
} |
@ -0,0 +1,14 @@ |
|||
{{^root_command}} |
|||
|
|||
|
|||
{{/root_command}} |
|||
## GLOBAL PARAMETERS |
|||
|
|||
{{#parameters}} |
|||
{{synopsis}} |
|||
{{desc}} |
|||
|
|||
{{/parameters}} |
|||
{{#root_command}} |
|||
Run 'wp help <command>' to get more information on a specific command. |
|||
{{/root_command}} |
Loading…
Reference in new issue