1<?php 2/** 3 * Zend Framework 4 * 5 * LICENSE 6 * 7 * This source file is subject to the new BSD license that is bundled 8 * with this package in the file LICENSE.txt. 9 * It is also available through the world-wide-web at this URL: 10 * http://framework.zend.com/license/new-bsd 11 * If you did not receive a copy of the license and are unable to 12 * obtain it through the world-wide-web, please send an email 13 * to license@zend.com so we can send you a copy immediately. 14 * 15 * @category Zend 16 * @package Zend_Controller 17 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 18 * @license http://framework.zend.com/license/new-bsd New BSD License 19 * @version $Id$ 20 */ 21 22/** 23 * Zend_Controller_Response_Abstract 24 * 25 * Base class for Zend_Controller responses 26 * 27 * @package Zend_Controller 28 * @subpackage Response 29 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 30 * @license http://framework.zend.com/license/new-bsd New BSD License 31 */ 32abstract class Zend_Controller_Response_Abstract 33{ 34 /** 35 * Body content 36 * @var array 37 */ 38 protected $_body = array(); 39 40 /** 41 * Exception stack 42 * @var Exception 43 */ 44 protected $_exceptions = array(); 45 46 /** 47 * Array of headers. Each header is an array with keys 'name' and 'value' 48 * @var array 49 */ 50 protected $_headers = array(); 51 52 /** 53 * Array of raw headers. Each header is a single string, the entire header to emit 54 * @var array 55 */ 56 protected $_headersRaw = array(); 57 58 /** 59 * HTTP response code to use in headers 60 * @var int 61 */ 62 protected $_httpResponseCode = 200; 63 64 /** 65 * Flag; is this response a redirect? 66 * @var boolean 67 */ 68 protected $_isRedirect = false; 69 70 /** 71 * Whether or not to render exceptions; off by default 72 * @var boolean 73 */ 74 protected $_renderExceptions = false; 75 76 /** 77 * Flag; if true, when header operations are called after headers have been 78 * sent, an exception will be raised; otherwise, processing will continue 79 * as normal. Defaults to true. 80 * 81 * @see canSendHeaders() 82 * @var boolean 83 */ 84 public $headersSentThrowsException = true; 85 86 /** 87 * Normalize a header name 88 * 89 * Normalizes a header name to X-Capitalized-Names 90 * 91 * @param string $name 92 * @return string 93 */ 94 protected function _normalizeHeader($name) 95 { 96 $filtered = str_replace(array('-', '_'), ' ', (string) $name); 97 $filtered = ucwords(strtolower($filtered)); 98 $filtered = str_replace(' ', '-', $filtered); 99 return $filtered; 100 } 101 102 /** 103 * Set a header 104 * 105 * If $replace is true, replaces any headers already defined with that 106 * $name. 107 * 108 * @param string $name 109 * @param string $value 110 * @param boolean $replace 111 * @return Zend_Controller_Response_Abstract 112 */ 113 public function setHeader($name, $value, $replace = false) 114 { 115 $this->canSendHeaders(true); 116 $name = $this->_normalizeHeader($name); 117 $value = (string) $value; 118 119 if ($replace) { 120 foreach ($this->_headers as $key => $header) { 121 if ($name == $header['name']) { 122 unset($this->_headers[$key]); 123 } 124 } 125 } 126 127 $this->_headers[] = array( 128 'name' => $name, 129 'value' => $value, 130 'replace' => $replace 131 ); 132 133 return $this; 134 } 135 136 /** 137 * Set redirect URL 138 * 139 * Sets Location header and response code. Forces replacement of any prior 140 * redirects. 141 * 142 * @param string $url 143 * @param int $code 144 * @return Zend_Controller_Response_Abstract 145 */ 146 public function setRedirect($url, $code = 302) 147 { 148 $this->canSendHeaders(true); 149 $this->setHeader('Location', $url, true) 150 ->setHttpResponseCode($code); 151 152 return $this; 153 } 154 155 /** 156 * Is this a redirect? 157 * 158 * @return boolean 159 */ 160 public function isRedirect() 161 { 162 return $this->_isRedirect; 163 } 164 165 /** 166 * Return array of headers; see {@link $_headers} for format 167 * 168 * @return array 169 */ 170 public function getHeaders() 171 { 172 return $this->_headers; 173 } 174 175 /** 176 * Clear headers 177 * 178 * @return Zend_Controller_Response_Abstract 179 */ 180 public function clearHeaders() 181 { 182 $this->_headers = array(); 183 184 return $this; 185 } 186 187 /** 188 * Clears the specified HTTP header 189 * 190 * @param string $name 191 * @return Zend_Controller_Response_Abstract 192 */ 193 public function clearHeader($name) 194 { 195 if (! count($this->_headers)) { 196 return $this; 197 } 198 199 foreach ($this->_headers as $index => $header) { 200 if ($name == $header['name']) { 201 unset($this->_headers[$index]); 202 } 203 } 204 205 return $this; 206 } 207 208 /** 209 * Set raw HTTP header 210 * 211 * Allows setting non key => value headers, such as status codes 212 * 213 * @param string $value 214 * @return Zend_Controller_Response_Abstract 215 */ 216 public function setRawHeader($value) 217 { 218 $this->canSendHeaders(true); 219 if ('Location' == substr($value, 0, 8)) { 220 $this->_isRedirect = true; 221 } 222 $this->_headersRaw[] = (string) $value; 223 return $this; 224 } 225 226 /** 227 * Retrieve all {@link setRawHeader() raw HTTP headers} 228 * 229 * @return array 230 */ 231 public function getRawHeaders() 232 { 233 return $this->_headersRaw; 234 } 235 236 /** 237 * Clear all {@link setRawHeader() raw HTTP headers} 238 * 239 * @return Zend_Controller_Response_Abstract 240 */ 241 public function clearRawHeaders() 242 { 243 $this->_headersRaw = array(); 244 return $this; 245 } 246 247 /** 248 * Clears the specified raw HTTP header 249 * 250 * @param string $headerRaw 251 * @return Zend_Controller_Response_Abstract 252 */ 253 public function clearRawHeader($headerRaw) 254 { 255 if (! count($this->_headersRaw)) { 256 return $this; 257 } 258 259 $key = array_search($headerRaw, $this->_headersRaw); 260 if ($key !== false) { 261 unset($this->_headersRaw[$key]); 262 } 263 264 return $this; 265 } 266 267 /** 268 * Clear all headers, normal and raw 269 * 270 * @return Zend_Controller_Response_Abstract 271 */ 272 public function clearAllHeaders() 273 { 274 return $this->clearHeaders() 275 ->clearRawHeaders(); 276 } 277 278 /** 279 * Set HTTP response code to use with headers 280 * 281 * @param int $code 282 * @return Zend_Controller_Response_Abstract 283 */ 284 public function setHttpResponseCode($code) 285 { 286 if (!is_int($code) || (100 > $code) || (599 < $code)) { 287 throw new Zend_Controller_Response_Exception('Invalid HTTP response code'); 288 } 289 290 if ((300 <= $code) && (307 >= $code)) { 291 $this->_isRedirect = true; 292 } else { 293 $this->_isRedirect = false; 294 } 295 296 $this->_httpResponseCode = $code; 297 return $this; 298 } 299 300 /** 301 * Retrieve HTTP response code 302 * 303 * @return int 304 */ 305 public function getHttpResponseCode() 306 { 307 return $this->_httpResponseCode; 308 } 309 310 /** 311 * Can we send headers? 312 * 313 * @param boolean $throw Whether or not to throw an exception if headers have been sent; defaults to false 314 * @return boolean 315 * @throws Zend_Controller_Response_Exception 316 */ 317 public function canSendHeaders($throw = false) 318 { 319 $ok = headers_sent($file, $line); 320 if ($ok && $throw && $this->headersSentThrowsException) { 321 throw new Zend_Controller_Response_Exception('Cannot send headers; headers already sent in ' . $file . ', line ' . $line); 322 } 323 324 return !$ok; 325 } 326 327 /** 328 * Send all headers 329 * 330 * Sends any headers specified. If an {@link setHttpResponseCode() HTTP response code} 331 * has been specified, it is sent with the first header. 332 * 333 * @return Zend_Controller_Response_Abstract 334 */ 335 public function sendHeaders() 336 { 337 // Only check if we can send headers if we have headers to send 338 if (count($this->_headersRaw) || count($this->_headers) || (200 != $this->_httpResponseCode)) { 339 $this->canSendHeaders(true); 340 } elseif (200 == $this->_httpResponseCode) { 341 // Haven't changed the response code, and we have no headers 342 return $this; 343 } 344 345 $httpCodeSent = false; 346 347 foreach ($this->_headersRaw as $header) { 348 if (!$httpCodeSent && $this->_httpResponseCode) { 349 header($header, true, $this->_httpResponseCode); 350 $httpCodeSent = true; 351 } else { 352 header($header); 353 } 354 } 355 356 foreach ($this->_headers as $header) { 357 if (!$httpCodeSent && $this->_httpResponseCode) { 358 header($header['name'] . ': ' . $header['value'], $header['replace'], $this->_httpResponseCode); 359 $httpCodeSent = true; 360 } else { 361 header($header['name'] . ': ' . $header['value'], $header['replace']); 362 } 363 } 364 365 if (!$httpCodeSent) { 366 header('HTTP/1.1 ' . $this->_httpResponseCode); 367 $httpCodeSent = true; 368 } 369 370 return $this; 371 } 372 373 /** 374 * Set body content 375 * 376 * If $name is not passed, or is not a string, resets the entire body and 377 * sets the 'default' key to $content. 378 * 379 * If $name is a string, sets the named segment in the body array to 380 * $content. 381 * 382 * @param string $content 383 * @param null|string $name 384 * @return Zend_Controller_Response_Abstract 385 */ 386 public function setBody($content, $name = null) 387 { 388 if ((null === $name) || !is_string($name)) { 389 $this->_body = array('default' => (string) $content); 390 } else { 391 $this->_body[$name] = (string) $content; 392 } 393 394 return $this; 395 } 396 397 /** 398 * Append content to the body content 399 * 400 * @param string $content 401 * @param null|string $name 402 * @return Zend_Controller_Response_Abstract 403 */ 404 public function appendBody($content, $name = null) 405 { 406 if ((null === $name) || !is_string($name)) { 407 if (isset($this->_body['default'])) { 408 $this->_body['default'] .= (string) $content; 409 } else { 410 return $this->append('default', $content); 411 } 412 } elseif (isset($this->_body[$name])) { 413 $this->_body[$name] .= (string) $content; 414 } else { 415 return $this->append($name, $content); 416 } 417 418 return $this; 419 } 420 421 /** 422 * Clear body array 423 * 424 * With no arguments, clears the entire body array. Given a $name, clears 425 * just that named segment; if no segment matching $name exists, returns 426 * false to indicate an error. 427 * 428 * @param string $name Named segment to clear 429 * @return boolean 430 */ 431 public function clearBody($name = null) 432 { 433 if (null !== $name) { 434 $name = (string) $name; 435 if (isset($this->_body[$name])) { 436 unset($this->_body[$name]); 437 return true; 438 } 439 440 return false; 441 } 442 443 $this->_body = array(); 444 return true; 445 } 446 447 /** 448 * Return the body content 449 * 450 * If $spec is false, returns the concatenated values of the body content 451 * array. If $spec is boolean true, returns the body content array. If 452 * $spec is a string and matches a named segment, returns the contents of 453 * that segment; otherwise, returns null. 454 * 455 * @param boolean $spec 456 * @return string|array|null 457 */ 458 public function getBody($spec = false) 459 { 460 if (false === $spec) { 461 ob_start(); 462 $this->outputBody(); 463 return ob_get_clean(); 464 } elseif (true === $spec) { 465 return $this->_body; 466 } elseif (is_string($spec) && isset($this->_body[$spec])) { 467 return $this->_body[$spec]; 468 } 469 470 return null; 471 } 472 473 /** 474 * Append a named body segment to the body content array 475 * 476 * If segment already exists, replaces with $content and places at end of 477 * array. 478 * 479 * @param string $name 480 * @param string $content 481 * @return Zend_Controller_Response_Abstract 482 */ 483 public function append($name, $content) 484 { 485 if (!is_string($name)) { 486 throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")'); 487 } 488 489 if (isset($this->_body[$name])) { 490 unset($this->_body[$name]); 491 } 492 $this->_body[$name] = (string) $content; 493 return $this; 494 } 495 496 /** 497 * Prepend a named body segment to the body content array 498 * 499 * If segment already exists, replaces with $content and places at top of 500 * array. 501 * 502 * @param string $name 503 * @param string $content 504 * @return void 505 */ 506 public function prepend($name, $content) 507 { 508 if (!is_string($name)) { 509 throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")'); 510 } 511 512 if (isset($this->_body[$name])) { 513 unset($this->_body[$name]); 514 } 515 516 $new = array($name => (string) $content); 517 $this->_body = $new + $this->_body; 518 519 return $this; 520 } 521 522 /** 523 * Insert a named segment into the body content array 524 * 525 * @param string $name 526 * @param string $content 527 * @param string $parent 528 * @param boolean $before Whether to insert the new segment before or 529 * after the parent. Defaults to false (after) 530 * @return Zend_Controller_Response_Abstract 531 */ 532 public function insert($name, $content, $parent = null, $before = false) 533 { 534 if (!is_string($name)) { 535 throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")'); 536 } 537 538 if ((null !== $parent) && !is_string($parent)) { 539 throw new Zend_Controller_Response_Exception('Invalid body segment parent key ("' . gettype($parent) . '")'); 540 } 541 542 if (isset($this->_body[$name])) { 543 unset($this->_body[$name]); 544 } 545 546 if ((null === $parent) || !isset($this->_body[$parent])) { 547 return $this->append($name, $content); 548 } 549 550 $ins = array($name => (string) $content); 551 $keys = array_keys($this->_body); 552 $loc = array_search($parent, $keys); 553 if (!$before) { 554 // Increment location if not inserting before 555 ++$loc; 556 } 557 558 if (0 === $loc) { 559 // If location of key is 0, we're prepending 560 $this->_body = $ins + $this->_body; 561 } elseif ($loc >= (count($this->_body))) { 562 // If location of key is maximal, we're appending 563 $this->_body = $this->_body + $ins; 564 } else { 565 // Otherwise, insert at location specified 566 $pre = array_slice($this->_body, 0, $loc); 567 $post = array_slice($this->_body, $loc); 568 $this->_body = $pre + $ins + $post; 569 } 570 571 return $this; 572 } 573 574 /** 575 * Echo the body segments 576 * 577 * @return void 578 */ 579 public function outputBody() 580 { 581 $body = implode('', $this->_body); 582 echo $body; 583 } 584 585 /** 586 * Register an exception with the response 587 * 588 * @param Exception $e 589 * @return Zend_Controller_Response_Abstract 590 */ 591 public function setException(Exception $e) 592 { 593 $this->_exceptions[] = $e; 594 return $this; 595 } 596 597 /** 598 * Retrieve the exception stack 599 * 600 * @return array 601 */ 602 public function getException() 603 { 604 return $this->_exceptions; 605 } 606 607 /** 608 * Has an exception been registered with the response? 609 * 610 * @return boolean 611 */ 612 public function isException() 613 { 614 return !empty($this->_exceptions); 615 } 616 617 /** 618 * Does the response object contain an exception of a given type? 619 * 620 * @param string $type 621 * @return boolean 622 */ 623 public function hasExceptionOfType($type) 624 { 625 foreach ($this->_exceptions as $e) { 626 if ($e instanceof $type) { 627 return true; 628 } 629 } 630 631 return false; 632 } 633 634 /** 635 * Does the response object contain an exception with a given message? 636 * 637 * @param string $message 638 * @return boolean 639 */ 640 public function hasExceptionOfMessage($message) 641 { 642 foreach ($this->_exceptions as $e) { 643 if ($message == $e->getMessage()) { 644 return true; 645 } 646 } 647 648 return false; 649 } 650 651 /** 652 * Does the response object contain an exception with a given code? 653 * 654 * @param int $code 655 * @return boolean 656 */ 657 public function hasExceptionOfCode($code) 658 { 659 $code = (int) $code; 660 foreach ($this->_exceptions as $e) { 661 if ($code == $e->getCode()) { 662 return true; 663 } 664 } 665 666 return false; 667 } 668 669 /** 670 * Retrieve all exceptions of a given type 671 * 672 * @param string $type 673 * @return false|array 674 */ 675 public function getExceptionByType($type) 676 { 677 $exceptions = array(); 678 foreach ($this->_exceptions as $e) { 679 if ($e instanceof $type) { 680 $exceptions[] = $e; 681 } 682 } 683 684 if (empty($exceptions)) { 685 $exceptions = false; 686 } 687 688 return $exceptions; 689 } 690 691 /** 692 * Retrieve all exceptions of a given message 693 * 694 * @param string $message 695 * @return false|array 696 */ 697 public function getExceptionByMessage($message) 698 { 699 $exceptions = array(); 700 foreach ($this->_exceptions as $e) { 701 if ($message == $e->getMessage()) { 702 $exceptions[] = $e; 703 } 704 } 705 706 if (empty($exceptions)) { 707 $exceptions = false; 708 } 709 710 return $exceptions; 711 } 712 713 /** 714 * Retrieve all exceptions of a given code 715 * 716 * @param mixed $code 717 * @return void 718 */ 719 public function getExceptionByCode($code) 720 { 721 $code = (int) $code; 722 $exceptions = array(); 723 foreach ($this->_exceptions as $e) { 724 if ($code == $e->getCode()) { 725 $exceptions[] = $e; 726 } 727 } 728 729 if (empty($exceptions)) { 730 $exceptions = false; 731 } 732 733 return $exceptions; 734 } 735 736 /** 737 * Whether or not to render exceptions (off by default) 738 * 739 * If called with no arguments or a null argument, returns the value of the 740 * flag; otherwise, sets it and returns the current value. 741 * 742 * @param boolean $flag Optional 743 * @return boolean 744 */ 745 public function renderExceptions($flag = null) 746 { 747 if (null !== $flag) { 748 $this->_renderExceptions = $flag ? true : false; 749 } 750 751 return $this->_renderExceptions; 752 } 753 754 /** 755 * Send the response, including all headers, rendering exceptions if so 756 * requested. 757 * 758 * @return void 759 */ 760 public function sendResponse() 761 { 762 $this->sendHeaders(); 763 764 if ($this->isException() && $this->renderExceptions()) { 765 $exceptions = ''; 766 foreach ($this->getException() as $e) { 767 $exceptions .= $e->__toString() . "\n"; 768 } 769 echo $exceptions; 770 return; 771 } 772 773 $this->outputBody(); 774 } 775 776 /** 777 * Magic __toString functionality 778 * 779 * Proxies to {@link sendResponse()} and returns response value as string 780 * using output buffering. 781 * 782 * @return string 783 */ 784 public function __toString() 785 { 786 ob_start(); 787 $this->sendResponse(); 788 return ob_get_clean(); 789 } 790} 791