6 private const MODULE_BRIEFING_VERSION =
'0.1';
8 private function esc($value):
string {
9 return htmlspecialchars((
string)$value, ENT_QUOTES,
'UTF-8');
12 private function modulesRoot():
string {
16 private function validName($name):
bool {
17 return is_string($name) && preg_match(
'/^[A-Za-z][A-Za-z0-9_]{1,63}$/', $name);
20 private function moduleDir(
string $modul):
string {
21 return dbx_os_path_file($this->modulesRoot() . $modul . DIRECTORY_SEPARATOR);
24 private function modulePath(
string $modul,
string $rel):
string {
25 $rel = str_replace(array(
'\\',
"\0"), array(
'/',
''), $rel);
26 $rel = ltrim($rel,
'/');
27 if ($rel ===
'' || strpos($rel,
'../') !==
false) {
30 $base = $this->moduleDir($modul);
32 $baseNorm = str_replace(
'\\',
'/', rtrim(
$base,
'/\\') .
'/');
33 $pathNorm = str_replace(
'\\',
'/', $path);
34 return strpos($pathNorm, $baseNorm) === 0 ? $path :
'';
37 private function moduleUrl(
string $run1, array $params = array()):
string {
38 $url =
'?dbx_modul=dbxKi&dbx_run1=' . rawurlencode($run1);
39 foreach ($params as $key => $value) {
40 $url .=
'&' . rawurlencode((
string)$key) .
'=' . rawurlencode((
string)$value);
45 private function tpl() {
46 return dbx()->get_system_obj(
'dbxTPL');
50 return dbx()->get_include_obj(
'dbxKiHelp',
'dbxKi');
53 private function moduleBar(
string $screen,
string $actionsHtml =
''):
string {
54 return $this->tpl()->get_tpl(
'dbx|module-bar', $this->help()->moduleBarTemplateData($screen, $actionsHtml));
57 private function selectOptions(array $options,
string $selected):
string {
59 foreach ($options as $value => $label) {
60 $sel = (string)$value === $selected ?
' selected' :
'';
61 $html .=
'<option value="' . $this->esc($value) .
'"' . $sel .
'>' . $this->esc($label) .
'</option>';
66 private function moduleOptions(
string $selected =
''):
string {
67 $html =
'<option value="">Bitte Modul waehlen</option>';
68 $dirs = glob($this->modulesRoot() .
'*', GLOB_ONLYDIR) ?: array();
70 foreach ($dirs as
$dir) {
71 $name = basename(
$dir);
72 if (!$this->validName($name)) {
75 $sel = $name === $selected ?
' selected' :
'';
76 $html .=
'<option value="' . $this->esc($name) .
'"' . $sel .
'>' . $this->esc($name) .
'</option>';
81 private function fileTree(
string $modul,
bool $withContent =
false): array {
82 $dir = $this->moduleDir($modul);
87 $it = new \RecursiveIteratorIterator(
88 new \RecursiveDirectoryIterator(
$dir, \FilesystemIterator::SKIP_DOTS),
89 \RecursiveIteratorIterator::SELF_FIRST
91 foreach ($it as $item) {
92 $full = $item->getPathname();
93 $rel = str_replace(
'\\',
'/', substr($full, strlen(rtrim(
$dir,
'/\\')) + 1));
95 'path' =>
'dbx/modules/' . $modul .
'/' . $rel,
96 'type' => $item->isDir() ?
'dir' :
'file',
97 'size' => $item->isFile() ? (
int)$item->getSize() : 0,
99 if ($withContent && $item->isFile() && $this->isTextFile($full) && $item->getSize() <= 262144) {
100 $row[
'content'] = (string)file_get_contents($full);
107 private function isTextFile(
string $file):
bool {
108 return (
bool)preg_match(
'/\.(php|phtml|pht|htm|html|css|js|json|md|txt|sql|xml|svg)$/i', $file);
111 private function moduleDescribe(
string $modul =
''): array {
116 'briefing_version' => self::MODULE_BRIEFING_VERSION,
117 'endpoint' => $this->moduleUrl(
'module_api'),
118 'hard_rules' => $this->hardRules($modul ?:
'{module}'),
119 'dbxapp_way' => $this->dbxappWay(),
120 'module_pipeline' => $this->modulePipeline($modul ?:
'{module}'),
121 'ui_contract' => $this->uiContract(),
122 'dbx_api_contract' => $this->dbxApiContract($modul ?:
'{module}'),
123 'api_actions' => $this->apiCatalog(),
124 'answer_zip_contract' => $this->answerZipContract($modul ?:
'{module}'),
128 private function hardRules(
string $modul): array {
130 'Nur Dateien unter dbx/modules/' . $modul .
'/ bearbeiten.',
131 'Keine Aenderungen an dbx/include, globaler config.php, anderen Modulen oder files/ ausser explizit erlaubten Modul-Assets.',
132 'Vor destruktiven Aenderungen muss ein vollstaendiges Modul-ZIP als Backup existieren.',
133 'DD-Dateien muessen vollstaendig sein; keine DD-Includes.',
134 'DD->DB Sync nur fuer ' . $modul .
'|{dd}. Keine Migrationen, keine Altlasten.',
135 'Templates ueber dbxTPL lesen/rendern: dbx()->get_system_obj("dbxTPL")->get_tpl("' . $modul .
'|template", $data).',
136 'Formulare ueber dbxForm bauen: save_post(), add_flds(), Callbacks, Modul-Bar Actions.',
137 'Reports ueber dbxReport bauen: Remember-Multi-Select, Multi-Delete, Row-Edit, Row-Detail, Row-Delete, Callbacks.',
138 'README-MODUL-WIZARD.md aktualisieren, wenn Verhalten oder API geaendert wird.',
142 private function dbxappWay(): array {
144 'module_entry' =>
'dbx/modules/{module}/{module}.class.php routet dbx_run1 und delegiert an include Services.',
145 'service_code' =>
'Fachlogik liegt unter include/*.class.php im Modul.',
146 'dd' =>
'Data Dictionary liegt unter dd/*.dd.php und beschreibt Tabelle, Rechte, Felder, Indexe vollstaendig.',
147 'fd' =>
'Form-/Report-Felder liegen unter fd/*.fd.php.',
148 'templates' =>
'Templates liegen unter tpl/htm und werden per dbxTPL get_tpl gerendert.',
149 'sqlite' =>
'*.db3 fuer ein Modul bleibt im Modulkontext; DD->DB Sync ueber dbxAdmin Schema.',
153 private function apiCatalog(): array {
155 'system.describe' => array(
'method' =>
'GET/POST',
'params' => array(),
'result' =>
'Regeln, Aktionen, Antwort-ZIP-Vertrag'),
156 'module.describe' => array(
'params' => array(
'xmodul'),
'result' =>
'Regeln und Kontext fuer ein Modul'),
157 'module.snapshot' => array(
'params' => array(
'xmodul',
'with_content=0|1'),
'result' =>
'Dateibaum, optional kleine Textdateien mit Inhalt'),
158 'module.file.read' => array(
'params' => array(
'xmodul',
'path'),
'result' =>
'Einzelne Textdatei innerhalb des Moduls'),
159 'module.pipeline_guide' => array(
'params' => array(
'xmodul',
'task_type'),
'result' =>
'Verbindliches Job-Schema fuer Modul-Aenderungen'),
160 'module.job.execute' => array(
'method' =>
'POST JSON',
'params' => array(
'manifest',
'job'),
'result' =>
'Fuehrt ein Modul-job.json direkt ueber dieselbe Pipeline aus'),
164 private function answerZipContract(
string $modul): array {
166 'filename' =>
'antwort.zip',
167 'required_files' => array(
'manifest.json',
'job.json',
'README.md'),
168 'allowed_payload_paths' => array(
'dbx/modules/' . $modul .
'