Browse Source

prepare delete command

feature/refactor-php
harshadyeola 9 years ago
parent
commit
e72e17d9c2
  1. 1
      VERSION
  2. 46
      bin/ee
  3. 32
      composer.json
  4. 223
      php/EE_CLI/Configurator.php
  5. 93
      php/EE_CLI/Dispatcher/CommandFactory.php
  6. 261
      php/EE_CLI/Dispatcher/CompositeCommand.php
  7. 60
      php/EE_CLI/Dispatcher/RootCommand.php
  8. 299
      php/EE_CLI/Dispatcher/Subcommand.php
  9. 129
      php/EE_CLI/DocParser.php
  10. 51
      php/EE_CLI/Loggers/Base.php
  11. 57
      php/EE_CLI/Loggers/Quiet.php
  12. 79
      php/EE_CLI/Loggers/Regular.php
  13. 511
      php/EE_CLI/Runner.php
  14. 110
      php/EE_CLI/SynopsisParser.php
  15. 163
      php/EE_CLI/SynopsisValidator.php
  16. 1035
      php/Spyc.php
  17. 17
      php/boot-fs.php
  18. 11
      php/class-ee-cli-command.php
  19. 505
      php/class-ee-cli.php
  20. 30
      php/commands/site.php
  21. 102
      php/config-spec.php
  22. 19
      php/dispatcher.php
  23. 21
      php/ee-cli.php
  24. 25
      php/export/class-wp-export-oxymel.php
  25. 372
      php/export/class-wp-export-query.php
  26. 261
      php/export/class-wp-export-wxr-formatter.php
  27. 41
      php/export/functions.export.php
  28. 80
      php/export/iterators.php
  29. 184
      php/export/writers.php
  30. 575
      php/utils.php
  31. 14
      templates/man-params.mustache

1
VERSION

@ -0,0 +1 @@
0.1-alpha

46
bin/ee

@ -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" "$@"

32
composer.json

@ -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" ]
}
}

223
php/EE_CLI/Configurator.php

@ -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;
}
}
}

93
php/EE_CLI/Dispatcher/CommandFactory.php

@ -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(), '__' );
}
}

261
php/EE_CLI/Dispatcher/CompositeCommand.php

@ -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 );
}
}

60
php/EE_CLI/Dispatcher/RootCommand.php

@ -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();
}
}

299
php/EE_CLI/Dispatcher/Subcommand.php

@ -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 ) ) );
}
}

129
php/EE_CLI/DocParser.php

@ -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 '';
}
}

51
php/EE_CLI/Loggers/Base.php

@ -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" );
}
}

57
php/EE_CLI/Loggers/Quiet.php

@ -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" ) );
}
}

79
php/EE_CLI/Loggers/Regular.php

@ -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" );
}
}

511
php/EE_CLI/Runner.php

@ -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();
}
}

110
php/EE_CLI/SynopsisParser.php

@ -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 );
}
}
}

163
php/EE_CLI/SynopsisValidator.php

@ -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;
}
}

1035
php/Spyc.php

File diff suppressed because it is too large

17
php/boot-fs.php

@ -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';

11
php/class-ee-cli-command.php

@ -0,0 +1,11 @@
<?php
/**
* Base class for WP-CLI commands
*
* @package wp-cli
*/
abstract class EE_CLI_Command {
public function __construct() {}
}

505
php/class-ee-cli.php

@ -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 );
}
}

30
php/commands/site.php

@ -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' );

102
php/config-spec.php

@ -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,
),
);

19
php/dispatcher.php

@ -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;
}

21
php/ee-cli.php

@ -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();

25
php/export/class-wp-export-oxymel.php

@ -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 );
}
}

372
php/export/class-wp-export-query.php

@ -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 {
}

261
php/export/class-wp-export-wxr-formatter.php

@ -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;
}
}

41
php/export/functions.export.php

@ -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 );
}

80
php/export/iterators.php

@ -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 {
}

184
php/export/writers.php

@ -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();
}
}

575
php/utils.php

@ -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;
}

14
templates/man-params.mustache

@ -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…
Cancel
Save