Bearsampp 2026.9.23
Loading...
Searching...
No Matches
class.bin.postgresql.php
Go to the documentation of this file.
1<?php
2/*
3 * Copyright (c) 2021-2026 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
16class BinPostgresql extends Module
17{
21 const SERVICE_NAME = 'bearsampppostgresql';
22
26 const ROOT_CFG_ENABLE = 'postgresqlEnable';
30 const ROOT_CFG_VERSION = 'postgresqlVersion';
31
35 const LOCAL_CFG_CTL_EXE = 'postgresqlCtlExe';
39 const LOCAL_CFG_CLI_EXE = 'postgresqlCliExe';
43 const LOCAL_CFG_DUMP_EXE = 'postgresqlDumpExe';
47 const LOCAL_CFG_DUMP_ALL_EXE = 'postgresqlDumpAllExe';
51 const LOCAL_CFG_CONF = 'postgresqlConf';
55 const LOCAL_CFG_HBA_CONF = 'postgresqlUserConf';
59 const LOCAL_CFG_ALT_CONF = 'postgresqlAltConf';
63 const LOCAL_CFG_ALT_HBA_CONF = 'postgresqlAltUserConf';
67 const LOCAL_CFG_PORT = 'postgresqlPort';
71 const LOCAL_CFG_ROOT_USER = 'postgresqlRootUser';
75 const LOCAL_CFG_ROOT_PWD = 'postgresqlRootPwd';
76
80 const CMD_VERSION = '--version';
81
85 private $service;
89 private $errorLog;
93 private $ctlExe;
97 private $cliExe;
101 private $dumpExe;
105 private $dumpAllExe;
109 private $conf;
113 private $hbaConf;
117 private $altConf;
121 private $altHbaConf;
125 private $port;
129 private $rootUser;
133 private $rootPwd;
134
141 public function __construct($id, $type)
142 {
143 Log::initClass($this);
144 $this->reload($id, $type);
145 }
146
153 public function reload($id = null, $type = null)
154 {
156 Log::reloadClass($this);
157
158 $this->name = $bearsamppLang->getValue(Lang::POSTGRESQL);
159 $this->version = $bearsamppConfig->getRaw(self::ROOT_CFG_VERSION);
160 parent::reload($id, $type);
161
162 $this->enable = $this->enable && $bearsamppConfig->getRaw(self::ROOT_CFG_ENABLE);
163 $this->service = new Win32Service(self::SERVICE_NAME);
164 $this->errorLog = Path::getLogsPath() . '/postgresql.log';
165
166 if ($this->bearsamppConfRaw !== false) {
167 $this->ctlExe = $this->symlinkPath . '/' . $this->bearsamppConfRaw[self::LOCAL_CFG_CTL_EXE];
168 $this->cliExe = $this->symlinkPath . '/' . $this->bearsamppConfRaw[self::LOCAL_CFG_CLI_EXE];
169 $this->dumpExe = $this->symlinkPath . '/' . $this->bearsamppConfRaw[self::LOCAL_CFG_DUMP_EXE];
170 $this->dumpAllExe = $this->symlinkPath . '/' . $this->bearsamppConfRaw[self::LOCAL_CFG_DUMP_ALL_EXE];
171 $this->conf = $this->symlinkPath . '/' . $this->bearsamppConfRaw[self::LOCAL_CFG_CONF];
172 $this->hbaConf = $this->symlinkPath . '/' . $this->bearsamppConfRaw[self::LOCAL_CFG_HBA_CONF];
173 $this->altConf = $this->symlinkPath . '/' . $this->bearsamppConfRaw[self::LOCAL_CFG_ALT_CONF];
174 $this->altHbaConf = $this->symlinkPath . '/' . $this->bearsamppConfRaw[self::LOCAL_CFG_ALT_HBA_CONF];
175 $this->port = $this->bearsamppConfRaw[self::LOCAL_CFG_PORT];
176 $this->rootUser = isset($this->bearsamppConfRaw[self::LOCAL_CFG_ROOT_USER]) ? $this->bearsamppConfRaw[self::LOCAL_CFG_ROOT_USER] : 'postgres';
177 $this->rootPwd = isset($this->bearsamppConfRaw[self::LOCAL_CFG_ROOT_PWD]) ? $this->bearsamppConfRaw[self::LOCAL_CFG_ROOT_PWD] : '';
178 }
179
180 if (!$this->enable) {
181 Log::info($this->name . ' is not enabled!');
182
183 return;
184 }
185 if (!is_dir($this->currentPath)) {
186 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_FILE_NOT_FOUND), $this->name . ' ' . $this->version, $this->currentPath));
187
188 return;
189 }
190 if (!is_dir($this->symlinkPath)) {
191 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_FILE_NOT_FOUND), $this->name . ' ' . $this->version, $this->symlinkPath));
192
193 return;
194 }
195 if (!is_file($this->bearsamppConf)) {
196 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_CONF_NOT_FOUND), $this->name . ' ' . $this->version, $this->bearsamppConf));
197
198 return;
199 }
200 if (!file_exists($this->conf)) {
201 $this->conf = $this->altConf;
202 }
203 if (!file_exists($this->hbaConf)) {
204 $this->hbaConf = $this->altHbaConf;
205 }
206
207 if (!is_file($this->ctlExe)) {
208 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_EXE_NOT_FOUND), $this->name . ' ' . $this->version, $this->ctlExe));
209
210 return;
211 }
212 if (!is_file($this->cliExe)) {
213 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_EXE_NOT_FOUND), $this->name . ' ' . $this->version, $this->cliExe));
214
215 return;
216 }
217 if (!is_file($this->dumpExe)) {
218 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_EXE_NOT_FOUND), $this->name . ' ' . $this->version, $this->dumpExe));
219
220 return;
221 }
222 if (!is_file($this->dumpAllExe)) {
223 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_EXE_NOT_FOUND), $this->name . ' ' . $this->version, $this->dumpAllExe));
224
225 return;
226 }
227 if (!is_file($this->conf)) {
228 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_CONF_NOT_FOUND), $this->name . ' ' . $this->version, $this->conf));
229
230 return;
231 }
232 if (!is_file($this->hbaConf)) {
233 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_CONF_NOT_FOUND), $this->name . ' ' . $this->version, $this->hbaConf));
234
235 return;
236 }
237 if (!is_numeric($this->port) || $this->port <= 0) {
238 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_INVALID_PARAMETER), self::LOCAL_CFG_PORT, $this->port));
239
240 return;
241 }
242 if (empty($this->rootUser)) {
243 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_INVALID_PARAMETER), self::LOCAL_CFG_ROOT_USER, $this->rootUser));
244
245 return;
246 }
247
248 $this->service->setDisplayName(APP_TITLE . ' ' . $this->getName());
249 $this->service->setBinPath($this->ctlExe);
250 $this->service->setStartType(Win32Service::SERVICE_DEMAND_START);
251 $this->service->setErrorControl(Win32Service::SERVER_ERROR_NORMAL);
252 }
253
259 protected function replaceAll($params)
260 {
261 $content = file_get_contents($this->bearsamppConf);
262
263 foreach ($params as $key => $value) {
264 $content = preg_replace('|' . $key . ' = .*|', $key . ' = ' . '"' . $value . '"', $content);
265 $this->bearsamppConfRaw[$key] = $value;
266 switch ($key) {
267 case self::LOCAL_CFG_PORT:
268 $this->port = $value;
269 break;
270 case self::LOCAL_CFG_ROOT_USER:
271 $this->rootUser = $value;
272 break;
273 case self::LOCAL_CFG_ROOT_PWD:
274 $this->rootPwd = $value;
275 break;
276 }
277 }
278
279 file_put_contents($this->bearsamppConf, $content);
280
281 self::invalidateConfigCacheForPath($this->bearsamppConf);
282 }
283
293 public function changePort($port, $checkUsed = false, $wbProgressBar = null)
294 {
295 global $bearsamppWinbinder;
296
297 if (!Util::isValidPort($port)) {
298 Log::error($this->getName() . ' port not valid: ' . $port);
299
300 return false;
301 }
302
303 $port = intval($port);
304 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
305
306 $isPortInUse = ServiceHelper::isPortInUse($port);
307 if (!$checkUsed || $isPortInUse === false) {
308 // bearsampp.conf
309 $this->setPort($port);
310 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
311
312 // conf
313 $this->update();
314 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
315
316 return true;
317 }
318
319 Log::debug($this->getName() . ' port in used: ' . $port . ' - ' . $isPortInUse);
320
321 return $isPortInUse;
322 }
323
332 public function checkPort($port, $showWindow = false)
333 {
334 global $bearsamppLang, $bearsamppWinbinder;
335 $boxTitle = sprintf($bearsamppLang->getValue(Lang::CHECK_PORT_TITLE), $this->getName(), $port);
336
337 if (!Util::isValidPort($port)) {
338 Log::error($this->getName() . ' port not valid: ' . $port);
339
340 return false;
341 }
342
343 $dbLink = @pg_connect('host=' . APP_LOCALHOST . " port=$port user={$this->rootUser} password={$this->rootPwd}");
344 if (!$dbLink) {
345 $lastError = error_get_last();
346 $errorMsg = $lastError ? $lastError['message'] : 'Unknown error';
347 Log::debug($this->getName() . ' connection failed: ' . $errorMsg);
348
349 return $this->handleNonPostgresUsage($port, $showWindow, $boxTitle);
350 }
351
352 // Verify active PostgreSQL connection
353 $connectionStatus = pg_connection_status($dbLink);
354 $isPostgres = $connectionStatus === PGSQL_CONNECTION_OK;
355 pg_close($dbLink);
356
357 if ($isPostgres) {
358 Log::debug($this->getName() . " port $port is used by PostgreSQL");
359 if ($showWindow) {
360 $bearsamppWinbinder->messageBoxInfo(
361 sprintf($bearsamppLang->getValue(Lang::PORT_USED_BY), $port, 'PostgreSQL'),
362 $boxTitle
363 );
364 }
365
366 return true;
367 }
368
369 return $this->handleNonPostgresUsage($port, $showWindow, $boxTitle);
370 }
371
375 private function handleNonPostgresUsage($port, $showWindow, $boxTitle)
376 {
377 global $bearsamppLang, $bearsamppWinbinder;
378
380 Log::debug($this->getName() . " port $port used by non-PostgreSQL service");
381 if ($showWindow) {
382 $bearsamppWinbinder->messageBoxWarning(
384 $boxTitle
385 );
386 }
387
388 return false;
389 }
390
391 Log::debug($this->getName() . " port $port not in use");
392 if ($showWindow) {
393 $bearsamppWinbinder->messageBoxError(
394 sprintf($bearsamppLang->getValue(Lang::PORT_NOT_USED), $port),
395 $boxTitle
396 );
397 }
398
399 return false;
400 }
401
411 public function changeRootPassword($currentPwd, $newPwd, $wbProgressBar = null)
412 {
413 global $bearsamppWinbinder;
414 $error = null;
415
416 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
417 $dbLink = pg_connect('host=' . APP_LOCALHOST . ' port=' . $this->port . ' user=' . $this->rootUser . ' password=' . $currentPwd);
418
419 if (!$dbLink) {
420 $error = error_get_last()['message'];
421 }
422
423 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
424 $pgr = pg_query_params($dbLink, 'SELECT quote_ident($1)', array(pg_escape_string($this->rootUser)));
425 list($quoted_user) = pg_fetch_array($pgr);
426 $password = pg_escape_string($newPwd);
427 $result = pg_query($dbLink, "ALTER USER $quoted_user WITH PASSWORD '$password'");
428 if (empty($error) && !$result) {
429 $error = pg_last_error($dbLink);
430 }
431
432 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
433 if ($dbLink) {
434 pg_close($dbLink);
435 }
436
437 if (!empty($error)) {
438 return $error;
439 }
440
441 // bearsampp.conf
442 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
443 $this->setRootPwd($newPwd);
444
445 // conf
446 $this->update();
447 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
448
449 return true;
450 }
451
460 public function checkRootPassword($currentPwd = null, $wbProgressBar = null)
461 {
462 global $bearsamppWinbinder;
463 $currentPwd = $currentPwd == null ? $this->rootPwd : $currentPwd;
464 $error = null;
465
466 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
467 $dbLink = pg_connect('host=' . APP_LOCALHOST . ' port=' . $this->port . ' user=' . $this->rootUser . ' password=' . $currentPwd);
468 if (!$dbLink) {
469 $error = error_get_last()['message'];
470 }
471
472 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
473 if ($dbLink) {
474 pg_close($dbLink);
475 }
476
477 if (!empty($error)) {
478 return $error;
479 }
480
481 return true;
482 }
483
492 public function switchVersion($version, $showWindow = false)
493 {
494 Log::debug('Switch ' . $this->name . ' version to ' . $version);
495
496 return $this->updateConfig($version, 0, $showWindow);
497 }
498
508 protected function updateConfig($version = null, $sub = 0, $showWindow = false)
509 {
510 global $bearsamppLang, $bearsamppApps, $bearsamppWinbinder;
511
512 if (!$this->enable) {
513 return true;
514 }
515
516 $version = $version == null ? $this->version : $version;
517 Log::debug(($sub > 0 ? str_repeat(' ', 2 * $sub) : '') . 'Update ' . $this->name . ' ' . $version . ' config');
518
519 $boxTitle = sprintf($bearsamppLang->getValue(Lang::SWITCH_VERSION_TITLE), $this->getName(), $version);
520
521 $currentPath = str_replace('postgresql' . $this->getVersion(), 'postgresql' . $version, Path::getModuleCurrentPath($this));
522 $conf = str_replace('postgresql' . $this->getVersion(), 'postgresql' . $version, $this->getConf());
523 $bearsamppConf = str_replace('postgresql' . $this->getVersion(), 'postgresql' . $version, $this->bearsamppConf);
524
525 if ($this->version != $version) {
526 $this->initData($currentPath);
527 }
528
529 if (!file_exists($conf) || !file_exists($bearsamppConf)) {
530 Log::error('bearsampp config files not found for ' . $this->getName() . ' ' . $version);
531 if ($showWindow) {
532 $bearsamppWinbinder->messageBoxError(
533 sprintf($bearsamppLang->getValue(Lang::BEARSAMPP_CONF_NOT_FOUND_ERROR), $this->getName() . ' ' . $version),
534 $boxTitle
535 );
536 }
537
538 return false;
539 }
540
541 $bearsamppConfRaw = parse_ini_file($bearsamppConf);
542 if ($bearsamppConfRaw === false || !isset($bearsamppConfRaw[self::ROOT_CFG_VERSION]) || $bearsamppConfRaw[self::ROOT_CFG_VERSION] != $version) {
543 Log::error('bearsampp config file malformed for ' . $this->getName() . ' ' . $version);
544 if ($showWindow) {
545 $bearsamppWinbinder->messageBoxError(
546 sprintf($bearsamppLang->getValue(Lang::BEARSAMPP_CONF_MALFORMED_ERROR), $this->getName() . ' ' . $version),
547 $boxTitle
548 );
549 }
550
551 return false;
552 }
553
554 // bearsampp.conf
555 $this->setVersion($version);
556
557 // conf
558 Util::replaceInFile($this->getConf(), array(
559 '/^port(.*?)=(.*?)(\d+)/' => 'port = ' . $this->port
560 ));
561
562 // phppgadmin
563 $bearsamppApps->getPhppgadmin()->update($sub + 1);
564
565 return true;
566 }
567
573 public function initData($path = null)
574 {
575 $path = $path != null ? $path : Path::getModuleCurrentPath($this);
576
577 if (file_exists($path . '/data')) {
578 // Even if it exists, ensure placeholders are replaced in the whole folder
579 $filesToScan = Util::getFilesToScan(array(
580 array(
581 'path' => $path,
582 'includes' => array('.conf', '.bat', '.ber'),
583 'recursive' => true
584 )
585 ));
586 if (!empty($filesToScan)) {
587 Path::changePath($filesToScan);
588 }
589
590 return;
591 }
592
594
595 // Replace placeholders in the newly created data directory and the whole version folder
596 $filesToScan = Util::getFilesToScan(array(
597 array(
598 'path' => $path,
599 'includes' => array('.conf', '.bat', '.ber'),
600 'recursive' => true
601 )
602 ));
603 if (!empty($filesToScan)) {
604 Path::changePath($filesToScan);
605 }
606 }
607
611 public function rebuildConf()
612 {
613 Util::replaceInFile($this->conf, array(
614 '/^port(.*?)=(.*?)(\d+)/' => 'port = ' . $this->port
615 ));
616 Util::replaceInFile($this->altConf, array(
617 '/^port(.*?)=(.*?)(\d+)/' => 'port = ' . $this->port
618 ));
619 }
620
628 public function getCmdLineOutput($cmd)
629 {
630 $result = null;
631
632 $bin = $this->getCliExe();
633 if (file_exists($bin)) {
634 $tmpResult = Batch::exec('postgresqlGetCmdLineOutput', '"' . $bin . '" ' . $cmd);
635 if ($tmpResult !== false && is_array($tmpResult)) {
636 $result = trim(str_replace($bin, '', implode(PHP_EOL, $tmpResult)));
637 }
638 }
639
640 return $result;
641 }
642
648 public function setVersion($version)
649 {
650 global $bearsamppConfig;
651 $this->version = $version;
652 $bearsamppConfig->replace(self::ROOT_CFG_VERSION, $version);
653 $this->reload();
654 }
655
661 public function getService()
662 {
663 return $this->service;
664 }
665
672 public function setEnable($enabled, $showWindow = false)
673 {
674 global $bearsamppConfig, $bearsamppLang, $bearsamppWinbinder;
675
676 if ($enabled == Config::ENABLED && !is_dir($this->currentPath)) {
677 Log::debug($this->getName() . ' cannot be enabled because bundle ' . $this->getVersion() . ' does not exist in ' . $this->currentPath);
678 if ($showWindow) {
679 $bearsamppWinbinder->messageBoxError(
680 sprintf($bearsamppLang->getValue(Lang::ENABLE_BUNDLE_NOT_EXIST), $this->getName(), $this->getVersion(), $this->currentPath),
681 sprintf($bearsamppLang->getValue(Lang::ENABLE_TITLE), $this->getName())
682 );
683 }
684 $enabled = Config::DISABLED;
685 }
686
687 Log::info($this->getName() . ' switched to ' . ($enabled == Config::ENABLED ? 'enabled' : 'disabled'));
688 $this->enable = $enabled == Config::ENABLED;
689 $bearsamppConfig->replace(self::ROOT_CFG_ENABLE, $enabled);
690
691 $this->reload();
692 if ($this->enable) {
693 ServiceHelper::installService($this, $this->port, null, $showWindow);
694 } else {
695 ServiceHelper::removeService($this->service, $this->name);
696 }
697 }
698
704 public function getErrorLog()
705 {
706 return $this->errorLog;
707 }
708
714 public function getCtlExe()
715 {
716 return $this->ctlExe;
717 }
718
724 public function getCliExe()
725 {
726 return $this->cliExe;
727 }
728
734 public function getDumpExe()
735 {
736 return $this->dumpExe;
737 }
738
744 public function getDumpAllExe()
745 {
746 return $this->dumpAllExe;
747 }
748
754 public function getConf()
755 {
756 return $this->conf;
757 }
758
764 public function getHbaConf()
765 {
766 return $this->hbaConf;
767 }
768
774 public function getPort()
775 {
776 return $this->port;
777 }
778
784 public function setPort($port)
785 {
786 $this->replace(self::LOCAL_CFG_PORT, $port);
787 }
788
794 public function getRootUser()
795 {
796 return $this->rootUser;
797 }
798
804 public function setRootUser($rootUser)
805 {
806 $this->replace(self::LOCAL_CFG_ROOT_USER, $rootUser);
807 }
808
814 public function getRootPwd()
815 {
816 return $this->rootPwd;
817 }
818
824 public function setRootPwd($rootPwd)
825 {
826 $this->replace(self::LOCAL_CFG_ROOT_PWD, $rootPwd);
827 }
828}
$result
global $bearsamppLang
global $bearsamppRoot
static initializePostgresql($path)
static exec($basename, $content, $timeout=true, $catchOutput=true, $standalone=false, $silent=true, $rebuild=true)
handleNonPostgresUsage($port, $showWindow, $boxTitle)
updateConfig($version=null, $sub=0, $showWindow=false)
changeRootPassword($currentPwd, $newPwd, $wbProgressBar=null)
switchVersion($version, $showWindow=false)
checkPort($port, $showWindow=false)
changePort($port, $checkUsed=false, $wbProgressBar=null)
setEnable($enabled, $showWindow=false)
reload($id=null, $type=null)
checkRootPassword($currentPwd=null, $wbProgressBar=null)
const DISABLED
const ENABLED
const ENABLE_BUNDLE_NOT_EXIST
const BEARSAMPP_CONF_MALFORMED_ERROR
const ERROR_EXE_NOT_FOUND
const ERROR_CONF_NOT_FOUND
const POSTGRESQL
const BEARSAMPP_CONF_NOT_FOUND_ERROR
const PORT_NOT_USED
const ERROR_INVALID_PARAMETER
const ENABLE_TITLE
const SWITCH_VERSION_TITLE
const CHECK_PORT_TITLE
const PORT_USED_BY
const PORT_USED_BY_ANOTHER_DBMS
const ERROR_FILE_NOT_FOUND
static info($data, $file=null)
static debug($data, $file=null)
static reloadClass($classInstance)
static error($data, $file=null)
static initClass($classInstance)
update($sub=0, $showWindow=false)
static invalidateConfigCacheForPath($sourcePath)
replace($key, $value)
static getLogsPath($aetrayPath=false)
static changePath($filesToScan, $rootPath=null)
static getModuleCurrentPath($module)
static installService($bin, $port, $syntaxCheckCmd, $showWindow=false)
static removeService($service, $name)
static isPortInUse($port)
static isValidPort($port)
static getFilesToScan($path=null, $useCache=true, $forceRefresh=false)
static replaceInFile($path, $replaceList)
global $bearsamppConfig
Definition homepage.php:46
const APP_LOCALHOST
Definition root.php:21
const APP_TITLE
Definition root.php:19