Skip to content

Commit b9472ff

Browse files
author
dFayet
committed
Add new Form WeekType
1 parent 19811b8 commit b9472ff

File tree

10 files changed

+329
-11
lines changed

10 files changed

+329
-11
lines changed

src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,17 @@
255255
{{ block('form_widget_simple') }}
256256
{%- endblock color_widget -%}
257257

258+
{%- block week_widget -%}
259+
{%- if widget == 'single_text' -%}
260+
{{ block('form_widget_simple') }}
261+
{%- else -%}
262+
{%- set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} -%}
263+
<div {{ block('widget_container_attributes') }}>
264+
{{ form_widget(form.year, vars) }}-{{ form_widget(form.week, vars) }}
265+
</div>
266+
{%- endif -%}
267+
{%- endblock week_widget -%}
268+
258269
{# Labels #}
259270

260271
{%- block form_label -%}

src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2719,6 +2719,21 @@ public function testColor()
27192719
[@name="name"]
27202720
[@class="my&class form-control"]
27212721
[@value="#0000ff"]
2722+
'
2723+
);
2724+
}
2725+
2726+
public function testWeek()
2727+
{
2728+
$form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01');
2729+
2730+
$this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']],
2731+
'/input
2732+
[@type="week"]
2733+
[@name="holidays"]
2734+
[@class="my&class form-control"]
2735+
[@value="1970-W01"]
2736+
[not(@maxlength)]
27222737
'
27232738
);
27242739
}

src/Symfony/Bridge/Twig/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"symfony/asset": "^3.4|^4.0|^5.0",
2525
"symfony/dependency-injection": "^3.4|^4.0|^5.0",
2626
"symfony/finder": "^3.4|^4.0|^5.0",
27-
"symfony/form": "^4.3|^5.0",
27+
"symfony/form": "^4.4|^5.0",
2828
"symfony/http-foundation": "^4.3|^5.0",
2929
"symfony/http-kernel": "^3.4|^4.0|^5.0",
3030
"symfony/mime": "^4.3|^5.0",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<?php echo $view['form']->block($form, 'form_widget_simple', ['type' => isset($type) ? $type : 'week']);

src/Symfony/Component/Form/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
CHANGELOG
22
=========
33

4+
4.3.0
5+
-----
6+
* add new `WeekType`
7+
48
4.3.0
59
-----
610

src/Symfony/Component/Form/Extension/Core/CoreExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ protected function loadTypes()
8383
new Type\CurrencyType(),
8484
new Type\TelType(),
8585
new Type\ColorType(),
86+
new Type\WeekType(),
8687
];
8788
}
8889

src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public function transform($dateTime)
5858
return array_intersect_key([
5959
'year' => '',
6060
'month' => '',
61+
'week' => '',
6162
'day' => '',
6263
'hour' => '',
6364
'minute' => '',
@@ -80,6 +81,7 @@ public function transform($dateTime)
8081
$result = array_intersect_key([
8182
'year' => $dateTime->format('Y'),
8283
'month' => $dateTime->format('m'),
84+
'week' => $dateTime->format('W'),
8385
'day' => $dateTime->format('d'),
8486
'hour' => $dateTime->format('H'),
8587
'minute' => $dateTime->format('i'),
@@ -146,6 +148,16 @@ public function reverseTransform($value)
146148
throw new TransformationFailedException('This year is invalid');
147149
}
148150

151+
if (isset($value['week'])) {
152+
if (0 !== strpos($value['week'], 'W') || !ctype_digit($weekNumber = ltrim($value['week'], 'W'))) {
153+
throw new TransformationFailedException('This week is invalid');
154+
}
155+
156+
if (date('W', strtotime('28th December '.$value['year'] ?? '1970')) < $weekNumber) {
157+
throw new TransformationFailedException(sprintf('Week No. %d does not exists for year %d', $weekNumber, $value['year'] ?? '1970'));
158+
}
159+
}
160+
149161
if (!empty($value['month']) && !empty($value['day']) && !empty($value['year']) && false === checkdate($value['month'], $value['day'], $value['year'])) {
150162
throw new TransformationFailedException('This is an invalid date');
151163
}
@@ -163,15 +175,24 @@ public function reverseTransform($value)
163175
}
164176

165177
try {
166-
$dateTime = new \DateTime(sprintf(
167-
'%s-%s-%s %s:%s:%s',
168-
empty($value['year']) ? '1970' : $value['year'],
169-
empty($value['month']) ? '1' : $value['month'],
170-
empty($value['day']) ? '1' : $value['day'],
171-
empty($value['hour']) ? '0' : $value['hour'],
172-
empty($value['minute']) ? '0' : $value['minute'],
173-
empty($value['second']) ? '0' : $value['second']
174-
),
178+
if (\in_array('week', $this->fields)) {
179+
$date = sprintf(
180+
'%s-%s',
181+
empty($value['year']) ? '1970' : $value['year'],
182+
empty($value['week']) ? 'W01' : $value['week']
183+
);
184+
} else {
185+
$date = sprintf(
186+
'%s-%s-%s %s:%s:%s',
187+
empty($value['year']) ? '1970' : $value['year'],
188+
empty($value['month']) ? '1' : $value['month'],
189+
empty($value['day']) ? '1' : $value['day'],
190+
empty($value['hour']) ? '0' : $value['hour'],
191+
empty($value['minute']) ? '0' : $value['minute'],
192+
empty($value['second']) ? '0' : $value['second']
193+
);
194+
}
195+
$dateTime = new \DateTime($date,
175196
new \DateTimeZone($this->outputTimezone)
176197
);
177198

src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,28 @@ public function reverseTransform($value)
117117
}
118118

119119
$outputTz = new \DateTimeZone($this->outputTimezone);
120-
$dateTime = \DateTime::createFromFormat($this->parseFormat, $value, $outputTz);
120+
121+
if ('Y-\\WW|' === $this->parseFormat) {
122+
if (0 === preg_match('/^\d{4}\-W\d{2}$/', $value)) {
123+
throw new TransformationFailedException('Given data does not follow the date format "Y-\WW"');
124+
}
125+
126+
$weekData = explode('-W', $value);
127+
128+
// The 28th December is always in the last week of the year
129+
if (date('W', strtotime('28th December '.$weekData[0])) < $weekData[1]) {
130+
throw new TransformationFailedException(sprintf('Week No. %d does not exists for year %d', $weekData[1], $weekData[0]));
131+
}
132+
133+
$dateTime = (new \DateTime())->setISODate(...$weekData);
134+
135+
// For when the first day of the first week is in the previous year
136+
if ($dateTime->format('Y') < $weekData[0]) {
137+
$dateTime->setDate($weekData[0], '01', '01');
138+
}
139+
} else {
140+
$dateTime = \DateTime::createFromFormat($this->parseFormat, $value, $outputTz);
141+
}
121142

122143
$lastErrors = \DateTime::getLastErrors();
123144

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
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\Form\Extension\Core\DataTransformer\DateTimeImmutableToDateTimeTransformer;
16+
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer;
17+
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer;
18+
use Symfony\Component\Form\FormBuilderInterface;
19+
use Symfony\Component\Form\FormInterface;
20+
use Symfony\Component\Form\FormView;
21+
use Symfony\Component\Form\ReversedTransformer;
22+
use Symfony\Component\OptionsResolver\Options;
23+
use Symfony\Component\OptionsResolver\OptionsResolver;
24+
25+
class WeekType extends AbstractType
26+
{
27+
private static $widgets = [
28+
'text' => TextType::class,
29+
'choice' => ChoiceType::class,
30+
];
31+
32+
/**
33+
* {@inheritdoc}
34+
*/
35+
public function buildForm(FormBuilderInterface $builder, array $options)
36+
{
37+
$parts = ['year', 'week'];
38+
39+
$format = 'Y-\WW';
40+
41+
if ('single_text' === $options['widget']) {
42+
$builder->addViewTransformer(new DateTimeToStringTransformer($options['model_timezone'], $options['view_timezone'], $format));
43+
} else {
44+
$yearOptions = $weekOptions = [
45+
'error_bubbling' => true,
46+
'empty_data' => '',
47+
];
48+
// when the form is compound the entries of the array are ignored in favor of children data
49+
// so we need to handle the cascade setting here
50+
$emptyData = $builder->getEmptyData() ?: [];
51+
52+
if (isset($options['invalid_message'])) {
53+
$yearOptions['invalid_message'] = $options['invalid_message'];
54+
$weekOptions['invalid_message'] = $options['invalid_message'];
55+
}
56+
57+
if (isset($options['invalid_message_parameters'])) {
58+
$yearOptions['invalid_message_parameters'] = $options['invalid_message_parameters'];
59+
$weekOptions['invalid_message_parameters'] = $options['invalid_message_parameters'];
60+
}
61+
62+
if ('choice' === $options['widget']) {
63+
$years = $weeks = [];
64+
65+
foreach ($options['years'] as $year) {
66+
$years[str_pad($year, 2, '0', STR_PAD_LEFT)] = $year;
67+
}
68+
69+
foreach ($options['weeks'] as $week) {
70+
$weeks[str_pad($week, 2, '0', STR_PAD_LEFT)] = $week;
71+
}
72+
73+
// Only pass a subset of the options to children
74+
$yearOptions['choices'] = $years;
75+
$yearOptions['placeholder'] = $options['placeholder']['year'];
76+
$yearOptions['choice_translation_domain'] = $options['choice_translation_domain']['year'];
77+
78+
$weekOptions['choices'] = $weeks;
79+
$weekOptions['placeholder'] = $options['placeholder']['week'];
80+
$weekOptions['choice_translation_domain'] = $options['choice_translation_domain']['week'];
81+
82+
// Append generic carry-along options
83+
foreach (['required', 'translation_domain'] as $passOpt) {
84+
$yearOptions[$passOpt] = $options[$passOpt];
85+
86+
$weekOptions[$passOpt] = $options[$passOpt];
87+
}
88+
}
89+
90+
$builder->add('year', self::$widgets[$options['widget']], $yearOptions);
91+
92+
$builder->add('week', self::$widgets[$options['widget']], $weekOptions);
93+
94+
if (isset($emptyData['week'])) {
95+
$weekOptions['empty_data'] = $emptyData['week'];
96+
}
97+
$builder->add('week', self::$widgets[$options['widget']], $weekOptions);
98+
99+
$builder->addViewTransformer(new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts, 'text' === $options['widget']));
100+
}
101+
102+
if ('datetime_immutable' === $options['input']) {
103+
$builder->addModelTransformer(new DateTimeImmutableToDateTimeTransformer());
104+
} elseif ('string' === $options['input']) {
105+
$builder->addModelTransformer(new ReversedTransformer(
106+
new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone'], $format)
107+
));
108+
} elseif ('array' === $options['input']) {
109+
$builder->addModelTransformer(new ReversedTransformer(
110+
new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], $parts)
111+
));
112+
}
113+
}
114+
115+
/**
116+
* {@inheritdoc}
117+
*/
118+
public function buildView(FormView $view, FormInterface $form, array $options)
119+
{
120+
$view->vars = array_replace($view->vars, [
121+
'widget' => $options['widget'],
122+
]);
123+
124+
if ($options['html5'] && 'single_text' === $options['widget']) {
125+
$view->vars['type'] = 'week';
126+
}
127+
}
128+
129+
/**
130+
* {@inheritdoc}
131+
*/
132+
public function configureOptions(OptionsResolver $resolver)
133+
{
134+
$compound = function (Options $options) {
135+
return 'single_text' !== $options['widget'];
136+
};
137+
138+
$placeholderDefault = function (Options $options) {
139+
return $options['required'] ? null : '';
140+
};
141+
142+
$placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) {
143+
if (\is_array($placeholder)) {
144+
$default = $placeholderDefault($options);
145+
146+
return array_merge(
147+
['year' => $default, 'week' => $default],
148+
$placeholder
149+
);
150+
}
151+
152+
return [
153+
'year' => $placeholder,
154+
'week' => $placeholder,
155+
];
156+
};
157+
158+
$choiceTranslationDomainNormalizer = function (Options $options, $choiceTranslationDomain) {
159+
if (\is_array($choiceTranslationDomain)) {
160+
$default = false;
161+
162+
return array_replace(
163+
['year' => $default, 'week' => $default,],
164+
$choiceTranslationDomain
165+
);
166+
}
167+
168+
return [
169+
'year' => $choiceTranslationDomain,
170+
'week' => $choiceTranslationDomain,
171+
];
172+
};
173+
174+
$weeksNumbers = array_map(function ($item) {
175+
return sprintf('W%02d', $item);
176+
}, range(1, 53));
177+
178+
$resolver->setDefaults([
179+
'years' => range(date('Y') - 5, date('Y') + 5),
180+
'weeks' => array_combine($weeksNumbers, $weeksNumbers),
181+
'widget' => 'choice',
182+
'input' => 'datetime',
183+
'model_timezone' => null,
184+
'view_timezone' => null,
185+
'placeholder' => $placeholderDefault,
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+
'empty_data' => function (Options $options) {
197+
return $options['compound'] ? [] : '';
198+
},
199+
'compound' => $compound,
200+
'choice_translation_domain' => false,
201+
]);
202+
203+
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
204+
$resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
205+
206+
$resolver->setAllowedValues('input', [
207+
'datetime',
208+
'datetime_immutable',
209+
'string',
210+
'array',
211+
]);
212+
213+
$resolver->setAllowedValues('widget', [
214+
'single_text',
215+
'text',
216+
'choice',
217+
]);
218+
219+
$resolver->setAllowedTypes('years', 'array');
220+
$resolver->setAllowedTypes('weeks', 'array');
221+
}
222+
223+
/**
224+
* {@inheritdoc}
225+
*/
226+
public function getBlockPrefix()
227+
{
228+
return 'week';
229+
}
230+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy