GetLogFromFileAction   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 120
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 17
eloc 79
c 1
b 0
f 0
dl 0
loc 120
rs 10

1 Method

Rating   Name   Duplication   Size   Complexity  
F main() 0 107 17
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright © 2017-2025 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
namespace MikoPBX\PBXCoreREST\Lib\SysLogs;
21
22
use MikoPBX\Core\System\Directories;
23
use MikoPBX\Core\System\Processes;
24
use MikoPBX\Core\System\Util;
25
use MikoPBX\PBXCoreREST\Lib\PBXApiResult;
26
use Phalcon\Di\Injectable;
27
28
/**
29
 * Gets partially filtered log file strings.
30
 *
31
 * @package MikoPBX\PBXCoreREST\Lib\SysLogs
32
 */
33
class GetLogFromFileAction extends Injectable
34
{
35
    /**
36
     * Gets partially filtered log file strings.
37
     *
38
     * @param array $data An array containing the following parameters:
39
     *                    - filename (string): The name of the log file.
40
     *                    - filter (string): The filter string.
41
     *                    - lines (int): The number of lines to return.
42
     *                    - offset (int): The number of lines to skip.
43
     *
44
     * @return PBXApiResult An object containing the result of the API call.
45
     */
46
    public static function main(array $data): PBXApiResult
47
    {
48
        $filename = (string)($data['filename'] ?? '');
49
        $filter = (string)($data['filter'] ?? '');
50
        $lines = (int)($data['lines'] ?? '');
51
        $offset = (int)($data['offset'] ?? '');
52
53
        $res = new PBXApiResult();
54
        $res->processor = __METHOD__;
55
        $filename = Directories::getDir(Directories::CORE_LOGS_DIR) . '/' . $filename;
56
        if (!file_exists($filename)) {
57
            $res->success = false;
58
            $res->messages[] = 'No access to the file ' . $filename;
59
        } else {
60
            $res->success = true;
61
            $head = Util::which('head');
62
            $grep = '/bin/grep'; //can work with -text option
63
            if (!is_executable($grep)) {
64
                $grep = Util::which('grep');
65
            }
66
            $tail = Util::which('tail');
67
            $filter = escapeshellarg($filter);
68
            $linesPlusOffset = $lines + $offset;
69
70
            $cacheDir = Directories::getDir(Directories::WWW_DOWNLOAD_CACHE_DIR);
71
            if (!file_exists($cacheDir)) {
72
                Util::mwMkdir($cacheDir, true);
73
            }
74
            $filenameTmp = $cacheDir . '/' . __FUNCTION__ . '_' . time() . '.log';
75
            
76
            // Check if the file is an archive
77
            $isArchive = false;
78
            $fileExtension = pathinfo($filename, PATHINFO_EXTENSION);
79
            if (in_array($fileExtension, ['gz', 'zip', 'bz2', 'xz'])) {
80
                $isArchive = true;
81
                $decompressedFile = $cacheDir . '/' . basename($filename, '.' . $fileExtension);
0 ignored issues
show
Bug introduced by
Are you sure $fileExtension of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

81
                $decompressedFile = $cacheDir . '/' . basename($filename, '.' . /** @scrutinizer ignore-type */ $fileExtension);
Loading history...
82
                
83
                // Decompress based on file extension
84
                switch ($fileExtension) {
85
                    case 'gz':
86
                        $cmd = Util::which('gunzip') . ' -c ' . $filename . ' > ' . $decompressedFile;
87
                        Processes::mwExec($cmd);
88
                        break;
89
                    case 'bz2':
90
                        $cmd = Util::which('bunzip2') . ' -c ' . $filename . ' > ' . $decompressedFile;
91
                        Processes::mwExec($cmd);
92
                        break;
93
                    case 'xz':
94
                        $cmd = Util::which('unxz') . ' -c ' . $filename . ' > ' . $decompressedFile;
95
                        Processes::mwExec($cmd);
96
                        break;
97
                    case 'zip':
98
                        // Use ZipArchive for ZIP files
99
                        $zip = new \ZipArchive();
100
                        if ($zip->open($filename) === true) {
101
                            // Assuming there's only one file in the archive or we want the first one
102
                            if ($zip->numFiles > 0) {
103
                                $zipEntryContent = $zip->getFromIndex(0);
104
                                file_put_contents($decompressedFile, $zipEntryContent);
105
                            }
106
                            $zip->close();
107
                        } else {
108
                            $isArchive = false;
109
                        }
110
                        break;
111
                    default:
112
                        $isArchive = false;
113
                        break;
114
                }
115
                
116
                if ($isArchive && file_exists($decompressedFile)) {
117
                    // Use decompressed file for further operations
118
                    $fileToProcess = $decompressedFile;
119
                    $res->data['decompressed'] = true;
120
                } else {
121
                    $fileToProcess = $filename;
122
                }
123
            } else {
124
                $fileToProcess = $filename;
125
            }
126
            
127
            if (empty($filter)) {
128
                $cmd = "$tail -n $linesPlusOffset $fileToProcess";
129
            } else {
130
                $cmd = "$grep --text -h -e " . str_replace('&', "' -e '", $filter) . " -F $fileToProcess | $tail -n $linesPlusOffset";
131
            }
132
            if ($offset > 0) {
133
                $cmd .= " | $head -n $lines";
134
            }
135
136
            $sed = Util::which('sed');
137
            $cmd .= ' | ' . $sed . ' -E \'s/\\\\([tnrfvb]|040)/ /g\'';
138
            $cmd .= " > $filenameTmp";
139
140
            Processes::mwExec($cmd);
141
            $res->data['cmd'] = $cmd;
142
            $res->data['filename'] = $filenameTmp;
143
            $res->data['content'] = mb_convert_encoding('' . file_get_contents($filenameTmp), 'UTF-8', 'UTF-8');
144
            unlink($filenameTmp);
145
            
146
            // Clean up decompressed file if it was created
147
            if ($isArchive && file_exists($decompressedFile)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $decompressedFile does not seem to be defined for all execution paths leading up to this point.
Loading history...
148
                unlink($decompressedFile);
149
            }
150
        }
151
152
        return $res;
153
    }
154
}