Issues (7)

src/PhoneField.php (1 issue)

1
<?php
2
3
namespace LeKoala\PhoneNumber;
4
5
use LeKoala\PhoneNumber\Test\PhoneNumberTest;
6
use SilverStripe\Forms\TextField;
7
use libphonenumber\PhoneNumberFormat;
8
use libphonenumber\NumberParseException;
9
use libphonenumber\PhoneNumber;
10
use libphonenumber\PhoneNumberType;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\Forms\Validator;
13
use Exception;
14
15
/**
16
 * A simple phone field
17
 *
18
 * Formatting only works with international number because we don't know the country
19
 *
20
 * For national numbers, use CountryPhoneField that use a combination of CountryCode + PhoneNumber field
21
 */
22
class PhoneField extends TextField
23
{
24
    /**
25
     * @var string|null
26
     */
27
    protected $countryField = null;
28
29
    /**
30
     * @var string|null
31
     */
32
    protected $countryCode = null;
33
34
    /**
35
     * @var bool
36
     */
37
    protected $isMobile = false;
38
39
    /**
40
     * @var int
41
     */
42
    protected $displayFormat = 1; // INTERNATIONAL
43
44
    /**
45
     * @return string
46
     */
47
    public function getInputType()
48
    {
49
        return 'phone';
50
    }
51
52
    /**
53
     * @return string
54
     */
55
    public function Type()
56
    {
57
        return 'text';
58
    }
59
60
    /**
61
     * @param string $name
62
     * @param string|null $title
63
     * @param mixed $value
64
     */
65
    public function __construct($name, $title = null, $value = null)
66
    {
67
        // Autodetect mobile fields
68
        if (strpos(strtolower($name), 'mobile') !== false) {
69
            $this->isMobile = true;
70
        }
71
        parent::__construct($name, $title, $value);
72
    }
73
74
    /**
75
     * @param mixed $value Either the parent object, or array of source data being loaded
76
     * @param array<mixed>|DataObject|null $data {@see Form::loadDataFrom}
77
     * @return $this
78
     */
79
    public function setValue($value, $data = null)
80
    {
81
        $isInternational = strpos((string)$value, '+') === 0;
82
83
        if (!$isInternational && $this->countryField) {
84
            if (isset($data[$this->countryField])) {
85
                $countryValue = $data[$this->countryField];
86
                $this->countryCode = $countryValue;
87
88
                if (strpos((string)$countryValue, '+') === 0) {
89
                    // It's a + prefix, eg +33, +32
90
                    $value = $countryValue . ltrim((string)$value, "0");
91
                } elseif (is_numeric($countryValue)) {
92
                    // It's a plain prefix, eg 33, 32
93
                    $value = '+' . $countryValue . ltrim((string)$value, "0");
94
                } else {
95
                    // It's a country code (FR, BE...)
96
                    $countryValue = PhoneHelper::convertCountryCodeToPrefix($countryValue);
97
                    if ($countryValue) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $countryValue of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
98
                        $value = '+' . $countryValue . ltrim((string)$value, "0");
99
                    }
100
                }
101
            }
102
103
            // Test again!
104
            $isInternational = strpos((string)$value, '+') === 0;
105
        }
106
107
        // We have an international number that we can format easily without knowing the country
108
        if ($isInternational) {
109
            $util = PhoneHelper::getPhoneNumberUtil();
110
            try {
111
                $number = $util->parse($value);
112
                $newValue = $util->format($number, $this->displayFormat);
113
            } catch (NumberParseException $ex) {
114
                $newValue = $value;
115
            }
116
            $value = $newValue;
117
        }
118
        return parent::setValue($value, $data);
119
    }
120
121
    /**
122
     * Validate this field
123
     *
124
     * @param Validator $validator
125
     * @return bool
126
     */
127
    public function validate($validator)
128
    {
129
        if ($this->isMobile && $this->value) {
130
            $hasPlus = strpos((string)$this->value, '+') === 0;
131
            $util = PhoneHelper::getPhoneNumberUtil();
132
            $region = $hasPlus ? null : $this->countryCode;
133
            try {
134
                $number = $util->parse($this->value, $region);
135
                $type = $util->getNumberType($number);
136
                if (!in_array($type, [PhoneNumberType::FIXED_LINE_OR_MOBILE, PhoneNumberType::MOBILE, PhoneNumberType::UNKNOWN])) {
137
                    $validator->validationError(
138
                        $this->name,
139
                        _t('PhoneField.IsNotAMobileNumber', 'This is not a valid mobile number')
140
                    );
141
                }
142
            } catch (Exception $e) {
143
                // Don't block if parsing fails
144
            }
145
        }
146
        return parent::validate($validator);
147
    }
148
149
    /**
150
     * Value in E164 format (no formatting)
151
     *
152
     * @return string
153
     */
154
    public function dataValue()
155
    {
156
        $value = $this->Value();
157
158
        // It's an international number or we have a country set, format without spaces
159
        if (strpos((string)$value, '+') === 0 || $this->countryCode) {
160
            $util = PhoneHelper::getPhoneNumberUtil();
161
            try {
162
                $number = $util->parse($value, $this->countryCode);
163
                $formatted = $util->format($number, PhoneNumberFormat::E164);
164
            } catch (NumberParseException $ex) {
165
                $formatted = $value;
166
            }
167
            return $formatted;
168
        }
169
        return $value;
170
    }
171
172
    /**
173
     * Name of the country field
174
     *
175
     * @return string
176
     */
177
    public function getCountryField()
178
    {
179
        return $this->countryField;
180
    }
181
182
    /**
183
     * Name of the country field
184
     *
185
     * @param string $countryField
186
     * @return $this
187
     */
188
    public function setCountryField($countryField)
189
    {
190
        $this->countryField = $countryField;
191
        return $this;
192
    }
193
194
    /**
195
     * @return string
196
     */
197
    public function getCountryCode()
198
    {
199
        return $this->countryCode;
200
    }
201
202
    /**
203
     * @param string $countryCode
204
     * @return $this
205
     */
206
    public function setCountryCode($countryCode)
207
    {
208
        $this->countryCode = $countryCode;
209
        return $this;
210
    }
211
212
    /**
213
     * Get the value of isMobile
214
     * @return bool
215
     */
216
    public function getIsMobile()
217
    {
218
        return $this->isMobile;
219
    }
220
221
    /**
222
     * Set the value of isMobile
223
     *
224
     * @param bool $isMobile
225
     * @return $this
226
     */
227
    public function setIsMobile(bool $isMobile)
228
    {
229
        $this->isMobile = $isMobile;
230
        return $this;
231
    }
232
233
    /**
234
     * Get the value of displayFormat
235
     *
236
     * @return int
237
     */
238
    public function getDisplayFormat()
239
    {
240
        return $this->displayFormat;
241
    }
242
243
    /**
244
     * Set the value of displayFormat
245
     *
246
     * @param int $displayFormat
247
     * @return $this
248
     */
249
    public function setDisplayFormat($displayFormat)
250
    {
251
        $this->displayFormat = $displayFormat;
252
        return $this;
253
    }
254
}
255