PageRenderTime 52ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/system/classes/kohana/http/header.php

https://bitbucket.org/alameya/alameya_core
PHP | 932 lines | 461 code | 112 blank | 359 comment | 47 complexity | f171f5dff2cc23f975aeab5975e909a7 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * The Kohana_HTTP_Header class provides an Object-Orientated interface
  4. * to HTTP headers. This can parse header arrays returned from the
  5. * PHP functions `apache_request_headers()` or the `http_parse_headers()`
  6. * function available within the PECL HTTP library.
  7. *
  8. * @package Kohana
  9. * @category HTTP
  10. * @author Kohana Team
  11. * @since 3.1.0
  12. * @copyright (c) 2008-2011 Kohana Team
  13. * @license http://kohanaphp.com/license
  14. */
  15. class Kohana_HTTP_Header extends ArrayObject {
  16. // Default Accept-* quality value if none supplied
  17. const DEFAULT_QUALITY = 1;
  18. /**
  19. * Parses an Accept(-*) header and detects the quality
  20. *
  21. * @param array accept header parts
  22. * @return array
  23. * @since 3.2.0
  24. */
  25. public static function accept_quality(array $parts)
  26. {
  27. $parsed = array();
  28. // Resource light iteration
  29. $parts_keys = array_keys($parts);
  30. foreach ($parts_keys as $key)
  31. {
  32. $value = trim(str_replace(array("\r", "\n"), '', $parts[$key]));
  33. $pattern = '~\b(\;\s*+)?q\s*+=\s*+([.0-9]+)~';
  34. // If there is no quality directive, return default
  35. if ( ! preg_match($pattern, $value, $quality))
  36. {
  37. $parsed[$value] = (float) HTTP_Header::DEFAULT_QUALITY;
  38. }
  39. else
  40. {
  41. $quality = $quality[2];
  42. if ($quality[0] === '.')
  43. {
  44. $quality = '0'.$quality;
  45. }
  46. // Remove the quality value from the string and apply quality
  47. $parsed[trim(preg_replace($pattern, '', $value, 1), '; ')] = (float) $quality;
  48. }
  49. }
  50. return $parsed;
  51. }
  52. /**
  53. * Parses the accept header to provide the correct quality values
  54. * for each supplied accept type.
  55. *
  56. * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
  57. * @param string accept content header string to parse
  58. * @return array
  59. * @since 3.2.0
  60. */
  61. public static function parse_accept_header($accepts = NULL)
  62. {
  63. $accepts = explode(',', (string) $accepts);
  64. // If there is no accept, lets accept everything
  65. if ($accepts === NULL)
  66. return array('*' => array('*' => (float) HTTP_Header::DEFAULT_QUALITY));
  67. // Parse the accept header qualities
  68. $accepts = HTTP_Header::accept_quality($accepts);
  69. $parsed_accept = array();
  70. // This method of iteration uses less resource
  71. $keys = array_keys($accepts);
  72. foreach ($keys as $key)
  73. {
  74. // Extract the parts
  75. $parts = explode('/', $key, 2);
  76. // Invalid content type- bail
  77. if ( ! isset($parts[1]))
  78. continue;
  79. // Set the parsed output
  80. $parsed_accept[$parts[0]][$parts[1]] = $accepts[$key];
  81. }
  82. return $parsed_accept;
  83. }
  84. /**
  85. * Parses the `Accept-Charset:` HTTP header and returns an array containing
  86. * the charset and associated quality.
  87. *
  88. * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2
  89. * @param string charset string to parse
  90. * @return array
  91. * @since 3.2.0
  92. */
  93. public static function parse_charset_header($charset = NULL)
  94. {
  95. if ($charset === NULL)
  96. {
  97. return array('*' => (float) HTTP_Header::DEFAULT_QUALITY);
  98. }
  99. return HTTP_Header::accept_quality(explode(',', (string) $charset));
  100. }
  101. /**
  102. * Parses the `Accept-Encoding:` HTTP header and returns an array containing
  103. * the charsets and associated quality.
  104. *
  105. * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
  106. * @param string charset string to parse
  107. * @return array
  108. * @since 3.2.0
  109. */
  110. public static function parse_encoding_header($encoding = NULL)
  111. {
  112. // Accept everything
  113. if ($encoding === NULL)
  114. {
  115. return array('*' => (float) HTTP_Header::DEFAULT_QUALITY);
  116. }
  117. elseif ($encoding === '')
  118. {
  119. return array('identity' => (float) HTTP_Header::DEFAULT_QUALITY);
  120. }
  121. else
  122. {
  123. return HTTP_Header::accept_quality(explode(',', (string) $encoding));
  124. }
  125. }
  126. /**
  127. * Parses the `Accept-Language:` HTTP header and returns an array containing
  128. * the languages and associated quality.
  129. *
  130. * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
  131. * @param string charset string to parse
  132. * @return array
  133. * @since 3.2.0
  134. */
  135. public static function parse_language_header($language = NULL)
  136. {
  137. if ($language === NULL)
  138. {
  139. return array('*' => array('*' => (float) HTTP_Header::DEFAULT_QUALITY));
  140. }
  141. $language = HTTP_Header::accept_quality(explode(',', (string) $language));
  142. $parsed_language = array();
  143. $keys = array_keys($language);
  144. foreach ($keys as $key)
  145. {
  146. // Extract the parts
  147. $parts = explode('-', $key, 2);
  148. // Invalid content type- bail
  149. if ( ! isset($parts[1]))
  150. {
  151. $parsed_language[$parts[0]]['*'] = $language[$key];
  152. }
  153. else
  154. {
  155. // Set the parsed output
  156. $parsed_language[$parts[0]][$parts[1]] = $language[$key];
  157. }
  158. }
  159. return $parsed_language;
  160. }
  161. /**
  162. * Generates a Cache-Control HTTP header based on the supplied array.
  163. *
  164. * // Set the cache control headers you want to use
  165. * $cache_control = array(
  166. * 'max-age' => 3600,
  167. * 'must-revalidate',
  168. * 'public'
  169. * );
  170. *
  171. * // Create the cache control header, creates :
  172. * // cache-control: max-age=3600, must-revalidate, public
  173. * $response->headers('Cache-Control', HTTP_Header::create_cache_control($cache_control);
  174. *
  175. * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13
  176. * @param array Cache-Control to render to string
  177. * @return string
  178. */
  179. public static function create_cache_control(array $cache_control)
  180. {
  181. $parts = array();
  182. foreach ($cache_control as $key => $value)
  183. {
  184. $parts[] = (is_int($key)) ? $value : ($key.'='.$value);
  185. }
  186. return implode(', ', $parts);
  187. }
  188. /**
  189. * Parses the Cache-Control header and returning an array representation of the Cache-Control
  190. * header.
  191. *
  192. * // Create the cache control header
  193. * $response->headers('cache-control', 'max-age=3600, must-revalidate, public');
  194. *
  195. * // Parse the cache control header
  196. * if ($cache_control = HTTP_Header::parse_cache_control($response->headers('cache-control')))
  197. * {
  198. * // Cache-Control header was found
  199. * $maxage = $cache_control['max-age'];
  200. * }
  201. *
  202. * @param array $cache_control Array of headers
  203. * @return mixed
  204. */
  205. public static function parse_cache_control($cache_control)
  206. {
  207. $directives = explode(',', strtolower($cache_control));
  208. if ($directives === FALSE)
  209. return FALSE;
  210. $output = array();
  211. foreach ($directives as $directive)
  212. {
  213. if (strpos($directive, '=') !== FALSE)
  214. {
  215. list($key, $value) = explode('=', trim($directive), 2);
  216. $output[$key] = ctype_digit($value) ? (int) $value : $value;
  217. }
  218. else
  219. {
  220. $output[] = trim($directive);
  221. }
  222. }
  223. return $output;
  224. }
  225. /**
  226. * @var array Accept: (content) types
  227. */
  228. protected $_accept_content;
  229. /**
  230. * @var array Accept-Charset: parsed header
  231. */
  232. protected $_accept_charset;
  233. /**
  234. * @var array Accept-Encoding: parsed header
  235. */
  236. protected $_accept_encoding;
  237. /**
  238. * @var array Accept-Language: parsed header
  239. */
  240. protected $_accept_language;
  241. /**
  242. * Constructor method for [Kohana_HTTP_Header]. Uses the standard constructor
  243. * of the parent `ArrayObject` class.
  244. *
  245. * $header_object = new HTTP_Header(array('x-powered-by' => 'Kohana 3.1.x', 'expires' => '...'));
  246. *
  247. * @param mixed Input array
  248. * @param int Flags
  249. * @param string The iterator class to use
  250. */
  251. public function __construct(array $input = array(), $flags = NULL, $iterator_class = 'ArrayIterator')
  252. {
  253. /**
  254. * @link http://www.w3.org/Protocols/rfc2616/rfc2616.html
  255. *
  256. * HTTP header declarations should be treated as case-insensitive
  257. */
  258. $input = array_change_key_case($input, CASE_LOWER);
  259. parent::__construct($input, $flags, $iterator_class);
  260. }
  261. /**
  262. * Returns the header object as a string, including
  263. * the terminating new line
  264. *
  265. * // Return the header as a string
  266. * echo (string) $request->headers();
  267. *
  268. * @return string
  269. */
  270. public function __toString()
  271. {
  272. $header = '';
  273. foreach ($this as $key => $value)
  274. {
  275. // Put the keys back the Case-Convention expected
  276. $key = Text::ucfirst($key);
  277. if (is_array($value))
  278. {
  279. $header .= $key.': '.(implode(', ', $value))."\r\n";
  280. }
  281. else
  282. {
  283. $header .= $key.': '.$value."\r\n";
  284. }
  285. }
  286. return $header."\r\n";
  287. }
  288. /**
  289. * Overloads `ArrayObject::offsetSet()` to enable handling of header
  290. * with multiple instances of the same directive. If the `$replace` flag
  291. * is `FALSE`, the header will be appended rather than replacing the
  292. * original setting.
  293. *
  294. * @param mixed index to set `$newval` to
  295. * @param mixed new value to set
  296. * @param boolean replace existing value
  297. * @return void
  298. * @since 3.2.0
  299. */
  300. public function offsetSet($index, $newval, $replace = TRUE)
  301. {
  302. // Ensure the index is lowercase
  303. $index = strtolower($index);
  304. $newval = (string) $newval;
  305. if ($replace OR ! $this->offsetExists($index))
  306. {
  307. return parent::offsetSet($index, $newval);
  308. }
  309. $current_value = $this->offsetGet($index);
  310. if (is_array($current_value))
  311. {
  312. $current_value[] = $newval;
  313. }
  314. else
  315. {
  316. $current_value = array($current_value, $newval);
  317. }
  318. return parent::offsetSet($index, $current_value);
  319. }
  320. /**
  321. * Overloads the `ArrayObject::offsetExists()` method to ensure keys
  322. * are lowercase.
  323. *
  324. * @param string $index
  325. * @return boolean
  326. * @since 3.2.0
  327. */
  328. public function offsetExists($index)
  329. {
  330. return parent::offsetExists(strtolower($index));
  331. }
  332. /**
  333. * Overloads the `ArrayObject::offsetUnset()` method to ensure keys
  334. * are lowercase.
  335. *
  336. * @param string index
  337. * @return void
  338. * @since 3.2.0
  339. */
  340. public function offsetUnset($index)
  341. {
  342. return parent::offsetUnset(strtolower($index));
  343. }
  344. /**
  345. * Overload the `ArrayObject::offsetGet()` method to ensure that all
  346. * keys passed to it are formatted correctly for this object.
  347. *
  348. * @param string index to retrieve
  349. * @return mixed
  350. * @since 3.2.0
  351. */
  352. public function offsetGet($index)
  353. {
  354. return parent::offsetGet(strtolower($index));
  355. }
  356. /**
  357. * Parses a HTTP Message header line and applies it to this HTTP_Header
  358. *
  359. * $header = $response->headers();
  360. * $header->parse_header_string(NULL, 'content-type: application/json');
  361. *
  362. * @param resource the resource (required by Curl API)
  363. * @param string the line from the header to parse
  364. * @return int
  365. * @since 3.2.0
  366. */
  367. public function parse_header_string($resource, $header_line)
  368. {
  369. $headers = array();
  370. if (preg_match_all('/(\w[^\s:]*):[ ]*([^\r\n]*(?:\r\n[ \t][^\r\n]*)*)/', $header_line, $matches))
  371. {
  372. foreach ($matches[0] as $key => $value)
  373. {
  374. $this->offsetSet($matches[1][$key], $matches[2][$key], FALSE);
  375. }
  376. }
  377. return strlen($header_line);
  378. }
  379. /**
  380. * Returns the accept quality of a submitted mime type based on the
  381. * request `Accept:` header. If the `$explicit` argument is `TRUE`,
  382. * only precise matches will be returned, excluding all wildcard (`*`)
  383. * directives.
  384. *
  385. * // Accept: application/xml; application/json; q=.5; text/html; q=.2, text/*
  386. * // Accept quality for application/json
  387. *
  388. * // $quality = 0.5
  389. * $quality = $request->headers()->accepts_at_quality('application/json');
  390. *
  391. * // $quality_explicit = FALSE
  392. * $quality_explicit = $request->headers()->accepts_at_quality('text/plain', TRUE);
  393. *
  394. * @param string type
  395. * @param boolean explicit check, excludes `*`
  396. * @return mixed
  397. * @since 3.2.0
  398. */
  399. public function accepts_at_quality($type, $explicit = FALSE)
  400. {
  401. // Parse Accept header if required
  402. if ($this->_accept_content === NULL)
  403. {
  404. if ($this->offsetExists('Accept'))
  405. {
  406. $accept = $this->offsetGet('Accept');
  407. }
  408. else
  409. {
  410. $accept = '*/*';
  411. }
  412. $this->_accept_content = HTTP_Header::parse_accept_header($accept);
  413. }
  414. // If not a real mime, try and find it in config
  415. if (strpos($type, '/') === FALSE)
  416. {
  417. $mime = Kohana::$config->load('mimes.'.$type);
  418. if ($mime === NULL)
  419. return FALSE;
  420. $quality = FALSE;
  421. foreach ($mime as $_type)
  422. {
  423. $quality_check = $this->accepts_at_quality($_type, $explicit);
  424. $quality = ($quality_check > $quality) ? $quality_check : $quality;
  425. }
  426. return $quality;
  427. }
  428. $parts = explode('/', $type, 2);
  429. if (isset($this->_accept_content[$parts[0]][$parts[1]]))
  430. {
  431. return $this->_accept_content[$parts[0]][$parts[1]];
  432. }
  433. elseif ($explicit === TRUE)
  434. {
  435. return FALSE;
  436. }
  437. else
  438. {
  439. if (isset($this->_accept_content[$parts[0]]['*']))
  440. {
  441. return $this->_accept_content[$parts[0]]['*'];
  442. }
  443. elseif (isset($this->_accept_content['*']['*']))
  444. {
  445. return $this->_accept_content['*']['*'];
  446. }
  447. else
  448. {
  449. return FALSE;
  450. }
  451. }
  452. }
  453. /**
  454. * Returns the preferred response content type based on the accept header
  455. * quality settings. If items have the same quality value, the first item
  456. * found in the array supplied as `$types` will be returned.
  457. *
  458. * // Get the preferred acceptable content type
  459. * // Accept: text/html, application/json; q=.8, text/*
  460. * $result = $header->preferred_accept(array(
  461. * 'text/html'
  462. * 'text/rtf',
  463. * 'application/json'
  464. * )); // $result = 'application/json'
  465. *
  466. * $result = $header->preferred_accept(array(
  467. * 'text/rtf',
  468. * 'application/xml'
  469. * ), TRUE); // $result = FALSE (none matched explicitly)
  470. *
  471. *
  472. * @param array the content types to examine
  473. * @param boolean only allow explicit references, no wildcards
  474. * @return string name of the preferred content type
  475. * @since 3.2.0
  476. */
  477. public function preferred_accept(array $types, $explicit = FALSE)
  478. {
  479. $preferred = FALSE;
  480. $ceiling = 0;
  481. foreach ($types as $type)
  482. {
  483. $quality = $this->accepts_at_quality($type, $explicit);
  484. if ($quality > $ceiling)
  485. {
  486. $preferred = $type;
  487. $ceiling = $quality;
  488. }
  489. }
  490. return $preferred;
  491. }
  492. /**
  493. * Returns the quality of the supplied `$charset` argument. This method
  494. * will automatically parse the `Accept-Charset` header if present and
  495. * return the associated resolved quality value.
  496. *
  497. * // Accept-Charset: utf-8, utf-16; q=.8, iso-8859-1; q=.5
  498. * $quality = $header->accepts_charset_at_quality('utf-8');
  499. * // $quality = (float) 1
  500. *
  501. * @param string charset to examine
  502. * @return float the quality of the charset
  503. * @since 3.2.0
  504. */
  505. public function accepts_charset_at_quality($charset)
  506. {
  507. if ($this->_accept_charset === NULL)
  508. {
  509. if ($this->offsetExists('Accept-Charset'))
  510. {
  511. $charset_header = strtolower($this->offsetGet('Accept-Charset'));
  512. $this->_accept_charset = HTTP_Header::parse_charset_header($charset_header);
  513. }
  514. else
  515. {
  516. $this->_accept_charset = HTTP_Header::parse_charset_header(NULL);
  517. }
  518. }
  519. $charset = strtolower($charset);
  520. if (isset($this->_accept_charset[$charset]))
  521. {
  522. return $this->_accept_charset[$charset];
  523. }
  524. elseif (isset($this->_accept_charset['*']))
  525. {
  526. return $this->_accept_charset['*'];
  527. }
  528. elseif ($charset === 'iso-8859-1')
  529. {
  530. return (float) 1;
  531. }
  532. return (float) 0;
  533. }
  534. /**
  535. * Returns the preferred charset from the supplied array `$charsets` based
  536. * on the `Accept-Charset` header directive.
  537. *
  538. * // Accept-Charset: utf-8, utf-16; q=.8, iso-8859-1; q=.5
  539. * $charset = $header->preferred_charset(array(
  540. * 'utf-10', 'ascii', 'utf-16', 'utf-8'
  541. * )); // $charset = 'utf-8'
  542. *
  543. * @param array charsets to test
  544. * @return mixed preferred charset or `FALSE`
  545. * @since 3.2.0
  546. */
  547. public function preferred_charset(array $charsets)
  548. {
  549. $preferred = FALSE;
  550. $ceiling = 0;
  551. foreach ($charsets as $charset)
  552. {
  553. $quality = $this->accepts_charset_at_quality($charset);
  554. if ($quality > $ceiling)
  555. {
  556. $preferred = $charset;
  557. $ceiling = $quality;
  558. }
  559. }
  560. return $preferred;
  561. }
  562. /**
  563. * Returns the quality of the `$encoding` type passed to it. Encoding
  564. * is usually compression such as `gzip`, but could be some other
  565. * message encoding algorithm. This method allows explicit checks to be
  566. * done ignoring wildcards.
  567. *
  568. * // Accept-Encoding: compress, gzip, *; q=.5
  569. * $encoding = $header->accepts_encoding_at_quality('gzip');
  570. * // $encoding = (float) 1.0s
  571. *
  572. * @param string encoding type to interrogate
  573. * @param boolean explicit check, ignoring wildcards and `identity`
  574. * @return float
  575. * @since 3.2.0
  576. */
  577. public function accepts_encoding_at_quality($encoding, $explicit = FALSE)
  578. {
  579. if ($this->_accept_encoding === NULL)
  580. {
  581. if ($this->offsetExists('Accept-Encoding'))
  582. {
  583. $encoding_header = $this->offsetGet('Accept-Encoding');
  584. }
  585. else
  586. {
  587. $encoding_header = NULL;
  588. }
  589. $this->_accept_encoding = HTTP_Header::parse_encoding_header($encoding_header);
  590. }
  591. // Normalize the encoding
  592. $encoding = strtolower($encoding);
  593. if (isset($this->_accept_encoding[$encoding]))
  594. {
  595. return $this->_accept_encoding[$encoding];
  596. }
  597. if ($explicit === FALSE)
  598. {
  599. if (isset($this->_accept_encoding['*']))
  600. {
  601. return $this->_accept_encoding['*'];
  602. }
  603. elseif ($encoding === 'identity')
  604. {
  605. return (float) HTTP_Header::DEFAULT_QUALITY;
  606. }
  607. }
  608. return (float) 0;
  609. }
  610. /**
  611. * Returns the preferred message encoding type based on quality, and can
  612. * optionally ignore wildcard references. If two or more encodings have the
  613. * same quality, the first listed in `$encodings` will be returned.
  614. *
  615. * // Accept-Encoding: compress, gzip, *; q.5
  616. * $encoding = $header->preferred_encoding(array(
  617. * 'gzip', 'bzip', 'blowfish'
  618. * ));
  619. * // $encoding = 'gzip';
  620. *
  621. * @param array encodings to test against
  622. * @param boolean explicit check, if `TRUE` wildcards are excluded
  623. * @return mixed
  624. * @since 3.2.0
  625. */
  626. public function preferred_encoding(array $encodings, $explicit = FALSE)
  627. {
  628. $ceiling = 0;
  629. $preferred = FALSE;
  630. foreach ($encodings as $encoding)
  631. {
  632. $quality = $this->accepts_encoding_at_quality($encoding, $explicit);
  633. if ($quality > $ceiling)
  634. {
  635. $ceiling = $quality;
  636. $preferred = $encoding;
  637. }
  638. }
  639. return $preferred;
  640. }
  641. /**
  642. * Returns the quality of `$language` supplied, optionally ignoring
  643. * wildcards if `$explicit` is set to a non-`FALSE` value. If the quality
  644. * is not found, `0.0` is returned.
  645. *
  646. * // Accept-Language: en-us, en-gb; q=.7, en; q=.5
  647. * $lang = $header->accepts_language_at_quality('en-gb');
  648. * // $lang = (float) 0.7
  649. *
  650. * $lang2 = $header->accepts_language_at_quality('en-au');
  651. * // $lang2 = (float) 0.5
  652. *
  653. * $lang3 = $header->accepts_language_at_quality('en-au', TRUE);
  654. * // $lang3 = (float) 0.0
  655. *
  656. * @param string language to interrogate
  657. * @param boolean explicit interrogation, `TRUE` ignores wildcards
  658. * @return float
  659. * @since 3.2.0
  660. */
  661. public function accepts_language_at_quality($language, $explicit = FALSE)
  662. {
  663. if ($this->_accept_language === NULL)
  664. {
  665. if ($this->offsetExists('Accept-Language'))
  666. {
  667. $language_header = $this->offsetGet('Accept-Language');
  668. }
  669. else
  670. {
  671. $language_header = NULL;
  672. }
  673. $this->_accept_language = HTTP_Header::parse_language_header($language_header);
  674. }
  675. // Normalize the language
  676. $language_parts = explode('-', strtolower($language), 2);
  677. if (isset($this->_accept_language[$language_parts[0]]))
  678. {
  679. if (isset($language_parts[1]))
  680. {
  681. if (isset($this->_accept_language[$language_parts[0]][$language_parts[1]]))
  682. {
  683. return $this->_accept_language[$language_parts[0]][$language_parts[1]];
  684. }
  685. elseif ($explicit === FALSE AND isset($this->_accept_language[$language_parts[0]]['*']))
  686. {
  687. return $this->_accept_language[$language_parts[0]]['*'];
  688. }
  689. }
  690. elseif (isset($this->_accept_language[$language_parts[0]]['*']))
  691. {
  692. return $this->_accept_language[$language_parts[0]]['*'];
  693. }
  694. }
  695. if ($explicit === FALSE AND isset($this->_accept_language['*']))
  696. {
  697. return $this->_accept_language['*'];
  698. }
  699. return (float) 0;
  700. }
  701. /**
  702. * Returns the preferred language from the supplied array `$languages` based
  703. * on the `Accept-Language` header directive.
  704. *
  705. * // Accept-Language: en-us, en-gb; q=.7, en; q=.5
  706. * $lang = $header->preferred_language(array(
  707. * 'en-gb', 'en-au', 'fr', 'es'
  708. * )); // $lang = 'en-gb'
  709. *
  710. * @param array languages
  711. * @param boolean explicit
  712. * @return mixed
  713. * @since 3.2.0
  714. */
  715. public function preferred_language(array $languages, $explicit = FALSE)
  716. {
  717. $ceiling = 0;
  718. $preferred = FALSE;
  719. foreach ($languages as $language)
  720. {
  721. $quality = $this->accepts_language_at_quality($language, $explicit);
  722. if ($quality > $ceiling)
  723. {
  724. $ceiling = $quality;
  725. $preferred = $language;
  726. }
  727. }
  728. return $preferred;
  729. }
  730. /**
  731. * Sends headers to the php processor, or supplied `$callback` argument.
  732. * This method formats the headers correctly for output, re-instating their
  733. * capitalization for transmission.
  734. *
  735. * [!!] if you supply a custom header handler via `$callback`, it is
  736. * recommended that `$response` is returned
  737. *
  738. * @param HTTP_Response header to send
  739. * @param boolean replace existing value
  740. * @param callback optional callback to replace PHP header function
  741. * @return mixed
  742. * @since 3.2.0
  743. */
  744. public function send_headers(HTTP_Response $response = NULL, $replace = FALSE, $callback = NULL)
  745. {
  746. if ($response === NULL)
  747. {
  748. // Default to the initial request message
  749. $response = Request::initial()->response();
  750. }
  751. $protocol = $response->protocol();
  752. $status = $response->status();
  753. // Create the response header
  754. $processed_headers = array($protocol.' '.$status.' '.Response::$messages[$status]);
  755. // Get the headers array
  756. $headers = $response->headers()->getArrayCopy();
  757. foreach ($headers as $header => $value)
  758. {
  759. if (is_array($value))
  760. {
  761. $value = implode(', ', $value);
  762. }
  763. $processed_headers[] = Text::ucfirst($header).': '.$value;
  764. }
  765. if ( ! isset($headers['content-type']))
  766. {
  767. $processed_headers[] = 'Content-Type: '.Kohana::$content_type.
  768. '; charset='.Kohana::$charset;
  769. }
  770. if (Kohana::$expose AND ! isset($headers['x-powered-by']))
  771. {
  772. $processed_headers[] = 'X-Powered-By: Kohana Framework '.
  773. Kohana::VERSION.' ('.Kohana::CODENAME.')';
  774. }
  775. // Get the cookies and apply
  776. if ($cookies = $response->cookie())
  777. {
  778. $processed_headers['Set-Cookie'] = $cookies;
  779. }
  780. if (is_callable($callback))
  781. {
  782. // Use the callback method to set header
  783. return call_user_func($callback, $response, $processed_headers, $replace);
  784. }
  785. else
  786. {
  787. $this->_send_headers_to_php($processed_headers, $replace);
  788. return $response;
  789. }
  790. }
  791. /**
  792. * Sends the supplied headers to the PHP output buffer. If cookies
  793. * are included in the message they will be handled appropriately.
  794. *
  795. * @param array headers to send to php
  796. * @param boolean replace existing headers
  797. * @return self
  798. * @since 3.2.0
  799. */
  800. protected function _send_headers_to_php(array $headers, $replace)
  801. {
  802. // If the headers have been sent, get out
  803. if (headers_sent())
  804. return $this;
  805. foreach ($headers as $key => $line)
  806. {
  807. if ($key == 'Set-Cookie' AND is_array($line))
  808. {
  809. // Send cookies
  810. foreach ($line as $name => $value)
  811. {
  812. Cookie::set($name, $value['value'], $value['expiration']);
  813. }
  814. continue;
  815. }
  816. header($line, $replace);
  817. }
  818. return $this;
  819. }
  820. } // End Kohana_HTTP_Header