Source Code: utility_functions_inc.php

  1: <?php
  2: /**
  3:  * This file contains utility functions.
  4:  *
  5:  * @author Oleg Schildt
  6:  *
  7:  * @package Utility Functions
  8:  */
  9: 
 10: namespace SmartFactory;
 11: 
 12: /**
 13:  * Checks whether the array $array is associative.
 14:  *
 15:  * @param array &$array
 16:  * Array to be checked.
 17:  *
 18:  * @return bool
 19:  * Returns true if the array is associative, otherwise false.
 20:  *
 21:  * @author Oleg Schildt
 22:  */
 23: function is_associative(array &$array): bool
 24: {
 25:     if (!is_array($array) || empty($array)) {
 26:         return false;
 27:     }
 28:     
 29:     $keys = array_keys($array);
 30:     
 31:     return array_keys($keys) !== $keys;
 32: } // is_associative
 33: 
 34: /**
 35:  * Checks whether the session is cmd client or web.
 36:  *
 37:  * @return bool
 38:  * Returns true if the session is web, otherwise false.
 39:  *
 40:  * @author Oleg Schildt
 41:  */
 42: function is_web(): bool
 43: {
 44:     return http_response_code() !== false;
 45: } // is_web
 46: 
 47: /**
 48:  * Defines the common prefix of two strings.
 49:  *
 50:  * @param string $s1
 51:  * First string to be checked.
 52:  *
 53:  * @param string $s2
 54:  * Second string to be checked.
 55:  *
 56:  * @param int $max
 57:  * The maximal number of charactes to check.
 58:  *
 59:  * @return string
 60:  * Returns the common prefix of the passed strings.
 61:  *
 62:  * @author Oleg Schildt
 63:  */
 64: function common_prefix(string $s1, string $s2, $max = 1000): string
 65: {
 66:     $prefix = "";
 67:     
 68:     $l1 = strlen($s1);
 69:     $l2 = strlen($s2);
 70:     
 71:     for ($i = 0; $i < $max; $i++) {
 72:         if ($i >= $l1 || $i >= $l2) {
 73:             break;
 74:         }
 75:         
 76:         if ($s1[$i] != $s2[$i]) {
 77:             break;
 78:         }
 79:         
 80:         $prefix .= $s1[$i];
 81:     }
 82:     
 83:     return $prefix;
 84: } // common_prefix
 85: 
 86: /**
 87:  * Converts the JSON string to an array.
 88:  *
 89:  * It is a wrapper over the system function json_decode. It
 90:  * is introduced to give the ability to overwrite the system
 91:  * function if necessary.
 92:  *
 93:  * @param string &$json
 94:  * Input JSON string.
 95:  *
 96:  * @param array &$array
 97:  * target array.
 98:  *
 99:  * @throws \Exception
100:  * It might throw the exception if the JSON cannot be parsed.
101:  *
102:  * @return void
103:  *
104:  * @author Oleg Schildt
105:  */
106: function json_to_array(string &$json, array &$array): void
107: {
108:     $result = json_decode($json, true);
109:     if ($result === null) {
110:         throw new \Exception(json_last_error_msg());
111:     }
112:     
113:     if (empty($array)) {
114:         $array = [];
115:     }
116:     
117:     $array = array_merge($array, $result);
118: } // json_to_array
119: 
120: /**
121:  * Converts the array to JSON string.
122:  *
123:  * It is a wrapper over the system function json_encode. It
124:  * is introduced to give the ability to overwrite the system
125:  * function if necessary.
126:  *
127:  * @param array &$array
128:  * Array to be converted.
129:  *
130:  * @return string
131:  * Returns the JSON string of the array.
132:  *
133:  * @author Oleg Schildt
134:  */
135: function array_to_json(array &$array): string
136: {
137:     return json_encode($array, JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK | JSON_UNESCAPED_UNICODE);
138: } // array_to_json
139: 
140: /**
141:  * Converts an array to XML DOM structure.
142:  *
143:  * This function can convert an array of any dimensions
144:  * to a DOM structure. It might be used for loading and saving
145:  * settings to a config file.
146:  *
147:  * @param \DOMNode &$node
148:  * The parent node of the DOM structure.
149:  *
150:  * @param array &$array
151:  * The array to be converted to the DOM structure.
152:  *
153:  * @return void
154:  *
155:  * @see \SmartFactory\dom_to_array()
156:  *
157:  * @author Oleg Schildt
158:  */
159: function array_to_dom(&$node, &$array): void
160: {
161:     $xmldoc = $node->ownerDocument;
162:     
163:     $node->setAttribute("array", 1);
164:     
165:     foreach ($array as $key => &$val) {
166:         $child = $xmldoc->createElement("item");
167:         $child->setAttribute("name", $key);
168:         
169:         if (is_array($val)) {
170:             array_to_dom($child, $val);
171:         } else {
172:             $txtnode = $xmldoc->createTextNode($val);
173:             $child->appendChild($txtnode);
174:         }
175:         
176:         $node->appendChild($child);
177:     }
178: } // array_to_dom
179: 
180: /**
181:  * Converts a XML DOM structure to an array.
182:  *
183:  * This function can convert any DOM structure to an array.
184:  * It might be used for loading and saving settings to a
185:  * config file.
186:  *
187:  * @param \DOMNode &$node
188:  * The parent node of the DOM structure to be converted to the array.
189:  *
190:  * @param array &$array
191:  * The tagrget array to be filled from the DOM structure.
192:  *
193:  * @return void
194:  *
195:  * @see \SmartFactory\array_to_dom()
196:  *
197:  * @author Oleg Schildt
198:  */
199: function dom_to_array(\DOMNode &$node, array &$array): void
200: {
201:     if (!$node->hasChildNodes()) {
202:         return;
203:     }
204:     
205:     foreach ($node->childNodes as $child) {
206:         if ($child->nodeType == XML_TEXT_NODE) {
207:             continue;
208:         }
209:         
210:         $name = $child->nodeName;
211:         if ($child->nodeName == "item") {
212:             $name = $child->getAttribute("name");
213:         }
214:         
215:         // has a single text node
216:         
217:         if ($child->hasChildNodes() && $child->childNodes->length == 1 && $child->childNodes->item(0)->nodeType == XML_TEXT_NODE) {
218:             $array[$name] = $child->childNodes->item(0)->nodeValue;
219:             continue;
220:         }
221:         
222:         // has a collection
223:         
224:         if ($child->hasChildNodes() || $child->getAttribute("array") == "1") {
225:             if (!isset($array[$name])) {
226:                 $array[$name] = array();
227:             }
228:             dom_to_array($child, $array[$name]);
229:             continue;
230:         }
231:         
232:         $array[$name] = $child->nodeValue;
233:     }
234: } // dom_to_array
235: 
236: /**
237:  * Checks whether the text is an empty string.
238:  *
239:  * @param ?string $text
240:  * The text to be checked.
241:  *
242:  * @return bool
243:  * Returns true if the string is an empty string.
244:  *
245:  * @author Oleg Schildt
246:  */
247: function string_empty(?string $text): bool
248: {
249:     if ($text === null) return true;
250: 
251:     if ($text === "") return true;
252:     
253:     return false;
254: } // string_empty
255: 
256: /**
257:  * Escapes the HTML special characters in the text.
258:  *
259:  * @param ?string $text
260:  * The text to be escaped.
261:  *
262:  * @return ?string
263:  * Returns the text with escaped HTML special characters.
264:  *
265:  * @see \SmartFactory\escape_html_array()
266:  * @see \SmartFactory\escape_js()
267:  *
268:  * @author Oleg Schildt
269:  */
270: function escape_html(?string $text): ?string
271: {
272:     if (empty($text)) {
273:         return $text;
274:     }
275: 
276:     return htmlspecialchars($text, ENT_QUOTES);
277: } // escape_html
278: 
279: /**
280:  * Escapes recursively the HTML special characters in the values
281:  * of the array.
282:  *
283:  * @param array &$array
284:  * The array to be escaped.
285:  *
286:  * @return void
287:  *
288:  * @see \SmartFactory\escape_html()
289:  * @see \SmartFactory\escape_js()
290:  *
291:  * @author Oleg Schildt
292:  */
293: 
294: function escape_html_array(array &$array): void
295: {
296:     foreach ($array as &$val) {
297:         if (is_array($val)) {
298:             escape_html_array($val);
299:         } else {
300:             $val = htmlspecialchars($val, ENT_QUOTES);
301:         }
302:     }
303: } // escape_html_array
304: 
305: /**
306:  * Escapes the JavaScript special characters in the text.
307:  *
308:  * @param string $text
309:  * The text to be escaped.
310:  *
311:  * @return string
312:  * Returns the text with escaped JavaScript special characters.
313:  *
314:  * @see \SmartFactory\escape_html()
315:  *
316:  * @author Oleg Schildt
317:  */
318: function escape_js(string $text): string
319: {
320:     $text = str_replace("\\", "\\\\", $text);
321:     $text = str_replace("\n", "\\n", $text);
322:     $text = str_replace("\r", "\\r", $text);
323:     
324:     $text = str_replace("/", "\\/", $text);
325:     $text = str_replace("'", "\\'", $text);
326:     $text = str_replace("\"", "\\\"", $text);
327:     
328:     return $text;
329: } // escape_js
330: 
331: /**
332:  * Gets the cookie value by name.
333:  *
334:  * @param string $name
335:  * Name of the cookie.
336:  *
337:  * @return string
338:  * Returns the cookie value of empty string if the cookie is not set.
339:  *
340:  * @author Oleg Schildt
341:  */
342: function get_cookie(string $name): string
343: {
344:     return empty($_COOKIE[$name]) ? "" : $_COOKIE[$name];
345: } // get_cookie
346: 
347: /**
348:  * Gets the header by its name.
349:  *
350:  * @param string $name
351:  * Name of the header.
352:  *
353:  * @return string
354:  * Returns the header value of empty string if the header is not set.
355:  *
356:  * @author Oleg Schildt
357:  */
358: function get_header(string $name): string {
359:     static $headers;
360: 
361:     if(empty($headers)) {
362:         $tmp = getallheaders();
363:         foreach($tmp as $header => $value) {
364:             $headers[strtolower($header)] = $value;
365:         }
366:     }
367: 
368:     if(empty($headers[strtolower($name)])) return "";
369: 
370:     return $headers[strtolower($name)];
371: }
372: 
373: /**
374:  * Sets the cookie name=value.
375:  *
376:  * @param string $name
377:  * Name of the cookie.
378:  *
379:  * @param string $value
380:  * Value of the cookie.
381:  *
382:  * @param int $expires
383:  * The time the cookie expires.
384:  *
385:  * @param array $params
386:  * Any additional parameters like expires, path, domain, secure, httponly or samesite.
387:  *
388:  * @return bool
389:  * Returns true if the cookie has been set successfully, otherwise false.
390:  *
391:  * @author Oleg Schildt
392:  */
393: function set_cookie(string $name, string $value = "", int $expires = 0, array $params = []): bool
394: {
395:     if (version_compare(phpversion(), "7.3") >= 0) {
396:         $params["expires"] = $expires;
397:         return setcookie($name, $value, $params);
398:     }
399:     
400:     $path = "";
401:     if (!empty($params["path"])) {
402:         $path = $params["path"];
403:     }
404:     if (!empty($params["samesite"])) {
405:         $path .= "/; samesite=" . $params["samesite"];
406:     }
407:     return setcookie($name, $value, $expires, $path);
408: } // set_cookie
409: 
410: /**
411:  * Escapes the special characters in the text used for the regular
412:  * expression pattern.
413:  *
414:  * @param string $pattern
415:  * The pattern to be escaped.
416:  *
417:  * @return string
418:  * Returns the text with escaped special characters.
419:  *
420:  * @see \SmartFactory\preg_r_escape()
421:  *
422:  * @author Oleg Schildt
423:  */
424: function preg_p_escape(string $pattern): string
425: {
426:     return preg_replace("/[\\\\\\[\\]\\+\\?\\-\\^\\$\\(\\)\\/\\.\\|\\{\\}\\|]/", "\\\\$0", $pattern);
427: } // preg_p_escape
428: 
429: /**
430:  * Escapes the special characters in the text used for the regular
431:  * expression replacement.
432:  *
433:  * @param string $pattern
434:  * The pattern to be escaped.
435:  *
436:  * @return string
437:  * Returns the text with escaped special characters.
438:  *
439:  * @see \SmartFactory\preg_p_escape()
440:  *
441:  * @author Oleg Schildt
442:  */
443: function preg_r_escape(string $pattern): string
444: {
445:     return preg_replace("/[\\\\\\$]/", "\\\\$0", $pattern);
446: } // preg_r_escape
447: 
448: /**
449:  * Converts the string representing the date/time in the
450:  * specified format to the timestap.
451:  *
452:  * @param string $time_string
453:  * The date/time string.
454:  *
455:  * @param string $format
456:  * The date format of the date/time string in the PHP systax
457:  * for the date formats, e.g. "Y.m.d H:i:s".
458:  *
459:  * @return int|string|null
460:  * If the date/time string could be converted, the timestamp is
461:  * returned, otherwise the string "error" is returned.
462:  *
463:  * @author Oleg Schildt
464:  */
465: function timestamp(string $time_string, string $format): int|string|null
466: {
467:     if (empty($time_string)) {
468:         return null;
469:     }
470:     
471:     $err_status = "error";
472:     
473:     $pattern = preg_replace(array("/Y/", "/m/", "/d/", "/H/", "/i/", "/s/"), array("([0-9]{4})", "([0-9]{1,2})", "([0-9]{1,2})", "([0-9]{1,2})", "([0-9]{1,2})", "([0-9]{1,2})"), preg_quote($format));
474:     
475:     $units = array();
476:     
477:     if (!preg_match("/" . $pattern . "/", $time_string, $units)) {
478:         return $err_status;
479:     }
480:     
481:     array_shift($units);
482:     
483:     //return implode("|", $units);
484:     
485:     $order = preg_replace("/[^YmdHis]/", "", $format);
486:     
487:     $date_part = "";
488:     $pos_Y = strpos($order, "Y");
489:     $pos_m = strpos($order, "m");
490:     $pos_d = strpos($order, "d");
491:     if (!($pos_Y === false || $pos_m === false || $pos_d === false)) {
492:         if (!checkdate($units[$pos_m], $units[$pos_d], $units[$pos_Y])) {
493:             return $err_status;
494:         }
495:         
496:         $date_part = $units[$pos_Y] . "-" . $units[$pos_m] . "-" . $units[$pos_d];
497:     }
498:     
499:     $time_part = "";
500:     $pos_H = strpos($order, "H");
501:     $pos_i = strpos($order, "i");
502:     $pos_s = strpos($order, "s");
503:     if (!($pos_H === false || $pos_i === false)) {
504:         if (!is_numeric($units[$pos_H]) || $units[$pos_H] < 0 || $units[$pos_H] > 23) {
505:             return $err_status;
506:         }
507:         if (!is_numeric($units[$pos_i]) || $units[$pos_i] < 0 || $units[$pos_i] > 59) {
508:             return $err_status;
509:         }
510:         
511:         $time_part = $units[$pos_H] . ":" . $units[$pos_i];
512:         
513:         if (!($pos_s === false)) {
514:             if (!is_numeric($units[$pos_s]) || $units[$pos_s] < 0 || $units[$pos_s] > 59) {
515:                 return $err_status;
516:             }
517:             $time_part .= ":" . $units[$pos_s];
518:         }
519:     }
520:     
521:     return strtotime(trim($date_part . " " . $time_part));
522: } // timestamp
523: 
524: /**
525:  * Formats the number due to the specified settings.
526:  * It is a wrapper over the system function number_format.
527:  *
528:  * @param string $number
529:  * The number to be formatted.
530:  *
531:  * @param int $decimals
532:  * The number of digits after the dot.
533:  *
534:  * @param string $dec_point
535:  * The decimal separator.
536:  *
537:  * @param string $thousand_sep
538:  * The thousand separator.
539:  *
540:  * @return ?string
541:  * If the number is a vialid number, its formatted value is returned. Otherwise,
542:  * the empty value is returned. It is usefil if we need to distiguish the empty value and 0.
543:  *
544:  * @author Oleg Schildt
545:  */
546: function format_number(string $number, int $decimals = 0, string $dec_point = ".", string $thousand_sep = ","): ?string
547: {
548:     if ($number === null || $number === "") {
549:         return $number;
550:     }
551:     
552:     return number_format($number, $decimals, $dec_point, $thousand_sep);
553: } // format_number
554: 
555: /**
556:  * Converts the string to number due to the specified settings.
557:  *
558:  * @param string $str
559:  * The string to be converted.
560:  *
561:  * @param string $dec_point
562:  * The decimal separator.
563:  *
564:  * @param string $thousand_sep
565:  * The thousand separator.
566:  *
567:  * @throws \Exception
568:  * It might throw the exception if the string is not a number
569:  *
570:  * @return float|string|null
571:  * If the string is a vialid number, its numeric value is returned.
572:  *
573:  * @author Oleg Schildt
574:  */
575: function string_to_number(string $str, string $dec_point = ".", string $thousand_sep = ","): float|string|null
576: {
577:     if ($str === "" || $str === null) {
578:         return $str;
579:     }
580:     
581:     $str = str_replace($thousand_sep, "", $str);
582:     $str = str_replace($dec_point, ".", $str);
583:     $number = floatval($str);
584:     
585:     return $number;
586: }
587: 
588: /**
589:  * Encrypts the text with the AES 256 using a password key.
590:  *
591:  * @param string $data
592:  * The data to be encrypted.
593:  *
594:  * @param string $password_key
595:  * The password key used for the encryption.
596:  *
597:  * @return string
598:  * Returns the encrypted text.
599:  *
600:  * @see \SmartFactory\aes_256_decrypt()
601:  *
602:  * @author Oleg Schildt
603:  */
604: function aes_256_encrypt(string $data, string $password_key): string
605: {
606:     // Set a random salt
607:     $salt = openssl_random_pseudo_bytes(16);
608:     
609:     $salted = '';
610:     $dx = '';
611:     // Salt the key(32) and iv(16) = 48
612:     while (strlen($salted) < 48) {
613:         $dx = hash('sha256', $dx . $password_key . $salt, true);
614:         $salted .= $dx;
615:     }
616:     
617:     $key = substr($salted, 0, 32);
618:     $iv = substr($salted, 32, 16);
619:     
620:     $encrypted_data = openssl_encrypt($data, 'AES-256-CBC', $key, true, $iv);
621:     return base64_encode($salt . $encrypted_data);
622: } // aes_256_encrypt
623: 
624: /**
625:  * Decrypts the text previously encrypted with the AES 256 using a password key.
626:  *
627:  * @param string $edata
628:  * The data to be decrypted.
629:  *
630:  * @param string $password_key
631:  * The password key used for the encryption.
632:  *
633:  * @return string
634:  * Returns the decrypted text.
635:  *
636:  * @see \SmartFactory\aes_256_decrypt()
637:  *
638:  * @author Oleg Schildt
639:  */
640: function aes_256_decrypt(string $edata, string $password_key): string
641: {
642:     $data = base64_decode($edata);
643:     $salt = substr($data, 0, 16);
644:     $ct = substr($data, 16);
645:     
646:     $rounds = 3; // depends on key length
647:     $data00 = $password_key . $salt;
648:     $hash = array();
649:     $hash[0] = hash('sha256', $data00, true);
650:     $result = $hash[0];
651:     
652:     for ($i = 1; $i < $rounds; $i++) {
653:         $hash[$i] = hash('sha256', $hash[$i - 1] . $data00, true);
654:         $result .= $hash[$i];
655:     }
656:     
657:     $key = substr($result, 0, 32);
658:     $iv = substr($result, 32, 16);
659:     
660:     return openssl_decrypt($ct, 'AES-256-CBC', $key, true, $iv);
661: } // aes_256_decrypt