]>
Commit | Line | Data |
---|---|---|
1 | <?php | |
2 | ||
3 | class Upgrade | |
4 | { | |
5 | public $web_dir; | |
6 | private $temp_dir; | |
7 | private static $temp_extracted_dir; | |
8 | public static $upgrade_available; | |
9 | public static $last_check; | |
10 | public $error; | |
11 | public static $latest_version; | |
12 | function __construct() | |
13 | { | |
14 | global $config; | |
15 | read_config_db(); | |
16 | if (!get_config('upgrade')) | |
17 | { | |
18 | $config['upgrade'] =[]; | |
19 | write_config('upgrade'); | |
20 | } | |
21 | ||
22 | $tok = split(__DIR__, '/'); | |
23 | unset($tok[count($tok) - 1]); | |
24 | $this->web_dir = implode('/',$tok).'/'; | |
25 | ||
26 | /** prepare the temp directory */ | |
27 | $temp_dir = $this->web_dir."panel_upgrade"; | |
28 | $temp_dir .= ($temp_dir[strlen($temp_dir) - 1] != '/') ? "/" : ""; | |
29 | if (file_exists($temp_dir)) { | |
30 | deleteDirectoryContents($temp_dir); | |
31 | rmdir($temp_dir); | |
32 | } | |
33 | $mkdir = mkdir($temp_dir, 0755, true); | |
34 | ||
35 | $this->temp_dir = $mkdir ? $temp_dir : NULL; | |
36 | $this->error = $mkdir ? NULL : "Could not create directory: $temp_dir"; | |
37 | Upgrade::$upgrade_available = false; | |
38 | if ($this->error) | |
39 | error_log($this->error); | |
40 | } | |
41 | ||
42 | /** Checks for a new upgrade */ | |
43 | function checkForNew() | |
44 | { | |
45 | global $config; | |
46 | read_config_db(); | |
47 | $last_check = &$config['upgrade']['last_check']; | |
48 | if (isset($last_check) && time() - $last_check < 300) // only check every 15 mins | |
49 | { | |
50 | error_log("Skipping upgrade check, checked ".time() - $last_check." seconds ago"); | |
51 | return false; | |
52 | } | |
53 | error_log(time()." - ".$last_check." = ".time()-$last_check); | |
54 | $apiUrl = "https://api.github.com/repos/unrealircd/unrealircd-webpanel/releases"; | |
55 | $response = file_get_contents($apiUrl, false, stream_context_create( | |
56 | ["http" => ["method" => "GET", "header" => "User-agent: UnrealIRCd Webpanel"]] | |
57 | )); | |
58 | ||
59 | if ($response === false) | |
60 | { | |
61 | $this->error = "Couldn't check github."; | |
62 | return false; | |
63 | } | |
64 | $data = json_decode($response, true); | |
65 | $latest = $data[count($data) - 1]; | |
66 | $config['upgrade']['latest_version'] = $latest['tag_name']; | |
67 | $last_check = time(); | |
68 | $config['upgrade']['download_link'] = $latest['zipball_url']; | |
69 | write_config('upgrade'); | |
70 | Upgrade::$upgrade_available = (float)$latest['tag_name'] > WEBPANEL_VERSION ? true : false; | |
71 | } | |
72 | ||
73 | function downloadUpgradeZip() | |
74 | { | |
75 | $ch = curl_init(get_config('upgrade::download_link')); | |
76 | $fp = fopen($this->temp_dir."unrealircd-webpanel-upgrade.zip", 'w+'); | |
77 | ||
78 | curl_setopt($ch, CURLOPT_FILE, $fp); | |
79 | curl_setopt($ch, CURLOPT_TIMEOUT, 60); | |
80 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); | |
81 | curl_setopt($ch, CURLOPT_HTTPHEADER, [ | |
82 | 'User-Agent: UnrealIRCd Webpanel', | |
83 | ]); | |
84 | $success = curl_exec($ch); | |
85 | $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
86 | ||
87 | if ($code == "403" || $code == "404") | |
88 | { | |
89 | $this->error ="Unable to download"; | |
90 | } | |
91 | curl_close($ch); | |
92 | fclose($fp); | |
93 | ||
94 | return $success; | |
95 | } | |
96 | function extractZip() { | |
97 | $zip = new ZipArchive; | |
98 | if ($zip->open($this->temp_dir."unrealircd-webpanel-upgrade.zip") === true) | |
99 | { | |
100 | $zip->extractTo("$this->temp_dir"); | |
101 | $zip->close(); | |
102 | self::$temp_extracted_dir = findOnlyDirectory($this->temp_dir); | |
103 | error_log(self::$temp_extracted_dir); | |
104 | return true; | |
105 | } else { | |
106 | return false; | |
107 | } | |
108 | } | |
109 | function cleanupOldFiles() | |
110 | { | |
111 | foreach ($this->compareAndGetFilesToDelete() as $file) | |
112 | { | |
113 | unlink("$this->web_dir$file"); | |
114 | error_log("Deleting: $file"); | |
115 | } | |
116 | } | |
117 | function compareAndGetFilesToDelete() : array | |
118 | { | |
119 | $currentFiles = $this->listFiles($this->web_dir); | |
120 | $updateFiles = $this->listFiles(self::$temp_extracted_dir); | |
121 | $filesToDelete = array_diff($currentFiles, $updateFiles); | |
122 | $filesToActuallyDelete = []; | |
123 | error_log("Comparing... Files to delete:"); | |
124 | foreach ($filesToDelete as $file) | |
125 | { | |
126 | // skip the relevant directories | |
127 | if (str_starts_with($file, "panel_upgrade/") | |
128 | || str_starts_with($file, "vendor/") | |
129 | || str_starts_with($file, "config/") | |
130 | || str_starts_with($file, "data/") | |
131 | || str_starts_with($file, "plugins/")) | |
132 | continue; | |
133 | $filesToActuallyDelete[] = $file; | |
134 | } | |
135 | return $filesToActuallyDelete; | |
136 | } | |
137 | ||
138 | function extractToWebdir() | |
139 | { | |
140 | recurse_copy(self::$temp_extracted_dir, $this->web_dir); | |
141 | } | |
142 | ||
143 | /** | |
144 | * Cleans up the extracted update files | |
145 | * @return void | |
146 | */ | |
147 | function cleanupDownloadFiles() | |
148 | { | |
149 | $ex_dir = self::$temp_extracted_dir ?? findOnlyDirectory($this->temp_dir); | |
150 | deleteDirectoryContents($ex_dir); | |
151 | rmdir($ex_dir); | |
152 | } | |
153 | ||
154 | function listFiles($dir) { | |
155 | $files = []; | |
156 | $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)); | |
157 | foreach ($iterator as $file) | |
158 | { | |
159 | if ($file->isFile()) | |
160 | { | |
161 | $f = substr($file->getPathname(), strlen($dir)); | |
162 | if ($f[0] == "/") $f = substr($f,1); | |
163 | ||
164 | $files[] = $f; | |
165 | } | |
166 | } | |
167 | return $files; | |
168 | } | |
169 | } | |
170 | ||
171 | ||
172 | function findOnlyDirectory($topDir) { | |
173 | // Ensure the directory exists and is indeed a directory | |
174 | if (!is_dir($topDir)) { | |
175 | die("The specified path is not a directory."); | |
176 | } | |
177 | ||
178 | // Open the directory | |
179 | $dirHandle = opendir($topDir); | |
180 | if ($dirHandle === false) { | |
181 | die("Unable to open directory."); | |
182 | } | |
183 | ||
184 | $directories = []; | |
185 | ||
186 | // Read through the directory contents | |
187 | while (($entry = readdir($dirHandle)) !== false) { | |
188 | $fullPath = $topDir . DIRECTORY_SEPARATOR . $entry; | |
189 | // Check if the entry is a directory and not . or .. | |
190 | if (is_dir($fullPath) && $entry !== '.' && $entry !== '..') { | |
191 | $directories[] = $fullPath; | |
192 | } | |
193 | } | |
194 | ||
195 | // Close the directory handle | |
196 | closedir($dirHandle); | |
197 | ||
198 | // Check if there is exactly one directory | |
199 | if (count($directories) === 1) { | |
200 | return $directories[0]; | |
201 | } elseif (count($directories) === 0) { | |
202 | return "No directories found after extracting. Possibly missing php-zip extention. Aborting upgrade."; | |
203 | } else { | |
204 | return "Multiple directories found. Previous cleanup was unsuccessful for some reason, maybe a permissions error? Aborting upgrade."; | |
205 | } | |
206 | } | |
207 | ||
208 | ||
209 | function deleteDirectoryContents($dir) { | |
210 | error_log("Deleting directory contents at $dir"); | |
211 | if (!is_dir($dir)) { | |
212 | echo "The provided path is not a directory."; | |
213 | return false; | |
214 | } | |
215 | ||
216 | // Open the directory | |
217 | $handle = opendir($dir); | |
218 | if ($handle === false) { | |
219 | echo "Failed to open the directory."; | |
220 | return false; | |
221 | } | |
222 | ||
223 | // Loop through the directory contents | |
224 | while (($item = readdir($handle)) !== false) { | |
225 | // Skip the special entries "." and ".." | |
226 | if ($item == "." || $item == "..") { | |
227 | continue; | |
228 | } | |
229 | ||
230 | $itemPath = $dir."/".$item; | |
231 | ||
232 | // If the item is a directory, recursively delete its contents | |
233 | if (is_dir($itemPath)) { | |
234 | deleteDirectoryContents($itemPath); | |
235 | // Remove the empty directory | |
236 | rmdir($itemPath); | |
237 | } else { | |
238 | // If the item is a file, delete it | |
239 | unlink($itemPath); | |
240 | } | |
241 | } | |
242 | ||
243 | // Close the directory handle | |
244 | closedir($handle); | |
245 | ||
246 | return true; | |
247 | } | |
248 | ||
249 | function recurse_copy($src, $dst) { | |
250 | $dir = opendir($src); | |
251 | @mkdir($dst); | |
252 | while(false !== ( $file = readdir($dir)) ) | |
253 | if (( $file != '.' ) && ( $file != '..' )) | |
254 | { | |
255 | if ( is_dir($src . '/' . $file) ) | |
256 | recurse_copy($src . '/' . $file, $dst . '/' . $file); | |
257 | ||
258 | else | |
259 | copy($src . '/' . $file, $dst . '/' . $file); | |
260 | } | |
261 | ||
262 | ||
263 | closedir($dir); | |
264 | } |