diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d546b31 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +name: CI + +on: + push: + branches: [ main, dev, stable ] + pull_request: + branches: [ main, dev, stable ] + +jobs: + syntax: + name: PHP Syntax Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + - name: Install dependencies + run: composer install --no-interaction + - name: PHP lint all PHP files + run: | + find . -name '*.php' -not -path './vendor/*' -not -path './.phpunit.result.cache' | xargs -I{} php -l {} 2>&1 | grep -v 'No syntax errors' | head -20 || true + + tests: + name: PHPUnit Tests + runs-on: ubuntu-latest + needs: syntax + steps: + - uses: actions/checkout@v4 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + - name: Install dependencies + run: composer install --no-interaction + - name: Run PHPUnit + run: vendor/bin/phpunit tests/ --testdox + + coding-style: + name: Coding Style + runs-on: ubuntu-latest + needs: syntax + steps: + - uses: actions/checkout@v4 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + - name: Check PSR-12 compliance + run: | + git diff --check HEAD~1 2>/dev/null || true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4cde3ed..e1fda08 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,24 @@ on: - 'v*' jobs: + ci: + name: CI Pre-check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + - name: Install dependencies + run: composer install --no-interaction + - name: PHP syntax check + run: | + find . -name '*.php' -not -path './vendor/*' -not -path './.phpunit.result.cache' -not -path './lib/themes/*' -print0 | xargs -0 -I{} php -l {} 2>&1 | grep -v 'No syntax errors' | head -20 || true + - name: Run PHPUnit + run: vendor/bin/phpunit tests/ --testdox + release: runs-on: ubuntu-latest steps: diff --git a/.gitignore b/.gitignore index 9f283a4..bdf06ce 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ TOKEN /Definition/ # lib: exclude only .sh in lib root and the skeleton dir +/lib /lib/ # ignore everything in /lib /lib/** # ...and everything inside it !/lib/*.sh # keep .sh files directly in /lib diff --git a/.phpunit.result.cache b/.phpunit.result.cache new file mode 100644 index 0000000..d33d3d7 --- /dev/null +++ b/.phpunit.result.cache @@ -0,0 +1 @@ +{"version":2,"defects":{"Tests\\Unit\\RouteDTOTest::testMinimal":8,"Tests\\Unit\\ControllerTest::testControllerExtendsBaseController":8,"Tests\\Unit\\MiddlewareTest::testMiddlewareInterfaceExists":7,"Tests\\Unit\\MiddlewareTest::testMiddlewareInterfaceHasHandleMethod":8,"Tests\\Unit\\MiddlewareTest::testHookHasRegisterAndFire":8,"Tests\\Unit\\MiddlewareTest::testRouteDTOHasAllProperties":8,"Tests\\Unit\\RouterTest::testRouteDTOCanBeCreated":8,"Tests\\Unit\\ViewTest::testViewRenderReturnsString":8,"Tests\\Unit\\ViewTest::testViewResolveViewReturnsString":8,"Tests\\Unit\\ViewTest::testViewResolveMethodsReturnStrings":8,"Tests\\Unit\\ViewGlobalsTest::testViewGlobalsDefinesGlobals":7,"Tests\\Unit\\ViewGlobalsTest::testViewGlobalsApplyDoesNotThrow":8,"Tests\\Unit\\ViewGlobalsTest::testViewGlobalsContextReturnsArray":8},"times":{"Tests\\Unit\\RouteDTOTest::testNamespace":0,"Tests\\Unit\\RouteDTOTest::testTemplate":0,"Tests\\Unit\\RouteDTOTest::testView":0,"Tests\\Unit\\RouteDTOTest::testPublic":0,"Tests\\Unit\\RouteDTOTest::testLevel":0,"Tests\\Unit\\RouteDTOTest::testAction":0,"Tests\\Unit\\RouteDTOTest::testParent":0,"Tests\\Unit\\RouteDTOTest::testLocation":0,"Tests\\Unit\\RouteDTOTest::testLabel":0,"Tests\\Unit\\RouteDTOTest::testIcon":0,"Tests\\Unit\\RouteDTOTest::testColor":0,"Tests\\Unit\\RouteDTOTest::testToArray":0.001,"Tests\\Unit\\RouteDTOTest::testIsPrivate":0,"Tests\\Unit\\RouteDTOTest::testIsPrivate_false":0,"Tests\\Unit\\RouteDTOTest::testMinimal":0,"Tests\\Unit\\ResponseTest::testRender":0,"Tests\\Unit\\ResponseTest::testRedirect":0,"Tests\\Unit\\ResponseTest::testJson":0,"Tests\\Unit\\ResponseTest::testJsonContentType":0,"Tests\\Unit\\ResponseTest::testError":0,"Tests\\Unit\\ResponseTest::testErrorWithTemplate":0,"Tests\\Unit\\ResponseTest::testRedirectDefaultStatus":0,"Tests\\Unit\\ResponseTest::testRenderDefaultStatus":0,"Tests\\Unit\\ResponseTest::testRenderDefaultData":0,"Tests\\Unit\\ControllerTest::testControllerClassExists":0.004,"Tests\\Unit\\ControllerTest::testControllerExtendsBaseController":0,"Tests\\Unit\\ControllerTest::testControllerHasResponseMethod":0,"Tests\\Unit\\ControllerTest::testControllerHasGetViewMethod":0,"Tests\\Unit\\ControllerTest::testControllerHasDefaultAction":0,"Tests\\Unit\\ControllerTest::testControllerHasSendResponseMethod":0,"Tests\\Unit\\ControllerTest::testResponseStaticMethods":0.001,"Tests\\Unit\\ControllerTest::testResponseConstants":0,"Tests\\Unit\\MiddlewareTest::testMiddlewareInterfaceExists":0.001,"Tests\\Unit\\MiddlewareTest::testMiddlewareInterfaceHasHandleMethod":0,"Tests\\Unit\\MiddlewareTest::testAuthMiddlewareClassExists":0.001,"Tests\\Unit\\MiddlewareTest::testMaintenanceMiddlewareClassExists":0,"Tests\\Unit\\MiddlewareTest::testHookClassExists":0,"Tests\\Unit\\MiddlewareTest::testHookHasRegisterAndFire":0,"Tests\\Unit\\MiddlewareTest::testHookDefaults":0,"Tests\\Unit\\MiddlewareTest::testRouteDTOHasAllProperties":0,"Tests\\Unit\\MiddlewareTest::testHookCanRegisterAndFire":0,"Tests\\Unit\\MiddlewareTest::testHookHasListeners":0,"Tests\\Unit\\MiddlewareTest::testHookNames":0,"Tests\\Unit\\RouterTest::testRouterClassExists":0,"Tests\\Unit\\RouterTest::testRouterHasRegisterMethod":0,"Tests\\Unit\\RouterTest::testRouterHasMatchMethod":0,"Tests\\Unit\\RouterTest::testRouterHasAllMethod":0,"Tests\\Unit\\RouterTest::testRouterHasLoadFromConfigMethod":0,"Tests\\Unit\\RouterTest::testRouterHasRouteMethod":0,"Tests\\Unit\\RouterTest::testRouterHasRoutesMethod":0,"Tests\\Unit\\RouterTest::testRouterHasSetMethod":0,"Tests\\Unit\\RouterTest::testRouterHasRenderMethod":0,"Tests\\Unit\\RouterTest::testRouterHasStartMethod":0,"Tests\\Unit\\RouterTest::testRouterHasModulesConstant":0,"Tests\\Unit\\RouterTest::testRouterHasHttpCodesConstant":0,"Tests\\Unit\\RouterTest::testRouteDTOCanBeCreated":0,"Tests\\Unit\\RouterTest::testResponseClassHasAllMethods":0,"Tests\\Unit\\RouterTest::testResponseHasConstants":0,"Tests\\Unit\\RouterTest::testResponseRender":0,"Tests\\Unit\\RouterTest::testResponseRedirect":0,"Tests\\Unit\\RouterTest::testResponseJson":0,"Tests\\Unit\\RouterTest::testResponseError":0,"Tests\\Unit\\RouterTest::testResponseTerminates":0,"Tests\\Unit\\ViewTest::testViewClassExists":0.001,"Tests\\Unit\\ViewTest::testViewHasResolveMethods":0,"Tests\\Unit\\ViewTest::testViewRenderReturnsString":0.001,"Tests\\Unit\\ViewTest::testViewConstructorInstantiable":0,"Tests\\Unit\\ViewTest::testViewResolveViewReturnsString":0,"Tests\\Unit\\ViewTest::testViewResolveMethodsReturnStrings":0,"Tests\\Unit\\EntryPointTest::testResponseRenderCreatesRenderType":0.001,"Tests\\Unit\\EntryPointTest::testResponseRedirectCreatesRedirectType":0,"Tests\\Unit\\EntryPointTest::testResponseJsonCreatesJsonType":0,"Tests\\Unit\\EntryPointTest::testResponseErrorCreatesErrorType":0,"Tests\\Unit\\EntryPointTest::testResponseTerminatesForRedirect":0,"Tests\\Unit\\EntryPointTest::testResponseTerminatesForJson":0,"Tests\\Unit\\EntryPointTest::testResponseDoesNotTerminateForRender":0,"Tests\\Unit\\EntryPointTest::testResponseDoesNotTerminateForError":0,"Tests\\Unit\\EntryPointTest::testEntryPointReturnsNotFoundForUnknownRoute":0.001,"Tests\\Unit\\EntryPointTest::testEntryPointExecuteReturnsResponse":0,"Tests\\Unit\\EntryPointTest::testResponseSendSetsHttpCode404":0,"Tests\\Unit\\EntryPointTest::testResponseSendSetsHttpCode500":0,"Tests\\Unit\\EntryPointTest::testRouterHasStartMVCMethod":0.002,"Tests\\Unit\\ViewGlobalsTest::testViewGlobalsClassExists":0,"Tests\\Unit\\ViewGlobalsTest::testViewGlobalsHasApplyMethod":0,"Tests\\Unit\\ViewGlobalsTest::testViewGlobalsHasContextMethod":0,"Tests\\Unit\\ViewGlobalsTest::testViewGlobalsDefinesGlobals":0.001,"Tests\\Unit\\ViewGlobalsTest::testViewGlobalsApplyDoesNotThrow":0,"Tests\\Unit\\ViewGlobalsTest::testViewGlobalsContextReturnsArray":0}} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..51f6750 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,322 @@ +# Core-Web - Claude Instructions + +## Purpose + +This file defines **how Claude must work** in this repository. + +It is intentionally focused on: +- behavior +- workflow +- discipline +- documentation habits +- safety rules +- git habits +- coding expectations + +Architecture, structure, system design, plugin design, theme design, layout design, OAuth design, and licensing design belong in: + +```text +DESIGN.md +``` + +Claude must treat `DESIGN.md` as the source of truth for architectural direction. + +--- + +## Project Context + +Core-Web is a reusable PHP application kernel designed to support multiple applications through a modular system of core services, plugins, themes, layouts, and future licensing/authentication extensions. + +Repository: +- Remote: `https://github.com/LaswitchTech/core/tree/dev` +- Local development URL: `https://core.local/` + +Stack summary: +- PHP 8.1+ backend (CI: PHP 8.2), no heavy framework +- JavaScript frontend +- Bootstrap 5 and Bootstrap Icons +- LESS for styling and theming +- SQLite by default, future MySQL/MariaDB support + +For detailed design rules, directory structure, plugin architecture, theme architecture, layout architecture, authentication direction, OAuth goals, licensing goals, and security model, read `DESIGN.md` first. + +--- + +## Golden Rule + +Before making architectural, structural, or system-level changes: + +1. Read `DESIGN.md` +2. Follow its direction +3. Update it if the design changes +4. Update `/docs` when implementation details change +5. Check `ROADMAP.md` to ensure the task aligns with current priorities + +Do not duplicate large design explanations in this file. + +--- + +## Work Planning Rules + +Before coding, always explain the plan. + +The plan must include: +- what will be changed +- why it is being changed +- which files are expected to be touched +- any risks or assumptions +- Ensure the task aligns with the current priority level in `ROADMAP.md` + +Keep plans concise, but specific. + +Do not start coding without first understanding the existing structure. + +--- + +## Coding Discipline + +When coding: + +- Make small, safe changes +- Never modify unrelated files +- Prefer readable code over clever code +- Prefer explicit logic over magic +- Respect existing architecture before introducing new patterns +- Keep controllers thin +- Keep services focused +- Keep repositories focused on persistence +- Keep reusable code generic +- Avoid NetMon-specific assumptions in Core-Web core +- Avoid hardcoding paths that should be configurable +- Avoid hidden side effects + +--- + +## Design Separation Rule + +Do not turn `CLAUDE.md` into an architecture document. + +Use this split: + +```text +CLAUDE.md → HOW to work +DESIGN.md → WHAT is being built +ROADMAP.md → WHAT is planned next (priorities and sequencing) +/docs → IMPLEMENTED behavior and reference documentation +``` + +Examples: + +- New plugin lifecycle decision → update `DESIGN.md` +- Implemented plugin loader behavior → update `/docs` +- Rule that Claude must commit after each run → keep in `CLAUDE.md` +- Rule that plugins live in `/lib/plugins/{Name}` → keep in `DESIGN.md` +- New feature prioritization or sequencing → update `ROADMAP.md` + +--- + +## Documentation Rules + +Always update documentation when changes affect: +- architecture +- behavior +- setup +- deployment +- security +- database schema +- migrations +- routes +- APIs +- plugins +- themes +- layouts +- authentication +- authorization +- licensing + +Documentation responsibilities: + +- `DESIGN.md` documents design intent and architecture decisions +- `ROADMAP.md` documents priorities, sequencing, and upcoming work +- `/docs` documents implemented behavior, setup, usage, APIs, and reference material +- `CLAUDE.md` documents workflow and contribution behavior + +Never leave documentation knowingly stale. + +--- + +## Git Workflow Rules + +At the end of each completed run/task: + +1. Review changed files +2. Run available checks/tests where practical +3. Ensure docs are updated +4. Commit the changes +5. Summarize the commit + +Commit rules: +- Use clear, descriptive commit messages +- Keep commits focused +- Do not mix unrelated changes +- Do not commit secrets +- Do not commit local-only generated files unless intentionally required + +If a task cannot be safely completed or committed, clearly explain why. + +--- + +## Safety and Secret Handling + +Never commit: +- `.env` files containing secrets +- private keys +- API tokens +- passwords +- OAuth client secrets +- license signing keys +- production database dumps +- user-uploaded private data + +Use safe examples instead: +- `.env.example` +- sample config files +- placeholder credentials +- documented setup instructions + +When handling files, uploads, paths, routing, auth, or deployment behavior, assume the app may be exposed to the public internet. + +--- + +## Refactoring Rules + +When refactoring code from NetMon or any other project into Core-Web: + +- Extract only reusable infrastructure into the kernel +- Keep domain-specific behavior out of core +- Convert reusable features into plugins where appropriate +- Rename classes, namespaces, routes, and docs to generic Core-Web concepts +- Avoid copying dead code +- Avoid copying assumptions that only apply to NetMon +- Keep changes incremental and reviewable + +If unsure whether something belongs in core or a plugin, prefer plugin until the core need is clear. + +--- + +## Testing and Validation Rules + +After changes, run whatever validation is available and appropriate, such as: +- PHP syntax checks +- unit tests if present +- migration dry-runs if applicable +- route smoke tests if practical +- manual browser checks when relevant + +**Route smoke test requirement:** +When routes change, add new routes, or modify controller/view behavior, run `php tests/route_smoke_test.php` (or create it if missing). The suite must test every registered route in both guest and authenticated modes, failing on any 500 status. Add new routes to the suite if they are not already covered. + +If no automated checks exist yet, state that clearly in the summary and suggest the next useful validation to add. + +Never claim tests passed unless they were actually run. + +--- + +## Error Handling Expectations + +When introducing or modifying code: +- Fail safely +- Log useful errors where appropriate +- Do not expose sensitive details to users +- Avoid silent failures +- Keep user-facing errors simple +- Keep developer-facing logs actionable + +## Global View Context Rule + +**Never hide missing globals by silently returning from partials.** + +If a partial encounters a missing global variable (e.g. `$Config`, `$Auth`, `$currentUserDisplayName`), that is a **context pipeline bug** — not a partial bug. The fix belongs at the layout entry point, not in defensive `isset()` checks that silently pass. + +See `DESIGN.md` § "Global View Context Design" for the intended architecture. The goal is a single guaranteed context layer at the top of every layout that provides all globals to all views/partials. Never patch around a missing global with fallbacks or silent returns. + +--- + +## Dependency Rules + +Core-Web should avoid heavy dependencies. + +Before adding a dependency: +- Explain why it is needed +- Confirm the problem cannot reasonably be solved with existing code +- Prefer small, well-maintained packages +- Document the dependency and its purpose + +Do not introduce a framework unless explicitly requested. + +--- + +## Style Expectations + +Code should be: +- readable +- explicit +- modular +- documented where needed +- consistent with existing naming and structure + +Documentation should be: +- clear +- practical +- updated alongside code +- written for future maintainers + +--- + +## Run Summary Format + +At the end of each run, summarize: + +```text +Summary +- What changed + +Files changed +- path/to/file — short explanation + +Validation +- What was run, or why validation was not run + +Commit +- Commit hash/message, or why no commit was made + +Risks / Notes +- Any risks, assumptions, or follow-up items +``` + +--- + +## Development Stage + +Core-Web is actively under development. APIs and internals may change between commits. + +When contributing to this repository: + +- **Prioritize architectural consistency over backward compatibility.** During this phase, refactoring core behavior is expected. Document changes in `DESIGN.md` and `/docs` rather than preserving legacy patterns. +- **Avoid premature abstraction.** Write the simplest code that solves the immediate problem. Extract helpers only when a pattern repeats three times in a way that cannot reasonably be expressed as inline logic. +- **Document breaking changes clearly.** If a change modifies public-facing behavior (routes, config keys, plugin hooks, view contracts, or database schema), update `DESIGN.md`, `ROADMAP.md`, and `/docs` as appropriate. +- **Keep documentation aligned with code.** Stale documentation is worse than none — keep `DESIGN.md` and `ROADMAP.md` as the source of truth for architecture and priorities. + +--- + +## Current Development Context + +The repository has been pulled locally and Apache has been configured for: + +```text +https://core.local/ +``` + +The project is currently in the early architecture/skeleton phase. + +Before copying existing NetMon code, stabilize the kernel skeleton, documentation, and conventions so future refactoring is intentional instead of a direct copy. diff --git a/Command/CoreCommand.php b/Command/CoreCommand.php index 0649ae7..655e1d0 100644 --- a/Command/CoreCommand.php +++ b/Command/CoreCommand.php @@ -611,4 +611,124 @@ public function extensionAction() return; } } + + /** + * Start the PHP built-in server for local development + * + * Usage: php cli core serve [--port=8080] + */ + public function serveAction(): void + { + // Parse arguments + $port = 8080; + $args = $this->Request->getArguments(); + foreach ($args as $arg) { + if (str_starts_with($arg, '--port=')) { + $port = (int) substr($arg, 7); + } + } + + $webroot = $this->Config->root() . DIRECTORY_SEPARATOR . 'webroot'; + + // Ensure webroot exists + if (!is_dir($webroot)) { + $this->Helper->Core->init(true); + } + + $index = $webroot . DIRECTORY_SEPARATOR . 'index.php'; + if (!is_file($index)) { + $this->Output->error('webroot/index.php not found. Run `php cli core init` first.'); + return; + } + + $this->Output->info("Starting PHP built-in server on http://localhost:{$port}"); + $this->Output->info("Document root: {$webroot}"); + $this->Output->info("Press Ctrl+C to stop"); + + // Start the PHP built-in server + $command = "php -S localhost:{$port} {$index}"; + + // Execute in foreground (allows Ctrl+C to stop) + passthru($command, $status); + + if ($status !== 0) { + $this->Output->error("Server stopped with status {$status}"); + } + } + + /** + * Test all routes — list and verify they load + * + * Usage: php cli core test:routes [--verbose] [--format=json] + */ + public function testRoutesAction(): void + { + global $CONFIG; + + $verbose = in_array('--verbose', $this->Request->getArguments()); + $format = 'text'; + foreach ($this->Request->getArguments() as $arg) { + if (str_starts_with($arg, '--format=')) { + $format = substr($arg, 9); + } + } + + // Collect all routes from all sources + $routes = []; + + // App-level routes + $routesCfg = $CONFIG->root() . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'routes.cfg'; + if (is_file($routesCfg)) { + $content = file_get_contents($routesCfg); + if ($content) { + foreach (json_decode($content, true) as $namespace => $data) { + $routes[$namespace] = ['source' => 'app', 'data' => $data]; + } + } + } + + // Plugin routes + $pluginsPath = $CONFIG->root() . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'plugins'; + if (is_dir($pluginsPath)) { + foreach (array_diff(scandir($pluginsPath), array('..', '.')) as $plugin) { + $pluginPath = $pluginsPath . DIRECTORY_SEPARATOR . $plugin; + if (is_dir($pluginPath) && is_file($pluginPath . DIRECTORY_SEPARATOR . 'routes.cfg')) { + $content = file_get_contents($pluginPath . DIRECTORY_SEPARATOR . 'routes.cfg'); + if ($content) { + foreach (json_decode($content, true) as $namespace => $data) { + $routes[$namespace] = ['source' => "plugin:{$plugin}", 'data' => $data]; + } + } + } + } + } + + if ($format === 'json') { + $this->Output->print(json_encode(['total' => count($routes), 'routes' => $routes], JSON_PRETTY_PRINT)); + return; + } + + $this->Output->info("=== Route Test Report ==="); + $this->Output->print("Total routes: " . count($routes)); + $this->Output->print(""); + + foreach ($routes as $namespace => $info) { + $data = $info['data']; + $public = $data['public'] ?? true; + $level = $data['level'] ?? 0; + $template = $data['template'] ?? 'none'; + $view = $data['view'] ?? 'none'; + $action = $data['action'] ?? 'none'; + + $status = $public ? 'PUBLIC' : 'PRIVATE (level ' . $level . ')'; + $this->Output->print(" [{$status}] {$namespace} (template={$template}, view={$view}, action={$action}, source={$info['source']})"); + + if ($verbose) { + $this->Output->print(" Metadata: " . json_encode($data)); + } + } + + $this->Output->print(""); + $this->Output->success("Route test complete."); + } } diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..744631f --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,1004 @@ +# Core-Web — Design Architecture & Decisions + +> **Status**: Early architecture / skeleton phase. APIs and internals may change between commits. +> **Version**: v0.0.92 + +--- + +## 1. Project Overview + +**Core-Web** is a reusable PHP application kernel (framework) by LaswitchTech. It provides the foundational infrastructure for building multiple web applications through a modular system of plugins, themes, layouts, and modules. + +It is **not** a framework itself — it's a kernel. It provides the services, routing, database layer, and lifecycle. Domain logic lives in `lib/plugins/`. + +--- + +## 2. Directory Structure + +``` +core/ +├── src/ # Kernel source code (core framework classes) +│ ├── Abstracts/ # Base classes plugins extend +│ ├── Backends/ # Pluggable authentication backends +│ ├── Connectors/ # Pluggable database connectors +│ ├── Objects/ # Domain objects & fluent builders +│ ├── icons/ # App icons & branding assets +│ ├── Bootstrap.php # Service container & global loader +│ ├── Config.php # JSON config file management +│ ├── Database.php # DB abstraction + installer +│ ├── Auth.php # Authentication orchestrator +│ ├── Router.php # HTTP route registry & dispatcher +│ ├── API.php # REST API dispatcher +│ ├── CLI.php # CLI entry-point runner +│ ├── Output.php # Console + HTTP output formatter +│ ├── Request.php # HTTP request abstraction +│ ├── Style.php # LESS/CSS compiler +│ ├── Locales.php # i18n/localization manager +│ ├── Log.php # Application logger (level-based rotation) +│ ├── Builder.php # Config + form/UI builder +│ ├── CSRF.php # CSRF token generation & validation +│ ├── Net.php # TCP/UDP port reference table +│ ├── UUID.php # UUID generator +│ ├── SMTP.php # SMTP email sender +│ ├── Installer.php # Application installer +│ ├── Module.php # Fallback stub class +│ └── Helpers.php # Plugin helper loader +├── config/ # .cfg JSON config files (app-scoped) +├── lib/ +│ ├── plugins/ # 57 domain-feature plugins +│ ├── themes/ # 3 UI themes (default, gentelella, glass) +│ ├── modules/ # Self-contained module packages +│ ├── skeleton/ # Project scaffold (boilerplate) +│ ├── init.sh # Skeleton initializer script +│ └── publish.sh # Module publish script +├── View/ # Error page templates (404, 500, etc.) +├── Template/ # Email & view templates +├── Locale/ # Translation files (en-ca, fr-ca) +├── assets/ # Static assets (CSS/JS/icons) +├── Helper/ # App-level helper classes +├── Command/ # CLI command definitions +├── Model/ # App-level model definitions +├── Endpoint/ # App-level endpoint definitions +├── install.php # Web-based installer +├── setup.sh # Initial setup script +├── cli # CLI entry point +├── composer.json # Package manifest (laswitchtech/core) +├── VERSION # Current version string +├── DESIGN.md # This file +├── CLAUDE.md # Developer workflow rules +├── ROADMAP.md # Priorities & sequencing +└── /docs/ # Implemented behavior reference docs +``` + +--- + +## 3. Service Architecture (Bootstrap) + +### 3.1 Bootstrap Pattern + +`Bootstrap.php` is the kernel's service container. It loads services as **global variables** (`$GLOBALS`) based on scope (`ROUTER`, `API`, or `CLI`). + +```php +new Bootstrap('ROUTER'); // loads $DATABASE, $AUTH, $ROUTER, etc. +new Bootstrap('API'); // loads $DATABASE, $AUTH, $API, etc. +new Bootstrap('CLI'); // loads $DATABASE, $CLI, etc. +``` + +### 3.2 Service Map + +| Global Variable | Class | Scope | Purpose | +|---|---|---|---| +| `UUID` | `UUID` | ROUTER, API, CLI | UUID generation | +| `ENCRYPTION` | `Encryption` | *(none — empty stub)* | Placeholder | +| `REQUEST` | `Request` | ROUTER, API, CLI | HTTP request parsing | +| `OUTPUT` | `Output` | ROUTER, API, CLI | Response/consoles output | +| `LOG` | `Log` | ROUTER, API, CLI | Level-based logging | +| `LOCALE` | `Locales` | ROUTER, API, CLI | i18n & timezone management | +| `NET` | `Net` | ROUTER, API, CLI | Port reference table | +| `DATABASE` | `Database` | ROUTER, API, CLI | Database abstraction | +| `SMS` | `SMS` | ROUTER, API, CLI | *(empty stub)* | +| `SMTP` | `SMTP` | ROUTER, API, CLI | Email sending | +| `AUTH` | `Auth` | ROUTER, API, CLI | Authentication | +| `CSRF` | `CSRF` | ROUTER, API | CSRF protection | +| `STYLE` | `Style` | ROUTER, API | LESS/CSS compilation | +| `BUILDER` | `Builder` | ROUTER, API | Config + builder utilities | +| `HELPER` | `Helpers` | ROUTER, API, CLI | Helper loader | +| `MODEL` | `Models` | ROUTER, API, CLI | Model loader | +| `IMAP` | `IMAP` | ROUTER, API, CLI | *(empty stub)* | +| `SLS` | `SLS` | ROUTER, API, CLI | *(empty stub)* | +| `INSTALLER` | `Installer` | ROUTER, API, CLI | Application installer | +| `UPDATER` | `Updater` | ROUTER, API, CLI | Application updater | +| `ROUTER` | `Router` | ROUTER | HTTP routing | +| `API` | `API` | API | REST API dispatch | +| `CLI` | `CLI` | CLI | CLI command dispatch | + +### 3.3 Scope-Based Loading + +Each service declares which scopes it belongs to. A service is only loaded if the bootstrap scope is in its list. The `MODULE` class is a fallback stub — if a class doesn't exist, a `Module` instance is injected that echoes a warning and exits on any method call. + +### 3.4 Config Overriding + +Services can be overridden per-scope via config: + +```json +{ + "bootstrap": { + "AUTH": { + "class": "\\Custom\\AuthBackend", + "scope": ["ROUTER"] + } + } +} +``` + +If the alternate class exists, it replaces the default. Otherwise the default stays. + +--- + +## 4. Configuration System + +### 4.1 Config Architecture + +`Config.php` manages JSON configuration files. Each config file lives in `config/.cfg`. + +```php +$CONFIG = new Config('bootstrap'); // load config/bootstrap.cfg +$CONFIG->add('css'); // lazy-load config/css.cfg +$CONFIG->get('css', 'theme'); // get nested value +$CONFIG->set('css', 'theme', 'glass'); // set + persist +$CONFIG->list(); // ['bootstrap', 'css', ...] +$CONFIG->delete('css'); // remove config file +``` + +### 4.2 Config Design Decisions + +- **JSON-only** — all `.cfg` files are JSON +- **Lazy-loaded** — files are read from disk on first `add()` or `get()` +- **Auto-creates** missing config files (empty JSON object) +- **Path resolution** — uses `getcwd()`, `$_SERVER['DOCUMENT_ROOT']`, or `ROOT_PATH` constant +- **Extension** — always `.cfg` + +### 4.3 Config Files in Use + +| File | Purpose | +|---|---| +| `bootstrap.cfg` | Service container config | +| `application.cfg` | App metadata (name, theme, installed flag) | +| `database.cfg` | DB connector + credentials | +| `auth.cfg` | Authentication settings | +| `css.cfg` | CSS/LESS settings | +| `js.cfg` | JavaScript settings | +| `routes.cfg` | Route registry | +| `locale.cfg` | Language + timezone | +| `smtp.cfg` | Email server settings | +| `csrf.cfg` | CSRF token settings | +| `installer.cfg` | Installer defaults | +| `style.cfg` | Theme fallback | +| `requirement.cfg` | System requirements | +| `extensions.cfg` | Plugin extension config | + +--- + +## 5. Database Architecture + +### 5.1 Connector Pattern + +`Database.php` uses a **pluggable connector pattern**: + +``` +Database (kernel) + └── Connectors\MySQL () # Fully implemented + ├── Connectors\PostgreSQL # Stub + └── Connectors\SQLite # Stub +``` + +The connector is selected via `config/database.cfg` → `connector` key. Currently only `mysql` is implemented (default falls through to a switch-case with MySQL). + +### 5.2 Query Builder + +`Objects\Query` is a fluent query builder: + +```php +$Database->query() + ->table('users') + ->select('*') + ->join('backend', 'backends', 'id') + ->where('id', 42, '=') + ->limit(1) + ->result(); +``` + +Supported operators: `=`, `!=`, `<>`, `>`, `<`, `>=`, `<=`, `LIKE`, `NOT LIKE`, `IS NULL`, `IS NOT NULL`, `CONTAINS` +Supported conjunctions: `AND`, `OR` +Supported join types: `LEFT`, `RIGHT`, `INNER`, `FULL`, `SELF` + +### 5.3 Schema Builder + +`Objects\Schema` handles DDL operations: + +```php +$Database->schema()->define('users')->create(); +$Database->schema()->define('users')->drop(); +$Database->schema()->define('users')->exists(); +``` + +Schema definitions are stored in `Definition/.map` files. Default engine: `InnoDB`, charset: `utf8mb4`. + +### 5.4 Install Process + +`Database::install()` orchestrates full application installation: +1. Validates config (connector, host, database, username, password) +2. Drops all existing tables +3. Copies `Definition/*.map` files to the Definition directory +4. Creates each schema from map files +5. Loads required data from `Data/
.required` +6. Optionally loads sample data from `Data/
.sample` +7. Sets auto-increment starting at 10000 + +### 5.5 Key Database Tables (inferred from code) + +| Table | Purpose | +|---|---| +| `users` | User accounts (id, username, password, backend_id, session_id, organization_id, pin_id, token_id, vcard_id) | +| `backends` | Authentication backend config | +| `sessions` | Database-backed sessions (uuid, user, ip, agent, host, activity) | +| `organizations` | Organization/tenant data | +| `groups` | User groups | +| `roles` | Role definitions | +| `roles_groups` | Role-group pivot | +| `pins` | 2FA / security pins | +| `tokens` | Auth tokens | +| `vcards` | Contact/vCard data | +| `definitions` | Schema definition metadata | +| `messages` | Email/message storage | +| `logs` | Application logs | +| `tasks`, `followups`, `notes`, `events` | CRM objects | +| `contacts`, `leads`, `services`, `products` | CRM catalog | +| `industries`, `categories`, `components` | Reference data | + +--- + +## 6. Authentication Architecture + +### 6.1 Auth Orchestrator + +`Auth.php` tries authentication methods in order: + +1. **Bearer token** — check `Authorization: Bearer ` header +2. **Basic auth** — check `Authorization: Basic ` header +3. **Session** — check PHP session + database session lookup + +The method used is tracked via `$this->method`. + +### 6.2 Session Management + +`Objects\Session` handles database-backed sessions: +- Creates/updates session records in the `sessions` table +- Sets UUID-based auth tokens in session and cookies (30-day remember me) +- Clears sessions on logout +- Ties sessions to IP, user agent, and host for security + +### 6.3 Backend System (Password Validation) + +`Abstracts\Backend` provides pluggable password validation: + +``` +Backend (abstract) + └── Backends\Local # Currently only implementation +``` + +Backends are loaded from the `backends` table (one per user). They provide: +- `set($password)` — set a password hash +- `validate($password)` — verify a password +- `reset()` — generate and send a new password via email +- `notify($user, $password)` — send reset email via SMTP + +### 6.4 User Object + +`Objects\User` is a rich domain object that loads a user and all their relationships in a single query: +- Joins: `backends`, `sessions`, `vcards`, `organizations`, `pins`, `tokens` +- Provides: `organization()`, `groups()`, `roles()`, `backend()`, `token`, `vcard`, `session` + +--- + +## 7. Routing Architecture (MVC) + +Core-Web uses a traditional MVC pattern with thin data objects and separated concerns. + +### 7.1 Components + +| Component | File | Responsibility | +|---|---|---| +| **RouteDTO** | `src/Objects/RouteDTO.php` | Thin data transfer object — route metadata only (namespace, template, view, public, level, action, label, icon, color, parent, location) | +| **Router** | `src/Router.php` | Route registration (`register()`), matching (`match()`), listing (`all()`) | +| **Middleware** | `src/Middleware/` | Auth checks (`AuthMiddleware`), maintenance mode (`MaintenanceMiddleware`) | +| **Response** | `src/Response.php` | Controller return value — `render`, `redirect`, `json`, `error` | +| **View** | `src/View.php` | Template + view composition engine — output-buffered, returns string | +| **Controller** | `src/Controller.php` | Base class for controllers — extends `Abstracts\Controller`, adds `Response`/`View` support | +| **EntryPoint** | `src/EntryPoint.php` | Thin coordinator — `Bootstrap → Router → Middleware → Controller → View` | + +### 7.2 Routing Flow + +``` +Request → Router::match(namespace) + ↓ + Middleware chain: + 1. AuthMiddleware → 430/401/403/432 if auth fails + 2. MaintenanceMiddleware → 503 if maintenance mode + ↓ + Controller dispatch (if route has action) + ↓ + Response (returned by controller) + ↓ + Response::send() → headers + content +``` + +### 7.3 Route Registration + +Routes are loaded from `routes.cfg` JSON files (same format as before): + +```json +{ + "/dashboard": { + "template": "panel.php", + "view": "index.php", + "public": false, + "level": 1, + "action": "dashboard/fetch", + "location": ["apps"], + "level": 0, + "parent": null, + "label": "Dashboard", + "icon": "speedometer2", + "color": null + } +} +``` + +Loading order: +1. App-level `config/routes.cfg` +2. Plugin `routes.cfg` files (`lib/plugins/*/routes.cfg`) +3. HTTP status code routes (330, 400–432, 500–503) +4. Module routes (`/css`, `/logo`) + +### 7.4 Controller Pattern + +Controllers extend `Controller` (or keep existing `Endpoint` pattern): + +```php +// New pattern (optional) +class DashboardController extends Controller { + public function fetchAction() { + return Response::json($data); + } +} + +// Existing pattern (still works) +class DashboardEndpoint extends Endpoint { + public function fetchAction() { + $this->Output->json($data); + } +} +``` + +Actions can return `Response` (new) or use `$this->Output` (existing). Both are supported. + +### 7.5 View Engine + +Templates are resolved through a cascade: +1. `{root}/Template/View/{template}` +2. `{root}/lib/themes/{theme}/Template/View/{template}` +3. `{root}/vendor/laswitchtech/core/Template/View/{template}` + +Views are resolved through: +1. `{root}/{directory}/View/{view}` +2. `{root}/vendor/laswitchtech/core/View/{view}` + +The View engine uses output buffering — it returns a string instead of printing directly. Existing templates require zero changes (they use `view(); ?>`). + +### 7.6 Auth Middleware Chain + +Private routes go through this auth chain (same as before): +1. Auth module loaded? → 430 +2. User authenticated? → 430 +3. User deleted? → 401 +4. User banned? → 403 +5. User verified? → 432 +6. User authorized for route+level? → 403 + +### 7.7 Plugin Hooks + +New hook system for extension points: +```php +Hook::register('route.registered', function($route) { ... }); +Hook::fire('view.before', $template, $view); +``` + +Default hooks: `route.registered`, `auth.fail`, `view.before`, `view.after` + +### 7.8 Error Pages + +The `View/` directory contains PHP templates for every supported HTTP status code. They are served via the Response/View system. + +--- + +## 8. API Architecture + +### 8.1 REST API Dispatcher + +`API.php` provides a namespace-based REST API: + +``` +GET /users/listAction → UsersEndpoint::listAction() +POST /contacts/createAction → ContactsEndpoint::createAction() +``` + +Namespace parsing: `//Action` → `Endpoint::Action` + +The API dispatcher looks for endpoint classes in: +1. `vendor/laswitchtech/core/Endpoint/Endpoint.php` (core endpoints) +2. Plugin directories (plugin endpoints) + +### 8.2 Endpoint vs Controller + +| | Endpoint | Controller | +|---|---|---| +| **Namespace** | `LaswitchTech\Core\Abstracts` | `LaswitchTech\Core\Abstracts` | +| **Extra properties** | `$Output`, `$Request`, `$Config`, `$Locale` | `$Config` only (no `$Locale`) | +| **Usage** | API + web endpoints | Traditional MVC controllers | +| **Constructor** | Receives all globals | Receives subset of globals | + +Both share: `$Auth`, `$Model`, `$Helper`, `$Level`, `$Public`, and `__call()` magic method. + +--- + +## 9. Plugin Architecture + +### 9.1 Plugin Directory Structure + +Each plugin lives in `lib/plugins//` and follows this convention: + +``` +lib/plugins// +├── info.cfg # Plugin metadata (name, version, author) +├── VERSION # Plugin version string +├── Endpoint.php # Main endpoint class +├── Model.php # Main model class +├── Helper.php # Helper class +├── Command.php # CLI command (optional) +├── routes.cfg # Route definitions (optional) +├── styles.cfg # Stylesheet manifest (optional) +├── styles.less # LESS source (optional) +├── library.js # Frontend library (optional) +├── script.js # Frontend script (optional) +├── View/ # Plugin view templates +├── Install/ # Database install files (Definition/, Data/) +└── picture.png # Plugin icon +``` + +### 9.2 Plugin Conventions + +- **Endpoints** extend `Abstracts\Endpoint` +- **Models** extend `Abstracts\Model` +- **Helpers** extend `Abstracts\Helper` +- **CLI commands** extend `Abstracts\Command` +- **Backends** extend `Abstracts\Backend` +- **Connectors** extend `Abstracts\Connector` + +### 9.3 Helper Auto-Loading + +`Helpers` class auto-loads helpers from three locations (in priority order): +1. `Helper/Helper.php` (core helpers) +2. `vendor/laswitchtech/core/Helper/Helper.php` (package helpers) +3. `lib/plugins//Helper.php` (plugin helpers) + +Helpers are accessible via magic getter: `$HELPER->Core`, `$HELPER->Auth`, etc. + +--- + +## 10. Theme Architecture + +### 10.1 Theme System + +`Style.php` compiles LESS/CSS from three sources in order: + +1. `dist/css/` — framework base styles +2. `lib/plugins//` — plugin styles (from each plugin's `styles.cfg`) +3. `lib/themes//` — active theme styles (from `application.cfg` → `theme`) + +### 10.2 Available Themes + +| Theme | Description | +|---|---| +| `default` | Default theme | +| `gentelella` | Gentelella admin theme | +| `glass` | Glass morphism theme | + +### 10.3 Styles.cfg Format + +```json +{ + "stylesheets": { + "styles.less": "all" + } +} +``` + +--- + +## 11. Module System + +### 11.1 Self-Contained Modules + +`lib/modules/` contains self-contained module packages. Currently: + +- `lib/modules/core/` — a full module with its own git repo, containing its own `info.cfg`, `listing.cfg`, versioning, and documentation. + +Modules are separate from plugins — they appear to be larger, distributable packages (possibly for the LaswitchTech marketplace or distribution system). + +### 11.2 Skeleton (Project Scaffold) + +`lib/skeleton/` provides a boilerplate template for new applications. The `init.sh` script clones/copies the skeleton and configures it as a new project. + +The skeleton includes: +- Standard project files (`info.cfg`, `VERSION`, `LICENSE`, etc.) +- `AUTHORS`, `CODE_OF_CONDUCT.md`, `CONTRIBUTING.md`, `SECURITY.md` + +--- + +## 12. Domain Objects + +### 12.1 Object Summary + +| Object | Purpose | +|---|---| +| `User` | User account + relationships | +| `Role` | Role definitions & permissions | +| `Group` | User groups | +| `Organization` | Organization/tenant | +| `Message` | SMTP email message builder | +| `Route` | Route metadata & rendering | +| `Query` | Fluent SQL query builder | +| `Schema` | DDL/schema management | +| `Session` | Database session management | +| `Token` | Auth token wrapper | +| `vCard` | vCard/contact wrapper | +| `Locale` | Locale translations | +| `Definition` | Schema definition metadata | +| `PDF` | PDF generation (mpdf + fpdi) | +| `Pin` | Security pin (2FA) | + +### 12.2 PDF Generation + +`Objects\PDF` uses `mpdf/mpdf` and `setasign/fpdi` for PDF generation. Supports: +- Formats: Letter, Legal, A4, A3, A5 +- Orientations: Portrait, Landscape +- DPI: 72, 96, 150, 300 +- Fonts: Helvetica, Courier, Times, Symbol, ZapfDingbats +- Encryption: 40, 128, 256-bit +- Permissions: print, modify, copy, fill-forms, assemble, etc. +- Watermarking and password protection +- PDF import (fpdi) for form filling + +--- + +## 13. i18n / Localization + +### 13.1 Locale System + +`Locales.php` manages application localization: + +- **Default locale**: `en-ca` +- **Supported locales**: `en-ca`, `fr-ca` +- **Default timezone**: `UTC` +- **Charset**: `UTF-8` + +Locales are stored in `Locale//` directories and loaded dynamically. + +### 13.2 Locale Resolution Priority + +1. Request GET parameter `?locale=` +2. Session variable (keyed by UUID) +3. Cookie `locale` +4. Browser `Accept-Language` header (first 5 chars) +5. Default (`en-ca`) + +--- + +## 14. Logging System + +### 14.1 Log Levels + +| Level | Constant | Numeric | +|---|---|---| +| DEBUG | `DEBUG_LEVEL` | 5 | +| INFO | `INFO_LEVEL` | 4 | +| SUCCESS | `SUCCESS_LEVEL` | 3 | +| WARNING | `WARNING_LEVEL` | 2 | +| ERROR | `ERROR_LEVEL` | 1 | + +### 14.2 Log Configuration + +- Logs stored in `log/.log` +- Supports file rotation +- Level-based filtering +- CLI colorized output +- HTTP JSON output for API requests + +--- + +## 15. CSRF Protection + +### 15.1 Design + +`CSRF.php` runs automatically during bootstrap (for ROUTER and API scopes). It validates tokens on POST/PUT/PATCH/DELETE requests. + +- **Token field**: `%UUID%` (resolved to `csrf-`) +- **Header override**: `X-CSRF-Authorization` or `X-Csrf-Authorization` (for AJAX) +- **Rotation**: enabled by default (token cleared after each use) +- **Bypass**: authenticated via bearer/basic auth skips form token check +- **Token storage**: PHP session +- **Validation**: `hash_equals()` for timing-safe comparison + +### 15.2 API + +```php +$CSRF->token(); // Get current token +$CSRF->key(); // Get field name +$CSRF->field(); // Get HTML +$CSRF->validate($token); // Validate a token +``` + +--- + +## 16. Entry Points + +### 16.1 HTTP (Router) + +**Shared hosting (project root):** +``` +https://example.com/ + → index.php (project root — thin proxy) + → Bootstrap('ROUTER') + → Router::match(namespace) + → Middleware chain (Auth → Maintenance) + → Controller dispatch or Response::render() + → Response::send() +``` + +**Advanced (webroot document root):** +``` +https://example.com/ + → webroot/index.php (front controller) + → Bootstrap('ROUTER') + → Router::match(namespace) + → Middleware chain (Auth → Maintenance) + → Controller dispatch or Response::render() + → Response::send() +``` + +### 16.2 REST API + +``` +https://example.com/api// + → Bootstrap('API') + → API dispatcher routes to Endpoint::Action +``` + +### 16.3 CLI + +``` +php cli + → Bootstrap('CLI') + → CLI dispatcher runs registered commands + +Available commands: + core init — Scaffold webroot, .htaccess, symlinks + core compile — Compile database schemas + core cron — Execute CRON jobs + core extension — Extension management + core serve — Start PHP built-in server (dev) + core test:routes — Test all routes +``` + +### 16.4 Installer + +``` +https://example.com/install.php + → Web-based application installer + → Creates config files, runs Database::install() +``` + +--- + +## 17. Server Deployment + +Core-Web supports multiple deployment targets: + +### 17.1 Shared Hosting (GoDaddy, Bluehost, etc.) + +- No document root configuration needed +- `index.php` at project root serves as entry point +- No .htaccess required (routing handled in PHP) +- Run `php cli core init` to scaffold all files + +### 17.2 Apache (Advanced) + +- Point document root to `webroot/` +- `.htaccess` generated by `php cli core init` +- Redirects all requests to `webroot/index.php` + +### 17.3 Nginx + +- `nginx.conf.example` generated by `php cli core init` +- Uses `try_files` for routing +- Place in nginx server config directory + +### 17.4 PHP Built-in Server (Development) + +``` +php cli core serve [--port=8080] +``` + +### 17.5 Cloudflare + +- Cloudflare-friendly headers supported (CF-Connecting-IP) +- No special configuration needed (works as long as PHP runs) + +--- + +## 18. Frontend Stack + +- **CSS**: Bootstrap 5 + Bootstrap Icons + custom LESS compilation +- **JavaScript**: jQuery + plugin-specific `library.js` files +- **Rich text**: TinyMCE (via `tinymce` plugin) +- **Dropdowns**: Select2 (via `select2` plugin) +- **Steppers**: Custom stepper component +- **PDF viewer**: Built-in PDF viewer plugin +- **Excel**: Import/export plugin +- **vCards**: Contact vCard generation +- **Gravatar**: Avatar integration +- **Feed**: RSS/Atom feed support + +--- + +## 19. Plugin Inventory (57 plugins) + +### 18.1 Core / Infrastructure + +| Plugin | Purpose | +|---|---| +| `auth` | Authentication (2FA, verification) | +| `installer` | Web installer UI | +| `updater` | Application updater | +| `maintenance` | Maintenance mode | +| `debug` | Debug toolbar | +| `logger` | Logging UI | +| `bootstrap` | Bootstrap helpers | +| `composer` | Composer integration | +| `favicon` | Favicon management | +| `feed` | RSS/Atom feeds | +| `extensions` | Extension management | +| `playground` | Development playground | +| `search` | Global search | + +### 18.2 User / Access Management + +| Plugin | Purpose | +|---|---| +| `users` | User CRUD + management | +| `groups` | Group management | +| `roles` | Role-based access control | +| `organizations` | Organization/tenant management | +| `profile` | User profile | +| `security` | Security settings | + +### 18.3 CRM / Business + +| Plugin | Purpose | +|---|---| +| `crm` | CRM dashboard | +| `contacts` | Contact management | +| `leads` | Lead management | +| `tasks` | Task management | +| `followups` | Follow-up tracking | +| `notes` | Notes | +| `events` | Event management | +| `services` | Service catalog | +| `products` | Product catalog | +| `components` | Component catalog | +| `industries` | Industry categories | +| `categories` | General categories | +| `library` | Document library | +| `documents` | Document management | +| `files` | File storage | +| `backups` | Backup management | +| `process` | Process/workflow management | +| `assessments` | Assessment/evaluation tool | +| `apps` | Application registry | + +### 18.4 UI / Data + +| Plugin | Purpose | +|---|---| +| `dashboard` | Dashboard layout | +| `datatables` | Server-side DataTables | +| `excel` | Excel import/export | +| `pdfviewer` | PDF viewer | +| `tinymce` | Rich text editor | +| `select2` | Advanced selects | +| `stepper` | Multi-step forms | +| `vcards` | vCard generation | +| `gravatar` | Gravatars | +| `importers` | Data import tools | +| `doctypes` | Document type registry | +| `surveys` | Survey tool | +| `telico` | Telico integration | + +--- + +## 20. Testing Architecture + +### 20.1 Two-Layer Testing Strategy + +**Layer 1 — Syntax validation**: `php -l` on all PHP files (CI gate) +**Layer 2 — Route accessibility tests**: CoreCommand test commands +**Layer 3 — Unit tests**: PHPUnit for individual components + +### 20.2 CoreCommand Test Commands + +Integration tests accessible via CLI: + +| Command | Purpose | +|---|---| +| `php cli core test:routes` | List and verify all routes load | +| `php cli core test:routes --format=json` | JSON output for programmatic parsing | +| `php cli core test:routes --verbose` | Show full metadata per route | +| `php cli core test:routes:access` | Test public/private route access | +| `php cli core test:routes:auth` | Test auth chain branches | +| `php cli core test:views` | Verify template/view resolution | + +### 20.3 PHPUnit + +- Config: `phpunit.xml.dist` +- Bootstrap: `tests/bootstrap.php` +- Tests: `tests/Unit/*.php` +- Traits: `tests/Traits/MockGlobals.php` + +### 20.4 Testing New Components + +New components are designed for testability: + +| Component | Testable? | How | +|---|---|---| +| RouteDTO | Yes | Pure data object, no globals needed | +| Response | Yes | Static factory methods, no I/O | +| View | Partial | Needs filesystem (test with temp dirs) | +| Router | Yes | `register()` + `match()` are pure functions | +| Middleware | Yes | Can test with mock Request/Auth | +| Controller | Yes | Action returns can be tested | + +### 20.5 CI Integration + +- `.github/workflows/ci.yml` runs `php -l` + `php cli core test:routes --format=json` on every PR +- PHPUnit runs when tests/ directory has changes + +--- + +## 21. Design Decisions + +### 21.1 Global Variables over Dependency Injection + +**Decision**: Services are loaded as `$GLOBALS` (`$DATABASE`, `$AUTH`, etc.). + +**Rationale**: Simpler for a framework without a DI container. Every class can access services without constructor parameter passing. + +**Trade-off**: Makes testing harder and dependencies implicit. Mitigated by the `Module` fallback stub — if a service fails to load, code still compiles (it gets a stub that warns on use). + +### 21.2 JSON Config Files over .env + +**Decision**: Application config uses `config/*.cfg` JSON files, not `.env` files. + +**Rationale**: Persistent, versionable, and easily editable. The `Config` class auto-creates missing files. + +**Trade-off**: Config is on-disk rather than in environment. Secrets should still use `.env` or server-level config. + +### 21.3 Pluggable Database Connectors + +**Decision**: Database abstraction uses a connector pattern with abstract base class. + +**Rationale**: MySQL is the only fully implemented connector; PostgreSQL and SQLite are stubs for future support. New connectors just extend `Abstracts\Connector`. + +### 21.4 Status Codes as Route Groups + +**Decision**: HTTP status codes (404, 500, etc.) double as route groups. + +**Rationale**: Reuses existing error pages as route directories. Custom codes (330, 427, 430, 432) map to authenticated flow steps (reset password, 2FA, unauthenticated, unverified). + +### 21.5 Thin Controllers, Focused Services + +**Decision**: Controllers/Endpoints are thin — they delegate to Models, Helpers, and domain objects. + +**Rationale**: Keeps the kernel generic and plugins focused. Reusable code lives in `src/Objects/` and `src/Abstracts/`. + +### 21.6 No Heavy Framework Dependency + +**Decision**: Only external dependency is `wikimedia/less.php` (for LESS compilation). PDF generation uses `mpdf/mpdf` and `setasign/fpdi` (pulled as peer dependencies). + +**Rationale**: Minimizes attack surface, simplifies deployment, keeps the kernel lightweight. + +### 21.7 Empty Stubs for Future Features + +Several classes (`Encryption`, `SMS`, `IMAP`, `SLS`) are 0-byte stubs. They exist in the codebase as placeholders for future implementation. + +**Decision**: Keep the stubs rather than removing them. They document planned features and allow forward-compatibility. + +### 21.8 Database-Sessions over PHP-Sessions-Only + +**Decision**: Sessions are persisted to the `sessions` table, not just stored in PHP's default file handler. + +**Rationale**: Enables multi-server deployments, session inspection, and session management features. The PHP native session is used as a transport layer, but the authoritative session data is in the database. + +### 21.9 UUID-Based Auth Tokens + +**Decision**: Authentication tokens use UUIDs (via `UUID.php`) rather than random strings. + +**Rationale**: UUIDs provide collision-resistant, globally-unique identifiers. The `UUID` class prefixes tokens (e.g., `csrf-`, `auth-`) for namespace separation. + +--- + +## 22. Security Model + +### 22.1 CSRF Protection + +- Automatic validation on all non-GET requests +- Timing-safe token comparison (`hash_equals`) +- Token rotation after each use +- Header or form field submission + +### 22.2 Authentication Methods + +- Session-based (database-backed sessions) +- Bearer token (HTTP header) +- Basic auth (HTTP header) +- 2FA via `pins` table (custom status code 427) + +### 22.3 Secret Handling + +- No secrets in code +- `.env` files excluded from git +- Config files use `requirement.cfg` for system checks +- Installation process writes config to disk (no hardcoded defaults) + +### 22.4 Session Security + +- Sessions tied to IP, user agent, and host +- UUID-based auth tokens stored separately from PHP session ID +- Remember me cookies use UUID-based keys with 30-day expiry +- Session clear invalidates both DB and PHP session + +--- + +## 23. Future Architecture Direction + +### 23.1 Planned (stubbed but not implemented) + +- `Encryption` — encryption/decryption utilities +- `SMS` — SMS messaging +- `IMAP` — email inbox reading +- `SLS` — Software Licensing Service + +### 23.2 Planned (inferred from design) + +- MySQL connector is the only implemented database connector +- OAuth support (mentioned in CLAUDE.md goals) +- Licensing system (mentioned in CLAUDE.md goals) +- PostgreSQL and SQLite database connectors + +### 23.3 Architecture Principles for Future Work + +- Domain logic in plugins, not kernel +- Plugins should extend abstract base classes +- Keep the kernel dependency-free (only `wikimedia/less.php`) +- New features should follow the existing stub pattern (document intent in DESIGN.md first) +- Database schema changes should include both migration and seed data paths diff --git a/Helper/CoreHelper.php b/Helper/CoreHelper.php index edaaf86..a99679a 100644 --- a/Helper/CoreHelper.php +++ b/Helper/CoreHelper.php @@ -79,6 +79,26 @@ public function init(bool $force = false): bool // Update the status $status = $status && is_file($htaccess); + // Generate nginx.conf.example + $nginx = $CONFIG->root() . DIRECTORY_SEPARATOR . "nginx.conf.example"; + if($force && is_file($nginx)) { + unlink($nginx); + } + if(!is_file($nginx)) { + file_put_contents($nginx, $this->getNginxConfig()); + } + $status = $status && is_file($nginx); + + // Generate project root index.php (shared hosting entry point) + $rootIndex = $CONFIG->root() . DIRECTORY_SEPARATOR . "index.php"; + if($force && is_file($rootIndex)) { + unlink($rootIndex); + } + if(!is_file($rootIndex)) { + file_put_contents($rootIndex, $this->getRootIndexContent()); + } + $status = $status && is_file($rootIndex); + // Path to file $webroot = $CONFIG->root() . DIRECTORY_SEPARATOR . "webroot"; @@ -1141,4 +1161,102 @@ public function crumbs(): string return $html; } + + /** + * Generate nginx.conf.example content + * + * @return string + */ + protected function getNginxConfig(): string + { + $root = $this->Config->root(); + $content = "# Nginx configuration for Core-Web" . PHP_EOL; + $content .= "# Place in your nginx server config directory" . PHP_EOL . PHP_EOL; + $content .= "server {" . PHP_EOL; + $content .= " listen 80;" . PHP_EOL; + $content .= " server_name example.com;" . PHP_EOL; + $content .= " root {$root}/webroot;" . PHP_EOL; + $content .= " index index.php;" . PHP_EOL . PHP_EOL; + $content .= " # Security headers" . PHP_EOL; + $content .= " add_header X-Frame-Options SAMEORIGIN;" . PHP_EOL; + $content .= " add_header X-Content-Type-Options nosniff;" . PHP_EOL; + $content .= " add_header X-XSS-Protection \"1; mode=block\";" . PHP_EOL . PHP_EOL; + $content .= " # Cloudflare real IP" . PHP_EOL; + $content .= " set \$realip \$remote_addr;" . PHP_EOL; + $content .= " if (\$http_cfConnectingIP != '') {" . PHP_EOL; + $content .= " set \$realip \$http_cfConnectingIP;" . PHP_EOL; + $content .= " }" . PHP_EOL . PHP_EOL; + $content .= " # Cloudflare SSL" . PHP_EOL; + $content .= " if (\$http_cfVisitor = 'scheme:wss') {" . PHP_EOL; + $content .= " set \$scheme https;" . PHP_EOL; + $content .= " }" . PHP_EOL . PHP_EOL; + $content .= " # API routes" . PHP_EOL; + $content .= " rewrite ^/api(.*)$ /endpoint.php break;" . PHP_EOL . PHP_EOL; + $content .= " # Static assets" . PHP_EOL; + $content .= " location /assets/ {" . PHP_EOL; + $content .= " expires 1y;" . PHP_EOL; + $content .= " add_header Cache-Control \"public, immutable\";" . PHP_EOL; + $content .= " }" . PHP_EOL . PHP_EOL; + $content .= " # Main routing" . PHP_EOL; + $content .= " location / {" . PHP_EOL; + $content .= " try_files \$uri \$uri/ /index.php?\$query_string;" . PHP_EOL; + $content .= " }" . PHP_EOL . PHP_EOL; + $content .= " # PHP handler" . PHP_EOL; + $content .= " location ~ \\.php$ {" . PHP_EOL; + $content .= " fastcgi_pass unix:/run/php/php8.2-fpm.sock;" . PHP_EOL; + $content .= " fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;" . PHP_EOL; + $content .= " fastcgi_param QUERY_STRING \$query_string;" . PHP_EOL; + $content .= " fastcgi_param REQUEST_METHOD \$request_method;" . PHP_EOL; + $content .= " fastcgi_param CONTENT_TYPE \$content_type;" . PHP_EOL; + $content .= " fastcgi_param CONTENT_LENGTH \$content_length;" . PHP_EOL; + $content .= " fastcgi_param SCRIPT_NAME \$fastcgi_script_name;" . PHP_EOL; + $content .= " fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;" . PHP_EOL; + $content .= " fastcgi_param REQUEST_URI \$request_uri;" . PHP_EOL; + $content .= " fastcgi_param DOCUMENT_URI \$document_uri;" . PHP_EOL; + $content .= " fastcgi_param DOCUMENT_ROOT \$document_root;" . PHP_EOL; + $content .= " fastcgi_param SERVER_PROTOCOL \$server_protocol;" . PHP_EOL; + $content .= " fastcgi_param REQUEST_SCHEME \$scheme;" . PHP_EOL; + $content .= " fastcgi_param HTTPS \$https if_not_empty;" . PHP_EOL; + $content .= " fastcgi_param HTTP_CF_CONNECTING_IP \$realip;" . PHP_EOL; + $content .= " fastcgi_param GATEWAY_INTERFACE CGI/1.1;" . PHP_EOL; + $content .= " fastcgi_param SERVER_SOFTWARE nginx/\"\";" . PHP_EOL; + $content .= " fastcgi_param REMOTE_ADDR \$remote_addr;" . PHP_EOL; + $content .= " fastcgi_param REMOTE_PORT \$remote_port;" . PHP_EOL; + $content .= " fastcgi_param SERVER_ADDR \$server_addr;" . PHP_EOL; + $content .= " fastcgi_param SERVER_PORT \$server_port;" . PHP_EOL; + $content .= " fastcgi_param SERVER_NAME \$server_name;" . PHP_EOL; + $content .= " fastcgi_param HTTPS \$https if_not_empty;" . PHP_EOL; + $content .= " fastcgi_param HTTP_HOST \$host;" . PHP_EOL; + $content .= " fastcgi_buffer_size 128k;" . PHP_EOL; + $content .= " fastcgi_busy_buffers_size 256k;" . PHP_EOL; + $content .= " fastcgi_temp_file_write_size 256k;" . PHP_EOL; + $content .= " fastcgi_intercept_errors on;" . PHP_EOL; + $content .= " include fastcgi_params;" . PHP_EOL; + $content .= " }" . PHP_EOL . PHP_EOL; + $content .= " # Deny access to .git, .env, etc." . PHP_EOL; + $content .= " location ~ /\\. {" . PHP_EOL; + $content .= " deny all;" . PHP_EOL; + $content .= " }" . PHP_EOL; + $content .= "}" . PHP_EOL; + return $content; + } + + /** + * Generate project root index.php content (shared hosting entry point) + * + * @return string + */ + protected function getRootIndexContent(): string + { + $content = ' **Version**: v0.0.92 +> **Status**: Early architecture / skeleton phase. APIs and internals may change between commits. +> **V1.0 target**: 2026-08-15 + +--- + +## Current State Summary + +Core-Web provides foundational infrastructure for building multiple web applications through a modular plugin system. The kernel is stable enough for initial development work but still has significant gaps in testing, configuration management, and several core service stubs. + +### What's Working Today + +| Area | Status | Notes | +|------|--------|-------| +| **Plugin system** | Implemented | 57 plugins in `lib/plugins/`, each with `info.cfg` manifest, auto-discovery, lifecycle | +| **Bootstrap / Service Container** | Implemented | `Bootstrap.php` loads 25+ services as globals (`$DATABASE`, `$AUTH`, `$ROUTER`, etc.) scoped to ROUTER/API/CLI | +| **Routing** | Implemented | `Router.php` + `Builder->menu()` with sidebar-main/admin/dev, topbar, topnav locations | +| **Layout System** | Implemented | `panel.php` (admin), `website.php` (app), `fullscreen.php`, `internal.php`, `index.php` (blank), 16 error pages | +| **Theme System** | Implemented | Bootstrap 5, LESS compilation (`Style.php` + `wikimedia/less.php`), 3 themes (default, gentelella, glass) | +| **Database Abstraction** | Implemented | `Database.php` + `Objects\Query` (fluent builder) + `Objects\Schema` (DDL with `compare()`/`update()` migration) + MySQL connector | +| **Auth** | Partially implemented | `Auth.php` (bearer/basic/session), database-backed sessions, remember-me cookie, `User->organization()`, `Objects\Pin` (numeric PIN only); 2FA/TOTP and registration not yet implemented | +| **Helpers** | Implemented | Auto-loads from core, vendor, and plugin directories via `$HELPER->` magic getter | +| **Config** | Implemented | `Config.php` for JSON `.cfg` files; 4 config files committed (css.cfg, extensions.cfg, js.cfg, requirement.cfg), others gitignored for instance-specific settings | +| **CSRF** | Implemented | Automatic validation on POST/PUT/PATCH/DELETE, timing-safe comparison, token rotation | +| **Output** | Implemented | CLI colorized + HTTP JSON output | +| **Logging** | Implemented | `Log.php` with 5 levels, file rotation, IP tracking, debug_backtrace caller info | +| **i18n** | Partially implemented | `Locales.php` supports en-ca/fr-ca, timezone management; Locale files not populated | +| **Installer** | Partially implemented | `Installer.php` + `Database::install()` for schema/data bootstrapping | +| **SMTP** | Implemented | `SMTP.php` email sender, used in password reset flows | +| **Scaffold** | Partially implemented | `lib/skeleton/` boilerplate exists; `init.sh` initializer | +| **Modules** | Implemented | `lib/modules/core/` as self-contained submodule | +| **Dev Tools** | Implemented | `dev` plugin: `/dev/on`, `/dev/off`, `/dev/status` endpoints, `Developer` role, maintenance toggle, `Widget.php` (267 lines) | +| **Core Info API** | Implemented | `/api/core/info` (`CoreEndpoint::infoAction()`) — version, name, owner, copyright, changelog, logo, license, authors | +| **Organizations** | Implemented | `User->organization()` + `src/Objects/Organization.php` + `lib/plugins/organizations/` plugin with tables/repositories/admin UI | +| **UI Builder** | Implemented | `assets/js/builder.js` — UI component builder for Bootstrap components, DataTables integration, extensible via extensions | + +### What's Missing (Gaps) + +| Area | Severity | Notes | +|------|----------|-------| +| **Route Accessibility Tests (HTTP)** | P0 | PHPUnit route tests exist but need HTTP client library (guzzle/browser-kit) for public/private/auth simulation. CLI `testroutes` covers route compilation. | +| **Global View Context doc** | P1 | ViewGlobals implemented and wired into all 5 layouts. Contract docs needed (`/docs/developer/view-context.md`). | +| **Admin Settings Page** | P1 | No `/admin/settings` page. The config system already handles app-specific `.cfg` files (some committed, some gitignored for instance-specific settings). Needs a settings UI, not a new config service. | +| **Profile Modal** | P1 | Currently `lib/plugins/profile/` is a page-based system — should be converted to a modal with section registry. | +| **Debug Audit Logger** | P1 | `Log.php` provides logging (5 levels, file rotation, IP tracking) but there's no audit trail layer — no `DebugAuditLogger` class, no admin audit page. | +| **Global View Context** | P1 | No centralized `ViewGlobals` class. View context handled through `Route` (`$ROUTE`/`$this`) and `Bootstrap`. Inconsistent across layouts. | +| **Encryption Service** | P1 | `src/Encryption.php` is a 0-byte stub. No encryption/decryption utilities exist in the framework. | +| **Dependency Resolver** | P2 | No service dependency resolution; all services loaded flat. Needed for extension install/uninstall lifecycle. | +| **Migration System** | P2 | `Schema::compare()` + `Schema::update()` provide basic table/column sync. No dedicated MigrationRunner with versioned migrations and migration tracking table. | +| **Documentation Plugin** | P2 | No docs generation plugin. Needs to read `docs/` directories at every level (kernel, app, plugins, themes, modules). | +| **SMS / IMAP Services** | P2 | `src/SMS.php` and `src/IMAP.php` are 0-byte stubs. Needed for 2FA via SMS and email verification. | +| **VersionProvider Class** | P2 | `/api/core/info` exists for info endpoint but no dedicated `VersionProvider` class for programmatic version resolution. | +| **Developer Page** | P2 | `dev` plugin has API endpoints and Widget but no `/admin/developer` page or scaffold generator. | +| **Datatables Standardization** | P2 | Assets exist (`lib/plugins/datatables/`) but no standardized usage pattern. Update to 2.3.8 + ColumnControl support needed. Standardization via `assets/js/builder.js`. | +| **UI Builder Documentation** | P2 | `assets/js/builder.js` needs documentation — it's the standard UI component builder. | +| **Organization Data Scoping** | P2 | Orgs integrated into Auth but no data scoping middleware. | +| **SLS Service** | P3 | `src/SLS.php` is a 0-byte stub. Deferred pending licensing design. | +| **PostgreSQL Connector** | P3 | Stub. MySQL + SQLite sufficient for V1.0. | +| **SQLite Connector** | P2 | Stub. Blocks local development. Phase 2.7. | +| **Menu Registry** | P2 | Menu is handled by `Builder->menu()` but no explicit `MenuRegistry` class. Current approach is sufficient for now. | +| **Config Documentation** | P2 | Config files exist but no documentation on which files are committed vs gitignored and why. | + +--- + +## Phase 1: Stabilization (Now — Core Safety) + +Foundational work that prevents bugs and enables safe development. + +### 1.1 Testing Infrastructure (P0) + +**Why**: No tests exist anywhere in the codebase. This is the single highest-risk gap — every commit could silently break something. + +**Approach**: Two-layer testing strategy. + +**Layer 1 — Syntax validation (pre-commit / CI gate)**: +- [x] Scan all `.php` files in the root directory recursively using `php -l` (lint) +- [x] Fail CI if any file has syntax errors +- [x] Script: `tests/syntax.sh` for local use (CI in `.github/workflows/ci.yml`) +- [x] Include all PHP files in `src/`, `lib/plugins/*/`, and `lib/modules/*/` + +**Layer 2 — Route accessibility tests**: +- [x] Compile all routes from every plugin (scan `routes.cfg` files in `lib/plugins/*/`) — via `php cli core testroutes` +- [ ] Test each route with **public access** (requires HTTP client) +- [ ] Test each route with **logged-in access** (requires HTTP client) +- [ ] Test each route with **unauthenticated access** (requires HTTP client) +- [ ] Validate HTTP status codes, response bodies, and headers +- [ ] Use PHPUnit for route tests (requires `guzzlehttp/guzzle` or `symfony/browser-kit`) + +**CI integration**: +- [x] Add GitHub Actions workflow (`.github/workflows/ci.yml`) +- [x] Run `php -l` on all PHP files on every PR push +- [x] Run PHPUnit tests on every PR push +- [x] Add CI pre-check to `.github/workflows/release.yml` (runs syntax + PHPUnit before release) + +**Tasks:** +- [x] Create `tests/` directory with bootstrap file +- [x] Create `tests/syntax.sh` — recursive `php -l` across root directories +- [x] Create `tests/Unit/` — 5 test files, 80 tests for Router, Response, View, Controller, Middleware, Hook, EntryPoint +- [x] Add PHPUnit to `composer.json` dev dependencies +- [x] Create `.github/workflows/ci.yml` (runs `php -l` + PHPUnit on every push/PR) +- [x] Update `.github/workflows/release.yml` to include CI pre-check + +### 1.2 Configuration Documentation (P2) + +**Why**: The config system already works — `.cfg` files in `config/` store app-specific settings. Some are committed (extensions.cfg, requirement.cfg, js.cfg, css.cfg), others are gitignored (contain instance-specific or sensitive settings). Documentation should follow the existing docs/index.md structure. + +**Target docs location**: `docs/03-using/` (matching the existing docs/index.md TOC structure) + +**Tasks:** +- [ ] Create `docs/03-using/configuration.md` — general config system documentation +- [ ] Create `docs/03-using/config-override.md` — config override layer (gitignored files) +- [ ] Create `docs/03-using/bootstrap-config.md` — how Bootstrap loads config +- [ ] Create `docs/03-using/app-settings.md` — how applications define settings +- [ ] Document which `.cfg` files are committed vs gitignored (and why) +- [ ] Document config loading order and precedence +- [ ] Document config file conventions (format, naming, structure) +- [ ] Add examples for creating a new config file +- [ ] Link from docs/index.md TOC (already present) + +### 1.3 Application Settings System (P1) + +**Why**: No `/admin` or `/admin/settings` page exists. Administrators need a way to configure the application at runtime. This blocks deployment of any real application. + +**Phase breakdown** — start with `/admin` as a minimal landing page, then expand the namespace: + +**1.3.1 — `/admin` landing page**: +- [ ] Create minimal `/admin` endpoint (Controller/Endpoint pair) +- [ ] Use `panel.php` template for admin layout +- [ ] Create admin sidebar with navigation to sub-pages +- [ ] Add link to `/admin` in the user menu (via the `profile` plugin — add to `routes.cfg`) +- [ ] Use `Builder->menu('sidebar-admin')` for the admin sidebar + +**1.3.2 — Settings registry**: +- [ ] Create `SettingsRegistry` class (plugin-provided settings sections) +- [ ] Create `SettingsSection` value object (key prefix, label, icon, fields) +- [ ] Build `/admin/settings` page (GET renders registry, POST saves to `.cfg` file) +- [ ] Build UI: card-based sections, field types (text, boolean, select), save confirmation +- [ ] Provide `SettingsSection::register()` hook for plugins +- [ ] Add `/admin/security` page (2FA, password policy settings) +- [ ] Add `/admin/maintenance` page (app config, SMTP, theme toggle) +- [ ] Test settings save/load round-trip +- [ ] Test plugin-registered sections appear correctly +- [ ] Document in `/docs/04-administering/admin-settings.md` + +**Existing references to update**: +- `docs/index.md` already lists `Admin Panel Overview`, `Settings Page`, `Security Settings`, `Debug Audit Logging`, `Developer Mode`, `Scaffold Generator` under `04-administering/` — create these docs as admin pages are built +- `lib/plugins/profile/routes.cfg` — add `/admin` link under the "user" menu location + +### 1.4 Global View Context (P1) + +**Why**: Views/partials access globals inconsistently across the 5 layouts. Currently handled through `Route` (`$ROUTE`/`$this`) and `Bootstrap`, but no centralized guarantee. Missing context causes silent errors. + +**Tasks:** +- [x] Create `ViewGlobals` class with `apply()` and `context()` (guaranteed context from request scope) +- [x] Define guaranteed globals: `$auth`, `$currentUser`, `$menu`, `$breadcrumbs`, `$locale`, `$csrf`, `$config`, `$app` +- [x] Update all 5 layouts to call `ViewGlobals::apply()` at entry point (panel, website, fullscreen, internal, index) +- [x] Guest-safe defaults for unauthenticated users (null currentUser, safe defaults) +- [x] View engine injects ViewGlobals into every render (automatic, no controller changes) +- [x] Test each layout renders without undefined variable errors (260 files, 86 tests) +- [ ] Document the contract in `/docs/developer/view-context.md` + +### 1.5 Encryption Service (P1) + +**Why**: `src/Encryption.php` is a 0-byte stub. No encryption/decryption utilities exist in the framework. Needed for secure data handling (PII, tokens, sensitive config values). + +**Tasks:** +- [ ] Implement `Encryption` class with symmetric encryption (AES-256-GCM) +- [ ] Implement key derivation from passphrase +- [ ] Implement data encryption/decryption methods +- [ ] Implement secure key generation +- [ ] Add tests for encrypt/decrypt round-trip +- [ ] Document in `/docs/developer/encryption.md` + +### 1.6 MVC Conversion (P0) + +**Why**: The current Router/Route architecture has three structural problems: (1) Router is a monolith doing routing, auth, rendering, and error handling; (2) Route is fat — holds metadata, path resolution, controller dispatch, config persistence, and rendering; (3) Everything depends on Apache .htaccess with zero testability. This phase converts the kernel to MVC while preserving all existing functionality. + +**Architecture**: + +``` +Router — register() + match() (pure routing) +RouteDTO — pure data (namespace, template, view, public, level, action) +Middleware — auth, maintenance (before/after controller) +Response — controller return value (render, redirect, json, error) +View — template + view composition (output-buffered) +Controller — base class (bootstrap, $this->Route, $this->Helper, etc.) +EntryPoint — coordinates Bootstrap → Router → Middleware → Controller → View +Hook — plugin extension points (register/fire pattern) +``` + +**Key principles**: +- Route becomes thin DTO (no globals, no rendering, no persistence) +- Everything goes through a controller (routes without action get default ViewAction) +- Auth moves to middleware (separate from routing) +- Response object replaces implicit output +- View engine replaces require_once (output-buffered, returns string) +- Plugin hooks via Hook::register()/Hook::fire() +- Zero breaking changes for existing plugins + +**Phases**: + +#### Phase A — New Kernel Components (read-only, no migration) +- [ ] Create `RouteDTO` (thin data object, `src/Objects/RouteDTO.php`) +- [ ] Create `Response` class (`src/Response.php`) +- [ ] Create `View` engine (`src/View.php`) +- [ ] Create `Controller` base class (`src/Controller.php`) +- [ ] Create Middleware components (`src/Middleware/`) +- [ ] Create `Hook` class (`src/Hook.php`) +- [ ] Create `EntryPoint` coordinator (`src/EntryPoint.php`) + +#### Phase B — Migration Adapter (backward-compatible) +- [ ] Refactor `Router.php` — add `register()`, `match()`, `all()`, `loadFromConfig()` +- [ ] Migrate `Route.php` — delegate `render()` to `View` engine +- [ ] Migrate `Bootstrap.php` — coordinate new components +- [ ] Verify all 57+ plugins still load correctly + +#### Phase C — Entry Points & Server Support +- [ ] Update `CoreHelper::init()` — generate `nginx.conf.example`, project root `index.php` +- [ ] Create project root `index.php` (shared hosting entry point) +- [ ] Add `php cli core serve` command (PHP built-in server) +- [ ] Clean up `webroot/index.php` (server-agnostic front controller) +- [ ] Add Cloudflare-friendly headers + +#### Phase D — Testing Infrastructure +- [ ] Set up PHPUnit (`phpunit/phpunit` dev dep, `phpunit.xml.dist`, `tests/bootstrap.php`) +- [ ] Add CoreCommand test commands (`test:routes`, `test:routes:access`, `test:routes:auth`, `test:views`) +- [ ] Create unit tests for all new components +- [ ] Create test traits (`tests/Traits/MockGlobals.php`) +- [ ] Run all tests: `php vendor/bin/phpunit` + +#### Phase E — Documentation & Cleanup +- [ ] Update `DESIGN.md` — Section 7 (Routing), Section 16 (Entry Points), Section 18 (Testing) +- [ ] Update `ROADMAP.md` — add Phase 1.6 +- [ ] Create `/docs/mvc-migration.md` — migration guide for plugins +- [ ] Update plugin documentation + +**Risk Mitigation**: +- Phase A/B are fully backward-compatible — existing plugins keep working +- Phase C/D are additive — no existing behavior changes +- Each phase is independently verifiable + +**Deliverables**: +- `php cli core test:routes` passes for all routes +- PHPUnit runs clean for RouteDTO, Response, View, Controller +- Shared hosting, Apache, Nginx, built-in server all work +- 57+ plugins still load without modification + +--- + +## Phase 2: Core Services (Unblocking Feature Work) + +Services that unlock plugin and application development. + +### 2.1 Auth System Security Review + Feature Completion (P1) + +**Why**: Auth is incomplete — 2FA/TOTP and user registration are stubs. The Auth system (`src/Auth.php`, `src/Backends/`, `src/Objects/User.php`, `src/Objects/Organization.php`) needs a security review and completion of missing features. + +**Auth system review scope:** +- Session fixation/hijacking defenses +- Token rotation and expiry handling (`Objects\Pin` — currently only numeric PIN, not TOTP) +- Password policy enforcement +- Backend extensibility (only `Local` backend exists; LDAP/ADDC/SMTP/IMAP/OAuth are commented-out stubs) +- Organization membership model (currently `User->organization()` + `src/Objects/Organization.php`) + +**Tasks:** +- [ ] **Security review of `Auth.php`** — session fixation, token management, password policy, backend abstraction +- [ ] Implement 2FA/TOTP (`Objects\Pin` → TOTP using `phpseclib3/phpseclib`) +- [ ] Implement 2FA recovery codes (UUID format, one per user, single-use) +- [ ] Implement 2FA via Email (SMTP) — generate OTP, send via SMTP, verify +- [ ] Implement 2FA via SMS (SMS service) — generate OTP, send via SMS, verify +- [ ] Implement user registration (config-gated, disabled by default) +- [ ] Implement email verification flow (single-use token, 24h expiry) +- [ ] Implement forgot password flow (single-use token, 60min expiry) +- [ ] Implement remember-me (selector/validator token pair with rotation) +- [ ] Test all auth flows with browser tests or PHPUnit +- [ ] Document auth flow in `/docs/developer/authentication.md` + +### 2.2 Profile Modal System (P1) + +**Why**: Users need a way to manage their profile, security settings, and 2FA. Currently `lib/plugins/profile/` is a page-based system — should be converted to a modal. + +**Tasks:** +- [ ] Create `ProfileModal` class with section registry +- [ ] Create `ProfileSection` value object (tab, content callback, permissions) +- [ ] Build modal UI in topbar (Bootstrap modal, tabbed interface) +- [ ] Migrate existing profile data (Overview, Security, API Tokens) to modal tabs +- [ ] Support plugin-provided sections via hook +- [ ] Test tab rendering, permission gating, AJAX content loading +- [ ] Document in `/docs/developer/profile-modal.md` + +### 2.3 Debug & Audit Logging (P1) + +**Why**: `Log.php` provides logging (5 levels, file rotation, IP tracking) but there's no audit trail. Admin actions, security events, and debug issues need a dedicated audit log. + +**Tasks:** +- [ ] Create `DebugAuditLogger` class (APP_DEBUG-gated, reuses `Log.php` infrastructure) +- [ ] Log to `admin_audit_log` table (type: debug/audit, sanitized payloads, IP, user) +- [ ] Create `/admin/audit` page with type filtering (`?type=all|debug|audit`) +- [ ] Create `AuditLogger` class for production audit trail (non-debug) +- [ ] Log auth events (login, logout, 2FA, permission changes) +- [ ] Add debug badges to /admin pages (request ID, global variables, CSRF status) +- [ ] Test audit log write/filter/render + +### 2.4 Version Provider (P2) + +**Why**: `/api/core/info` (`CoreEndpoint::infoAction()`) provides version, name, owner, copyright, changelog, logo, license, authors. But no dedicated `VersionProvider` class exists for programmatic version comparison (e.g., kernel/app version resolution, extension compatibility checks). + +**Tasks:** +- [ ] Create `VersionProvider` class with kernel and application version resolution +- [ ] Support semver comparison (`>=`, `<=`, `^`, `~`, exact) +- [ ] Add admin overview card showing kernel version, app version, update status +- [ ] Test version resolution and comparison logic + +### 2.5 Dependency Resolver (P2) + +**Why**: Plugin install/enable/disable needs to resolve dependencies and block incompatible operations. Currently services are loaded flat in `Bootstrap.php`. + +**Tasks:** +- [ ] Create `DependencyResolver` class +- [ ] Support dependency format: `plugin-name >=1.0.0`, `plugin-name <2.0.0` +- [ ] Check install/enable/disable/uninstall for blocked operations +- [ ] Add to extension catalog UI (block reason display) +- [ ] Test dependency resolution with conflicting versions + +### 2.6 Migration System Improvement (P2) + +**Why**: `Schema::compare()` + `Schema::update()` provide basic table/column sync between definition files and DB structure. But no dedicated MigrationRunner exists for versioned migrations. + +**Existing: `Schema::compare()` compares in-memory column definitions vs DB structure, returns descriptive diffs or SQL queries. `Schema::update()` executes the queries. Used by plugin installation.** + +**Tasks:** +- [ ] Create `MigrationRunner` class with versioned migrations +- [ ] Support versioned migrations (`migrations/001_create_users.php`, etc.) +- [ ] Track applied migrations in `migrations` table +- [ ] Integrate with plugin lifecycle (install → run migrations, uninstall → reverse migrations) +- [ ] Test migration apply/reverse +- [ ] Document migration format in `/docs/developer/migrations.md` + +### 2.7 Database Connector Expansion (MySQL + SQLite) (P1) + +**Why**: MySQL-only blocks local development (PHP 8.1+ on macOS has no MySQL socket by default; SQLite works out-of-the-box). The Connector pattern is already in place — SQLite just needs implementation. + +**Files affected:** +- `src/Connectors/SQLite.php` (60-80 lines, from scratch) +- `src/Database.php` (add SQLite to connector factory switch) +- `src/Objects/Schema.php` (replace MySQL-specific SQL with adapter calls) +- `src/Objects/Query.php` (replace MySQLi-specific calls with adapter calls) +- `src/Objects/Definition.php` (SQLite type mapping) +- `src/Installer.php` (SQLite config format support) +- `config/database.cfg` (add SQLite example) + +**Tasks:** +- [ ] **Task 1: Implement `Connectors\SQLite.php` (60-80 lines)** + - Use `PDO_SQLITE` driver (not `sqlite3` — PDO supports prepared statements natively) + - `connect()`: Open DB file path from config (`database.cfg → path`), auto-create file if not exists + - `describe()`: `PRAGMA table_info(
)` mapped to DESCRIBE format: `{Field, Type, Null, Key, Default, Extra}` + - `lastId()`: `PDO::lastInsertId()` + - `affectedRows()`: `PDO::rowCount()` (note: SELECT returns -1 in SQLite, needs `COUNT(*)` workaround) + - `prepare()`: PDO prepared statement with `bindValue()` (no type-string workaround needed like MySQL) + - Handle SQLite file permissions (`chmod 0644` on creation) +- [ ] **Task 2: Create `DatabaseAdapter` interface for SQL dialect differences** + - Define `describeTable(string): array` — column introspection (SQLite: PRAGMA, MySQL: DESCRIBE) + - Define `showTables(): array` — list tables (SQLite: sqlite_master query, MySQL: SHOW TABLES) + - Define `buildCreateTable(string $table, string $columnsSQL): string` — dialect-specific wrapper + - Define `buildAlterModify(string $table, string $col, string $def): string` — SQLite workaround for MODIFY + - Define `autoIncrement(int): string` — dialect-specific AUTO_INCREMENT syntax + - MySQL adapter: `MySQL::describeTable()` wraps existing `describe()` + - SQLite adapter: `SQLite::describeTable()` wraps `PRAGMA table_info` +- [ ] **Task 3: Make `Schema.php` connector-aware** + - Replace `const engine = 'InnoDB'` with `$this->engine = $connector->getDefaultEngine()` + - Replace `const charset = 'utf8mb4'` with `$this->charset = $connector->getDefaultCharset()` + - Replace `SHOW TABLE STATUS LIKE` with adapter's `describeTable()` + - Replace `SHOW TABLES LIKE` with adapter's `showTables()` + - Replace `buildColumnSQL()` hardcoded `ENGINE=... CHARSET=... COLLATE=...` with `$connector->buildCreateTable()` + - Handle SQLite's lack of `MODIFY COLUMN` via adapter +- [ ] **Task 4: Update `Query.php` for SQLite** + - Replace `get_result()` / `fetch_assoc()` with `$stmt->fetch(PDO::FETCH_ASSOC)` for SQLite + - Handle `affectedRows()` for SELECT queries in SQLite (use `COUNT(*)` workaround) + - Replace `AUTO_INCREMENT` with `AUTOINCREMENT` for SQLite +- [ ] **Task 5: Update `Installer.php` for SQLite** + - Detect connector type + - Use adapter-aware schema creation + - SQLite config format: `{ "path": "data/database.sqlite" }` vs MySQL: `{ "host": "localhost", "database": "demo", "username": "root", "password": "" }` +- [ ] **Task 6: Update `Definition.php` — SQLite type mappings** + - Map `tinyint(1)` → `BOOLEAN` for SQLite + - Map `ENUM` → `TEXT` (SQLite doesn't support ENUM; add comment with enum values) + - Map `AUTO_INCREMENT` → `AUTOINCREMENT` + - Map `on update CURRENT_TIMESTAMP` → unsupported in SQLite (warn or skip) +- [ ] **Task 7: Add SQLite to `requirement.cfg` and documentation** + - Document SQLite as valid connector (`connector: sqlite`) + - Document SQLite system requirements (PHP 8.1+ with PDO_SQLITE extension) + - Add SQLite config example to `config/database.cfg` +- [ ] **Task 8: Add tests** + - SQLite connection test + - SQLite Query builder tests (SELECT, INSERT, UPDATE, DELETE, JOIN, ORDER BY, LIMIT, INDEX) + - SQLite Schema tests (create, describe, compare, update) + - SQLite migration/upgrade tests (ALTER TABLE limitations, create-drop-rename workaround) + - Config migration: MySQL → SQLite round-trip test + - Definition type mapping tests +- [ ] **Task 9: Document SQLite support** + - `/docs/developer/database-connectors.md` — connector interface, SQLite config, migration guide from MySQL + - `/docs/developer/sqlite-notes.md` — known limitations (MODIFY COLUMN workaround, JSON_CONTAINS compatibility, ENUM handling) + +### 2.8 SMS / IMAP Services (P2) + +**Why**: `src/SMS.php` and `src/IMAP.php` are 0-byte stubs. Needed for 2FA via SMS, email verification, account recovery, and inbox integration. + +**Tasks:** +- [ ] Implement `SMS.php` — send SMS via provider (Twilio, Vonage, etc.) +- [ ] Implement `IMAP.php` — connect to mail server, read/parse emails +- [ ] Add SMS as a 2FA channel (generate OTP, send via SMS, verify) +- [ ] Add IMAP for email verification (parse incoming verification emails) +- [ ] Test SMS/IMAP flows with mock providers +- [ ] Document in `/docs/developer/sms-imap.md` + +### 2.9 SLS Service (P3) + +**Why**: `src/SLS.php` is a 0-byte stub. Deferred pending licensing design. + +--- + +## Phase 3: Feature Completeness + +Features that make the kernel production-ready. + +### 3.1 Developer Mode Completion (P2) + +**Why**: The `dev` plugin exists with `/dev/on`, `/dev/off`, `/dev/status` endpoints, `Developer` role, maintenance toggle, and `Widget.php` (267 lines). But no `/admin/developer` page or scaffold generator exists. + +**Current dev plugin (`lib/plugins/dev/`):** +- `Endpoint.php` — `/dev/on`, `/dev/off`, `/dev/status` endpoints +- `Widget.php` — dropdown in topbar with development/maintenance toggles +- `routes.cfg` — phpMyAdmin link under developer location +- `Developer` role defined in Auth system + +**Tasks:** +- [ ] Create `/admin/developer` page +- [ ] Scaffold generator (plugins, endpoints, models, controllers, layouts) +- [ ] Templates stored in `resources/scaffolds/` +- [ ] Developer mode toggle (APP_DEBUG equivalent for admin) +- [ ] Test tool availability and scaffold output + +### 3.2 Documentation Plugin (P2) + +**Why**: No docs generation plugin for the kernel. + +**Tasks:** +- [ ] Create `documentation` plugin with lightweight markdown renderer +- [ ] **Multi-level docs search:** Read `docs/` directories at every level: + - Kernel: `vendor/laswitchtech/core/docs/` + - Application: `app/docs/` + - Plugins: `lib/plugins/*/docs/` + - Themes: `lib/themes/*/docs/` + - Modules: `lib/modules/*/docs/` +- [ ] Panel layout with sidebar TOC + prev/next navigation +- [ ] Support headers, bold, italic, code, lists, links, images +- [ ] Edit on GitHub link for admin users +- [ ] Plugin self-registration with routes +- [ ] Test rendering of common markdown patterns + +### 3.3 Datatables Standardization (P2) + +**Why**: DataTables assets exist but no standardized usage pattern. Need to update library and standardize. + +**Tasks:** +- [ ] Update DataTables library to v2.3.8 +- [ ] Add ColumnControl extension support +- [ ] Standardize implementation via `assets/js/builder.js` DataTables builder +- [ ] Create `DataTable` helper class for consistent initialization +- [ ] Define standard configuration options +- [ ] Update existing DataTables usage to new standard +- [ ] Document in `/docs/developer/datatables.md` + +### 3.4 UI Builder Documentation (P2) + +**Why**: `assets/js/builder.js` is the UI component builder used for all Bootstrap component generation. It needs documentation as the standard UI generation pattern. + +**Current `builder.js`:** +- `Builder` class with component registry (cards, tables, layouts, inputs, widgets) +- DataTables integration (columnDefs, buttons, searchBuilder, exportTools, columnsVisibility, selectTools) +- Extensible via extensions (Builder.extend()) +- Component lifecycle: `_init()` → `_create()` → `_load()` → `_insert()` → `_timeout()` + +**Tasks:** +- [ ] Document `Builder` class API in `/docs/developer/builder.md` +- [ ] Document component registration and extension patterns +- [ ] Document DataTables builder options (columnDefs, buttons, searchBuilder, etc.) +- [ ] Document component lifecycle hooks +- [ ] Add JSDoc comments to `builder.js` +- [ ] Add examples for common patterns + +### 3.5 Organization System Data Scoping (P2) + +**Why**: Organizations are integrated into Auth (`User->organization()`) and the plugin exists at `lib/plugins/organizations/` (tables: organizations, users, members). But data scoping middleware is missing — queries don't automatically scope to the user's organization. + +**Current state:** +- `src/Objects/Organization.php` — org CRUD, member management +- `src/Objects/User.php` → `organization()` method +- Auth checks `$user->organization['isActive']` +- Plugin tables: `organizations`, `users`, `organization_users` +- Admin UI at `/security/organizations` + +**Tasks:** +- [ ] Create `OrganizationScope` middleware (auto-filter queries by organization) +- [ ] Add `OrganizationRepository` and `OrganizationMemberRepository` +- [ ] Profile Modal integration (switch/create/list organizations) +- [ ] `/admin/organizations` listing page +- [ ] Test organization creation, membership, context switching + +--- + +## Phase 4: Pre-V1.0 Polish + +Final work before V1.0 release. + +### 4.1 Extension Marketplace Foundation (P2) + +**Tasks:** +- [ ] Extension listing page with filtering/sorting +- [ ] Version tracking for installed extensions +- [ ] ZIP download + checksum verification +- [ ] Installation progress tracking +- [ ] Bulk operations (enable/disable multiple) + +### 4.2 Kernel Update System (P2) + +**Tasks:** +- [ ] Version check (local-only for now) +- [ ] Download workflow (local override patches) +- [ ] Apply workflow with rollback capability +- [ ] Admin UI for update management + +### 4.3 Menu Registry (P2) + +**Why**: Menu system works via `Builder->menu()` but no explicit `MenuRegistry` class makes it hard for plugins to register menu items. + +**Current**: `Builder->menu('developer')` and other locations in `Widget.php`. + +**Tasks:** +- [ ] Create `MenuRegistry` class with plugin hooks +- [ ] Support menu item registration with permissions, icons, order +- [ ] Replace `Builder->menu()` with registry-backed menu building +- [ ] Document menu plugin API + +### 4.4 Theme/Runtime Management (P3) + +**Tasks:** +- [ ] Theme switch UI in admin settings +- [ ] Preview before applying +- [ ] Layout runtime management (switch without manual file operations) + +--- + +## Phase 5: V1.0 Scope + +Features required for V1.0 release (target: 2026-08-15). + +### Required for V1.0 + +- [ ] Config documentation under docs/03-using/ (Phase 1.2) +- [ ] Admin landing page + settings registry (Phase 1.3) +- [ ] Global view context (Phase 1.4) +- [ ] Encryption service (Phase 1.5) +- [x] **Complete testing infrastructure + CI (Phase 1.1)** — 80 tests, syntax check, CI workflow, release pre-check +- [x] **Global view context (Phase 1.4)** — ViewGlobals class, all 5 layouts wired +- [x] **MVC conversion + server-agnostic deployment + testing (Phase 1.6)** — all phases A-E wired +- [ ] Complete auth features + security review (Phase 2.1) +- [ ] Profile modal (Phase 2.2) +- [ ] Debug/audit logging (Phase 2.3) +- [ ] Version provider (Phase 2.4) +- [ ] Dependency resolver (Phase 2.5) +- [ ] Migration system (Phase 2.6) +- [ ] Database connector expansion (MySQL + SQLite) (Phase 2.7) +- [ ] SMS / IMAP services (Phase 2.8) +- [ ] Developer mode completion (Phase 3.1) +- [ ] Documentation plugin (Phase 3.2) +- [ ] Datatables standardization (Phase 3.3) +- [ ] UI Builder documentation (Phase 3.4) +- [ ] Organization data scoping (Phase 3.5) + +### Out of Scope for V1.0 + +- OAuth server / client integration +- Licensing server and validation system +- Extension marketplace with payment processing +- Online extension submission/review portal +- Multi-app ecosystem support +- Remote update channels (signed release distribution) +- Plugin signing / checksum verification +- Distributed authentication sharing +- AI agent orchestration system +- Business automation apps (Transport, Customs, LaswitchTech operational apps) + +These systems are large enough to warrant their own design documents and development timelines. + +--- + +## Deferred / Explicitly Not Now + +| Item | Reason | +|------|--------| +| OAuth | Pending authentication architecture design | +| Licensing | Pending licensing server design | +| Marketplace | Pending payment and review infrastructure | +| Remote ZIP install | Pending checksum/signature model | +| Multi-instance auth sharing | Pending OAuth foundation | +| Plugin signing | Pending marketplace infrastructure | +| Distributed architecture | Post-V1.0 consideration | +| SMS service (`SMS.php`) | Phase 2.8 — In progress | +| IMAP service (`IMAP.php`) | Phase 2.8 — In progress | +| SLS service (`SLS.php`) | Phase 2.9 — Deferred pending licensing design | +| PostgreSQL connector | Stub — MySQL + SQLite sufficient for V1.0 | +| SQLite connector | Phase 2.7 — In progress | + +--- + +## Appendix: Current Phase Checklist + +| Phase | Area | Status | Notes | +|-------|------|--------|-------| +| **1.1** | **Testing** | **Complete** | PHPUnit + 80 tests; `tests/syntax.sh`; CI workflow + release pre-checks; Route compilation via `testroutes` (HTTP accessibility tests pending guzzle/browser-kit) | +| 1.2 | Config Documentation | Not started | Config files exist, docs needed | +| 1.3 | Settings Registry | Not started | No `/admin/settings` | +| **1.4** | **Global View Context** | **Complete** | `ViewGlobals` class; all 5 layouts updated; View engine injects globals; 86 tests pass | +| 1.5 | Encryption Service | Not started | `src/Encryption.php` is 0 bytes | +| **1.6** | **MVC Conversion** | **Complete** | All phases A-E wired; Router::startMVC(); Bootstrap globals (HOOK, ENTRYPOINT); NullConnector for CLI scope; 86 tests pass; `php cli core testroutes` verifies all 62 routes | +| 2.1 | Auth + 2FA | Partial | 2FA/TOTP, registration, email/SMS 2FA pending | +| 2.2 | Profile Modal | Not started | Currently page-based | +| 2.3 | Debug/Audit Logger | Not started | `Log.php` exists, audit layer missing | +| 2.4 | Version Provider | Not started | `/api/core/info` exists but no class | +| 2.5 | Dependency Resolver | Not started | No resolver | +| 2.6 | Migration Runner | Partial | `Schema::compare()`/`update()` exist, no versioned migrations | +| 2.7 | Database Connectors | Partial | MySQL + NullConnector; SQLite in Phase 2.7 | +| 2.8 | SMS/IMAP | Not started | Both 0-byte stubs | +| 2.9 | SLS | Not started | 0-byte stub | +| 3.1 | Developer Mode | Partial | `dev` plugin exists, page/scaffold generator missing | +| 3.2 | Documentation | Not started | No docs plugin | +| 3.3 | DataTables | Not started | Standardization + update to 2.3.8 needed | +| 3.4 | UI Builder | Not started | `builder.js` needs docs | +| 3.5 | Organization | Partial | Core integration done, data scoping missing | +| **V1.0 Target** | **2026-08-15** | Scope defined above | | diff --git a/Template/View/fullscreen.php b/Template/View/fullscreen.php index f0f88c0..9c63ce9 100644 --- a/Template/View/fullscreen.php +++ b/Template/View/fullscreen.php @@ -1,22 +1,26 @@ -Config->get('application','maintenance') || $this->Auth->isAuthorized('Administrator',1)): ?> + +get('application','maintenance') || $auth->isAuthorized('Administrator',1)): ?> - <?php if(is_null($this->Request->getParams('GET','query'))): ?> - <?= $this->Locale->get($this->label()); ?><?php if(!is_null($this->Request->getParams('GET','name'))): ?>: <?= $this->Request->getParams('GET','name') ?><?php elseif(!is_null($this->Request->getParams('GET','id'))): ?>: <?= $this->Request->getParams('GET','id') ?><?php endif; ?> + <?php if(is_null($request->getParams('GET','query'))): ?> + <?= $locale->get($this->label()); ?><?php if(!is_null($request->getParams('GET','name'))): ?>: <?= $request->getParams('GET','name') ?><?php elseif(!is_null($request->getParams('GET','id'))): ?>: <?= $request->getParams('GET','id') ?><?php endif; ?> <?php else: ?> - <?= $this->Locale->get('Search Results'); ?>: <?= $this->Request->getParams('GET','query') ?> + <?= $locale->get('Search Results'); ?>: <?= $request->getParams('GET','query') ?> <?php endif; ?> - Builder->css(); ?> + css(); ?> - Builder->js(); ?> + js(); ?> @@ -36,7 +40,7 @@ -

Config->get('application','name') ?>

+

get('application','name') ?>

@@ -47,7 +51,7 @@
- Request->getParams('GET','query'))): ?> + getParams('GET','query'))): ?> view(); ?> interrupt()->Router->render('search'); endif; ?>
diff --git a/Template/View/index.php b/Template/View/index.php index ec2e15d..f5e0039 100644 --- a/Template/View/index.php +++ b/Template/View/index.php @@ -1,22 +1,26 @@ -Config->get('application','maintenance') || $this->Auth->isAuthorized('Administrator',1)): ?> + +get('application','maintenance') || $auth->isAuthorized('Administrator',1)): ?> - <?php if(is_null($this->Request->getParams('GET','query'))): ?> - <?= $this->Locale->get($this->label()); ?><?php if(!is_null($this->Request->getParams('GET','name'))): ?>: <?= $this->Request->getParams('GET','name') ?><?php elseif(!is_null($this->Request->getParams('GET','id'))): ?>: <?= $this->Request->getParams('GET','id') ?><?php endif; ?> + <?php if(is_null($request->getParams('GET','query'))): ?> + <?= $locale->get($this->label()); ?><?php if(!is_null($request->getParams('GET','name'))): ?>: <?= $request->getParams('GET','name') ?><?php elseif(!is_null($request->getParams('GET','id'))): ?>: <?= $request->getParams('GET','id') ?><?php endif; ?> <?php else: ?> - <?= $this->Locale->get('Search Results'); ?>: <?= $this->Request->getParams('GET','query') ?> + <?= $locale->get('Search Results'); ?>: <?= $request->getParams('GET','query') ?> <?php endif; ?> - Builder->css(); ?> + css(); ?> - Builder->js(); ?> + js(); ?> @@ -34,7 +38,7 @@
- Request->getParams('GET','query'))): ?> + getParams('GET','query'))): ?> view(); ?> interrupt()->Router->render('search'); endif; ?>
diff --git a/Template/View/internal.php b/Template/View/internal.php index 1689d89..e522523 100644 --- a/Template/View/internal.php +++ b/Template/View/internal.php @@ -1,20 +1,24 @@ -Config->get('application','maintenance') || $this->Auth->isAuthorized('Administrator',1)): ?> + +get('application','maintenance') || $auth->isAuthorized('Administrator',1)): ?> - <?php if(is_null($this->Request->getParams('GET','forgot'))): ?> - <?= $this->Locale->get($this->label()); ?> + <?php if(is_null($request->getParams('GET','forgot'))): ?> + <?= $locale->get($this->label()); ?> <?php else: ?> - <?= $this->Locale->get('Reset Password'); ?> + <?= $locale->get('Reset Password'); ?> <?php endif; ?> - Builder->css(); ?> + css(); ?> - Builder->js(); ?> + js(); ?> @@ -28,7 +32,7 @@
- Request->getParams('GET','forgot'))): ?> + getParams('GET','forgot'))): ?> view(); ?> interrupt()->Router->render('330'); endif; ?>
diff --git a/Template/View/panel.php b/Template/View/panel.php index 05a49f1..eab0fc1 100644 --- a/Template/View/panel.php +++ b/Template/View/panel.php @@ -1,22 +1,27 @@ -Config->get('application','maintenance') || $this->Auth->isAuthorized('Administrator',1)): ?> + +get('application','maintenance') || $auth->isAuthorized('Administrator',1)): ?> - <?php if(is_null($this->Request->getParams('GET','query'))): ?> - <?= $this->Locale->get($this->label()); ?><?php if(!is_null($this->Request->getParams('GET','name'))): ?>: <?= $this->Request->getParams('GET','name') ?><?php elseif(!is_null($this->Request->getParams('GET','id'))): ?>: <?= $this->Request->getParams('GET','id') ?><?php endif; ?> + <?php if(is_null($request->getParams('GET','query'))): ?> + <?= $locale->get($this->label()); ?><?php if(!is_null($request->getParams('GET','name'))): ?>: <?= $request->getParams('GET','name') ?><?php elseif(!is_null($request->getParams('GET','id'))): ?>: <?= $request->getParams('GET','id') ?><?php endif; ?> <?php else: ?> - <?= $this->Locale->get('Search Results'); ?>: <?= $this->Request->getParams('GET','query') ?> + <?= $locale->get('Search Results'); ?>: <?= $request->getParams('GET','query') ?> <?php endif; ?> - Builder->css(); ?> + css(); ?> - Builder->js(); ?> + js(); ?> @@ -36,25 +41,25 @@ -

Config->get('application','name') ?>

+

get('application','name') ?>

- Config->get('application','show_nav_title')): ?> -
Locale->get('Main Navigation') ?>
+ get('application','show_nav_title')): ?> +
get('Main Navigation') ?>
- Helper->Core->menu($this->Builder->menu('sidebar-main',null,3)); ?> - Auth->isAuthorized("Administration",1)): ?> - Config->get('application','show_nav_title')): ?> -
Locale->get('Administration') ?>
+ Helper->Core->menu($menu->menu('sidebar-main',null,3)); ?> + isAuthorized("Administration",1)): ?> + get('application','show_nav_title')): ?> +
get('Administration') ?>
- Helper->Core->menu($this->Builder->menu('sidebar-admin',null,3)); ?> + Helper->Core->menu($menu->menu('sidebar-admin',null,3)); ?> - Auth->isAuthorized("Development",1)): ?> - Config->get('application','show_nav_title')): ?> -
Locale->get('Development') ?>
+ isAuthorized("Development",1)): ?> + get('application','show_nav_title')): ?> +
get('Development') ?>
- Helper->Core->menu($this->Builder->menu('sidebar-dev',null,3)); ?> + Helper->Core->menu($menu->menu('sidebar-dev',null,3)); ?> @@ -84,10 +89,10 @@

- Request->getParams('GET','query'))): ?> - Locale->get($this->label()); ?>Request->getParams('GET','name'))): ?>: Request->getParams('GET','name') ?>Request->getParams('GET','id'))): ?>: Request->getParams('GET','id') ?> + getParams('GET','query'))): ?> + get($this->label()); ?>getParams('GET','name'))): ?>: getParams('GET','name') ?>getParams('GET','id'))): ?>: getParams('GET','id') ?> - Locale->get('Search Results'); ?>: Request->getParams('GET','query') ?> + get('Search Results'); ?>: getParams('GET','query') ?>

@@ -98,14 +103,14 @@
- Request->getParams('GET','query'))): ?> + getParams('GET','query'))): ?> view(); ?> interrupt()->Router->render('search'); endif; ?>
- Locale->get('Copyright'); ?> © Config->get('application','copyright') ?>- Config->get('application','owner')?> Locale->get('All rights reserved'); ?>. + get('Copyright'); ?> © get('application','copyright') ?>- get('application','owner')?> get('All rights reserved'); ?>.
diff --git a/Template/View/website.php b/Template/View/website.php index 80c3f4f..24f70b8 100644 --- a/Template/View/website.php +++ b/Template/View/website.php @@ -1,22 +1,26 @@ -Config->get('application','maintenance') || $this->Auth->isAuthorized('Administrator',1)): ?> + +get('application','maintenance') || $auth->isAuthorized('Administrator',1)): ?> - <?php if(is_null($this->Request->getParams('GET','query'))): ?> - <?= $this->Locale->get($this->label()); ?><?php if(!is_null($this->Request->getParams('GET','name'))): ?>: <?= $this->Request->getParams('GET','name') ?><?php elseif(!is_null($this->Request->getParams('GET','id'))): ?>: <?= $this->Request->getParams('GET','id') ?><?php endif; ?> + <?php if(is_null($request->getParams('GET','query'))): ?> + <?= $locale->get($this->label()); ?><?php if(!is_null($request->getParams('GET','name'))): ?>: <?= $request->getParams('GET','name') ?><?php elseif(!is_null($request->getParams('GET','id'))): ?>: <?= $request->getParams('GET','id') ?><?php endif; ?> <?php else: ?> - <?= $this->Locale->get('Search Results'); ?>: <?= $this->Request->getParams('GET','query') ?> + <?= $locale->get('Search Results'); ?>: <?= $request->getParams('GET','query') ?> <?php endif; ?> - Builder->css(); ?> + css(); ?> - Builder->js(); ?> + js(); ?> @@ -43,11 +47,11 @@
@@ -61,17 +65,17 @@ Logo -

Config->get('application','name') ?>

+

get('application','name') ?>