123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861 |
- <?php
- /*
- * This file is part of the symfony package.
- * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- /**
- * sfWebResponse class.
- *
- * This class manages web reponses. It supports cookies and headers management.
- *
- * @package symfony
- * @subpackage response
- * @author Fabien Potencier <fabien.potencier@symfony-project.com>
- * @version SVN: $Id: sfWebResponse.class.php 13564 2008-11-30 21:34:25Z fabien $
- */
- class sfWebResponse extends sfResponse
- {
- const
- FIRST = 'first',
- MIDDLE = '',
- LAST = 'last',
- ALL = 'ALL',
- RAW = 'RAW';
- protected
- $cookies = array(),
- $statusCode = 200,
- $statusText = 'OK',
- $headerOnly = false,
- $headers = array(),
- $metas = array(),
- $httpMetas = array(),
- $positions = array('first', '', 'last'),
- $stylesheets = array(),
- $javascripts = array(),
- $slots = array();
- static protected $statusTexts = array(
- '100' => 'Continue',
- '101' => 'Switching Protocols',
- '200' => 'OK',
- '201' => 'Created',
- '202' => 'Accepted',
- '203' => 'Non-Authoritative Information',
- '204' => 'No Content',
- '205' => 'Reset Content',
- '206' => 'Partial Content',
- '300' => 'Multiple Choices',
- '301' => 'Moved Permanently',
- '302' => 'Found',
- '303' => 'See Other',
- '304' => 'Not Modified',
- '305' => 'Use Proxy',
- '306' => '(Unused)',
- '307' => 'Temporary Redirect',
- '400' => 'Bad Request',
- '401' => 'Unauthorized',
- '402' => 'Payment Required',
- '403' => 'Forbidden',
- '404' => 'Not Found',
- '405' => 'Method Not Allowed',
- '406' => 'Not Acceptable',
- '407' => 'Proxy Authentication Required',
- '408' => 'Request Timeout',
- '409' => 'Conflict',
- '410' => 'Gone',
- '411' => 'Length Required',
- '412' => 'Precondition Failed',
- '413' => 'Request Entity Too Large',
- '414' => 'Request-URI Too Long',
- '415' => 'Unsupported Media Type',
- '416' => 'Requested Range Not Satisfiable',
- '417' => 'Expectation Failed',
- '500' => 'Internal Server Error',
- '501' => 'Not Implemented',
- '502' => 'Bad Gateway',
- '503' => 'Service Unavailable',
- '504' => 'Gateway Timeout',
- '505' => 'HTTP Version Not Supported',
- );
- /**
- * Initializes this sfWebResponse.
- *
- * Available options:
- *
- * * charset: The charset to use (utf-8 by default)
- * * content_type: The content type (text/html by default)
- * * send_http_headers: Whether to send HTTP headers or not (true by default)
- * * http_protocol: The HTTP protocol to use for the response (HTTP/1.0 by default)
- *
- * @param sfEventDispatcher $dispatcher An sfEventDispatcher instance
- * @param array $options An array of options
- *
- * @return bool true, if initialization completes successfully, otherwise false
- *
- * @throws <b>sfInitializationException</b> If an error occurs while initializing this sfResponse
- *
- * @see sfResponse
- */
- public function initialize(sfEventDispatcher $dispatcher, $options = array())
- {
- parent::initialize($dispatcher, $options);
- $this->javascripts = array_combine($this->positions, array_fill(0, count($this->positions), array()));
- $this->stylesheets = array_combine($this->positions, array_fill(0, count($this->positions), array()));
- if (!isset($this->options['charset']))
- {
- $this->options['charset'] = 'utf-8';
- }
- if (!isset($this->options['send_http_headers']))
- {
- $this->options['send_http_headers'] = true;
- }
- if (!isset($this->options['http_protocol']))
- {
- $this->options['http_protocol'] = 'HTTP/1.0';
- }
- $this->options['content_type'] = $this->fixContentType(isset($this->options['content_type']) ? $this->options['content_type'] : 'text/html');
- }
- /**
- * Sets if the response consist of just HTTP headers.
- *
- * @param bool $value
- */
- public function setHeaderOnly($value = true)
- {
- $this->headerOnly = (boolean) $value;
- }
- /**
- * Returns if the response must only consist of HTTP headers.
- *
- * @return bool returns true if, false otherwise
- */
- public function isHeaderOnly()
- {
- return $this->headerOnly;
- }
- /**
- * Sets a cookie.
- *
- * @param string $name HTTP header name
- * @param string $value Value for the cookie
- * @param string $expire Cookie expiration period
- * @param string $path Path
- * @param string $domain Domain name
- * @param bool $secure If secure
- * @param bool $httpOnly If uses only HTTP
- *
- * @throws <b>sfException</b> If fails to set the cookie
- */
- public function setCookie($name, $value, $expire = null, $path = '/', $domain = '', $secure = false, $httpOnly = false)
- {
- if ($expire !== null)
- {
- if (is_numeric($expire))
- {
- $expire = (int) $expire;
- }
- else
- {
- $expire = strtotime($expire);
- if ($expire === false || $expire == -1)
- {
- throw new sfException('Your expire parameter is not valid.');
- }
- }
- }
- $this->cookies[$name] = array(
- 'name' => $name,
- 'value' => $value,
- 'expire' => $expire,
- 'path' => $path,
- 'domain' => $domain,
- 'secure' => $secure ? true : false,
- 'httpOnly' => $httpOnly,
- );
- }
- /**
- * Sets response status code.
- *
- * @param string $code HTTP status code
- * @param string $name HTTP status text
- *
- */
- public function setStatusCode($code, $name = null)
- {
- $this->statusCode = $code;
- $this->statusText = null !== $name ? $name : self::$statusTexts[$code];
- }
- /**
- * Retrieves status text for the current web response.
- *
- * @return string Status text
- */
- public function getStatusText()
- {
- return $this->statusText;
- }
- /**
- * Retrieves status code for the current web response.
- *
- * @return integer Status code
- */
- public function getStatusCode()
- {
- return $this->statusCode;
- }
- /**
- * Sets a HTTP header.
- *
- * @param string $name HTTP header name
- * @param string $value Value (if null, remove the HTTP header)
- * @param bool $replace Replace for the value
- *
- */
- public function setHttpHeader($name, $value, $replace = true)
- {
- $name = $this->normalizeHeaderName($name);
- if (is_null($value))
- {
- unset($this->headers[$name]);
- return;
- }
- if ('Content-Type' == $name)
- {
- if ($replace || !$this->getHttpHeader('Content-Type', null))
- {
- $this->setContentType($value);
- }
- return;
- }
- if (!$replace)
- {
- $current = isset($this->headers[$name]) ? $this->headers[$name] : '';
- $value = ($current ? $current.', ' : '').$value;
- }
- $this->headers[$name] = $value;
- }
- /**
- * Gets HTTP header current value.
- *
- * @param string $name HTTP header name
- * @param string $default Default value returned if named HTTP header is not found
- *
- * @return array
- */
- public function getHttpHeader($name, $default = null)
- {
- $name = $this->normalizeHeaderName($name);
- return isset($this->headers[$name]) ? $this->headers[$name] : $default;
- }
- /**
- * Checks if response has given HTTP header.
- *
- * @param string $name HTTP header name
- *
- * @return bool
- */
- public function hasHttpHeader($name)
- {
- return array_key_exists($this->normalizeHeaderName($name), $this->headers);
- }
- /**
- * Sets response content type.
- *
- * @param string $value Content type
- *
- */
- public function setContentType($value)
- {
- $this->headers['Content-Type'] = $this->fixContentType($value);
- }
- /**
- * Gets the current charset as defined by the content type.
- *
- * @return string The current charset
- */
- public function getCharset()
- {
- return $this->options['charset'];
- }
- /**
- * Gets response content type.
- *
- * @return array
- */
- public function getContentType()
- {
- return $this->getHttpHeader('Content-Type', $this->options['content_type']);
- }
- /**
- * Sends HTTP headers and cookies.
- *
- */
- public function sendHttpHeaders()
- {
- if (!$this->options['send_http_headers'])
- {
- return;
- }
- // status
- $status = $this->options['http_protocol'].' '.$this->statusCode.' '.$this->statusText;
- header($status);
- if ($this->options['logging'])
- {
- $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Send status "%s"', $status))));
- }
- // headers
- if (!$this->getHttpHeader('Content-Type'))
- {
- $this->setContentType($this->options['content_type']);
- }
- foreach ($this->headers as $name => $value)
- {
- header($name.': '.$value);
- if ($value != '' && $this->options['logging'])
- {
- $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Send header "%s: %s"', $name, $value))));
- }
- }
- // cookies
- foreach ($this->cookies as $cookie)
- {
- setrawcookie($cookie['name'], $cookie['value'], $cookie['expire'], $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httpOnly']);
- if ($this->options['logging'])
- {
- $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Send cookie "%s": "%s"', $cookie['name'], $cookie['value']))));
- }
- }
- }
- /**
- * Send content for the current web response.
- *
- */
- public function sendContent()
- {
- if (!$this->headerOnly)
- {
- parent::sendContent();
- }
- }
- /**
- * Sends the HTTP headers and the content.
- */
- public function send()
- {
- $this->sendHttpHeaders();
- $this->sendContent();
- }
- /**
- * Retrieves a normalized Header.
- *
- * @param string $name Header name
- *
- * @return string Normalized header
- */
- protected function normalizeHeaderName($name)
- {
- return preg_replace('/\-(.)/e', "'-'.strtoupper('\\1')", strtr(ucfirst(strtolower($name)), '_', '-'));
- }
- /**
- * Retrieves a formated date.
- *
- * @param string $timetamp Timestamp
- * @param string $type Format type
- *
- * @return string Formatted date
- */
- static public function getDate($timestamp, $type = 'rfc1123')
- {
- $type = strtolower($type);
- if ($type == 'rfc1123')
- {
- return substr(gmdate('r', $timestamp), 0, -5).'GMT';
- }
- else if ($type == 'rfc1036')
- {
- return gmdate('l, d-M-y H:i:s ', $timestamp).'GMT';
- }
- else if ($type == 'asctime')
- {
- return gmdate('D M j H:i:s', $timestamp);
- }
- else
- {
- throw new InvalidArgumentException('The second getDate() method parameter must be one of: rfc1123, rfc1036 or asctime.');
- }
- }
- /**
- * Adds vary to a http header.
- *
- * @param string $header HTTP header
- */
- public function addVaryHttpHeader($header)
- {
- $vary = $this->getHttpHeader('Vary');
- $currentHeaders = array();
- if ($vary)
- {
- $currentHeaders = preg_split('/\s*,\s*/', $vary);
- }
- $header = $this->normalizeHeaderName($header);
- if (!in_array($header, $currentHeaders))
- {
- $currentHeaders[] = $header;
- $this->setHttpHeader('Vary', implode(', ', $currentHeaders));
- }
- }
- /**
- * Adds an control cache http header.
- *
- * @param string $name HTTP header
- * @param string $value Value for the http header
- */
- public function addCacheControlHttpHeader($name, $value = null)
- {
- $cacheControl = $this->getHttpHeader('Cache-Control');
- $currentHeaders = array();
- if ($cacheControl)
- {
- foreach (preg_split('/\s*,\s*/', $cacheControl) as $tmp)
- {
- $tmp = explode('=', $tmp);
- $currentHeaders[$tmp[0]] = isset($tmp[1]) ? $tmp[1] : null;
- }
- }
- $currentHeaders[strtr(strtolower($name), '_', '-')] = $value;
- $headers = array();
- foreach ($currentHeaders as $key => $value)
- {
- $headers[] = $key.(null !== $value ? '='.$value : '');
- }
- $this->setHttpHeader('Cache-Control', implode(', ', $headers));
- }
- /**
- * Retrieves meta headers for the current web response.
- *
- * @return string Meta headers
- */
- public function getHttpMetas()
- {
- return $this->httpMetas;
- }
- /**
- * Adds a HTTP meta header.
- *
- * @param string $key Key to replace
- * @param string $value HTTP meta header value (if null, remove the HTTP meta)
- * @param bool $replace Replace or not
- */
- public function addHttpMeta($key, $value, $replace = true)
- {
- $key = $this->normalizeHeaderName($key);
- // set HTTP header
- $this->setHttpHeader($key, $value, $replace);
- if (is_null($value))
- {
- unset($this->httpMetas[$key]);
- return;
- }
- if ('Content-Type' == $key)
- {
- $value = $this->getContentType();
- }
- elseif (!$replace)
- {
- $current = isset($this->httpMetas[$key]) ? $this->httpMetas[$key] : '';
- $value = ($current ? $current.', ' : '').$value;
- }
- $this->httpMetas[$key] = $value;
- }
- /**
- * Retrieves all meta headers.
- *
- * @return array List of meta headers
- */
- public function getMetas()
- {
- return $this->metas;
- }
- /**
- * Adds a meta header.
- *
- * @param string $key Name of the header
- * @param string $value Meta header value (if null, remove the meta)
- * @param bool $replace true if it's replaceable
- * @param bool $escape true for escaping the header
- */
- public function addMeta($key, $value, $replace = true, $escape = true)
- {
- $key = strtolower($key);
- if (is_null($value))
- {
- unset($this->metas[$key]);
- return;
- }
- // FIXME: If you use the i18n layer and escape the data here, it won't work
- // see include_metas() in AssetHelper
- if ($escape)
- {
- $value = htmlspecialchars($value, ENT_QUOTES, $this->options['charset']);
- }
- $current = isset($this->metas[$key]) ? $this->metas[$key] : null;
- if ($replace || !$current)
- {
- $this->metas[$key] = $value;
- }
- }
- /**
- * Retrieves title for the current web response.
- *
- * @return string Title
- */
- public function getTitle()
- {
- return isset($this->metas['title']) ? $this->metas['title'] : '';
- }
- /**
- * Sets title for the current web response.
- *
- * @param string $title Title name
- * @param bool $escape true, for escaping the title
- */
- public function setTitle($title, $escape = true)
- {
- $this->addMeta('title', $title, true, $escape);
- }
- /**
- * Returns the available position names for stylesheets and javascripts in order.
- *
- * @return array An array of position names
- */
- public function getPositions()
- {
- return $this->positions;
- }
- /**
- * Retrieves stylesheets for the current web response.
- *
- * By default, the position is sfWebResponse::ALL,
- * and the method returns all stylesheets ordered by position.
- *
- * @param string $position The position
- *
- * @return array An associative array of stylesheet files as keys and options as values
- */
- public function getStylesheets($position = self::ALL)
- {
- if (self::ALL === $position)
- {
- $stylesheets = array();
- foreach ($this->getPositions() as $position)
- {
- foreach ($this->stylesheets[$position] as $file => $options)
- {
- $stylesheets[$file] = $options;
- }
- }
- return $stylesheets;
- }
- else if (self::RAW === $position)
- {
- return $this->stylesheets;
- }
- $this->validatePosition($position);
- return $this->stylesheets[$position];
- }
- /**
- * Adds a stylesheet to the current web response.
- *
- * @param string $file The stylesheet file
- * @param string $position Position
- * @param string $options Stylesheet options
- */
- public function addStylesheet($file, $position = '', $options = array())
- {
- $this->validatePosition($position);
- $this->stylesheets[$position][$file] = $options;
- }
- /**
- * Removes a stylesheet from the current web response.
- *
- * @param string $css The stylesheet file to remove
- */
- public function removeStylesheet($file)
- {
- foreach ($this->getPositions() as $position)
- {
- unset($this->stylesheets[$position][$file]);
- }
- }
- /**
- * Retrieves javascript files from the current web response.
- *
- * By default, the position is sfWebResponse::ALL,
- * and the method returns all javascripts ordered by position.
- *
- * @param string $position The position
- *
- * @return array An associative array of javascript files as keys and options as values
- */
- public function getJavascripts($position = self::ALL)
- {
- if (self::ALL === $position)
- {
- $javascripts = array();
- foreach ($this->getPositions() as $position)
- {
- foreach ($this->javascripts[$position] as $file => $options)
- {
- $javascripts[$file] = $options;
- }
- }
- return $javascripts;
- }
- else if (self::RAW === $position)
- {
- return $this->javascripts;
- }
- $this->validatePosition($position);
- return $this->javascripts[$position];
- }
- /**
- * Adds javascript code to the current web response.
- *
- * @param string $gile The JavaScript file
- * @param string $position Position
- * @param string $options Javascript options
- */
- public function addJavascript($file, $position = '', $options = array())
- {
- $this->validatePosition($position);
- $this->javascripts[$position][$file] = $options;
- }
- /**
- * Removes a JavaScript file from the current web response.
- *
- * @param string $file The Javascript file to remove
- */
- public function removeJavascript($file)
- {
- foreach ($this->getPositions() as $position)
- {
- unset($this->javascripts[$position][$file]);
- }
- }
- /**
- * Retrieves slots from the current web response.
- *
- * @return string Javascript code
- */
- public function getSlots()
- {
- return $this->slots;
- }
- /**
- * Sets a slot content.
- *
- * @param string $name Slot name
- * @param string $content Content
- */
- public function setSlot($name, $content)
- {
- $this->slots[$name] = $content;
- }
- /**
- * Retrieves cookies from the current web response.
- *
- * @return array Cookies
- */
- public function getCookies()
- {
- return $this->cookies;
- }
- /**
- * Retrieves HTTP headers from the current web response.
- *
- * @return string HTTP headers
- */
- public function getHttpHeaders()
- {
- return $this->headers;
- }
- /**
- * Cleans HTTP headers from the current web response.
- */
- public function clearHttpHeaders()
- {
- $this->headers = array();
- }
- /**
- * Copies all properties from a given sfWebResponse object to the current one.
- *
- * @param sfWebResponse $response An sfWebResponse instance
- */
- public function copyProperties(sfWebResponse $response)
- {
- $this->options = $response->getOptions();
- $this->headers = $response->getHttpHeaders();
- $this->metas = $response->getMetas();
- $this->httpMetas = $response->getHttpMetas();
- $this->stylesheets = $response->getStylesheets(self::RAW);
- $this->javascripts = $response->getJavascripts(self::RAW);
- $this->slots = $response->getSlots();
- }
- /**
- * Merges all properties from a given sfWebResponse object to the current one.
- *
- * @param sfWebResponse $response An sfWebResponse instance
- */
- public function merge(sfWebResponse $response)
- {
- foreach ($this->getPositions() as $position)
- {
- $this->javascripts[$position] = array_merge($this->getJavascripts($position), $response->getJavascripts($position));
- $this->stylesheets[$position] = array_merge($this->getStylesheets($position), $response->getStylesheets($position));
- }
- $this->slots = array_merge($this->getSlots(), $response->getSlots());
- }
- /**
- * @see sfResponse
- */
- public function serialize()
- {
- 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));
- }
- /**
- * @see sfResponse
- */
- public function unserialize($serialized)
- {
- 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);
- }
- /**
- * Validate a position name.
- *
- * @param string $position
- *
- * @throws InvalidArgumentException if the position is not available
- */
- protected function validatePosition($position)
- {
- if (!in_array($position, $this->positions, true))
- {
- throw new InvalidArgumentException(sprintf('The position "%s" does not exist (available positions: %s).', $position, implode(', ', $this->positions)));
- }
- }
- /**
- * Fixes the content type by adding the charset for text content types.
- *
- * @param string $content The content type
- *
- * @return string The content type with the charset if needed
- */
- protected function fixContentType($contentType)
- {
- // add charset if needed (only on text content)
- if (false === stripos($contentType, 'charset') && (0 === stripos($contentType, 'text/') || strlen($contentType) - 3 === strripos($contentType, 'xml')))
- {
- $contentType .= '; charset='.$this->options['charset'];
- }
- // change the charset for the response
- if (preg_match('/charset\s*=\s*(.+)\s*$/', $contentType, $match))
- {
- $this->options['charset'] = $match[1];
- }
- return $contentType;
- }
- }
|