Development

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

You must first sign up to be able to contribute.

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

Revision 28347, 21.6 kB (checked in by fabien, 5 years ago)

[1.2, 1.3, 1.4] removed cookies from Response objects serialization as it does not make any sense and can cause weird behaviors

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