PageRenderTime 43ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/php/WP_CLI/Runner.php

https://gitlab.com/Blueprint-Marketing/wp-cli
PHP | 708 lines | 464 code | 123 blank | 121 comment | 106 complexity | 227c174ae3bf0217444d0758a62d7538 MD5 | raw file
  1. <?php
  2. namespace WP_CLI;
  3. use WP_CLI;
  4. use WP_CLI\Utils;
  5. use WP_CLI\Dispatcher;
  6. /**
  7. * Performs the execution of a command.
  8. *
  9. * @package WP_CLI
  10. */
  11. class Runner {
  12. private $global_config_path, $project_config_path;
  13. private $config, $extra_config;
  14. private $arguments, $assoc_args;
  15. private $_early_invoke = array();
  16. public function __get( $key ) {
  17. if ( '_' === $key[0] )
  18. return null;
  19. return $this->$key;
  20. }
  21. /**
  22. * Register a command for early invocation, generally before WordPress loads.
  23. *
  24. * @param string $when Named execution hook
  25. * @param WP_CLI\Dispatcher\Subcommand $command
  26. */
  27. public function register_early_invoke( $when, $command ) {
  28. $this->_early_invoke[ $when ][] = array_slice( Dispatcher\get_path( $command ), 1 );
  29. }
  30. /**
  31. * Perform the early invocation of a command.
  32. *
  33. * @param string $when Named execution hook
  34. */
  35. private function do_early_invoke( $when ) {
  36. if ( !isset( $this->_early_invoke[ $when ] ) )
  37. return;
  38. foreach ( $this->_early_invoke[ $when ] as $path ) {
  39. if ( $this->cmd_starts_with( $path ) ) {
  40. $this->_run_command();
  41. exit;
  42. }
  43. }
  44. }
  45. /**
  46. * Get the path to the global configuration YAML file.
  47. *
  48. * @return string|false
  49. */
  50. private static function get_global_config_path() {
  51. $config_path = getenv( 'WP_CLI_CONFIG_PATH' );
  52. if ( isset( $runtime_config['config'] ) ) {
  53. $config_path = $runtime_config['config'];
  54. }
  55. if ( !$config_path ) {
  56. $config_path = getenv( 'HOME' ) . '/.wp-cli/config.yml';
  57. }
  58. if ( !is_readable( $config_path ) )
  59. return false;
  60. return $config_path;
  61. }
  62. /**
  63. * Get the path to the project-specific configuration
  64. * YAML file.
  65. * wp-cli.local.yml takes priority over wp-cli.yml.
  66. *
  67. * @return string|false
  68. */
  69. private static function get_project_config_path() {
  70. $config_files = array(
  71. 'wp-cli.local.yml',
  72. 'wp-cli.yml'
  73. );
  74. // Stop looking upward when we find we have emerged from a subdirectory
  75. // install into a parent install
  76. return Utils\find_file_upward( $config_files, getcwd(), function ( $dir ) {
  77. static $wp_load_count = 0;
  78. $wp_load_path = $dir . DIRECTORY_SEPARATOR . 'wp-load.php';
  79. if ( file_exists( $wp_load_path ) ) {
  80. $wp_load_count += 1;
  81. }
  82. return $wp_load_count > 1;
  83. } );
  84. }
  85. /**
  86. * Attempts to find the path to the WP install inside index.php
  87. *
  88. * @param string $index_path
  89. * @return string|false
  90. */
  91. private static function extract_subdir_path( $index_path ) {
  92. $index_code = file_get_contents( $index_path );
  93. if ( !preg_match( '|^\s*require\s*\(?\s*(.+?)/wp-blog-header\.php([\'"])|m', $index_code, $matches ) ) {
  94. return false;
  95. }
  96. $wp_path_src = $matches[1] . $matches[2];
  97. $wp_path_src = Utils\replace_path_consts( $wp_path_src, $index_path );
  98. $wp_path = eval( "return $wp_path_src;" );
  99. if ( !Utils\is_path_absolute( $wp_path ) ) {
  100. $wp_path = dirname( $index_path ) . "/$wp_path";
  101. }
  102. return $wp_path;
  103. }
  104. /**
  105. * Find the directory that contains the WordPress files.
  106. * Defaults to the current working dir.
  107. *
  108. * @return string An absolute path
  109. */
  110. private function find_wp_root() {
  111. if ( !empty( $this->config['path'] ) ) {
  112. $path = $this->config['path'];
  113. if ( !Utils\is_path_absolute( $path ) )
  114. $path = getcwd() . '/' . $path;
  115. return $path;
  116. }
  117. if ( $this->cmd_starts_with( array( 'core', 'download' ) ) ) {
  118. return getcwd();
  119. }
  120. $dir = getcwd();
  121. while ( is_readable( $dir ) ) {
  122. if ( file_exists( "$dir/wp-load.php" ) ) {
  123. return $dir;
  124. }
  125. if ( file_exists( "$dir/index.php" ) ) {
  126. if ( $path = self::extract_subdir_path( "$dir/index.php" ) )
  127. return $path;
  128. }
  129. $parent_dir = dirname( $dir );
  130. if ( empty($parent_dir) || $parent_dir === $dir ) {
  131. break;
  132. }
  133. $dir = $parent_dir;
  134. }
  135. }
  136. /**
  137. * Set WordPress root as a given path.
  138. *
  139. * @param string $path
  140. */
  141. private static function set_wp_root( $path ) {
  142. define( 'ABSPATH', rtrim( $path, '/' ) . '/' );
  143. $_SERVER['DOCUMENT_ROOT'] = realpath( $path );
  144. }
  145. /**
  146. * Set a specific user context for WordPress.
  147. *
  148. * @param array $assoc_args
  149. */
  150. private static function set_user( $assoc_args ) {
  151. if ( isset( $assoc_args['user'] ) ) {
  152. $fetcher = new \WP_CLI\Fetchers\User;
  153. $user = $fetcher->get_check( $assoc_args['user'] );
  154. wp_set_current_user( $user->ID );
  155. } else {
  156. kses_remove_filters();
  157. }
  158. }
  159. /**
  160. * Guess which URL context WP-CLI has been invoked under.
  161. *
  162. * @param array $assoc_args
  163. * @return string|false
  164. */
  165. private static function guess_url( $assoc_args ) {
  166. if ( isset( $assoc_args['blog'] ) ) {
  167. $assoc_args['url'] = $assoc_args['blog'];
  168. }
  169. if ( isset( $assoc_args['url'] ) ) {
  170. $url = $assoc_args['url'];
  171. if ( true === $url ) {
  172. WP_CLI::warning( 'The --url parameter expects a value.' );
  173. }
  174. } elseif ( $wp_config_path = Utils\locate_wp_config() ) {
  175. // Try to find the blog parameter in the wp-config file
  176. $wp_config_file = file_get_contents( $wp_config_path );
  177. $hit = array();
  178. $re_define = "#.*define\s*\(\s*(['|\"]{1})(.+)(['|\"]{1})\s*,\s*(['|\"]{1})(.+)(['|\"]{1})\s*\)\s*;#iU";
  179. if ( preg_match_all( $re_define, $wp_config_file, $matches ) ) {
  180. foreach ( $matches[2] as $def_key => $def_name ) {
  181. if ( 'DOMAIN_CURRENT_SITE' == $def_name )
  182. $hit['domain'] = $matches[5][$def_key];
  183. if ( 'PATH_CURRENT_SITE' == $def_name )
  184. $hit['path'] = $matches[5][$def_key];
  185. }
  186. }
  187. if ( !empty( $hit ) && isset( $hit['domain'] ) ) {
  188. $url = $hit['domain'];
  189. if ( isset( $hit['path'] ) )
  190. $url .= $hit['path'];
  191. }
  192. }
  193. if ( isset( $url ) ) {
  194. return $url;
  195. }
  196. return false;
  197. }
  198. private function cmd_starts_with( $prefix ) {
  199. return $prefix == array_slice( $this->arguments, 0, count( $prefix ) );
  200. }
  201. /**
  202. * Given positional arguments, find the command to execute.
  203. *
  204. * @param array $args
  205. * @return array|string Command, args, and path on success; error message on failure
  206. */
  207. public function find_command_to_run( $args ) {
  208. $command = \WP_CLI::get_root_command();
  209. $cmd_path = array();
  210. while ( !empty( $args ) && $command->can_have_subcommands() ) {
  211. $cmd_path[] = $args[0];
  212. $full_name = implode( ' ', $cmd_path );
  213. $subcommand = $command->find_subcommand( $args );
  214. if ( !$subcommand ) {
  215. return sprintf(
  216. "'%s' is not a registered wp command. See 'wp help'.",
  217. $full_name
  218. );
  219. }
  220. if ( $this->is_command_disabled( $subcommand ) ) {
  221. return sprintf(
  222. "The '%s' command has been disabled from the config file.",
  223. $full_name
  224. );
  225. }
  226. $command = $subcommand;
  227. }
  228. return array( $command, $args, $cmd_path );
  229. }
  230. /**
  231. * Find the WP-CLI command to run given arguments,
  232. * and invoke it.
  233. *
  234. * @param array $args Positional arguments including command name
  235. * @param array $assoc_args
  236. */
  237. public function run_command( $args, $assoc_args = array() ) {
  238. $r = $this->find_command_to_run( $args );
  239. if ( is_string( $r ) ) {
  240. WP_CLI::error( $r );
  241. }
  242. list( $command, $final_args, $cmd_path ) = $r;
  243. $name = implode( ' ', $cmd_path );
  244. if ( isset( $this->extra_config[ $name ] ) ) {
  245. $extra_args = $this->extra_config[ $name ];
  246. } else {
  247. $extra_args = array();
  248. }
  249. try {
  250. $command->invoke( $final_args, $assoc_args, $extra_args );
  251. } catch ( WP_CLI\Iterators\Exception $e ) {
  252. WP_CLI::error( $e->getMessage() );
  253. }
  254. }
  255. private function _run_command() {
  256. $this->run_command( $this->arguments, $this->assoc_args );
  257. }
  258. /**
  259. * Check whether a given command is disabled by the config
  260. *
  261. * @return bool
  262. */
  263. public function is_command_disabled( $command ) {
  264. $path = implode( ' ', array_slice( \WP_CLI\Dispatcher\get_path( $command ), 1 ) );
  265. return in_array( $path, $this->config['disabled_commands'] );
  266. }
  267. /**
  268. * Returns wp-config.php code, skipping the loading of wp-settings.php
  269. *
  270. * @return string
  271. */
  272. public function get_wp_config_code() {
  273. $wp_config_path = Utils\locate_wp_config();
  274. $wp_config_code = explode( "\n", file_get_contents( $wp_config_path ) );
  275. $found_wp_settings = false;
  276. $lines_to_run = array();
  277. foreach ( $wp_config_code as $line ) {
  278. if ( preg_match( '/^\s*require.+wp-settings\.php/', $line ) ) {
  279. $found_wp_settings = true;
  280. continue;
  281. }
  282. $lines_to_run[] = $line;
  283. }
  284. if ( !$found_wp_settings ) {
  285. WP_CLI::error( 'Strange wp-config.php file: wp-settings.php is not loaded directly.' );
  286. }
  287. $source = implode( "\n", $lines_to_run );
  288. $source = Utils\replace_path_consts( $source, $wp_config_path );
  289. return preg_replace( '|^\s*\<\?php\s*|', '', $source );
  290. }
  291. /**
  292. * Transparently convert deprecated syntaxes
  293. *
  294. * @param array $args
  295. * @param array $assoc_args
  296. * @return array
  297. */
  298. private static function back_compat_conversions( $args, $assoc_args ) {
  299. $top_level_aliases = array(
  300. 'sql' => 'db',
  301. 'blog' => 'site'
  302. );
  303. if ( count( $args ) > 0 ) {
  304. foreach ( $top_level_aliases as $old => $new ) {
  305. if ( $old == $args[0] ) {
  306. $args[0] = $new;
  307. break;
  308. }
  309. }
  310. }
  311. // *-meta -> * meta
  312. if ( !empty( $args ) && preg_match( '/(post|comment|user|network)-meta/', $args[0], $matches ) ) {
  313. array_shift( $args );
  314. array_unshift( $args, 'meta' );
  315. array_unshift( $args, $matches[1] );
  316. }
  317. // core (multsite-)install --admin_name= -> --admin_user=
  318. if ( count( $args ) > 0 && 'core' == $args[0] && isset( $assoc_args['admin_name'] ) ) {
  319. $assoc_args['admin_user'] = $assoc_args['admin_name'];
  320. unset( $assoc_args['admin_name'] );
  321. }
  322. // site --site_id= -> site --network_id=
  323. if ( count( $args ) > 0 && 'site' == $args[0] && isset( $assoc_args['site_id'] ) ) {
  324. $assoc_args['network_id'] = $assoc_args['site_id'];
  325. unset( $assoc_args['site_id'] );
  326. }
  327. // {plugin|theme} update-all -> {plugin|theme} update --all
  328. if ( count( $args ) > 1 && in_array( $args[0], array( 'plugin', 'theme' ) )
  329. && $args[1] == 'update-all'
  330. ) {
  331. $args[1] = 'update';
  332. $assoc_args['all'] = true;
  333. }
  334. // plugin scaffold -> scaffold plugin
  335. if ( array( 'plugin', 'scaffold' ) == array_slice( $args, 0, 2 ) ) {
  336. list( $args[0], $args[1] ) = array( $args[1], $args[0] );
  337. }
  338. // foo --help -> help foo
  339. if ( isset( $assoc_args['help'] ) ) {
  340. array_unshift( $args, 'help' );
  341. unset( $assoc_args['help'] );
  342. }
  343. // {post|user} list --ids -> {post|user} list --format=ids
  344. if ( count( $args ) > 1 && in_array( $args[0], array( 'post', 'user' ) )
  345. && $args[1] == 'list'
  346. && isset( $assoc_args['ids'] )
  347. ) {
  348. $assoc_args['format'] = 'ids';
  349. unset( $assoc_args['ids'] );
  350. }
  351. // --json -> --format=json
  352. if ( isset( $assoc_args['json'] ) ) {
  353. $assoc_args['format'] = 'json';
  354. unset( $assoc_args['json'] );
  355. }
  356. // --{version|info} -> cli {version|info}
  357. if ( empty( $args ) ) {
  358. $special_flags = array( 'version', 'info' );
  359. foreach ( $special_flags as $key ) {
  360. if ( isset( $assoc_args[ $key ] ) ) {
  361. $args = array( 'cli', $key );
  362. unset( $assoc_args[ $key ] );
  363. break;
  364. }
  365. }
  366. }
  367. return array( $args, $assoc_args );
  368. }
  369. /**
  370. * Whether or not the output should be rendered in color
  371. *
  372. * @return bool
  373. */
  374. public function in_color() {
  375. return $this->colorize;
  376. }
  377. private function init_colorization() {
  378. if ( 'auto' === $this->config['color'] ) {
  379. $this->colorize = ( !\cli\Shell::isPiped() && !\WP_CLI\Utils\is_windows() );
  380. } else {
  381. $this->colorize = $this->config['color'];
  382. }
  383. }
  384. private function init_logger() {
  385. if ( $this->config['quiet'] )
  386. $logger = new \WP_CLI\Loggers\Quiet;
  387. else
  388. $logger = new \WP_CLI\Loggers\Regular( $this->in_color() );
  389. WP_CLI::set_logger( $logger );
  390. }
  391. /**
  392. * Do WordPress core files exist?
  393. *
  394. * @return bool
  395. */
  396. private function wp_exists() {
  397. return is_readable( ABSPATH . 'wp-includes/version.php' );
  398. }
  399. private function check_wp_version() {
  400. if ( !$this->wp_exists() ) {
  401. WP_CLI::error(
  402. "This does not seem to be a WordPress install.\n" .
  403. "Pass --path=`path/to/wordpress` or run `wp core download`." );
  404. }
  405. include ABSPATH . 'wp-includes/version.php';
  406. $minimum_version = '3.5.2';
  407. // @codingStandardsIgnoreStart
  408. if ( version_compare( $wp_version, $minimum_version, '<' ) ) {
  409. WP_CLI::error(
  410. "WP-CLI needs WordPress $minimum_version or later to work properly. " .
  411. "The version currently installed is $wp_version.\n" .
  412. "Try running `wp core download --force`."
  413. );
  414. }
  415. // @codingStandardsIgnoreEnd
  416. }
  417. private function init_config() {
  418. $configurator = \WP_CLI::get_configurator();
  419. // File config
  420. {
  421. $this->global_config_path = self::get_global_config_path();
  422. $this->project_config_path = self::get_project_config_path();
  423. $configurator->merge_yml( $this->global_config_path );
  424. $configurator->merge_yml( $this->project_config_path );
  425. }
  426. // Runtime config and args
  427. {
  428. list( $args, $assoc_args, $runtime_config ) = $configurator->parse_args(
  429. array_slice( $GLOBALS['argv'], 1 ) );
  430. list( $this->arguments, $this->assoc_args ) = self::back_compat_conversions(
  431. $args, $assoc_args );
  432. $configurator->merge_array( $runtime_config );
  433. }
  434. list( $this->config, $this->extra_config ) = $configurator->to_array();
  435. }
  436. private function check_root() {
  437. if ( $this->config['allow-root'] )
  438. return; # they're aware of the risks!
  439. if ( !function_exists( 'posix_geteuid') )
  440. return; # posix functions not available
  441. if ( posix_geteuid() !== 0 )
  442. return; # not root
  443. WP_CLI::error(
  444. "YIKES! It looks like you're running this as root. You probably meant to " .
  445. "run this as the user that your WordPress install exists under.\n" .
  446. "\n" .
  447. "If you REALLY mean to run this as root, we won't stop you, but just " .
  448. "bear in mind that any code on this site will then have full control of " .
  449. "your server, making it quite DANGEROUS.\n" .
  450. "\n" .
  451. "If you'd like to continue as root, please run this again, adding this " .
  452. "flag: --allow-root\n" .
  453. "\n" .
  454. "If you'd like to run it as the user that this site is under, you can " .
  455. "run the following to become the respective user:\n" .
  456. "\n" .
  457. " sudo -u USER -i -- wp <command>\n" .
  458. "\n"
  459. );
  460. }
  461. public function before_wp_load() {
  462. $this->init_config();
  463. $this->init_colorization();
  464. $this->init_logger();
  465. $this->check_root();
  466. if ( empty( $this->arguments ) )
  467. $this->arguments[] = 'help';
  468. // Protect 'cli info' from most of the runtime
  469. if ( 'cli' === $this->arguments[0] && ! empty( $this->arguments[1] ) && 'info' === $this->arguments[1] ) {
  470. $this->_run_command();
  471. exit;
  472. }
  473. // Load bundled commands early, so that they're forced to use the same
  474. // APIs as non-bundled commands.
  475. Utils\load_command( $this->arguments[0] );
  476. if ( isset( $this->config['require'] ) ) {
  477. foreach ( $this->config['require'] as $path ) {
  478. if ( ! file_exists( $path ) ) {
  479. WP_CLI::error( sprintf( "Required file '%s' doesn't exist", basename( $path ) ) );
  480. }
  481. Utils\load_file( $path );
  482. }
  483. }
  484. // Show synopsis if it's a composite command.
  485. $r = $this->find_command_to_run( $this->arguments );
  486. if ( is_array( $r ) ) {
  487. list( $command ) = $r;
  488. if ( $command->can_have_subcommands() ) {
  489. $command->show_usage();
  490. exit;
  491. }
  492. }
  493. // Handle --path parameter
  494. self::set_wp_root( $this->find_wp_root() );
  495. // First try at showing man page
  496. if ( 'help' === $this->arguments[0] && ( isset( $this->arguments[1] ) || !$this->wp_exists() ) ) {
  497. $this->_run_command();
  498. }
  499. // Handle --url parameter
  500. $url = self::guess_url( $this->config );
  501. if ( $url )
  502. \WP_CLI::set_url( $url );
  503. $this->do_early_invoke( 'before_wp_load' );
  504. $this->check_wp_version();
  505. if ( $this->cmd_starts_with( array( 'core', 'config' ) ) ) {
  506. $this->_run_command();
  507. exit;
  508. }
  509. if ( !Utils\locate_wp_config() ) {
  510. WP_CLI::error(
  511. "wp-config.php not found.\n" .
  512. "Either create one manually or use `wp core config`." );
  513. }
  514. if ( $this->cmd_starts_with( array( 'db' ) ) && !$this->cmd_starts_with( array( 'db', 'tables' ) ) ) {
  515. eval( $this->get_wp_config_code() );
  516. $this->_run_command();
  517. exit;
  518. }
  519. if ( $this->cmd_starts_with( array( 'core', 'is-installed' ) ) ) {
  520. define( 'WP_INSTALLING', true );
  521. }
  522. if (
  523. count( $this->arguments ) >= 2 &&
  524. $this->arguments[0] == 'core' &&
  525. in_array( $this->arguments[1], array( 'install', 'multisite-install' ) )
  526. ) {
  527. define( 'WP_INSTALLING', true );
  528. // We really need a URL here
  529. if ( !isset( $_SERVER['HTTP_HOST'] ) ) {
  530. $url = 'http://example.com';
  531. \WP_CLI::set_url( $url );
  532. }
  533. if ( 'multisite-install' == $this->arguments[1] ) {
  534. // need to fake some globals to skip the checks in wp-includes/ms-settings.php
  535. $url_parts = Utils\parse_url( $url );
  536. self::fake_current_site_blog( $url_parts );
  537. if ( !defined( 'COOKIEHASH' ) ) {
  538. define( 'COOKIEHASH', md5( $url_parts['host'] ) );
  539. }
  540. }
  541. }
  542. if ( $this->cmd_starts_with( array( 'import') ) ) {
  543. define( 'WP_LOAD_IMPORTERS', true );
  544. define( 'WP_IMPORTING', true );
  545. }
  546. if ( $this->cmd_starts_with( array( 'plugin' ) ) ) {
  547. $GLOBALS['pagenow'] = 'plugins.php';
  548. }
  549. }
  550. private static function fake_current_site_blog( $url_parts ) {
  551. global $current_site, $current_blog;
  552. if ( !isset( $url_parts['path'] ) ) {
  553. $url_parts['path'] = '/';
  554. }
  555. $current_site = (object) array(
  556. 'id' => 1,
  557. 'blog_id' => 1,
  558. 'domain' => $url_parts['host'],
  559. 'path' => $url_parts['path'],
  560. 'cookie_domain' => $url_parts['host'],
  561. 'site_name' => 'Fake Site',
  562. );
  563. $current_blog = (object) array(
  564. 'blog_id' => 1,
  565. 'site_id' => 1,
  566. 'domain' => $url_parts['host'],
  567. 'path' => $url_parts['path'],
  568. 'public' => '1',
  569. 'archived' => '0',
  570. 'mature' => '0',
  571. 'spam' => '0',
  572. 'deleted' => '0',
  573. 'lang_id' => '0',
  574. );
  575. }
  576. public function after_wp_load() {
  577. add_filter( 'filesystem_method', function() { return 'direct'; }, 99 );
  578. // Handle --user parameter
  579. if ( ! defined( 'WP_INSTALLING' ) ) {
  580. self::set_user( $this->config );
  581. }
  582. $this->_run_command();
  583. }
  584. }