Completed
Push — master ( 3b478a...55def5 )
by Adam
03:25
created

Phone::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4286
cc 1
eloc 3
nc 1
nop 3
crap 1
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
namespace IPub\FormPhone\Controls;
16
17
use Nette;
18
use Nette\Forms;
19
use Nette\Localization;
20
use Nette\Utils;
21
22
use IPub;
23
use IPub\FormPhone;
24
use IPub\FormPhone\Exceptions;
25
26
use IPub\Phone\Phone as PhoneUtils;
27 1
28 1
use libphonenumber;
29
use libphonenumber\geocoding;
30
31
/**
32
 * Form phone control element
33
 *
34
 * @package        iPublikuj:FormPhone!
35
 * @subpackage     Controls
36
 *
37
 * @author         Adam Kadlec <[email protected]>
38
 *
39
 * @property-read string $emptyValue
40
 * @property-read Nette\Forms\Rules $rules
41
 */
42
class Phone extends Forms\Controls\TextInput
43 1
{
44
	/**
45
	 * Define filed attributes
46
	 */
47
	const FIELD_COUNTRY = 'country';
48
	const FIELD_NUMBER = 'number';
49
50
	/**
51
	 * @var IPub\Phone\Phone
52
	 */
53
	private $phoneUtils;
54
55
	/**
56
	 * List of allowed countries
57
	 *
58
	 * @var array
59
	 */
60
	private $allowedCountries = [];
61
62 1
	/**
63
	 * List of allowed phone types
64
	 *
65
	 * @var array
66
	 */
67
	private $allowedTypes = [];
68
69
	/**
70
	 * @var string|NULL
71
	 */
72
	private $number = NULL;
73
74
	/**
75
	 * @var string|NULL
76
	 */
77
	private $country = NULL;
78
79
	/**
80
	 * @var string
81
	 */
82
	private $defaultCountry;
83
84
	/**
85
	 * @var bool
86
	 */
87
	private static $registered = FALSE;
88
89 1
	/**
90
	 * @param PhoneUtils $phoneUtils
91
	 * @param string|NULL $label
92
	 * @param int|NULL $maxLength
93
	 */
94
	public function __construct(PhoneUtils $phoneUtils, $label = NULL, $maxLength = NULL)
95
	{
96 1
		parent::__construct($label, $maxLength);
97
98 1
		$this->phoneUtils = $phoneUtils;
99 1
	}
100
101
	/**
102
	 * @param array $countries
103
	 *
104
	 * @return $this
105
	 *
106
	 * @throws Exceptions\NoValidCountryException
107
	 */
108
	public function setAllowedCountries(array $countries = [])
109
	{
110 1
		$this->allowedCountries = [];
111
112 1
		foreach($countries as $country)
113
		{
114 1
			$country = $this->validateCountry($country);
115 1
			$this->allowedCountries[] = strtoupper($country);
116 1
		}
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 1
	 *
134
	 * @throws Exceptions\NoValidCountryException
135
	 */
136
	public function addAllowedCountry($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
		} else if (($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()
158
	{
159 1
		if (in_array('AUTO', $this->allowedCountries, TRUE) || $this->allowedCountries === []) {
160 1
			return $this->phoneUtils->getSupportedCountries();
161
162
		} else {
163 1
			return $this->allowedCountries;
164
		}
165
	}
166
167
	/**
168
	 * @param string|NULL $country
169
	 *
170
	 * @return $this
171
	 *
172
	 * @throws Exceptions\NoValidCountryException
173
	 */
174
	public function setDefaultCountry($country = NULL)
175
	{
176 1
		if ($country === NULL) {
177 1
			$this->defaultCountry = NULL;
178
179 1
		} else {
180 1
			$country = $this->validateCountry($country);
181
182 1
			$this->defaultCountry = strtoupper($country);
183
		}
184 1
185 1
		return $this;
186
	}
187
188
	/**
189 1
	 * @param array $types
190 1
	 *
191
	 * @return $this
192
	 *
193
	 * @throws Exceptions\NoValidTypeException
194
	 */
195
	public function setAllowedPhoneTypes(array $types = [])
196
	{
197
		$this->allowedTypes = [];
198
199
		foreach($types as $type)
200
		{
201
			$type = $this->validateType($type);
202
			$this->allowedTypes[] = strtoupper($type);
203
		}
204
205
		// Remove duplicities
206
		array_unique($this->allowedTypes);
207
208
		return $this;
209
	}
210
211
	/**
212
	 * @param string $type
213
	 *
214
	 * @return $this
215
	 *
216
	 * @throws Exceptions\NoValidTypeException
217
	 */
218
	public function addAllowedPhoneType($type)
219
	{
220 1
		$type = $this->validateType($type);
221 1
		$this->allowedTypes[] = strtoupper($type);
222
223
		// Remove duplicities
224 1
		array_unique($this->allowedTypes);
225
226 1
		return $this;
227
	}
228
229
	/**
230
	 * @return array
231
	 */
232
	public function getAllowedPhoneTypes()
233
	{
234 1
		return $this->allowedTypes;
235
	}
236
237
	/**
238
	 * @param string
239
	 *
240
	 * @return $this
241
	 *
242
	 * @throws Exceptions\InvalidArgumentException
243
	 * @throws IPub\Phone\Exceptions\NoValidCountryException
244
	 * @throws IPub\Phone\Exceptions\NoValidPhoneException
245
	 */
246
	public function setValue($value)
247
	{
248 1
		if ($value === NULL) {
249 1
			$this->country = NULL;
250 1
			$this->number = NULL;
251
252 1
			return $this;
253
		}
254
255 1
		foreach($this->getAllowedCountries() as $country) {
256 1
			if ($this->phoneUtils->isValid($value, $country)) {
257 1
				$phone = IPub\Phone\Entities\Phone::fromNumber($value, $country);
258
259 1
				$this->country = $phone->getCountry();
260 1
				$this->number = str_replace(' ', '', $phone->getNationalNumber());
261
262 1
				return $this;
263
			}
264 1
		}
265
266 1
		throw new Exceptions\InvalidArgumentException('Provided value is not valid phone number, or is out of list of allowed countries, "' . $value . '" given.');
267
	}
268
269
	/**
270
	 * @return IPub\Phone\Entities\Phone|NULL
271
	 */
272
	public function getValue()
273
	{
274 1
		if ($this->country === NULL || $this->number === NULL) {
275 1
			return NULL;
276
		}
277
278
		try {
279
			// Try to parse number & country
280 1
			$number = IPub\Phone\Entities\Phone::fromNumber($this->number, $this->country);
281
282 1
			return $number === NULL ? NULL : $number;
283
284 1
		} catch (IPub\Phone\Exceptions\NoValidCountryException $ex) {
285
			return NULL;
286
287 1
		} catch (IPub\Phone\Exceptions\NoValidPhoneException $ex) {
288 1
			return NULL;
289
		}
290
	}
291
292
	/**
293
	 * Loads HTTP data
294
	 *
295
	 * @return void
296
	 */
297
	public function loadHttpData()
298
	{
299 1
		$country = $this->getHttpData(Forms\Form::DATA_LINE, '[' . static::FIELD_COUNTRY . ']');
300 1
		$this->country = ($country === '' || $country === NULL) ? NULL : (string) $country;
301
302 1
		$number = $this->getHttpData(Forms\Form::DATA_LINE, '[' . static::FIELD_NUMBER . ']');
303 1
		$this->number = ($number === '' || $number === NULL) ? NULL : (string) $number;
304 1
	}
305
306 1
	/**
307 1
	 * @return Utils\Html
308
	 */
309 1
	public function getControl()
310
	{
311 1
		return Utils\Html::el()
312 1
			->add($this->getControlPart(static::FIELD_COUNTRY) . $this->getControlPart(static::FIELD_NUMBER));
313
	}
314
315
	/**
316
	 * @param string $key
317
	 *
318
	 * @return Utils\Html
319
	 *
320
	 * @throws Exceptions\InvalidArgumentException
321
	 */
322
	public function getControlPart($key)
323
	{
324 1
		$name = $this->getHtmlName();
325
326
		// Try to get translator
327 1
		$translator = $this->getTranslator();
328
329 1
		if ($translator instanceof Localization\ITranslator && method_exists($translator, 'getLocale') === TRUE) {
330
			try {
331
				$locale = $translator->getLocale();
0 ignored issues
show
Bug introduced by
The method getLocale() does not seem to exist on object<Nette\Localization\ITranslator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
332
333
			} catch (\Exception $ex) {
334
				$locale = 'en_US';
335
			}
336 1
337
		} else {
338 1
			$locale = 'en_US';
339
		}
340
341 1
		if ($key === static::FIELD_COUNTRY) {
342 1
			$control = Forms\Helpers::createSelectBox(
343 1
				array_reduce($this->getAllowedCountries(), function (array $result, $row) use ($locale) {
344 1
					$countryName = geocoding\Locale::getDisplayRegion(
345 1
						geocoding\Locale::countryCodeToLocale($row),
346
						$locale
347 1
					);
348
349 1
					$result[$row] = Utils\Html::el('option')
350 1
						->setText('+' . $this->phoneUtils->getCountryCodeForCountry($row) . ' ('. $countryName . ')')
351 1
						->addAttributes([
352 1
							'data-mask' => preg_replace('/[0-9]/', '9', $this->phoneUtils->getExampleNationalNumber($row)),
353 1
						])
354 1
						->value($row);
355
356 1
					return $result;
357 1
				}, []),
358
				[
359 1
					'selected?' => $this->country === NULL ? $this->defaultCountry : $this->country,
360
				]
361 1
			);
362
363 1
			$control->addAttributes([
364 1
				'name'	=> $name . '[' . static::FIELD_COUNTRY . ']',
365 1
				'id'	=> $this->getHtmlId() . '-' . static::FIELD_COUNTRY,
366
367 1
				'data-ipub-forms-phone'	=> '',
368 1
				'data-settings'			=> json_encode([
369 1
					'field' => $name . '[' . static::FIELD_NUMBER . ']'
370 1
				])
371 1
			]);
372
373 1
			if ($this->isDisabled()) {
374
				$control->disabled(TRUE);
375
			}
376
377 1
			return $control;
378
379 1
		} else if ($key === static::FIELD_NUMBER) {
380 1
			$input = parent::getControl();
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (getControl() instead of getControlPart()). Are you sure this is correct? If so, you might want to change this to $this->getControl().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
381
382 1
			$control = Utils\Html::el('input');
383
384 1
			$control->addAttributes([
385 1
				'name'	=> $name . '[' . static::FIELD_NUMBER . ']',
386 1
				'id'	=> $this->getHtmlId() . '-' . static::FIELD_NUMBER,
387 1
				'value'	=> $this->number,
388 1
				'type'	=> 'text',
389
390 1
				'data-nette-empty-value'	=> Utils\Strings::trim($this->translate($this->emptyValue)),
391 1
				'data-nette-rules'			=> $input->{'data-nette-rules'},
392 1
			]);
393
394 1
			if ($this->isDisabled()) {
395
				$control->disabled(TRUE);
396
			}
397
398 1
			return $control;
399
		}
400
401
		throw new Exceptions\InvalidArgumentException('Part ' . $key . ' does not exist.');
402
	}
403
404
	/**
405
	 * @return NULL
406
	 */
407
	public function getLabelPart()
408
	{
409 1
		return NULL;
410
	}
411
412
	/**
413
	 * @param string $country
414
	 *
415
	 * @return string
416
	 *
417
	 * @throws Exceptions\NoValidCountryException
418
	 */
419
	protected function validateCountry($country)
420
	{
421
		// Country code have to be upper-cased
422 1
		$country = strtoupper($country);
423
424 1
		if ((strlen($country) === 2 && ctype_alpha($country) && ctype_upper($country) && in_array($country, $this->phoneUtils->getSupportedCountries())) || $country === 'AUTO') {
425 1
			return $country;
426
427
		} else {
428 1
			throw new Exceptions\NoValidCountryException('Provided country code "' . $country . '" is not valid. Provide valid country code or AUTO for automatic detection.');
429
		}
430
	}
431
432
	/**
433
	 * @param string $type
434
	 *
435
	 * @return string
436
	 *
437
	 * @throws Exceptions\NoValidTypeException
438
	 */
439
	protected function validateType($type)
440
	{
441
		// Phone type have to be upper-cased
442 1
		$type = strtoupper($type);
443
444 1
		if (defined('\IPub\Phone\Phone::TYPE_' . $type)) {
445 1
			return $type;
446
447
		} else {
448
			throw new Exceptions\NoValidTypeException('Provided phone type "' . $type . '" is not valid. Provide valid phone type.');
449
		}
450
	}
451
452
	/**
453
	 * @param PhoneUtils $phoneUtils
454
	 * @param string $method
455
	 */
456
	public static function register(PhoneUtils $phoneUtils, $method = 'addPhone')
457
	{
458
		// Check for multiple registration
459 1
		if (self::$registered) {
460 1
			throw new Nette\InvalidStateException('Phone control already registered.');
461
		}
462
463 1
		self::$registered = TRUE;
464
465 1
		$class = function_exists('get_called_class') ? get_called_class() : __CLASS__;
466 1
		Forms\Container::extensionMethod(
467 1
			$method, function (Forms\Container $form, $name, $label = NULL, $maxLength = NULL) use ($class, $phoneUtils) {
468 1
			$component = new $class($phoneUtils, $label, $maxLength);
469 1
			$form->addComponent($component, $name);
470
471 1
			return $component;
472
		}
473 1
		);
474 1
	}
475
}
476