SproutCMS

This is the code documentation for the SproutCMS project

source of /sprout/Helpers/Locales/LocaleInfo.php

  1. <?php
  2. /*
  3.  * Copyright (C) 2017 Karmabunny Pty Ltd.
  4.  *
  5.  * This file is a part of SproutCMS.
  6.  *
  7.  * SproutCMS is free software: you can redistribute it and/or modify it under the terms
  8.  * of the GNU General Public License as published by the Free Software Foundation, either
  9.  * version 2 of the License, or (at your option) any later version.
  10.  *
  11.  * For more information, visit <http://getsproutcms.com>.
  12.  */
  13.  
  14. namespace Sprout\Helpers\Locales;
  15.  
  16. use InvalidArgumentException;
  17. use stdClass;
  18.  
  19. use Kohana;
  20.  
  21. use Sprout\Helpers\CountryConstants;
  22. use Sprout\Helpers\Form;
  23. use Sprout\Helpers\Validator;
  24.  
  25.  
  26. /**
  27.  * Class to handle locale-dependent data such as addresses and currency
  28.  */
  29. class LocaleInfo
  30. {
  31. protected $state_name = 'State/Province';
  32. protected $state_list = null;
  33.  
  34. protected $town_name = 'Suburb/Town';
  35.  
  36. protected $line1 = 'Line 1';
  37. protected $line2 = 'Line 2';
  38.  
  39. protected $postcode_name = 'Postcode';
  40.  
  41. protected $decimal_seperator = '.';
  42. protected $group_seperator = ',';
  43.  
  44. protected $currency_symbol = '$';
  45. protected $currency_decimal = 2;
  46. protected $currency_name = 'Dollar';
  47.  
  48. protected $shortdate = 'j/n/Y';
  49. protected $longdate = 'D jS M Y';
  50. protected $time = 'g:ia';
  51.  
  52.  
  53. public static $auto;
  54.  
  55. /**
  56.   * Automatically chooses a locale and returns it
  57.   * @return LocaleInfo
  58.   */
  59. public static function auto()
  60. {
  61. if (! self::$auto) {
  62. $l = Kohana::config('sprout.locale');
  63. if ($l == '') $l = 'AUS';
  64. $auto = self::get($l);
  65. }
  66.  
  67. return $auto;
  68. }
  69.  
  70.  
  71. /**
  72.   * Returns the LocaleInfo class for the specified country code.
  73.   * If no LocaleInfo can be found, a generic version is used.
  74.   * @param string $code 3-letter country code (ISO 3166-1 alpha-3)
  75.   * @return LocaleInfo
  76.   */
  77. public static function get($code = null)
  78. {
  79. $code = strtoupper($code);
  80. $code = preg_replace('/[^A-Z]/', '', $code);
  81.  
  82. $class_name = 'LocaleInfo' . $code;
  83. if (!file_exists(__DIR__ . "/{$class_name}.php")) {
  84. $class_name = 'LocaleInfo';
  85. }
  86.  
  87. $class_name = __NAMESPACE__ . '\\' . $class_name;
  88. $locale = new $class_name();
  89. return $locale;
  90. }
  91.  
  92.  
  93. /**
  94.   * Return the raw parameters of this locale
  95.   */
  96. public function getParameters()
  97. {
  98. return [
  99. 'decimal_seperator' => $this->decimal_seperator,
  100. 'group_seperator' => $this->group_seperator,
  101. 'currency_symbol' => $this->currency_symbol,
  102. 'currency_decimal' => $this->currency_decimal,
  103. ];
  104. }
  105.  
  106.  
  107. /**
  108.   * Organises the state list so that it doesn't have numeric keys.
  109.   *
  110.   * The state names become the form field values if state list is a numeric array.
  111.   * This makes it easy to configure a new locate with states/provinces/etc which don't have standard abbreviations.
  112.   *
  113.   * E.g. ['State 1', 'State 2'] would become: ['State 1' => 'State 1', 'State 2' => 'State 2']
  114.   *
  115.   * @return array
  116.   */
  117. public function nonNumericStates()
  118. {
  119. $states = $this->state_list;
  120. reset($states);
  121. $key = key($states);
  122. $val = current($states);
  123. if (is_int($key) and !is_array($val)) {
  124. $states = array_combine($states, $states);
  125. }
  126. return $states;
  127. }
  128.  
  129.  
  130. /**
  131.   * Extracts only the enterable values from the state list
  132.   *
  133.   * This should be used for validation.
  134.   *
  135.   * @return array
  136.   */
  137. public function stateValues()
  138. {
  139. $states = $this->nonNumericStates();
  140. if (!is_array(reset($states))) {
  141. return array_keys($states);
  142. }
  143.  
  144. // Handle grouped states, e.g. Japan's prefectures, grouped by region (Chihō)
  145. $keys = [];
  146. foreach ($states as $group) {
  147. foreach ($group as $key => $name) {
  148. $keys[] = $key;
  149. }
  150. }
  151.  
  152. return $keys;
  153. }
  154.  
  155.  
  156. /**
  157.   * Get the name of a given state by looking it up in the states list
  158.   *
  159.   * @param string $val Values stored by the {@see LocaleInfo::outputAddressFields} dropdown field
  160.   * @return string Full name of state, e.g. 'South Australia'
  161.   * @return null If the country does not have states (e.g. Vatican City)
  162.   */
  163. public function getStateName($val)
  164. {
  165. if (empty($this->state_list)) return null;
  166.  
  167. // Most common case of standard array
  168. if (isset($this->state_list[$val])) {
  169. return $this->state_list[$val];
  170. }
  171.  
  172. // Handle grouped states, e.g. Japan's prefectures, grouped by region (Chihō)
  173. foreach ($this->state_list as $group) {
  174. if (isset($group[$val])) {
  175. return $group[$val];
  176. }
  177. }
  178.  
  179. return $val;
  180. }
  181.  
  182.  
  183. /**
  184.   * Generates the address fields for the current locale
  185.   *
  186.   * @param string $prefix Optional prefix, e.g. 'postal_'
  187.   * @param bool $required True if the address as a whole is required, irrespective of individual fields
  188.   * @return string
  189.   */
  190. public function outputAddressFields($prefix = '', $required = false)
  191. {
  192. $out = '';
  193. foreach (['street', 'street2', 'town', 'state', 'postcode'] as $field) {
  194. $out .= self::outputAddressField($field, $prefix, $required);
  195. }
  196. return $out;
  197. }
  198.  
  199.  
  200. /**
  201.   * Generates a single address field for the current locale
  202.   *
  203.   * @param string $field 'street', 'street2', 'town', 'state', or 'postcode'
  204.   * @param string $prefix Optional prefix, e.g. 'postal_'
  205.   * @param bool $required True if the address as a whole is required, irrespective of the individual field
  206.   * @return string
  207.   */
  208. public function outputAddressField($field, $prefix = '', $required = false)
  209. {
  210. switch ($field) {
  211. case 'street':
  212. Form::nextFieldDetails($this->line1, $required);
  213. return Form::text($prefix . 'street', ['-wrapper-class' => 'address-street1']);
  214.  
  215. case 'street2':
  216. Form::nextFieldDetails($this->line2, false);
  217. return Form::text($prefix . 'street2', ['-wrapper-class' => 'address-street2']);
  218.  
  219. case 'town':
  220. Form::nextFieldDetails($this->town_name, $required);
  221. return Form::text($prefix . 'town', ['-wrapper-class' => 'address-town']);
  222.  
  223. case 'state':
  224. if ($this->state_name) {
  225. Form::nextFieldDetails($this->state_name, $required);
  226. if ($this->state_list) {
  227. $states = $this->nonNumericStates();
  228. return Form::dropdown($prefix . 'state', ['-wrapper-class' => 'address-state'], $states);
  229. } else {
  230. return Form::text($prefix . 'state', ['-wrapper-class' => 'address-state']);
  231. }
  232. } else {
  233. Form::nextFieldDetails('State/Province (please ignore)', false);
  234. $field = Form::text($prefix . 'state', ['-wrapper-class' => 'address-state']);
  235. return preg_replace('/^<div /', '<div style="display:none;" ', $field);
  236. }
  237.  
  238. case 'postcode':
  239. if ($this->postcode_name) {
  240. Form::nextFieldDetails($this->postcode_name, $required);
  241. return Form::text($prefix . 'postcode', ['-wrapper-class' => 'address-postcode']);
  242. } else {
  243. Form::nextFieldDetails('Postcode (please ignore)', false);
  244. $field = Form::text($prefix . 'postcode', ['-wrapper-class' => 'address-postcode']);
  245. return preg_replace('/^<div /', '<div style="display:none;" ', $field);
  246. }
  247.  
  248. default:
  249. throw new InvalidArgumentException('Unrecognised address field');
  250. }
  251. }
  252.  
  253.  
  254. /**
  255.   * Returns a string which is a formatted version of the address specified using the provided data.
  256.   * The string contains newlines which will need converting to BRs for HTML output
  257.   *
  258.   * @param array|object $data A record from the database
  259.   * required columns: street, town, state, postcode, country
  260.   * @return string Plaintext
  261.   **/
  262. public function outputAddressText($data)
  263. {
  264. if ($data instanceof stdClass) $data = get_object_vars($data);
  265.  
  266. $str = $data['street'] . "\n" . $data['town'];
  267.  
  268. if ($this->state_name) {
  269. if ($this->state_list and is_numeric($data['state'])) {
  270. $str .= ', ' . $this->state_list[$data['state']];
  271. } else {
  272. $str .= ', ' . $data['state'];
  273. }
  274. }
  275.  
  276. if ($this->postcode_name) {
  277. $str .= ' ' . $data['postcode'];
  278. }
  279.  
  280. $str .= "\n" . CountryConstants::$alpha3[$data['country']];
  281.  
  282. return $str;
  283. }
  284.  
  285.  
  286. /**
  287.   * Validate address fields
  288.   *
  289.   * @param Validator $valid Validator for the form being processed
  290.   * @param bool $required Are the address fields required?
  291.   * @return void
  292.   */
  293. public function validateAddress(Validator $valid, $required = false)
  294. {
  295. $field_names = [
  296. 'street' => $this->line1,
  297. 'street2' => $this->line2,
  298. 'town' => $this->town_name,
  299. 'state' => $this->state_name,
  300. 'postcode' => $this->postcode_name,
  301. ];
  302. foreach ($field_names as $field => $label) {
  303. $valid->setFieldLabel($field, $label);
  304. }
  305.  
  306. if ($required) $valid->required(['street']);
  307. $valid->check('street', 'Validity::length', 0, 200);
  308. $valid->check('street2', 'Validity::length', 0, 200);
  309.  
  310. if ($required) $valid->required(['town']);
  311. $valid->check('town', 'Validity::length', 0, 100);
  312.  
  313. if ($this->state_name) {
  314. if ($required) $valid->required(['state']);
  315. $valid->check('state', 'Validity::length', 0, 100);
  316.  
  317. if ($this->state_list) {
  318. $states = $this->stateValues();
  319. $valid->check('state', 'Validity::inArray', $states);
  320. }
  321. }
  322.  
  323. if ($this->postcode_name) {
  324. if ($required) $valid->required(['postcode']);
  325. $valid->check('postcode', 'Validity::length', 0, 10);
  326. }
  327. }
  328.  
  329.  
  330. /**
  331.   * Formats numbers, like the interal {@see number_format} function
  332.   *
  333.   * @param int|float The number to format
  334.   * @param int $precision The number of decimal places to render
  335.   * @return string
  336.   **/
  337. public function numberFormat($number, $precision = 0)
  338. {
  339. return number_format($number, $precision, $this->decimal_seperator, $this->group_seperator);
  340. }
  341.  
  342.  
  343. /**
  344.   * Formats currency values, similar to the interal {@see number_format} function
  345.   *
  346.   * @param int|float The number to format
  347.   * @param int $precision The number of decimal places to render; if NULL then it's locale-dependent
  348.   * @return string
  349.   **/
  350. public function moneyFormat($number, $precision = null)
  351. {
  352. if ($precision === null) $precision = $this->currency_decimal;
  353. if ($number < 0.0) {
  354. return '-' . $this->currency_symbol . $this->numberFormat(abs($number), $precision);
  355. } else {
  356. return $this->currency_symbol . $this->numberFormat($number, $precision);
  357. }
  358. }
  359.  
  360.  
  361. /**
  362.   * Formats dates in a long format
  363.   *
  364.   * @param int $timestamp Unix timestamp
  365.   * @return string
  366.   **/
  367. public function longdate($timestamp)
  368. {
  369. return date($this->longdate, $timestamp);
  370. }
  371.  
  372.  
  373. /**
  374.   * Formats dates in a short format
  375.   *
  376.   * @param int $timestamp Unix timestamp
  377.   * @return string
  378.   **/
  379. public function shortdate($timestamp)
  380. {
  381. return date($this->shortdate, $timestamp);
  382. }
  383.  
  384.  
  385. /**
  386.   * Formats times
  387.   *
  388.   * @param int $timestamp Unix timestamp
  389.   * @return string
  390.   **/
  391. public function time($timestamp)
  392. {
  393. return date($this->time, $timestamp);
  394. }
  395.  
  396.  
  397. /**
  398.   * Gets a list of states
  399.   * @return array For use with {@see Fb::dropdown}
  400.   */
  401. public function getStateList()
  402. {
  403. return $this->state_list;
  404. }
  405.  
  406.  
  407. /**
  408.   * The name of the currency, e.g. 'Dollar' for $
  409.   * @return string
  410.   */
  411. public function getCurrencyName()
  412. {
  413. return $this->currency_name;
  414. }
  415.  
  416. }
  417.  
  418.  
  419.