Utils::GetCutOffDate()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 8
rs 10
c 1
b 0
f 0
1
<?php
2
3
/*
4
 * SPDX-License-Identifier: AGPL-3.0-only
5
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
6
 * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH
7
 *
8
 * Several utility functions
9
 */
10
11
class Utils {
12
	/**
13
	 * Prints a variable as string
14
	 * If a boolean is sent, 'true' or 'false' is displayed.
15
	 *
16
	 * @param string $var
17
	 *
18
	 * @return string
19
	 */
20
	public static function PrintAsString($var) {
21
		return ($var) ? (($var === true) ? 'true' : $var) : (($var === false) ? 'false' : (($var === '') ? 'empty' : ($var ?? 'null')));
0 ignored issues
show
introduced by
The condition $var === false is always false.
Loading history...
introduced by
The condition $var === true is always false.
Loading history...
22
	}
23
24
	/**
25
	 * Splits a "domain\user" string into two values
26
	 * If the string contains only the user, domain is returned empty.
27
	 *
28
	 * @param string $domainuser
29
	 *
30
	 * @return array index 0: user  1: domain
31
	 */
32
	public static function SplitDomainUser($domainuser) {
33
		$pos = strrpos($domainuser, '\\');
34
		if ($pos === false) {
35
			$user = $domainuser;
36
			$domain = '';
37
		}
38
		else {
39
			$domain = substr($domainuser, 0, $pos);
40
			$user = substr($domainuser, $pos + 1);
41
		}
42
43
		return [$user, $domain];
44
	}
45
46
	/**
47
	 * Build an address string from the components.
48
	 *
49
	 * @param string $street  the street
50
	 * @param string $zip     the zip code
51
	 * @param string $city    the city
52
	 * @param string $state   the state
53
	 * @param string $country the country
54
	 *
55
	 * @return string the address string or null
56
	 */
57
	public static function BuildAddressString($street, $zip, $city, $state, $country) {
58
		$out = $country ?? "";
59
60
		$zcs = $zip ?? "";
61
		if ($city != "") {
62
			$zcs .= (($zcs) ? " " : "") . $city;
63
		}
64
		if ($state != "") {
65
			$zcs .= (($zcs) ? " " : "") . $state;
66
		}
67
		if ($zcs) {
68
			$out = $zcs . "\r\n" . $out;
69
		}
70
71
		if ($street != "") {
72
			$out = $street . (($out) ? "\r\n\r\n" . $out : "");
73
		}
74
75
		return $out ?? null;
76
	}
77
78
	/**
79
	 * Build the fileas string from the components according to the configuration.
80
	 *
81
	 * @param string $lastname
82
	 * @param string $firstname
83
	 * @param string $middlename
84
	 * @param string $company
85
	 *
86
	 * @return string fileas
87
	 */
88
	public static function BuildFileAs($lastname = "", $firstname = "", $middlename = "", $company = "") {
89
		if (defined('FILEAS_ORDER')) {
90
			$fileas = $lastfirst = $firstlast = "";
91
			$names = trim($firstname . " " . $middlename);
92
			$lastname = trim($lastname);
93
			$company = trim($company);
94
95
			// lastfirst is "lastname, firstname middlename"
96
			// firstlast is "firstname middlename lastname"
97
			if (strlen($lastname) > 0) {
98
				$lastfirst = $lastname;
99
				if (strlen($names) > 0) {
100
					$lastfirst .= ", {$names}";
101
					$firstlast = "{$names} {$lastname}";
102
				}
103
				else {
104
					$firstlast = $lastname;
105
				}
106
			}
107
			elseif (strlen($names) > 0) {
108
				$lastfirst = $firstlast = $names;
109
			}
110
111
			// if fileas with a company is selected
112
			// but company is empty then it will
113
			// fallback to firstlast or lastfirst
114
			// (depending on which is selected for company)
115
			switch (FILEAS_ORDER) {
116
				case SYNC_FILEAS_COMPANYONLY:
117
					if (strlen($company) > 0) {
118
						$fileas = $company;
119
					}
120
					elseif (strlen($firstlast) > 0) {
121
						$fileas = $lastfirst;
122
					}
123
					break;
124
125
				case SYNC_FILEAS_COMPANYLAST:
126
					if (strlen($company) > 0) {
127
						$fileas = $company;
128
						if (strlen($lastfirst) > 0) {
129
							$fileas .= "({$lastfirst})";
130
						}
131
					}
132
					elseif (strlen($lastfirst) > 0) {
133
						$fileas = $lastfirst;
134
					}
135
					break;
136
137
				case SYNC_FILEAS_COMPANYFIRST:
138
					if (strlen($company) > 0) {
139
						$fileas = $company;
140
						if (strlen($firstlast) > 0) {
141
							$fileas .= " ({$firstlast})";
142
						}
143
					}
144
					elseif (strlen($firstlast) > 0) {
145
						$fileas = $firstlast;
146
					}
147
					break;
148
149
				case SYNC_FILEAS_FIRSTCOMPANY:
150
					if (strlen($firstlast) > 0) {
151
						$fileas = $firstlast;
152
						if (strlen($company) > 0) {
153
							$fileas .= " ({$company})";
154
						}
155
					}
156
					elseif (strlen($company) > 0) {
157
						$fileas = $company;
158
					}
159
					break;
160
161
				case SYNC_FILEAS_LASTCOMPANY:
162
					if (strlen($lastfirst) > 0) {
163
						$fileas = $lastfirst;
164
						if (strlen($company) > 0) {
165
							$fileas .= " ({$company})";
166
						}
167
					}
168
					elseif (strlen($company) > 0) {
169
						$fileas = $company;
170
					}
171
					break;
172
173
				case SYNC_FILEAS_LASTFIRST:
174
					if (strlen($lastfirst) > 0) {
175
						$fileas = $lastfirst;
176
					}
177
					break;
178
179
				default:
180
					$fileas = $firstlast;
181
					break;
182
			}
183
			if (strlen($fileas) == 0) {
184
				SLog::Write(LOGLEVEL_DEBUG, "Fileas is empty.");
185
			}
186
187
			return $fileas;
188
		}
189
		SLog::Write(LOGLEVEL_DEBUG, "FILEAS_ORDER not defined. Add it to your config.php.");
190
191
		return null;
192
	}
193
194
	/**
195
	 * Extracts the basedate of the GlobalObjectID and the RecurStartTime.
196
	 *
197
	 * @param string $goid           OL compatible GlobalObjectID
198
	 * @param long   $recurStartTime
0 ignored issues
show
Bug introduced by
The type long was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
199
	 *
200
	 * @return long basedate
201
	 */
202
	public static function ExtractBaseDate($goid, $recurStartTime) {
203
		$hexbase = substr(bin2hex($goid), 32, 8);
204
		$day = hexdec(substr($hexbase, 6, 2));
205
		$month = hexdec(substr($hexbase, 4, 2));
206
		$year = hexdec(substr($hexbase, 0, 4));
207
208
		if ($day && $month && $year) {
209
			$h = $recurStartTime >> 12;
210
			$m = ($recurStartTime - $h * 4096) >> 6;
211
			$s = $recurStartTime - $h * 4096 - $m * 64;
212
213
			return gmmktime($h, $m, $s, $month, $day, $year);
0 ignored issues
show
Bug introduced by
It seems like $day can also be of type double; however, parameter $day of gmmktime() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

213
			return gmmktime($h, $m, $s, $month, /** @scrutinizer ignore-type */ $day, $year);
Loading history...
Bug Best Practice introduced by
The expression return gmmktime($h, $m, $s, $month, $day, $year) returns the type integer which is incompatible with the documented return type long.
Loading history...
Bug introduced by
It seems like $year can also be of type double; however, parameter $year of gmmktime() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

213
			return gmmktime($h, $m, $s, $month, $day, /** @scrutinizer ignore-type */ $year);
Loading history...
Bug introduced by
It seems like $month can also be of type double; however, parameter $month of gmmktime() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

213
			return gmmktime($h, $m, $s, /** @scrutinizer ignore-type */ $month, $day, $year);
Loading history...
214
		}
215
216
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type long.
Loading history...
217
	}
218
219
	/**
220
	 * Converts SYNC_FILTERTYPE into a timestamp.
221
	 *
222
	 * @param int $filtertype Filtertype
223
	 *
224
	 * @return long
225
	 */
226
	public static function GetCutOffDate($filtertype) {
227
		$back = Utils::GetFiltertypeInterval($filtertype);
228
229
		if ($back === false) {
230
			return 0; // unlimited
0 ignored issues
show
Bug Best Practice introduced by
The expression return 0 returns the type integer which is incompatible with the documented return type long.
Loading history...
231
		}
232
233
		return time() - $back;
0 ignored issues
show
Bug Best Practice introduced by
The expression return time() - $back returns the type integer which is incompatible with the documented return type long.
Loading history...
234
	}
235
236
	/**
237
	 * Returns the interval indicated by the filtertype.
238
	 *
239
	 * @param int $filtertype
240
	 *
241
	 * @return bool|long returns false on invalid filtertype
242
	 */
243
	public static function GetFiltertypeInterval($filtertype) {
244
		$back = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $back is dead and can be removed.
Loading history...
245
246
		return match ($filtertype) {
247
			SYNC_FILTERTYPE_1DAY => 60 * 60 * 24,
248
			SYNC_FILTERTYPE_3DAYS => 60 * 60 * 24 * 3,
249
			SYNC_FILTERTYPE_1WEEK => 60 * 60 * 24 * 7,
250
			SYNC_FILTERTYPE_2WEEKS => 60 * 60 * 24 * 14,
251
			SYNC_FILTERTYPE_1MONTH => 60 * 60 * 24 * 31,
252
			SYNC_FILTERTYPE_3MONTHS => 60 * 60 * 24 * 31 * 3,
253
			SYNC_FILTERTYPE_6MONTHS => 60 * 60 * 24 * 31 * 6,
254
			default => false,
255
		};
256
	}
257
258
	/**
259
	 * Converts SYNC_TRUNCATION into bytes.
260
	 *
261
	 * @param int       SYNC_TRUNCATION
0 ignored issues
show
Bug introduced by
The type SYNC_TRUNCATION was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
262
	 * @param mixed $truncation
263
	 *
264
	 * @return long
265
	 */
266
	public static function GetTruncSize($truncation) {
267
		return match ($truncation) {
268
			SYNC_TRUNCATION_HEADERS => 0,
269
			SYNC_TRUNCATION_512B => 512,
270
			SYNC_TRUNCATION_1K => 1024,
271
			SYNC_TRUNCATION_2K => 2 * 1024,
272
			SYNC_TRUNCATION_5K => 5 * 1024,
273
			SYNC_TRUNCATION_10K => 10 * 1024,
274
			SYNC_TRUNCATION_20K => 20 * 1024,
275
			SYNC_TRUNCATION_50K => 50 * 1024,
276
			SYNC_TRUNCATION_100K => 100 * 1024,
277
			SYNC_TRUNCATION_ALL => 1024 * 1024,
278
			default => 1024,
279
		};
280
	}
281
282
	/**
283
	 * Truncate an UTF-8 encoded string correctly.
284
	 *
285
	 * If it's not possible to truncate properly, an empty string is returned
286
	 *
287
	 * @param string $string   the string
288
	 * @param string $length   position where string should be cut
289
	 * @param bool   $htmlsafe doesn't cut html tags in half, doesn't ensure correct html - default: false
290
	 *
291
	 * @return string truncated string
292
	 */
293
	public static function Utf8_truncate($string, $length, $htmlsafe = false) {
294
		// skip empty strings
295
		if (empty($string)) {
296
			return "";
297
		}
298
299
		// make sure length is always an integer
300
		$length = (int) $length;
301
302
		// if the input string is shorter then the trunction, make sure it's valid UTF-8!
303
		if (strlen($string) <= $length) {
304
			$length = strlen($string) - 1;
305
		}
306
307
		// The intent is not to cut HTML tags in half which causes displaying issues.
308
		// The used method just tries to cut outside of tags, without checking tag validity and closing tags.
309
		if ($htmlsafe) {
310
			$offset = 0 - strlen($string) + $length;
311
			$validPos = strrpos($string, "<", $offset);
312
			if ($validPos > strrpos($string, ">", $offset)) {
313
				$length = $validPos;
314
			}
315
		}
316
317
		while ($length >= 0) {
318
			if ((ord($string[$length]) < 0x80) || (ord($string[$length]) >= 0xC0)) {
319
				return substr($string, 0, $length);
320
			}
321
			--$length;
322
		}
323
324
		return "";
325
	}
326
327
	/**
328
	 * Indicates if the specified folder type is a system folder.
329
	 *
330
	 * @param int $foldertype
331
	 *
332
	 * @return bool
333
	 */
334
	public static function IsSystemFolder($foldertype) {
335
		return (
336
			$foldertype == SYNC_FOLDER_TYPE_INBOX ||
337
			$foldertype == SYNC_FOLDER_TYPE_DRAFTS ||
338
			$foldertype == SYNC_FOLDER_TYPE_WASTEBASKET ||
339
			$foldertype == SYNC_FOLDER_TYPE_SENTMAIL ||
340
			$foldertype == SYNC_FOLDER_TYPE_OUTBOX ||
341
			$foldertype == SYNC_FOLDER_TYPE_TASK ||
342
			$foldertype == SYNC_FOLDER_TYPE_APPOINTMENT ||
343
			$foldertype == SYNC_FOLDER_TYPE_CONTACT ||
344
			$foldertype == SYNC_FOLDER_TYPE_NOTE ||
345
			$foldertype == SYNC_FOLDER_TYPE_JOURNAL
346
		) ? true : false;
347
	}
348
349
	/**
350
	 * Checks for valid email addresses
351
	 * The used regex actually only checks if a valid email address is part of the submitted string
352
	 * it also returns true for the mailbox format, but this is not checked explicitly.
353
	 *
354
	 * @param string $email address to be checked
355
	 *
356
	 * @return bool
357
	 */
358
	public static function CheckEmail($email) {
359
		return str_contains($email, '@') ? true : false;
360
	}
361
362
	/**
363
	 * Checks if a string is base64 encoded.
364
	 *
365
	 * @param string $string the string to be checked
366
	 *
367
	 * @return bool
368
	 */
369
	public static function IsBase64String($string) {
370
		return (bool) preg_match("#^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+/]{4})?$#", $string);
371
	}
372
373
	/**
374
	 * Returns a command string for a given command code.
375
	 *
376
	 * @param int $code
377
	 *
378
	 * @return string or false if code is unknown
379
	 */
380
	public static function GetCommandFromCode($code) {
381
		return match ($code) {
382
			GSync::COMMAND_SYNC => 'Sync',
383
			GSync::COMMAND_SENDMAIL => 'SendMail',
384
			GSync::COMMAND_SMARTFORWARD => 'SmartForward',
385
			GSync::COMMAND_SMARTREPLY => 'SmartReply',
386
			GSync::COMMAND_GETATTACHMENT => 'GetAttachment',
387
			GSync::COMMAND_FOLDERSYNC => 'FolderSync',
388
			GSync::COMMAND_FOLDERCREATE => 'FolderCreate',
389
			GSync::COMMAND_FOLDERDELETE => 'FolderDelete',
390
			GSync::COMMAND_FOLDERUPDATE => 'FolderUpdate',
391
			GSync::COMMAND_MOVEITEMS => 'MoveItems',
392
			GSync::COMMAND_GETITEMESTIMATE => 'GetItemEstimate',
393
			GSync::COMMAND_MEETINGRESPONSE => 'MeetingResponse',
394
			GSync::COMMAND_SEARCH => 'Search',
395
			GSync::COMMAND_SETTINGS => 'Settings',
396
			GSync::COMMAND_PING => 'Ping',
397
			GSync::COMMAND_ITEMOPERATIONS => 'ItemOperations',
398
			GSync::COMMAND_PROVISION => 'Provision',
399
			GSync::COMMAND_RESOLVERECIPIENTS => 'ResolveRecipients',
400
			GSync::COMMAND_VALIDATECERT => 'ValidateCert',
401
			GSync::COMMAND_GETHIERARCHY => 'GetHierarchy',
402
			GSync::COMMAND_CREATECOLLECTION => 'CreateCollection',
403
			GSync::COMMAND_DELETECOLLECTION => 'DeleteCollection',
404
			GSync::COMMAND_MOVECOLLECTION => 'MoveCollection',
405
			GSync::COMMAND_NOTIFY => 'Notify',
406
			GSync::COMMAND_FIND => 'Find',
407
			default => false,
408
		};
409
	}
410
411
	/**
412
	 * Returns a command code for a given command.
413
	 *
414
	 * @param string $command
415
	 *
416
	 * @return int or false if command is unknown
417
	 */
418
	public static function GetCodeFromCommand($command) {
419
		switch ($command) {
420
			case 'Sync':                 return GSync::COMMAND_SYNC;
421
422
			case 'SendMail':             return GSync::COMMAND_SENDMAIL;
423
424
			case 'SmartForward':         return GSync::COMMAND_SMARTFORWARD;
425
426
			case 'SmartReply':           return GSync::COMMAND_SMARTREPLY;
427
428
			case 'GetAttachment':        return GSync::COMMAND_GETATTACHMENT;
429
430
			case 'FolderSync':           return GSync::COMMAND_FOLDERSYNC;
431
432
			case 'FolderCreate':         return GSync::COMMAND_FOLDERCREATE;
433
434
			case 'FolderDelete':         return GSync::COMMAND_FOLDERDELETE;
435
436
			case 'FolderUpdate':         return GSync::COMMAND_FOLDERUPDATE;
437
438
			case 'MoveItems':            return GSync::COMMAND_MOVEITEMS;
439
440
			case 'GetItemEstimate':      return GSync::COMMAND_GETITEMESTIMATE;
441
442
			case 'MeetingResponse':      return GSync::COMMAND_MEETINGRESPONSE;
443
444
			case 'Search':               return GSync::COMMAND_SEARCH;
445
446
			case 'Settings':             return GSync::COMMAND_SETTINGS;
447
448
			case 'Ping':                 return GSync::COMMAND_PING;
449
450
			case 'ItemOperations':       return GSync::COMMAND_ITEMOPERATIONS;
451
452
			case 'Provision':            return GSync::COMMAND_PROVISION;
453
454
			case 'ResolveRecipients':    return GSync::COMMAND_RESOLVERECIPIENTS;
455
456
			case 'ValidateCert':         return GSync::COMMAND_VALIDATECERT;
457
458
				// Deprecated commands
459
			case 'GetHierarchy':         return GSync::COMMAND_GETHIERARCHY;
460
461
			case 'CreateCollection':     return GSync::COMMAND_CREATECOLLECTION;
462
463
			case 'DeleteCollection':     return GSync::COMMAND_DELETECOLLECTION;
464
465
			case 'MoveCollection':       return GSync::COMMAND_MOVECOLLECTION;
466
467
			case 'Notify':               return GSync::COMMAND_NOTIFY;
468
469
			case 'Find':                 return GSync::COMMAND_FIND;
470
		}
471
472
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
473
	}
474
475
	/**
476
	 * Normalize the given timestamp to the start of the day.
477
	 *
478
	 * @param long $timestamp
479
	 *
480
	 * @return long
481
	 */
482
	public static function getDayStartOfTimestamp($timestamp) {
483
		return $timestamp - ($timestamp % (60 * 60 * 24));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $timestamp - $timestamp % 60 * 60 * 24 returns the type integer which is incompatible with the documented return type long.
Loading history...
484
	}
485
486
	/**
487
	 * Returns a formatted string output from an optional timestamp.
488
	 * If no timestamp is sent, NOW is used.
489
	 *
490
	 * @param long $timestamp
491
	 *
492
	 * @return string
493
	 */
494
	public static function GetFormattedTime($timestamp = false) {
495
		if (!$timestamp) {
496
			$timestamp = time();
497
		}
498
499
		return date("d/m/Y H:i:s", $timestamp);
0 ignored issues
show
Bug introduced by
It seems like $timestamp can also be of type long; however, parameter $timestamp of date() does only seem to accept integer|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

499
		return date("d/m/Y H:i:s", /** @scrutinizer ignore-type */ $timestamp);
Loading history...
500
	}
501
502
	/**
503
	 * Get charset name from a codepage.
504
	 *
505
	 * @see http://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx
506
	 *
507
	 * Table taken from common/codepage.cpp
508
	 *
509
	 * @param int codepage Codepage
0 ignored issues
show
Bug introduced by
The type codepage was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
510
	 * @param mixed $codepage
511
	 *
512
	 * @return string iconv-compatible charset name
513
	 */
514
	public static function GetCodepageCharset($codepage) {
515
		$codepages = [
516
			20106 => "DIN_66003",
517
			20108 => "NS_4551-1",
518
			20107 => "SEN_850200_B",
519
			950 => "big5",
520
			50221 => "csISO2022JP",
521
			51932 => "euc-jp",
522
			51936 => "euc-cn",
523
			51949 => "euc-kr",
524
			949 => "euc-kr",
525
			936 => "gb18030",
526
			52936 => "csgb2312",
527
			852 => "ibm852",
528
			866 => "ibm866",
529
			50220 => "iso-2022-jp",
530
			50222 => "iso-2022-jp",
531
			50225 => "iso-2022-kr",
532
			1252 => "windows-1252",
533
			28591 => "iso-8859-1",
534
			28592 => "iso-8859-2",
535
			28593 => "iso-8859-3",
536
			28594 => "iso-8859-4",
537
			28595 => "iso-8859-5",
538
			28596 => "iso-8859-6",
539
			28597 => "iso-8859-7",
540
			28598 => "iso-8859-8",
541
			28599 => "iso-8859-9",
542
			28603 => "iso-8859-13",
543
			28605 => "iso-8859-15",
544
			20866 => "koi8-r",
545
			21866 => "koi8-u",
546
			932 => "shift-jis",
547
			1200 => "unicode",
548
			1201 => "unicodebig",
549
			65000 => "utf-7",
550
			65001 => "utf-8",
551
			1250 => "windows-1250",
552
			1251 => "windows-1251",
553
			1253 => "windows-1253",
554
			1254 => "windows-1254",
555
			1255 => "windows-1255",
556
			1256 => "windows-1256",
557
			1257 => "windows-1257",
558
			1258 => "windows-1258",
559
			874 => "windows-874",
560
			20127 => "us-ascii",
561
		];
562
563
		// Defaulting to iso-8859-15 since it is more likely for someone to make a mistake in the codepage
564
		// when using west-european charsets then when using other charsets since utf-8 is binary compatible
565
		// with the bottom 7 bits of west-european
566
		return $codepages[$codepage] ?? "iso-8859-15";
567
	}
568
569
	/**
570
	 * Converts a string encoded with codepage into an UTF-8 string.
571
	 *
572
	 * @param int    $codepage
573
	 * @param string $string
574
	 *
575
	 * @return string
576
	 */
577
	public static function ConvertCodepageStringToUtf8($codepage, $string) {
578
		if (function_exists("iconv")) {
579
			$charset = self::GetCodepageCharset($codepage);
580
581
			return iconv($charset, "utf-8", $string);
582
		}
583
584
		SLog::Write(LOGLEVEL_WARN, "Utils::ConvertCodepageStringToUtf8() 'iconv' is not available. Charset conversion skipped.");
585
586
		return $string;
587
	}
588
589
	/**
590
	 * Converts a string to another charset.
591
	 *
592
	 * @param int    $in
593
	 * @param int    $out
594
	 * @param string $string
595
	 *
596
	 * @return string
597
	 */
598
	public static function ConvertCodepage($in, $out, $string) {
599
		// do nothing if both charsets are the same
600
		if ($in == $out) {
601
			return $string;
602
		}
603
604
		if (function_exists("iconv")) {
605
			$inCharset = self::GetCodepageCharset($in);
606
			$outCharset = self::GetCodepageCharset($out);
607
608
			return iconv($inCharset, $outCharset, $string);
609
		}
610
611
		SLog::Write(LOGLEVEL_WARN, "Utils::ConvertCodepage() 'iconv' is not available. Charset conversion skipped.");
612
613
		return $string;
614
	}
615
616
	/**
617
	 * Returns the best match of preferred body preference types.
618
	 *
619
	 * @param array $bpTypes
620
	 *
621
	 * @return int
622
	 */
623
	public static function GetBodyPreferenceBestMatch($bpTypes) {
624
		if ($bpTypes === false) {
0 ignored issues
show
introduced by
The condition $bpTypes === false is always false.
Loading history...
625
			return SYNC_BODYPREFERENCE_PLAIN;
626
		}
627
		// The bettter choice is HTML and then MIME in order to save bandwidth
628
		// because MIME is a complete message including the headers and attachments
629
		if (in_array(SYNC_BODYPREFERENCE_HTML, $bpTypes)) {
630
			return SYNC_BODYPREFERENCE_HTML;
631
		}
632
		if (in_array(SYNC_BODYPREFERENCE_MIME, $bpTypes)) {
633
			return SYNC_BODYPREFERENCE_MIME;
634
		}
635
636
		return SYNC_BODYPREFERENCE_PLAIN;
637
	}
638
639
	/**
640
	 * Returns AS-style LastVerbExecuted value from the server value.
641
	 *
642
	 * @param int $verb
643
	 *
644
	 * @return int
645
	 */
646
	public static function GetLastVerbExecuted($verb) {
647
		return match ($verb) {
648
			NOTEIVERB_REPLYTOSENDER => AS_REPLYTOSENDER,
649
			NOTEIVERB_REPLYTOALL => AS_REPLYTOALL,
650
			NOTEIVERB_FORWARD => AS_FORWARD,
651
			default => 0,
652
		};
653
	}
654
655
	/**
656
	 * Returns the local part from email address.
657
	 *
658
	 * @param string $email
659
	 *
660
	 * @return string
661
	 */
662
	public static function GetLocalPartFromEmail($email) {
663
		$pos = strpos($email, '@');
664
		if ($pos === false) {
665
			return $email;
666
		}
667
668
		return substr($email, 0, $pos);
669
	}
670
671
	/**
672
	 * Format bytes to a more human readable value.
673
	 *
674
	 * @param int $bytes
675
	 * @param int $precision
676
	 *
677
	 * @return string|void
678
	 */
679
	public static function FormatBytes($bytes, $precision = 2) {
680
		if ($bytes <= 0) {
681
			return '0 B';
682
		}
683
684
		$units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB'];
685
		$base = log($bytes, 1024);
686
		$fBase = floor($base);
687
		$pow = 1024 ** ($base - $fBase);
688
689
		return sprintf("%.{$precision}f %s", $pow, $units[$fBase]);
690
	}
691
692
	/**
693
	 * Returns folder origin identifier from its id.
694
	 *
695
	 * @param string $folderid
696
	 *
697
	 * @return bool|string matches values of DeviceManager::FLD_ORIGIN_*
698
	 */
699
	public static function GetFolderOriginFromId($folderid) {
700
		$origin = substr($folderid, 0, 1);
701
702
		switch ($origin) {
703
			case DeviceManager::FLD_ORIGIN_CONFIG:
704
			case DeviceManager::FLD_ORIGIN_GAB:
705
			case DeviceManager::FLD_ORIGIN_SHARED:
706
			case DeviceManager::FLD_ORIGIN_USER:
707
			case DeviceManager::FLD_ORIGIN_IMPERSONATED:
708
				return $origin;
709
		}
710
		SLog::Write(LOGLEVEL_WARN, sprintf("Utils->GetFolderOriginFromId(): Unknown folder origin for folder with id '%s'", $folderid));
711
712
		return false;
713
	}
714
715
	/**
716
	 * Returns folder origin as string from its id.
717
	 *
718
	 * @param string $folderid
719
	 *
720
	 * @return string
721
	 */
722
	public static function GetFolderOriginStringFromId($folderid) {
723
		$origin = substr($folderid, 0, 1);
724
725
		switch ($origin) {
726
			case DeviceManager::FLD_ORIGIN_CONFIG:
727
				return 'configured';
728
729
			case DeviceManager::FLD_ORIGIN_GAB:
730
				return 'GAB';
731
732
			case DeviceManager::FLD_ORIGIN_SHARED:
733
				return 'shared';
734
735
			case DeviceManager::FLD_ORIGIN_USER:
736
				return 'user';
737
738
			case DeviceManager::FLD_ORIGIN_IMPERSONATED:
739
				return 'impersonated';
740
		}
741
		SLog::Write(LOGLEVEL_WARN, sprintf("Utils->GetFolderOriginStringFromId(): Unknown folder origin for folder with id '%s'", $folderid));
742
743
		return 'unknown';
744
	}
745
746
	/**
747
	 * Splits the id into folder id and message id parts. A colon in the $id indicates
748
	 * that the id has folderid:messageid format.
749
	 *
750
	 * @param string $id
751
	 *
752
	 * @return array
753
	 */
754
	public static function SplitMessageId($id) {
755
		if (str_contains($id, ':')) {
756
			return explode(':', $id);
757
		}
758
759
		return [null, $id];
760
	}
761
762
	/**
763
	 * Transforms an AS timestamp into a unix timestamp.
764
	 *
765
	 * @param string $ts
766
	 *
767
	 * @return long
768
	 */
769
	public static function ParseDate($ts) {
770
		if (preg_match("/(\\d{4})[^0-9]*(\\d{2})[^0-9]*(\\d{2})(T(\\d{2})[^0-9]*(\\d{2})[^0-9]*(\\d{2})(.\\d+)?Z){0,1}$/", $ts, $matches)) {
771
			if ($matches[1] >= 2038) {
772
				$matches[1] = 2038;
773
				$matches[2] = 1;
774
				$matches[3] = 18;
775
				$matches[5] = $matches[6] = $matches[7] = 0;
776
			}
777
778
			if (!isset($matches[5])) {
779
				$matches[5] = 0;
780
			}
781
			if (!isset($matches[6])) {
782
				$matches[6] = 0;
783
			}
784
			if (!isset($matches[7])) {
785
				$matches[7] = 0;
786
			}
787
788
			return gmmktime($matches[5], $matches[6], $matches[7], $matches[2], $matches[3], $matches[1]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return gmmktime($matches...atches[3], $matches[1]) returns the type integer which is incompatible with the documented return type long.
Loading history...
789
		}
790
791
		return 0;
0 ignored issues
show
Bug Best Practice introduced by
The expression return 0 returns the type integer which is incompatible with the documented return type long.
Loading history...
792
	}
793
794
	/**
795
	 * Transforms an unix timestamp into an AS timestamp or a human readable format.
796
	 *
797
	 * Oh yeah, this is beautiful. Exchange outputs date fields differently in calendar items
798
	 * and emails. We could just always send one or the other, but unfortunately nokia's 'Mail for
799
	 * exchange' depends on this quirk. So we have to send a different date type depending on where
800
	 * it's used. Sigh.
801
	 *
802
	 * @param int $ts
803
	 * @param int $type int (StreamerType) (optional) if not set a human readable format is returned
804
	 *
805
	 * @return string
806
	 */
807
	public static function FormatDate($ts, $type = "") {
808
		// fallback to a human readable format (used for logging)
809
		$formatString = "yyyy-MM-dd HH:mm:SS' UTC'";
810
		if ($type == Streamer::STREAMER_TYPE_DATE) {
811
			$formatString = "yyyyMMdd'T'HHmmSS'Z'";
812
		}
813
		elseif ($type == Streamer::STREAMER_TYPE_DATE_DASHES) {
814
			$formatString = "yyyy-MM-dd'T'HH:mm:SS'.000Z'";
815
		}
816
817
		$formatter = datefmt_create(
818
			'en_US',
819
			IntlDateFormatter::FULL,
820
			IntlDateFormatter::FULL,
821
			'UTC',
822
			IntlDateFormatter::GREGORIAN,
823
			$formatString
824
		);
825
826
		return datefmt_format($formatter, $ts);
827
	}
828
829
	/**
830
	 * Returns the appropriate SyncObjectResponse object based on message class.
831
	 *
832
	 * @param string $messageClass
833
	 *
834
	 * @return object
835
	 */
836
	public static function GetResponseFromMessageClass($messageClass) {
837
		$messageClass = strtolower($messageClass);
838
839
		return match ($messageClass) {
840
			'syncappointment' => new SyncAppointmentResponse(),
841
			'synccontact' => new SyncContactResponse(),
842
			'syncnote' => new SyncNoteResponse(),
843
			'synctask' => new SyncTaskResponse(),
844
			default => new SyncMailResponse(),
845
		};
846
847
		return new SyncMailResponse();
0 ignored issues
show
Unused Code introduced by
return new SyncMailResponse() is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
848
	}
849
}
850