├── home
└── api.ospanel
│ ├── public_html
│ ├── favicon.ico
│ ├── .htaccess
│ └── index.php
│ ├── .osp
│ └── project.ini
│ └── core
│ ├── Controllers
│ ├── Controller.php
│ ├── ModuleController.php
│ ├── SettingsController.php
│ ├── IndexController.php
│ ├── FilesController.php
│ └── SitesController.php
│ ├── Traits
│ └── Makeable.php
│ ├── Services
│ ├── Http.php
│ ├── Api.php
│ ├── Modules.php
│ ├── IniFile.php
│ └── Domains.php
│ ├── DTO
│ ├── Module.php
│ └── Domain.php
│ ├── Router
│ ├── Response.php
│ ├── Router.php
│ └── Request.php
│ └── functions.php
├── system
└── templates
│ ├── data.php.template
│ ├── dev_index.html.template
│ └── index.html.template
├── LICENSE
├── README.md
└── config
└── program.ini
/home/api.ospanel/public_html/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/delphinpro/oscp/HEAD/home/api.ospanel/public_html/favicon.ico
--------------------------------------------------------------------------------
/home/api.ospanel/.osp/project.ini:
--------------------------------------------------------------------------------
1 | [api.ospanel]
2 | ;enabled = on
3 | php_engine = PHP-8.1
4 | public_dir = {base_dir}\public_html
5 | ssl = off
6 |
--------------------------------------------------------------------------------
/home/api.ospanel/public_html/.htaccess:
--------------------------------------------------------------------------------
1 |
'; 73 | dump($var); 74 | echo ''; 75 | } 76 | exit; 77 | } 78 | 79 | function templatePath(?string $path): ?string 80 | { 81 | if ($path === null) return null; 82 | 83 | return str_replace(str_replace('/', '\\', ROOT_DIR), '{root_dir}', $path); 84 | } 85 | -------------------------------------------------------------------------------- /home/api.ospanel/core/Router/Router.php: -------------------------------------------------------------------------------- 1 | request = $request; 28 | } 29 | 30 | public function __call($name, $args) 31 | { 32 | [$route, $method] = $args; 33 | 34 | if (!in_array(strtoupper($name), $this->supportedHttpMethods, true)) { 35 | $this->invalidMethodHandler($name); 36 | } 37 | 38 | $this->routes[strtolower($name)][$this->formatRoute($route)] = $method; 39 | } 40 | 41 | /** 42 | * Resolves a route 43 | */ 44 | public function resolve(): void 45 | { 46 | $methodDictionary = $this->routes[strtolower($this->request->requestMethod)]; 47 | $formattedRoute = $this->formatRoute($this->request->requestUri); 48 | $callable = $methodDictionary[$formattedRoute] ?? null; 49 | 50 | switch (true) { 51 | case is_string($callable): 52 | $controller = new $callable(); 53 | echo $controller($this->request); 54 | break; 55 | 56 | case is_array($callable) && count($callable) === 2: 57 | $controller = new $callable[0](); 58 | $method = $callable[1]; 59 | echo $controller->$method($this->request); 60 | break; 61 | 62 | case is_callable($callable): 63 | echo $callable($this->request); 64 | break; 65 | 66 | default: 67 | $this->defaultRequestHandler(); 68 | } 69 | } 70 | 71 | private function invalidMethodHandler(string $method): void 72 | { 73 | echo Response::json() 74 | ->headers(["{$this->request->serverProtocol} 405 Method Not Allowed"]) 75 | ->message('405 Method Not Allowed: '.$method) 76 | ->status(405); 77 | } 78 | 79 | /** 80 | * Removes trailing forward slashes from the right of the route. 81 | */ 82 | private function formatRoute(string $route): string 83 | { 84 | $result = trim($route, '/'); 85 | if ($result === '') { 86 | return '/'; 87 | } 88 | 89 | return $result; 90 | } 91 | 92 | private function defaultRequestHandler(): void 93 | { 94 | echo Response::json() 95 | ->headers(["{$this->request->serverProtocol} 404 Not Found"]) 96 | ->message('404 Not Found: '.$this->request->requestMethod.' '.$this->request->requestUri) 97 | ->status(404); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /home/api.ospanel/core/Router/Request.php: -------------------------------------------------------------------------------- 1 | bootstrapSelf(); 44 | $this->parseBody(); 45 | } 46 | 47 | public function __get(string $name) 48 | { 49 | return $this->requestVariables[$name] ?? null; 50 | } 51 | 52 | public function get(string $key, string|int|bool|null $default = null): string|int|bool|null 53 | { 54 | return $this->queryParams[$key] ?? $default; 55 | } 56 | 57 | public function input(string $key, string|int|bool|null $default = null): string|int|bool|null 58 | { 59 | return $this->body[$key] ?? $default; 60 | } 61 | 62 | public function all(): array 63 | { 64 | return $this->body; 65 | } 66 | 67 | public function except(array $keys): array 68 | { 69 | return array_filter( 70 | $this->body, 71 | static fn($key) => !in_array($key, $keys, true), 72 | ARRAY_FILTER_USE_KEY 73 | ); 74 | } 75 | 76 | public function only(array $keys): array 77 | { 78 | return array_filter( 79 | $this->body, 80 | static fn($key) => in_array($key, $keys, true), 81 | ARRAY_FILTER_USE_KEY 82 | ); 83 | } 84 | 85 | public function getBody(): ?array 86 | { 87 | return $this->body; 88 | } 89 | 90 | public function toArray(): array 91 | { 92 | return $this->requestVariables; 93 | } 94 | 95 | private function bootstrapSelf(): void 96 | { 97 | foreach ($_SERVER as $key => $value) { 98 | $this->requestVariables[$this->toCamelCase($key)] = $value; 99 | } 100 | [$requestUri] = explode('?', $this->requestUri); 101 | $this->requestVariables['requestUri'] = $requestUri; 102 | $this->queryParams = $_GET; 103 | } 104 | 105 | private function parseBody(): void 106 | { 107 | if ($this->requestMethod === "POST") { 108 | $this->body = []; 109 | foreach ($_POST as $key => $value) { 110 | $this->body[$key] = $this->castInputValue($value); 111 | } 112 | } 113 | } 114 | 115 | private function castInputValue(mixed $value) 116 | { 117 | if (is_array($value)) { 118 | return array_map(fn($value) => $this->castInputValue($value), $value); 119 | } 120 | 121 | return match (true) { 122 | $value === 'true' => true, 123 | $value === 'false' => false, 124 | $value === 'null' => null, 125 | is_numeric($value) => (int)$value, 126 | default => $value, 127 | }; 128 | 129 | } 130 | 131 | private function toCamelCase($string): array|string 132 | { 133 | $result = strtolower($string); 134 | 135 | preg_match_all('/_[a-z]/', $result, $matches); 136 | 137 | foreach ($matches[0] as $match) { 138 | $c = str_replace('_', '', strtoupper($match)); 139 | $result = str_replace($match, $c, $result); 140 | } 141 | 142 | return $result; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /config/program.ini: -------------------------------------------------------------------------------- 1 | [main] 2 | 3 | api_domain = ospanel 4 | api_ip = 127.127.127.127 5 | api_port = 80 6 | clear_dns_cache = on 7 | lang = auto 8 | log_max_filesize = 1 9 | projects_search_depth = 3 10 | projects_search_path = {root_dir}\home 11 | task_scheduler = on 12 | terminal_ansi_fix = auto 13 | update_check = on 14 | use_hosts_file = on 15 | use_win_terminal = off 16 | 17 | [menu] 18 | 19 | do_not_group_single_item = on 20 | show_icons = on 21 | show_addons = on 22 | show_addons_in_groups = on 23 | show_addons_in_submenu = off 24 | show_modules = on 25 | show_modules_in_groups = on 26 | show_modules_in_submenu = on 27 | show_projects = on 28 | show_projects_in_groups = off 29 | show_projects_in_submenu = off 30 | show_tray_icon = on 31 | show_hr_after_addons = on 32 | show_hr_after_modules = on 33 | show_hr_after_projects = on 34 | 35 | [projects] 36 | 37 | aliases = www 38 | enabled = on 39 | environment = System 40 | ip = 41 | nginx_engine = 42 | node_engine = 43 | php_engine = 44 | project_dir = {base_dir} 45 | project_url = https://{host_decoded} 46 | public_dir = {base_dir} 47 | ssl = on 48 | ssl_cert_file = 49 | ssl_key_file = 50 | start_command = 51 | terminal_codepage = 65001 52 | 53 | [modules] 54 | 55 | allowed_system_env_vars = allusersprofile appdata commonprogramfiles commonprogramfiles(x86) commonprogramw6432 computername comspec driverdata homedrive homepath localappdata logonserver number_of_processors os path pathext processor_architecture processor_identifier processor_level processor_revision programdata programfiles programfiles(x86) programw6432 psmodulepath public sessionname systemdrive systemroot temp tmp userdomain userdomain_roamingprofile username userprofile windir 56 | log_max_filesize = 1 57 | log_write_title = on 58 | max_probation_fails = 1 59 | max_shutdown_time = 0 60 | probation = 30 61 | silent_mode = on 62 | terminal_codepage = 65001 63 | time_zone = system 64 | used_addons_environment = 65 | 66 | [smtp] 67 | 68 | open_email_after_saving = off 69 | saved_email_extension = .eml 70 | smtp_port = 25 71 | smtp_server = on 72 | 73 | [environment] 74 | 75 | CURL_CA_BUNDLE = {root_dir}\data\ssl\cacert.pem 76 | HOME = {root_dir}\user 77 | HOMEDRIVE = {root_drive} 78 | HOMEPATH = {root_path}\user 79 | PATH = {root_dir}\bin;%SYSTEMROOT%\system32;%SYSTEMROOT%;%SYSTEMROOT%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0 80 | TERM = dumb 81 | TEMP = {root_dir}\temp 82 | TMP = {root_dir}\temp 83 | 84 | [index.html] 85 | 86 | destination = {root_dir}\system\public_html\index.html 87 | directory_separator = / 88 | enabled = on 89 | encoding = UTF8 90 | 91 | [default.html] 92 | 93 | destination = {root_dir}\system\public_html\default\index.html 94 | directory_separator = / 95 | enabled = on 96 | encoding = UTF8 97 | 98 | [osp.bat] 99 | 100 | comment = :: 101 | destination = {root_dir}\bin\osp.bat 102 | directory_separator = \ 103 | enabled = on 104 | encoding = UTF8 105 | 106 | [tail.bat] 107 | 108 | comment = :: 109 | destination = {root_dir}\system\bin\tail.bat 110 | directory_separator = \ 111 | enabled = on 112 | encoding = UTF8 113 | 114 | ; Open Server Web Panel by delphinpro 115 | 116 | [index.html.template] 117 | 118 | destination = {root_dir}\system\public_html\index.html 119 | directory_separator = / 120 | enabled = on 121 | encoding = UTF8 122 | 123 | [data.php.template] 124 | 125 | destination = {root_dir}\home\api.ospanel\data.php 126 | directory_separator = / 127 | enabled = on 128 | encoding = UTF8 129 | 130 | [dev_index.html.template] 131 | 132 | destination = {root_dir}\home\api.ospanel\public\index.html 133 | directory_separator = / 134 | enabled = on 135 | encoding = UTF8 136 | -------------------------------------------------------------------------------- /home/api.ospanel/core/Controllers/SitesController.php: -------------------------------------------------------------------------------- 1 | domains = Domains::make(); 24 | } 25 | 26 | public function index(): Response 27 | { 28 | $settings = IniFile::open('config/program.ini')->get('menu'); 29 | $isGroupDomains = $settings['show_projects_in_groups'] ?? false; 30 | 31 | $sites = $isGroupDomains 32 | ? $this->domains->toGroups() 33 | : $this->domains->toArray(); 34 | 35 | return Response::json([ 36 | 'grouped' => $isGroupDomains, 37 | 'sites' => $sites, 38 | ]); 39 | } 40 | 41 | public function defaults(): Response 42 | { 43 | return Response::json( 44 | Domain::getDefaults() 45 | ); 46 | } 47 | 48 | public function store(Request $request): Response 49 | { 50 | $host = $request->input('host'); 51 | $data = $request->only(Domain::$params); 52 | 53 | if ($this->domains->has($host)) { 54 | return Response::json()->status(500)->message("Хост
$host уже существует");
55 | }
56 |
57 | $baseDir = $data['base_dir'];
58 | $ospDir = $baseDir.'/.osp';
59 |
60 | if (!is_dir($ospDir) && !mkdir($ospDir, true) && !is_dir($ospDir)) {
61 | return Response::json()->status(500)->message("Не удалось создать каталог $ospDir");
62 | }
63 |
64 | $domain = Domain::create($data);
65 |
66 | $domain->save();
67 |
68 | return Response::json([
69 | ])->message('Saved');
70 | }
71 |
72 | public function edit(Request $request): Response
73 | {
74 | $host = $request->get('host');
75 |
76 | if (!$this->domains->has($host)) {
77 | return Response::json()->status(404)->message('Хост не найден');
78 | }
79 |
80 | $site = $this->domains->get($host)->toArray();
81 |
82 | return Response::json([
83 | 'host' => $host,
84 | 'site' => $site,
85 | ]);
86 | }
87 |
88 | public function save(Request $request): Response
89 | {
90 | $oldHost = $request->input('old_host');
91 | $host = $request->input('host');
92 |
93 | $data = $request->only(Domain::$params);
94 |
95 | if ($oldHost !== $host && $this->domains->has($host)) {
96 | return Response::json()->status(500)->message("Хост $host уже существует");
97 | }
98 |
99 | $this->domains->get($oldHost)->delete();
100 | Domain::create($data)->save();
101 |
102 | return Response::json([
103 | ])->message('Saved');
104 | }
105 |
106 | public function delete(Request $request): Response
107 | {
108 | try {
109 | $host = $request->input('host');
110 |
111 | if (!$this->domains->has($host)) {
112 | return Response::json()->status(404)->message('Хост не найден');
113 | }
114 |
115 | $this->domains->get($host)->delete();
116 |
117 | return Response::json()->message('Сайт удалён');
118 |
119 | } catch (\Exception $e) {
120 |
121 | return Response::json()
122 | ->status(500)
123 | ->message($e->getMessage());
124 |
125 | }
126 | }
127 |
128 | public function openConsole(Request $request): Response
129 | {
130 | $host = $request->input('host');
131 |
132 | Http::apiCall('cli/'.$host);
133 |
134 | // $file = ROOT_DIR.'/bin/osp__exec.bat';
135 | // file_put_contents($file, "osp project $host".PHP_EOL);
136 | //
137 | // $cmd = str_replace('/', DIRECTORY_SEPARATOR, $file);
138 | //
139 | // pclose(popen("start $cmd", 'r'));
140 |
141 | return Response::json();
142 | }
143 |
144 | private function updateDomain(string $host, array $data, ?string $oldHost = null): Response
145 | {
146 | try {
147 |
148 | if (!$this->domains->has($oldHost ?? $host)) {
149 | throw new \RuntimeException('Ошибка: Хост ['.htmlspecialchars($oldHost ?? $host).'] отсутствует.');
150 | }
151 |
152 | $this->domains
153 | ->update($oldHost, $data)
154 | ->save();
155 |
156 | return Response::json()->message('Сайт сохранён');
157 |
158 | } catch (\Exception $e) {
159 |
160 | return Response::json()
161 | ->status(500)
162 | ->message($e->getMessage());
163 |
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/home/api.ospanel/core/Services/IniFile.php:
--------------------------------------------------------------------------------
1 | filename = $absolute
19 | ? $filename
20 | : ROOT_DIR.'/'.ltrim($filename, '/');
21 | }
22 |
23 | public static function make(string $filename, bool $absolute = false): IniFile
24 | {
25 | return new self($filename, $absolute);
26 | }
27 |
28 | public static function open(string $filename, bool $absolute = false): IniFile
29 | {
30 | return (new self($filename, $absolute))
31 | ->read();
32 | }
33 |
34 | public static function castValue(string $value): float|bool|int|string|null
35 | {
36 | if (in_array(strtolower($value), ['on', 'true', 'yes'])) {
37 | return true;
38 | }
39 | if (in_array(strtolower($value), ['off', 'false', 'no'])) {
40 | return false;
41 | }
42 | if (strtolower($value) === 'null') {
43 | return null;
44 | }
45 | if (is_numeric($value)) {
46 | if (str_contains($value, '.')) {
47 | return (float)$value;
48 | }
49 |
50 | return (int)$value;
51 | }
52 |
53 | return $value;
54 | }
55 |
56 | public static function iniValue(float|bool|int|string|null $value): string
57 | {
58 | return match (true) {
59 | is_bool($value) => $value ? 'on' : 'off',
60 | is_null($value) => 'null',
61 | is_string($value) => str_replace('\\\\', '\\', $value),
62 | default => (string)$value
63 | };
64 | }
65 |
66 | public function get(string $section = null): array
67 | {
68 | return $section ? $this->data[$section] ?? [] : $this->data;
69 | }
70 |
71 | public function set(array $data): IniFile
72 | {
73 | $this->data = $data;
74 |
75 | return $this;
76 | }
77 |
78 | public function read(): IniFile
79 | {
80 | if (!file_exists($this->filename)) {
81 | return $this;
82 | }
83 |
84 | $iniString = implode(PHP_EOL,
85 | array_map(static function ($s) {
86 | $s = trim($s);
87 | if (!str_contains($s, '=')) return $s;
88 | [$k, $v] = explode('=', $s);
89 | $v = trim($v);
90 |
91 | if (str_contains($v, ';')) $v = '"'.$v.'"';
92 |
93 | return $k.' = '.$v;
94 | }, file($this->filename))
95 | );
96 |
97 | $iniSections = parse_ini_string($iniString, true, INI_SCANNER_RAW);
98 |
99 | $this->data = [];
100 |
101 | foreach ($iniSections as $section => $params) {
102 | $this->data[$section] = [];
103 | foreach ($params as $key => $value) {
104 | $this->data[$section][$key] = self::castValue($value);
105 | }
106 | }
107 |
108 | return $this;
109 | }
110 |
111 | public function write(bool $totalAlign = true): void
112 | {
113 | $ini = '';
114 |
115 | if (!empty($this->data)) {
116 | $length = $this->maxLengthKey($this->data);
117 |
118 | if ($totalAlign) {
119 | $lengths = [];
120 | foreach ($this->data as $k => $item) {
121 | if (is_array($item)) {
122 | $lengths[] = $this->maxLengthKey($item);
123 | } else {
124 | $lengths[] = strlen($k);
125 | }
126 | }
127 | $length = max($lengths);
128 | }
129 |
130 | foreach ($this->data as $k => $item) {
131 | if (is_array($item)) {
132 | $ini .= PHP_EOL.'['.$k.']'.PHP_EOL.PHP_EOL;
133 | $len = $totalAlign ? $length : max($this->maxLengthKey($item), $length);
134 | foreach ($item as $key => $value) {
135 | $ini .= $key.str_repeat(' ', $len - strlen($key)).' = '.self::iniValue($value).PHP_EOL;
136 | }
137 | } else {
138 | $ini .= $k.str_repeat(' ', $length - strlen($k)).' = '.self::iniValue($item).PHP_EOL;
139 | }
140 | }
141 | }
142 |
143 | file_put_contents($this->filename, ltrim($ini));
144 | }
145 |
146 | public function update(string $section, array $data): IniFile
147 | {
148 | if (!array_key_exists($section, $this->data)) {
149 | $this->data[$section] = [];
150 | }
151 | foreach ($data as $key => $value) {
152 | $this->data[$section][$key] = $value;
153 | }
154 |
155 | return $this;
156 | }
157 |
158 | public function replace(string $section, array $data): IniFile
159 | {
160 | $this->data[$section] = $data;
161 |
162 | return $this;
163 | }
164 |
165 | public function delete(string $section): IniFile
166 | {
167 | unset($this->data[$section]);
168 |
169 | return $this;
170 | }
171 |
172 | public function save(): void
173 | {
174 | $this->write();
175 | }
176 |
177 | private function maxLengthKey(array $array): int
178 | {
179 | return array_reduce(
180 | array_keys($array),
181 | static fn($carry, $key) => max($carry, strlen($key)),
182 | 0
183 | );
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/home/api.ospanel/core/Services/Domains.php:
--------------------------------------------------------------------------------
1 | readConfig();
27 | }
28 |
29 | /**
30 | * @return \OpenServer\DTO\Domain[]
31 | */
32 | public function getAll(): array
33 | {
34 | return $this->domains;
35 | }
36 |
37 | /**
38 | * @return \OpenServer\DTO\Domain[]
39 | */
40 | public function toArray(): array
41 | {
42 | $domains = $this->filterDomains();
43 |
44 | usort($domains, static function (Domain $a, Domain $b) {
45 | if ($a->host === $b->host) return 0;
46 |
47 | return $a->host < $b->host ? -1 : 1;
48 | });
49 |
50 | return array_map(static fn(Domain $d) => $d->toArray(), $domains);
51 | }
52 |
53 | public function toGroups(): array
54 | {
55 | $result = [];
56 |
57 | foreach ($this->filterDomains() as $item) {
58 | if (str_contains($item->host, '.')) {
59 | [, $group] = explode('.', $item->host, 2);
60 | } else {
61 | $group = TLD;
62 | }
63 |
64 | if (!array_key_exists($group, $result)) $result[$group] = [];
65 |
66 | $result[$group][$item->host] = $item;
67 | }
68 |
69 | $result = $this->groupSubDomains($result);
70 |
71 | ksort($result);
72 |
73 | return $result;
74 | }
75 |
76 | public function has(string $host): bool
77 | {
78 | return array_key_exists($host, $this->domains);
79 | }
80 |
81 | public function get(string $host): Domain
82 | {
83 | return $this->domains[$host];
84 | }
85 |
86 | public function update(string $oldHost, array $data): static
87 | {
88 | $domain = new Domain($this->domains[$oldHost]->getRawData());
89 | $domain->update($data);
90 | unset($this->domains[$oldHost]);
91 | $this->domains[$domain->host] = $domain;
92 |
93 | return $this;
94 | }
95 |
96 | public function save(): void
97 | {
98 | $iniData = [];
99 |
100 | foreach ($this->domains as $domain) {
101 | $iniData[$domain->host] = array_filter(
102 | $domain->getRawData(),
103 | static fn($value, $key) => $key !== 'host' && $value !== null,
104 | ARRAY_FILTER_USE_BOTH
105 | );
106 | }
107 |
108 | IniFile::open('config/domains.ini')->set($iniData)->write(20);
109 | }
110 |
111 | public function count(): int
112 | {
113 | return count($this->filterDomains());
114 | }
115 |
116 | public function countDisabled(): int
117 | {
118 | return count(
119 | array_filter($this->filterDomains(), static function (Domain $d) {
120 | return $d->enabled === false;
121 | })
122 | );
123 | }
124 |
125 | public function countProblems(): int
126 | {
127 | return count(
128 | array_filter($this->filterDomains(), static function (Domain $d) {
129 | return !$d->isAvailable() || !$d->isValidRoot();
130 | })
131 | );
132 | }
133 |
134 | /**
135 | * @return \OpenServer\DTO\Domain[]
136 | */
137 | private function filterDomains(): array
138 | {
139 | return array_filter($this->domains, static fn(Domain $d) => $d->host !== API_DOMAIN);
140 | }
141 |
142 | private function groupSubdomains(array $input): array
143 | {
144 | $result = [];
145 | foreach ($input as $groupName => $group) {
146 | $parts = explode('.', $groupName);
147 | $result[$groupName] = $group;
148 | if ((count($parts) > 1) && isset($input[$parts[1]][$groupName])) {
149 | $result[$groupName][$groupName] = $input[$parts[1]][$groupName] ?? null;
150 | }
151 | }
152 |
153 | foreach ($result as $groupName => &$group) {
154 | foreach ($group as $domainName => $domain) {
155 | if (array_key_exists($domainName, $result) && $domainName !== $groupName) unset($group[$domainName]);
156 | if ($domainName === TLD) unset($group[$domainName]);
157 | }
158 | }
159 | unset($group);
160 |
161 |
162 | foreach ($result as $name => $group) {
163 | usort($group, static function (?Domain $a, ?Domain $b) {
164 | if (!$a || !$b) return 0;
165 | $d1 = $a->host;
166 | $d2 = $b->host;
167 | $l1 = count(explode('.', $d1));
168 | $l2 = count(explode('.', $d2));
169 | if ($d1 === $d2) return 0;
170 | if ($l1 === $l2) return $d1 < $d2 ? -1 : 1;
171 |
172 | return $l1 - $l2;
173 | });
174 |
175 | $hasActive = false;
176 | $group = array_map(static function (Domain $d) use (&$hasActive) {
177 | if ($d->enabled && $d->isAvailable() && $d->isValidRoot()) $hasActive = true;
178 |
179 | return $d->toArray();
180 | }, $group);
181 |
182 | $result[$name] = [
183 | 'name' => $name,
184 | 'hasActive' => $hasActive,
185 | 'domains' => $group,
186 | ];
187 | }
188 |
189 | return array_filter($result, static fn(array $group) => count($group['domains']));
190 | }
191 |
192 | private function readConfig(): void
193 | {
194 | try {
195 | $domainsData = Api::make()->getProjects();
196 | } catch (JsonException $e) {
197 | $domainsData = [];
198 | }
199 |
200 | foreach ($domainsData as $host => $domain) {
201 | $this->domains[$host] = new Domain(['host' => $host, ...$domain]);
202 | }
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/home/api.ospanel/core/DTO/Domain.php:
--------------------------------------------------------------------------------
1 | host_aliases
22 | * @property-read Array