Overview

Packages

  • Cli

Classes

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