Bearsampp 2026.9.23
Loading...
Searching...
No Matches
class.bin.mailpit.php
Go to the documentation of this file.
1<?php
2/*
3 *
4 * * Copyright (c) 2022-2025 Bearsampp
5 * * License: GNU General Public License version 3 or later; see LICENSE.txt
6 * * Website: https://bearsampp.com
7 * * Github: https://github.com/Bearsampp
8 *
9 */
10
17class BinMailpit extends Module
18{
22 const SERVICE_NAME = 'bearsamppmailpit';
26 const SERVICE_PARAMS = ' --listen "%s:%d" --smtp "%s:%d" --webroot "%s"';
27
31 const ROOT_CFG_ENABLE = 'mailpitEnable';
35 const ROOT_CFG_VERSION = 'mailpitVersion';
36
40 const LOCAL_CFG_EXE = 'mailpitExe';
44 const LOCAL_CFG_WEB_ROOT = 'mailpitWebRoot';
48 const LOCAL_CFG_UI_PORT = 'mailpitUiPort';
52 const LOCAL_CFG_SMTP_PORT = 'mailpitSmtpPort';
56 const LOCAL_CFG_LISTEN = 'mailpitListen';
57
61 private $service;
65 private $log;
66
70 private $exe;
74 private $webRoot;
78 private $uiPort;
82 private $smtpPort;
86 private $listen;
87
94 public function __construct($id, $type)
95 {
96 Log::initClass($this);
97 $this->reload($id, $type);
98 }
99
106 public function reload($id = null, $type = null)
107 {
109 Log::reloadClass($this);
110
111 $this->name = $bearsamppLang->getValue(Lang::MAILPIT);
112 $this->version = $bearsamppConfig->getRaw(self::ROOT_CFG_VERSION);
113 parent::reload($id, $type);
114
115 $this->enable = $this->enable && $bearsamppConfig->getRaw(self::ROOT_CFG_ENABLE);
116 $this->service = new Win32Service(self::SERVICE_NAME);
117 $this->log = Path::getLogsPath() . '/mailpit.log';
118
119 if ($this->bearsamppConfRaw !== false) {
120 $this->exe = $this->symlinkPath . '/' . $this->bearsamppConfRaw[self::LOCAL_CFG_EXE];
121 $this->webRoot = $this->bearsamppConfRaw[self::LOCAL_CFG_WEB_ROOT];
122 $this->uiPort = intval($this->bearsamppConfRaw[self::LOCAL_CFG_UI_PORT]);
123 $this->smtpPort = intval($this->bearsamppConfRaw[self::LOCAL_CFG_SMTP_PORT]);
124 $this->listen = $this->bearsamppConfRaw[self::LOCAL_CFG_LISTEN];
125 }
126
127 if (!$this->enable) {
128 Log::info($this->name . ' is not enabled!');
129
130 return;
131 }
132 if (!is_dir($this->currentPath)) {
133 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_FILE_NOT_FOUND), $this->name . ' ' . $this->version, $this->currentPath));
134
135 return;
136 }
137 if (!is_dir($this->symlinkPath)) {
138 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_FILE_NOT_FOUND), $this->name . ' ' . $this->version, $this->symlinkPath));
139
140 return;
141 }
142 if (!is_file($this->bearsamppConf)) {
143 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_CONF_NOT_FOUND), $this->name . ' ' . $this->version, $this->bearsamppConf));
144
145 return;
146 }
147 if (!is_file($this->exe)) {
148 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_EXE_NOT_FOUND), $this->name . ' ' . $this->version, $this->exe));
149
150 return;
151 }
152 if ((empty($this->webRoot) && $this->webRoot !== '' || is_numeric($this->webRoot))) {
153 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_INVALID_PARAMETER), self::LOCAL_CFG_WEB_ROOT, $this->webRoot));
154
155 return;
156 }
157 if (empty($this->uiPort)) {
158 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_INVALID_PARAMETER), self::LOCAL_CFG_UI_PORT, $this->uiPort));
159
160 return;
161 }
162 if (empty($this->smtpPort)) {
163 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_INVALID_PARAMETER), self::LOCAL_CFG_SMTP_PORT, $this->smtpPort));
164
165 return;
166 }
167 if (empty($this->listen)) {
168 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_INVALID_PARAMETER), self::LOCAL_CFG_LISTEN, $this->listen));
169
170 return;
171 }
172
173 $nssm = new Nssm(self::SERVICE_NAME);
174 $nssm->setDisplayName(APP_TITLE . ' ' . $this->getName());
175 $nssm->setBinPath($this->exe);
176 $nssm->setParams(sprintf(self::SERVICE_PARAMS, $this->listen, $this->uiPort, $this->listen, $this->smtpPort, $this->webRoot));
177 $nssm->setStart(Nssm::SERVICE_DEMAND_START);
178 $nssm->setStdout(Path::getLogsPath() . '/mailpit.out.log');
179 $nssm->setStderr(Path::getLogsPath() . '/mailpit.err.log');
180
181 $this->service->setNssm($nssm);
182 }
183
189 protected function replaceAll($params)
190 {
191 $content = file_get_contents($this->bearsamppConf);
192
193 foreach ($params as $key => $value) {
194 $content = preg_replace('|' . $key . ' = .*|', $key . ' = ' . '"' . $value . '"', $content);
195 $this->bearsamppConfRaw[$key] = $value;
196 switch ($key) {
197 case self::LOCAL_CFG_UI_PORT:
198 $this->uiPort = intval($value);
199 break;
200 case self::LOCAL_CFG_SMTP_PORT:
201 $this->smtpPort = intval($value);
202 break;
203 }
204 }
205
206 file_put_contents($this->bearsamppConf, $content);
207
208 self::invalidateConfigCacheForPath($this->bearsamppConf);
209 }
210
216 public function rebuildConf()
217 {
218 global $bearsamppRegistry;
219
220 Log::trace("Starting rebuildConf for Mailpit service");
221 Log::trace("Checking if registry key exists for Mailpit service parameters");
222
223 $registryPath = 'SYSTEM\CurrentControlSet\Services\\' . self::SERVICE_NAME . '\Parameters';
224 Log::trace("Registry path: " . $registryPath);
225
226 $exists = $bearsamppRegistry->exists(
228 $registryPath,
230 );
231
232 if ($exists) {
233 Log::trace("Registry key exists, updating service parameters");
234
235 $serviceParams = sprintf(self::SERVICE_PARAMS, $this->listen, $this->uiPort, $this->listen, $this->smtpPort, $this->webRoot);
236 Log::trace("Service parameters: " . $serviceParams);
237
238 $result = $bearsamppRegistry->setExpandStringValue(
240 $registryPath,
242 $serviceParams
243 );
244
245 Log::trace("Registry update " . ($result ? "succeeded" : "failed"));
246
247 return $result;
248 }
249
250 Log::trace("Registry key does not exist for Mailpit service parameters");
251
252 return false;
253 }
254
264 public function changePort($port, $checkUsed = false, $wbProgressBar = null)
265 {
266 global $bearsamppWinbinder;
267
268 if (!Util::isValidPort($port)) {
269 Log::error($this->getName() . ' port not valid: ' . $port);
270
271 return false;
272 }
273
274 $port = intval($port);
275 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
276
277 $isPortInUse = ServiceHelper::isPortInUse($port);
278 if (!$checkUsed || $isPortInUse === false) {
279 // bearsampp.conf
280 $this->setSmtpPort($port);
281 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
282
283 // conf
284 $this->update();
285 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
286
287 return true;
288 }
289
290 Log::debug($this->getName() . ' port in used: ' . $port . ' - ' . $isPortInUse);
291
292 return $isPortInUse;
293 }
294
303 public function checkPort($port, $showWindow = false)
304 {
305 global $bearsamppLang, $bearsamppWinbinder;
306 $boxTitle = sprintf($bearsamppLang->getValue(Lang::CHECK_PORT_TITLE), $this->getName(), $port);
307
308 if (!Util::isValidPort($port)) {
309 Log::error($this->getName() . ' port not valid: ' . $port);
310
311 return false;
312 }
313
314 $headers = HttpClient::getHeaders($this->listen, $port);
315 if (!empty($headers)) {
316 if (UtilString::contains($headers[0], 'Mailpit')) {
317 Log::debug($this->getName() . ' port ' . $port . ' is used by: ' . str_replace('220 ', '', $headers[0]));
318 if ($showWindow) {
319 $bearsamppWinbinder->messageBoxInfo(
320 sprintf($bearsamppLang->getValue(Lang::PORT_USED_BY), $port, str_replace('220 ', '', $headers[0])),
321 $boxTitle
322 );
323 }
324
325 return true;
326 }
327 Log::debug($this->getName() . ' port ' . $port . ' is used by another application');
328 if ($showWindow) {
329 $bearsamppWinbinder->messageBoxWarning(
330 sprintf($bearsamppLang->getValue(Lang::PORT_NOT_USED_BY), $port),
331 $boxTitle
332 );
333 }
334 } else {
335 Log::debug($this->getName() . ' port ' . $port . ' is not used');
336 if ($showWindow) {
337 $bearsamppWinbinder->messageBoxError(
338 sprintf($bearsamppLang->getValue(Lang::PORT_NOT_USED), $port),
339 $boxTitle
340 );
341 }
342 }
343
344 return false;
345 }
346
355 public function switchVersion($version, $showWindow = false)
356 {
357 Log::debug('Switch ' . $this->name . ' version to ' . $version);
358
359 return $this->updateConfig($version, 0, $showWindow);
360 }
361
371 protected function updateConfig($version = null, $sub = 0, $showWindow = false)
372 {
373 global $bearsamppLang, $bearsamppWinbinder;
374
375 if (!$this->enable) {
376 return true;
377 }
378
379 $version = $version == null ? $this->version : $version;
380 Log::debug(($sub > 0 ? str_repeat(' ', 2 * $sub) : '') . 'Update ' . $this->name . ' ' . $version . ' config');
381
382 $boxTitle = sprintf($bearsamppLang->getValue(Lang::SWITCH_VERSION_TITLE), $this->getName(), $version);
383
384 $bearsamppConf = str_replace('mailpit' . $this->getVersion(), 'mailpit' . $version, $this->bearsamppConf);
385 if (!file_exists($bearsamppConf)) {
386 Log::error('bearsampp config files not found for ' . $this->getName() . ' ' . $version);
387 if ($showWindow) {
388 $bearsamppWinbinder->messageBoxError(
389 sprintf($bearsamppLang->getValue(Lang::BEARSAMPP_CONF_NOT_FOUND_ERROR), $this->getName() . ' ' . $version),
390 $boxTitle
391 );
392 }
393
394 return false;
395 }
396
397 $bearsamppConfRaw = parse_ini_file($bearsamppConf);
398 if ($bearsamppConfRaw === false || !isset($bearsamppConfRaw[self::ROOT_CFG_VERSION]) || $bearsamppConfRaw[self::ROOT_CFG_VERSION] != $version) {
399 Log::error('bearsampp config file malformed for ' . $this->getName() . ' ' . $version);
400 if ($showWindow) {
401 $bearsamppWinbinder->messageBoxError(
402 sprintf($bearsamppLang->getValue(Lang::BEARSAMPP_CONF_MALFORMED_ERROR), $this->getName() . ' ' . $version),
403 $boxTitle
404 );
405 }
406
407 return false;
408 }
409
410 // bearsampp.conf
411 $this->setVersion($version);
412
413 return true;
414 }
415
421 public function setVersion($version)
422 {
423 global $bearsamppConfig;
424 $this->version = $version;
425 $bearsamppConfig->replace(self::ROOT_CFG_VERSION, $version);
426 $this->reload();
427
428 // Rebuild NSSM configuration after version change
429 Log::trace('Rebuilding Mailpit NSSM configuration after version switch');
430 $this->rebuildConf();
431 }
432
438 public function getService()
439 {
440 return $this->service;
441 }
442
449 public function setEnable($enabled, $showWindow = false)
450 {
451 global $bearsamppConfig, $bearsamppLang, $bearsamppWinbinder;
452
453 if ($enabled == Config::ENABLED && !is_dir($this->currentPath)) {
454 Log::debug($this->getName() . ' cannot be enabled because bundle ' . $this->getVersion() . ' does not exist in ' . $this->currentPath);
455 if ($showWindow) {
456 $bearsamppWinbinder->messageBoxError(
457 sprintf($bearsamppLang->getValue(Lang::ENABLE_BUNDLE_NOT_EXIST), $this->getName(), $this->getVersion(), $this->currentPath),
458 sprintf($bearsamppLang->getValue(Lang::ENABLE_TITLE), $this->getName())
459 );
460 }
461 $enabled = Config::DISABLED;
462 }
463
464 Log::info($this->getName() . ' switched to ' . ($enabled == Config::ENABLED ? 'enabled' : 'disabled'));
465 $this->enable = $enabled == Config::ENABLED;
466 $bearsamppConfig->replace(self::ROOT_CFG_ENABLE, $enabled);
467
468 $this->reload();
469 if ($this->enable) {
470 ServiceHelper::installService($this, $this->smtpPort, null, $showWindow);
471 } else {
472 ServiceHelper::removeService($this->service, $this->name);
473 }
474 }
475
481 public function getLog()
482 {
483 return $this->log;
484 }
485
491 public function getExe()
492 {
493 return $this->exe;
494 }
495
501 public function getWebRoot()
502 {
503 return $this->webRoot;
504 }
505
511 public function setWebRoot($webRoot)
512 {
513 $this->replace(self::LOCAL_CFG_WEB_ROOT, $webRoot);
514 }
515
521 public function getUiPort()
522 {
523 return $this->uiPort;
524 }
525
531 public function setUiPort($uiPort)
532 {
533 $this->replace(self::LOCAL_CFG_UI_PORT, $uiPort);
534 }
535
541 public function getSmtpPort()
542 {
543 return $this->smtpPort;
544 }
545
551 public function setSmtpPort($smtpPort)
552 {
553 $this->replace(self::LOCAL_CFG_SMTP_PORT, $smtpPort);
554 }
555
561 public function getListen()
562 {
563 return $this->listen;
564 }
565
571 public function setListen()
572 {
573 return $this->replace(self::LOCAL_CFG_LISTEN, $this->listen);
574 }
575}
576
$result
global $bearsamppLang
global $bearsamppRoot
$port
setWebRoot($webRoot)
updateConfig($version=null, $sub=0, $showWindow=false)
const LOCAL_CFG_SMTP_PORT
switchVersion($version, $showWindow=false)
setVersion($version)
checkPort($port, $showWindow=false)
changePort($port, $checkUsed=false, $wbProgressBar=null)
setEnable($enabled, $showWindow=false)
setSmtpPort($smtpPort)
reload($id=null, $type=null)
__construct($id, $type)
const DISABLED
const ENABLED
static getHeaders($host, $port, $ssl=false)
const ENABLE_BUNDLE_NOT_EXIST
const BEARSAMPP_CONF_MALFORMED_ERROR
const ERROR_EXE_NOT_FOUND
const ERROR_CONF_NOT_FOUND
const BEARSAMPP_CONF_NOT_FOUND_ERROR
const PORT_NOT_USED
const ERROR_INVALID_PARAMETER
const ENABLE_TITLE
const PORT_NOT_USED_BY
const SWITCH_VERSION_TITLE
const CHECK_PORT_TITLE
const PORT_USED_BY
const ERROR_FILE_NOT_FOUND
const MAILPIT
static info($data, $file=null)
static debug($data, $file=null)
static reloadClass($classInstance)
static trace($data, $file=null)
static error($data, $file=null)
static initClass($classInstance)
update($sub=0, $showWindow=false)
static invalidateConfigCacheForPath($sourcePath)
replace($key, $value)
const INFO_APP_PARAMETERS
const SERVICE_DEMAND_START
static getLogsPath($aetrayPath=false)
const HKEY_LOCAL_MACHINE
static installService($bin, $port, $syntaxCheckCmd, $showWindow=false)
static removeService($service, $name)
static isPortInUse($port)
static isValidPort($port)
static contains($string, $search)
global $bearsamppConfig
Definition homepage.php:46
const APP_TITLE
Definition root.php:19