Mailcode_Number_Info::parse_thousands_separator()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 29
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 13
nc 3
nop 1
dl 0
loc 29
rs 9.8333
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Mailcode;
6
7
use AppUtils\ConvertHelper;
8
use AppUtils\OperationResult;
9
10
class Mailcode_Number_Info extends OperationResult
11
{
12
    public const ERROR_VALIDATION_METHOD_MISSING = 72301;
13
14
    public const DEFAULT_FORMAT = "1000.00";
15
16
    /**
17
     * @var string
18
     */
19
    private $format;
20
21
    /**
22
     * @var int
23
     */
24
    private $padding = 0;
25
26
    /**
27
     * @var string
28
     */
29
    private $thousandsSeparator = '';
30
31
    /**
32
     * @var int
33
     */
34
    private $decimals = 0;
35
36
    /**
37
     * @var string
38
     */
39
    private $decimalsSeparator = '';
40
41
    public function __construct(string $format)
42
    {
43
        $format = trim($format);
44
45
        if(empty($format))
46
        {
47
            $format = self::DEFAULT_FORMAT;
48
        }
49
50
        $this->format = $format;
51
52
        $this->parse();
53
    }
54
55
    public function getDecimalsSeparator() : string
56
    {
57
        return $this->decimalsSeparator;
58
    }
59
60
    public function getThousandsSeparator() : string
61
    {
62
        return $this->thousandsSeparator;
63
    }
64
65
    public function getDecimals() : int
66
    {
67
        return $this->decimals;
68
    }
69
70
    public function getPadding() : int
71
    {
72
        return $this->padding;
73
    }
74
75
    public function hasDecimals() : bool
76
    {
77
        return $this->decimals > 0;
78
    }
79
80
    public function hasPadding() : bool
81
    {
82
        return $this->padding > 0;
83
    }
84
85
    public function hasThousandsSeparator() : bool
86
    {
87
        return !empty($this->thousandsSeparator);
88
    }
89
90
    /**
91
     * @var string[]
92
     */
93
    private $validations = array(
94
        'padding',
95
        'number',
96
        'thousands_separator',
97
        'decimal_separator',
98
        'separators',
99
        'decimals',
100
        'regex'
101
    );
102
103
    /**
104
     *
105
     * @see Mailcode_Commands_Command_ShowNumber::VALIDATION_PADDING_SEPARATOR_OVERFLOW
106
     */
107
    private function parse() : void
108
    {
109
        $format = $this->format;
110
111
        foreach($this->validations as $validation)
112
        {
113
            $method = 'parse_'.$validation;
114
115
            if(method_exists($this, $method))
116
            {
117
                $format = $this->$method($format);
118
119
                if(!$this->isValid())
120
                {
121
                    return;
122
                }
123
124
                continue;
125
            }
126
127
            throw new Mailcode_Exception(
128
                'Missing format validation method.',
129
                sprintf(
130
                    'The validation method [%s] is missing in the class [%s].',
131
                    $method,
132
                    get_class($this)
133
                ),
134
                self::ERROR_VALIDATION_METHOD_MISSING
135
            );
136
        }
137
    }
138
139
    private function parse_padding(string $format) : string
140
    {
141
        if(strstr($format, ':') === false) {
142
            return $format;
143
        }
144
145
        $parts = ConvertHelper::explodeTrim(':', $this->format);
146
147
        if(count($parts) !== 2)
148
        {
149
            $this->makeError(
150
                t(
151
                    'The padding sign %1$s may only be used once in the format string.',
152
                    '<code>:</code>'
153
                ),
154
                Mailcode_Commands_Command_ShowNumber::VALIDATION_PADDING_SEPARATOR_OVERFLOW
155
            );
156
157
            return '';
158
        }
159
160
        $padding = $parts[1];
161
162
        if(!preg_match('/\A[#]+\z/x', $padding))
163
        {
164
            $this->makeError(
165
                t('The padding may only contain hashes (%1$s given).', '<code>'.$padding.'</code>'),
166
                Mailcode_Commands_Command_ShowNumber::VALIDATION_PADDING_INVALID_CHARS
167
            );
168
169
            return $format;
170
        }
171
172
        $this->padding = strlen($padding);
173
174
        return $parts[0];
175
    }
176
177
    private function parse_number(string $format) : string
178
    {
179
        if($format[0] !== '1')
180
        {
181
            $this->makeError(
182
                t('The first character of the format must be a %1$s.', '<code>1</code>'),
183
                Mailcode_Commands_Command_ShowNumber::VALIDATION_INVALID_FORMAT_NUMBER
184
            );
185
186
            return $format;
187
        }
188
189
        // Get the actual number behind the format
190
        $base = str_replace(array('.', ',', ' '), '', $format);
191
        $number = intval(substr($base, 0, 4));
192
193
        if($number === 1000) {
194
            return $format;
195
        }
196
197
        $this->makeError(
198
            t(
199
                'The format must be specified using the number %1$s.',
200
                '<code>1000</code>'
201
            ),
202
            Mailcode_Commands_Command_ShowNumber::VALIDATION_INVALID_FORMAT_NUMBER
203
        );
204
205
        return $format;
206
    }
207
208
    private function parse_thousands_separator(string $format) : string
209
    {
210
        $separator = $format[1];
211
212
        // No thousands separator
213
        if($separator === '0')
214
        {
215
            return $format;
216
        }
217
218
        // Valid thousands separator
219
        $validSeparators = array(' ', ',', '.');
220
221
        if(in_array($separator, $validSeparators))
222
        {
223
            $this->thousandsSeparator = $separator;
224
            $format = str_replace('1'.$separator, '1', $format);
225
            return $format;
226
        }
227
228
        $this->makeError(
229
            t(
230
                'The character %1$s is not a valid thousands separator.',
231
                '<code>'.$separator.'</code>'
232
            ),
233
            Mailcode_Commands_Command_ShowNumber::VALIDATION_INVALID_THOUSANDS_SEPARATOR
234
        );
235
236
        return $format;
237
    }
238
239
    private function parse_decimal_separator(string $format) : string
240
    {
241
        // Number is 1000, so no decimals
242
        if (strlen($format) === 4)
243
        {
244
            return $format;
245
        }
246
247
        if ($this->validateDecimalSeparator($format[4]))
248
        {
249
            $this->decimalsSeparator = $format[4];
250
        }
251
252
        return $format;
253
    }
254
255
    private function parse_separators(string $format) : string
256
    {
257
        if(!empty($this->thousandsSeparator) && !empty($this->decimalsSeparator) && $this->thousandsSeparator === $this->decimalsSeparator)
258
        {
259
            $this->makeError(
260
                t(
261
                    'Cannot use %1$s as both thousands and decimals separator character.',
262
                    '<code>'.$this->thousandsSeparator.'</code>'
263
                ),
264
                Mailcode_Commands_Command_ShowNumber::VALIDATION_SEPARATORS_SAME_CHARACTER
265
            );
266
        }
267
268
        return $format;
269
    }
270
271
    private function parse_decimals(string $format) : string
272
    {
273
        if(empty($this->decimalsSeparator))
274
        {
275
            return $format;
276
        }
277
278
        $parts = ConvertHelper::explodeTrim($this->decimalsSeparator, $format);
279
280
        if(!isset($parts[1]))
281
        {
282
            $this->makeError(
283
                t('Cannot determine the amount of decimals.').' '.
284
                    t('Add the amount of decimals by adding the according amount of zeros.').' '.
285
                    t('Example:').' '.
286
                    t(
287
                        '%1$s would add two decimals.',
288
                        '<code>'.number_format(1000, 2, $this->decimalsSeparator, $this->thousandsSeparator).'</code>'
289
                    ),
290
                Mailcode_Commands_Command_ShowNumber::VALIDATION_INVALID_DECIMALS_NO_DECIMALS
291
            );
292
293
            return $format;
294
        }
295
296
        if($this->validateDecimals($parts[1]))
297
        {
298
            $this->decimals = strlen($parts[1]);
299
        }
300
301
        return $format;
302
    }
303
304
    private function validateDecimals(string $decimals) : bool
305
    {
306
        if(preg_match('/\A[0]+\z/x', $decimals)) {
307
            return true;
308
        }
309
310
        $this->makeError(
311
            t(
312
                'The decimals may only contain zeros, other characters are not allowed (%1$s given)',
313
                '<code>'.htmlspecialchars($decimals).'</code>'
314
            ),
315
            Mailcode_Commands_Command_ShowNumber::VALIDATION_INVALID_DECIMALS_CHARS
316
        );
317
318
        return false;
319
    }
320
321
    private function validateDecimalSeparator(string $separator) : bool
322
    {
323
        $validSeparators = array('.', ',');
324
325
        if(in_array($separator, $validSeparators)) {
326
            return true;
327
        }
328
329
        $this->makeError(
330
            t('Invalid decimal separator character.'),
331
            Mailcode_Commands_Command_ShowNumber::VALIDATION_INVALID_DECIMAL_SEPARATOR
332
        );
333
334
        return false;
335
    }
336
337
    /**
338
     * Fallback regex check: The previous validations cannot take
339
     * all possibilities into account, so we validate the resulting
340
     * format string with a regex.
341
     *
342
     * @param string $format
343
     * @return string
344
     */
345
    private function parse_regex(string $format) : string
346
    {
347
        if(preg_match('/1[ ,.]?000|1[ ,.]?000[.,][0]+/x', $format))
348
        {
349
            return $format;
350
        }
351
352
        $this->makeError(
353
            t('Some invalid characters were found in the format string.'),
354
            Mailcode_Commands_Command_ShowNumber::VALIDATION_INVALID_CHARACTERS
355
        );
356
357
        return $format;
358
    }
359
}