1<?php 2/** 3 * Zend Framework (http://framework.zend.com/) 4 * 5 * @link http://github.com/zendframework/zf2 for the canonical source repository 6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 7 * @license http://framework.zend.com/license/new-bsd New BSD License 8 */ 9 10namespace Zend\Console\Adapter; 11 12use Zend\Console\Charset; 13use Zend\Console\Exception; 14use Zend\Stdlib\StringUtils; 15 16/** 17 * Common console adapter codebase 18 */ 19abstract class AbstractAdapter implements AdapterInterface 20{ 21 /** 22 * Whether or not mbstring is enabled 23 * 24 * @var null|bool 25 */ 26 protected static $hasMBString; 27 28 /** 29 * @var Charset\CharsetInterface 30 */ 31 protected $charset; 32 33 /** 34 * Current cursor X position 35 * 36 * @var int 37 */ 38 protected $posX; 39 40 /** 41 * Current cursor Y position 42 * 43 * @var int 44 */ 45 protected $posY; 46 47 /** 48 * Write a chunk of text to console. 49 * 50 * @param string $text 51 * @param null|int $color 52 * @param null|int $bgColor 53 */ 54 public function write($text, $color = null, $bgColor = null) 55 { 56 //Encode text to match console encoding 57 $text = $this->encodeText($text); 58 59 if ($color !== null || $bgColor !== null) { 60 echo $this->colorize($text, $color, $bgColor); 61 } else { 62 echo $text; 63 } 64 } 65 66 /** 67 * Alias for write() 68 * 69 * @param string $text 70 * @param null|int $color 71 * @param null|int $bgColor 72 */ 73 public function writeText($text, $color = null, $bgColor = null) 74 { 75 return $this->write($text, $color, $bgColor); 76 } 77 78 /** 79 * Write a single line of text to console and advance cursor to the next line. 80 * 81 * @param string $text 82 * @param null|int $color 83 * @param null|int $bgColor 84 */ 85 public function writeLine($text = "", $color = null, $bgColor = null) 86 { 87 $this->write($text . PHP_EOL, $color, $bgColor); 88 } 89 90 /** 91 * Write a piece of text at the coordinates of $x and $y 92 * 93 * 94 * @param string $text Text to write 95 * @param int $x Console X coordinate (column) 96 * @param int $y Console Y coordinate (row) 97 * @param null|int $color 98 * @param null|int $bgColor 99 */ 100 public function writeAt($text, $x, $y, $color = null, $bgColor = null) 101 { 102 $this->setPos($x, $y); 103 $this->write($text, $color, $bgColor); 104 } 105 106 /** 107 * Write a box at the specified coordinates. 108 * If X or Y coordinate value is negative, it will be calculated as the distance from far right or bottom edge 109 * of the console (respectively). 110 * 111 * @param int $x1 Top-left corner X coordinate (column) 112 * @param int $y1 Top-left corner Y coordinate (row) 113 * @param int $x2 Bottom-right corner X coordinate (column) 114 * @param int $y2 Bottom-right corner Y coordinate (row) 115 * @param int $lineStyle (optional) Box border style. 116 * @param int $fillStyle (optional) Box fill style or a single character to fill it with. 117 * @param int $color (optional) Foreground color 118 * @param int $bgColor (optional) Background color 119 * @param null|int $fillColor (optional) Foreground color of box fill 120 * @param null|int $fillBgColor (optional) Background color of box fill 121 * @throws Exception\BadMethodCallException if coordinates are invalid 122 */ 123 public function writeBox( 124 $x1, 125 $y1, 126 $x2, 127 $y2, 128 $lineStyle = self::LINE_SINGLE, 129 $fillStyle = self::FILL_NONE, 130 $color = null, 131 $bgColor = null, 132 $fillColor = null, 133 $fillBgColor = null 134 ) { 135 // Sanitize coordinates 136 $x1 = (int) $x1; 137 $y1 = (int) $y1; 138 $x2 = (int) $x2; 139 $y2 = (int) $y2; 140 141 // Translate negative coordinates 142 if ($x2 < 0) { 143 $x2 = $this->getWidth() - $x2; 144 } 145 146 if ($y2 < 0) { 147 $y2 = $this->getHeight() - $y2; 148 } 149 150 // Validate coordinates 151 if ($x1 < 0 152 || $y1 < 0 153 || $x2 < $x1 154 || $y2 < $y1 155 ) { 156 throw new Exception\BadMethodCallException('Supplied X,Y coordinates are invalid.'); 157 } 158 159 // Determine charset and dimensions 160 $charset = $this->getCharset(); 161 $width = $x2 - $x1 + 1; 162 163 if ($width <= 2) { 164 $lineStyle = static::LINE_NONE; 165 } 166 167 // Activate line drawing 168 $this->write($charset::ACTIVATE); 169 170 // Draw horizontal lines 171 if ($lineStyle !== static::LINE_NONE) { 172 switch ($lineStyle) { 173 case static::LINE_SINGLE: 174 $lineChar = $charset::LINE_SINGLE_EW; 175 break; 176 177 case static::LINE_DOUBLE: 178 $lineChar = $charset::LINE_DOUBLE_EW; 179 break; 180 181 case static::LINE_BLOCK: 182 default: 183 $lineChar = $charset::LINE_BLOCK_EW; 184 break; 185 } 186 187 $this->setPos($x1 + 1, $y1); 188 $this->write(str_repeat($lineChar, $width - 2), $color, $bgColor); 189 $this->setPos($x1 + 1, $y2); 190 $this->write(str_repeat($lineChar, $width - 2), $color, $bgColor); 191 } 192 193 // Draw vertical lines and fill 194 if (is_numeric($fillStyle) 195 && $fillStyle !== static::FILL_NONE) { 196 switch ($fillStyle) { 197 case static::FILL_SHADE_LIGHT: 198 $fillChar = $charset::SHADE_LIGHT; 199 break; 200 case static::FILL_SHADE_MEDIUM: 201 $fillChar = $charset::SHADE_MEDIUM; 202 break; 203 case static::FILL_SHADE_DARK: 204 $fillChar = $charset::SHADE_DARK; 205 break; 206 case static::FILL_BLOCK: 207 default: 208 $fillChar = $charset::BLOCK; 209 break; 210 } 211 } elseif ($fillStyle) { 212 $fillChar = StringUtils::getWrapper()->substr($fillStyle, 0, 1); 213 } else { 214 $fillChar = ' '; 215 } 216 217 if ($lineStyle === static::LINE_NONE) { 218 for ($y = $y1; $y <= $y2; $y++) { 219 $this->setPos($x1, $y); 220 $this->write(str_repeat($fillChar, $width), $fillColor, $fillBgColor); 221 } 222 } else { 223 switch ($lineStyle) { 224 case static::LINE_DOUBLE: 225 $lineChar = $charset::LINE_DOUBLE_NS; 226 break; 227 case static::LINE_BLOCK: 228 $lineChar = $charset::LINE_BLOCK_NS; 229 break; 230 case static::LINE_SINGLE: 231 default: 232 $lineChar = $charset::LINE_SINGLE_NS; 233 break; 234 } 235 236 for ($y = $y1 + 1; $y < $y2; $y++) { 237 $this->setPos($x1, $y); 238 $this->write($lineChar, $color, $bgColor); 239 $this->write(str_repeat($fillChar, $width - 2), $fillColor, $fillBgColor); 240 $this->write($lineChar, $color, $bgColor); 241 } 242 } 243 244 // Draw corners 245 if ($lineStyle !== static::LINE_NONE) { 246 if ($color !== null) { 247 $this->setColor($color); 248 } 249 if ($bgColor !== null) { 250 $this->setBgColor($bgColor); 251 } 252 if ($lineStyle === static::LINE_SINGLE) { 253 $this->writeAt($charset::LINE_SINGLE_NW, $x1, $y1); 254 $this->writeAt($charset::LINE_SINGLE_NE, $x2, $y1); 255 $this->writeAt($charset::LINE_SINGLE_SE, $x2, $y2); 256 $this->writeAt($charset::LINE_SINGLE_SW, $x1, $y2); 257 } elseif ($lineStyle === static::LINE_DOUBLE) { 258 $this->writeAt($charset::LINE_DOUBLE_NW, $x1, $y1); 259 $this->writeAt($charset::LINE_DOUBLE_NE, $x2, $y1); 260 $this->writeAt($charset::LINE_DOUBLE_SE, $x2, $y2); 261 $this->writeAt($charset::LINE_DOUBLE_SW, $x1, $y2); 262 } elseif ($lineStyle === static::LINE_BLOCK) { 263 $this->writeAt($charset::LINE_BLOCK_NW, $x1, $y1); 264 $this->writeAt($charset::LINE_BLOCK_NE, $x2, $y1); 265 $this->writeAt($charset::LINE_BLOCK_SE, $x2, $y2); 266 $this->writeAt($charset::LINE_BLOCK_SW, $x1, $y2); 267 } 268 } 269 270 // Deactivate line drawing and reset colors 271 $this->write($charset::DEACTIVATE); 272 $this->resetColor(); 273 } 274 275 /** 276 * Write a block of text at the given coordinates, matching the supplied width and height. 277 * In case a line of text does not fit desired width, it will be wrapped to the next line. 278 * In case the whole text does not fit in desired height, it will be truncated. 279 * 280 * @param string $text Text to write 281 * @param int $width Maximum block width. Negative value means distance from right edge. 282 * @param int|null $height Maximum block height. Negative value means distance from bottom edge. 283 * @param int $x Block X coordinate (column) 284 * @param int $y Block Y coordinate (row) 285 * @param null|int $color (optional) Text color 286 * @param null|int $bgColor (optional) Text background color 287 * @throws Exception\InvalidArgumentException 288 */ 289 public function writeTextBlock( 290 $text, 291 $width, 292 $height = null, 293 $x = 0, 294 $y = 0, 295 $color = null, 296 $bgColor = null 297 ) { 298 if ($x < 0 || $y < 0) { 299 throw new Exception\InvalidArgumentException('Supplied X,Y coordinates are invalid.'); 300 } 301 302 if ($width < 1) { 303 throw new Exception\InvalidArgumentException('Invalid width supplied.'); 304 } 305 306 if (null !== $height && $height < 1) { 307 throw new Exception\InvalidArgumentException('Invalid height supplied.'); 308 } 309 310 // ensure the text is not wider than the width 311 if (strlen($text) <= $width) { 312 // just write the line at the spec'd position 313 $this->setPos($x, $y); 314 $this->write($text, $color, $bgColor); 315 return; 316 } 317 318 $text = wordwrap($text, $width, PHP_EOL, true); 319 320 // convert to array of lines 321 $lines = explode(PHP_EOL, $text); 322 323 // truncate if height was specified 324 if (null !== $height && count($lines) > $height) { 325 $lines = array_slice($lines, 0, $height); 326 } 327 328 // write each line 329 $curY = $y; 330 foreach ($lines as $line) { 331 $this->setPos($x, $curY); 332 $this->write($line, $color, $bgColor); 333 $curY++;//next line 334 } 335 } 336 337 /** 338 * Determine and return current console width. 339 * 340 * @return int 341 */ 342 public function getWidth() 343 { 344 return 80; 345 } 346 347 /** 348 * Determine and return current console height. 349 * 350 * @return int 351 */ 352 public function getHeight() 353 { 354 return 25; 355 } 356 357 /** 358 * Determine and return current console width and height. 359 * 360 * @return int[] array($width, $height) 361 */ 362 public function getSize() 363 { 364 return array( 365 $this->getWidth(), 366 $this->getHeight(), 367 ); 368 } 369 370 /** 371 * Check if console is UTF-8 compatible 372 * 373 * @return bool 374 */ 375 public function isUtf8() 376 { 377 return true; 378 } 379 380 /** 381 * Set cursor position 382 * 383 * @param int $x 384 * @param int $y 385 */ 386 public function setPos($x, $y) 387 { 388 } 389 390 /** 391 * Show console cursor 392 */ 393 public function showCursor() 394 { 395 } 396 397 /** 398 * Hide console cursor 399 */ 400 public function hideCursor() 401 { 402 } 403 404 /** 405 * Return current console window title. 406 * 407 * @return string 408 */ 409 public function getTitle() 410 { 411 return ''; 412 } 413 414 /** 415 * Prepare a string that will be rendered in color. 416 * 417 * @param string $string 418 * @param int $color 419 * @param null|int $bgColor 420 * @return string 421 */ 422 public function colorize($string, $color = null, $bgColor = null) 423 { 424 return $string; 425 } 426 427 /** 428 * Change current drawing color. 429 * 430 * @param int $color 431 */ 432 public function setColor($color) 433 { 434 } 435 436 /** 437 * Change current drawing background color 438 * 439 * @param int $color 440 */ 441 public function setBgColor($color) 442 { 443 } 444 445 /** 446 * Reset color to console default. 447 */ 448 public function resetColor() 449 { 450 } 451 452 /** 453 * Set Console charset to use. 454 * 455 * @param Charset\CharsetInterface $charset 456 */ 457 public function setCharset(Charset\CharsetInterface $charset) 458 { 459 $this->charset = $charset; 460 } 461 462 /** 463 * Get charset currently in use by this adapter. 464 * 465 * @return Charset\CharsetInterface $charset 466 */ 467 public function getCharset() 468 { 469 if ($this->charset === null) { 470 $this->charset = $this->getDefaultCharset(); 471 } 472 473 return $this->charset; 474 } 475 476 /** 477 * @return Charset\Utf8 478 */ 479 public function getDefaultCharset() 480 { 481 return new Charset\Utf8; 482 } 483 484 /** 485 * Clear console screen 486 */ 487 public function clear() 488 { 489 echo "\f"; 490 } 491 492 /** 493 * Clear line at cursor position 494 */ 495 public function clearLine() 496 { 497 echo "\r" . str_repeat(" ", $this->getWidth()) . "\r"; 498 } 499 500 /** 501 * Clear console screen 502 */ 503 public function clearScreen() 504 { 505 return $this->clear(); 506 } 507 508 /** 509 * Read a single line from the console input 510 * 511 * @param int $maxLength Maximum response length 512 * @return string 513 */ 514 public function readLine($maxLength = 2048) 515 { 516 $f = fopen('php://stdin', 'r'); 517 $line = stream_get_line($f, $maxLength, PHP_EOL); 518 fclose($f); 519 return rtrim($line, "\n\r"); 520 } 521 522 /** 523 * Read a single character from the console input 524 * 525 * @param string|null $mask A list of allowed chars 526 * @return string 527 */ 528 public function readChar($mask = null) 529 { 530 $f = fopen('php://stdin', 'r'); 531 do { 532 $char = fread($f, 1); 533 } while ("" === $char || ($mask !== null && false === strstr($mask, $char))); 534 fclose($f); 535 return $char; 536 } 537 538 /** 539 * Encode a text to match console encoding 540 * 541 * @param string $text 542 * @return string the encoding text 543 */ 544 public function encodeText($text) 545 { 546 if ($this->isUtf8()) { 547 if (StringUtils::isValidUtf8($text)) { 548 return $text; 549 } 550 551 return utf8_encode($text); 552 } 553 554 if (StringUtils::isValidUtf8($text)) { 555 return utf8_decode($text); 556 } 557 558 return $text; 559 } 560} 561