1: | <?php declare(strict_types=1); |
2: | |
3: | use Module\Support\Webapps\App\Loader; |
4: | use Module\Support\Webapps\App\Type\Adhoc\Manifest; |
5: | use Module\Support\Webapps\App\Type\Unknown\Handler as Unknown; |
6: | use Module\Support\Webapps\Finder; |
7: | use Module\Support\Webapps\MetaManager; |
8: | |
9: | |
10: | |
11: | |
12: | |
13: | |
14: | |
15: | |
16: | |
17: | |
18: | |
19: | |
20: | class Webapp_Module extends \Module\Support\Webapps implements \Module\Skeleton\Contracts\Hookable |
21: | { |
22: | public function __construct() |
23: | { |
24: | parent::__construct(); |
25: | $this->exportedFunctions += [ |
26: | 'prune' => PRIVILEGE_SITE, |
27: | 'list' => PRIVILEGE_SITE, |
28: | 'refresh_apps' => PRIVILEGE_ADMIN |
29: | ]; |
30: | } |
31: | |
32: | public function install(string $hostname, string $path = '', array $opts = array()): bool |
33: | { |
34: | return error('Unsupported universal function %s', __METHOD__); |
35: | } |
36: | |
37: | public function uninstall(string $hostname, string $path = '', $delete = 'all'): bool |
38: | { |
39: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
40: | } |
41: | |
42: | public function plugin_status(string $hostname, string $path = '', string $plugin = null) |
43: | { |
44: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
45: | } |
46: | |
47: | public function install_plugin(string $hostname, string $path, string $plugin, string $version = ''): bool |
48: | { |
49: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
50: | } |
51: | |
52: | public function uninstall_plugin(string $hostname, string $path, string $plugin, bool $force = false): bool |
53: | { |
54: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
55: | } |
56: | |
57: | public function disable_all_plugins(string $hostname, string $path = ''): bool |
58: | { |
59: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
60: | } |
61: | |
62: | public function db_config(string $hostname, string $path = '') |
63: | { |
64: | $oldex = \Error_Reporter::exception_upgrade(\Error_Reporter::E_FATAL); |
65: | try { |
66: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
67: | } catch (\apnscpException $e) { |
68: | $credentials = $this->loadManifest($hostname, $path)['database'] ?? []; |
69: | |
70: | return empty($credentials['db']) ? [] : $credentials; |
71: | } finally { |
72: | \Error_Reporter::exception_upgrade($oldex); |
73: | } |
74: | } |
75: | |
76: | public function get_versions(): array |
77: | { |
78: | return []; |
79: | } |
80: | |
81: | public function change_admin(string $hostname, string $path, array $fields): bool |
82: | { |
83: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
84: | } |
85: | |
86: | public function get_admin(string $hostname, string $path = ''): ?string |
87: | { |
88: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
89: | } |
90: | |
91: | public function get_version(string $hostname, string $path = ''): ?string |
92: | { |
93: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
94: | } |
95: | |
96: | public function update_all(string $hostname, string $path = '', string $version = null): bool |
97: | { |
98: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
99: | } |
100: | |
101: | public function update(string $hostname, string $path = '', string $version = null): bool |
102: | { |
103: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
104: | } |
105: | |
106: | public function update_plugins(string $hostname, string $path = '', array $plugins = array()): bool |
107: | { |
108: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
109: | } |
110: | |
111: | public function update_themes(string $hostname, string $path = '', array $themes = array()): bool |
112: | { |
113: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
114: | } |
115: | |
116: | public function fortify(string $hostname, string $path = '', string $mode = 'max', $args = []): bool |
117: | { |
118: | if (null === ($handler = $this->autoloadDriver($hostname, $path))) { |
119: | return parent::fortify($hostname, $path, $mode, $args); |
120: | } |
121: | |
122: | return $this->{"{$handler}_" . __FUNCTION__}($hostname, $path, $mode, $args); |
123: | } |
124: | |
125: | public function unfortify(string $hostname, string $path = ''): bool |
126: | { |
127: | if (null === ($handler = $this->autoloadDriver($hostname, $path))) { |
128: | return parent::unfortify($hostname, $path); |
129: | } |
130: | |
131: | return $this->{"{$handler}_" . __FUNCTION__}($hostname, $path); |
132: | } |
133: | |
134: | public function has_fortification(string $hostname, string $path = '', string $mode = null): bool |
135: | { |
136: | if (null === ($handler = $this->autoloadDriver($hostname, $path))) { |
137: | $modes = $this->loadManifest($hostname, $path)['fortification'] ?? []; |
138: | |
139: | return ($mode === null) ? !empty($modes) : isset($modes[$mode]); |
140: | } |
141: | |
142: | return $this->{"{$handler}_" . __FUNCTION__}($hostname, $path, $mode); |
143: | } |
144: | |
145: | public function fortification_modes(string $hostname, string $path = ''): array |
146: | { |
147: | if (null === ($handler = $this->autoloadDriver($hostname, $path))) { |
148: | return array_keys($this->loadManifest($hostname, $path)['fortification'] ?? []); |
149: | } |
150: | |
151: | return (array)$this->{"{$handler}_" . __FUNCTION__}($hostname, $path); |
152: | } |
153: | |
154: | |
155: | |
156: | |
157: | public function valid(string $hostname, string $path = ''): bool |
158: | { |
159: | return $this->discover($hostname, $path) !== null; |
160: | } |
161: | |
162: | |
163: | |
164: | |
165: | |
166: | |
167: | |
168: | |
169: | |
170: | |
171: | protected function redirect(string $method, string $hostname, string $path, ...$args) |
172: | { |
173: | if (!$handler = $this->autoloadDriver($hostname, $path)) { |
174: | fatal('Unknown or unsupported app located in %s/%s', $hostname, $path); |
175: | } |
176: | |
177: | return $this->{"{$handler}_{$method}"}($hostname, $path, ...$args); |
178: | |
179: | } |
180: | |
181: | |
182: | |
183: | |
184: | |
185: | |
186: | public function list(): array |
187: | { |
188: | return array_filter((new Finder($this->getAuthContext()))->getAllApplicationRoots(), static function ($meta) { |
189: | return !empty($meta['type']); |
190: | }); |
191: | } |
192: | |
193: | |
194: | |
195: | |
196: | |
197: | |
198: | public function available(): array |
199: | { |
200: | return array_values(\Module\Support\Webapps::knownApps()); |
201: | } |
202: | |
203: | |
204: | |
205: | |
206: | |
207: | |
208: | |
209: | |
210: | |
211: | protected function autoloadDriver(string $hostname, string $path = '', bool $force = false): ?string |
212: | { |
213: | |
214: | $app = Loader::fromHostname(null, $hostname, $path, $this->getAuthContext()); |
215: | if (!($docroot = $app->getDocumentMetaPath())) { |
216: | |
217: | return null; |
218: | } |
219: | |
220: | if (!$force && ($type = $app->getClassMapping()) !== 'webapp') { |
221: | return $type; |
222: | } |
223: | |
224: | if ($force) { |
225: | foreach (Loader::getKnownApps() as $type) { |
226: | if (Loader::isApp($docroot, $type, $this->getAuthContext())) { |
227: | return Loader::fromHostname($type, $hostname, $path, $this->getAuthContext())->getClassMapping(); |
228: | } |
229: | } |
230: | } |
231: | |
232: | return null; |
233: | } |
234: | |
235: | |
236: | |
237: | |
238: | |
239: | |
240: | |
241: | |
242: | |
243: | protected function autoloadType(string $hostname, string $path = '', bool $force = false): ?string |
244: | { |
245: | $app = Loader::fromHostname(null, $hostname, $path, $this->getAuthContext()); |
246: | if (!($docroot = $app->getDocumentMetaPath())) { |
247: | |
248: | return null; |
249: | } |
250: | if (!$force && ($type = $app->getModuleName()) !== 'webapp') { |
251: | return $type; |
252: | } |
253: | foreach (Loader::getKnownApps() as $type) { |
254: | if (Loader::isApp($docroot, $type, $this->getAuthContext())) { |
255: | return $type; |
256: | } |
257: | } |
258: | |
259: | return null; |
260: | } |
261: | |
262: | |
263: | |
264: | |
265: | |
266: | |
267: | |
268: | |
269: | public function discover(string $hostname, string $path = ''): ?string |
270: | { |
271: | if (null !== ($type = $this->autoloadType($hostname, $path, true))) { |
272: | success("detected `%s'; updating records", $type); |
273: | } |
274: | |
275: | $app = Loader::fromHostname($type, $hostname, $path, $this->getAuthContext()); |
276: | $meta = [ |
277: | 'version' => $app->getVersion(true) ?: null, |
278: | 'type' => $type, |
279: | 'path' => $path, |
280: | 'hostname' => $app->getHostname() |
281: | ]; |
282: | $app->initializeMeta($meta); |
283: | $app->getPane()->freshen(true); |
284: | |
285: | return $type; |
286: | } |
287: | |
288: | |
289: | |
290: | |
291: | |
292: | |
293: | |
294: | public function detect(string $hostname, string $path = ''): ?string |
295: | { |
296: | return $this->discover($hostname, $path); |
297: | } |
298: | |
299: | |
300: | |
301: | |
302: | |
303: | |
304: | |
305: | |
306: | public function manifest_sign(string $hostname, string $path = ''): bool |
307: | { |
308: | return $this->loadManifest($hostname, $path)->sign(); |
309: | } |
310: | |
311: | |
312: | |
313: | |
314: | |
315: | |
316: | |
317: | |
318: | public function manifest_signed(string $hostname, string $path = ''): bool |
319: | { |
320: | return $this->loadManifest($hostname, $path)->verifySignature(); |
321: | } |
322: | |
323: | |
324: | |
325: | |
326: | |
327: | |
328: | |
329: | |
330: | public function manifest_create(string $hostname, string $path = ''): bool |
331: | { |
332: | return $this->loadManifest($hostname, $path)->create(); |
333: | } |
334: | |
335: | protected function loadManifest(string $hostname, string $path): Manifest |
336: | { |
337: | $app = Loader::fromHostname('adhoc', $hostname, $path, $this->getAuthContext()); |
338: | |
339: | return Manifest::instantiateContexted($this->getAuthContext(), [$app]); |
340: | } |
341: | |
342: | |
343: | |
344: | |
345: | |
346: | |
347: | |
348: | public static function blacklisted(string $app): bool |
349: | { |
350: | if ($app === 'webapp') { |
351: | return false; |
352: | } |
353: | |
354: | return parent::blacklisted($app); |
355: | } |
356: | |
357: | |
358: | |
359: | |
360: | public function reconfigure(string $hostname, string $path, $param, $value = null): bool |
361: | { |
362: | if (static::class !== self::class || !($module = $this->autoloadDriver($hostname, $path))) { |
363: | return parent::reconfigure($hostname, $path, $param, $value); |
364: | } |
365: | |
366: | return $this->{$module . '_reconfigure'}($hostname, $path, $param, $value); |
367: | } |
368: | |
369: | |
370: | |
371: | |
372: | public function reconfigurables(string $hostname, string $path = ''): array |
373: | { |
374: | if (static::class !== self::class || !($module = $this->autoloadDriver($hostname, $path))) { |
375: | |
376: | return parent::reconfigurables($hostname, $path); |
377: | } |
378: | |
379: | return $this->{$module . '_reconfigurables'}($hostname, $path); |
380: | } |
381: | |
382: | |
383: | |
384: | |
385: | |
386: | |
387: | public function prune() |
388: | { |
389: | \Module\Support\Webapps\Finder::prune([$this->site]); |
390: | } |
391: | |
392: | |
393: | |
394: | |
395: | public function get_reconfigurable(string $hostname, string $path, $setting) |
396: | { |
397: | if (static::class !== self::class || !($module = $this->autoloadDriver($hostname, $path))) { |
398: | |
399: | return parent::get_reconfigurable($hostname, $path, $setting); |
400: | } |
401: | |
402: | return $this->{$module . '_get_reconfigurable'}($hostname, $path, $setting); |
403: | } |
404: | |
405: | |
406: | |
407: | |
408: | public function snapshot(string $hostname, string $path = '', string $comment = 'snapshot'): bool |
409: | { |
410: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
411: | } |
412: | |
413: | |
414: | |
415: | |
416: | public function rollback(string $hostname, string $path = '', string $commit = null): bool |
417: | { |
418: | return $this->redirect(__FUNCTION__, $hostname, $path, ...array_slice(func_get_args(), 2)); |
419: | } |
420: | |
421: | |
422: | |
423: | |
424: | |
425: | |
426: | |
427: | |
428: | public function get_meta(string $hostname, string $path = ''): array |
429: | { |
430: | return MetaManager::factory($this->getAuthContext())->get($this->web_get_docroot($hostname, $path))->toArray(); |
431: | } |
432: | |
433: | |
434: | |
435: | |
436: | |
437: | |
438: | |
439: | |
440: | |
441: | |
442: | |
443: | |
444: | |
445: | |
446: | |
447: | |
448: | |
449: | |
450: | |
451: | public function set_meta(string $hostname, string $path, $key, $opt = null): bool |
452: | { |
453: | if (false === ($docroot = $this->web_get_docroot($hostname, $path))) { |
454: | return error("Unknown hostname %s", $hostname); |
455: | } |
456: | $vars = $this->get_meta($hostname, $path); |
457: | if ($key && !array_has($vars, $key)) { |
458: | warn("Key %s unset in meta", $key); |
459: | } |
460: | |
461: | $manager = MetaManager::factory($this->getAuthContext()); |
462: | |
463: | if ($key === null) { |
464: | $manager->forget($docroot); |
465: | return info("Removed all meta for %s", rtrim(implode("/", [$hostname, $path]), '/')); |
466: | } |
467: | |
468: | if (!is_array($key)) { |
469: | $key = [$key => $opt]; |
470: | } |
471: | |
472: | $dot = []; |
473: | foreach ($key as $k => $v) { |
474: | array_set($dot, $k, $v); |
475: | } |
476: | |
477: | $manager->replace($docroot, $dot); |
478: | return true; |
479: | } |
480: | |
481: | |
482: | |
483: | |
484: | |
485: | public function refresh_apps(): void |
486: | { |
487: | \Module\Support\Webapps\PathManager::flush(); |
488: | } |
489: | |
490: | protected function getAppName(): ?string |
491: | { |
492: | return static::class === self::class ? 'unknown' : parent::getAppName(); |
493: | } |
494: | |
495: | public function _housekeeping() |
496: | { |
497: | \Module\Support\Webapps\PathManager::flush(); |
498: | \Module\Support\Webapps\PathManager::applicationViewPaths(); |
499: | } |
500: | |
501: | public function _edit() { |
502: | if ($this->getNewServices('siteinfo') !== $this->getOldServices('siteinfo')) { |
503: | |
504: | |
505: | \Module\Support\Webapps\App\UIPanel::instantiateContexted($this->getAuthContext())->purge(); |
506: | } |
507: | } |
508: | |
509: | public function _verify_conf(\Opcenter\Service\ConfigurationContext $ctx): bool |
510: | { |
511: | |
512: | } |
513: | |
514: | public function _create() |
515: | { |
516: | |
517: | } |
518: | |
519: | public function _delete() |
520: | { |
521: | |
522: | } |
523: | |
524: | public function _create_user(string $user) |
525: | { |
526: | |
527: | } |
528: | |
529: | public function _delete_user(string $user) |
530: | { |
531: | |
532: | } |
533: | |
534: | public function _edit_user(string $userold, string $usernew, array $oldpwd) |
535: | { |
536: | |
537: | } |
538: | |
539: | |
540: | } |