/core/src/main/php/scriptlet/LocaleNegotiator.class.php

https://github.com/oanas/xp-framework · PHP · 158 lines · 55 code · 8 blank · 95 comment · 9 complexity · d58f0b891a43376128ed534bab5b0839 MD5 · raw file

  1. <?php
  2. /* This class is part of the XP framework
  3. *
  4. * $Id$
  5. */
  6. uses('util.Locale');
  7. /**
  8. * Class to aid website internationalization based on the
  9. * Accept-Language and Accept-Charset headers.
  10. *
  11. * Basic usage example:
  12. * <code>
  13. * uses('scriptlet.LocaleNegotiator');
  14. *
  15. * $negotiator= new LocaleNegotiator(
  16. * 'de-at, de;q=0.75, en-us;q=0.50, en;q=0.25',
  17. * 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'
  18. * );
  19. * var_dump(
  20. * $negotiator,
  21. * $negotiator->getLocale(
  22. * $supported= array('de_DE', 'en_US'),
  23. * $default= 'de_DE'
  24. * ),
  25. * $negotiator->getCharset(
  26. * $supported= array('iso-8859-1', 'utf-8'),
  27. * $default= 'iso-8859-1'
  28. * )
  29. * );
  30. * </code>
  31. *
  32. * Within a scriptlet, use the getHeader() method of the request
  33. * object to retrieve the values of the Accept-Language / Accept-Charset
  34. * headers and the setHeader() method of the response object to
  35. * indicate language negotation has took place.
  36. *
  37. * Abbreviated example:
  38. * <code>
  39. * function doGet($req, $res) {
  40. * $negotiator= new LocaleNegotiator(
  41. * $req->getHeader('Accept-Language'),
  42. * $req->getHeader('Accept-Charset')
  43. * );
  44. * $locale= $negotiator->getLocale(array('de_DE', 'en_US'), 'de_DE');
  45. *
  46. * // [... Do whatever needs to be done for this language ...]
  47. *
  48. * $res->setHeader('Content-Language', $locale->getLanguage());
  49. * $res->setHeader('Vary', 'Accept-Language');
  50. * }
  51. * </code>
  52. *
  53. * @test xp://net.xp_framework.unittest.scriptlet.LocaleNegotiatorTest
  54. * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
  55. * @purpose Negotiate locales
  56. */
  57. class LocaleNegotiator extends Object {
  58. public
  59. $languages = array(),
  60. $charsets = array();
  61. /**
  62. * Constructor
  63. *
  64. * @param string languages
  65. * @param string charsets
  66. */
  67. public function __construct($languages, $charsets= '') {
  68. $this->languages= $this->_parse($languages);
  69. $this->charsets= $this->_parse($charsets);
  70. }
  71. /**
  72. * Retrieve locale
  73. *
  74. * @param string[] supported
  75. * @param string default default NULL
  76. * @return util.Locale
  77. */
  78. public function getLocale(array $supported, $default= NULL) {
  79. $chosen= NULL;
  80. foreach ($this->languages as $lang => $q) {
  81. if (
  82. ($chosen= $this->_find($lang, $supported)) ||
  83. ($chosen= $this->_find($lang, $supported, 2))
  84. ) break;
  85. }
  86. return new util·Locale($chosen ? $chosen : $default);
  87. }
  88. /**
  89. * Retrieve charset
  90. *
  91. * @param string[] supported
  92. * @param string default default NULL
  93. * @return string charset or default if none matches
  94. */
  95. public function getCharset(array $supported, $default= NULL) {
  96. $chosen= NULL;
  97. foreach ($this->charsets as $charset => $q) {
  98. if ($chosen= $this->_find($charset, $supported)) {
  99. break;
  100. } else if ('*' === $charset) {
  101. $chosen= $supported[0];
  102. break;
  103. }
  104. }
  105. return $chosen ? $chosen : $default;
  106. }
  107. /**
  108. * Private helper that parses a string of the following format:
  109. *
  110. * <pre>
  111. * Accept-Language: en,de;q=0.5
  112. * Accept-Language: en-UK;q=0.7, en-US;q=0.6, no;q=1.0, dk;q=0.8
  113. * Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
  114. * </pre>
  115. *
  116. * @param string str
  117. * @return array values
  118. */
  119. protected function _parse($str) {
  120. $values= array();
  121. if ($t= strtok($str, ', ')) do {
  122. if (FALSE === ($p= strpos($t, ';'))) {
  123. $value= strtr($t, '-', '_');
  124. $q= 1.0;
  125. } else {
  126. $value= strtr(substr($t, 0, $p), '-', '_');
  127. $q= (float)substr($t, $p + 3); // skip ";q="
  128. }
  129. $values[strtolower($value)]= $q;
  130. } while ($t= strtok(', '));
  131. asort($values, SORT_NUMERIC);
  132. return array_reverse($values);
  133. }
  134. /**
  135. * Private helper that searches an array using strncasecmp as comparator
  136. *
  137. * @see php://strncasecmp
  138. * @param string value
  139. * @param string[] array
  140. * @param int len default -1
  141. * @return string found or NULL to indicate it wasn't found
  142. */
  143. protected function _find($value, $array, $len= -1) {
  144. foreach ($array as $cmp) {
  145. if (0 === strncasecmp($value, $cmp, -1 === $len ? strlen($cmp) : $len)) return $cmp;
  146. }
  147. return NULL;
  148. }
  149. }
  150. ?>