2024.8.23
Loading...
Searching...
No Matches
class.batch.php
Go to the documentation of this file.
1<?php
2/*
3 * Copyright (c) 2021-2024 Bearsampp
4 * License: GNU General Public License version 3 or later; see LICENSE.txt
5 * Author: Bear
6 * Website: https://bearsampp.com
7 * Github: https://github.com/Bearsampp
8 */
9
10/**
11 * Class Batch
12 *
13 * This class provides various utility functions for managing processes, services, and environment variables
14 * within the Bearsampp application. It includes methods for finding executables by process ID, checking which
15 * process is using a specific port, exiting and restarting the application, managing services, and executing
16 * batch scripts.
17 *
18 * Key functionalities include:
19 * - Finding executables by process ID.
20 * - Checking which process is using a specific port.
21 * - Exiting and restarting the application.
22 * - Managing services (installing, uninstalling, setting descriptions, etc.).
23 * - Executing batch scripts with optional output capture and timeout.
24 * - Refreshing environment variables.
25 * - Creating and removing symbolic links.
26 * - Retrieving operating system information.
27 *
28 * The class utilizes global variables to access application settings and paths, and logs operations for debugging purposes.
29 */
30class Batch
31{
32 const END_PROCESS_STR = 'FINISHED!';
33 const CATCH_OUTPUT_FALSE = 'bearsamppCatchOutputFalse';
34
35 /**
36 * Constructor for the Batch class.
37 */
38 public function __construct()
39 {
40 }
41
42 /**
43 * Writes a log entry to the batch log file.
44 *
45 * @param string $log The log message to write.
46 */
47 private static function writeLog($log)
48 {
49 global $bearsamppRoot;
50 Util::logDebug($log, $bearsamppRoot->getBatchLogFilePath());
51 }
52
53 /**
54 * Finds the executable name by its process ID (PID).
55 *
56 * @param int $pid The process ID to search for.
57 * @return string|false The executable name if found, false otherwise.
58 */
59 public static function findExeByPid($pid)
60 {
61 $result = self::exec('findExeByPid', 'TASKLIST /FO CSV /NH /FI "PID eq ' . $pid . '"', 5);
62 if ($result !== false) {
63 $expResult = explode('","', $result[0]);
64 if (is_array($expResult) && count($expResult) > 2 && isset($expResult[0]) && !empty($expResult[0])) {
65 return substr($expResult[0], 1);
66 }
67 }
68
69 return false;
70 }
71
72 /**
73 * Gets the process using a specific port.
74 *
75 * @param int $port The port number to check.
76 * @return string|int|null The executable name and PID if found, the PID if executable not found, or null if no process is using the port.
77 */
78 public static function getProcessUsingPort($port)
79 {
80 $result = self::exec('getProcessUsingPort', 'NETSTAT -aon', 4);
81 if ($result !== false) {
82 foreach ($result as $row) {
83 if (!Util::startWith($row, 'TCP')) {
84 continue;
85 }
86 $rowExp = explode(' ', preg_replace('/\s+/', ' ', $row));
87 if (count($rowExp) == 5 && Util::endWith($rowExp[1], ':' . $port) && $rowExp[3] == 'LISTENING') {
88 $pid = intval($rowExp[4]);
89 $exe = self::findExeByPid($pid);
90 if ($exe !== false) {
91 return $exe . ' (' . $pid . ')';
92 }
93 return $pid;
94 }
95 }
96 }
97
98 return null;
99 }
100
101 /**
102 * Exits the application, optionally restarting it.
103 *
104 * @param bool $restart Whether to restart the application after exiting.
105 */
106 public static function exitApp($restart = false)
107 {
109
110 $content = 'PING 1.1.1.1 -n 1 -w 2000 > nul' . PHP_EOL;
111 $content .= '"' . $bearsamppRoot->getExeFilePath() . '" -quit -id={bearsampp}' . PHP_EOL;
112 if ($restart) {
113 $basename = 'restartApp';
114 Util::logInfo('Restart App');
115 $content .= '"' . $bearsamppCore->getPhpExe() . '" "' . Core::isRoot_FILE . '" "' . Action::RESTART . '"' . PHP_EOL;
116 } else {
117 $basename = 'exitApp';
118 Util::logInfo('Exit App');
119 }
120
122 self::execStandalone($basename, $content);
123 }
124
125 /**
126 * Restarts the application.
127 */
128 public static function restartApp()
129 {
130 self::exitApp(true);
131 }
132
133 /**
134 * Gets the version of PEAR installed.
135 *
136 * @return string|null The PEAR version if found, null otherwise.
137 */
138 public static function getPearVersion()
139 {
140 global $bearsamppBins;
141
142 $result = self::exec('getPearVersion', 'CMD /C "' . $bearsamppBins->getPhp()->getPearExe() . '" -V', 5);
143 if (is_array($result)) {
144 foreach ($result as $row) {
145 if (Util::startWith($row, 'PEAR Version:')) {
146 $expResult = explode(' ', $row);
147 if (count($expResult) == 3) {
148 return trim($expResult[2]);
149 }
150 }
151 }
152 }
153
154 return null;
155 }
156
157 /**
158 * Refreshes the environment variables.
159 */
160 public static function refreshEnvVars()
161 {
163 self::execStandalone('refreshEnvVars', '"' . $bearsamppCore->getSetEnvExe() . '" -a ' . Registry::APP_PATH_REG_ENTRY . ' "' . Util::formatWindowsPath($bearsamppRoot->getRootPath()) . '"');
164 }
165
166 /**
167 * Installs the FileZilla service.
168 *
169 * @return bool True if the service was installed successfully, false otherwise.
170 */
171 public static function installFilezillaService()
172 {
173 global $bearsamppBins;
174
175 self::exec('installFilezillaService', '"' . $bearsamppBins->getFilezilla()->getExe() . '" /install', true, false);
176
177 if (!$bearsamppBins->getFilezilla()->getService()->isInstalled()) {
178 return false;
179 }
180
181 self::setServiceDescription(BinFilezilla::SERVICE_NAME, $bearsamppBins->getFilezilla()->getService()->getDisplayName());
182
183 return true;
184 }
185
186 /**
187 * Uninstalls the FileZilla service.
188 *
189 * @return bool True if the service was uninstalled successfully, false otherwise.
190 */
191 public static function uninstallFilezillaService()
192 {
193 global $bearsamppBins;
194
195 self::exec('uninstallFilezillaService', '"' . $bearsamppBins->getFilezilla()->getExe() . '" /uninstall', true, false);
196 return !$bearsamppBins->getFilezilla()->getService()->isInstalled();
197 }
198
199 /**
200 * Initializes MySQL using a specified path.
201 *
202 * @param string $path The path to the MySQL initialization script.
203 */
204 public static function initializeMysql($path)
205 {
206 if (!file_exists($path . '/init.bat')) {
207 Util::logWarning($path . '/init.bat does not exist');
208 return;
209 }
210 self::exec('initializeMysql', 'CMD /C "' . $path . '/init.bat"', 60);
211 }
212
213 /**
214 * Installs the PostgreSQL service.
215 *
216 * @return bool True if the service was installed successfully, false otherwise.
217 */
218 public static function installPostgresqlService()
219 {
220 global $bearsamppBins;
221
222 $cmd = '"' . Util::formatWindowsPath($bearsamppBins->getPostgresql()->getCtlExe()) . '" register -N "' . BinPostgresql::SERVICE_NAME . '"';
223 $cmd .= ' -U "LocalSystem" -D "' . Util::formatWindowsPath($bearsamppBins->getPostgresql()->getSymlinkPath()) . '\\data"';
224 $cmd .= ' -l "' . Util::formatWindowsPath($bearsamppBins->getPostgresql()->getErrorLog()) . '" -w';
225 self::exec('installPostgresqlService', $cmd, true, false);
226
227 if (!$bearsamppBins->getPostgresql()->getService()->isInstalled()) {
228 return false;
229 }
230
231 self::setServiceDisplayName(BinPostgresql::SERVICE_NAME, $bearsamppBins->getPostgresql()->getService()->getDisplayName());
232 self::setServiceDescription(BinPostgresql::SERVICE_NAME, $bearsamppBins->getPostgresql()->getService()->getDisplayName());
234
235 return true;
236 }
237
238 /**
239 * Uninstalls the PostgreSQL service.
240 *
241 * @return bool True if the service was uninstalled successfully, false otherwise.
242 */
243 public static function uninstallPostgresqlService()
244 {
245 global $bearsamppBins;
246
247 $cmd = '"' . Util::formatWindowsPath($bearsamppBins->getPostgresql()->getCtlExe()) . '" unregister -N "' . BinPostgresql::SERVICE_NAME . '"';
248 $cmd .= ' -l "' . Util::formatWindowsPath($bearsamppBins->getPostgresql()->getErrorLog()) . '" -w';
249 self::exec('uninstallPostgresqlService', $cmd, true, false);
250 return !$bearsamppBins->getPostgresql()->getService()->isInstalled();
251 }
252
253 /**
254 * Initializes PostgreSQL using a specified path.
255 *
256 * @param string $path The path to the PostgreSQL initialization script.
257 */
258 public static function initializePostgresql($path)
259 {
260 if (!file_exists($path . '/init.bat')) {
261 Util::logWarning($path . '/init.bat does not exist');
262 return;
263 }
264 self::exec('initializePostgresql', 'CMD /C "' . $path . '/init.bat"', 15);
265 }
266
267 /**
268 * Creates a symbolic link.
269 *
270 * @param string $src The source path.
271 * @param string $dest The destination path.
272 */
273 public static function createSymlink($src, $dest)
274 {
275 global $bearsamppCore;
276 $src = Util::formatWindowsPath($src);
277 $dest = Util::formatWindowsPath($dest);
278 self::exec('createSymlink', '"' . $bearsamppCore->getLnExe() . '" --absolute --symbolic --traditional --1023safe "' . $src . '" ' . '"' . $dest . '"', true, false);
279 }
280
281 /**
282 * Removes a symbolic link.
283 *
284 * @param string $link The path to the symbolic link.
285 */
286 public static function removeSymlink($link)
287 {
288 self::exec('removeSymlink', 'rmdir /Q "' . Util::formatWindowsPath($link) . '"', true, false);
289 }
290
291 /**
292 * Gets the operating system information.
293 *
294 * @return string The operating system information.
295 */
296 public static function getOsInfo()
297 {
298 $result = self::exec('getOsInfo', 'ver', 5);
299 if (is_array($result)) {
300 foreach ($result as $row) {
301 if (Util::startWith($row, 'Microsoft')) {
302 return trim($row);
303 }
304 }
305 }
306 return '';
307 }
308
309 /**
310 * Sets the display name of a service.
311 *
312 * @param string $serviceName The name of the service.
313 * @param string $displayName The display name to set.
314 */
315 public static function setServiceDisplayName($serviceName, $displayName)
316 {
317 $cmd = 'sc config ' . $serviceName . ' DisplayName= "' . $displayName . '"';
318 self::exec('setServiceDisplayName', $cmd, true, false);
319 }
320
321 /**
322 * Sets the description of a service.
323 *
324 * @param string $serviceName The name of the service.
325 * @param string $desc The description to set.
326 */
327 public static function setServiceDescription($serviceName, $desc)
328 {
329 $cmd = 'sc description ' . $serviceName . ' "' . $desc . '"';
330 self::exec('setServiceDescription', $cmd, true, false);
331 }
332
333 /**
334 * Sets the start type of a service.
335 *
336 * @param string $serviceName The name of the service.
337 * @param string $startType The start type to set (e.g., "auto", "demand").
338 */
339 public static function setServiceStartType($serviceName, $startType)
340 {
341 $cmd = 'sc config ' . $serviceName . ' start= ' . $startType;
342 self::exec('setServiceStartType', $cmd, true, false);
343 }
344
345 /**
346 * Executes a standalone batch script.
347 *
348 * @param string $basename The base name for the script and result files.
349 * @param string $content The content of the batch script.
350 * @param bool $silent Whether to execute the script silently.
351 * @return array|false The result of the execution, or false on failure.
352 */
353 public static function execStandalone($basename, $content, $silent = true)
354 {
355 return self::exec($basename, $content, false, false, true, $silent);
356 }
357
358 /**
359 * Executes a batch script.
360 *
361 * @param string $basename The base name for the script and result files.
362 * @param string $content The content of the batch script.
363 * @param int|bool $timeout The timeout for the script execution in seconds, or true for default timeout, or false for no timeout.
364 * @param bool $catchOutput Whether to capture the output of the script.
365 * @param bool $standalone Whether the script is standalone.
366 * @param bool $silent Whether to execute the script silently.
367 * @param bool $rebuild Whether to rebuild the result array.
368 * @return array|false The result of the execution, or false on failure.
369 */
370 public static function exec($basename, $content, $timeout = true, $catchOutput = true, $standalone = false, $silent = true, $rebuild = true)
371 {
372 global $bearsamppConfig, $bearsamppWinbinder;
373 $result = false;
374
375 $resultFile = self::getTmpFile('.tmp', $basename);
376 $scriptPath = self::getTmpFile('.bat', $basename);
377 $checkFile = self::getTmpFile('.tmp', $basename);
378
379 // Redirect output
380 if ($catchOutput) {
381 $content .= '> "' . $resultFile . '"' . (!Util::endWith($content, '2') ? ' 2>&1' : '');
382 }
383
384 // Header
385 $header = '@ECHO OFF' . PHP_EOL . PHP_EOL;
386
387 // Footer
388 $footer = PHP_EOL . (!$standalone ? PHP_EOL . 'ECHO ' . self::END_PROCESS_STR . ' > "' . $checkFile . '"' : '');
389
390 // Process
391 file_put_contents($scriptPath, $header . $content . $footer);
392 $bearsamppWinbinder->exec($scriptPath, null, $silent);
393
394 if (!$standalone) {
395 $timeout = is_numeric($timeout) ? $timeout : ($timeout === true ? $bearsamppConfig->getScriptsTimeout() : false);
396 $maxtime = time() + $timeout;
397 $noTimeout = $timeout === false;
398 while ($result === false || empty($result)) {
399 if (file_exists($checkFile)) {
400 $check = file($checkFile);
401 if (!empty($check) && trim($check[0]) == self::END_PROCESS_STR) {
402 if ($catchOutput && file_exists($resultFile)) {
403 $result = file($resultFile);
404 } else {
406 }
407 }
408 }
409 if ($maxtime < time() && !$noTimeout) {
410 break;
411 }
412 }
413 }
414
415 self::writeLog('Exec:');
416 self::writeLog('-> basename: ' . $basename);
417 self::writeLog('-> content: ' . str_replace(PHP_EOL, ' \\\\ ', $content));
418 self::writeLog('-> checkFile: ' . $checkFile);
419 self::writeLog('-> resultFile: ' . $resultFile);
420 self::writeLog('-> scriptPath: ' . $scriptPath);
421
422 if ($result !== false && !empty($result) && is_array($result)) {
423 if ($rebuild) {
424 $rebuildResult = array();
425 foreach ($result as $row) {
426 $row = trim($row);
427 if (!empty($row)) {
428 $rebuildResult[] = $row;
429 }
430 }
431 $result = $rebuildResult;
432 }
433 self::writeLog('-> result: ' . substr(implode(' \\\\ ', $result), 0, 2048));
434 } else {
435 self::writeLog('-> result: N/A');
436 }
437
438 return $result;
439 }
440
441 /**
442 * Gets a temporary file path with a specified extension and optional custom name.
443 *
444 * @param string $ext The file extension.
445 * @param string|null $customName An optional custom name for the file.
446 * @return string The temporary file path.
447 */
448 private static function getTmpFile($ext, $customName = null)
449 {
450 global $bearsamppCore;
451 return Util::formatWindowsPath($bearsamppCore->getTmpPath() . '/' . (!empty($customName) ? $customName . '-' : '') . Util::random() . $ext);
452 }
453}
$result
global $bearsamppBins
global $bearsamppRoot
$port
global $bearsamppCore
const RESTART
static findExeByPid($pid)
static exec($basename, $content, $timeout=true, $catchOutput=true, $standalone=false, $silent=true, $rebuild=true)
static execStandalone($basename, $content, $silent=true)
__construct()
static setServiceDisplayName($serviceName, $displayName)
static removeSymlink($link)
static createSymlink($src, $dest)
static getPearVersion()
static exitApp($restart=false)
static installPostgresqlService()
static writeLog($log)
const CATCH_OUTPUT_FALSE
static uninstallFilezillaService()
static refreshEnvVars()
static initializePostgresql($path)
static initializeMysql($path)
static uninstallPostgresqlService()
static setServiceDescription($serviceName, $desc)
const END_PROCESS_STR
static setServiceStartType($serviceName, $startType)
static restartApp()
static getOsInfo()
static getTmpFile($ext, $customName=null)
static getProcessUsingPort($port)
static installFilezillaService()
const isRoot_FILE
const APP_PATH_REG_ENTRY
static startWith($string, $search)
static endWith($string, $search)
static random($length=32, $withNumeric=true)
static logDebug($data, $file=null)
static logInfo($data, $file=null)
static logWarning($data, $file=null)
static formatWindowsPath($path)
static killBins($refreshProcs=false)
global $bearsamppConfig
Definition homepage.php:26