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: