Development

/branches/1.1/lib/response/sfWebResponse.class.php

You must first sign up to be able to contribute.

root/branches/1.1/lib/response/sfWebResponse.class.php

Revision 11962, 20.0 kB (checked in by fabien, 6 years ago)

[1.1, 1.2] fixed typo in PHPDoc

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1 <?php
2
3 /*
4  * This file is part of the symfony package.
5  * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10
11 /**
12  * sfWebResponse class.
13  *
14  * This class manages web reponses. It supports cookies and headers management.
15  *
16  * @package    symfony
17  * @subpackage response
18  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
19  * @version    SVN: $Id$
20  */
21 class sfWebResponse extends sfResponse
22 {
23   protected
24     $cookies     = array(),
25     $statusCode  = 200,
26     $statusText  = 'OK',
27     $headerOnly  = false,
28     $headers     = array(),
29     $metas       = array(),
30     $httpMetas   = array(),
31     $positions   = array('first', '', 'last'),
32     $stylesheets = array(),
33     $javascripts = array(),
34     $slots       = array();
35
36   static protected $statusTexts = array(
37     '100' => 'Continue',
38     '101' => 'Switching Protocols',
39     '200' => 'OK',
40     '201' => 'Created',
41     '202' => 'Accepted',
42     '203' => 'Non-Authoritative Information',
43     '204' => 'No Content',
44     '205' => 'Reset Content',
45     '206' => 'Partial Content',
46     '300' => 'Multiple Choices',
47     '301' => 'Moved Permanently',
48     '302' => 'Found',
49     '303' => 'See Other',
50     '304' => 'Not Modified',
51     '305' => 'Use Proxy',
52     '306' => '(Unused)',
53     '307' => 'Temporary Redirect',
54     '400' => 'Bad Request',
55     '401' => 'Unauthorized',
56     '402' => 'Payment Required',
57     '403' => 'Forbidden',
58     '404' => 'Not Found',
59     '405' => 'Method Not Allowed',
60     '406' => 'Not Acceptable',
61     '407' => 'Proxy Authentication Required',
62     '408' => 'Request Timeout',
63     '409' => 'Conflict',
64     '410' => 'Gone',
65     '411' => 'Length Required',
66     '412' => 'Precondition Failed',
67     '413' => 'Request Entity Too Large',
68     '414' => 'Request-URI Too Long',
69     '415' => 'Unsupported Media Type',
70     '416' => 'Requested Range Not Satisfiable',
71     '417' => 'Expectation Failed',
72     '500' => 'Internal Server Error',
73     '501' => 'Not Implemented',
74     '502' => 'Bad Gateway',
75     '503' => 'Service Unavailable',
76     '504' => 'Gateway Timeout',
77     '505' => 'HTTP Version Not Supported',
78   );
79
80   /**
81    * Initializes this sfWebResponse.
82    *
83    * Available options:
84    *
85    *  * charset:           The charset to use (utf-8 by default)
86    *  * content_type:      The content type (text/html by default)
87    *  * send_http_headers: Whether to send HTTP headers or not (true by default)
88    *  * http_protocol:     The HTTP protocol to use for the response (HTTP/1.0 by default)
89    *
90    * @param  sfEventDispatcher $dispatcher  An sfEventDispatcher instance
91    * @param  array             $options     An array of options
92    *
93    * @return bool true, if initialization completes successfully, otherwise false
94    *
95    * @throws <b>sfInitializationException</b> If an error occurs while initializing this sfResponse
96    *
97    * @see sfResponse
98    */
99   public function initialize(sfEventDispatcher $dispatcher, $options = array())
100   {
101     parent::initialize($dispatcher, $options);
102
103     $this->javascripts = array_combine($this->positions, array_fill(0, count($this->positions), array()));
104     $this->stylesheets = array_combine($this->positions, array_fill(0, count($this->positions), array()));
105
106     if (!isset($this->options['charset']))
107     {
108       $this->options['charset'] = 'utf-8';
109     }
110
111     if (!isset($this->options['http_protocol']))
112     {
113       $this->options['http_protocol'] = 'HTTP/1.0';
114     }
115
116     $this->options['content_type'] = $this->fixContentType(isset($this->options['content_type']) ? $this->options['content_type'] : 'text/html');
117   }
118
119   /**
120    * Sets if the response consist of just HTTP headers.
121    *
122    * @param bool $value
123    */
124   public function setHeaderOnly($value = true)
125   {
126     $this->headerOnly = (boolean) $value;
127   }
128
129   /**
130    * Returns if the response must only consist of HTTP headers.
131    *
132    * @return bool returns true if, false otherwise
133    */
134   public function isHeaderOnly()
135   {
136     return $this->headerOnly;
137   }
138
139   /**
140    * Sets a cookie.
141    *
142    * @param  string  $name      HTTP header name
143    * @param  string  $value     Value for the cookie
144    * @param  string  $expire    Cookie expiration period
145    * @param  string  $path      Path
146    * @param  string  $domain    Domain name
147    * @param  bool    $secure    If secure
148    * @param  bool    $httpOnly  If uses only HTTP
149    *
150    * @throws <b>sfException</b> If fails to set the cookie
151    */
152   public function setCookie($name, $value, $expire = null, $path = '/', $domain = '', $secure = false, $httpOnly = false)
153   {
154     if ($expire !== null)
155     {
156       if (is_numeric($expire))
157       {
158         $expire = (int) $expire;
159       }
160       else
161       {
162         $expire = strtotime($expire);
163         if ($expire === false || $expire == -1)
164         {
165           throw new sfException('Your expire parameter is not valid.');
166         }
167       }
168     }
169
170     $this->cookies[] = array(
171       'name'     => $name,
172       'value'    => $value,
173       'expire'   => $expire,
174       'path'     => $path,
175       'domain'   => $domain,
176       'secure'   => $secure ? true : false,
177       'httpOnly' => $httpOnly,
178     );
179   }
180
181   /**
182    * Sets response status code.
183    *
184    * @param string $code  HTTP status code
185    * @param string $name  HTTP status text
186    *
187    */
188   public function setStatusCode($code, $name = null)
189   {
190     $this->statusCode = $code;
191     $this->statusText = null !== $name ? $name : self::$statusTexts[$code];
192   }
193
194   /**
195    * Retrieves status code for the current web response.
196    *
197    * @return string Status code
198    */
199   public function getStatusCode()
200   {
201     return $this->statusCode;
202   }
203
204   /**
205    * Sets a HTTP header.
206    *
207    * @param string  $name     HTTP header name
208    * @param string  $value    Value (if null, remove the HTTP header)
209    * @param bool    $replace  Replace for the value
210    *
211    */
212   public function setHttpHeader($name, $value, $replace = true)
213   {
214     $name = $this->normalizeHeaderName($name);
215
216     if (is_null($value))
217     {
218       unset($this->headers[$name]);
219
220       return;
221     }
222
223     if ('Content-Type' == $name)
224     {
225       if ($replace || !$this->getHttpHeader('Content-Type', null))
226       {
227         $this->setContentType($value);
228       }
229
230       return;
231     }
232
233     if (!$replace)
234     {
235       $current = isset($this->headers[$name]) ? $this->headers[$name] : '';
236       $value = ($current ? $current.', ' : '').$value;
237     }
238
239     $this->headers[$name] = $value;
240   }
241
242   /**
243    * Gets HTTP header current value.
244    *
245    * @param  string $name     HTTP header name
246    * @param  string $default  Default value returned if named HTTP header is not found
247    *
248    * @return array
249    */
250   public function getHttpHeader($name, $default = null)
251   {
252     $name = $this->normalizeHeaderName($name);
253
254     return isset($this->headers[$name]) ? $this->headers[$name] : $default;
255   }
256
257   /**
258    * Checks if response has given HTTP header.
259    *
260    * @param  string $name  HTTP header name
261    *
262    * @return bool
263    */
264   public function hasHttpHeader($name)
265   {
266     return array_key_exists($this->normalizeHeaderName($name), $this->headers);
267   }
268
269   /**
270    * Sets response content type.
271    *
272    * @param string $value  Content type
273    *
274    */
275   public function setContentType($value)
276   {
277     $this->headers['Content-Type'] = $this->fixContentType($value);
278   }
279
280   /**
281    * Gets response content type.
282    *
283    * @return array
284    */
285   public function getContentType()
286   {
287     return $this->getHttpHeader('Content-Type', $this->options['content_type']);
288   }
289
290   /**
291    * Sends HTTP headers and cookies.
292    *
293    */
294   public function sendHttpHeaders()
295   {
296     if (sfConfig::get('sf_test'))
297     {
298       return;
299     }
300
301     // status
302     $status = $this->options['http_protocol'].' '.$this->statusCode.' '.$this->statusText;
303     header($status);
304
305     if ($this->options['logging'])
306     {
307       $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Send status "%s"', $status))));
308     }
309
310     // headers
311     if (!$this->getHttpHeader('Content-Type'))
312     {
313       $this->setContentType($this->options['content_type']);
314     }
315     foreach ($this->headers as $name => $value)
316     {
317       header($name.': '.$value);
318
319       if ($value != '' && $this->options['logging'])
320       {
321         $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Send header "%s": "%s"', $name, $value))));
322       }
323     }
324
325     // cookies
326     foreach ($this->cookies as $cookie)
327     {
328       if (version_compare(phpversion(), '5.2', '>='))
329       {
330         setrawcookie($cookie['name'], $cookie['value'], $cookie['expire'], $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httpOnly']);
331       }
332       else
333       {
334         setrawcookie($cookie['name'], $cookie['value'], $cookie['expire'], $cookie['path'], $cookie['domain'], $cookie['secure']);
335       }
336
337       if ($this->options['logging'])
338       {
339         $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Send cookie "%s": "%s"', $cookie['name'], $cookie['value']))));
340       }
341     }
342   }
343
344   /**
345    * Send content for the current web response.
346    *
347    */
348   public function sendContent()
349   {
350     if (!$this->headerOnly)
351     {
352       parent::sendContent();
353     }
354   }
355
356   /**
357    * Sends the HTTP headers and the content.
358    */
359   public function send()
360   {
361     $this->sendHttpHeaders();
362     $this->sendContent();
363   }
364
365   /**
366    * Retrieves a normalized Header.
367    *
368    * @param  string $name  Header name
369    *
370    * @return string Normalized header
371    */
372   protected function normalizeHeaderName($name)
373   {
374     return preg_replace('/\-(.)/e', "'-'.strtoupper('\\1')", strtr(ucfirst(strtolower($name)), '_', '-'));
375   }
376
377   /**
378    * Retrieves a formated date.
379    *
380    * @param  string $timetamp  Timestamp
381    * @param  string $type      Format type
382    *
383    * @return string Formatted date
384    */
385   public function getDate($timestamp, $type = 'rfc1123')
386   {
387     $type = strtolower($type);
388
389     if ($type == 'rfc1123')
390     {
391       return substr(gmdate('r', $timestamp), 0, -5).'GMT';
392     }
393     else if ($type == 'rfc1036')
394     {
395       return gmdate('l, d-M-y H:i:s ', $timestamp).'GMT';
396     }
397     else if ($type == 'asctime')
398     {
399       return gmdate('D M j H:i:s', $timestamp);
400     }
401     else
402     {
403       throw new InvalidArgumentException('The second getDate() method parameter must be one of: rfc1123, rfc1036 or asctime.');
404     }
405   }
406
407   /**
408    * Adds vary to a http header.
409    *
410    * @param string $header  HTTP header
411    */
412   public function addVaryHttpHeader($header)
413   {
414     $vary = $this->getHttpHeader('Vary');
415     $currentHeaders = array();
416     if ($vary)
417     {
418       $currentHeaders = split('/\s*,\s*/', $vary);
419     }
420     $header = $this->normalizeHeaderName($header);
421
422     if (!in_array($header, $currentHeaders))
423     {
424       $currentHeaders[] = $header;
425       $this->setHttpHeader('Vary', implode(', ', $currentHeaders));
426     }
427   }
428
429   /**
430    * Adds an control cache http header.
431    *
432    * @param string $name   HTTP header
433    * @param string $value  Value for the http header
434    */
435   public function addCacheControlHttpHeader($name, $value = null)
436   {
437     $cacheControl = $this->getHttpHeader('Cache-Control');
438     $currentHeaders = array();
439     if ($cacheControl)
440     {
441       foreach (split('/\s*,\s*/', $cacheControl) as $tmp)
442       {
443         $tmp = explode('=', $tmp);
444         $currentHeaders[$tmp[0]] = isset($tmp[1]) ? $tmp[1] : null;
445       }
446     }
447     $currentHeaders[strtr(strtolower($name), '_', '-')] = $value;
448
449     $headers = array();
450     foreach ($currentHeaders as $key => $value)
451     {
452       $headers[] = $key.(null !== $value ? '='.$value : '');
453     }
454
455     $this->setHttpHeader('Cache-Control', implode(', ', $headers));
456   }
457
458   /**
459    * Retrieves meta headers for the current web response.
460    *
461    * @return string Meta headers
462    */
463   public function getHttpMetas()
464   {
465     return $this->httpMetas;
466   }
467
468   /**
469    * Adds a HTTP meta header.
470    *
471    * @param string  $key      Key to replace
472    * @param string  $value    HTTP meta header value (if null, remove the HTTP meta)
473    * @param bool    $replace  Replace or not
474    */
475   public function addHttpMeta($key, $value, $replace = true)
476   {
477     $key = $this->normalizeHeaderName($key);
478
479     // set HTTP header
480     $this->setHttpHeader($key, $value, $replace);
481
482     if (is_null($value))
483     {
484       unset($this->httpMetas[$key]);
485
486       return;
487     }
488
489     if ('Content-Type' == $key)
490     {
491       $value = $this->getContentType();
492     }
493     elseif (!$replace)
494     {
495       $current = isset($this->httpMetas[$key]) ? $this->httpMetas[$key] : '';
496       $value = ($current ? $current.', ' : '').$value;
497     }
498
499     $this->httpMetas[$key] = $value;
500   }
501
502   /**
503    * Retrieves all meta headers.
504    *
505    * @return array List of meta headers
506    */
507   public function getMetas()
508   {
509     return $this->metas;
510   }
511
512   /**
513    * Adds a meta header.
514    *
515    * @param string  $key      Name of the header
516    * @param string  $value    Meta header value (if null, remove the meta)
517    * @param bool    $replace  true if it's replaceable
518    * @param bool    $escape   true for escaping the header
519    */
520   public function addMeta($key, $value, $replace = true, $escape = true)
521   {
522     $key = strtolower($key);
523
524     if (is_null($value))
525     {
526       unset($this->metas[$key]);
527
528       return;
529     }
530
531     // FIXME: If you use the i18n layer and escape the data here, it won't work
532     // see include_metas() in AssetHelper
533     if ($escape)
534     {
535       $value = htmlspecialchars($value, ENT_QUOTES, $this->options['charset']);
536     }
537
538     $current = isset($this->metas[$key]) ? $this->metas[$key] : null;
539     if ($replace || !$current)
540     {
541       $this->metas[$key] = $value;
542     }
543   }
544
545   /**
546    * Retrieves title for the current web response.
547    *
548    * @return string Title
549    */
550   public function getTitle()
551   {
552     return isset($this->metas['title']) ? $this->metas['title'] : '';
553   }
554
555   /**
556    * Sets title for the current web response.
557    *
558    * @param string  $title   Title name
559    * @param bool    $escape  true, for escaping the title
560    */
561   public function setTitle($title, $escape = true)
562   {
563     $this->addMeta('title', $title, true, $escape);
564   }
565
566   /**
567    * Returns the available position names for stylesheets and javascripts in order.
568    *
569    * @return array An array of position names
570    */
571   public function getPositions()
572   {
573     return $this->positions;
574   }
575
576   /**
577    * Retrieves stylesheets for the current web response.
578    *
579    * @param  string  $position
580    *
581    * @return string Stylesheets
582    */
583   public function getStylesheets($position = '')
584   {
585     if ($position == 'ALL')
586     {
587       return $this->stylesheets;
588     }
589
590     $this->validatePosition($position);
591
592     return isset($this->stylesheets[$position]) ? $this->stylesheets[$position] : array();
593   }
594
595   /**
596    * Adds a stylesheet to the current web response.
597    *
598    * @param string $css       Stylesheet
599    * @param string $position  Position
600    * @param string $options   Stylesheet options
601    */
602   public function addStylesheet($css, $position = '', $options = array())
603   {
604     $this->validatePosition($position);
605
606     $this->stylesheets[$position][$css] = $options;
607   }
608
609   /**
610    * Removes a stylesheet from the current web response.
611    *
612    * @param string $css       Stylesheet
613    * @param string $position  Position
614    */
615   public function removeStylesheet($css, $position = '')
616   {
617     $this->validatePosition($position);
618
619     unset($this->stylesheets[$position][$css]);
620   }
621
622   /**
623    * Retrieves javascript code from the current web response.
624    *
625    * @param  string $position  Position
626    *
627    * @return string Javascript code
628    */
629   public function getJavascripts($position = '')
630   {
631     if ($position == 'ALL')
632     {
633       return $this->javascripts;
634     }
635
636     $this->validatePosition($position);
637
638     return isset($this->javascripts[$position]) ? $this->javascripts[$position] : array();
639   }
640
641   /**
642    * Adds javascript code to the current web response.
643    *
644    * @param string $js        Javascript code
645    * @param string $position  Position
646    * @param string $options   Javascript options
647    */
648   public function addJavascript($js, $position = '', $options = array())
649   {
650     $this->validatePosition($position);
651
652     $this->javascripts[$position][$js] = $options;
653   }
654
655   /**
656    * Removes javascript code from the current web response.
657    *
658    * @param string $js        Javascript code
659    * @param string $position  Position
660    */
661   public function removeJavascript($js, $position = '')
662   {
663     $this->validatePosition($position);
664
665     unset($this->javascripts[$position][$js]);
666   }
667
668   /**
669    * Retrieves slots from the current web response.
670    *
671    * @return string Javascript code
672    */
673   public function getSlots()
674   {
675     return $this->slots;
676   }
677
678   /**
679    * Sets a slot content.
680    *
681    * @param string $name     Slot name
682    * @param string $content  Content
683    */
684   public function setSlot($name, $content)
685   {
686     $this->slots[$name] = $content;
687   }
688
689   /**
690    * Retrieves cookies from the current web response.
691    *
692    * @return array Cookies
693    */
694   public function getCookies()
695   {
696     $cookies = array();
697     foreach ($this->cookies as $cookie)
698     {
699       $cookies[$cookie['name']] = $cookie;
700     }
701
702     return $cookies;
703   }
704
705   /**
706    * Retrieves HTTP headers from the current web response.
707    *
708    * @return string HTTP headers
709    */
710   public function getHttpHeaders()
711   {
712     return $this->headers;
713   }
714
715   /**
716    * Cleans HTTP headers from the current web response.
717    */
718   public function clearHttpHeaders()
719   {
720     $this->headers = array();
721   }
722
723   /**
724    * Copies all properties from a given sfWebResponse object to the current one.
725    *
726    * @param sfWebResponse $response  An sfWebResponse instance
727    */
728   public function copyProperties(sfWebResponse $response)
729   {
730     $this->options     = $response->getOptions();
731     $this->headers     = $response->getHttpHeaders();
732     $this->metas       = $response->getMetas();
733     $this->httpMetas   = $response->getHttpMetas();
734     $this->stylesheets = $response->getStylesheets('ALL');
735     $this->javascripts = $response->getJavascripts('ALL');
736     $this->slots       = $response->getSlots();
737   }
738
739   /**
740    * Merges all properties from a given sfWebResponse object to the current one.
741    *
742    * @param sfWebResponse $response  An sfWebResponse instance
743    */
744   public function merge(sfWebResponse $response)
745   {
746     foreach ($this->getPositions() as $position)
747     {
748       $this->javascripts[$position] = array_merge($this->getJavascripts($position), $response->getJavascripts($position));
749       $this->stylesheets[$position] = array_merge($this->getStylesheets($position), $response->getStylesheets($position));
750     }
751
752     $this->slots = array_merge($this->getSlots(), $response->getSlots());
753   }
754
755   /**
756    * @see sfResponse
757    */
758   public function serialize()
759   {
760     return serialize(array($this->content, $this->statusCode, $this->statusText, $this->options, $this->cookies, $this->headerOnly, $this->headers, $this->metas, $this->httpMetas, $this->stylesheets, $this->javascripts, $this->slots));
761   }
762
763   /**
764    * @see sfResponse
765    */
766   public function unserialize($serialized)
767   {
768     list($this->content, $this->statusCode, $this->statusText, $this->options, $this->cookies, $this->headerOnly, $this->headers, $this->metas, $this->httpMetas, $this->stylesheets, $this->javascripts, $this->slots) = unserialize($serialized);
769   }
770
771   /**
772    * Validate a position name.
773    *
774    * @param  string $position
775    *
776    * @throws InvalidArgumentException if the position is not available
777    */
778   protected function validatePosition($position)
779   {
780     if (!in_array($position, $this->positions, true))
781     {
782       throw new InvalidArgumentException(sprintf('The position "%s" does not exist (available positions: %s).', $position, implode(', ', $this->positions)));
783     }
784   }
785
786   /**
787    * Fixes the content type by adding the charset for text content types.
788    *
789    * @param  string $content  The content type
790    *
791    * @return string The content type with the charset if needed
792    */
793   protected function fixContentType($contentType)
794   {
795     // add charset if needed (only on text content)
796     if (false === stripos($contentType, 'charset') && (0 === stripos($contentType, 'text/') || strlen($contentType) - 3 === strripos($contentType, 'xml')))
797     {
798       $contentType .= '; charset='.$this->options['charset'];
799     }
800
801     return $contentType;
802   }
803 }
804
Note: See TracBrowser for help on using the browser.