Completed
Push — master ( 282043...cf52f2 )
by Adam
02:51
created

Phone::setDefaultCountry()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 13
ccs 5
cts 5
cp 1
rs 9.4286
cc 2
eloc 7
nc 2
nop 1
crap 2
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
class Phone extends Forms\Controls\TextInput
40
{
41
	/**
42
	 * Define filed attributes
43
	 */
44 1
	const FIELD_COUNTRY = 'country';
45
	const FIELD_NUMBER = 'number';
46
47
	/**
48
	 * @var IPub\Phone\Phone
49
	 */
50
	private $phoneUtils;
51
52
	/**
53
	 * List of allowed countries
54
	 *
55
	 * @var array
56
	 */
57
	private $allowedCountries = [];
58
59
	/**
60
	 * List of allowed phone types
61
	 *
62 1
	 * @var array
63
	 */
64
	private $allowedTypes = [];
65
66
	/**
67
	 * @var string|NULL
68
	 */
69
	private $number = NULL;
70
71
	/**
72
	 * @var string|NULL
73
	 */
74
	private $country = NULL;
75
76
	/**
77
	 * @var string
78
	 */
79
	private $defaultCountry;
80
81
	/**
82
	 * @var bool
83
	 */
84
	private static $registered = FALSE;
85
86
	/**
87
	 * @param PhoneUtils $phoneUtils
88
	 * @param string|NULL $label
89 1
	 * @param int|NULL $maxLength
90
	 */
91
	public function __construct(PhoneUtils $phoneUtils, $label = NULL, $maxLength = NULL)
92
	{
93
		parent::__construct($label, $maxLength);
94
95
		$this->phoneUtils = $phoneUtils;
96
	}
97 1
98
	/**
99 1
	 * @param array $countries
100 1
	 *
101
	 * @return $this
102
	 *
103
	 * @throws Exceptions\NoValidCountryException
104
	 */
105
	public function setAllowedCountries(array $countries = [])
106
	{
107
		$this->allowedCountries = [];
108
109
		foreach($countries as $country)
110
		{
111 1
			$country = $this->validateCountry($country);
112
			$this->allowedCountries[] = strtoupper($country);
113 1
		}
114
115 1
		// Check for auto country detection
116 1
		if (in_array('AUTO', $this->allowedCountries)) {
117 1
			$this->allowedCountries = ['AUTO'];
118
		}
119
120 1
		// Remove duplicities
121
		array_unique($this->allowedCountries);
122
123
		return $this;
124
	}
125 1
126
	/**
127 1
	 * @param string $country
128
	 *
129
	 * @return $this
130
	 *
131
	 * @throws Exceptions\NoValidCountryException
132
	 */
133 1
	public function addAllowedCountry($country)
134
	{
135
		$country = $this->validateCountry($country);
136
		$this->allowedCountries[] = strtoupper($country);
137
138
		// Remove duplicities
139 1
		array_unique($this->allowedCountries);
140 1
141
		if (strtoupper($country) === 'AUTO') {
142
			$this->allowedCountries = ['AUTO'];
143 1
144
		} else if (($key = array_search('AUTO', $this->allowedCountries)) && $key !== FALSE) {
145 1
			unset($this->allowedCountries[$key]);
146
		}
147
148 1
		return $this;
149
	}
150
151
	/**
152 1
	 * @return array
153
	 */
154
	public function getAllowedCountries()
155
	{
156
		if (in_array('AUTO', $this->allowedCountries, TRUE) || $this->allowedCountries === []) {
157
			return $this->phoneUtils->getSupportedCountries();
158
159
		} else {
160 1
			return $this->allowedCountries;
161 1
		}
162
	}
163
164 1
	/**
165
	 * @param string|NULL $country
166
	 *
167
	 * @return $this
168
	 *
169
	 * @throws Exceptions\NoValidCountryException
170
	 */
171
	public function setDefaultCountry($country = NULL)
172
	{
173
		if ($country === NULL) {
174
			$this->defaultCountry = NULL;
175
176
		} else {
177 1
			$country = $this->validateCountry($country);
178 1
179
			$this->defaultCountry = strtoupper($country);
180 1
		}
181 1
182
		return $this;
183 1
	}
184 1
185
	/**
186 1
	 * @param array $types
187
	 *
188
	 * @return $this
189 1
	 *
190 1
	 * @throws Exceptions\NoValidTypeException
191
	 */
192
	public function setAllowedPhoneTypes(array $types = [])
193
	{
194
		$this->allowedTypes = [];
195
196
		foreach($types as $type)
197
		{
198
			$type = $this->validateType($type);
199
			$this->allowedTypes[] = strtoupper($type);
200
		}
201
202
		// Remove duplicities
203
		array_unique($this->allowedTypes);
204
205
		return $this;
206
	}
207
208
	/**
209
	 * @param string $type
210
	 *
211
	 * @return $this
212
	 *
213
	 * @throws Exceptions\NoValidTypeException
214
	 */
215
	public function addAllowedPhoneType($type)
216
	{
217
		$type = $this->validateType($type);
218
		$this->allowedTypes[] = strtoupper($type);
219
220
		// Remove duplicities
221 1
		array_unique($this->allowedTypes);
222 1
223
		return $this;
224
	}
225 1
226
	/**
227 1
	 * @return array
228
	 */
229
	public function getAllowedPhoneTypes()
230
	{
231
		return $this->allowedTypes;
232
	}
233
234
	/**
235 1
	 * @param string
236
	 *
237
	 * @return $this
238
	 *
239
	 * @throws Exceptions\InvalidArgumentException
240
	 */
241
	public function setValue($value)
242
	{
243
		if ($value === NULL) {
244
			$this->country = NULL;
245
			$this->number = NULL;
246
247 1
			return $this;
248 1
		}
249 1
250
		foreach($this->getAllowedCountries() as $country) {
251 1
			if ($this->phoneUtils->isValid($value, $country)) {
252
				$phone = $this->phoneUtils->parse($value, $country);
253
254 1
				$this->country = $phone->getCountry();
255 1
				$this->number = str_replace(' ', '', $phone->getNationalNumber());
256 1
257
				return $this;
258 1
			}
259 1
		}
260
261 1
		throw new Exceptions\InvalidArgumentException('Provided value is not valid phone number, or is out of list of allowed countries, "' . $value . '" given.');
262
	}
263 1
264
	/**
265 1
	 * @return IPub\Phone\Entities\Phone|NULL
266
	 */
267
	public function getValue()
268
	{
269
		if ($this->country === NULL || $this->number === NULL) {
270
			return NULL;
271
		}
272
273 1
		try {
274 1
			// Try to parse number & country
275
			$number = $this->phoneUtils->parse($this->number, $this->country);
276
277
			return $number === NULL ? NULL : $number;
278 1
279 1
		} catch (IPub\Phone\Exceptions\NoValidCountryException $ex) {
280
			return NULL;
281
282 1
		} catch (IPub\Phone\Exceptions\NoValidPhoneException $ex) {
283 1
			return NULL;
284
		}
285
	}
286 1
287
	/**
288
	 * Loads HTTP data
289
	 *
290
	 * @return void
291
	 */
292
	public function loadHttpData()
293
	{
294
		$country = $this->getHttpData(Forms\Form::DATA_LINE, '[' . static::FIELD_COUNTRY . ']');
295
		$this->country = ($country === '' || $country === NULL) ? NULL : (string) $country;
296 1
297 1
		$number = $this->getHttpData(Forms\Form::DATA_LINE, '[' . static::FIELD_NUMBER . ']');
298 1
		$this->number = ($number === '' || $number === NULL) ? NULL : (string) $number;
299
	}
300
301
	/**
302
	 * @return Utils\Html
303
	 */
304
	public function getControl()
305 1
	{
306 1
		return Utils\Html::el()
307 1
			->add($this->getControlPart(static::FIELD_COUNTRY) . $this->getControlPart(static::FIELD_NUMBER));
308
	}
309 1
310
	/**
311
	 * @param string $key
312
	 *
313
	 * @return Utils\Html
314
	 *
315
	 * @throws Exceptions\InvalidArgumentException
316
	 */
317 1
	public function getControlPart($key)
318
	{
319 1
		$name = $this->getHtmlName();
320
321
		// Try to get translator
322 1
		$translator = $this->getTranslator();
323
324
		if ($translator instanceof Localization\ITranslator && method_exists($translator, 'getLocale') === TRUE) {
325 1
			try {
326 1
				$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...
327 1
328 1
			} catch (\Exception $ex) {
329 1
				$locale = 'en_US';
330
			}
331 1
332
		} else {
333 1
			$locale = 'en_US';
334 1
		}
335 1
336 1
		if ($key === static::FIELD_COUNTRY) {
337 1
			$control = Forms\Helpers::createSelectBox(
338 1
				array_reduce($this->getAllowedCountries(), function (array $result, $row) use ($locale) {
339
					$countryName = geocoding\Locale::getDisplayRegion(
340 1
						geocoding\Locale::countryCodeToLocale($row),
341 1
						$locale
342
					);
343 1
344
					$result[$row] = Utils\Html::el('option')
345 1
						->setText('+' . $this->phoneUtils->getCountryCodeForCountry($row) . ' ('. $countryName . ')')
346
						->addAttributes([
347
							'data-mask' => preg_replace('/[0-9]/', '9', $this->phoneUtils->getExampleNationalNumber($row)),
348 1
						])
349 1
						->value($row);
350 1
351 1
					return $result;
352 1
				}, []),
353 1
				[
354
					'selected?' => $this->country === NULL ? $this->defaultCountry : $this->country,
355 1
				]
356
			);
357
358
			$control
359 1
				->name($name . '[' . static::FIELD_COUNTRY . ']')
360
				->id($this->getHtmlId() . '-' . static::FIELD_COUNTRY)
361 1
				->{'data-ipub-forms-phone'}('')
362 1
				->{'data-settings'}(json_encode([
363
					'field' => $name . '[' . static::FIELD_NUMBER . ']'
364 1
				]));
365
366
			if ($this->isDisabled()) {
367 1
				$control->disabled(TRUE);
368 1
			}
369 1
370 1
			return $control;
371 1
372
		} else if ($key === static::FIELD_NUMBER) {
373 1
			$control = Utils\Html::el('input');
374
375
			$control->addAttributes([
376
				'name'	=> $name . '[' . static::FIELD_NUMBER . ']',
377 1
				'id'	=> $this->getHtmlId() . '-' . static::FIELD_NUMBER,
378
				'value'	=> $this->number,
379
				'type'	=> 'text',
380
381
				'data-nette-rules'	=> Nette\Forms\Helpers::exportRules($this->rules) ?: NULL,
0 ignored issues
show
Documentation introduced by
The property $rules is declared private in Nette\Forms\Controls\BaseControl. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

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