Three bugs found deploying a fresh clone to seedproject.com, each fatal to the documented "spin up a new site" flow: - .gitignore: the unanchored `public/` pattern (meant for the root build output) also ignored api/public/ (the framework's front controller, controllers, models, views) and app/public/ (theme static assets: fonts, avatar placeholder). Neither was ever committed, so every fresh clone 500'd on all /api routes and 404'd on theme assets. Anchor the pattern to /public/ and commit both directories. - api/install/dump.sql: stray `CREATE DATABASE ochenta80_db123` (SQLyog export artifact) aborted `php console app:install` for any non-privileged DB user. The schema must import into whatever database the installer connects to. - PluginManager::boot() queries sp_plugins on every request, but no shipped schema creates it — even a successful install 500'd on every endpoint. Add migration 002_create_sp_plugins.sql matching the columns PluginManager reads/writes. Also empty api/system/errors.json, which shipped with stale error logs from an unrelated project. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C3JqxTe7TKaR7xufcMr7Ds
170 lines
4.9 KiB
Markdown
170 lines
4.9 KiB
Markdown
# Views — `public/views/`
|
|
|
|
PHP view templates, organized by controller name.
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
public/views/
|
|
├── <controller_name>/ # snake_case folder per controller
|
|
│ ├── index.php
|
|
│ ├── show.php
|
|
│ └── edit.php
|
|
├── modals/ # Modal fragment views (AJAX loaded)
|
|
│ └── <modal_name>.php
|
|
├── partial/ # Reusable partial views
|
|
│ ├── admin_header.php
|
|
│ ├── admin_sidebar.php
|
|
│ ├── admin_toolbar.php
|
|
│ └── admin_footer.php
|
|
└── wrapper/ # Layout wrappers
|
|
├── admin/
|
|
│ ├── header.php
|
|
│ └── footer.php
|
|
└── site/
|
|
├── header.php
|
|
└── footer.php
|
|
```
|
|
|
|
## File Naming
|
|
|
|
- Folder: `snake_case` matching the controller name (lowercased)
|
|
- Files: `snake_case.php` matching the method name
|
|
|
|
## Rendering
|
|
|
|
Called from the controller:
|
|
|
|
```php
|
|
// Standard — uses __CLASS__/__FUNCTION__ convention
|
|
$this->view->render(__CLASS__ . '/' . __FUNCTION__);
|
|
|
|
// With explicit wrapper
|
|
$this->view->render('clients/index', true, 'site');
|
|
$this->view->render('admin/dashboard', true, 'admin');
|
|
|
|
// Login / minimal wrapper
|
|
$this->view->render('auth/login', 'login');
|
|
|
|
// No wrapper
|
|
$this->view->render('auth/login', false);
|
|
```
|
|
|
|
## Accessing View Data
|
|
|
|
Variables set on the controller via `$this->view->VarName = $value` are accessed in the view as **`$this->VarName`**.
|
|
|
|
`View::render()` calls `extract((array) $this)` before including the template, so all properties set on the view object are available as bare `$VarName` locals.
|
|
|
|
```php
|
|
// Controller
|
|
$this->view->Users = Db::select("SELECT * FROM sp_users");
|
|
$this->view->PageTitle = 'Users';
|
|
|
|
// View — use bare $VarName
|
|
<h1><?= $PageTitle ?></h1>
|
|
<?php foreach ($Users as $user): ?>
|
|
<p><?= htmlspecialchars($user['name']) ?></p>
|
|
<?php endforeach; ?>
|
|
```
|
|
|
|
## Wrappers
|
|
|
|
Wrappers live in `public/views/wrapper/`. The wrapper auto-loads:
|
|
- `public/assets/css/<controllername>.css` if it exists
|
|
- `public/assets/js/<controllername>.js` if it exists
|
|
|
|
## Partial Views
|
|
|
|
Partials are reusable snippets loaded from the controller constructor:
|
|
|
|
```php
|
|
// In controller __construct()
|
|
$this->view->Header = $this->view->PartialView('admin_header');
|
|
$this->view->SideBar = $this->view->PartialView('admin_sidebar');
|
|
$this->view->ToolBar = $this->view->PartialView('admin_toolbar');
|
|
$this->view->Footer = $this->view->PartialView('admin_footer');
|
|
```
|
|
|
|
Then echoed in the view:
|
|
```php
|
|
<?= $Header ?>
|
|
<?= $SideBar ?>
|
|
```
|
|
|
|
## Modal System
|
|
|
|
Modals are loaded dynamically via AJAX. Trigger a modal with:
|
|
|
|
```html
|
|
<button class="RegularModal btn btn-primary"
|
|
data-url="/modals/load/<modal_name>"
|
|
data-size="mw-650px">
|
|
Open Modal
|
|
</button>
|
|
```
|
|
|
|
`data-size` accepts any Bootstrap modal width class or a custom `mw-*` value:
|
|
|
|
| Value | Width |
|
|
|-------|-------|
|
|
| *(omit)* | Default (~500px) |
|
|
| `mw-500px` | 500px max-width |
|
|
| `mw-650px` | 650px max-width |
|
|
| `mw-750px` | 750px max-width |
|
|
| `modal-lg` | ~800px (Bootstrap) |
|
|
| `modal-xl` | ~1140px (Bootstrap) — use for wizards / multi-step forms |
|
|
|
|
Modal views live at `public/views/modals/<modal_name>.php` and render **only** the `.modal-content` fragment (no full page wrapper):
|
|
|
|
```html
|
|
<!--begin::Modal content-->
|
|
<div class="modal-content">
|
|
<!--begin::Modal header-->
|
|
<div class="modal-header" id="kt_modal_add_client_header">
|
|
<h2>Add Client</h2>
|
|
<div class="btn btn-sm btn-icon btn-active-color-primary" data-bs-dismiss="modal">
|
|
<i class="ki-duotone ki-cross fs-1">
|
|
<span class="path1"></span>
|
|
<span class="path2"></span>
|
|
</i>
|
|
</div>
|
|
</div>
|
|
<!--end::Modal header-->
|
|
|
|
<!--begin::Modal body-->
|
|
<div class="modal-body">
|
|
<!-- form or content here -->
|
|
</div>
|
|
<!--end::Modal body-->
|
|
</div>
|
|
<!--end::Modal content-->
|
|
```
|
|
|
|
## Assets Structure
|
|
|
|
```
|
|
public/assets/
|
|
├── css/
|
|
│ ├── custom.css # Global overrides
|
|
│ └── <controller>.css # Page-specific (auto-loaded by wrapper)
|
|
├── js/
|
|
│ └── <controller>.js # Page-specific (auto-loaded by wrapper)
|
|
├── plugins/
|
|
│ └── datatables/
|
|
│ ├── datatables.bundle.css
|
|
│ └── datatables.bundle.js
|
|
└── media/
|
|
└── svg/
|
|
└── brand-logos/ # Integration / vendor logos
|
|
```
|
|
|
|
Inject additional assets from the controller method:
|
|
|
|
```php
|
|
$this->Styles[] = '/public/assets/plugins/datatables/datatables.bundle.css';
|
|
$this->view->Styles = $this->Styles;
|
|
|
|
$this->JavaScript[] = '/public/assets/plugins/datatables/datatables.bundle.js';
|
|
$this->view->JavaScript = $this->JavaScript;
|
|
```
|