PageRenderTime 41ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/application/protected/extensions/widgets/dateselect/ActiveDateList.php

https://bitbucket.org/dinhtrung/yiicorecms/
PHP | 294 lines | 156 code | 46 blank | 92 comment | 15 complexity | 4715ff2bcb9283e964256ce67a0a6881 MD5 | raw file
Possible License(s): GPL-3.0, BSD-3-Clause, CC0-1.0, BSD-2-Clause, GPL-2.0, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /**
  3. * ActiveDateList class file.
  4. *
  5. * @author ciss
  6. * @link http://www.yiiframework.com/
  7. *
  8. *
  9. * ActiveDateList creates dropdown lists from a date string.
  10. * For configuration examples, please see the examples for the $displayFormat property.
  11. *
  12. * Before validating your date property you will have to attach an event handler to your model.
  13. * This can be done as follows:
  14. *
  15. * $model = new myModel;
  16. * ActiveDateList::sanitize($model, 'myDateAttribute');
  17. *
  18. * or
  19. *
  20. * $record = myModel::model()->find(...);
  21. * ActiveDateList::sanitize($record, 'myDateAttribute');
  22. *
  23. * This applies a handler to the model's onBeforeValidation event.
  24. * The handler will merge the select fields back into a single string before validation occurs.
  25. *
  26. */
  27. class ActiveDateList extends CInputWidget
  28. {
  29. /**
  30. * @var string the locale ID (e.g. 'fr', 'de') for the language to be used by the select fields.
  31. * If not set, this property defaults to the application language. To prevent this, set it to false.
  32. */
  33. public $language;
  34. /**
  35. * @var string the format of $startDate, $endDate and the attribute value, using the date() syntax.
  36. */
  37. public $inputFormat = 'Y-m-d';
  38. /**
  39. * @var string the earliest selectable date. Format must conform to the specified input format.
  40. */
  41. public $startDate;
  42. /**
  43. * @var string the latest selectable date. Format must conform to the specified input format.
  44. */
  45. public $endDate;
  46. /**
  47. * @var boolean wether options should be sorted by their values.
  48. * When using very short time spans (a couple of months) this may not be a desired behaviour.
  49. */
  50. public $keySort = true;
  51. /**
  52. * @var array the select boxes to be displayed.
  53. * Each entry consists of an index defining the value format and a value defining the display format.
  54. * Make sure that the index is a substring of your defined input format, otherwise the values can't be merged back into a string.
  55. * Example: with inputFormat = "Y-m-d", having an index format "m-d" or "Y-m" will work; "d-m" or "Y/m" will not.
  56. *
  57. * Usage examples (assuming the default input format is used; "output" is an ascii representation of the generated select boxes):
  58. * <pre>
  59. * Config: array('d' => 'd', 'm' => 'F', 'Y' => 'Y')
  60. * Output: [03 |] [April |] [2010 |]
  61. *
  62. * Config: array('m-d' => 'l, F j', 'Y' => Y')
  63. * Output: [Saturday, April 3 |] [2010 |]
  64. *
  65. * Config: array('Y-m-d' => 'M/d/y')
  66. * Output: [Apr/03/10 |]
  67. * </pre>
  68. */
  69. public $displayFormat = array('m-d' => 'F jS', 'Y' => 'Y');
  70. /**
  71. * @var string the template the boxes should be wrapped in.
  72. * Recognizes numerical and value format placeholders and allows mixing of both.
  73. * If null, the boxes will be joined together using the $separator property.
  74. * Example: '<div><span class="first-select">{0}</span> <span class="year-select">{Y}</span></div>'
  75. */
  76. public $template;
  77. /**
  78. * @var string separator between select boxes.
  79. * Will only be used if no template has been defined.
  80. */
  81. public $separator = ' ';
  82. private $_localizableFormats = array(
  83. 'D' => array('substitute' => '{\D:w}', 'function' => 'getWeekDayName', 'format' => 'abbreviated'),
  84. 'l' => array('substitute' => '{\l:w}', 'function' => 'getWeekDayName', 'format' => 'wide'),
  85. 'M' => array('substitute' => '{\M:n}', 'function' => 'getMonthName', 'format' => 'abbreviated'),
  86. 'F' => array('substitute' => '{\F:n}', 'function' => 'getMonthName', 'format' => 'wide'),
  87. );
  88. /**
  89. * Creates a timestamp from the given format and value.
  90. * Though this function is not as versatile as strtotime(), it is still better suited for non-english date formats.
  91. * Recognized formatting options are d, j, m, n, y and Y.
  92. */
  93. public static function getTimestamp($format, $value)
  94. {
  95. $patterns = array(
  96. 'Y' => '(?<Y>[1-2][0-9][0-9][0-9])',
  97. 'y' => '(?<y>[0-9][0-9])',
  98. 'd' => '(?<d>0[1-9]|[12][0-9]|3[01])',
  99. 'j' => '(?<j>[1-9]|[12][0-9]|3[01])',
  100. 'S' => '(?<S>(?<=1)st|(?<=2)nd|(?<=3)rd|(?<=[4-0])th)',
  101. 'm' => '(?<m>0[1-9]|1[0-2])',
  102. 'n' => '(?<n>[1-9]|1[0-2])',
  103. );
  104. $mappers = array(
  105. 'j'=>'j',
  106. 'd'=>'j',
  107. 'n'=>'n',
  108. 'm'=>'n',
  109. 'Y'=>'Y',
  110. 'y'=>'Y'
  111. );
  112. $formatPattern = '/^' . strtr(preg_quote($format), $patterns) . '$/';
  113. if(!preg_match($formatPattern, $value, $date))
  114. return false;
  115. $date += array('j' => 0, 'n' => 0, 'Y' => 0);
  116. foreach($mappers as $from => $to)
  117. if(!empty($date[$from]))
  118. $date[$to] = $date[$from];
  119. return mktime(12, 0, 0, (int) $date['n'], (int) $date['j'], (int) $date['Y']);
  120. }
  121. /**
  122. * Replaces localizable formatters with placeholders.
  123. */
  124. private function replaceLocalizableFormats($formatStrings, $falseIfNoChange = false)
  125. {
  126. $unchanged = true;
  127. $replacements = array();
  128. foreach($this->_localizableFormats as $format => $options)
  129. $replacements[$format] = $options['substitute'];
  130. $replacedStrings = array();
  131. foreach($formatStrings as $index => $string)
  132. {
  133. $replacedStrings[$index] = strtr($string, $replacements);
  134. $unchanged = $falseIfNoChange && $unchanged && $replacedStrings[$index] === $string;
  135. }
  136. return $unchanged ? false : $replacedStrings;
  137. }
  138. /**
  139. * Replaces name placeholders with their localized names.
  140. */
  141. private function localize($data)
  142. {
  143. $locale = CLocale::getInstance($this->language);
  144. $locOptions = $this->_localizableFormats;
  145. $formats = implode('', array_keys($locOptions));
  146. foreach($data as &$field)
  147. foreach($field as &$string)
  148. {
  149. if(strstr($string, '{') === false)
  150. // nothing to replace, break loop
  151. break;
  152. preg_match_all("/\{(?<format>[$formats]):(?<index>.+?)\}/", $string, $matches, PREG_SET_ORDER);
  153. foreach($matches as $match)
  154. {
  155. $method = $locOptions[$match['format']]['function'];
  156. $nameFormat = $locOptions[$match['format']]['format'];
  157. $string = str_replace($match[0], $locale->$method($match['index'], $nameFormat), $string);
  158. }
  159. }
  160. return $data;
  161. }
  162. private function createDropDownLists()
  163. {
  164. list($name, $id) = $this->resolveNameID();
  165. $currentStamp = self::getTimestamp($this->inputFormat, $this->model->{$this->attribute});
  166. $startStamp = self::getTimestamp($this->inputFormat, $this->startDate);
  167. $endStamp = self::getTimestamp($this->inputFormat, $this->endDate);
  168. // alter name formatters for localized substitution
  169. if(!empty($this->language))
  170. {
  171. $localize = $this->replaceLocalizableFormats($this->displayFormat, true);
  172. if($localize !== false)
  173. {
  174. $this->displayFormat = $localize;
  175. $localize = true;
  176. }
  177. }
  178. else
  179. $localize = false;
  180. $step = 60*60*24;
  181. $data = array();
  182. // fill the options lists
  183. for($i = $startStamp; $i < $endStamp; $i = $i + $step)
  184. foreach($this->displayFormat as $valueFormat => $displayFormat)
  185. $data[$valueFormat][date($valueFormat, $i)] = date($displayFormat, $i);
  186. if($localize === true)
  187. $data = $this->localize($data);
  188. // preselect current date, sort item lists and generate dropdown lists
  189. $dropDownLists = array();
  190. foreach($this->displayFormat as $valueFormat => $displayFormat)
  191. {
  192. $selected[$valueFormat] = date($valueFormat, $currentStamp);
  193. if(!empty($this->keySort))
  194. ksort($data[$valueFormat], SORT_REGULAR);
  195. $htmlOptions = array('id' => CHtml::getIdByName($name."[$valueFormat]")) + $this->htmlOptions;
  196. $dropDownLists[$valueFormat] = CHtml::dropDownList($name."[$valueFormat]", $selected[$valueFormat], $data[$valueFormat], $htmlOptions);
  197. }
  198. return $dropDownLists;
  199. }
  200. public function init()
  201. {
  202. parent::init();
  203. if($this->language === null && Yii::app()->language !== Yii::app()->sourceLanguage)
  204. $this->language = Yii::app()->language;
  205. }
  206. /**
  207. * The actual handler attached to the model's onBeforeValidate event.
  208. */
  209. public static function conversionHandler($event, $attribute)
  210. {
  211. $value = &$event->sender->$attribute;
  212. if(is_string($value))
  213. return true;
  214. if(empty($value['inputFormat']))
  215. {
  216. $value = null;
  217. return false;
  218. }
  219. $value = strtr($value['inputFormat'], (array) $value);
  220. return true;
  221. }
  222. /**
  223. * Attaches an event handler to the model's onBeforeValidate event.
  224. * This handles merging values returned by the dropdown lists back into a single string.
  225. */
  226. public static function sanitize($model, $attributes)
  227. {
  228. $model->attachEventHandler('onBeforeValidate', create_function('$event', 'return ' . __CLASS__ . "::conversionHandler(\$event, $attributes);"));
  229. }
  230. public function run()
  231. {
  232. list($name) = $this->resolveNameID();
  233. echo CHtml::hiddenField($name.'[inputFormat]', $this->inputFormat);
  234. $lists = $this->createDropDownLists();
  235. if($this->template !== null)
  236. {
  237. $tokens = array();
  238. foreach(array_keys($lists) as $index => $key)
  239. {
  240. $tokens['{'.$index.'}'] = &$lists[$key];
  241. $tokens['{'.$key.'}'] = &$lists[$key];
  242. }
  243. echo strtr($this->template, $tokens);
  244. }
  245. else
  246. echo implode($this->separator, $lists);
  247. }
  248. }