1: <?php
2: 3: 4: 5: 6: 7:
8:
9: namespace Wei;
10:
11: 12: 13: 14: 15: 16: 17:
18: class Response extends Base
19: {
20: 21: 22: 23: 24:
25: protected $statusTexts = array(
26:
27: 200 => 'OK',
28: 201 => 'Created',
29: 202 => 'Accepted',
30: 203 => 'Non-Authoritative Information',
31: 204 => 'No Content',
32: 205 => 'Reset Content',
33: 206 => 'Partial Content',
34:
35: 300 => 'Multiple Choices',
36: 301 => 'Moved Permanently',
37: 302 => 'Found',
38: 303 => 'See Other',
39: 304 => 'Not Modified',
40: 305 => 'Use Proxy',
41: 307 => 'Temporary Redirect',
42:
43: 401 => 'Unauthorized',
44: 403 => 'Forbidden',
45: 404 => 'Not Found',
46: 405 => 'Method Not Allowed',
47: 406 => 'Not Acceptable',
48: 407 => 'Proxy Authentication Required',
49: 408 => 'Request Timeout',
50: 409 => 'Conflict',
51: 410 => 'Gone',
52: 411 => 'Length Required',
53: 412 => 'Precondition Failed',
54: 413 => 'Request Entity Too Large',
55: 414 => 'Request-URI Too Long',
56: 415 => 'Unsupported Media Type',
57: 416 => 'Requested Range Not Satisfiable',
58: 417 => 'Expectation Failed',
59:
60: 500 => 'Internal Server Error',
61: 501 => 'Not Implemented',
62: 502 => 'Bad Gateway',
63: 503 => 'Service Unavailable',
64: 504 => 'Gateway Timeout',
65: 505 => 'HTTP Version Not Supported'
66: );
67:
68: 69: 70: 71: 72:
73: protected $version = '1.1';
74:
75: 76: 77: 78: 79:
80: protected $statusCode = 200;
81:
82: 83: 84: 85: 86:
87: protected $statusText = 'OK';
88:
89: 90: 91: 92: 93:
94: protected $content;
95:
96: 97: 98: 99: 100:
101: protected $headers = array();
102:
103: 104: 105: 106: 107:
108: protected $sentHeaders = array();
109:
110: 111: 112: 113: 114:
115: protected $cookies = array();
116:
117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131:
132: protected $cookieOption = array(
133: 'expires' => 864000,
134: 'path' => '/',
135: 'domain' => null,
136: 'secure' => false,
137: 'httpOnly' => false,
138: 'raw' => false,
139: );
140:
141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155:
156: protected $downloadOption = array(
157: 'type' => 'application/x-download',
158: 'disposition' => 'attachment',
159: 'filename' => null,
160: );
161:
162: 163: 164: 165: 166:
167: protected $unitTest = false;
168:
169: 170: 171: 172: 173:
174: protected $redirectView;
175:
176: 177: 178: 179: 180:
181: protected $redirectWait = 0;
182:
183: 184: 185: 186: 187:
188: protected $beforeSend;
189:
190: 191: 192: 193: 194:
195: protected $afterSend;
196:
197: 198: 199: 200: 201: 202: 203:
204: public function __invoke($content = null, $status = null)
205: {
206: return $this->send($content, $status);
207: }
208:
209: 210: 211: 212: 213: 214: 215:
216: public function send($content = null, $status = null)
217: {
218:
219: if (is_array($content)) {
220: return $this->json($content)->send();
221: } elseif (null !== $content) {
222: $this->setContent($content);
223: }
224:
225: if (null !== $status) {
226: $this->setStatusCode($status);
227: }
228:
229:
230: $this->beforeSend && call_user_func($this->beforeSend, $this, $content);
231:
232: $this->sendHeader();
233: $this->sendContent();
234:
235:
236: $this->afterSend && call_user_func($this->afterSend, $this);
237:
238: return $this;
239: }
240:
241: 242: 243: 244: 245: 246:
247: public function setContent($content)
248: {
249: $this->content = $content;
250: return $this;
251: }
252:
253: 254: 255: 256: 257:
258: public function getContent()
259: {
260: return $this->content;
261: }
262:
263: 264: 265: 266: 267:
268: public function sendContent()
269: {
270: echo $this->content;
271: return $this;
272: }
273:
274: 275: 276: 277: 278: 279: 280:
281: public function setStatusCode($code, $text = null)
282: {
283: $this->statusCode = (int)$code;
284:
285: if ($text) {
286: $this->statusText = $text;
287: } elseif (isset($this->statusTexts[$code])) {
288: $this->statusText = $this->statusTexts[$code];
289: }
290:
291: return $this;
292: }
293:
294: 295: 296: 297: 298:
299: public function getStatusCode()
300: {
301: return $this->statusCode;
302: }
303:
304: 305: 306: 307: 308: 309:
310: public function setVersion($version)
311: {
312: $this->version = $version;
313: return $this;
314: }
315:
316: 317: 318: 319: 320:
321: public function getVersion()
322: {
323: return $this->version;
324: }
325:
326: 327: 328: 329: 330: 331: 332: 333:
334: public function setHeader($name, $values = null, $replace = true)
335: {
336: if (is_array($name)) {
337: foreach ($name as $key => $value) {
338: $this->setHeader($key, $value);
339: }
340: return $this;
341: }
342:
343: $values = (array)$values;
344: if (true === $replace || !isset($this->headers[$name])) {
345: $this->headers[$name] = $values;
346: } else {
347: $this->headers[$name] = array_merge($this->headers[$name], $values);
348: }
349:
350: return $this;
351: }
352:
353: 354: 355: 356: 357: 358: 359: 360:
361: public function getHeader($name, $default = null, $first = true)
362: {
363: if (!isset($this->headers[$name])) {
364: return $default;
365: }
366:
367: if (is_array($this->headers[$name]) && $first) {
368: return current($this->headers[$name]);
369: }
370:
371: return $this->headers[$name];
372: }
373:
374: 375: 376: 377: 378: 379:
380: public function removeHeader($name)
381: {
382: header_remove($name);
383: unset($this->headers[$name]);
384: return $this;
385: }
386:
387: 388: 389: 390: 391:
392: public function sendHeader()
393: {
394: $file = $line = null;
395: if ($this->isHeaderSent($file, $line)) {
396: if ($this->wei->has('logger')) {
397: $this->logger->debug(sprintf('Header has been at %s:%s', $file, $line));
398: }
399: return false;
400: }
401:
402:
403: $this->sendRawHeader(sprintf('HTTP/%s %d %s', $this->version, $this->statusCode, $this->statusText));
404:
405:
406: foreach ($this->headers as $name => $values) {
407: foreach ($values as $value) {
408: $this->sendRawHeader($name . ': ' . $value);
409: }
410: }
411:
412: $this->sendCookie();
413:
414: return true;
415: }
416:
417: 418: 419: 420: 421: 422: 423: 424:
425: protected function sendRawHeader($header)
426: {
427: $this->unitTest ? ($this->sentHeaders[] = $header) : header($header, false);
428: }
429:
430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441:
442: public function isHeaderSent(&$file = null, &$line = null)
443: {
444: return $this->unitTest ? (bool)$this->sentHeaders : headers_sent($file, $line);
445: }
446:
447: 448: 449: 450: 451: 452: 453:
454: public function getCookie($key, $default = null)
455: {
456: return isset($this->cookies[$key]) ? $this->cookies[$key]['value'] : $default;
457: }
458:
459: 460: 461: 462: 463: 464: 465: 466:
467: public function setCookie($key, $value, array $options = array())
468: {
469: $this->cookies[$key] = array('value' => $value) + $options;
470: return $this;
471: }
472:
473: 474: 475: 476: 477: 478:
479: public function removeCookie($key)
480: {
481: return $this->setCookie($key, '', array('expires' => -1));
482: }
483:
484: 485: 486: 487: 488:
489: public function sendCookie()
490: {
491: $time = time();
492:
493: $setCookie = function () {
494: };
495: foreach ($this->cookies as $name => $o) {
496: $o += $this->cookieOption;
497: $fn = $this->unitTest ? $setCookie : ($o['raw'] ? 'setrawcookie' : 'setcookie');
498: $fn($name, $o['value'], $time + $o['expires'], $o['path'], $o['domain'], $o['secure'], $o['httpOnly']);
499: }
500: return $this;
501: }
502:
503: 504: 505: 506: 507:
508: public function __toString()
509: {
510: return sprintf('HTTP/%s %d %s', $this->version, $this->statusCode, $this->statusText) . "\r\n"
511: . $this->getHeaderString() . "\r\n"
512: . $this->content;
513: }
514:
515: 516: 517: 518: 519:
520: public function getHeaderString()
521: {
522: $string = '';
523: foreach ($this->headers as $name => $values) {
524: foreach ($values as $value) {
525: $string .= $name . ': ' . $value . "\r\n";
526: }
527: }
528: return $string;
529: }
530:
531: 532: 533: 534: 535: 536: 537:
538: public function setRedirectView($redirectView)
539: {
540: if (!is_file($redirectView)) {
541: throw new \RuntimeException(sprintf('Redirect view file "%s" not found', $redirectView));
542: }
543: $this->redirectView = $redirectView;
544: return $this;
545: }
546:
547: 548: 549: 550: 551: 552: 553: 554:
555: public function redirect($url = null, $statusCode = 302, $options = array())
556: {
557: $this->setStatusCode($statusCode);
558: $this->setOption($options);
559:
560:
561: $escapedUrl = htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
562: $wait = (int)$this->redirectWait;
563:
564:
565: if (0 === $wait) {
566: $this->setHeader('Location', $url);
567: }
568:
569:
570: if ($this->redirectView) {
571: ob_start();
572: require $this->redirectView;
573: $content = ob_get_clean();
574: } else {
575: $content = sprintf('<!DOCTYPE html>
576: <html>
577: <head>
578: <meta charset="utf-8">
579: <meta http-equiv="refresh" content="%d;url=%2$s">
580: <title>Redirecting to %s</title>
581: </head>
582: <body>
583: <h1>Redirecting to <a href="%2$s">%2$s</a></h1>
584: </body>
585: </html>', $wait, $escapedUrl);
586: }
587:
588: return $this->setContent($content);
589: }
590:
591: 592: 593: 594: 595: 596: 597:
598: public function json($data, $jsonp = false)
599: {
600: $options = 0;
601: defined('JSON_UNESCAPED_UNICODE') && $options = JSON_UNESCAPED_UNICODE;
602: $content = json_encode($data, $options);
603:
604: if ($jsonp && preg_match('/^[$A-Z_][0-9A-Z_$.]*$/i', $this->request['callback']) === 1) {
605: $this->setHeader('Content-Type', 'application/javascript');
606: $content = $this->request['callback'] . '(' . $content . ')';
607: } else {
608: $this->setHeader('Content-Type', 'application/json');
609: }
610:
611: return $this->setContent($content);
612: }
613:
614: 615: 616: 617: 618: 619:
620: public function jsonp($data)
621: {
622: return $this->json($data, true);
623: }
624:
625: 626: 627: 628: 629: 630:
631: public function flush($content = null)
632: {
633: if (function_exists('apache_setenv')) {
634: apache_setenv('no-gzip', '1');
635: }
636:
637: 638: 639: 640:
641: if (!headers_sent() && extension_loaded('zlib')) {
642: ini_set('zlib.output_compression', '0');
643: }
644:
645: 646: 647: 648:
649: ob_implicit_flush();
650:
651: $this->send($content);
652:
653: 654: 655: 656:
657: if ($length = ini_get('output_buffering')) {
658: echo str_pad('', $length);
659: }
660:
661: while (ob_get_level()) {
662: ob_end_flush();
663: }
664:
665: return $this;
666: }
667:
668: 669: 670: 671: 672: 673: 674: 675:
676: public function download($file = null, array $downloadOptions = array())
677: {
678: $o = $downloadOptions + $this->downloadOption;
679:
680: if (!is_file($file)) {
681: throw new \RuntimeException('File not found', 404);
682: }
683:
684: $name = $o['filename'] ? : basename($file);
685: $name = rawurlencode($name);
686:
687:
688: $userAgent = $this->request->getServer('HTTP_USER_AGENT');
689: if (preg_match('/MSIE ([\w.]+)/', $userAgent)) {
690: $filename = '=' . $name;
691: } else {
692: $filename = "*=UTF-8''" . $name;
693: }
694:
695: $this->setHeader(array(
696: 'Content-Description' => 'File Transfer',
697: 'Content-Type' => $o['type'],
698: 'Content-Disposition' => $o['disposition'] . ';filename' . $filename,
699: 'Content-Transfer-Encoding' => 'binary',
700: 'Expires' => '0',
701: 'Cache-Control' => 'must-revalidate',
702: 'Pragma' => 'public',
703: 'Content-Length' => filesize($file),
704: ));
705:
706:
707: $this->send();
708:
709:
710: readfile($file);
711:
712: return $this;
713: }
714: }
715: