Util::parseIniSettings()   D
last analyzed

Complexity

Conditions 18
Paths 102

Size

Total Lines 65
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 41
c 3
b 0
f 0
dl 0
loc 65
rs 4.85
cc 18
nc 102
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * MikoPBX - free phone system for small business
5
 * Copyright © 2017-2023 Alexey Portnov and Nikolay Beketov
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License along with this program.
18
 * If not, see <https://www.gnu.org/licenses/>.
19
 */
20
21
namespace MikoPBX\Core\System;
22
23
use DateTime;
24
use Exception;
25
use MikoPBX\Common\Handlers\CriticalErrorsHandler;
26
use MikoPBX\Common\Models\{CustomFiles};
27
use MikoPBX\Common\Providers\AmiConnectionCommand;
28
use MikoPBX\Common\Providers\AmiConnectionListener;
29
use MikoPBX\Common\Providers\LanguageProvider;
30
use MikoPBX\Common\Providers\TranslationProvider;
31
use MikoPBX\Core\Asterisk\AsteriskManager;
32
use Phalcon\Di\Di;
33
use ReflectionClass;
34
use ReflectionException;
35
use Throwable;
36
37
/**
38
 * Class Util
39
 *
40
 * Universal commands and procedures
41
 *
42
 * @package MikoPBX\Core\System
43
 */
44
class Util
45
{
46
    /**
47
     * Overrides configuration array with manual attributes for a specific section.
48
     *
49
     * @param array $options The original configuration options.
50
     * @param array|null $manual_attributes The manual attributes to override the options.
51
     * @param string $section The section to override.
52
     *
53
     * @return string The resulting configuration string.
54
     */
55
    public static function overrideConfigurationArray(array &$options, ?array $manual_attributes, string $section): string
56
    {
57
        $result_config = '';
58
        if ($manual_attributes !== null && isset($manual_attributes[$section])) {
59
            foreach ($manual_attributes[$section] as $key => $value) {
60
                if ($key === 'type') {
61
                    continue;
62
                }
63
                $options[$key] = $value;
64
            }
65
        }
66
        foreach ($options as $key => $value) {
67
            if (empty($value) || empty($key)) {
68
                continue;
69
            }
70
            if (is_array($value)) {
71
                array_unshift($value, ' ');
72
                $result_config .= trim(implode("\n$key = ", $value)) . "\n";
73
            } else {
74
                $result_config .= "$key = $value\n";
75
            }
76
        }
77
78
        return "$result_config\n";
79
    }
80
81
    /**
82
     * Initiates a call using the Asterisk Manager Interface (AMI).
83
     *
84
     * @param string $peer_number The peer number.
85
     * @param string $peer_mobile The peer mobile number.
86
     * @param string $dest_number The destination number.
87
     *
88
     * @return array The result of the Originate command.
89
     * @throws Exception
90
     */
91
    public static function amiOriginate(string $peer_number, string $peer_mobile, string $dest_number): array
92
    {
93
        $am = self::getAstManager('off');
94
        $channel = 'Local/' . $peer_number . '@internal-originate';
95
        $context = 'all_peers';
96
        $variable = "pt1c_cid=$dest_number,__peer_mobile=$peer_mobile";
97
98
        return $am->Originate(
99
            $channel,
100
            $dest_number,
101
            $context,
102
            '1',
103
            null,
104
            null,
105
            null,
106
            null,
107
            $variable,
108
            null,
109
            true
110
        );
111
    }
112
113
    /**
114
     * Retrieves the Asterisk Manager object.
115
     *
116
     * @param string $events Whether to enable events or commands.
117
     *
118
     * @return AsteriskManager The Asterisk Manager object.
119
     *
120
     * @throws Exception
121
     */
122
    public static function getAstManager(string $events = 'on'): AsteriskManager
123
    {
124
        if ($events === 'on') {
125
            $nameService = AmiConnectionListener::SERVICE_NAME;
126
        } else {
127
            $nameService = AmiConnectionCommand::SERVICE_NAME;
128
        }
129
        $di = Di::getDefault();
130
        if ($di === null) {
131
            throw new \Exception("di not found");
132
        }
133
134
        // Try to connect to Asterisk Manager
135
        $am = $di->getShared($nameService);
136
        if (is_resource($am->socket)) {
137
            return $am;
138
        }
139
140
        return $di->get($nameService);
141
    }
142
143
    /**
144
     * Generates a random string of a given length.
145
     *
146
     * @param int $length The length of the random string (default: 10).
147
     *
148
     * @return string The generated random string.
149
     */
150
    public static function generateRandomString(int $length = 10): string
151
    {
152
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
153
        $charactersLength = strlen($characters);
154
        $randomString = '';
155
        for ($i = 0; $i < $length; $i++) {
156
            try {
157
                $randomString .= $characters[random_int(0, $charactersLength - 1)];
158
            } catch (Throwable $e) {
159
                CriticalErrorsHandler::handleExceptionWithSyslog($e);
160
                $randomString = '';
161
            }
162
        }
163
164
        return $randomString;
165
    }
166
167
    /**
168
     * Validates if a string is a valid JSON.
169
     *
170
     * @param mixed $jsonString The string to validate.
171
     *
172
     * @return bool True if the string is a valid JSON, false otherwise.
173
     */
174
    public static function isJson(mixed $jsonString): bool
175
    {
176
        return json_validate($jsonString);
0 ignored issues
show
Bug introduced by
The function json_validate was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

176
        return /** @scrutinizer ignore-call */ json_validate($jsonString);
Loading history...
177
    }
178
179
    /**
180
     * Returns the size of a file in megabytes.
181
     *
182
     * @param string $filename The filename.
183
     *
184
     * @return float|int The size of the file in megabytes.
185
     */
186
    public static function mFileSize(string $filename): float|int
187
    {
188
        $size = 0;
189
        if (file_exists($filename)) {
190
            $tmp_size = filesize($filename);
191
            if ($tmp_size !== false) {
192
                // Convert size to megabytes
193
                $size = $tmp_size;
194
            }
195
        }
196
197
        return $size;
198
    }
199
200
    /**
201
     * Returns a string with the specified number of 'X' characters.
202
     *
203
     * @param int $length The length of the string.
204
     *
205
     * @return string The string with 'X' characters.
206
     */
207
    public static function getExtensionX(int $length): string
208
    {
209
        $extension = '';
210
        for ($i = 0; $i < $length; $i++) {
211
            $extension .= 'X';
212
        }
213
214
        return $extension;
215
    }
216
217
    /**
218
     * Checks if a file exists and has a non-zero size.
219
     *
220
     * @param string $filename The filename.
221
     *
222
     * @return ?bool True if the file exists and has a non-zero size, false otherwise.
223
     */
224
    public static function recFileExists(string $filename): ?bool
225
    {
226
        return (file_exists($filename) && filesize($filename) > 0);
227
    }
228
229
    /**
230
     * Converts a number to a date if the input is numeric.
231
     *
232
     * @param mixed $data The input data.
233
     *
234
     * @return string The converted date string or the original input data.
235
     */
236
    public static function numberToDate(mixed $data): string
237
    {
238
        $re_number = '/^\d+.\d+$/';
239
        preg_match_all($re_number, $data, $matches, PREG_SET_ORDER, 0);
240
        if (count($matches) > 0) {
241
            $data = date('Y.m.d-H:i:s', $data);
242
        }
243
244
        return $data;
245
    }
246
247
    /**
248
     * Writes content to a file.
249
     *
250
     * @param string $filename The path of the file.
251
     * @param string $data The data to write to the file.
252
     *
253
     * @return void
254
     */
255
    public static function fileWriteContent(string $filename, string $data): void
256
    {
257
        /** @var CustomFiles $res */
258
        $res = CustomFiles::findFirst("filepath = '$filename'");
259
        if ($res === null) {
260
            // File is not yet registered in the database, create a new CustomFiles entry
261
            $res = new CustomFiles();
262
            $res->writeAttribute('filepath', $filename);
263
            $res->writeAttribute('mode', CustomFiles::MODE_NONE);
264
            $res->save();
265
        }
266
267
        $filename_orgn = "$filename.orgn";
268
269
        switch ($res->mode) {
270
            case CustomFiles::MODE_NONE:
271
                if (file_exists($filename_orgn)) {
272
                    unlink($filename_orgn);
273
                }
274
                file_put_contents($filename, $data);
275
                break;
276
            case CustomFiles::MODE_APPEND:
277
                file_put_contents($filename_orgn, $data);
278
                // Append to the file
279
                $data .= "\n\n";
280
                $data .= base64_decode((string)$res->content);
281
                file_put_contents($filename, $data);
282
                break;
283
            case CustomFiles::MODE_OVERRIDE:
284
                file_put_contents($filename_orgn, $data);
285
                // Override the file
286
                $data = base64_decode((string)$res->content);
287
                file_put_contents($filename, $data);
288
                break;
289
            case CustomFiles::MODE_SCRIPT:
290
                // Save the original copy.
291
                file_put_contents($filename_orgn, $data);
292
293
                // Save the config file.
294
                file_put_contents($filename, $data);
295
296
                // Apply custom script to the file
297
                $scriptText = base64_decode((string)$res->content);
298
                $tempScriptFile = tempnam(sys_get_temp_dir(), 'temp_script.sh');
299
                file_put_contents($tempScriptFile, $scriptText);
300
                $command = "/bin/sh $tempScriptFile $filename";
301
                Processes::mwExec($command);
302
                unlink($tempScriptFile);
303
304
                break;
305
            default:
306
        }
307
    }
308
309
    /**
310
     * Returns the current date as a string with millisecond precision.
311
     *
312
     * @return string|null The current date string, or null on error.
313
     */
314
    public static function getNowDate(): ?string
315
    {
316
        $result = null;
317
        try {
318
            $d = new DateTime();
319
            $result = $d->format("Y-m-d H:i:s.v");
320
        } catch (Exception $e) {
321
            unset($e);
322
        }
323
324
        return $result;
325
    }
326
327
    /**
328
     * Retrieves the extension of a file.
329
     *
330
     * @param string $filename The filename.
331
     *
332
     * @return string The extension of the file.
333
     */
334
    public static function getExtensionOfFile(string $filename): string
335
    {
336
        $path_parts = pathinfo($filename);
337
338
        return $path_parts['extension'] ?? '';
339
    }
340
341
    /**
342
     * Removes the extension from a filename.
343
     *
344
     * @param string $filename The filename.
345
     * @param string $delimiter The delimiter character (default: '.').
346
     *
347
     * @return string The filename without the extension.
348
     */
349
    public static function trimExtensionForFile(string $filename, string $delimiter = '.'): string
350
    {
351
        $tmp_arr = explode($delimiter, $filename);
352
        if (count($tmp_arr) > 1) {
353
            unset($tmp_arr[count($tmp_arr) - 1]);
354
            $filename = implode($delimiter, $tmp_arr);
355
        }
356
357
        return $filename;
358
    }
359
360
    /**
361
     * Get the size of a file in kilobytes.
362
     *
363
     * @param string $filename The path to the file.
364
     * @return float The size of the file in kilobytes.
365
     */
366
    public static function getSizeOfFile(string $filename): float
367
    {
368
        $result = 0;
369
        if (file_exists($filename)) {
370
            $du = self::which('du');
371
            $awk = self::which('awk');
372
            Processes::mwExec("$du -d 0 -k '$filename' | $awk  '{ print $1}'", $out);
373
            $time_str = implode($out);
0 ignored issues
show
Bug introduced by
It seems like $out can also be of type null; however, parameter $pieces of implode() does only seem to accept array, 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

373
            $time_str = implode(/** @scrutinizer ignore-type */ $out);
Loading history...
374
            preg_match_all('/^\d+$/', $time_str, $matches, PREG_SET_ORDER, 0);
375
            if (count($matches) > 0) {
376
                $result = round(1 * $time_str / 1024, 2);
377
            }
378
        }
379
380
        return $result;
381
    }
382
383
    /**
384
     * Searches for the executable path of a command.
385
     *
386
     * @param string $cmd The command to search for.
387
     *
388
     * @return string The path of the executable command, or the command itself if not found.
389
     */
390
    public static function which(string $cmd): string
391
    {
392
        global $_ENV;
393
394
        // Default binary folders to search if PATH is not set or command is not found
395
        $binaryFolders = $_ENV['PATH'] ?? '/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:/usr/local/sbin';
396
397
        // Search for the command in each binary folder
398
        foreach (explode(':', $binaryFolders) as $path) {
399
            if (is_executable("$path/$cmd")) {
400
                return "$path/$cmd";
401
            }
402
        }
403
404
        // Get BusyBox applets list from cache or generate it
405
        $busyBoxApplets = self::getBusyBoxCommands();
406
407
        // Check if the command is a BusyBox applet
408
        if (in_array($cmd, $busyBoxApplets)) {
409
            return "/bin/busybox $cmd"; // Prefix with 'busybox' if it is a BusyBox command
410
        }
411
412
        // Return the command as it is if not found and not a BusyBox applet
413
        return $cmd;
414
    }
415
416
    /**
417
     * Fetches or generates the list of BusyBox commands.
418
     *
419
     * @return array List of BusyBox commands.
420
     */
421
    public static function getBusyBoxCommands(): array
422
    {
423
        $filename = '/etc/busybox-commands';
424
        if (!file_exists($filename)) {
425
            // Get the list of BusyBox commands by executing busybox --list
426
            Processes::mwExec('busybox --list', $output);
427
            // Save the output to a file
428
            file_put_contents($filename, implode("\n", $output));
0 ignored issues
show
Bug introduced by
It seems like $output can also be of type null; however, parameter $pieces of implode() does only seem to accept array, 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

428
            file_put_contents($filename, implode("\n", /** @scrutinizer ignore-type */ $output));
Loading history...
429
            return $output;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $output could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
430
        } else {
431
            // Read the list from the file
432
            $commands = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
433
            return $commands;
434
        }
435
    }
436
437
    /**
438
     * Checks if a password is simple based on a dictionary.
439
     *
440
     * @param string $value The password to check.
441
     *
442
     * @return bool True if the password is found in the dictionary, false otherwise.
443
     */
444
    public static function isSimplePassword(string $value): bool
445
    {
446
        $passwords = [];
447
        Processes::mwExec('/bin/zcat /usr/share/wordlists/rockyou.txt.gz', $passwords);
448
        return in_array($value, $passwords, true);
0 ignored issues
show
Bug introduced by
It seems like $passwords can also be of type null; however, parameter $haystack of in_array() does only seem to accept array, 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

448
        return in_array($value, /** @scrutinizer ignore-type */ $passwords, true);
Loading history...
449
    }
450
451
    /**
452
     * Sets the Cyrillic font for the console.
453
     *
454
     * @return void
455
     */
456
    public static function setCyrillicFont(): void
457
    {
458
        $setfont = self::which('setfont');
459
        Processes::mwExec("$setfont /usr/share/consolefonts/Cyr_a8x16.psfu.gz 2>/dev/null");
460
    }
461
462
    /**
463
     * Translates a text string.
464
     *
465
     * @param string $text The text to translate.
466
     * @param bool $cliLang Whether to use CLI language or web language (default: true).
467
     * @param array $parameters The parameters to replace in the text.
468
     *
469
     * @return string The translated text.
470
     */
471
    public static function translate(string $text, bool $cliLang = true, array $parameters=[]): string
472
    {
473
        $di = Di::getDefault();
474
        if ($di !== null) {
475
            if (!$cliLang) {
476
                $di->setShared(LanguageProvider::PREFERRED_LANG_WEB, true);
477
            }
478
            $text = $di->getShared(TranslationProvider::SERVICE_NAME)->_($text, $parameters);
479
            if (!$cliLang) {
480
                $di->remove(LanguageProvider::PREFERRED_LANG_WEB);
481
            }
482
        }
483
        return $text;
484
    }
485
486
    /**
487
     * Recursively deletes a directory.
488
     *
489
     * @param string $dir The directory path to delete.
490
     *
491
     * @return void
492
     *
493
     * @link http://php.net/manual/en/function.rmdir.php
494
     */
495
    public static function rRmDir(string $dir): void
496
    {
497
        if (is_dir($dir)) {
498
            $objects = scandir($dir);
499
500
            // Recursively delete files and subdirectories
501
            foreach ($objects as $object) {
502
                if ($object != "." && $object != "..") {
503
                    if (filetype($dir . "/" . $object) == "dir") {
504
                        self::rRmDir($dir . "/" . $object);
505
                    } else {
506
                        unlink($dir . "/" . $object);
507
                    }
508
                }
509
            }
510
511
            // Reset the array pointer and remove the directory
512
            if ($objects !== false) {
513
                reset($objects);
514
            }
515
            rmdir($dir);
516
        }
517
    }
518
519
    /**
520
     * Generates an SSL certificate.
521
     *
522
     * @param array|null $options The options for the certificate (default: null).
523
     * @param array|null $config_args_pkey The configuration arguments for the private key (default: null).
524
     * @param array|null $config_args_csr The configuration arguments for the CSR (default: null).
525
     *
526
     * @return array The generated SSL certificate (public and private keys).
527
     */
528
    public static function generateSslCert(?array $options = null, ?array $config_args_pkey = null, ?array $config_args_csr = null): array
529
    {
530
        // Initialize options if not provided
531
        if (!$options) {
532
            $options = [
533
                "countryName" => 'RU',
534
                "stateOrProvinceName" => 'Moscow',
535
                "localityName" => 'Zelenograd',
536
                "organizationName" => 'MIKO LLC',
537
                "organizationalUnitName" => 'Software development',
538
                "commonName" => 'MIKO PBX',
539
                "emailAddress" => '[email protected]',
540
            ];
541
        }
542
543
        // Initialize CSR configuration arguments if not provided
544
        if (!$config_args_csr) {
545
            $config_args_csr = ['digest_alg' => 'sha256'];
546
        }
547
548
        // Initialize private key configuration arguments if not provided
549
        if (!$config_args_pkey) {
550
            $config_args_pkey = [
551
                "private_key_bits" => 2048,
552
                "private_key_type" => OPENSSL_KEYTYPE_RSA,
553
            ];
554
        }
555
556
        // Generate keys
557
        $private_key = openssl_pkey_new($config_args_pkey);
558
        $csr = openssl_csr_new($options, /** @scrutinizer ignore-type */$private_key, $config_args_csr);
559
        $x509 = openssl_csr_sign($csr, null, $private_key, $days = 3650, $config_args_csr);
560
561
        // Export keys
562
        openssl_x509_export($x509, $certout);
563
        openssl_pkey_export($private_key, $pkeyout);
564
        // echo $pkeyout; // -> WEBHTTPSPrivateKey
565
        // echo $certout; // -> WEBHTTPSPublicKey
566
        return ['PublicKey' => $certout, 'PrivateKey' => $pkeyout];
567
    }
568
569
    /**
570
     * Checks whether the current system is t2 SDE build.
571
     *
572
     * @return bool True if the system is t2 SDE build, false otherwise.
573
     */
574
    public static function isT2SdeLinux(): bool
575
    {
576
        return file_exists('/etc/t2-sde-build');
577
    }
578
579
    /**
580
     * Checks whether the current system has systemctl installed and executable.
581
     *
582
     * @return bool True if systemctl is available, false otherwise.
583
     */
584
    public static function isSystemctl(): bool
585
    {
586
        $pathSystemCtl = self::which('systemctl');
587
        return !empty($pathSystemCtl) && is_executable($pathSystemCtl);
588
    }
589
590
    /**
591
     * Checks whether the current process is running inside a Docker container.
592
     *
593
     * @return bool True if the process is inside a container, false otherwise.
594
     */
595
    public static function isDocker(): bool
596
    {
597
        return file_exists('/.dockerenv');
598
    }
599
600
    /**
601
     * Creates or updates a symlink to a target path.
602
     *
603
     * @param string $target The target path.
604
     * @param string $link The symlink path.
605
     * @param bool $isFile Whether the symlink should point to a file (false by default).
606
     *
607
     * @return bool True if the symlink was created or updated, false otherwise.
608
     */
609
    public static function createUpdateSymlink(string $target, string $link, bool $isFile = false): bool
610
    {
611
        $need_create_link = true;
612
        if (is_link($link)) {
613
            $old_target = readlink($link);
614
            $need_create_link = ($old_target !== $target);
615
616
            // If needed, remove the old symlink.
617
            if ($need_create_link) {
618
                $cpPath = self::which('cp');
619
                Processes::mwExec("$cpPath $old_target/* $target");
620
                unlink($link);
621
            }
622
        } elseif (is_dir($link)) {
623
            // It should be a symlink. Remove the directory.
624
            rmdir($link);
625
        } elseif (file_exists($link)) {
626
            // It should be a symlink. Remove the file.
627
            unlink($link);
628
        }
629
630
        // Create the target directory if $isFile is false
631
        if ($isFile === false) {
632
            self::mwMkdir($target);
633
        }
634
635
        if ($need_create_link) {
636
            $lnPath = self::which('ln');
637
            Processes::mwExec("$lnPath -s $target $link");
638
        }
639
640
        return $need_create_link;
641
    }
642
643
    /**
644
     * Creates directories with optional WWW rights.
645
     *
646
     * @param string $parameters The space-separated list of directory paths.
647
     * @param bool $addWWWRights Whether to add WWW rights to the directories.
648
     *
649
     * @return bool True if the directories were created successfully, false otherwise.
650
     */
651
    public static function mwMkdir(string $parameters, bool $addWWWRights = false): bool
652
    {
653
        $result = true;
654
655
        // Check if the script is running with root privileges
656
        if (posix_getuid() === 0) {
657
            $arrPaths = explode(' ', $parameters);
658
            if (count($arrPaths) > 0) {
659
                foreach ($arrPaths as $path) {
660
                    if (
661
                        !empty($path)
662
                        && !file_exists($path)
663
                        && !mkdir($path, 0755, true)
664
                        && !is_dir($path)
665
                    ) {
666
                        $result = false;
667
                        SystemMessages::sysLogMsg(__METHOD__, 'Error on create folder ' . $path, LOG_ERR);
668
                    }
669
                    if ($addWWWRights) {
670
                        self::addRegularWWWRights($path);
671
                    }
672
                }
673
            }
674
        }
675
676
        return $result;
677
    }
678
679
    /**
680
     * Apply regular rights for folders and files
681
     *
682
     * @param $folder
683
     */
684
    public static function addRegularWWWRights($folder): void
685
    {
686
        if (posix_getuid() === 0) {
687
            $find = self::which('find');
688
            $chown = self::which('chown');
689
            $chmod = self::which('chmod');
690
            Processes::mwExec("$find $folder -type d -exec $chmod 755 {} \;");
691
            Processes::mwExec("$find $folder -type f -exec $chmod 644 {} \;");
692
            Processes::mwExec("$chown -R www:www $folder");
693
        }
694
    }
695
696
    /**
697
     * Adds executable rights to files in a folder.
698
     *
699
     * @param string $folder The folder path.
700
     *
701
     * @return void
702
     */
703
    public static function addExecutableRights(string $folder): void
704
    {
705
        // Check if the script is running with root privileges
706
        if (posix_getuid() === 0) {
707
            $find = self::which('find');
708
            $chmod = self::which('chmod');
709
710
            // Execute find command to locate files and modify their permissions
711
            Processes::mwExec("$find $folder -type f -exec $chmod 755 {} \;");
712
        }
713
    }
714
715
    /**
716
     * Parses ini settings from a string.
717
     *
718
     * @param string $manual_attributes The ini settings string.
719
     *
720
     * @return array An array representing the parsed ini settings.
721
     */
722
    public static function parseIniSettings(string $manual_attributes): array
723
    {
724
        // Decode the base64-encoded string if it is valid
725
        $tmp_data = base64_decode($manual_attributes);
726
        if (base64_encode($tmp_data) === $manual_attributes) {
727
            $manual_attributes = $tmp_data;
728
        }
729
        unset($tmp_data);
730
731
        // TRIMMING: Remove leading/trailing spaces and section markers
732
        $tmp_arr = explode("\n", $manual_attributes);
733
        foreach ($tmp_arr as &$row) {
734
            $row = trim($row);
735
            $pos = strpos($row, ']');
736
            if ($pos !== false && str_starts_with($row, '[')) {
737
                $row = "\n" . substr($row, 0, $pos + 1);
738
            }
739
        }
740
        unset($row);
741
        $manual_attributes = implode("\n", $tmp_arr);
742
        // TRIMMING END
743
744
        $haveSection = strpos($manual_attributes, '[') !== false;
745
746
        $manual_data = [];
747
        $sections = explode("\n[", str_replace(']', '', $manual_attributes));
748
        foreach ($sections as $section) {
749
            $data_rows = explode("\n", trim($section));
750
            $section_name = trim($data_rows[0] ?? '');
751
            if (!$haveSection && str_contains($section_name, '=')) {
752
                // Noname section
753
                $section_name = ' ';
754
            } else {
755
                unset($data_rows[0]);
756
            }
757
            $manual_data[$section_name] = [];
758
            foreach ($data_rows as $row) {
759
                $value = '';
760
761
                // Skip rows without an equal sign
762
                if (!str_contains($row, '=')) {
763
                    continue;
764
                }
765
                $key = '';
766
                $arr_value = explode('=', $row);
767
                if (count($arr_value) > 1) {
768
                    $key = trim($arr_value[0]);
769
                    unset($arr_value[0]);
770
                    $value = trim(implode('=', $arr_value));
771
                }
772
773
                // Skip rows with empty key or value not equal to '0'
774
                if (($value !== '0' && empty($value)) || empty($key)) {
775
                    continue;
776
                }
777
                if( ('set_var' === $key && $section_name === 'endpoint') ||
778
                    ('setvar'  === $key && $section_name === ' ')) {
779
                    $manual_data[$section_name][$key][] = $value;
780
                }else{
781
                    $manual_data[$section_name][$key] = $value;
782
                }
783
            }
784
        }
785
786
        return $manual_data;
787
    }
788
789
    /**
790
     * Converts multidimensional array into single array
791
     *
792
     * @param array $array
793
     *
794
     * @return array
795
     */
796
    public static function flattenArray(array $array): array
797
    {
798
        $result = [];
799
        foreach ($array as $value) {
800
            if (is_array($value)) {
801
                $result = array_merge($result, self::flattenArray($value));
802
            } else {
803
                $result[] = $value;
804
            }
805
        }
806
807
        return $result;
808
    }
809
810
    /**
811
     * Try to find full path to php file by class name
812
     *
813
     * @param $className
814
     *
815
     * @return string|null
816
     */
817
    public static function getFilePathByClassName($className): ?string
818
    {
819
        $filename = null;
820
        try {
821
            $reflection = new ReflectionClass($className);
822
            $filename = $reflection->getFileName();
823
        } catch (ReflectionException $exception) {
824
            SystemMessages::sysLogMsg(__METHOD__, 'ReflectionException ' . $exception->getMessage(), LOG_ERR);
825
        }
826
827
        return $filename;
828
    }
829
830
    /**
831
     * Adds messages to Syslog.
832
     * @deprecated Use SystemMessages::sysLogMsg instead
833
     *
834
     * @param string $ident The category, class, or method identification.
835
     * @param string $message The log message.
836
     * @param int $level The log level (default: LOG_WARNING).
837
     *
838
     * @return void
839
     */
840
    public static function sysLogMsg(string $ident, string $message, int $level = LOG_WARNING): void
841
    {
842
        SystemMessages::sysLogMsg($ident, $message, $level);
843
    }
844
845
846
    /**
847
     * Echoes a message and logs it to the system log.
848
     * @deprecated Use SystemMessages::echoWithSyslog instead
849
     *
850
     * @param string $message The message to echo and log.
851
     *
852
     * @return void
853
     */
854
    public static function echoWithSyslog(string $message): void
855
    {
856
        SystemMessages::echoWithSyslog($message);
857
    }
858
859
    /**
860
     * Is recovery mode
861
     *
862
     * @return bool
863
     */
864
    public static function isRecoveryMode(): bool
865
    {
866
        return file_exists('/offload/livecd');
867
    }
868
869
}
870