]>
Commit | Line | Data |
---|---|---|
4f5b44bd NL |
1 | <?php |
2 | ||
3 | /* | |
4 | * This file is part of the Symfony package. | |
5 | * | |
6 | * (c) Fabien Potencier <fabien@symfony.com> | |
7 | * | |
8 | * For the full copyright and license information, please view the LICENSE | |
9 | * file that was distributed with this source code. | |
10 | */ | |
11 | ||
12 | namespace Symfony\Component\Form\Extension\Core\Type; | |
13 | ||
14 | use Symfony\Component\Form\AbstractType; | |
15 | use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; | |
16 | use Symfony\Component\Form\FormInterface; | |
17 | use Symfony\Component\Form\FormBuilderInterface; | |
18 | use Symfony\Component\Form\FormView; | |
19 | use Symfony\Component\Form\ReversedTransformer; | |
20 | use Symfony\Component\Form\Extension\Core\DataTransformer\DataTransformerChain; | |
21 | use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; | |
22 | use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; | |
23 | use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer; | |
24 | use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; | |
25 | use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToRfc3339Transformer; | |
26 | use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer; | |
27 | use Symfony\Component\OptionsResolver\Options; | |
28 | use Symfony\Component\OptionsResolver\OptionsResolverInterface; | |
29 | ||
30 | class DateTimeType extends AbstractType | |
31 | { | |
32 | const DEFAULT_DATE_FORMAT = \IntlDateFormatter::MEDIUM; | |
33 | ||
34 | const DEFAULT_TIME_FORMAT = \IntlDateFormatter::MEDIUM; | |
35 | ||
36 | /** | |
37 | * This is not quite the HTML5 format yet, because ICU lacks the | |
38 | * capability of parsing and generating RFC 3339 dates, which | |
39 | * are like the below pattern but with a timezone suffix. The | |
40 | * timezone suffix is | |
41 | * | |
42 | * * "Z" for UTC | |
43 | * * "(-|+)HH:mm" for other timezones (note the colon!) | |
44 | * | |
45 | * For more information see: | |
46 | * | |
47 | * http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax | |
48 | * http://www.w3.org/TR/html-markup/input.datetime.html | |
49 | * http://tools.ietf.org/html/rfc3339 | |
50 | * | |
51 | * An ICU ticket was created: | |
52 | * http://icu-project.org/trac/ticket/9421 | |
53 | * | |
54 | * It was supposedly fixed, but is not available in all PHP installations | |
55 | * yet. To temporarily circumvent this issue, DateTimeToRfc3339Transformer | |
56 | * is used when the format matches this constant. | |
57 | */ | |
58 | const HTML5_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"; | |
59 | ||
60 | private static $acceptedFormats = array( | |
61 | \IntlDateFormatter::FULL, | |
62 | \IntlDateFormatter::LONG, | |
63 | \IntlDateFormatter::MEDIUM, | |
64 | \IntlDateFormatter::SHORT, | |
65 | ); | |
66 | ||
67 | /** | |
68 | * {@inheritdoc} | |
69 | */ | |
70 | public function buildForm(FormBuilderInterface $builder, array $options) | |
71 | { | |
72 | $parts = array('year', 'month', 'day', 'hour'); | |
73 | $dateParts = array('year', 'month', 'day'); | |
74 | $timeParts = array('hour'); | |
75 | ||
76 | if ($options['with_minutes']) { | |
77 | $parts[] = 'minute'; | |
78 | $timeParts[] = 'minute'; | |
79 | } | |
80 | ||
81 | if ($options['with_seconds']) { | |
82 | $parts[] = 'second'; | |
83 | $timeParts[] = 'second'; | |
84 | } | |
85 | ||
86 | $dateFormat = is_int($options['date_format']) ? $options['date_format'] : self::DEFAULT_DATE_FORMAT; | |
87 | $timeFormat = self::DEFAULT_TIME_FORMAT; | |
88 | $calendar = \IntlDateFormatter::GREGORIAN; | |
89 | $pattern = is_string($options['format']) ? $options['format'] : null; | |
90 | ||
91 | if (!in_array($dateFormat, self::$acceptedFormats, true)) { | |
92 | throw new InvalidOptionsException('The "date_format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.'); | |
93 | } | |
94 | ||
95 | if ('single_text' === $options['widget']) { | |
96 | if (self::HTML5_FORMAT === $pattern) { | |
97 | $builder->addViewTransformer(new DateTimeToRfc3339Transformer( | |
98 | $options['model_timezone'], | |
99 | $options['view_timezone'] | |
100 | )); | |
101 | } else { | |
102 | $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer( | |
103 | $options['model_timezone'], | |
104 | $options['view_timezone'], | |
105 | $dateFormat, | |
106 | $timeFormat, | |
107 | $calendar, | |
108 | $pattern | |
109 | )); | |
110 | } | |
111 | } else { | |
112 | // Only pass a subset of the options to children | |
113 | $dateOptions = array_intersect_key($options, array_flip(array( | |
114 | 'years', | |
115 | 'months', | |
116 | 'days', | |
117 | 'empty_value', | |
118 | 'required', | |
119 | 'translation_domain', | |
120 | ))); | |
121 | ||
122 | $timeOptions = array_intersect_key($options, array_flip(array( | |
123 | 'hours', | |
124 | 'minutes', | |
125 | 'seconds', | |
126 | 'with_minutes', | |
127 | 'with_seconds', | |
128 | 'empty_value', | |
129 | 'required', | |
130 | 'translation_domain', | |
131 | ))); | |
132 | ||
133 | if (null !== $options['date_widget']) { | |
134 | $dateOptions['widget'] = $options['date_widget']; | |
135 | } | |
136 | ||
137 | if (null !== $options['time_widget']) { | |
138 | $timeOptions['widget'] = $options['time_widget']; | |
139 | } | |
140 | ||
141 | if (null !== $options['date_format']) { | |
142 | $dateOptions['format'] = $options['date_format']; | |
143 | } | |
144 | ||
145 | $dateOptions['input'] = $timeOptions['input'] = 'array'; | |
146 | $dateOptions['error_bubbling'] = $timeOptions['error_bubbling'] = true; | |
147 | ||
148 | $builder | |
149 | ->addViewTransformer(new DataTransformerChain(array( | |
150 | new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts), | |
151 | new ArrayToPartsTransformer(array( | |
152 | 'date' => $dateParts, | |
153 | 'time' => $timeParts, | |
154 | )), | |
155 | ))) | |
156 | ->add('date', 'date', $dateOptions) | |
157 | ->add('time', 'time', $timeOptions) | |
158 | ; | |
159 | } | |
160 | ||
161 | if ('string' === $options['input']) { | |
162 | $builder->addModelTransformer(new ReversedTransformer( | |
163 | new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone']) | |
164 | )); | |
165 | } elseif ('timestamp' === $options['input']) { | |
166 | $builder->addModelTransformer(new ReversedTransformer( | |
167 | new DateTimeToTimestampTransformer($options['model_timezone'], $options['model_timezone']) | |
168 | )); | |
169 | } elseif ('array' === $options['input']) { | |
170 | $builder->addModelTransformer(new ReversedTransformer( | |
171 | new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], $parts) | |
172 | )); | |
173 | } | |
174 | } | |
175 | ||
176 | /** | |
177 | * {@inheritdoc} | |
178 | */ | |
179 | public function buildView(FormView $view, FormInterface $form, array $options) | |
180 | { | |
181 | $view->vars['widget'] = $options['widget']; | |
182 | ||
183 | // Change the input to a HTML5 date input if | |
184 | // * the widget is set to "single_text" | |
185 | // * the format matches the one expected by HTML5 | |
186 | if ('single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) { | |
187 | $view->vars['type'] = 'datetime'; | |
188 | } | |
189 | } | |
190 | ||
191 | /** | |
192 | * {@inheritdoc} | |
193 | */ | |
194 | public function setDefaultOptions(OptionsResolverInterface $resolver) | |
195 | { | |
196 | $compound = function (Options $options) { | |
197 | return $options['widget'] !== 'single_text'; | |
198 | }; | |
199 | ||
200 | // Defaults to the value of "widget" | |
201 | $dateWidget = function (Options $options) { | |
202 | return $options['widget']; | |
203 | }; | |
204 | ||
205 | // Defaults to the value of "widget" | |
206 | $timeWidget = function (Options $options) { | |
207 | return $options['widget']; | |
208 | }; | |
209 | ||
210 | $resolver->setDefaults(array( | |
211 | 'input' => 'datetime', | |
212 | 'model_timezone' => null, | |
213 | 'view_timezone' => null, | |
214 | 'format' => self::HTML5_FORMAT, | |
215 | 'date_format' => null, | |
216 | 'widget' => null, | |
217 | 'date_widget' => $dateWidget, | |
218 | 'time_widget' => $timeWidget, | |
219 | 'with_minutes' => true, | |
220 | 'with_seconds' => false, | |
221 | // Don't modify \DateTime classes by reference, we treat | |
222 | // them like immutable value objects | |
223 | 'by_reference' => false, | |
224 | 'error_bubbling' => false, | |
225 | // If initialized with a \DateTime object, FormType initializes | |
226 | // this option to "\DateTime". Since the internal, normalized | |
227 | // representation is not \DateTime, but an array, we need to unset | |
228 | // this option. | |
229 | 'data_class' => null, | |
230 | 'compound' => $compound, | |
231 | )); | |
232 | ||
233 | // Don't add some defaults in order to preserve the defaults | |
234 | // set in DateType and TimeType | |
235 | $resolver->setOptional(array( | |
236 | 'empty_value', | |
237 | 'years', | |
238 | 'months', | |
239 | 'days', | |
240 | 'hours', | |
241 | 'minutes', | |
242 | 'seconds', | |
243 | )); | |
244 | ||
245 | $resolver->setAllowedValues(array( | |
246 | 'input' => array( | |
247 | 'datetime', | |
248 | 'string', | |
249 | 'timestamp', | |
250 | 'array', | |
251 | ), | |
252 | 'date_widget' => array( | |
253 | null, // inherit default from DateType | |
254 | 'single_text', | |
255 | 'text', | |
256 | 'choice', | |
257 | ), | |
258 | 'time_widget' => array( | |
259 | null, // inherit default from TimeType | |
260 | 'single_text', | |
261 | 'text', | |
262 | 'choice', | |
263 | ), | |
264 | // This option will overwrite "date_widget" and "time_widget" options | |
265 | 'widget' => array( | |
266 | null, // default, don't overwrite options | |
267 | 'single_text', | |
268 | 'text', | |
269 | 'choice', | |
270 | ), | |
271 | )); | |
272 | } | |
273 | ||
274 | /** | |
275 | * {@inheritdoc} | |
276 | */ | |
277 | public function getName() | |
278 | { | |
279 | return 'datetime'; | |
280 | } | |
281 | } |