Skip to content

Commit a4489ac

Browse files
committed
WIP but mostly working again
1 parent c26cbf3 commit a4489ac

File tree

17 files changed

+1266
-1189
lines changed

17 files changed

+1266
-1189
lines changed

public/index.php

+2-12
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
11
<?php
22

33
declare(strict_types=1);
4-
// Created: 20150101 - Updated: 20250203
4+
// Created: 20150101 - Updated: 20250205
55
// Copyright (C) 2015-2025 Mark Constable <markc@renta.net> (AGPL-3.0)
66

77
namespace HCP;
88

9-
const DBG = true;
10-
119
require_once __DIR__ . '/../vendor/autoload.php';
1210

13-
// Initialize with correct self path and hostname
14-
$self = str_replace('index.php', '', $_SERVER['PHP_SELF']);
15-
$host = getenv('HOSTNAME') ?: '';
16-
echo new Init(new Config($self, $host));
17-
18-
function dbg(mixed $var = null): void
19-
{
20-
error_log(var_export($var, true));
21-
}
11+
echo new Init(new Config());

src/Config.php

+18-20
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22

33
declare(strict_types=1);
4-
// Created: 20150101 - Updated: 20250203
4+
// Created: 20250101 - Updated: 20250204
55
// Copyright (C) 2015-2025 Mark Constable <markc@renta.net> (AGPL-3.0)
66

77
namespace HCP;
@@ -17,7 +17,7 @@ enum ACL: int
1717
case Anonymous = 9;
1818
}
1919

20-
readonly class Config
20+
class Config
2121
{
2222
public string $email;
2323
public string $admpw;
@@ -33,20 +33,17 @@ enum ACL: int
3333
public array $dns;
3434
public string $acl;
3535

36-
public function __construct(
37-
string $self = '/',
38-
string $host = ''
39-
)
36+
public function __construct()
4037
{
4138
Util::elog(__METHOD__);
4239

4340
$this->email = 'admin@example.com';
4441
$this->admpw = 'admin123';
4542
$this->file = '../src/.ht_conf.php';
4643
$this->hash = 'SHA512-CRYPT';
47-
$this->host = $host;
44+
$this->host = getenv('HOSTNAME') ?: '';
4845
$this->perp = 25;
49-
$this->self = $self;
46+
$this->self = str_replace('index.php', '', $_SERVER['PHP_SELF']);
5047
$this->db = [
5148
'host' => '127.0.0.1',
5249
'name' => 'sysadm',
@@ -72,24 +69,25 @@ public function __construct(
7269
['Phpmyadmin', 'phpmyadmin/', 'bi bi-globe'],
7370
], 'bi bi-list'],
7471
['Admin', [
75-
['Accounts', '?o=Accounts', 'bi bi-people'],
76-
['Vhosts', '?o=Vhosts', 'bi bi-globe'],
77-
['Mailboxes', '?o=Vmails', 'bi bi-envelope'],
78-
['Aliases', '?o=Valias', 'bi bi-envelope-fill'],
79-
['DKIM', '?o=Dkim', 'bi bi-person-vcard'],
80-
['Domains', '?o=Domains', 'bi bi-server'],
72+
['Accounts', '?plugin=Accounts', 'bi bi-people'],
73+
['Vhosts', '?plugin=Vhosts', 'bi bi-globe'],
74+
['Mailboxes', '?plugin=Vmails', 'bi bi-envelope'],
75+
['Aliases', '?plugin=Valias', 'bi bi-envelope-fill'],
76+
['DKIM', '?plugin=Dkim', 'bi bi-person-vcard'],
77+
['Domains', '?plugin=Domains', 'bi bi-server'],
8178
], 'bi bi-gear-fill'],
8279
['Stats', [
83-
['Sys Info', '?o=InfoSys', 'bi bi-speedometer'],
84-
['Processes', '?o=Processes', 'bi bi-diagram-2'],
85-
['Mail Info', '?o=Infomail', 'bi bi-envelope-fill'],
86-
['Mail Graph', '?o=Mailgraph', 'bi bi-envelope'],
80+
['Sys Info', '?plugin=InfoSys', 'bi bi-speedometer'],
81+
['Processes', '?plugin=Processes', 'bi bi-diagram-2'],
82+
['Mail Info', '?plugin=Infomail', 'bi bi-envelope-fill'],
83+
['Mail Graph', '?plugin=Mailgraph', 'bi bi-envelope'],
8784
], 'bi bi-graph-up'],
85+
['Example', '?plugin=Example', 'bi bi-globe']
8886
],
8987
];
9088
$this->nav2 = [
91-
['TopNav', '?t=TopNav', 'bi bi-list'],
92-
['SideBar', '?t=SideBar', 'bi bi-layout-sidebar']
89+
['TopNav', '?theme=TopNav', 'bi bi-list'],
90+
['SideBar', '?theme=SideBar', 'bi bi-layout-sidebar']
9391
];
9492
$this->nav3 = [];
9593
$this->dns = [

src/Controller.php

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
// Created: 20150101 - Updated: 20250204
5+
// Copyright (C) 2015-2025 Mark Constable <markc@renta.net> (AGPL-3.0)
6+
7+
namespace HCP;
8+
9+
class Controller
10+
{
11+
public array $input = [
12+
'api' => '',
13+
'domain' => '',
14+
'format' => 'html',
15+
'item' => null,
16+
'log' => '',
17+
'action' => 'list',
18+
'plugin' => 'Home',
19+
'remote' => 'local',
20+
'theme' => 'TopNav',
21+
'xhr' => '',
22+
];
23+
24+
public array $output = [
25+
'doc' => 'NetServa HCP',
26+
'css' => '',
27+
'log' => '',
28+
'nav1' => '',
29+
'nav2' => '',
30+
'nav3' => '',
31+
'head' => 'NetServa HCP',
32+
'main' => 'Error: missing page!',
33+
'foot' => 'Copyright (C) 2015-2025 Mark Constable (AGPL-3.0)',
34+
'js' => '',
35+
];
36+
37+
public ?object $pluginView = null;
38+
public ?object $currentTheme = null;
39+
public Theme $baseTheme;
40+
41+
public function __construct(
42+
public readonly Config $config
43+
)
44+
{
45+
Util::elog(__METHOD__);
46+
$this->initSession();
47+
$this->input = Util::esc($this->input);
48+
$this->initCsrf();
49+
$this->baseTheme = new Theme($this);
50+
}
51+
52+
protected function initSession(): void
53+
{
54+
if (session_status() !== PHP_SESSION_ACTIVE)
55+
{
56+
session_start();
57+
}
58+
59+
// Store session values
60+
Util::ses('plugin');
61+
Util::ses('action');
62+
Util::ses('log');
63+
}
64+
65+
protected function initCsrf(): void
66+
{
67+
if (!isset($_SESSION['csrf_token']))
68+
{
69+
$_SESSION['csrf_token'] = Util::random_token(32);
70+
}
71+
}
72+
73+
public function handlePluginAction(string $pluginClass): array
74+
{
75+
try
76+
{
77+
$plugin = new $pluginClass($this);
78+
$action = $this->input['action'];
79+
return $plugin->$action();
80+
}
81+
catch (\RuntimeException $e)
82+
{
83+
// Access denied or other error - already redirected in Plugin constructor
84+
return [
85+
'status' => 'error',
86+
'message' => $e->getMessage()
87+
];
88+
}
89+
}
90+
91+
public function html(): string
92+
{
93+
if ($this->input['xhr'])
94+
{
95+
return $this->renderXhr();
96+
}
97+
98+
// Use plugin view's html() method if available, otherwise fall back to theme rendering
99+
if ($this->pluginView && method_exists($this->pluginView, 'html'))
100+
{
101+
return $this->pluginView->html($this->output);
102+
}
103+
104+
// Use current theme if available, otherwise fall back to base theme
105+
return $this->currentTheme
106+
? $this->currentTheme->html($this->output)
107+
: $this->baseTheme->html();
108+
}
109+
110+
protected function renderXhr(): string
111+
{
112+
$content = $this->output[$this->input['xhr']]
113+
?? $this->output['main']
114+
?? '';
115+
116+
if (!$content)
117+
{
118+
return "Error: Content is empty";
119+
}
120+
121+
$contentType = match ($this->input['format'])
122+
{
123+
'json' => 'application/json',
124+
'text' => 'text/plain',
125+
'markdown' => 'text/markdown',
126+
default => 'text/html'
127+
};
128+
header("Content-Type: $contentType");
129+
130+
return match ($this->input['format'])
131+
{
132+
'json' => json_encode($content, JSON_PRETTY_PRINT),
133+
'text' => preg_replace('/^\h*\v+/m', '', strip_tags($content)),
134+
'markdown' => preg_replace('/^\h*\v+/m', '', $content),
135+
default => $content
136+
};
137+
}
138+
}

0 commit comments

Comments
 (0)