sfWebResponse.class.php 20 KB

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