18$fail =
static function (
string $message,
int $code): void {
19 fwrite(STDERR,
"FAIL: {$message}\n");
35 if (!is_file($requiredFile)) {
36 $fail(
'Erforderliche Doxygen-Datei fehlt: ' . $requiredFile, 1);
41 'myInvoices.class.php',
44 'dd/invoiceItem.dd.php',
45 'fd/invoice-form.fd.php',
46 'fd/rpt-invoice-selection.fd.php',
47 'include/myInvoicesFixtures.class.php',
48 'include/myInvoicesService.class.php',
49 'tpl/htm/invoice-form.htm',
50 'tpl/htm/invoice-report.htm',
51 'tpl/htm/invoice-items-report.htm',
52 'tpl/htm/invoice-row-action.htm',
53 'tpl/htm/invoice-install.htm',
54 'tools/install_demo.php',
55 'tests/myInvoices_contract_test.php',
56 'tests/myInvoices_integration_test.php',
60 $fail(
'Ausführbares Referenzmodul ist unvollständig: ' . $relative, 16);
66 $fail(
'Die nummerierte Doxygen-Dokumentation ist unvollstaendig.', 2);
74foreach (
$docs as $doc) {
75 $content = (string)file_get_contents($doc);
77 if (preg_match_all(
'/\{#(dbxapp_[A-Za-z0-9_]+)\}/',
$content, $matches)) {
78 foreach ($matches[1] as $anchor) {
87 if (preg_match_all(
'/@ref\s+(dbxapp_[A-Za-z0-9_]+)/',
$content, $matches)) {
95 $content = (string)file_get_contents($doxygenPageFile);
96 if (preg_match_all(
'/@page\s+(dbxapp_[A-Za-z0-9_]+)/',
$content, $matches)) {
97 foreach ($matches[1] as $anchor) {
101 basename($doxygenPageFile),
105 $anchors[$anchor] = basename($doxygenPageFile);
127 'docs/doxygen-navigation.dox',
128 'docs/dbxapp-system-update-user.dox',
129 'docs/generated/tutorials',
130 '25_Verbindliches_Modulhandbuch.md',
132) as $editorialInput) {
134 $fail(
'Redaktioneller CMS-Inhalt darf nicht mehr Doxygen-INPUT sein: ' . $editorialInput, 5);
140 '@mainpage dbxapp Quellcode-Referenz',
141 '@ref annotated "Klassen"',
142 '@ref namespaces "Namespaces"',
143 '@ref files "Dateien"',
144 '@ref examples "Beispiele"',
145 'dbxapp-Dokumentationsportal öffnen',
148 $fail(
'Mainpage-Vertrag fehlt: ' . $needle, 7);
154 '@page dbxapp_user_docs Anwenderdokumentation',
155 '@page dbxapp_user_start Einstieg und Bedienung',
156 '@page dbxapp_user_admin Administration und Systemstatus',
157 '@subpage dbxapp_user_system_update',
158 '@page dbxapp_user_content Inhalte, CMS und Medien',
159 '@page dbxapp_user_shop Shop bedienen und administrieren',
160 '@page dbxapp_user_workflows Workflows erstellen und benutzen',
161 '@page dbxapp_user_design Designs manuell und mit KI erstellen',
162 '@page dbxapp_user_ki_content KI-Bereich 1: Content',
163 '@page dbxapp_user_ki_design KI-Bereich 2: Design',
164 '@page dbxapp_user_ki_modules KI-Bereich 3: Module',
165 '@page dbxapp_developer_docs Entwicklerdokumentation',
166 '@page dbxapp_dev_orientation Architektur und Laufzeit',
167 '@page dbxapp_dev_modules Verbindliche Modulentwicklung',
168 '@page dbxapp_dev_pipelines Kernpipelines und Bibliotheken',
169 '@page dbxapp_dev_content_design CMS, KI, Design und Fachmodule',
170 '@page dbxapp_dev_operations Installation, Updates und Betrieb',
173 $fail(
'Doxygen-Navigation ist unvollständig: ' . $needle, 19);
179 '@page dbxapp_user_system_update System-Update sicher durchführen',
180 'Update automatisch vorbereiten',
182 'Jetzt sicher installieren',
183 'Letztes Update zurückrollen',
184 'DB3, MySQL und gemischte Installationen',
185 'System → System-Selbsttest',
186 'dokumentation-selbsttest',
187 'files/dbxError.log',
188 'dbxapp_install_update_dd_bindings.html',
191 $fail(
'Anwender-Updateanleitung ist unvollständig: ' . $needle, 32);
197 '<span class="dbx-area-number">1</span>',
199 '<span class="dbx-area-number">2</span>',
201 '<span class="dbx-area-number">3</span>',
203 '@subpage dbxapp_user_ki_content',
204 '@subpage dbxapp_user_ki_design',
205 '@subpage dbxapp_user_ki_modules',
208 $fail(
'Trennung der drei KI-Bereiche fehlt: ' . $needle, 20);
214 $fail(
'Der generierte dbxContent-Tutorialbestand muss 18 Seiten enthalten.', 21);
219 $tutorialContent = (string)file_get_contents($tutorialFile);
220 if (!preg_match(
'/^\/\*\*\s+@page\s+(dbxcontent_tutorial_[a-z0-9_]+)\s+/s', $tutorialContent, $match)) {
221 $fail(
'Generierte Tutorialseite besitzt keinen stabilen @page-Anker: '
222 . basename($tutorialFile), 22);
224 if (strpos($tutorialContent,
'dbxContent #') ===
false
225 || strpos($tutorialContent,
'@note Quelle: deutsche dbxContent-Seite') ===
false) {
226 $fail(
'Quellnachweis der Tutorialseite fehlt: ' . basename($tutorialFile), 23);
231 $fail(
'Generierte Tutorial-Anker sind nicht eindeutig.', 24);
237preg_match_all(
'/@image html (dbxcontent-media-[^\s"]+)/',
$tutorialOverview, $overviewMedia);
238if (count($overviewMedia[1] ?? array()) === 0) {
239 $fail(
'Die Tutorialübersicht enthält keine zugeordneten Medien.', 31);
241foreach (array_unique($overviewMedia[1]) as $asset) {
243 $fail(
'Der Tutorialübersicht fehlt die exportierte Mediendatei: ' . $asset, 31);
249 $fail(
'Tutorial ist keiner Doxygen-Navigation zugeordnet: ' . $tutorialLabel, 25);
255 "get_system_obj('dbxDB')",
256 "dbxContentLng::ddContent('de')",
257 "'folder = 15 AND activ = 1'",
264 $fail(
'Tutorial-Exportvertrag ist unvollständig: ' . $needle, 26);
267foreach (array(
'/\bPDO\b/',
'/\bmysqli?\b/i',
'/\bSQLite3\b/') as $forbiddenPattern) {
269 $fail(
'Tutorial-Export umgeht dbxDB: ' . $forbiddenPattern, 27);
274 'PROJECT_NAME = "dbxapp"',
276 'PROJECT_LOGO = dbXapp-Logo.jpeg',
277 'FULL_SIDEBAR = YES',
278 'FULL_PATH_NAMES = NO',
279 'PAGE_OUTLINE_PANEL = NO',
280 'STRIP_FROM_PATH = .',
281 'FILTER_PATTERNS = *dbxApi.php="php -n docs/tools/doxygen_php_utf8_filter.php"',
282 '*dbxUpload.class.php="php -n docs/tools/doxygen_php_utf8_filter.php"',
283 'FILTER_SOURCE_FILES = YES',
284 'docs/doxygen-generated-main.dox',
285 'docs/doxygen-awesome/header.html',
286 'docs/doxygen-awesome/dbxapp-doxygen.css',
289 $fail(
'Doxygen-Branding- oder Strukturkonfiguration fehlt: ' . $needle, 28);
299 'data-dbx-section="user"',
305 $fail(
'Zielgruppen-Navigation im dbxapp-Header fehlt: ' . $needle, 29);
312 '.dbx-audience-grid',
313 '#dbx-doc-audience a.is-active',
316 '.dbx-user-nav-grid',
318 '.dbx-update-state-grid',
319 'html.dbx-doc-embedded #doc-content',
322 $fail(
'dbxapp-Branding-CSS ist unvollständig: ' . $needle, 30);
337 'Kombination aus `delete` und `rid` automatisch',
339 'invoice_report_next_record',
340 'invoice_items_report_next_record',
343 "\$report->_mode = 'table'",
346 '[modul=myInvoices]dbx_run1=positions&invoice_id=',
351 'Verbindliche Arbeitsanweisung',
353 'myInvoices_contract_test.php',
354 'myInvoices_integration_test.php',
355 'include/myInvoicesFixtures.class.php',
357 '@include dbx/modules/myInvoices/dd/invoice.dd.php',
358 '@include dbx/modules/myInvoices/dd/invoiceItem.dd.php',
359 'dbxapp-Exportformat',
362 $fail(
'Referenzmodul ist unvollstaendig: ' . $needle, 8);
375 '/->(?:query|exec|prepare)\s*\(/',
376 '/private function (?:db|tpl|h)\s*\(/',
377) as $forbiddenPattern) {
380 'Ausführbares Referenzmodul verletzt den Daten-/Wrappervertrag: '
394 '/[\'"]' . preg_quote($systemField,
'/') .
'[\'"]\s*=>/',
398 'Fixture setzt automatisches dbxDB-Systemfeld: ' . $systemField,
404if (!preg_match_all(
'/```php\s*(<\?php.*?)(?=```)/s',
$referenceContent, $phpBlocks)
405 || count($phpBlocks[1]) < 5
407 $fail(
'Zu wenige vollstaendige PHP-Dateibeispiele im Referenzmodul.', 9);
410foreach ($phpBlocks[1] as
$index => $phpBlock) {
412 token_get_all($phpBlock, TOKEN_PARSE);
413 }
catch (ParseError $e) {
415 'PHP-Beispiel ' . (
$index + 1) .
' ist syntaktisch ungueltig: '
422if (!preg_match(
'/class myInvoicesService.*?\?>/s',
$referenceContent, $serviceMatch)) {
423 $fail(
'Servicebeispiel im Referenzmodul nicht gefunden.', 11);
428 '/private function (?:db|tpl|h)\s*\(/',
429 '/(?:private|protected|public) function \w+\s*\([^)]*\)[^{]*\{\s*return\s+dbx\(\)->get_system_obj\s*\(/s',
430 '/dbx\(\)->esc\s*\(/',
431 '/htmlspecialchars\s*\(/',
432 '/[\'"](?:create_date|create_uid|update_date|update_uid|owner)[\'"]\s*=>\s*(?:date|dbx\(\)->user)/',
433) as $forbiddenPattern) {
436 'Servicebeispiel enthaelt ein unzulaessiges Wrapper-, Escape- '
437 .
'oder Systemfeldmuster: ' . $forbiddenPattern,
444 'automatisch von `dbxDB` gesetzt',
445 'Kernel-Funktionen nicht durch eigene Methoden nachbauen',
446 'Werte nicht pauschal escapen',
447 'Endsumme der Positionen',
448 'geschützte Modulvariablen',
449) as $requiredContract) {
451 $fail(
'Vereinfachungsvertrag fehlt: ' . $requiredContract, 13);
456 $root .
'/08_Modulaufbau_Patterns.md'
459 $root .
'/06_dbxForm_Leitfaden.md'
463 '/private function (?:db|tpl|h)\s*\(/',
464 '/dbx\(\)->esc\s*\(/',
465 '/htmlspecialchars\s*\(/',
466) as $forbiddenPattern) {
469 'Modulpattern enthaelt einen unzulaessigen Alias oder '
470 .
'pauschales Escaping: ' . $forbiddenPattern,
477 '/set_post\s*\(\s*[\'"](?:create_date|create_uid|update_date|update_uid|owner)[\'"]/',
480 $fail(
'Formleitfaden setzt automatische dbxDB-Systemfelder manuell.', 15);
483echo
'OK Doxygen structure: '
484 . count(
$docs) .
' pages, '
487 . count($phpBlocks[1]) .
" PHP examples\n";
foreach(array( 'forms', 'reports', 'dd', 'db', 'audit', 'objects', 'escaping', 'get', 'action_links') as $requiredKey) foreach(array('{fid}_{event}-Callback-Defaults', 'add_rep()', '{rpt:colspan}') as $requiredReportRule) $reference
if($missingReferences) $doxyContent
foreach(array(' @page dbxapp_user_docs Anwenderdokumentation', ' @page dbxapp_user_start Einstieg und Bedienung', ' @page dbxapp_user_admin Administration und Systemstatus', ' @subpage dbxapp_user_system_update', ' @page dbxapp_user_content Inhalte, CMS und Medien', ' @page dbxapp_user_shop Shop bedienen und administrieren', ' @page dbxapp_user_workflows Workflows erstellen und benutzen', ' @page dbxapp_user_design Designs manuell und mit KI erstellen', ' @page dbxapp_user_ki_content KI-Bereich 1:Content', ' @page dbxapp_user_ki_design KI-Bereich 2:Design', ' @page dbxapp_user_ki_modules KI-Bereich 3:Module', ' @page dbxapp_developer_docs Entwicklerdokumentation', ' @page dbxapp_dev_orientation Architektur und Laufzeit', ' @page dbxapp_dev_modules Verbindliche Modulentwicklung', ' @page dbxapp_dev_pipelines Kernpipelines und Bibliotheken', ' @page dbxapp_dev_content_design CMS, KI, Design und Fachmodule', ' @page dbxapp_dev_operations Installation, Updates und Betrieb',) as $needle) $updateUserContent
if(!preg_match_all('/```php\s *(<\?php.*?)(?=```)/s', $referenceContent, $phpBlocks)||count($phpBlocks[1])< 5) foreach( $phpBlocks[1] as $index=> $phpBlock) if(!preg_match('/class myInvoicesService.*?\?>/s', $referenceContent, $serviceMatch)) $serviceExample
foreach(array( 'Anwender', 'Entwickler', 'KI-Bereiche', 'data-dbx-section="user"', 'dbx-doc-nav-icon', 'dbx-doc-tools', 'dbx-doc-embedded',) as $needle) foreach(array('--dbx-brand-navy', '--dbx-brand-red', '.dbx-doc-hero', '.dbx-audience-grid', '#dbx-doc-audience a.is-active', '.dbx-doc-nav-icon', '.dbx-doc-tools', '.dbx-user-nav-grid', '.dbx-update-steps', '.dbx-update-state-grid', 'html.dbx-doc-embedded #doc-content',) as $needle) $referenceContent
foreach(array('dbxTPL', 'dbxDB', 'dbxDD', 'dbxForm', 'dbxReport', 'DD', 'FD', 'dbxAjax', 'dbxConfirm', 'Kombination aus `delete` und `rid` automatisch', 'action_url', 'invoice_report_next_record', 'invoice_items_report_next_record', '{rpt:colspan}', 'add_rep()', "\$report->_mode = 'table'", "'sum' => 'Summe'", '{report_total}', '[modul=myInvoices]dbx_run1=positions &invoice_id=', '{rpt:col_count}', '< tfoot >', '[dbx:form]', 'dbx_split', 'Verbindliche Arbeitsanweisung', 'Mindesttestmatrix', 'myInvoices_contract_test.php', 'myInvoices_integration_test.php', 'include/myInvoicesFixtures.class.php', 'Browserkonsole', ' @include dbx/modules/myInvoices/dd/invoice.dd.php', ' @include dbx/modules/myInvoices/dd/invoiceItem.dd.php', 'dbxapp-Exportformat',) as $needle) $actualService
foreach( $tutorialFiles as $tutorialFile) if(count($tutorialLabels) !==18) $tutorialOverview
if(count($overviewMedia[1] ?? array())===0) foreach(array_unique( $overviewMedia[1]) as $asset) foreach(array_keys($tutorialLabels) as $tutorialLabel) $tutorialExportContent
foreach($docs as $doc) foreach(array( $navigationFile, $updateUserFile) as $doxygenPageFile) if($duplicateAnchors) $missingReferences
foreach(array(' @page dbxapp_user_system_update System-Update sicher durchführen', 'Update automatisch vorbereiten', 'Update stoppen', 'Jetzt sicher installieren', 'Letztes Update zurückrollen', 'DB3, MySQL und gemischte Installationen', 'System → System-Selbsttest', 'dokumentation-selbsttest', 'files/dbxError.log', 'dbxapp_install_update_dd_bindings.html',) as $needle) $kiAreasContent
foreach([ $doxyfile, $mainFile, $referenceFile, $navigationFile, $updateUserFile, $kiAreasFile, $tutorialExport, $brandingHeader, $brandingCss, $utf8Filter,] as $requiredFile) foreach(array('myInvoices.class.php', 'cfg/config.php', 'dd/invoice.dd.php', 'dd/invoiceItem.dd.php', 'fd/invoice-form.fd.php', 'fd/rpt-invoice-selection.fd.php', 'include/myInvoicesFixtures.class.php', 'include/myInvoicesService.class.php', 'tpl/htm/invoice-form.htm', 'tpl/htm/invoice-report.htm', 'tpl/htm/invoice-items-report.htm', 'tpl/htm/invoice-row-action.htm', 'tpl/htm/invoice-install.htm', 'tools/install_demo.php', 'tests/myInvoices_contract_test.php', 'tests/myInvoices_integration_test.php', 'README.md',) as $relative) $docs
foreach(array('docs/doxygen-navigation.dox', 'docs/dbxapp-system-update-user.dox', 'docs/generated/tutorials', '25_Verbindliches_Modulhandbuch.md', 'files/doku',) as $editorialInput) $mainContent
foreach(array(' @mainpage dbxapp Quellcode-Referenz', ' @ref annotated "Klassen"', ' @ref namespaces "Namespaces"', ' @ref files "Dateien"', ' @ref examples "Beispiele"', 'dbxapp-Dokumentationsportal öffnen',) as $needle) $navigationContent
if(!is_array($tutorialFiles)||count($tutorialFiles) !==18) $tutorialLabels
foreach(array("get_system_obj('dbxDB')", "dbxContentLng::ddContent('de')", "'folder = 15 AND activ = 1'", "'dbxMediaUsage'", "'dbxMedia'", "'--write'", "'--check'",) as $needle) foreach(array('/\bPDO\b/', '/\bmysqli?\b/i', '/\bSQLite3\b/') as $forbiddenPattern) foreach(array('PROJECT_NAME="dbxapp"', 'PROJECT_NUMBER="' . $releaseVersion . '"', 'PROJECT_LOGO=dbXapp-Logo.jpeg', 'FULL_SIDEBAR=YES', 'FULL_PATH_NAMES=NO', 'PAGE_OUTLINE_PANEL=NO', 'STRIP_FROM_PATH=.', 'FILTER_PATTERNS= *dbxApi.php="php -n docs/tools/doxygen_php_utf8_filter.php"', ' *dbxUpload.class.php="php -n docs/tools/doxygen_php_utf8_filter.php"', 'FILTER_SOURCE_FILES=YES', 'docs/doxygen-generated-main.dox', 'docs/doxygen-awesome/header.html', 'docs/doxygen-awesome/dbxapp-doxygen.css',) as $needle $headerContent)
foreach(array('< span class="dbx-area-number">1</span >', '< h2 >Content</h2 >', '< span class="dbx-area-number">2</span >', '< h2 >Design</h2 >', '< span class="dbx-area-number">3</span >', '< h2 >Module</h2 >', ' @subpage dbxapp_user_ki_content', ' @subpage dbxapp_user_ki_design', ' @subpage dbxapp_user_ki_modules',) as $needle) $tutorialFiles