PageRenderTime 42ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/php/utils.php

https://gitlab.com/Blueprint-Marketing/cli
PHP | 665 lines | 367 code | 74 blank | 224 comment | 61 complexity | cee81902b383cc759a898e92daaeb365 MD5 | raw file
  1. <?php
  2. namespace Terminus\Utils;
  3. use \Terminus\Dispatcher;
  4. use \Terminus\Iterators\Transform;
  5. use \ArrayIterator;
  6. if (!defined('JSON_PRETTY_PRINT')) {
  7. define('JSON_PRETTY_PRINT', 128);
  8. }
  9. /**
  10. * Composes positional arguments into a command string.
  11. *
  12. * @param [array] $args Array of arguments
  13. * @return [string] $command_string Param rendered into command string
  14. */
  15. function args_to_str($args) {
  16. $command_string = ' ' . implode(' ', array_map('escapeshellarg', $args));
  17. return $command_string;
  18. }
  19. /**
  20. * Composes associative arguments into a command string
  21. *
  22. * @param [array] $assoc_args Arguments for command line in array form
  23. * @return [string] $return Command string form of param
  24. */
  25. function assoc_args_to_str($assoc_args) {
  26. $return = '';
  27. foreach ($assoc_args as $key => $value) {
  28. if ($value === true) {
  29. $return .= " --$key";
  30. } else {
  31. $return .= " --$key=" . escapeshellarg($value);
  32. }
  33. }
  34. return $return;
  35. }
  36. /**
  37. * Outputs array as string, space-separated
  38. *
  39. * @param [array] $array Array to be stringified
  40. * @return [string] $output Param array imploded with spaces
  41. */
  42. function bash_out($array) {
  43. $output = '';
  44. foreach ($array as $index => $row) {
  45. if (is_array($row) OR is_object($row)) {
  46. $row = (array)$row;
  47. $row = join(' ', $row);
  48. }
  49. if (!is_numeric($index)) {
  50. $output .= "$index ";
  51. }
  52. $output .= $row . PHP_EOL;
  53. }
  54. return $output;
  55. }
  56. /**
  57. * Ensures that the given destination is valid
  58. *
  59. * @param [string] $destination Location of directory to ensure viability of
  60. * @param [boolean] $make True to create destination if it does not exist
  61. * @return [string] $destination Same as the parameter
  62. */
  63. function destination_is_valid($destination, $make = true) {
  64. if (file_exists($destination) AND !is_dir($destination)) {
  65. \Terminus::error("Destination given is a file. It must be a directory.");
  66. }
  67. if (!is_dir($destination)) {
  68. if (!$make) {
  69. $make = \Terminus::confirm("Directory does not exists. Create it now?");
  70. }
  71. if ($make) {
  72. mkdir($destination, 0755);
  73. }
  74. }
  75. return $destination;
  76. }
  77. /**
  78. * Given a template string and an arbitrary number of arguments,
  79. * returns the final command, with the parameters escaped.
  80. *
  81. * @param [string] $cmd Command to escape the parameters of
  82. * @return [string] $final_cmd Parameter-escaped command
  83. */
  84. function esc_cmd($cmd) {
  85. if (func_num_args() < 2) {
  86. trigger_error('esc_cmd() requires at least two arguments.', E_USER_WARNING);
  87. }
  88. $args = func_get_args();
  89. $cmd = array_shift($args);
  90. $final_cmd = vsprintf($cmd, array_map('escapeshellarg', $args));
  91. return $final_cmd;
  92. }
  93. /**
  94. * Search for file by walking up the directory tree until the first file is
  95. * found or until $stop_check($dir) returns true
  96. *
  97. * @param [array] $files The file(s) to search for
  98. * @param [string] $dir The directory to start searching from,
  99. * defaults to CWD
  100. * @param [function] $stop_check Passed the current dir each time a directory
  101. * level is traversed
  102. * @return [string] $path File name if found, null if the file was not found
  103. */
  104. function find_file_upward($files, $dir = null, $stop_check = null) {
  105. $files = (array)$files;
  106. if (is_null($dir)) {
  107. $dir = getcwd();
  108. }
  109. while (is_readable($dir)) {
  110. //Stop walking when the supplied callable returns true being passed the $dir
  111. if (is_callable($stop_check) && call_user_func($stop_check, $dir)) {
  112. return null;
  113. }
  114. foreach ($files as $file) {
  115. $path = $dir . DIRECTORY_SEPARATOR . $file;
  116. if (file_exists($path)) {
  117. return $path;
  118. }
  119. }
  120. $parent_dir = dirname($dir);
  121. if (empty($parent_dir) || ($parent_dir === $dir)) {
  122. break;
  123. }
  124. $dir = $parent_dir;
  125. }
  126. return null;
  127. }
  128. /**
  129. * Output items in a table, JSON, CSV, IDs, or the total count
  130. *
  131. * @param [string] $format Format to use: 'table', 'json', 'csv', 'ids', 'count'
  132. * @param [array] $items Data to output
  133. * @param [array] $fields Named fields for each datum,
  134. * array or comma-separated string
  135. * @return [void]
  136. */
  137. function format_items($format, $items, $fields) {
  138. $assoc_args = array(
  139. 'format' => $format,
  140. 'fields' => $fields
  141. );
  142. $formatter = new \Terminus\Formatter($assoc_args);
  143. $formatter->display_items($items);
  144. }
  145. /**
  146. * Get file name from a URL
  147. *
  148. * @param [string] $url A valid URL
  149. * @return [string] The file name from the given URL
  150. */
  151. function get_filename_from_url($url) {
  152. $path = parse_url($url);
  153. $parts = explode('/', $path['path']);
  154. $filename = end($parts);
  155. return $filename;
  156. }
  157. /**
  158. * Return an array of paths where vendor autoload files may be located
  159. *
  160. * @return [array] $vendor_paths
  161. */
  162. function get_vendor_paths() {
  163. $vendor_paths = array(
  164. TERMINUS_ROOT . '/../../../vendor',
  165. TERMINUS_ROOT . '/vendor'
  166. );
  167. return $vendor_paths;
  168. }
  169. /**
  170. * Processes exception message and throws it
  171. *
  172. * @param [Exception] $exception Exception object thrown
  173. * @return [void]
  174. */
  175. function handle_exception($exception) {
  176. $trace = $exception->getTrace();
  177. if (!empty($trace) AND \Terminus::get_config('verbose')) {
  178. foreach ($exception->getTrace() as $line) {
  179. $out_line = sprintf(
  180. "%s%s%s [%s:%s]",
  181. $line['class'],
  182. $line['type'],
  183. $line['function'],
  184. $line['file'],
  185. $line['line']
  186. );
  187. \Terminus\Loggers\Regular::redLine(">> $out_line");
  188. }
  189. }
  190. \Terminus::error("Exception thrown - %s", array($exception->getMessage()));
  191. }
  192. /**
  193. * Checks to see whether TERMINUS_HOST is pointed at Hermes
  194. *
  195. * @return [boolean] $is_hermes True if Terminus is operating on Hermes
  196. */
  197. function is_hermes() {
  198. $is_hermes = (TERMINUS_HOST == 'dashboard.getpantheon.com');
  199. return $is_hermes;
  200. }
  201. /**
  202. * Checks given path for whether it is absolute
  203. *
  204. * @param [string] $path Path to check
  205. * @return [boolean] $is_root True if path is absolute
  206. */
  207. function is_path_absolute($path) {
  208. $is_root = (isset($path[1]) && ($path[1] == ':') || ($path[0] == '/'));
  209. return $is_root;
  210. }
  211. /**
  212. * Checks whether email is in a valid or not
  213. *
  214. * @param [string] $email String to be evaluated for email address format
  215. * @return [boolean] $is_email True if $email is in email address format
  216. */
  217. function is_valid_email($email) {
  218. $is_email = filter_var($email, FILTER_VALIDATE_EMAIL);
  219. return (boolean)$is_email;
  220. }
  221. /**
  222. * Validates an Atlas UUID
  223. *
  224. * @param [string] $uuid String to check for being a valid Atlas UUID
  225. * @return [boolean] True if string is a valid UUID
  226. */
  227. function is_valid_uuid($uuid) {
  228. $regex = '#^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$#';
  229. $is_uuid = preg_match($regex, $uuid);
  230. return (boolean)$is_uuid;
  231. }
  232. /**
  233. * Check whether Terminus is running in a Windows environment
  234. *
  235. * @return [boolean] $is_windows True if OS running Terminus is Windows
  236. */
  237. function is_windows() {
  238. $is_windows = (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN');
  239. return $is_windows;
  240. }
  241. /**
  242. * Like array_map() except it returns a new iterator instead of a modified array
  243. *
  244. * Example:
  245. *
  246. * $arr = array('Football', 'Socker');
  247. *
  248. * $it = iterator_map($arr, 'strtolower', function($val) {
  249. * return str_replace('foo', 'bar', $val);
  250. * });
  251. *
  252. * foreach ( $it as $val ) {
  253. * var_dump($val);
  254. * }
  255. *
  256. * @param [array] $iterator Either a plain array or another iterator
  257. * @param [function] $function The function to apply to an element
  258. * @return [object] $iterator An iterator that applies the given callback(s)
  259. */
  260. function iterator_map($iterator, $function) {
  261. if (is_array($iterator)) {
  262. $iterator = new \ArrayIterator($iterator);
  263. }
  264. if (!method_exists($iterator, 'add_transform')) {
  265. $iterator = new Transform($iterator);
  266. }
  267. foreach (array_slice(func_get_args(), 1) as $function) {
  268. $iterator->add_transform($function);
  269. }
  270. return $iterator;
  271. }
  272. /**
  273. * Returns the var string in JSON format
  274. *
  275. * @param [string] $var String to be turned into JSON
  276. * @return [string] $jsonified JSONified param
  277. */
  278. function json_dump($var) {
  279. if (\cli\Shell::isPiped()) { //If it's a piped command, don't prettify JSON.
  280. $jsonified = json_encode($var);
  281. } else { //If it's not, make it legible to humans.
  282. $jsonified = json_encode($var, JSON_PRETTY_PRINT) . "\n";
  283. }
  284. return $jsonified;
  285. }
  286. /**
  287. * Includes every command file in the commands directory
  288. *
  289. * @return [void]
  290. */
  291. function load_all_commands() {
  292. $cmd_dir = TERMINUS_ROOT . '/php/commands';
  293. $iterator = new \DirectoryIterator($cmd_dir);
  294. foreach ($iterator as $filename) {
  295. if (substr($filename, -4) != '.php') {
  296. continue;
  297. }
  298. include_once "$cmd_dir/$filename";
  299. }
  300. }
  301. /**
  302. * Includes a single command file
  303. *
  304. * @param [string] $name Of command of which the class file will be included
  305. * @return [void]
  306. */
  307. function load_command($name) {
  308. $path = TERMINUS_ROOT . "/php/commands/$name.php";
  309. if (is_readable($path)) {
  310. include_once $path;
  311. }
  312. }
  313. /**
  314. * Requires inclusion of Composer's autoload file
  315. *
  316. * @return [void]
  317. */
  318. function load_dependencies() {
  319. if (strpos(TERMINUS_ROOT, 'phar:') === 0) {
  320. require TERMINUS_ROOT . '/vendor/autoload.php';
  321. return;
  322. }
  323. $has_autoload = false;
  324. foreach (get_vendor_paths() as $vendor_path) {
  325. if (file_exists($vendor_path . '/autoload.php') ) {
  326. require $vendor_path . '/autoload.php';
  327. $has_autoload = true;
  328. break;
  329. }
  330. }
  331. if (!$has_autoload) {
  332. fputs(STDERR, "Internal error: Can't find Composer autoloader.\n");
  333. exit(3);
  334. }
  335. }
  336. /**
  337. * Using require() directly inside a class grants access to private methods
  338. * to the loaded code
  339. *
  340. * @param [string] $path Path to the file to be required
  341. * @return [void]
  342. */
  343. function load_file($path) {
  344. require $path;
  345. }
  346. /**
  347. * Launch system's $EDITOR to edit text
  348. *
  349. * @param [string] $input Text to be put into the temp file for changing
  350. * @param [string] $title Name for the temporary file
  351. * @return [string] $output Output string if input has changed, false otherwise
  352. */
  353. function launch_editor_for_input($input, $title = 'Terminus') {
  354. $tmpfile = wp_tempnam($title);
  355. if (!$tmpfile) {
  356. \Terminus::error('Error creating temporary file.');
  357. }
  358. $output = '';
  359. file_put_contents($tmpfile, $input);
  360. $editor = getenv('EDITOR');
  361. if (!$editor) {
  362. if (isset($_SERVER['OS']) && (strpos($_SERVER['OS'], 'indows') !== false)) {
  363. $editor = 'notepad';
  364. } else {
  365. $editor = 'vi';
  366. }
  367. }
  368. \Terminus::launch("$editor " . escapeshellarg($tmpfile));
  369. $output = file_get_contents($tmpfile);
  370. unlink($tmpfile);
  371. if ($output == $input) {
  372. return false;
  373. }
  374. return $output;
  375. }
  376. /**
  377. * Creates progress bar for operation in progress
  378. *
  379. * @param [string] $message Message to be displayed with the progress bar
  380. * @param [integer] $count Number of progress dots to be displayed
  381. * @return [\cli\progress\Bar] $progress_bar Object which handles display of bar
  382. */
  383. function make_progress_bar($message, $count) {
  384. if (\cli\Shell::isPiped()) {
  385. return new \Terminus\NoOp;
  386. }
  387. $progress_bar = new \cli\progress\Bar($message, $count);
  388. return $progress_bar;
  389. }
  390. /**
  391. * Render PHP or other types of files using Mustache templates
  392. * IMPORTANT: Automatic HTML escaping is disabled!
  393. *
  394. * @param [string] $template_name File name of the template to be used
  395. * @param [array] $data Context to pass through for template use
  396. * @return [string] $rendered_template The rendered template
  397. */
  398. function mustache_render($template_name, $data) {
  399. if (!file_exists($template_name)) {
  400. $template_name = TERMINUS_ROOT . "/templates/$template_name";
  401. }
  402. $template = file_get_contents($template_name);
  403. $mustache = new \Mustache_Engine(
  404. array(
  405. 'escape' => function ($val) {
  406. return $val;
  407. }
  408. )
  409. );
  410. $rendered_template = $mustache->render($template, $data);
  411. return $rendered_template;
  412. }
  413. /**
  414. * Takes a host string such as from wp-config.php and parses it into an array
  415. *
  416. * @param [string] $raw_host MySQL host string, as defined in wp-config.php
  417. * @return [array] $assoc_args Connection inforrmation for MySQL
  418. */
  419. function mysql_host_to_cli_args($raw_host) {
  420. $assoc_args = array();
  421. $host_parts = explode(':', $raw_host);
  422. if (count($host_parts) == 2) {
  423. list($assoc_args['host'], $extra) = $host_parts;
  424. $extra = trim($extra);
  425. if (is_numeric($extra)) {
  426. $assoc_args['port'] = intval($extra);
  427. $assoc_args['protocol'] = 'tcp';
  428. } elseif ($extra !== '') {
  429. $assoc_args['socket'] = $extra;
  430. }
  431. } else {
  432. $assoc_args['host'] = $raw_host;
  433. }
  434. return $assoc_args;
  435. }
  436. /**
  437. * Parses a URL and returns its components
  438. * Overrides native PHP parse_url(string)
  439. *
  440. * @param [string] $url URL to parse
  441. * @return [array] $url_parts An array of URL components
  442. */
  443. function parse_url($url) {
  444. $url_parts = \parse_url($url);
  445. if (!isset($url_parts['scheme'])) {
  446. $url_parts = parse_url('http://' . $url);
  447. }
  448. return $url_parts;
  449. }
  450. /**
  451. * Pick fields from an associative array or object.
  452. *
  453. * @param [array] $item Associative array or object to pick fields from
  454. * @param [array] $fields List of fields to pick
  455. * @return [array] $values
  456. */
  457. function pick_fields($item, $fields) {
  458. $item = (object)$item;
  459. $values = array();
  460. foreach ($fields as $field) {
  461. $values[$field] = isset($item->$field) ? $item->$field : null;
  462. }
  463. return $values;
  464. }
  465. /**
  466. * Replaces directory structure constants in PHP source code
  467. *
  468. * @param [string] $source The PHP code to manipulate
  469. * @param [string] $path The path to use instead of path constants
  470. * @return [string] $altered_source Source with constants replaced
  471. */
  472. function replace_path_consts($source, $path) {
  473. $replacements = array(
  474. '__FILE__' => "'$path'",
  475. '__DIR__' => "'" . dirname($path) . "'"
  476. );
  477. $old = array_keys($replacements);
  478. $new = array_values($replacements);
  479. $altered_source = str_replace($old, $new, $source);
  480. return $altered_source;
  481. }
  482. /**
  483. * Fetches keys from the first object in a collection
  484. *
  485. * @param [array] $result Data for cURLing
  486. * @return [array] $keys Array keys of first object in param $result
  487. */
  488. function result_get_response_fields($result) {
  489. $iter = new ArrayIterator($result);
  490. if (!$iter) {
  491. return false;
  492. }
  493. $keys = array_keys((array)$iter->current());
  494. $keys = array_map('ucfirst', $keys);
  495. unset($iter);
  496. return $keys;
  497. }
  498. /**
  499. * Checks whether param is an array of multiple objects or of one
  500. *
  501. * @param [array] $array Array to evaluate
  502. * @return [boolean] True if first element in array is an object or an array
  503. */
  504. function result_is_multiobj($array) {
  505. $iter = new ArrayIterator($array);
  506. if (is_object($iter->current()) || is_array($iter->current())) {
  507. return true;
  508. }
  509. unset($iter);
  510. return false;
  511. }
  512. /**
  513. * Runs the given MySQL statement
  514. *
  515. * @param [string] $cmd MySQL statement to be executed
  516. * @param [array] $assoc_args Conditions to be formatted to use with statement
  517. * @param [array] $descriptors Any of: opened file, open socket, input method
  518. * @return [void]
  519. */
  520. function run_mysql_command($cmd, $assoc_args, $descriptors = null ) {
  521. if (!$descriptors) {
  522. $descriptors = array(STDIN, STDOUT, STDERR);
  523. }
  524. if (isset($assoc_args['host'])) {
  525. $assoc_args = array_merge(
  526. $assoc_args,
  527. mysql_host_to_cli_args($assoc_args['host'])
  528. );
  529. }
  530. $env = (array)$_ENV;
  531. if (isset($assoc_args['pass'])) {
  532. $env['MYSQL_PWD'] = $assoc_args['pass'];
  533. unset($assoc_args['pass']);
  534. }
  535. $final_cmd = $cmd . assoc_args_to_str($assoc_args);
  536. $proc = proc_open($final_cmd, $descriptors, $pipes, null, $env);
  537. if (!$proc) {
  538. exit(1);
  539. }
  540. $status = proc_close($proc);
  541. if ($status) {
  542. exit($status);
  543. }
  544. }
  545. /**
  546. * Sanitize the site name field
  547. *
  548. * @param [string] $string String to be sanitized
  549. * @return [string] $name Param string, sanitized
  550. */
  551. function sanitize_name($string) {
  552. $name = $string;
  553. // squash whitespace
  554. $name = trim(preg_replace('#\s+#', ' ', $name));
  555. // replace spacers with hyphens
  556. $name = preg_replace("#[\._ ]#", "-", $name);
  557. // crush everything else
  558. $name = strtolower(preg_replace("#[^A-Za-z0-9-]#", "", $name));
  559. return $name;
  560. }
  561. /**
  562. * Removes ".gz" from a filename
  563. *
  564. * @param [string] $filename Name of file from which to remove ".gz"
  565. * @return [string] $file Param string, ".gz" removed
  566. */
  567. function sql_from_zip($filename) {
  568. $file = preg_replace('#\.gz$#s', '', $filename);
  569. return $file;
  570. }
  571. /**
  572. * Write data as CSV to a given file
  573. *
  574. * @param [resource] $file_descriptor File descriptor
  575. * @param [array] $rows Array of rows to output
  576. * @param [array] $headers List of CSV columns
  577. * @return [void]
  578. */
  579. function write_csv($file_descriptor, $rows, $headers = array()) {
  580. if (!empty($headers)) {
  581. fputcsv($file_descriptor, $headers);
  582. }
  583. foreach ($rows as $row) {
  584. if (!empty($headers)) {
  585. $row = pick_fields($row, $headers);
  586. }
  587. fputcsv($file_descriptor, array_values($row));
  588. }
  589. }