Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
63.84% covered (warning)
63.84%
113 / 177
90.00% covered (success)
90.00%
27 / 30
CRAP
0.00% covered (danger)
0.00%
0 / 1
Util
63.84% covered (warning)
63.84%
113 / 177
90.00% covered (success)
90.00%
27 / 30
482.47
0.00% covered (danger)
0.00%
0 / 1
 getCookieDomain
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setCookie
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 clearCookie
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
2.00
 clearNumericCookie
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 clearRequiredNumericCookie
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getCookie
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 generateRandomString
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getPercentButAvoid100UntilComplete
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 encrypt
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 decrypt
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 extract
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 extractWithDefault
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getRatio
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getPercent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 indexByID
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 isInGithubCI
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 isInTestEnvironment
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 getMyAddress
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 getInternalAddress
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 addSqlCondition
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 addSqlOrCondition
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 boolString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getHealthBasedOnLevel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 query
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 clampOptional
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 strOrNull
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getGraphColor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getGraphGridColor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getValueGraphHeight
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 smallScoreTableRowColor
6.15% covered (danger)
6.15%
4 / 65
0.00% covered (danger)
0.00%
0 / 1
933.07
1<?php
2
3class Util
4{
5    public static function getCookieDomain(): ?string
6    {
7        $host = preg_replace('/:\d+$/', '', $_SERVER['HTTP_HOST'] ?? '');
8        return $host;
9    }
10
11    public static function setCookie($name, $value)
12    {
13        setcookie($name, $value, [
14            'expires'  => time() + 365 * 24 * 60 * 60,
15            'path'     => '/',
16            'secure'   => true,
17            'httponly' => false,
18            'samesite' => 'Lax'
19        ]);
20        // Also update $_COOKIE so the value is available in the current request
21        $_COOKIE[$name] = $value;
22    }
23
24    /* @return The value of the cleared cookie */
25    public static function clearCookie(string $name): ?string
26    {
27        $previous = $_COOKIE[$name] ?? null;
28
29        setcookie($name, '',
30            [
31                'expires'  => time() - 3600,
32                'path'     => '/',
33                'secure'   => true,
34                'httponly' => false,
35                'samesite' => 'Lax'
36            ]);
37
38        unset($_COOKIE[$name]);
39        if ($previous == 'deleted')
40            return null;
41        return $previous;
42    }
43
44    /* @return Int value of the cleared cookie, returns null if the cookeie isn't present. throws if it isn't numeric */
45    public static function clearNumericCookie(string $name): ?int
46    {
47        $result = Util::clearCookie($name);
48        if (!$result)
49            return null;
50        if (!is_numeric($result))
51            throw new Exception("Cookie " . $name . " should be numeric but has value '" . strval($result) . "'.");
52        return intval($result);
53    }
54
55    /* @return Int value of the cleared cookie, throws excpetion when the cookie isn't present or isn't numeric */
56    public static function clearRequiredNumericCookie(string $name): int
57    {
58        $result = Util::clearNumericCookie($name);
59        if (is_null($result))
60            throw new Exception("Cookie " . $name . " is expected to be defined, but it isn't");
61        return $result;
62    }
63
64    public static function getCookie(string $name, $default = null)
65    {
66        return isset($_COOKIE[$name]) ? $_COOKIE[$name] : $default;
67    }
68
69    public static function generateRandomString($length = 20)
70    {
71        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
72        $charactersLength = strlen($characters);
73        $randomString = '';
74        for ($i = 0; $i < $length; $i++)
75            $randomString .= $characters[rand(0, $charactersLength - 1)];
76        return $randomString;
77    }
78
79    public static function getPercentButAvoid100UntilComplete(int $value, int $max): int
80    {
81        assert($value >= 0);
82        assert($max >= 0);
83        assert($value <= $max);
84        $result = (int) round(Util::getRatio($value, $max) * 100);
85        if ($result == 100 && $value < $max)
86            return 99;
87        return $result;
88    }
89
90    private const SECRET_KEY = 'my_simple_secret_keyx';
91    private const SECRET_IV = 'my_simple_secret_ivx';
92    private const ENCRYPT_METHOD = 'AES-256-CBC';
93
94    public static function encrypt(string $str): string
95    {
96        $key = hash('sha256', self::SECRET_KEY);
97        $iv = substr(hash('sha256', self::SECRET_IV), 0, 16);
98        return base64_encode(openssl_encrypt($str, self::ENCRYPT_METHOD, $key, 0, $iv));
99    }
100
101    public static function decrypt(string $str): string
102    {
103        $key = hash('sha256', self::SECRET_KEY);
104        $iv = substr(hash('sha256', self::SECRET_IV), 0, 16);
105        return openssl_decrypt(base64_decode($str), self::ENCRYPT_METHOD, $key, 0, $iv);
106    }
107
108    public static function extract(string $name, array &$inputArray)
109    {
110        $result = $inputArray[$name];
111        unset($inputArray[$name]);
112        return $result;
113    }
114
115    public static function extractWithDefault(string $name, array &$inputArray, $default)
116    {
117        $result = $inputArray[$name];
118        unset($inputArray[$name]);
119        if (isset($result))
120            return $result;
121        return $default;
122    }
123
124    public static function getRatio(float|int $amount, float|int $max): float
125    {
126        if ($max == 0)
127            return 0;
128        return $amount / $max;
129    }
130
131    public static function getPercent(float|int $amount, float|int $max): float
132    {
133        return self::getRatio($amount, $max) * 100;
134    }
135
136    public static function indexByID($array, $prefix1, $prefix2)
137    {
138        $result = [];
139        foreach ($array as $value)
140            $result[$value[$prefix1]['id']] = $value[$prefix1][$prefix2];
141        return $result;
142    }
143
144    public static function isInGithubCI()
145    {
146        if ($testEnvironment = @$_SERVER['TEST_ENVIRONMENT'])
147            return $testEnvironment == 'github-ci';
148        if ($host = @$_SERVER['HTTP_HOST'])
149            return str_contains($host, 'host.docker.internal');
150        return false;
151    }
152
153    public static function isInTestEnvironment(): bool
154    {
155        if (@$_SERVER['DDEV_PRIMARY_URL'] && str_contains($_SERVER['DDEV_PRIMARY_URL'], "tsumego.ddev.site"))
156            return true;
157        return Util::isInGithubCI();
158    }
159
160    public static function getMyAddress()
161    {
162        if (Util::isInGithubCI())
163            return $_SERVER['TEST_APP_URL'];
164        if ($url = @$_SERVER['DDEV_PRIMARY_URL'] && $_SERVER['HTTP_X_FORWARDED_HOST'])
165            return 'https://' . $_SERVER['HTTP_X_FORWARDED_HOST'];
166        return "https://test.tsumego.ddev.site:33003";
167    }
168
169    public static function getInternalAddress()
170    {
171        if (Util::isInGithubCI())
172            return 'https://host.docker.internal:8443';
173        return 'http://localhost';
174    }
175
176    public static function addSqlCondition(&$existingCondition, $condition): void
177    {
178        if (empty($condition))
179            return;
180        if (empty($existingCondition))
181        {
182            $existingCondition = $condition;
183            return;
184        }
185        $existingCondition .= " AND " ;
186        if (str_contains($condition, " OR "))
187            $existingCondition .= '(' . $condition . ')';
188        else
189            $existingCondition .= $condition;
190    }
191
192    public static function addSqlOrCondition(&$existingCondition, $condition): void
193    {
194        if (empty($existingCondition))
195        {
196            $existingCondition = $condition;
197            return;
198        }
199        $existingCondition .= " OR " . $condition;
200    }
201
202    public static function boolString($bool)
203    {
204        return $bool ? 'true' : 'false';
205    }
206
207    public static function getHealthBasedOnLevel(int $level): int
208    {
209        return intdiv($level, 5) + 10;
210    }
211
212    public static function query($sql, $params = [])
213    {
214        /** @phpstan-ignore-next-line */
215        $stmt = ClassRegistry::init('Tsumego')->getDataSource()->getConnection()->prepare($sql);
216        $stmt->execute($params);
217        return $stmt->fetchAll(PDO::FETCH_ASSOC);
218    }
219
220    public static function clampOptional($value, $min, $max)
221    {
222        $result = $value;
223        if (!is_null($min))
224            $result = max($min, $result);
225        if (!is_null($max))
226            $result = min($max, $result);
227        return $result;
228    }
229
230    public static function strOrNull($input): ?string
231    {
232        if (is_null($input))
233            return null;
234        return strval($input);
235    }
236
237    public static function getGraphColor(): string
238    {
239        return Auth::lightMode() == Auth::$LIGHT_MODE ? '#ddd' : '#3e3e3e';
240    }
241
242    public static function getGraphGridColor(): string
243    {
244        return Auth::lightMode() == Auth::$LIGHT_MODE ? '#000' : '#fff';
245    }
246
247    public static function getValueGraphHeight($input)
248    {
249        return 160 + count($input) * 25;
250    }
251
252    public static function smallScoreTableRowColor(int $index): string
253    {
254        if ($index == 0)
255            return '#ffec85';
256        if ($index == 1)
257            return '#939393';
258        if ($index == 2)
259            return '#c28d47';
260        if ($index == 3)
261            return '#85e35d';
262        if ($index == 4)
263            return '#85e35d';
264        if ($index == 5)
265            return '#85e35d';
266        if ($index == 6)
267            return '#85e35d';
268        if ($index == 7)
269            return '#85e35d';
270        if ($index == 8)
271            return '#85e35d';
272        if ($index == 9)
273            return '#85e35d';
274        if ($index == 10)
275            return '#85e35d';
276        if ($index == 11)
277            return '#85e35d';
278        if ($index == 12)
279            return '#85e35d';
280        if ($index == 13)
281            return '#85e35d';
282        if ($index == 14)
283            return '#85e35d';
284        if ($index == 15)
285            return '#85e35d';
286        if ($index == 16)
287            return '#85e35d';
288        if ($index == 17)
289            return '#85e35d';
290        if ($index == 18)
291            return '#85e35d';
292        if ($index == 19)
293            return '#85e35d';
294        if ($index == 20)
295            return '#9cf974';
296        if ($index == 21)
297            return '#9cf974';
298        if ($index == 22)
299            return '#9cf974';
300        if ($index == 23)
301            return '#9cf974';
302        if ($index == 24)
303            return '#9cf974';
304        if ($index == 25)
305            return '#9cf974';
306        if ($index == 26)
307            return '#9cf974';
308        if ($index == 27)
309            return '#9cf974';
310        if ($index == 28)
311            return '#9cf974';
312        if ($index == 29)
313            return '#9cf974';
314        if ($index < 40)
315            return '#b6f998';
316        if ($index < 50)
317            return '#d3f9c2';
318        return '#e8f9e0';
319    }
320}