1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Phone.php |
4
|
|
|
* |
5
|
|
|
* @copyright More in license.md |
6
|
|
|
* @license http://www.ipublikuj.eu |
7
|
|
|
* @author Adam Kadlec <[email protected]> |
8
|
|
|
* @package iPublikuj:FormPhone! |
9
|
|
|
* @subpackage Controls |
10
|
|
|
* @since 1.0.0 |
11
|
|
|
* |
12
|
|
|
* @date 15.12.15 |
13
|
|
|
*/ |
14
|
|
|
|
15
|
|
|
declare(strict_types = 1); |
16
|
|
|
|
17
|
|
|
namespace IPub\FormPhone\Controls; |
18
|
|
|
|
19
|
|
|
use Nette; |
20
|
|
|
use Nette\Forms; |
21
|
|
|
use Nette\Localization; |
22
|
|
|
use Nette\Utils; |
23
|
|
|
|
24
|
|
|
use IPub; |
25
|
|
|
use IPub\FormPhone; |
26
|
|
|
use IPub\FormPhone\Exceptions; |
27
|
|
|
|
28
|
|
|
use IPub\Phone\Phone as PhoneUtils; |
29
|
|
|
|
30
|
|
|
use libphonenumber; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Form phone control element |
34
|
|
|
* |
35
|
|
|
* @package iPublikuj:FormPhone! |
36
|
|
|
* @subpackage Controls |
37
|
|
|
* |
38
|
|
|
* @author Adam Kadlec <[email protected]> |
39
|
|
|
* |
40
|
|
|
* @property-read string $emptyValue |
41
|
|
|
* @property-read Nette\Forms\Rules $rules |
42
|
|
|
*/ |
43
|
1 |
|
class Phone extends Forms\Controls\TextInput |
44
|
|
|
{ |
45
|
|
|
/** |
46
|
|
|
* Define filed attributes |
47
|
|
|
*/ |
48
|
|
|
const FIELD_COUNTRY = 'country'; |
49
|
|
|
const FIELD_NUMBER = 'number'; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @var IPub\Phone\Phone |
53
|
|
|
*/ |
54
|
|
|
private $phoneUtils; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* List of allowed countries |
58
|
|
|
* |
59
|
|
|
* @var array |
60
|
|
|
*/ |
61
|
|
|
private $allowedCountries = []; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* List of allowed phone types |
65
|
|
|
* |
66
|
|
|
* @var array |
67
|
|
|
*/ |
68
|
|
|
private $allowedTypes = []; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* @var string|NULL |
72
|
|
|
*/ |
73
|
|
|
private $number = NULL; |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* @var string|NULL |
77
|
|
|
*/ |
78
|
|
|
private $country = NULL; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* @var string |
82
|
|
|
*/ |
83
|
|
|
private $defaultCountry; |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @var bool |
87
|
|
|
*/ |
88
|
|
|
private static $registered = FALSE; |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @param PhoneUtils $phoneUtils |
92
|
|
|
* @param string|NULL $label |
93
|
|
|
* @param int|NULL $maxLength |
94
|
|
|
*/ |
95
|
|
|
public function __construct(PhoneUtils $phoneUtils, string $label = NULL, int $maxLength = NULL) |
96
|
|
|
{ |
97
|
1 |
|
parent::__construct($label, $maxLength); |
98
|
|
|
|
99
|
1 |
|
$this->phoneUtils = $phoneUtils; |
100
|
1 |
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* @param array $countries |
104
|
|
|
* |
105
|
|
|
* @return $this |
106
|
|
|
* |
107
|
|
|
* @throws Exceptions\NoValidCountryException |
108
|
|
|
*/ |
109
|
|
|
public function setAllowedCountries(array $countries = []) |
110
|
|
|
{ |
111
|
1 |
|
$this->allowedCountries = []; |
112
|
|
|
|
113
|
1 |
|
foreach ($countries as $country) { |
114
|
1 |
|
$country = $this->validateCountry($country); |
115
|
1 |
|
$this->allowedCountries[] = strtoupper($country); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
// Check for auto country detection |
119
|
1 |
|
if (in_array('AUTO', $this->allowedCountries)) { |
120
|
|
|
$this->allowedCountries = ['AUTO']; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
// Remove duplicities |
124
|
1 |
|
array_unique($this->allowedCountries); |
125
|
|
|
|
126
|
1 |
|
return $this; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* @param string $country |
131
|
|
|
* |
132
|
|
|
* @return $this |
133
|
|
|
* |
134
|
|
|
* @throws Exceptions\NoValidCountryException |
135
|
|
|
*/ |
136
|
|
|
public function addAllowedCountry(string $country) |
137
|
|
|
{ |
138
|
1 |
|
$country = $this->validateCountry($country); |
139
|
1 |
|
$this->allowedCountries[] = strtoupper($country); |
140
|
|
|
|
141
|
|
|
// Remove duplicities |
142
|
1 |
|
array_unique($this->allowedCountries); |
143
|
|
|
|
144
|
1 |
|
if (strtoupper($country) === 'AUTO') { |
145
|
|
|
$this->allowedCountries = ['AUTO']; |
146
|
|
|
|
147
|
1 |
|
} elseif (($key = array_search('AUTO', $this->allowedCountries)) && $key !== FALSE) { |
148
|
|
|
unset($this->allowedCountries[$key]); |
149
|
|
|
} |
150
|
|
|
|
151
|
1 |
|
return $this; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* @return array |
156
|
|
|
*/ |
157
|
|
|
public function getAllowedCountries() : array |
158
|
|
|
{ |
159
|
1 |
|
if (in_array('AUTO', $this->allowedCountries, TRUE) || $this->allowedCountries === []) { |
160
|
1 |
|
return $this->phoneUtils->getSupportedCountries(); |
161
|
|
|
} |
162
|
|
|
|
163
|
1 |
|
return $this->allowedCountries; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* @param string|NULL $country |
168
|
|
|
* |
169
|
|
|
* @return $this |
170
|
|
|
* |
171
|
|
|
* @throws Exceptions\NoValidCountryException |
172
|
|
|
*/ |
173
|
|
|
public function setDefaultCountry(string $country = NULL) |
174
|
|
|
{ |
175
|
1 |
|
if ($country === NULL) { |
176
|
1 |
|
$this->defaultCountry = NULL; |
177
|
|
|
|
178
|
|
|
} else { |
179
|
1 |
|
$country = $this->validateCountry($country); |
180
|
|
|
|
181
|
1 |
|
$this->defaultCountry = strtoupper($country); |
182
|
|
|
} |
183
|
|
|
|
184
|
1 |
|
return $this; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* @param array $types |
189
|
|
|
* |
190
|
|
|
* @return $this |
191
|
|
|
* |
192
|
|
|
* @throws Exceptions\NoValidTypeException |
193
|
|
|
*/ |
194
|
|
|
public function setAllowedPhoneTypes(array $types = []) |
195
|
|
|
{ |
196
|
|
|
$this->allowedTypes = []; |
197
|
|
|
|
198
|
|
|
foreach ($types as $type) { |
199
|
|
|
$type = $this->validateType($type); |
200
|
|
|
$this->allowedTypes[] = strtoupper($type); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
// Remove duplicities |
204
|
|
|
array_unique($this->allowedTypes); |
205
|
|
|
|
206
|
|
|
return $this; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* @param string $type |
211
|
|
|
* |
212
|
|
|
* @return $this |
213
|
|
|
* |
214
|
|
|
* @throws Exceptions\NoValidTypeException |
215
|
|
|
*/ |
216
|
|
|
public function addAllowedPhoneType(string $type) |
217
|
|
|
{ |
218
|
1 |
|
$type = $this->validateType($type); |
219
|
1 |
|
$this->allowedTypes[] = strtoupper($type); |
220
|
|
|
|
221
|
|
|
// Remove duplicities |
222
|
1 |
|
array_unique($this->allowedTypes); |
223
|
|
|
|
224
|
1 |
|
return $this; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* @return array |
229
|
|
|
*/ |
230
|
|
|
public function getAllowedPhoneTypes() : array |
231
|
|
|
{ |
232
|
1 |
|
return $this->allowedTypes; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* @param string |
237
|
|
|
* |
238
|
|
|
* @return $this |
239
|
|
|
* |
240
|
|
|
* @throws Exceptions\InvalidArgumentException |
241
|
|
|
* @throws IPub\Phone\Exceptions\NoValidCountryException |
242
|
|
|
* @throws IPub\Phone\Exceptions\NoValidPhoneException |
243
|
|
|
*/ |
244
|
|
|
public function setValue($value) |
245
|
|
|
{ |
246
|
1 |
|
if ($value === NULL) { |
247
|
1 |
|
$this->country = NULL; |
248
|
1 |
|
$this->number = NULL; |
249
|
|
|
|
250
|
1 |
|
return $this; |
251
|
|
|
} |
252
|
|
|
|
253
|
1 |
|
if ($value instanceof IPub\Phone\Entities\Phone) { |
|
|
|
|
254
|
|
|
if (in_array($value->getCountry(), $this->getAllowedCountries(), TRUE)) { |
255
|
|
|
$this->country = $value->getCountry(); |
256
|
|
|
$this->number = str_replace(' ', '', $value->getNationalNumber()); |
257
|
|
|
|
258
|
|
|
return $this; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
} else { |
262
|
1 |
|
foreach ($this->getAllowedCountries() as $country) { |
263
|
1 |
|
if ($this->phoneUtils->isValid((string) $value, $country)) { |
264
|
1 |
|
$phone = IPub\Phone\Entities\Phone::fromNumber((string) $value, $country); |
265
|
|
|
|
266
|
1 |
|
$this->country = $phone->getCountry(); |
267
|
1 |
|
$this->number = str_replace(' ', '', $phone->getNationalNumber()); |
268
|
|
|
|
269
|
1 |
|
return $this; |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
} |
273
|
|
|
|
274
|
1 |
|
throw new Exceptions\InvalidArgumentException(sprintf('Provided value is not valid phone number, or is out of list of allowed countries, "%s" given.', $value)); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* @param string $key |
279
|
|
|
* |
280
|
|
|
* @return string |
281
|
|
|
* |
282
|
|
|
* @throws Exceptions\InvalidArgumentException |
283
|
|
|
*/ |
284
|
|
|
public function getValuePart(string $key) : string |
285
|
|
|
{ |
286
|
1 |
|
if ($key === self::FIELD_COUNTRY) { |
287
|
1 |
|
return $this->country; |
288
|
|
|
|
289
|
1 |
|
} elseif ($key === self::FIELD_NUMBER) { |
290
|
1 |
|
return $this->number; |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
throw new Exceptions\InvalidArgumentException('Invalid field key provided.'); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* @return IPub\Phone\Entities\Phone|NULL|FALSE |
298
|
|
|
*/ |
299
|
|
|
public function getValue() |
300
|
|
|
{ |
301
|
1 |
|
if ($this->country === NULL || $this->number === NULL) { |
302
|
1 |
|
return NULL; |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
try { |
306
|
|
|
// Try to parse number & country |
307
|
1 |
|
return IPub\Phone\Entities\Phone::fromNumber($this->number, $this->country); |
308
|
|
|
|
309
|
|
|
} catch (IPub\Phone\Exceptions\NoValidCountryException $ex) { |
|
|
|
|
310
|
|
|
return FALSE; |
311
|
|
|
|
312
|
|
|
} catch (IPub\Phone\Exceptions\NoValidPhoneException $ex) { |
|
|
|
|
313
|
|
|
return FALSE; |
314
|
|
|
} |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Loads HTTP data |
319
|
|
|
* |
320
|
|
|
* @return void |
321
|
|
|
*/ |
322
|
|
|
public function loadHttpData() |
323
|
|
|
{ |
324
|
1 |
|
$country = $this->getHttpData(Forms\Form::DATA_LINE, '[' . self::FIELD_COUNTRY . ']'); |
325
|
1 |
|
$this->country = ($country === '' || $country === NULL) ? NULL : (string) $country; |
326
|
|
|
|
327
|
1 |
|
$number = $this->getHttpData(Forms\Form::DATA_LINE, '[' . self::FIELD_NUMBER . ']'); |
328
|
1 |
|
$this->number = ($number === '' || $number === NULL) ? NULL : (string) $number; |
329
|
1 |
|
} |
330
|
|
|
|
331
|
|
|
/** |
332
|
|
|
* @return Utils\Html |
333
|
|
|
*/ |
334
|
|
|
public function getControl() |
335
|
|
|
{ |
336
|
1 |
|
$el = Utils\Html::el(); |
337
|
1 |
|
$el->addHtml($this->getControlPart(self::FIELD_COUNTRY) . $this->getControlPart(self::FIELD_NUMBER)); |
338
|
|
|
|
339
|
1 |
|
return $el; |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
/** |
343
|
|
|
* @return Utils\Html |
344
|
|
|
* |
345
|
|
|
* @throws Exceptions\InvalidArgumentException |
346
|
|
|
*/ |
347
|
|
|
public function getControlPart() |
348
|
|
|
{ |
349
|
1 |
|
$args = func_get_args(); |
350
|
1 |
|
$key = reset($args); |
351
|
|
|
|
352
|
1 |
|
$name = $this->getHtmlName(); |
353
|
|
|
|
354
|
1 |
|
if ($key === self::FIELD_COUNTRY) { |
355
|
|
|
// Try to get translator |
356
|
1 |
|
$translator = $this->getTranslator(); |
357
|
|
|
|
358
|
1 |
|
$locale = 'en_US'; |
359
|
|
|
|
360
|
1 |
|
if ($translator instanceof Localization\ITranslator && method_exists($translator, 'getLocale') === TRUE) { |
|
|
|
|
361
|
|
|
try { |
362
|
|
|
$locale = $translator->getLocale(); |
363
|
|
|
|
364
|
|
|
} catch (\Exception $ex) { |
365
|
|
|
$locale = 'en_US'; |
366
|
|
|
} |
367
|
|
|
} |
368
|
|
|
|
369
|
1 |
|
$items = array_reduce($this->getAllowedCountries(), function (array $result, $row) use ($locale) { |
370
|
1 |
|
$countryName = FormPhone\Locale\Locale::getDisplayRegion( |
371
|
1 |
|
FormPhone\Locale\Locale::countryCodeToLocale($row), |
372
|
|
|
$locale |
373
|
|
|
); |
374
|
|
|
|
375
|
1 |
|
$result[$row] = Utils\Html::el('option'); |
376
|
1 |
|
$result[$row]->setText('+' . $this->phoneUtils->getCountryCodeForCountry($row) . ' (' . $countryName . ')'); |
377
|
1 |
|
$result[$row]->data('mask', preg_replace('/[0-9]/', '9', $this->phoneUtils->getExampleNationalNumber($row))); |
378
|
1 |
|
$result[$row]->addAttributes([ |
379
|
1 |
|
'value' => $row, |
380
|
|
|
]); |
381
|
|
|
|
382
|
1 |
|
return $result; |
383
|
1 |
|
}, []); |
384
|
|
|
|
385
|
1 |
|
$control = Forms\Helpers::createSelectBox( |
386
|
|
|
$items, |
387
|
|
|
[ |
388
|
1 |
|
'selected?' => $this->country === NULL ? $this->defaultCountry : $this->country, |
389
|
|
|
] |
390
|
|
|
); |
391
|
|
|
|
392
|
1 |
|
$control->addAttributes([ |
393
|
1 |
|
'name' => $name . '[' . self::FIELD_COUNTRY . ']', |
394
|
1 |
|
'id' => $this->getHtmlId() . '-' . self::FIELD_COUNTRY, |
395
|
|
|
]); |
396
|
1 |
|
$control->data('ipub-forms-phone', ''); |
397
|
1 |
|
$control->data('settings', json_encode([ |
398
|
1 |
|
'field' => $name . '[' . self::FIELD_NUMBER . ']' |
399
|
|
|
]) |
400
|
|
|
); |
401
|
|
|
|
402
|
1 |
|
if ($this->isDisabled()) { |
403
|
|
|
$control->addAttributes([ |
404
|
|
|
'disabled' => TRUE, |
405
|
|
|
]); |
406
|
|
|
} |
407
|
|
|
|
408
|
1 |
|
return $control; |
409
|
|
|
|
410
|
1 |
|
} elseif ($key === self::FIELD_NUMBER) { |
411
|
1 |
|
$prototype = $this->getControlPrototype(); |
412
|
|
|
|
413
|
1 |
|
$input = parent::getControl(); |
|
|
|
|
414
|
|
|
|
415
|
1 |
|
$control = Utils\Html::el('input'); |
416
|
|
|
|
417
|
1 |
|
$control->addAttributes([ |
418
|
1 |
|
'name' => $name . '[' . self::FIELD_NUMBER . ']', |
419
|
1 |
|
'id' => $this->getHtmlId() . '-' . self::FIELD_NUMBER, |
420
|
1 |
|
'value' => $this->number, |
421
|
1 |
|
'type' => 'text', |
422
|
1 |
|
'class' => $prototype->getAttribute('class'), |
423
|
1 |
|
'placeholder' => $prototype->getAttribute('placeholder'), |
424
|
1 |
|
'title' => $prototype->getAttribute('title') |
425
|
|
|
]); |
426
|
|
|
|
427
|
1 |
|
$control->data('nette-empty-value', Utils\Strings::trim($this->translate($this->emptyValue))); |
428
|
1 |
|
$control->data('nette-rules', $input->{'data-nette-rules'}); |
429
|
|
|
|
430
|
1 |
|
if ($this->isDisabled()) { |
431
|
|
|
$control->addAttributes([ |
432
|
|
|
'disabled' => TRUE, |
433
|
|
|
]); |
434
|
|
|
} |
435
|
|
|
|
436
|
1 |
|
return $control; |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
throw new Exceptions\InvalidArgumentException(sprintf('Part "%s" does not exist.', $key)); |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
/** |
443
|
|
|
* {@inheritdoc} |
444
|
|
|
*/ |
445
|
|
|
public function getLabel($caption = NULL) |
446
|
|
|
{ |
447
|
|
|
$label = parent::getLabel($caption); |
448
|
|
|
$label->for = $this->getHtmlId() . '-' . self::FIELD_NUMBER; |
449
|
|
|
|
450
|
|
|
return $label; |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
/** |
454
|
|
|
* {@inheritdoc} |
455
|
|
|
*/ |
456
|
|
|
public function getLabelPart() |
457
|
|
|
{ |
458
|
1 |
|
return NULL; |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
/** |
462
|
|
|
* @param string $country |
463
|
|
|
* |
464
|
|
|
* @return string |
465
|
|
|
* |
466
|
|
|
* @throws Exceptions\NoValidCountryException |
467
|
|
|
*/ |
468
|
|
|
private function validateCountry(string $country) : string |
469
|
|
|
{ |
470
|
|
|
// Country code have to be upper-cased |
471
|
1 |
|
$country = strtoupper($country); |
472
|
|
|
|
473
|
1 |
|
if ((strlen($country) === 2 && ctype_alpha($country) && ctype_upper($country) && in_array($country, $this->phoneUtils->getSupportedCountries())) || $country === 'AUTO') { |
474
|
1 |
|
return $country; |
475
|
|
|
|
476
|
|
|
} else { |
477
|
1 |
|
throw new Exceptions\NoValidCountryException(sprintf('Provided country code "%s" is not valid. Provide valid country code or AUTO for automatic detection.', $country)); |
478
|
|
|
} |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
/** |
482
|
|
|
* @param string $type |
483
|
|
|
* |
484
|
|
|
* @return string |
485
|
|
|
* |
486
|
|
|
* @throws Exceptions\NoValidTypeException |
487
|
|
|
*/ |
488
|
|
|
private function validateType(string $type) : string |
489
|
|
|
{ |
490
|
|
|
// Phone type have to be upper-cased |
491
|
1 |
|
$type = strtoupper($type); |
492
|
|
|
|
493
|
1 |
|
if (defined('\IPub\Phone\Phone::TYPE_' . $type)) { |
494
|
1 |
|
return $type; |
495
|
|
|
|
496
|
|
|
} else { |
497
|
|
|
throw new Exceptions\NoValidTypeException(sprintf('Provided phone type "%s" is not valid. Provide valid phone type.', $type)); |
498
|
|
|
} |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
/** |
502
|
|
|
* @param PhoneUtils $phoneUtils |
503
|
|
|
* @param string $method |
504
|
|
|
*/ |
505
|
|
|
public static function register(PhoneUtils $phoneUtils, string $method = 'addPhone') |
506
|
|
|
{ |
507
|
|
|
// Check for multiple registration |
508
|
1 |
|
if (self::$registered) { |
509
|
1 |
|
throw new Nette\InvalidStateException('Phone control already registered.'); |
510
|
|
|
} |
511
|
|
|
|
512
|
1 |
|
self::$registered = TRUE; |
513
|
|
|
|
514
|
1 |
|
$class = function_exists('get_called_class') ? get_called_class() : __CLASS__; |
515
|
1 |
|
Forms\Container::extensionMethod( |
516
|
1 |
|
$method, function (Forms\Container $form, $name, $label = NULL, $maxLength = NULL) use ($class, $phoneUtils) { |
517
|
1 |
|
$component = new $class($phoneUtils, $label, $maxLength); |
518
|
1 |
|
$form->addComponent($component, $name); |
519
|
|
|
|
520
|
1 |
|
return $component; |
521
|
1 |
|
} |
522
|
|
|
); |
523
|
1 |
|
} |
524
|
|
|
} |
525
|
|
|
|
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.