sfWebResponse.class.php 21 KB

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