Passed
Push — master ( f2763b...1b4955 )
by Stefan
08:26
created

SepaHelper::calcDelayedDate()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 13
rs 10
c 0
b 0
f 0
cc 4
nc 6
nop 2
1
<?php
2
namespace SKien\Sepa;
3
4
/**
5
 * Helper trait containing some methods used by multiple classes in package
6
 *
7
 * @package Sepa
8
 * @author Stefanius <[email protected]>
9
 * @copyright MIT License - see the LICENSE file for details
10
 * @internal
11
 */
12
trait SepaHelper
13
{
14
    /**
15
     * Check for valid type and trigger error in case of invalid type.
16
     * @param string $type
17
     * @return bool
18
     */
19
    protected function isValidType(string $type) : bool
20
    {
21
        if ($type != Sepa::CCT && $type != Sepa::CDD) {
22
            trigger_error('invalid type for ' . get_class($this), E_USER_ERROR);
23
        }
24
        return true;
25
    }
26
27
    /**
28
     * Create unique ID.
29
     * Format: 99999999-9999-9999-999999999999
30
     * @return string
31
     */
32
    public static function createUID() : string
33
    {
34
        mt_srand((int)microtime(true) * 10000);
35
        $charid = strtoupper(md5(uniqid((string)rand(), true)));
36
        $uuid = substr($charid, 0, 8) . chr(45) .
37
                substr($charid, 8, 4) . chr(45) .
38
                substr($charid, 12, 4) . chr(45) .
39
                substr($charid, 16, 12);
40
41
        return $uuid;
42
    }
43
44
    /**
45
     * Convert input to valid SEPA string.
46
     * <ol>
47
     *      <li>replacement of special chars</li>
48
     *      <li>limitation to supported chars dependend on validation type</li>
49
     *      <li>restriction to max length dependend on validation type</li>
50
     * </ol>
51
     *
52
     * Validation Types: <ul>
53
     *      <li>SepaHelper::MAX35:</li>
54
     *      <li>SepaHelper::MAX70:</li>
55
     *      <li>SepaHelper::MAX140:</li>
56
     *      <li>SepaHelper::MAX1025:
57
     *          <ul>
58
     *              <li>max length = MAX[xxx]</li>
59
     *              <li>supported chars: A...Z, a...z, 0...9, blank, dot, comma, plus, minus, slash, questionmark, colon, open/closing bracket</li>
60
     *          </ul></li>
61
     *      <li>SepaHelper::ID1:
62
     *          <ul>
63
     *              <li>max length = 35</li>
64
     *              <li>supported chars: A...Z, a...z, 0...9, blank, dot, comma, plus, minus, slash</li>
65
     *          </ul></li>
66
     *      <li>SepaHelper::ID2:
67
     *          <ul>
68
     *              <li>max length = 35</li>
69
     *              <li>supported chars: ID1 without blank</li>
70
     *          </ul></li>
71
     * </ul>
72
     *
73
     * @param string $str   string to validate
74
     * @param int $iType    type of validation: one of SepaHelper::MAX35, SepaHelper::MAX70, SepaHelper::MAX140, SepaHelper::MAX1025, SepaHelper::ID1, SepaHelper::ID2
75
     * @return string
76
     */
77
    public static function validString(string $str, int $iType) : string
78
    {
79
        // replace specialchars...
80
        $strValid = self::replaceSpecialChars($str);
81
82
        // regular expresion for 'standard' types MAXxxx
83
        $strRegEx = '/[^A-Za-z0-9 \.,\-\/\+():?]/'; // A...Z, a...z, 0...9, blank, dot, comma plus, minus, slash, questionmark, colon, open/closing bracket
84
        $strReplace = ' ';
85
        $iMaxLen = 1025;
86
        switch ($iType) {
87
            case Sepa::ID1:
88
                $iMaxLen = 35;
89
                $strRegEx = '/[^A-Za-z0-9 \.,\+\-\/]/'; // A...Z, a...z, 0...9, blank, dot, comma plus, minus, slash
90
                $strReplace = '';
91
                break;
92
            case Sepa::ID2:
93
                $iMaxLen = 35;
94
                $strRegEx = '/[^A-Za-z0-9\.,\+\-\/]/'; // same as ID1 except blank...
95
                $strReplace = '';
96
                break;
97
            case Sepa::MAX35:
98
                $iMaxLen = 35;
99
                break;
100
            case Sepa::MAX70:
101
                $iMaxLen = 70;
102
                break;
103
            case Sepa::MAX140:
104
                $iMaxLen = 140;
105
                break;
106
            case Sepa::MAX1025:
107
            default:
108
                break;
109
        }
110
111
        $strValid = preg_replace($strRegEx, $strReplace, $strValid);
112
        return substr($strValid ?? '', 0, $iMaxLen);
113
    }
114
115
    /**
116
     * Replace some special chars with nearest equivalent.
117
     * - umlauts, acute, circumflex, ...
118
     * - square/curly brackets
119
     * - underscore, at
120
     * @param string $str text to process
121
     * @return string
122
     */
123
    public static function replaceSpecialChars(string $str) : string
124
    {
125
        $strReplaced = '';
126
        if (strlen($str) > 0) {
127
            // replace known special chars
128
            $aSpecialChars = array(
129
                'á' => 'a', 'à' => 'a', 'ä' => 'ae', 'â' => 'a', 'ã' => 'a', 'å' => 'a', 'æ' => 'ae',
130
                'Á' => 'A', 'À' => 'A', 'Ä' => 'Ae', 'Â' => 'A', 'Ã' => 'A', 'Å' => 'A', 'Æ' => 'AE',
131
                'ç' => 'c', 'Ç' => 'C',
132
                'é' => 'e', 'è' => 'e', 'ê' => 'e', 'ë' => 'e', 'É' => 'E', 'È' => 'E', 'Ê' => 'E', 'Ë' => 'E',
133
                'ì' => 'i', 'î' => 'i', 'ï' => 'i', 'Í' => 'I', 'Ì' => 'I', 'Î' => 'I', 'Ï' => 'I',
134
                'ñ' => 'n', 'Ñ' => 'N',
135
                'ó' => 'o', 'ò' => 'o', 'ö' => 'oe', 'ô' => 'o', 'õ' => 'o', 'ø' => 'o', 'œ' => 'oe',
136
                'Ó' => 'O', 'Ò' => 'O', 'Ö' => 'Oe', 'Ô' => 'O', 'Õ' => 'O', 'Ø' => 'O', 'Œ' => 'OE',
137
                'ß' => 'ss', 'š' => 's', 'Š' => 'S',
138
                'ú' => 'u', 'ù' => 'u', 'ü' => 'ue', 'û' => 'u',
139
                'Ú' => 'U', 'Ù' => 'U', 'Ü' => 'Ue', 'Û' => 'U',
140
                'ý' => 'y', 'ÿ' => 'y', 'Ý' => 'Y', 'Ÿ' => 'Y',
141
                'ž' => 'z', 'Ž' => 'Z',
142
                '[' => '(', ']' => ')', '{' => '(', '}' => ')',
143
                '_' => '-', '@' => '(at)', '€' => 'EUR'
144
            );
145
146
            $strReplaced = strtr($str, $aSpecialChars);
147
        }
148
        return $strReplaced;
149
    }
150
151
    /**
152
     * Calculates valid delayed date from given date considering SEPA businessdays.
153
     * @param int $iDaysDelay   min count of days the payment delays
154
     * @param int $dtRequested  requested date (unix timestamp)
155
     * @return int unix timestamp
156
     */
157
    public static function calcDelayedDate(int $iDaysDelay, int $dtRequested = 0) : int
158
    {
159
        $dtEarliest = time();
160
161
        // FORWARD: should daytime ( < 08:30 / < 18:30 ) bear in mind ?
162
        $iBDays = 0;
163
        while ($iBDays < $iDaysDelay) {
164
            $dtEarliest += 86400; // add day ( 24 * 60 * 60 );
165
            if (self::isTarget2Day($dtEarliest)) {
166
                $iBDays++;
167
            }
168
        }
169
        return $dtEarliest > $dtRequested ? $dtEarliest : $dtRequested;
170
    }
171
172
    /**
173
     * Check for target2-Day (Sepa-Businessday).
174
     * Mo...Fr and NOT TARGET1-Day
175
     * TARGET1 Days:
176
     *  - New Year
177
     *  - Good Day
178
     *  - Easter Monday
179
     *  - 1'st May
180
     *  - 1.Christmas
181
     *  - 2.Christmas
182
     * @todo change to dynamic calculation of eastern and remove $aTarget2 - array
183
     * @param int $dt  unix timestamp to check
184
     * @return bool
185
     */
186
    public static function isTarget2Day(int $dt) : bool
187
    {
188
        $iWeekDay = date('N', $dt);
189
190
        //  New Year        Good Day     Easter Monday  1'stMay      1.Christmas   2.Christmas
191
        $aTarget2 = array(
192
            '2019-01-01', '2019-04-18', '2019-04-21', '2019-05-01', '2019-12-25', '2019-12-26',
193
            '2020-01-01', '2020-04-10', '2020-04-13', '2020-05-01', '2020-12-25', '2020-12-26',
194
            '2021-01-01', '2021-04-02', '2021-04-05', '2021-05-01', '2021-12-25', '2021-12-26',
195
            '2022-01-01', '2022-04-15', '2022-04-18', '2022-05-01', '2022-12-25', '2022-12-26',
196
            '2023-01-01', '2023-04-07', '2023-04-10', '2023-05-01', '2023-12-25', '2023-12-26',
197
            '2024-01-01', '2024-03-29', '2024-04-01', '2024-05-01', '2024-12-25', '2024-12-26',
198
            '2025-01-01', '2025-04-18', '2025-04-21', '2025-05-01', '2025-12-25', '2025-12-26',
199
        );
200
        return !($iWeekDay > 5 || in_array(date('Y-m-d', $dt), $aTarget2));
201
    }
202
}
203