Test Setup Failed
Push — master ( 3044dd...9e3d62 )
by Adam
07:01
created

Phone::getValue()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 7
cts 7
cp 1
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 9
nc 4
nop 0
crap 5
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 1
28 1
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 1
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 1
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 1
	{
97
		parent::__construct($label, $maxLength);
98 1
99 1
		$this->phoneUtils = $phoneUtils;
100
	}
101
102
	/**
103
	 * @param array $countries
104
	 *
105
	 * @return $this
106
	 *
107
	 * @throws Exceptions\NoValidCountryException
108
	 */
109
	public function setAllowedCountries(array $countries = [])
110 1
	{
111
		$this->allowedCountries = [];
112 1
113 1
		foreach ($countries as $country) {
114 1
			$country = $this->validateCountry($country);
115 1
			$this->allowedCountries[] = strtoupper($country);
116
		}
117
118 1
		// Check for auto country detection
119
		if (in_array('AUTO', $this->allowedCountries)) {
120
			$this->allowedCountries = ['AUTO'];
121
		}
122
123 1
		// Remove duplicities
124
		array_unique($this->allowedCountries);
125 1
126
		return $this;
127
	}
128
129
	/**
130
	 * @param string $country
131
	 *
132
	 * @return $this
133 1
	 *
134
	 * @throws Exceptions\NoValidCountryException
135
	 */
136
	public function addAllowedCountry(string $country)
137 1
	{
138 1
		$country = $this->validateCountry($country);
139
		$this->allowedCountries[] = strtoupper($country);
140
141 1
		// Remove duplicities
142
		array_unique($this->allowedCountries);
143 1
144
		if (strtoupper($country) === 'AUTO') {
145
			$this->allowedCountries = ['AUTO'];
146 1
147
		} elseif (($key = array_search('AUTO', $this->allowedCountries)) && $key !== FALSE) {
148
			unset($this->allowedCountries[$key]);
149
		}
150 1
151
		return $this;
152
	}
153
154
	/**
155
	 * @return array
156
	 */
157
	public function getAllowedCountries() : array
158 1
	{
159 1
		if (in_array('AUTO', $this->allowedCountries, TRUE) || $this->allowedCountries === []) {
160
			return $this->phoneUtils->getSupportedCountries();
161
		}
162 1
163
		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 1
		} 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 1
	 *
190 1
	 * @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) {
0 ignored issues
show
Bug introduced by
The class IPub\Phone\Entities\Phone does not exist. Did you forget a USE statement, or did you not list all dependencies?

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 the composer.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 or require-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 ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
254 1
			if (in_array($value->getCountry(), $this->getAllowedCountries(), TRUE)) {
255 1
				$this->country = $value->getCountry();
256
				$this->number = str_replace(' ', '', $value->getNationalNumber());
257 1
258 1
				return $this;
259
			}
260 1
261
		} else {
262 1
			foreach ($this->getAllowedCountries() as $country) {
263
				if ($this->phoneUtils->isValid((string) $value, $country)) {
264 1
					$phone = IPub\Phone\Entities\Phone::fromNumber((string) $value, $country);
265
266
					$this->country = $phone->getCountry();
267
					$this->number = str_replace(' ', '', $phone->getNationalNumber());
268
269
					return $this;
270
				}
271
			}
272 1
		}
273 1
274
		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 1
	 * @param string $key
279
	 *
280 1
	 * @return string
281
	 *
282 1
	 * @throws Exceptions\InvalidArgumentException
283
	 */
284
	public function getValuePart(string $key) : string
285 1
	{
286 1
		if ($key === self::FIELD_COUNTRY) {
287
			return $this->country;
288
289
		} elseif ($key === self::FIELD_NUMBER) {
290
			return $this->number;
291
		}
292
293
		throw new Exceptions\InvalidArgumentException('Invalid field key provided.');
294
	}
295
296
	/**
297 1
	 * @return IPub\Phone\Entities\Phone|NULL|FALSE
298 1
	 */
299
	public function getValue()
300 1
	{
301 1
		if ($this->country === NULL || $this->number === NULL) {
302 1
			return NULL;
303
		}
304
305
		try {
306 1
			// Try to parse number & country
307 1
			return IPub\Phone\Entities\Phone::fromNumber($this->number, $this->country);
308
309 1
		} catch (IPub\Phone\Exceptions\NoValidCountryException $ex) {
0 ignored issues
show
Bug introduced by
The class IPub\Phone\Exceptions\NoValidCountryException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
310 1
			return FALSE;
311
312
		} catch (IPub\Phone\Exceptions\NoValidPhoneException $ex) {
0 ignored issues
show
Bug introduced by
The class IPub\Phone\Exceptions\NoValidPhoneException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
313
			return FALSE;
314
		}
315
	}
316
317
	/**
318
	 * Loads HTTP data
319
	 *
320
	 * @return void
321
	 */
322 1
	public function loadHttpData()
323
	{
324
		$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
		$this->number = ($number === '' || $number === NULL) ? NULL : (string) $number;
329
	}
330
331
	/**
332
	 * @return Utils\Html
333
	 */
334
	public function getControl()
335
	{
336 1
		$el = Utils\Html::el();
337
		$el->addHtml($this->getControlPart(self::FIELD_COUNTRY) . $this->getControlPart(self::FIELD_NUMBER));
338
339 1
		return $el;
340 1
	}
341 1
342 1
	/**
343 1
	 * @return Utils\Html
344
	 *
345 1
	 * @throws Exceptions\InvalidArgumentException
346
	 */
347 1
	public function getControlPart()
348 1
	{
349 1
		$args = func_get_args();
350 1
		$key = reset($args);
351 1
352 1
		$name = $this->getHtmlName();
353
354 1
		if ($key === self::FIELD_COUNTRY) {
355 1
			// Try to get translator
356
			$translator = $this->getTranslator();
357 1
358
			$locale = 'en_US';
359 1
360
			if ($translator instanceof Localization\ITranslator && method_exists($translator, 'getLocale') === TRUE) {
0 ignored issues
show
Bug introduced by
The class Nette\Localization\ITranslator does not exist. Did you forget a USE statement, or did you not list all dependencies?

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 the composer.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 or require-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 ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
361 1
				try {
362 1
					$locale = $translator->getLocale();
363 1
364
				} catch (\Exception $ex) {
365 1
					$locale = 'en_US';
366 1
				}
367 1
			}
368 1
369 1
			$items = array_reduce($this->getAllowedCountries(), function (array $result, $row) use ($locale) {
370
				$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
				$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
					'value' => $row,
380 1
				]);
381
382 1
				return $result;
383 1
			}, []);
384 1
385 1
			$control = Forms\Helpers::createSelectBox(
386 1
				$items,
387
				[
388 1
					'selected?' => $this->country === NULL ? $this->defaultCountry : $this->country,
389 1
				]
390 1
			);
391
392 1
			$control->addAttributes([
393
				'name' => $name . '[' . self::FIELD_COUNTRY . ']',
394
				'id'   => $this->getHtmlId() . '-' . self::FIELD_COUNTRY,
395
			]);
396 1
			$control->data('ipub-forms-phone', '');
397
			$control->data('settings', json_encode([
398
					'field' => $name . '[' . self::FIELD_NUMBER . ']'
399
				])
400
			);
401
402
			if ($this->isDisabled()) {
403
				$control->addAttributes([
404
					'disabled' => TRUE,
405
				]);
406
			}
407 1
408
			return $control;
409
410
		} elseif ($key === self::FIELD_NUMBER) {
411
			$prototype = $this->getControlPrototype();
412
413
			$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...
414
415
			$control = Utils\Html::el('input');
416
417
			$control->addAttributes([
418
				'name'  => $name . '[' . self::FIELD_NUMBER . ']',
419
				'id'    => $this->getHtmlId() . '-' . self::FIELD_NUMBER,
420 1
				'value' => $this->number,
421
				'type'  => 'text',
422 1
				'class' => $prototype->getAttribute('class'),
423 1
			]);
424
425
			$control->data('nette-empty-value', Utils\Strings::trim($this->translate($this->emptyValue)));
426 1
			$control->data('nette-rules', $input->{'data-nette-rules'});
427
428
			if ($this->isDisabled()) {
429
				$control->addAttributes([
430
					'disabled' => TRUE,
431
				]);
432
			}
433
434
			return $control;
435
		}
436
437
		throw new Exceptions\InvalidArgumentException(sprintf('Part "%s" does not exist.', $key));
438
	}
439
440 1
	/**
441
	 * {@inheritdoc}
442 1
	 */
443 1
	public function getLabel($caption = NULL)
444
	{
445
		$label = parent::getLabel($caption);
446
		$label->for = $this->getHtmlId() . '-' . self::FIELD_NUMBER;
447
448
		return $label;
449
	}
450
451
	/**
452
	 * {@inheritdoc}
453
	 */
454
	public function getLabelPart()
455
	{
456
		return NULL;
457 1
	}
458 1
459
	/**
460
	 * @param string $country
461 1
	 *
462
	 * @return string
463 1
	 *
464 1
	 * @throws Exceptions\NoValidCountryException
465 1
	 */
466 1
	private function validateCountry(string $country) : string
467 1
	{
468
		// Country code have to be upper-cased
469 1
		$country = strtoupper($country);
470
471 1
		if ((strlen($country) === 2 && ctype_alpha($country) && ctype_upper($country) && in_array($country, $this->phoneUtils->getSupportedCountries())) || $country === 'AUTO') {
472 1
			return $country;
473
474 1
		} else {
475
			throw new Exceptions\NoValidCountryException(sprintf('Provided country code "%s" is not valid. Provide valid country code or AUTO for automatic detection.', $country));
476
		}
477
	}
478
479
	/**
480
	 * @param string $type
481
	 *
482
	 * @return string
483
	 *
484
	 * @throws Exceptions\NoValidTypeException
485
	 */
486
	private function validateType(string $type) : string
487
	{
488
		// Phone type have to be upper-cased
489
		$type = strtoupper($type);
490
491
		if (defined('\IPub\Phone\Phone::TYPE_' . $type)) {
492
			return $type;
493
494
		} else {
495
			throw new Exceptions\NoValidTypeException(sprintf('Provided phone type "%s" is not valid. Provide valid phone type.', $type));
496
		}
497
	}
498
499
	/**
500
	 * @param PhoneUtils $phoneUtils
501
	 * @param string $method
502
	 */
503
	public static function register(PhoneUtils $phoneUtils, string $method = 'addPhone')
504
	{
505
		// Check for multiple registration
506
		if (self::$registered) {
507
			throw new Nette\InvalidStateException('Phone control already registered.');
508
		}
509
510
		self::$registered = TRUE;
511
512
		$class = function_exists('get_called_class') ? get_called_class() : __CLASS__;
513
		Forms\Container::extensionMethod(
514
			$method, function (Forms\Container $form, $name, $label = NULL, $maxLength = NULL) use ($class, $phoneUtils) {
515
			$component = new $class($phoneUtils, $label, $maxLength);
516
			$form->addComponent($component, $name);
517
518
			return $component;
519
		}
520
		);
521
	}
522
}
523