PageRenderTime 148ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/Type/DateType.php

https://gitlab.com/matijabelec/bigpandadev
PHP | 326 lines | 233 code | 52 blank | 41 comment | 25 complexity | 0f5e670152c0706b17b9532396aee292 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Form\Extension\Core\Type;
  11. use Symfony\Component\Form\AbstractType;
  12. use Symfony\Component\Form\FormInterface;
  13. use Symfony\Component\Form\FormBuilderInterface;
  14. use Symfony\Component\Form\FormView;
  15. use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer;
  16. use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer;
  17. use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer;
  18. use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
  19. use Symfony\Component\Form\ReversedTransformer;
  20. use Symfony\Component\OptionsResolver\Options;
  21. use Symfony\Component\OptionsResolver\OptionsResolver;
  22. use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
  23. class DateType extends AbstractType
  24. {
  25. const DEFAULT_FORMAT = \IntlDateFormatter::MEDIUM;
  26. const HTML5_FORMAT = 'yyyy-MM-dd';
  27. private static $acceptedFormats = array(
  28. \IntlDateFormatter::FULL,
  29. \IntlDateFormatter::LONG,
  30. \IntlDateFormatter::MEDIUM,
  31. \IntlDateFormatter::SHORT,
  32. );
  33. /**
  34. * {@inheritdoc}
  35. */
  36. public function buildForm(FormBuilderInterface $builder, array $options)
  37. {
  38. $dateFormat = is_int($options['format']) ? $options['format'] : self::DEFAULT_FORMAT;
  39. $timeFormat = \IntlDateFormatter::NONE;
  40. $calendar = \IntlDateFormatter::GREGORIAN;
  41. $pattern = is_string($options['format']) ? $options['format'] : null;
  42. if (!in_array($dateFormat, self::$acceptedFormats, true)) {
  43. throw new InvalidOptionsException('The "format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.');
  44. }
  45. if (null !== $pattern && (false === strpos($pattern, 'y') || false === strpos($pattern, 'M') || false === strpos($pattern, 'd'))) {
  46. throw new InvalidOptionsException(sprintf('The "format" option should contain the letters "y", "M" and "d". Its current value is "%s".', $pattern));
  47. }
  48. if ('single_text' === $options['widget']) {
  49. $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer(
  50. $options['model_timezone'],
  51. $options['view_timezone'],
  52. $dateFormat,
  53. $timeFormat,
  54. $calendar,
  55. $pattern
  56. ));
  57. } else {
  58. $yearOptions = $monthOptions = $dayOptions = array(
  59. 'error_bubbling' => true,
  60. );
  61. $formatter = new \IntlDateFormatter(
  62. \Locale::getDefault(),
  63. $dateFormat,
  64. $timeFormat,
  65. null,
  66. $calendar,
  67. $pattern
  68. );
  69. // new \intlDateFormatter may return null instead of false in case of failure, see https://bugs.php.net/bug.php?id=66323
  70. if (!$formatter) {
  71. throw new InvalidOptionsException(intl_get_error_message(), intl_get_error_code());
  72. }
  73. $formatter->setLenient(false);
  74. if ('choice' === $options['widget']) {
  75. // Only pass a subset of the options to children
  76. $yearOptions['choices'] = $this->formatTimestamps($formatter, '/y+/', $this->listYears($options['years']));
  77. $yearOptions['placeholder'] = $options['placeholder']['year'];
  78. $monthOptions['choices'] = $this->formatTimestamps($formatter, '/[M|L]+/', $this->listMonths($options['months']));
  79. $monthOptions['placeholder'] = $options['placeholder']['month'];
  80. $dayOptions['choices'] = $this->formatTimestamps($formatter, '/d+/', $this->listDays($options['days']));
  81. $dayOptions['placeholder'] = $options['placeholder']['day'];
  82. }
  83. // Append generic carry-along options
  84. foreach (array('required', 'translation_domain') as $passOpt) {
  85. $yearOptions[$passOpt] = $monthOptions[$passOpt] = $dayOptions[$passOpt] = $options[$passOpt];
  86. }
  87. $builder
  88. ->add('year', $options['widget'], $yearOptions)
  89. ->add('month', $options['widget'], $monthOptions)
  90. ->add('day', $options['widget'], $dayOptions)
  91. ->addViewTransformer(new DateTimeToArrayTransformer(
  92. $options['model_timezone'], $options['view_timezone'], array('year', 'month', 'day')
  93. ))
  94. ->setAttribute('formatter', $formatter)
  95. ;
  96. }
  97. if ('string' === $options['input']) {
  98. $builder->addModelTransformer(new ReversedTransformer(
  99. new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone'], 'Y-m-d')
  100. ));
  101. } elseif ('timestamp' === $options['input']) {
  102. $builder->addModelTransformer(new ReversedTransformer(
  103. new DateTimeToTimestampTransformer($options['model_timezone'], $options['model_timezone'])
  104. ));
  105. } elseif ('array' === $options['input']) {
  106. $builder->addModelTransformer(new ReversedTransformer(
  107. new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], array('year', 'month', 'day'))
  108. ));
  109. }
  110. }
  111. /**
  112. * {@inheritdoc}
  113. */
  114. public function finishView(FormView $view, FormInterface $form, array $options)
  115. {
  116. $view->vars['widget'] = $options['widget'];
  117. // Change the input to a HTML5 date input if
  118. // * the widget is set to "single_text"
  119. // * the format matches the one expected by HTML5
  120. // * the html5 is set to true
  121. if ($options['html5'] && 'single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) {
  122. $view->vars['type'] = 'date';
  123. }
  124. if ($form->getConfig()->hasAttribute('formatter')) {
  125. $pattern = $form->getConfig()->getAttribute('formatter')->getPattern();
  126. // remove special characters unless the format was explicitly specified
  127. if (!is_string($options['format'])) {
  128. // remove quoted strings first
  129. $pattern = preg_replace('/\'[^\']+\'/', '', $pattern);
  130. // remove remaining special chars
  131. $pattern = preg_replace('/[^yMd]+/', '', $pattern);
  132. }
  133. // set right order with respect to locale (e.g.: de_DE=dd.MM.yy; en_US=M/d/yy)
  134. // lookup various formats at http://userguide.icu-project.org/formatparse/datetime
  135. if (preg_match('/^([yMd]+)[^yMd]*([yMd]+)[^yMd]*([yMd]+)$/', $pattern)) {
  136. $pattern = preg_replace(array('/y+/', '/M+/', '/d+/'), array('{{ year }}', '{{ month }}', '{{ day }}'), $pattern);
  137. } else {
  138. // default fallback
  139. $pattern = '{{ year }}{{ month }}{{ day }}';
  140. }
  141. $view->vars['date_pattern'] = $pattern;
  142. }
  143. }
  144. /**
  145. * {@inheritdoc}
  146. */
  147. public function configureOptions(OptionsResolver $resolver)
  148. {
  149. $compound = function (Options $options) {
  150. return $options['widget'] !== 'single_text';
  151. };
  152. $emptyValue = $placeholderDefault = function (Options $options) {
  153. return $options['required'] ? null : '';
  154. };
  155. $placeholder = function (Options $options) {
  156. return $options['empty_value'];
  157. };
  158. $placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) {
  159. if (is_array($placeholder)) {
  160. $default = $placeholderDefault($options);
  161. return array_merge(
  162. array('year' => $default, 'month' => $default, 'day' => $default),
  163. $placeholder
  164. );
  165. }
  166. return array(
  167. 'year' => $placeholder,
  168. 'month' => $placeholder,
  169. 'day' => $placeholder,
  170. );
  171. };
  172. $format = function (Options $options) {
  173. return $options['widget'] === 'single_text' ? DateType::HTML5_FORMAT : DateType::DEFAULT_FORMAT;
  174. };
  175. $resolver->setDefaults(array(
  176. 'years' => range(date('Y') - 5, date('Y') + 5),
  177. 'months' => range(1, 12),
  178. 'days' => range(1, 31),
  179. 'widget' => 'choice',
  180. 'input' => 'datetime',
  181. 'format' => $format,
  182. 'model_timezone' => null,
  183. 'view_timezone' => null,
  184. 'empty_value' => $emptyValue, // deprecated
  185. 'placeholder' => $placeholder,
  186. 'html5' => true,
  187. // Don't modify \DateTime classes by reference, we treat
  188. // them like immutable value objects
  189. 'by_reference' => false,
  190. 'error_bubbling' => false,
  191. // If initialized with a \DateTime object, FormType initializes
  192. // this option to "\DateTime". Since the internal, normalized
  193. // representation is not \DateTime, but an array, we need to unset
  194. // this option.
  195. 'data_class' => null,
  196. 'compound' => $compound,
  197. ));
  198. $resolver->setNormalizer('empty_value', $placeholderNormalizer);
  199. $resolver->setNormalizer('placeholder', $placeholderNormalizer);
  200. $resolver->setAllowedValues('input', array(
  201. 'datetime',
  202. 'string',
  203. 'timestamp',
  204. 'array',
  205. ));
  206. $resolver->setAllowedValues('widget', array(
  207. 'single_text',
  208. 'text',
  209. 'choice',
  210. ));
  211. $resolver->setAllowedTypes('format', array('int', 'string'));
  212. $resolver->setAllowedTypes('years', 'array');
  213. $resolver->setAllowedTypes('months', 'array');
  214. $resolver->setAllowedTypes('days', 'array');
  215. }
  216. /**
  217. * {@inheritdoc}
  218. */
  219. public function getName()
  220. {
  221. return 'date';
  222. }
  223. private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $timestamps)
  224. {
  225. $pattern = $formatter->getPattern();
  226. $timezone = $formatter->getTimezoneId();
  227. if ($setTimeZone = PHP_VERSION_ID >= 50500 || method_exists($formatter, 'setTimeZone')) {
  228. $formatter->setTimeZone('UTC');
  229. } else {
  230. $formatter->setTimeZoneId('UTC');
  231. }
  232. if (preg_match($regex, $pattern, $matches)) {
  233. $formatter->setPattern($matches[0]);
  234. foreach ($timestamps as $key => $timestamp) {
  235. $timestamps[$key] = $formatter->format($timestamp);
  236. }
  237. // I'd like to clone the formatter above, but then we get a
  238. // segmentation fault, so let's restore the old state instead
  239. $formatter->setPattern($pattern);
  240. }
  241. if ($setTimeZone) {
  242. $formatter->setTimeZone($timezone);
  243. } else {
  244. $formatter->setTimeZoneId($timezone);
  245. }
  246. return $timestamps;
  247. }
  248. private function listYears(array $years)
  249. {
  250. $result = array();
  251. foreach ($years as $year) {
  252. if (false !== $y = gmmktime(0, 0, 0, 6, 15, $year)) {
  253. $result[$year] = $y;
  254. }
  255. }
  256. return $result;
  257. }
  258. private function listMonths(array $months)
  259. {
  260. $result = array();
  261. foreach ($months as $month) {
  262. $result[$month] = gmmktime(0, 0, 0, $month, 15);
  263. }
  264. return $result;
  265. }
  266. private function listDays(array $days)
  267. {
  268. $result = array();
  269. foreach ($days as $day) {
  270. $result[$day] = gmmktime(0, 0, 0, 5, $day);
  271. }
  272. return $result;
  273. }
  274. }