├── .gitignore
├── Resources
├── docs
│ └── example.png
├── translations
│ ├── messages.sv.yml
│ ├── messages.en.yml
│ ├── validators.en.yml
│ └── validators.sv.yml
├── config
│ └── services.yml
└── views
│ └── Form
│ └── fields.html.twig
├── .travis.yml
├── HappyrBirthdayBundle.php
├── README.md
├── DependencyInjection
├── Configuration.php
└── HappyrBirthdayExtension.php
├── composer.json
├── phpunit.xml.dist
└── From
├── Transformer
└── BirthdayTransformer.php
└── BirthdayType.php
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | phpunit.xml
3 | composer.lock
4 | vendor
5 |
--------------------------------------------------------------------------------
/Resources/docs/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Happyr/BirthdayBundle/master/Resources/docs/example.png
--------------------------------------------------------------------------------
/Resources/translations/messages.sv.yml:
--------------------------------------------------------------------------------
1 | happyr.birthday:
2 | form:
3 | year: 'År'
4 | month: 'Månad'
5 | day: 'Dag'
--------------------------------------------------------------------------------
/Resources/translations/messages.en.yml:
--------------------------------------------------------------------------------
1 | happyr.birthday:
2 | form:
3 | year: 'Year'
4 | month: 'Month'
5 | day: 'Day'
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.4
5 | - 5.5
6 | - 5.6
7 | - hhvm
8 |
9 | before_install:
10 | - composer self-update
11 |
12 | install:
13 | - composer update
14 |
15 | script:
16 | - phpunit
--------------------------------------------------------------------------------
/Resources/config/services.yml:
--------------------------------------------------------------------------------
1 | parameters:
2 |
3 | services:
4 | happyr.birthday.form.birthday:
5 | class: Happyr\BirthdayBundle\From\BirthdayType
6 | arguments: ["@translator"]
7 | tags:
8 | - { name: form.type, alias: happyr_birthday }
--------------------------------------------------------------------------------
/HappyrBirthdayBundle.php:
--------------------------------------------------------------------------------
1 |
6 |
7 | {{ form_widget(form.year) }}
8 | {{ form_widget(form.month) }}
9 | {{ form_widget(form.day) }}
10 |
11 |
12 | {% endspaceless %}
13 | {% endblock %}
14 |
--------------------------------------------------------------------------------
/Resources/translations/validators.en.yml:
--------------------------------------------------------------------------------
1 | happyr.birthday:
2 | form:
3 | future: 'The date you entered is in the future.'
4 | incomplete: 'Date is incomplete'
5 | unknown_error: 'The birthday field does not look right.'
6 | year:
7 | max_message: 'Oldest age allowed is %limit% years.'
8 | min_message: 'Youngest age allowed is %limit% years.'
9 | invalid: 'Invalid date'
10 | format_error: 'Enter a year with 4 digits.'
11 | month.invalid: 'Invalid date'
12 | day.invalid: 'Invalid date'
--------------------------------------------------------------------------------
/Resources/translations/validators.sv.yml:
--------------------------------------------------------------------------------
1 | happyr.birthday:
2 | form:
3 | future: 'Du har angivit ett datum i framtiden. '
4 | incomplete: 'Datumet är bara delvis ifyllt.'
5 | unknown_error: 'Det ser ut att vara något fel med födelsedags-fältet.'
6 | year:
7 | max_message: 'Hösta tillåtna ålder är %limit% år.'
8 | min_message: 'Minsta tillåtna ålder är %limit% år.'
9 | invalid: 'Felaktigt datum'
10 | format_error: 'Ange ett årtal med 4 siffror.'
11 | month.invalid: 'Felaktigt datum'
12 | day.invalid: 'Felaktigt datum'
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Happyr Birthday Bundle
2 | ======================
3 |
4 | Get a nice looking form for birthdays. Enter the year manually and then drop down for month and day.
5 |
6 | 
7 |
8 |
9 | ## Install
10 |
11 | ``` yml
12 | // app/config/config.yml
13 | twig:
14 | form:
15 | resources:
16 | - 'HappyrBirthdayBundle:Form:fields.html.twig'
17 |
18 | ```
19 |
20 | ## Usage
21 |
22 | ``` php
23 |
24 | //WhateverFormType.php
25 |
26 | public function buildForm(FormBuilderInterface $builder, array $options)
27 | {
28 | $builder->add('birthday', 'happyr_birthday')
29 | }
30 | ```
--------------------------------------------------------------------------------
/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | root('happyr_birthday');
21 |
22 | return $treeBuilder;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "happyr/birthday-bundle",
3 | "type": "symfony-bundle",
4 | "description": "Happyr birthday bundle gives you a nicer way to render birthday widgets",
5 | "keywords": ["Birthday"],
6 | "homepage": "http://developer.happyr.com",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Tobias Nyholm",
11 | "email": "tobias@happyr.com"
12 | }
13 | ],
14 | "require": {
15 | "php": ">=5.4",
16 | "symfony/framework-bundle": "^2.3 || ^3.0"
17 | },
18 | "autoload": {
19 | "psr-0": {
20 | "Happyr\\BirthdayBundle\\": ""
21 | }
22 | },
23 | "target-dir": "Happyr/BirthdayBundle"
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/DependencyInjection/HappyrBirthdayExtension.php:
--------------------------------------------------------------------------------
1 | processConfiguration($configuration, $configs);
24 |
25 | $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
26 | $loader->load('services.yml');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
18 |
19 | ./Tests
20 |
21 |
22 |
23 |
24 |
25 | ./
26 |
27 | vendor
28 | Tests
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/From/Transformer/BirthdayTransformer.php:
--------------------------------------------------------------------------------
1 | norm
16 | */
17 | public function transform($date)
18 | {
19 | if ($date === null || !($date instanceof \DateTime)) {
20 | return null;
21 | }
22 |
23 | return array(
24 | 'year' => $date->format('Y'),
25 | 'month' => $date->format('n'),
26 | 'day' => $date->format('j'),
27 | );
28 | }
29 |
30 | /**
31 | * @param mixed $data
32 | *
33 | * @return \DateTime|mixed
34 | */
35 | public function reverseTransform($data)
36 | {
37 | if ($data['year'] === null && $data['month'] === null && $data['day'] === null) {
38 | //if left untouched
39 | return null;
40 | } elseif ($data['year'] === null || $data['month'] === null || $data['day'] === null) {
41 | //if just partially filled in
42 | return 'incomplete';
43 | } elseif (!is_int($data['year']) || strlen($data['year']) != 4 || $data['year'] < 0) {
44 | return 'error';
45 | }
46 |
47 | try {
48 | $date = new \DateTime(sprintf('%d-%d-%d', $data['year'], $data['month'], $data['day']));
49 | } catch (\Exception $e) {
50 | return 'unknown_error';
51 | }
52 |
53 | return $date;
54 | }
55 | }
--------------------------------------------------------------------------------
/From/BirthdayType.php:
--------------------------------------------------------------------------------
1 | translator = $translator;
52 | }
53 |
54 | public function buildForm(FormBuilderInterface $builder, array $options)
55 | {
56 | $timeFormat = \IntlDateFormatter::NONE;
57 | $calendar = \IntlDateFormatter::GREGORIAN;
58 |
59 | $yearOptions = $monthOptions = $dayOptions = array(
60 | 'error_bubbling' => true,
61 | );
62 |
63 | $formatter = new \IntlDateFormatter(
64 | \Locale::getDefault(),
65 | $options['format'],
66 | $timeFormat,
67 | 'UTC',
68 | $calendar
69 | );
70 | $formatter->setLenient(false);
71 |
72 |
73 | $currentYear=date('Y');
74 | // Only pass a subset of the options to children
75 | $yearOptions['label'] =$yearOptions['attr']['placeholder'] = $options['placeholder']['year'];
76 | $yearOptions['attr']['min'] = $currentYear-$options['max_age'];
77 | $yearOptions['attr']['max'] = $currentYear-$options['min_age'];
78 |
79 | $monthOptions['choices'] = $this->formatTimestamps($formatter, '/[M|L]+/', $this->listMonths($options['months']));
80 | $dayOptions['label'] = $monthOptions['placeholder'] = $this->translator->trans($options['placeholder']['month']);
81 | $monthOptions['choice_translation_domain'] = false;
82 |
83 | $dayOptions['choices'] = $this->formatTimestamps($formatter, '/d+/', $this->listDays($options['days']));
84 | $dayOptions['label'] = $dayOptions['placeholder'] = $this->translator->trans($options['placeholder']['day']);
85 | $dayOptions['choice_translation_domain'] = false;
86 |
87 |
88 | // Append generic carry-along options
89 | foreach (array('required', 'translation_domain') as $passOpt) {
90 | $yearOptions[$passOpt] = $monthOptions[$passOpt] = $dayOptions[$passOpt] = $options[$passOpt];
91 | }
92 |
93 | $this->doBuildForm($builder, $yearOptions, $monthOptions, $dayOptions, $formatter);
94 | $this->addValidation($builder, $options);
95 | }
96 |
97 | /**
98 | * Add validation
99 | *
100 | * @param FormBuilderInterface $builder
101 | * @param array $options
102 | */
103 | protected function addValidation(FormBuilderInterface $builder, array $options)
104 | {
105 | $builder->addEventListener(FormEvents::SUBMIT, function(FormEvent $event) use ($options) {
106 | $form = $event->getForm();
107 | $date=$event->getData();
108 |
109 | if ($date === null) {
110 | return;
111 | } elseif ($date==='incomplete') {
112 | $form->addError(new FormError('happyr.birthday.form.incomplete'));
113 | $event->setData(null);
114 | return;
115 | } elseif ($date==='error') {
116 | $form->addError(new FormError('happyr.birthday.form.year.format_error'));
117 | $event->setData(null);
118 | return;
119 | } elseif ($date==='unknown_error') {
120 | $form->addError(new FormError('happyr.birthday.form.unknown_error'));
121 | $event->setData(null);
122 | return;
123 | }
124 |
125 | $yearField = $form->get('year');
126 |
127 | /*
128 | * Verify date
129 | */
130 | if ($date->format('Y') != $yearField->getData()) {
131 | $form->addError(new FormError('happyr.birthday.form.year.invalid'));
132 | return;
133 | }
134 |
135 | if ($date->format('n') != $form->get('month')->getData()) {
136 | $form->addError(new FormError('happyr.birthday.form.month.invalid'));
137 | return;
138 | }
139 |
140 | if ($date->format('j') != $form->get('day')->getData()) {
141 | $form->addError(new FormError('happyr.birthday.form.day.invalid'));
142 | return;
143 | }
144 |
145 | /*
146 | * Verify age
147 | */
148 | $now = new \DateTime();
149 | $age = $date->diff($now)->format('%r%y');
150 |
151 | if ($now < $date) {
152 | $form->addError(new FormError('happyr.birthday.form.future'));
153 | } else if ($age > $options['max_age']) {
154 | $form->addError(new FormError('happyr.birthday.form.year.max_message', null, array('%limit%'=>$options['max_age'])));
155 | } else if ($age < $options['min_age']) {
156 | $form->addError(new FormError('happyr.birthday.form.year.min_message', null, array('%limit%'=>$options['min_age'])));
157 | }
158 | });
159 | }
160 |
161 | public function configureOptions(OptionsResolver $resolver)
162 | {
163 | $resolver->setDefaults(array(
164 | 'max_age' => 120,
165 | 'min_age' => 0,
166 | 'compound'=>true,
167 | 'months' => range(1, 12),
168 | 'days' => range(1, 31),
169 | 'input' => 'datetime',
170 | 'placeholder' => array(
171 | 'year' => 'happyr.birthday.form.year',
172 | 'month' => 'happyr.birthday.form.month',
173 | 'day' => 'happyr.birthday.form.day'
174 | ),
175 | 'format' => self::DEFAULT_FORMAT,
176 | 'error_bubbling' => false,
177 | ));
178 | }
179 |
180 |
181 | private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $timestamps)
182 | {
183 | $pattern = $formatter->getPattern();
184 | $timezone = $formatter->getTimezoneId();
185 |
186 | if ($setTimeZone = method_exists($formatter, 'setTimeZone')) {
187 | $formatter->setTimeZone('UTC');
188 | } else {
189 | $formatter->setTimeZoneId('UTC');
190 | }
191 |
192 | if (preg_match($regex, $pattern, $matches)) {
193 | $formatter->setPattern($matches[0]);
194 |
195 | foreach ($timestamps as $key => $timestamp) {
196 | $timestamps[$key] = $formatter->format($timestamp);
197 | }
198 |
199 | // I'd like to clone the formatter above, but then we get a
200 | // segmentation fault, so let's restore the old state instead
201 | $formatter->setPattern($pattern);
202 | }
203 |
204 | if ($setTimeZone) {
205 | $formatter->setTimeZone($timezone);
206 | } else {
207 | $formatter->setTimeZoneId($timezone);
208 | }
209 |
210 | return array_flip($timestamps);
211 | }
212 |
213 | private function listMonths(array $months)
214 | {
215 | $result = array();
216 |
217 | foreach ($months as $month) {
218 | $result[$month] = gmmktime(0, 0, 0, $month, 15);
219 | }
220 |
221 | return $result;
222 | }
223 |
224 | private function listDays(array $days)
225 | {
226 | $result = array();
227 |
228 | foreach ($days as $day) {
229 | $result[$day] = gmmktime(0, 0, 0, 5, $day);
230 | }
231 |
232 | return $result;
233 | }
234 |
235 | /**
236 | * Returns the name of this type.
237 | *
238 | * @return string The name of this type
239 | */
240 | public function getBlockPrefix()
241 | {
242 | return 'happyr_birthday';
243 | }
244 |
245 | /**
246 | * @param FormBuilderInterface $builder
247 | * @param $yearOptions
248 | * @param $monthOptions
249 | * @param $dayOptions
250 | * @param $formatter
251 | *
252 | * @return \Symfony\Component\Form\FormConfigBuilderInterface
253 | */
254 | protected function doBuildForm(FormBuilderInterface $builder, $yearOptions, $monthOptions, $dayOptions, $formatter)
255 | {
256 | return $builder
257 | ->add('year', IntegerType::class, $yearOptions)
258 | ->add('month', ChoiceType::class, $monthOptions)
259 | ->add('day', ChoiceType::class, $dayOptions)
260 | ->addViewTransformer(new BirthdayTransformer())
261 | ->setAttribute('formatter', $formatter);
262 | }
263 | }
--------------------------------------------------------------------------------