PageRenderTime 25ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/Cli/lib/Horde/Cli.php

https://github.com/sgtcarneiro/horde
PHP | 499 lines | 266 code | 43 blank | 190 comment | 36 complexity | 361581e373e32fc05621c5d220bee21f MD5 | raw file
  1. <?php
  2. /**
  3. * Horde_Cli:: API for basic command-line functionality/checks.
  4. *
  5. * Copyright 2003-2011 The Horde Project (http://www.horde.org/)
  6. *
  7. * See the enclosed file COPYING for license information (LGPL). If you
  8. * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
  9. *
  10. * @author Chuck Hagenbuch <chuck@horde.org>
  11. * @author Jan Schneider <jan@horde.org>
  12. * @package Cli
  13. */
  14. class Horde_Cli
  15. {
  16. /**
  17. * Are we running on a console?
  18. *
  19. * @var boolean
  20. */
  21. protected $_console;
  22. /**
  23. * The newline string to use.
  24. *
  25. * @var string
  26. */
  27. protected $_newline;
  28. /**
  29. * The string to use for clearing the screen.
  30. *
  31. * @var string
  32. */
  33. protected $_clearscreen = '';
  34. /**
  35. * The indent string to use.
  36. *
  37. * @var string
  38. */
  39. protected $_indent;
  40. /**
  41. * The string to mark the beginning of bold text.
  42. *
  43. * @var string
  44. */
  45. protected $_bold_start = '';
  46. /**
  47. * The string to mark the end of bold text.
  48. *
  49. * @var string
  50. */
  51. protected $_bold_end = '';
  52. /**
  53. * The strings to mark the beginning of coloured text.
  54. *
  55. * @var string
  56. */
  57. protected $_red_start = '';
  58. protected $_green_start = '';
  59. protected $_yellow_start = '';
  60. protected $_blue_start = '';
  61. /**
  62. * The strings to mark the end of coloured text.
  63. *
  64. * @var string
  65. */
  66. protected $_red_end = '';
  67. protected $_green_end = '';
  68. protected $_yellow_end = '';
  69. protected $_blue_end = '';
  70. /**
  71. * Terminal foreground color codes. Not used yet.
  72. *
  73. * @var array
  74. */
  75. protected $_terminalForegrounds = array(
  76. 'normal' => "\x1B[0m",
  77. 'black' => "\x1B[0m",
  78. 'bold' => "\x1b[1m",
  79. 'red' => "\x1B[31m",
  80. 'green' => "\x1B[32m",
  81. 'brown' => "\x1B[33m",
  82. 'blue' => "\x1B[34m",
  83. 'magenta' => "\x1B[35m",
  84. 'cyan' => "\x1B[36m",
  85. 'lightgray' => "\x1B[37m",
  86. 'white' => "\x1B[1m\x1B[37m",
  87. 'darkgray' => "\x1B[1m\x1B[0m",
  88. 'lightred' => "\x1B[1m\x1B[31m",
  89. 'lightgreen' => "\x1B[1m\x1B[32m",
  90. 'yellow' => "\x1B[1m\x1B[33m",
  91. 'lightblue' => "\x1B[1m\x1B[34m",
  92. 'lightmagenta' => "\x1B[1m\x1B[35m",
  93. 'lightcyan' => "\x1B[1m\x1B[36m",
  94. );
  95. /**
  96. * Terminal background color codes. Not used yet.
  97. *
  98. * @var array
  99. */
  100. protected $_terminalBackgrounds = array(
  101. 'normal' => "\x1B[0m",
  102. 'black' => "\x1B[0m",
  103. 'red' => "\x1B[41m",
  104. 'green' => "\x1B[42m",
  105. 'brown' => "\x1B[43m",
  106. 'blue' => "\x1B[44m",
  107. 'magenta' => "\x1B[45m",
  108. 'cyan' => "\x1B[46m",
  109. 'lightgray' => "\x1B[47m",
  110. );
  111. /**
  112. * Detect the current environment (web server or console) and sets
  113. * internal values accordingly.
  114. *
  115. * The constructor must not be called after init().
  116. */
  117. public function __construct()
  118. {
  119. $this->_console = $this->runningFromCLI();
  120. if ($this->_console) {
  121. $this->_newline = "\n";
  122. $this->_indent = ' ';
  123. $term = getenv('TERM');
  124. if ($term) {
  125. if (preg_match('/^(xterm|vt220|linux)/', $term)) {
  126. $this->_clearscreen = "\x1b[2J\x1b[H";
  127. $this->_bold_start = "\x1b[1m";
  128. $this->_red_start = "\x1b[01;31m";
  129. $this->_green_start = "\x1b[01;32m";
  130. $this->_yellow_start = "\x1b[01;33m";
  131. $this->_blue_start = "\x1b[01;34m";
  132. $this->_bold_end = $this->_red_end = $this->_green_end = $this->_yellow_end = $this->_blue_end = "\x1b[0m";
  133. } elseif (preg_match('/^vt100/', $term)) {
  134. $this->_clearscreen = "\x1b[2J\x1b[H";
  135. $this->_bold_start = "\x1b[1m";
  136. $this->_bold_end = "\x1b[0m";
  137. }
  138. }
  139. } else {
  140. $this->_newline = '<br />';
  141. $this->_indent = str_repeat('&nbsp;', 4);
  142. $this->_bold_start = '<strong>';
  143. $this->_bold_end = '</strong>';
  144. $this->_red_start = '<span style="color:red">';
  145. $this->_green_start = '<span style="color:green">';
  146. $this->_yellow_start = '<span style="color:yellow">';
  147. $this->_blue_start = '<span style="color:blue">';
  148. $this->_red_end = $this->_green_end = $this->_yellow_end = $this->_blue_end = '</span>';
  149. }
  150. // We really want to call this at the end of the script, not in the
  151. // destructor.
  152. if ($this->_console) {
  153. register_shutdown_function(array($this, 'shutdown'));
  154. }
  155. }
  156. /**
  157. * Prints $text on a single line.
  158. *
  159. * @param string $text The text to print.
  160. * @param boolean $pre If true the linebreak is printed before
  161. * the text instead of after it.
  162. */
  163. public function writeln($text = '', $pre = false)
  164. {
  165. if ($pre) {
  166. echo $this->_newline . $text;
  167. } else {
  168. echo $text . $this->_newline;
  169. }
  170. }
  171. /**
  172. * Clears the entire screen, if possible.
  173. */
  174. public function clearScreen()
  175. {
  176. echo $this->_clearscreen;
  177. }
  178. /**
  179. * Returns the indented string.
  180. *
  181. * @param string $text The text to indent.
  182. *
  183. * @return string The indented text.
  184. */
  185. public function indent($text)
  186. {
  187. return $this->_indent . $text;
  188. }
  189. /**
  190. * Returns a bold version of $text.
  191. *
  192. * @param string $text The text to bold.
  193. *
  194. * @return string The bolded text.
  195. */
  196. public function bold($text)
  197. {
  198. return $this->_bold_start . $text . $this->_bold_end;
  199. }
  200. /**
  201. * Returns a red version of $text.
  202. *
  203. * @param string $text The text to print in red.
  204. *
  205. * @return string The red text.
  206. */
  207. public function red($text)
  208. {
  209. return $this->_red_start . $text . $this->_red_end;
  210. }
  211. /**
  212. * Returns a green version of $text.
  213. *
  214. * @param string $text The text to print in green.
  215. *
  216. * @return string The green text.
  217. */
  218. public function green($text)
  219. {
  220. return $this->_green_start . $text . $this->_green_end;
  221. }
  222. /**
  223. * Returns a blue version of $text.
  224. *
  225. * @param string $text The text to print in blue.
  226. *
  227. * @return string The blue text.
  228. */
  229. public function blue($text)
  230. {
  231. return $this->_blue_start . $text . $this->_blue_end;
  232. }
  233. /**
  234. * Returns a yellow version of $text.
  235. *
  236. * @param string $text The text to print in yellow.
  237. *
  238. * @return string The yellow text.
  239. */
  240. public function yellow($text)
  241. {
  242. return $this->_yellow_start . $text . $this->_yellow_end;
  243. }
  244. /**
  245. * Displays a message.
  246. *
  247. * @param string $event The message string.
  248. * @param string $type The type of message: 'cli.error', 'cli.warning',
  249. * 'cli.success', or 'cli.message'.
  250. */
  251. public function message($message, $type = 'cli.message')
  252. {
  253. $message = wordwrap(str_replace("\n", "\n ", $message),
  254. 68, "\n ", true);
  255. switch ($type) {
  256. case 'cli.error':
  257. $type_message = $this->red('[ ERROR! ] ');
  258. break;
  259. case 'cli.warning':
  260. $type_message = $this->yellow('[ WARN ] ');
  261. break;
  262. case 'cli.success':
  263. $type_message = $this->green('[ OK ] ');
  264. break;
  265. case 'cli.message':
  266. $type_message = $this->blue('[ INFO ] ');
  267. break;
  268. default:
  269. $type_message = '';
  270. }
  271. $this->writeln($type_message . $message);
  272. }
  273. /**
  274. * Displays a fatal error message.
  275. *
  276. * @param mixed $error The error text to display, an exception or an
  277. * object with a getMessage() method.
  278. */
  279. public function fatal($error)
  280. {
  281. if ($error instanceof Exception) {
  282. $trace = $error;
  283. } else {
  284. $trace = debug_backtrace();
  285. }
  286. $backtrace = new Horde_Support_Backtrace($trace);
  287. if (is_object($error) && method_exists($error, 'getMessage')) {
  288. $error = $error->getMessage();
  289. }
  290. $this->writeln();
  291. $this->writeln($this->red('===================='));
  292. $this->writeln();
  293. $this->writeln($this->red(Horde_Cli_Translation::t("Fatal Error:")));
  294. $this->writeln($this->red($error));
  295. $this->writeln();
  296. $this->writeln((string)$backtrace);
  297. $this->writeln($this->red('===================='));
  298. exit(1);
  299. }
  300. /**
  301. * Prompts for a user response.
  302. *
  303. * @todo Horde 5: switch $choices and $default
  304. *
  305. * @param string $prompt The message to display when prompting the user.
  306. * @param array $choices The choices available to the user or null for a
  307. * text input.
  308. * @param string $default The default value if no value specified.
  309. *
  310. * @return mixed The user's response to the prompt.
  311. */
  312. public function prompt($prompt, $choices = null, $default = null)
  313. {
  314. // Main event loop to capture top level command.
  315. while (true) {
  316. // Print out the prompt message.
  317. if (is_array($choices) && !empty($choices)) {
  318. $this->writeln(wordwrap($prompt) . ' ', !is_array($choices));
  319. foreach ($choices as $key => $choice) {
  320. $this->writeln($this->indent('(' . $this->bold($key) . ') ' . $choice));
  321. }
  322. $question = Horde_Cli_Translation::t("Type your choice");
  323. if ($default !== null) {
  324. $question .= ' [' . $default . ']';
  325. }
  326. $this->writeln($question . ': ', true);
  327. @ob_flush();
  328. // Get the user choice.
  329. $response = trim(fgets(STDIN));
  330. if ($response === '' && $default !== null) {
  331. $response = $default;
  332. }
  333. if (isset($choices[$response])) {
  334. return $response;
  335. } else {
  336. $this->writeln($this->red(sprintf(Horde_Cli_Translation::t("\"%s\" is not a valid choice."), $response)));
  337. }
  338. } else {
  339. if ($default !== null) {
  340. $prompt .= ' [' . $default . ']';
  341. }
  342. $this->writeln(wordwrap($prompt) . ' ', true);
  343. @ob_flush();
  344. $response = trim(fgets(STDIN));
  345. if ($response === '' && $default !== null) {
  346. $response = $default;
  347. }
  348. return $response;
  349. }
  350. }
  351. return true;
  352. }
  353. /**
  354. * Interactively prompts for input without echoing to the terminal.
  355. * Requires a bash shell or Windows and won't work with safe_mode settings
  356. * (uses shell_exec).
  357. *
  358. * From: http://www.sitepoint.com/blogs/2009/05/01/interactive-cli-password-prompt-in-php/
  359. *
  360. * @param string $prompt The message to display when prompting the user.
  361. *
  362. * @return string The user's response to the prompt.
  363. */
  364. public function passwordPrompt($prompt)
  365. {
  366. $prompt .= ' ';
  367. if (preg_match('/^win/i', PHP_OS)) {
  368. $vbscript = sys_get_temp_dir() . 'prompt_password.vbs';
  369. file_put_contents($vbscript, 'wscript.echo(InputBox("' . addslashes($prompt) . '", "", "password here"))');
  370. $command = "cscript //nologo " . escapeshellarg($vbscript);
  371. $password = rtrim(shell_exec($command));
  372. unlink($vbscript);
  373. } else {
  374. $command = '/usr/bin/env bash -c "echo OK"';
  375. if (rtrim(shell_exec($command)) !== 'OK') {
  376. /* Cannot spawn shell, fall back to standard prompt. */
  377. return $this->prompt($prompt);
  378. }
  379. $command = '/usr/bin/env bash -c "read -s -p ' . escapeshellarg($prompt) . ' mypassword && echo \$mypassword"';
  380. $password = rtrim(shell_exec($command));
  381. echo "\n";
  382. }
  383. return $password;
  384. }
  385. /**
  386. * Reads everything that is sent through standard input and returns it
  387. * as a single string.
  388. *
  389. * @return string The contents of the standard input.
  390. */
  391. public function readStdin()
  392. {
  393. $in = '';
  394. while (!feof(STDIN)) {
  395. $in .= fgets(STDIN, 1024);
  396. }
  397. return $in;
  398. }
  399. /**
  400. * CLI scripts shouldn't timeout, so try to set the time limit to
  401. * none. Also initialize a few variables in $_SERVER that aren't present
  402. * from the CLI.
  403. *
  404. * You must not call init() statically before calling the constructor.
  405. * Either use the singleton() method to retrieve a Horde_Cli object after
  406. * calling init(), or don't call init() statically.
  407. *
  408. * @return Horde_Cli A Horde_Cli instance.
  409. */
  410. static public function init()
  411. {
  412. /* Run constructor now because it requires $_SERVER['SERVER_NAME'] to
  413. * be empty if called with a CGI SAPI. */
  414. $cli = new self();
  415. @set_time_limit(0);
  416. ob_implicit_flush(true);
  417. ini_set('html_errors', false);
  418. set_exception_handler(array($cli, 'fatal'));
  419. $_SERVER['HTTP_HOST'] = '127.0.0.1';
  420. $_SERVER['SERVER_NAME'] = '127.0.0.1';
  421. $_SERVER['SERVER_PORT'] = '';
  422. $_SERVER['REMOTE_ADDR'] = '';
  423. $_SERVER['PHP_SELF'] = isset($argv) ? $argv[0] : '';
  424. if (!defined('STDIN')) {
  425. define('STDIN', fopen('php://stdin', 'r'));
  426. }
  427. if (!defined('STDOUT')) {
  428. define('STDOUT', fopen('php://stdout', 'r'));
  429. }
  430. if (!defined('STDERR')) {
  431. define('STDERR', fopen('php://stderr', 'r'));
  432. }
  433. return $cli;
  434. }
  435. /**
  436. * Make sure we're being called from the command line, and not via
  437. * the web.
  438. *
  439. * @return boolean True if we are, false otherwise.
  440. */
  441. static public function runningFromCLI()
  442. {
  443. return (PHP_SAPI == 'cli') ||
  444. (((PHP_SAPI == 'cgi') || (PHP_SAPI == 'cgi-fcgi')) &&
  445. empty($_SERVER['SERVER_NAME']));
  446. }
  447. /**
  448. * Destroys any session on script end.
  449. */
  450. public function shutdown()
  451. {
  452. if (session_id()) {
  453. session_destroy();
  454. }
  455. }
  456. }