| 1 | <?php |
|---|
| 2 | |
|---|
| 3 | /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ |
|---|
| 4 | |
|---|
| 5 | // +-------------------------------------------------------------------+ |
|---|
| 6 | // | WiFiDog Authentication Server | |
|---|
| 7 | // | ============================= | |
|---|
| 8 | // | | |
|---|
| 9 | // | The WiFiDog Authentication Server is part of the WiFiDog captive | |
|---|
| 10 | // | portal suite. | |
|---|
| 11 | // +-------------------------------------------------------------------+ |
|---|
| 12 | // | PHP version 5 required. | |
|---|
| 13 | // +-------------------------------------------------------------------+ |
|---|
| 14 | // | Homepage: http://www.wifidog.org/ | |
|---|
| 15 | // | Source Forge: http://sourceforge.net/projects/wifidog/ | |
|---|
| 16 | // +-------------------------------------------------------------------+ |
|---|
| 17 | // | This program is free software; you can redistribute it and/or | |
|---|
| 18 | // | modify it under the terms of the GNU General Public License as | |
|---|
| 19 | // | published by the Free Software Foundation; either version 2 of | |
|---|
| 20 | // | the License, or (at your option) any later version. | |
|---|
| 21 | // | | |
|---|
| 22 | // | This program is distributed in the hope that it will be useful, | |
|---|
| 23 | // | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|---|
| 24 | // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|---|
| 25 | // | GNU General Public License for more details. | |
|---|
| 26 | // | | |
|---|
| 27 | // | You should have received a copy of the GNU General Public License | |
|---|
| 28 | // | along with this program; if not, contact: | |
|---|
| 29 | // | | |
|---|
| 30 | // | Free Software Foundation Voice: +1-617-542-5942 | |
|---|
| 31 | // | 59 Temple Place - Suite 330 Fax: +1-617-542-2652 | |
|---|
| 32 | // | Boston, MA 02111-1307, USA gnu@gnu.org | |
|---|
| 33 | // | | |
|---|
| 34 | // +-------------------------------------------------------------------+ |
|---|
| 35 | |
|---|
| 36 | /** |
|---|
| 37 | * @package WiFiDogAuthServer |
|---|
| 38 | * @author Benoit Grégoire <bock@step.polymtl.ca> |
|---|
| 39 | * @copyright 2004-2006 Benoit Grégoire, Technologies Coeus inc. |
|---|
| 40 | * @version Subversion $Id$ |
|---|
| 41 | * @link http://www.wifidog.org/ |
|---|
| 42 | */ |
|---|
| 43 | |
|---|
| 44 | error_reporting(E_ALL); |
|---|
| 45 | |
|---|
| 46 | // Detect Gettext support. |
|---|
| 47 | if (!function_exists('gettext')) { |
|---|
| 48 | /** |
|---|
| 49 | * Define Gettext has NOT been found on the system |
|---|
| 50 | */ |
|---|
| 51 | define('GETTEXT_AVAILABLE', false); |
|---|
| 52 | |
|---|
| 53 | // Redefine the gettext functions if gettext isn't installed. |
|---|
| 54 | |
|---|
| 55 | function gettext($string) { |
|---|
| 56 | return $string; |
|---|
| 57 | } |
|---|
| 58 | |
|---|
| 59 | function _($string) { |
|---|
| 60 | return $string; |
|---|
| 61 | } |
|---|
| 62 | } else { |
|---|
| 63 | /** |
|---|
| 64 | * Define Gettext has been found on the system |
|---|
| 65 | * |
|---|
| 66 | * @ignore |
|---|
| 67 | */ |
|---|
| 68 | define('GETTEXT_AVAILABLE', true); |
|---|
| 69 | } |
|---|
| 70 | require_once('classes/Session.php'); |
|---|
| 71 | /** |
|---|
| 72 | * Designates a human language, possibly localized ie fr_CA |
|---|
| 73 | * |
|---|
| 74 | * @package WiFiDogAuthServer |
|---|
| 75 | * @author Benoit Grégoire <bock@step.polymtl.ca> |
|---|
| 76 | * @copyright 2004-2006 Benoit Grégoire, Technologies Coeus inc. |
|---|
| 77 | */ |
|---|
| 78 | class Locale { |
|---|
| 79 | // Private attributes |
|---|
| 80 | private $mLang; |
|---|
| 81 | private $mCountry; |
|---|
| 82 | |
|---|
| 83 | /** |
|---|
| 84 | * Constructor |
|---|
| 85 | * @param string $p_locale Locale in POSIX format (excluding charset), such |
|---|
| 86 | * as fr ou fr_CA: "xx(x)_YY_(n*z)". Both '_' and '-' are acceptable as |
|---|
| 87 | * separator. |
|---|
| 88 | */ |
|---|
| 89 | function __construct($p_locale) { |
|---|
| 90 | $matches = self :: decomposeLocaleId($p_locale); |
|---|
| 91 | $locale = $matches[1]; |
|---|
| 92 | $this->mLang = $matches[1]; |
|---|
| 93 | |
|---|
| 94 | if ($this->mLang == null) { |
|---|
| 95 | throw new Exception(_("Locale(): Could not a locale matching $locale"), EXCEPTION_CREATE_OBJECT_FAILED); |
|---|
| 96 | } |
|---|
| 97 | |
|---|
| 98 | if (empty ($matches[2])) { |
|---|
| 99 | $this->mCountry = null; |
|---|
| 100 | } else { |
|---|
| 101 | $locale .= '_'.$matches[2]; |
|---|
| 102 | $this->mCountry = $matches[2]; |
|---|
| 103 | } |
|---|
| 104 | |
|---|
| 105 | if (empty ($matches[3])) { |
|---|
| 106 | // TODO: Optionally support subcode ? |
|---|
| 107 | } else { |
|---|
| 108 | // region |
|---|
| 109 | $locale .= '_'.$matches[3]; |
|---|
| 110 | // $this->mRegion = $matches[3]; |
|---|
| 111 | } |
|---|
| 112 | |
|---|
| 113 | $this->mId = $locale; |
|---|
| 114 | } |
|---|
| 115 | |
|---|
| 116 | /** |
|---|
| 117 | * Get the Locale object |
|---|
| 118 | * @param string $content_id The content id |
|---|
| 119 | * @return The Content object, or null if there was an error (an exception is also thrown) |
|---|
| 120 | */ |
|---|
| 121 | static function getObject($locale_id) { |
|---|
| 122 | return new self($locale_id); |
|---|
| 123 | } |
|---|
| 124 | |
|---|
| 125 | public static function getCurrentLocale() { |
|---|
| 126 | $session = Session::getObject(); |
|---|
| 127 | $AVAIL_LOCALE_ARRAY = LocaleList::getAvailableLanguageArray(); |
|---|
| 128 | $object = null; |
|---|
| 129 | $locale_id = $session->get(SESS_LANGUAGE_VAR); |
|---|
| 130 | //echo sprintf("Debug in /classes/Locale.php getCurrentLocale(): session->get(SESS_LANGUAGE_VAR)=%s", $session->get(SESS_LANGUAGE_VAR))."<br/>"; |
|---|
| 131 | |
|---|
| 132 | /* Try to guess the lang */ |
|---|
| 133 | if (empty($locale_id) || empty($AVAIL_LOCALE_ARRAY[$locale_id])) { |
|---|
| 134 | $locale_id = self :: getBestLanguage(); |
|---|
| 135 | } |
|---|
| 136 | |
|---|
| 137 | /* If we still don't have it, fill in default */ |
|---|
| 138 | if (empty ($locale_id)) { |
|---|
| 139 | $object = self :: getObject(DEFAULT_LANG); |
|---|
| 140 | } else { |
|---|
| 141 | $object = self :: getObject($locale_id); |
|---|
| 142 | } |
|---|
| 143 | |
|---|
| 144 | return $object; |
|---|
| 145 | } |
|---|
| 146 | |
|---|
| 147 | /** |
|---|
| 148 | * Try to find best language according to HTTP_ACCEPT_LANGUAGE passed |
|---|
| 149 | * by the browser. |
|---|
| 150 | * @return string Best language from list of available languages, otherwise |
|---|
| 151 | * empty. |
|---|
| 152 | */ |
|---|
| 153 | public static function getBestLanguage($availableLanguages=false) { |
|---|
| 154 | $AVAIL_LOCALE_ARRAY = LocaleList::getAvailableLanguageArray(); |
|---|
| 155 | if (empty($availableLanguages)) $availableLanguages=$AVAIL_LOCALE_ARRAY; |
|---|
| 156 | |
|---|
| 157 | // the HTTP_ACCEPT_LANGUAGE server string comes from the browser in the |
|---|
| 158 | // Accept-Language: header. It is a list of browser language preferences separated by commas. |
|---|
| 159 | // the language preference is a 2 part field separated by semicolons. the first part is the language. |
|---|
| 160 | // the languages are the iso codes. the language may have a hyphen and a country code appended to |
|---|
| 161 | // it, like "fr-CA" or "en-gb". |
|---|
| 162 | // the second part of the language preference may be missing. if it's not there it's assumed to be |
|---|
| 163 | // "q=1.0". This part gives the preference rating and is an float between 0.0 and 1.0. 0.8 corresponds |
|---|
| 164 | // to 80%. |
|---|
| 165 | |
|---|
| 166 | // $AVAIL_LOCALE_ARRAY, set in config.php. this is a list of available locales. |
|---|
| 167 | // The format is different from that of HTTP_ACCEPT_LANGUAGE. It is: |
|---|
| 168 | // LANGUAGE [ _COUNTRY [ .ENCODING ] ] |
|---|
| 169 | // where LANGUAGE and COUNTRY are 2 letter codes (usually), and encoding is something like iso88591 or utf8. |
|---|
| 170 | // for example: |
|---|
| 171 | // english or en or en_CA or en_CA.utf8 or en_CA.iso88591 or en_US.iso885915 |
|---|
| 172 | // french or fr or fr_CA or fr_CA.utf8 or fr_CA.iso88591 |
|---|
| 173 | |
|---|
| 174 | $browser_preferences = array(); |
|---|
| 175 | foreach(explode(',', empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? DEFAULT_LANG : $_SERVER['HTTP_ACCEPT_LANGUAGE']) as $lang) { |
|---|
| 176 | //echo $lang."\n"; |
|---|
| 177 | if (preg_match('/^\s*([a-z_-]+).*?(?:;\s*q=([0-9.]+))?/i', $lang.';q=1.0', $split)) { |
|---|
| 178 | $browser_preferences[sprintf('%f%d', $split[2], rand(0,9999))] = strtolower($split[1]); |
|---|
| 179 | } |
|---|
| 180 | } |
|---|
| 181 | |
|---|
| 182 | // sort preferences by key in reverse order, from high to low |
|---|
| 183 | // best is first, worst is last |
|---|
| 184 | krsort($browser_preferences); |
|---|
| 185 | |
|---|
| 186 | foreach($browser_preferences as $score => $language_spec) { |
|---|
| 187 | //echo "$score => $language_spec\n"; |
|---|
| 188 | @list($prefered_language, $prefered_country) = preg_split('/[-_]/', $language_spec); |
|---|
| 189 | // better to use explode('-', $language_spec) except that $language_spec may come from |
|---|
| 190 | // config.php DEFAULT_LANG, which may be given as a locale, with an underscore |
|---|
| 191 | // between the language and country. |
|---|
| 192 | |
|---|
| 193 | $prefered_locale = empty($prefered_country) ? $prefered_language : |
|---|
| 194 | $prefered_language . '_' . strtoupper($prefered_country); |
|---|
| 195 | |
|---|
| 196 | // if the browser's preference is matched exactly in $availableLanguages, great! |
|---|
| 197 | if (!empty($availableLanguages[$prefered_locale])) return $prefered_locale; |
|---|
| 198 | |
|---|
| 199 | if (empty($prefered_country)) { |
|---|
| 200 | // browser doesn't care what country |
|---|
| 201 | // try to find a match in $availableLanguages ignoring the country |
|---|
| 202 | foreach($availableLanguages as $my_locale => $language_name) { |
|---|
| 203 | @list($my_language, $my_country, $my_encoding) = preg_split('/[_.]/', $my_locale); |
|---|
| 204 | if ($my_language === $prefered_language) return $my_locale; |
|---|
| 205 | } |
|---|
| 206 | } |
|---|
| 207 | } |
|---|
| 208 | |
|---|
| 209 | return false; |
|---|
| 210 | |
|---|
| 211 | // return array_shift(array_merge(array_intersect($browser_preferences, $availableLanguages), $availableLanguages)); |
|---|
| 212 | } |
|---|
| 213 | |
|---|
| 214 | /** Initialise the system locale (gettext, setlocale, etc.) |
|---|
| 215 | * @return boolean true on success, false on failure. |
|---|
| 216 | */ |
|---|
| 217 | public static function setCurrentLocale($locale) { |
|---|
| 218 | $session = Session::getObject(); |
|---|
| 219 | $AVAIL_LOCALE_ARRAY = LocaleList::getAvailableLanguageArray(); |
|---|
| 220 | $retval = false; |
|---|
| 221 | |
|---|
| 222 | // Get new locale ID, assume default if null |
|---|
| 223 | if ($locale != null) { |
|---|
| 224 | $locale_id = $locale->getId(); |
|---|
| 225 | $retval = true; |
|---|
| 226 | $q = "parameter"; |
|---|
| 227 | } else { |
|---|
| 228 | $locale_id = DEFAULT_LANG; |
|---|
| 229 | $retval = false; |
|---|
| 230 | $q = "default"; |
|---|
| 231 | } |
|---|
| 232 | //pretty_print_r($locale); |
|---|
| 233 | //echo sprintf("Debug in /classes/Locale.php setCurentLocale(): locale_id=%s", $locale_id)."<br/>"; |
|---|
| 234 | |
|---|
| 235 | if (GETTEXT_AVAILABLE) { |
|---|
| 236 | $lang_only_locale_id = substr ($locale_id, 0 , 2); |
|---|
| 237 | if(!isset($AVAIL_LOCALE_ARRAY[$locale_id]) && !isset($AVAIL_LOCALE_ARRAY[$lang_only_locale_id])) |
|---|
| 238 | { |
|---|
| 239 | echo sprintf("Warning in /classes/Locale.php setCurentLocale: Neither %s or %s are available in AVAIL_LOCALE_ARRAY", $locale_id, $lang_only_locale_id)."<br/>"; |
|---|
| 240 | } |
|---|
| 241 | // Try to set locale |
|---|
| 242 | $candidate_locale_array[] = str_ireplace('.UTF8', '', $locale_id).'.UTF-8'; |
|---|
| 243 | $candidate_locale_array[] = str_ireplace('.UTF8', '', $locale_id); |
|---|
| 244 | $candidate_locale_array[] = $lang_only_locale_id.'.UTF-8'; |
|---|
| 245 | $candidate_locale_array[] = $lang_only_locale_id; |
|---|
| 246 | |
|---|
| 247 | |
|---|
| 248 | $current_locale = setlocale(LC_ALL, $candidate_locale_array); |
|---|
| 249 | //echo sprintf("Warning in /classes/Locale.php setCurentLocale: Unable to setlocale() to %s: %s. I tried %s, %s, %s, %s, and got return value: %s, current locale is: %s",$q, $locale_id, $candidate_locale_array[0], $candidate_locale_array[1], $candidate_locale_array[2], $candidate_locale_array[3], $current_locale, setlocale(LC_ALL, 0))."<br/>"; |
|---|
| 250 | |
|---|
| 251 | // Test it against current PHP locale |
|---|
| 252 | if (substr ($current_locale, 0 , 2) != $lang_only_locale_id) { |
|---|
| 253 | echo sprintf("Warning in /classes/Locale.php setCurentLocale: Unable to setlocale() to %s: %s. I tried %s, %s, %s, %s, and got return value: %s, current locale is: %s",$q, $locale_id, $candidate_locale_array[0], $candidate_locale_array[1], $candidate_locale_array[2], $candidate_locale_array[3], $current_locale, setlocale(LC_ALL, 0))."<br/>"; |
|---|
| 254 | $retval = false; |
|---|
| 255 | } else { |
|---|
| 256 | bindtextdomain('messages', WIFIDOG_ABS_FILE_PATH . 'locale'); |
|---|
| 257 | bind_textdomain_codeset('messages', 'UTF-8'); |
|---|
| 258 | textDomain('messages'); |
|---|
| 259 | |
|---|
| 260 | putenv("LC_ALL=".$current_locale); |
|---|
| 261 | putenv("LANGUAGE=".$current_locale); |
|---|
| 262 | $retval = true; |
|---|
| 263 | } |
|---|
| 264 | } |
|---|
| 265 | return $retval; |
|---|
| 266 | } |
|---|
| 267 | |
|---|
| 268 | /** |
|---|
| 269 | * Example: 'fr_CA_montreal' will give |
|---|
| 270 | * $matches[0]=fr_CA_montreal |
|---|
| 271 | * $matches[1]=fr |
|---|
| 272 | * $matches[2]=CA |
|---|
| 273 | * $matches[3]=montreal |
|---|
| 274 | * Note: Off course, matches 2 and 3 could be empty if the information |
|---|
| 275 | * wasn't present. |
|---|
| 276 | */ |
|---|
| 277 | public static function decomposeLocaleId($locale_id) { |
|---|
| 278 | // Init values |
|---|
| 279 | $_matches = ""; |
|---|
| 280 | |
|---|
| 281 | $_regex = '/^([^-_]*)(?:[-_]([^-_]*))?(?:[-_]([^-_]*))?$/'; |
|---|
| 282 | $_match_retval = preg_match($_regex, $locale_id, $_matches); |
|---|
| 283 | return $_matches; |
|---|
| 284 | } |
|---|
| 285 | |
|---|
| 286 | /** |
|---|
| 287 | * Used by Langstring::GetString() (and other functions) to help select the |
|---|
| 288 | * best langstring_entry to display to the user. |
|---|
| 289 | * @return A sql fragment |
|---|
| 290 | */ |
|---|
| 291 | public static function getSqlCaseStringSelect($locale_id) { |
|---|
| 292 | $decomposed_locale = Locale :: decomposeLocaleId($locale_id); |
|---|
| 293 | |
|---|
| 294 | // The case will rate locales and choose the best one. |
|---|
| 295 | |
|---|
| 296 | $sql = " (CASE\n"; |
|---|
| 297 | // Look for part of the string or the full-length locale |
|---|
| 298 | $sql .= " WHEN locales_id='$decomposed_locale[0]' THEN 1\n"; |
|---|
| 299 | // Look for a string or the language part of the locale (match generic language first) |
|---|
| 300 | $sql .= " WHEN locales_id='{$decomposed_locale[1]}' THEN 2\n"; |
|---|
| 301 | // Look for the full string or any possible combination |
|---|
| 302 | $sql .= " WHEN locales_id LIKE '{$decomposed_locale[1]}%' THEN 3\n"; |
|---|
| 303 | |
|---|
| 304 | // Look for a string matching the language or the country of the user |
|---|
| 305 | if (!empty ($decomposed_locale[2])) { |
|---|
| 306 | $sql .= " WHEN locales_id LIKE '%{$decomposed_locale[2]}' THEN 4\n"; |
|---|
| 307 | } |
|---|
| 308 | |
|---|
| 309 | // Look for a string with no locale associated, it's more likely to be readable than a random string |
|---|
| 310 | $sql .= " WHEN locales_id IS NULL THEN 5\n"; |
|---|
| 311 | |
|---|
| 312 | $sql .= " ELSE 20 "; |
|---|
| 313 | $sql .= " END)\n"; |
|---|
| 314 | |
|---|
| 315 | return $sql; |
|---|
| 316 | } |
|---|
| 317 | |
|---|
| 318 | public function GetId() { |
|---|
| 319 | return $this->mId; |
|---|
| 320 | } |
|---|
| 321 | |
|---|
| 322 | /** |
|---|
| 323 | * Returns the locale in POSIX format, such as fr ou fr_CA "xx_YY". |
|---|
| 324 | * @return string Locale |
|---|
| 325 | */ |
|---|
| 326 | function GetLocale() { |
|---|
| 327 | $retval = $this->mLang->GetShort(); |
|---|
| 328 | |
|---|
| 329 | if ($this->mCountry != null) { |
|---|
| 330 | $retval .= '_'.$this->mCountry->GetShort(); |
|---|
| 331 | } |
|---|
| 332 | |
|---|
| 333 | return $retval; |
|---|
| 334 | } |
|---|
| 335 | |
|---|
| 336 | /** |
|---|
| 337 | * Returns the locale in W3C XML format (xs:language), such as fr ou fr-CA "xx-YY". |
|---|
| 338 | * @return string Locale |
|---|
| 339 | */ |
|---|
| 340 | function GetXMLLanguage() { |
|---|
| 341 | return $this->mId; |
|---|
| 342 | } |
|---|
| 343 | |
|---|
| 344 | /** |
|---|
| 345 | * Returns the language |
|---|
| 346 | * @return Lang. |
|---|
| 347 | */ |
|---|
| 348 | function GetLang() { |
|---|
| 349 | return $this->mLang; |
|---|
| 350 | } |
|---|
| 351 | |
|---|
| 352 | /** |
|---|
| 353 | * Returns the country, if available |
|---|
| 354 | * @return Country or null |
|---|
| 355 | */ |
|---|
| 356 | function GetCountry() { |
|---|
| 357 | return $this->mCountry; |
|---|
| 358 | } |
|---|
| 359 | |
|---|
| 360 | /** |
|---|
| 361 | * Returns a HTML formatted string for output to string (with an image) |
|---|
| 362 | */ |
|---|
| 363 | function GetString() { |
|---|
| 364 | // Init values. |
|---|
| 365 | $str = ""; |
|---|
| 366 | $resultats = ""; |
|---|
| 367 | $preflang_result = ""; |
|---|
| 368 | |
|---|
| 369 | $tmp_loc = $this->GetLocale(); |
|---|
| 370 | |
|---|
| 371 | // Look for the country in the database and match the preferred locale |
|---|
| 372 | $sql = "SELECT * FROM languages_iso_639_1, locales LEFT JOIN countries ON (locales.countries_id = countries.countries_id) "; |
|---|
| 373 | $sql .= "WHERE locales.languages_iso_639_1_id = languages_iso_639_1.iso639_1_id "; |
|---|
| 374 | $sql .= "AND locales.locales_id = '$tmp_loc' "; |
|---|
| 375 | $this->mBd->ExecuterSqlResUnique($sql, $resultats, FALSE); |
|---|
| 376 | |
|---|
| 377 | $tmp_pref_lang = $this->mSession->GetPrefLocale(); |
|---|
| 378 | $sql = "SELECT * FROM locales WHERE locales.locales_id = '$tmp_pref_lang'"; |
|---|
| 379 | $this->mBd->ExecuterSqlResUnique($sql, $preflang_result, FALSE); |
|---|
| 380 | |
|---|
| 381 | switch ($preflang_result['languages_iso_639_1_id']) { |
|---|
| 382 | case ('fr') : |
|---|
| 383 | $str .= "$resultats[french_name], $resultats[country_french_name]"; |
|---|
| 384 | break; |
|---|
| 385 | |
|---|
| 386 | case ('en') : |
|---|
| 387 | $str .= "$resultats[english_name], $resultats[country_english_name]"; |
|---|
| 388 | break; |
|---|
| 389 | |
|---|
| 390 | case ('es') : |
|---|
| 391 | $str .= "$resultats[spanish_name], $resultats[country_spanish_name]"; |
|---|
| 392 | break; |
|---|
| 393 | |
|---|
| 394 | case ('de') : |
|---|
| 395 | $str .= "$resultats[german_name], $resultats[country_german_name]"; |
|---|
| 396 | break; |
|---|
| 397 | |
|---|
| 398 | case ('ja') : |
|---|
| 399 | $str .= "$resultats[japanese_name], $resultats[country_japanese_name]"; |
|---|
| 400 | break; |
|---|
| 401 | |
|---|
| 402 | case ('pt') : |
|---|
| 403 | $str .= "$resultats[portuguese_name], $resultats[country_portuguese_name]"; |
|---|
| 404 | break; |
|---|
| 405 | |
|---|
| 406 | default : |
|---|
| 407 | $str .= "$resultats[french_name], $resultats[country_french_name]"; |
|---|
| 408 | break; |
|---|
| 409 | } |
|---|
| 410 | |
|---|
| 411 | return $str; |
|---|
| 412 | } |
|---|
| 413 | |
|---|
| 414 | } |
|---|
| 415 | |
|---|
| 416 | /* |
|---|
| 417 | * Local variables: |
|---|
| 418 | * tab-width: 4 |
|---|
| 419 | * c-basic-offset: 4 |
|---|
| 420 | * c-hanging-comment-ender-p: nil |
|---|
| 421 | * End: |
|---|
| 422 | */ |
|---|
| 423 | |
|---|
| 424 | |
|---|