PageRenderTime 41ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/php/utils.php

https://gitlab.com/Blueprint-Marketing/wp-cli
PHP | 506 lines | 406 code | 56 blank | 44 comment | 28 complexity | 9304999d68c8f147592173537fe18480 MD5 | raw file
  1. <?php
  2. // Utilities that do NOT depend on WordPress code.
  3. namespace WP_CLI\Utils;
  4. use \WP_CLI\Dispatcher;
  5. use \WP_CLI\Iterators\Transform;
  6. function load_dependencies() {
  7. if ( 0 === strpos( WP_CLI_ROOT, 'phar:' ) ) {
  8. require WP_CLI_ROOT . '/vendor/autoload.php';
  9. return;
  10. }
  11. $has_autoload = false;
  12. foreach ( get_vendor_paths() as $vendor_path ) {
  13. if ( file_exists( $vendor_path . '/autoload.php' ) ) {
  14. require $vendor_path . '/autoload.php';
  15. $has_autoload = true;
  16. break;
  17. }
  18. }
  19. if ( !$has_autoload ) {
  20. fputs( STDERR, "Internal error: Can't find Composer autoloader.\nTry running: composer install\n" );
  21. exit(3);
  22. }
  23. }
  24. function get_vendor_paths() {
  25. return array(
  26. WP_CLI_ROOT . '/../../../vendor', // part of a larger project / installed via Composer (preferred)
  27. WP_CLI_ROOT . '/vendor', // top-level project / installed as Git clone
  28. );
  29. }
  30. // Using require() directly inside a class grants access to private methods to the loaded code
  31. function load_file( $path ) {
  32. require_once $path;
  33. }
  34. function load_command( $name ) {
  35. $path = WP_CLI_ROOT . "/php/commands/$name.php";
  36. if ( is_readable( $path ) ) {
  37. include_once $path;
  38. }
  39. }
  40. function load_all_commands() {
  41. $cmd_dir = WP_CLI_ROOT . '/php/commands';
  42. $iterator = new \DirectoryIterator( $cmd_dir );
  43. foreach ( $iterator as $filename ) {
  44. if ( '.php' != substr( $filename, -4 ) )
  45. continue;
  46. include_once "$cmd_dir/$filename";
  47. }
  48. }
  49. /**
  50. * Like array_map(), except it returns a new iterator, instead of a modified array.
  51. *
  52. * Example:
  53. *
  54. * $arr = array('Football', 'Socker');
  55. *
  56. * $it = iterator_map($arr, 'strtolower', function($val) {
  57. * return str_replace('foo', 'bar', $val);
  58. * });
  59. *
  60. * foreach ( $it as $val ) {
  61. * var_dump($val);
  62. * }
  63. *
  64. * @param array|object Either a plain array or another iterator
  65. * @param callback The function to apply to an element
  66. * @return object An iterator that applies the given callback(s)
  67. */
  68. function iterator_map( $it, $fn ) {
  69. if ( is_array( $it ) ) {
  70. $it = new \ArrayIterator( $it );
  71. }
  72. if ( !method_exists( $it, 'add_transform' ) ) {
  73. $it = new Transform( $it );
  74. }
  75. foreach ( array_slice( func_get_args(), 1 ) as $fn ) {
  76. $it->add_transform( $fn );
  77. }
  78. return $it;
  79. }
  80. /**
  81. * Search for file by walking up the directory tree until the first file is found or until $stop_check($dir) returns true
  82. * @param string|array The files (or file) to search for
  83. * @param string|null The directory to start searching from; defaults to CWD
  84. * @param callable Function which is passed the current dir each time a directory level is traversed
  85. * @return null|string Null if the file was not found
  86. */
  87. function find_file_upward( $files, $dir = null, $stop_check = null ) {
  88. $files = (array) $files;
  89. if ( is_null( $dir ) ) {
  90. $dir = getcwd();
  91. }
  92. while ( is_readable( $dir ) ) {
  93. // Stop walking up when the supplied callable returns true being passed the $dir
  94. if ( is_callable( $stop_check ) && call_user_func( $stop_check, $dir ) ) {
  95. return null;
  96. }
  97. foreach ( $files as $file ) {
  98. $path = $dir . DIRECTORY_SEPARATOR . $file;
  99. if ( file_exists( $path ) ) {
  100. return $path;
  101. }
  102. }
  103. $parent_dir = dirname( $dir );
  104. if ( empty($parent_dir) || $parent_dir === $dir ) {
  105. break;
  106. }
  107. $dir = $parent_dir;
  108. }
  109. return null;
  110. }
  111. function is_path_absolute( $path ) {
  112. // Windows
  113. if ( isset($path[1]) && ':' === $path[1] )
  114. return true;
  115. return $path[0] === '/';
  116. }
  117. /**
  118. * Composes positional arguments into a command string.
  119. *
  120. * @param array
  121. * @return string
  122. */
  123. function args_to_str( $args ) {
  124. return ' ' . implode( ' ', array_map( 'escapeshellarg', $args ) );
  125. }
  126. /**
  127. * Composes associative arguments into a command string.
  128. *
  129. * @param array
  130. * @return string
  131. */
  132. function assoc_args_to_str( $assoc_args ) {
  133. $str = '';
  134. foreach ( $assoc_args as $key => $value ) {
  135. if ( true === $value )
  136. $str .= " --$key";
  137. else
  138. $str .= " --$key=" . escapeshellarg( $value );
  139. }
  140. return $str;
  141. }
  142. /**
  143. * Given a template string and an arbitrary number of arguments,
  144. * returns the final command, with the parameters escaped.
  145. */
  146. function esc_cmd( $cmd ) {
  147. if ( func_num_args() < 2 )
  148. trigger_error( 'esc_cmd() requires at least two arguments.', E_USER_WARNING );
  149. $args = func_get_args();
  150. $cmd = array_shift( $args );
  151. return vsprintf( $cmd, array_map( 'escapeshellarg', $args ) );
  152. }
  153. function locate_wp_config() {
  154. static $path;
  155. if ( null === $path ) {
  156. if ( file_exists( ABSPATH . 'wp-config.php' ) )
  157. $path = ABSPATH . 'wp-config.php';
  158. elseif ( file_exists( ABSPATH . '../wp-config.php' ) && ! file_exists( ABSPATH . '/../wp-settings.php' ) )
  159. $path = ABSPATH . '../wp-config.php';
  160. else
  161. $path = false;
  162. if ( $path )
  163. $path = realpath( $path );
  164. }
  165. return $path;
  166. }
  167. /**
  168. * Output items in a table, JSON, CSV, ids, or the total count
  169. *
  170. * @param string $format Format to use: 'table', 'json', 'csv', 'ids', 'count'
  171. * @param array $items Data to output
  172. * @param array|string $fields Named fields for each item of data. Can be array or comma-separated list
  173. */
  174. function format_items( $format, $items, $fields ) {
  175. $assoc_args = compact( 'format', 'fields' );
  176. $formatter = new \WP_CLI\Formatter( $assoc_args );
  177. $formatter->display_items( $items );
  178. }
  179. /**
  180. * Write data as CSV to a given file.
  181. *
  182. * @param resource $fd File descriptor
  183. * @param array $rows Array of rows to output
  184. * @param array $headers List of CSV columns (optional)
  185. */
  186. function write_csv( $fd, $rows, $headers = array() ) {
  187. if ( ! empty( $headers ) ) {
  188. fputcsv( $fd, $headers );
  189. }
  190. foreach ( $rows as $row ) {
  191. if ( ! empty( $headers ) ) {
  192. $row = pick_fields( $row, $headers );
  193. }
  194. fputcsv( $fd, array_values( $row ) );
  195. }
  196. }
  197. /**
  198. * Pick fields from an associative array or object.
  199. *
  200. * @param array|object Associative array or object to pick fields from
  201. * @param array List of fields to pick
  202. * @return array
  203. */
  204. function pick_fields( $item, $fields ) {
  205. $item = (object) $item;
  206. $values = array();
  207. foreach ( $fields as $field ) {
  208. $values[ $field ] = isset( $item->$field ) ? $item->$field : null;
  209. }
  210. return $values;
  211. }
  212. /**
  213. * Launch system's $EDITOR to edit text
  214. *
  215. * @param str $content Text to edit (eg post content)
  216. * @return str|bool Edited text, if file is saved from editor
  217. * False, if no change to file
  218. */
  219. function launch_editor_for_input( $input, $title = 'WP-CLI' ) {
  220. $tmpfile = wp_tempnam( $title );
  221. if ( !$tmpfile )
  222. \WP_CLI::error( 'Error creating temporary file.' );
  223. $output = '';
  224. file_put_contents( $tmpfile, $input );
  225. $editor = getenv( 'EDITOR' );
  226. if ( !$editor ) {
  227. if ( isset( $_SERVER['OS'] ) && false !== strpos( $_SERVER['OS'], 'indows' ) )
  228. $editor = 'notepad';
  229. else
  230. $editor = 'vi';
  231. }
  232. $descriptorspec = array( STDIN, STDOUT, STDERR );
  233. $process = proc_open( "$editor " . escapeshellarg( $tmpfile ), $descriptorspec, $pipes );
  234. $r = proc_close( $process );
  235. if ( $r ) {
  236. exit( $r );
  237. }
  238. $output = file_get_contents( $tmpfile );
  239. unlink( $tmpfile );
  240. if ( $output === $input )
  241. return false;
  242. return $output;
  243. }
  244. /**
  245. * @param string MySQL host string, as defined in wp-config.php
  246. * @return array
  247. */
  248. function mysql_host_to_cli_args( $raw_host ) {
  249. $assoc_args = array();
  250. $host_parts = explode( ':', $raw_host );
  251. if ( count( $host_parts ) == 2 ) {
  252. list( $assoc_args['host'], $extra ) = $host_parts;
  253. $extra = trim( $extra );
  254. if ( is_numeric( $extra ) ) {
  255. $assoc_args['port'] = intval( $extra );
  256. $assoc_args['protocol'] = 'tcp';
  257. } else if ( $extra !== '' ) {
  258. $assoc_args['socket'] = $extra;
  259. }
  260. } else {
  261. $assoc_args['host'] = $raw_host;
  262. }
  263. return $assoc_args;
  264. }
  265. function run_mysql_command( $cmd, $assoc_args, $descriptors = null ) {
  266. if ( !$descriptors )
  267. $descriptors = array( STDIN, STDOUT, STDERR );
  268. if ( isset( $assoc_args['host'] ) ) {
  269. $assoc_args = array_merge( $assoc_args, mysql_host_to_cli_args( $assoc_args['host'] ) );
  270. }
  271. $pass = $assoc_args['pass'];
  272. unset( $assoc_args['pass'] );
  273. $old_pass = getenv( 'MYSQL_PWD' );
  274. putenv( 'MYSQL_PWD=' . $pass );
  275. $final_cmd = $cmd . assoc_args_to_str( $assoc_args );
  276. $proc = proc_open( $final_cmd, $descriptors, $pipes );
  277. if ( !$proc )
  278. exit(1);
  279. $r = proc_close( $proc );
  280. putenv( 'MYSQL_PWD=' . $old_pass );
  281. if ( $r ) exit( $r );
  282. }
  283. /**
  284. * Render PHP or other types of files using Mustache templates.
  285. *
  286. * IMPORTANT: Automatic HTML escaping is disabled!
  287. */
  288. function mustache_render( $template_name, $data ) {
  289. if ( ! file_exists( $template_name ) )
  290. $template_name = WP_CLI_ROOT . "/templates/$template_name";
  291. $template = file_get_contents( $template_name );
  292. $m = new \Mustache_Engine( array(
  293. 'escape' => function ( $val ) { return $val; }
  294. ) );
  295. return $m->render( $template, $data );
  296. }
  297. function make_progress_bar( $message, $count ) {
  298. if ( \cli\Shell::isPiped() )
  299. return new \WP_CLI\NoOp;
  300. return new \cli\progress\Bar( $message, $count );
  301. }
  302. function parse_url( $url ) {
  303. $url_parts = \parse_url( $url );
  304. if ( !isset( $url_parts['scheme'] ) ) {
  305. $url_parts = parse_url( 'http://' . $url );
  306. }
  307. return $url_parts;
  308. }
  309. /**
  310. * Check if we're running in a Windows environment (cmd.exe).
  311. */
  312. function is_windows() {
  313. return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
  314. }
  315. /**
  316. * Replace magic constants in some PHP source code.
  317. *
  318. * @param string $source The PHP code to manipulate.
  319. * @param string $path The path to use instead of the magic constants
  320. */
  321. function replace_path_consts( $source, $path ) {
  322. $replacements = array(
  323. '__FILE__' => "'$path'",
  324. '__DIR__' => "'" . dirname( $path ) . "'"
  325. );
  326. $old = array_keys( $replacements );
  327. $new = array_values( $replacements );
  328. return str_replace( $old, $new, $source );
  329. }
  330. /**
  331. * Make a HTTP request to a remote URL
  332. *
  333. * @param string $method
  334. * @param string $url
  335. * @param array $headers
  336. * @param array $options
  337. * @return object
  338. */
  339. function http_request( $method, $url, $data = null, $headers = array(), $options = array() ) {
  340. $pem_copied = false;
  341. // cURL can't read Phar archives
  342. if ( 0 === strpos( WP_CLI_ROOT, 'phar://' ) ) {
  343. $options['verify'] = sys_get_temp_dir() . '/wp-cli-cacert.pem';
  344. copy(
  345. WP_CLI_ROOT . '/vendor/rmccue/requests/library/Requests/Transport/cacert.pem',
  346. $options['verify']
  347. );
  348. $pem_copied = true;
  349. }
  350. try {
  351. $request = \Requests::request( $url, $headers, $data, $method, $options );
  352. if ( $pem_copied ) {
  353. unlink( $options['verify'] );
  354. }
  355. return $request;
  356. } catch( \Requests_Exception $ex ) {
  357. // Handle SSL certificate issues gracefully
  358. \WP_CLI::warning( $ex->getMessage() );
  359. if ( $pem_copied ) {
  360. unlink( $options['verify'] );
  361. }
  362. $options['verify'] = false;
  363. try {
  364. return \Requests::request( $url, $headers, $data, $method, $options );
  365. } catch( \Requests_Exception $ex ) {
  366. \WP_CLI::error( $ex->getMessage() );
  367. }
  368. }
  369. }
  370. /**
  371. * Increments a version string using the "x.y.z-pre" format
  372. *
  373. * Can increment the major, minor or patch number by one
  374. * If $new_version == "same" the version string is not changed
  375. * If $new_version is not a known keyword, it will be used as the new version string directly
  376. *
  377. * @param string $current_version
  378. * @param string $new_version
  379. * @return string
  380. */
  381. function increment_version( $current_version, $new_version ) {
  382. // split version assuming the format is x.y.z-pre
  383. $current_version = explode( '-', $current_version, 2 );
  384. $current_version[0] = explode( '.', $current_version[0] );
  385. switch ( $new_version ) {
  386. case 'same':
  387. // do nothing
  388. break;
  389. case 'patch':
  390. $current_version[0][2]++;
  391. $current_version = array( $current_version[0] ); // drop possible pre-release info
  392. break;
  393. case 'minor':
  394. $current_version[0][1]++;
  395. $current_version[0][2] = 0;
  396. $current_version = array( $current_version[0] ); // drop possible pre-release info
  397. break;
  398. case 'major':
  399. $current_version[0][0]++;
  400. $current_version[0][1] = 0;
  401. $current_version[0][2] = 0;
  402. $current_version = array( $current_version[0] ); // drop possible pre-release info
  403. break;
  404. default: // not a keyword
  405. $current_version = array( array( $new_version ) );
  406. break;
  407. }
  408. // reconstruct version string
  409. $current_version[0] = implode( '.', $current_version[0] );
  410. $current_version = implode( '-', $current_version );
  411. return $current_version;
  412. }