From f0685a2fe1bb808778f633150c608ebaec205ce8 Mon Sep 17 00:00:00 2001 From: Oleg Savchuk Date: Thu, 30 Apr 2026 16:25:02 -0500 Subject: [PATCH 1/3] initial --- .github/copilot-instructions.md | 5 +- .gitignore | 7 + AGENTS.md | 5 +- bin/phpunit-local.bat | 7 + db/fwdatabase.sql | 31 +- .../upd2026-04-30-framework-upgrades.sql | 31 + docs/agents/heuristics.md | 5 +- .../tasks/summary-2026-04-18-agents-guide.md | 17 +- docs/crud.md | 108 ++ docs/db.md | 116 ++ docs/dynamic.md | 202 ++ docs/templates.md | 174 ++ phpunit.xml | 14 + www/php/composer.json | 9 + www/php/composer.lock | 1683 ++++++++++++++++- www/php/configs/config.php | 20 +- www/php/controllers/AdminReports.php | 4 +- www/php/controllers/Att.php | 4 +- www/php/controllers/DevManage.php | 35 +- www/php/controllers/Login.php | 17 +- www/php/fw/DateUtils.php | 87 +- www/php/fw/FormUtils.php | 44 +- www/php/fw/FwActivityLogs.php | 13 +- www/php/fw/FwAdminController.php | 11 +- www/php/fw/FwController.php | 34 +- www/php/fw/FwDynamicController.php | 109 +- www/php/fw/FwExceptions.php | 54 +- www/php/fw/FwModel.php | 61 +- www/php/fw/FwVirtualController.php | 51 +- www/php/fw/FwVueController.php | 9 +- www/php/fw/ImageUtils.php | 2 + www/php/fw/ParsePage.php | 59 +- www/php/fw/UploadUtils.php | 8 +- www/php/fw/Utils.php | 369 +++- www/php/fw/db.php | 105 +- www/php/fw/dispatcher.php | 10 +- www/php/fw/fw.php | 97 +- www/php/models/Att.php | 93 +- www/php/models/DemosItems.php | 2 +- www/php/models/Locks.php | 66 +- www/php/models/S3.php | 251 ++- www/php/models/Spages.php | 2 +- www/php/models/Users.php | 63 +- www/php/tests/DatabaseTestCase.php | 90 + www/php/tests/FrameworkTestCase.php | 61 + www/php/tests/TestHostResolver.php | 206 ++ www/php/tests/bootstrap.php | 34 + .../tests/controllers/ControllerTestCase.php | 70 + .../tests/controllers/TestControllerTest.php | 17 + www/php/tests/models/UsersModelTest.php | 17 + www/php/tests/run-local-phpunit.php | 48 + www/php/tests/unit/FrameworkBootstrapTest.php | 26 + .../common/form/show/one_fieldsel.html | 4 +- www/template/common/form/show/plaintext.html | 2 +- .../common/form/show/plaintext_json.html | 1 + .../common/form/show/plaintext_link.html | 3 +- .../common/form/show/plaintext_url.html | 1 + www/template/common/list/form_list.html | 3 +- www/template/common/list/tbody.html | 2 +- www/template/common/virtual/index/main.html | 1 + www/template/common/vue/form-one-control.html | 20 + www/template/common/vue/list-header.html | 12 + www/template/common/vue/store.js | 15 +- 63 files changed, 4361 insertions(+), 366 deletions(-) create mode 100644 bin/phpunit-local.bat create mode 100644 db/updates/upd2026-04-30-framework-upgrades.sql create mode 100644 docs/crud.md create mode 100644 docs/db.md create mode 100644 docs/dynamic.md create mode 100644 docs/templates.md create mode 100644 phpunit.xml create mode 100644 www/php/tests/DatabaseTestCase.php create mode 100644 www/php/tests/FrameworkTestCase.php create mode 100644 www/php/tests/TestHostResolver.php create mode 100644 www/php/tests/bootstrap.php create mode 100644 www/php/tests/controllers/ControllerTestCase.php create mode 100644 www/php/tests/controllers/TestControllerTest.php create mode 100644 www/php/tests/models/UsersModelTest.php create mode 100644 www/php/tests/run-local-phpunit.php create mode 100644 www/php/tests/unit/FrameworkBootstrapTest.php create mode 100644 www/template/common/form/show/plaintext_json.html create mode 100644 www/template/common/form/show/plaintext_url.html diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e982c34..f1c0592 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -186,9 +186,8 @@ data. ## Command Notes -- The repository may be checked out under Windows with ownership that triggers Git's dubious-ownership protection. Use - `git -c safe.directory=C:/DOCS_PROJ/github/osafw-php ` for local inspection, or ask before changing global Git - config. +- The repository may be checked out under Windows with ownership that triggers Git's dubious-ownership protection. Run + Git from the repository root and prefer a local command override for inspection, or ask before changing global Git config. - Do not revert unrelated dirty files. This repo may contain local config, generated vendor metadata, or developer changes unrelated to the current task. - Prefer `rg` / `rg --files` for searches. Use focused file reads before broad sweeps. diff --git a/.gitignore b/.gitignore index ce6b6fb..fc9070e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,13 @@ www/assets/lib/* www/php/vendor/* !www/php/vendor/.gitkeep +# Local public DB/admin consoles and backups must not be staged. +www/ppm.php +www/web.config.bak + +# Machine-local host configs must not be staged. +www/php/configs/*.lo.php + doc/ .idea/ diff --git a/AGENTS.md b/AGENTS.md index e982c34..f1c0592 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -186,9 +186,8 @@ data. ## Command Notes -- The repository may be checked out under Windows with ownership that triggers Git's dubious-ownership protection. Use - `git -c safe.directory=C:/DOCS_PROJ/github/osafw-php ` for local inspection, or ask before changing global Git - config. +- The repository may be checked out under Windows with ownership that triggers Git's dubious-ownership protection. Run + Git from the repository root and prefer a local command override for inspection, or ask before changing global Git config. - Do not revert unrelated dirty files. This repo may contain local config, generated vendor metadata, or developer changes unrelated to the current task. - Prefer `rg` / `rg --files` for searches. Use focused file reads before broad sweeps. diff --git a/bin/phpunit-local.bat b/bin/phpunit-local.bat new file mode 100644 index 0000000..c85a627 --- /dev/null +++ b/bin/phpunit-local.bat @@ -0,0 +1,7 @@ +@echo off +setlocal + +set "ROOT_DIR=%~dp0.." +php "%ROOT_DIR%\www\php\tests\run-local-phpunit.php" %* +set "EXIT_CODE=%ERRORLEVEL%" +exit /b %EXIT_CODE% diff --git a/db/fwdatabase.sql b/db/fwdatabase.sql index e282925..ec5debc 100644 --- a/db/fwdatabase.sql +++ b/db/fwdatabase.sql @@ -79,6 +79,27 @@ CREATE TABLE fwupdates UNIQUE INDEX UX_fwupdates_iname (iname) ) ENGINE = InnoDB; +-- distributed lock manager +DROP TABLE IF EXISTS locks; +CREATE TABLE locks +( + icode VARCHAR(255) NOT NULL, -- lock code + environment VARCHAR(16) NOT NULL DEFAULT '', -- empty(production), staging, develop, local + item_id INT UNSIGNED NOT NULL DEFAULT 0, -- if >0, lock applies only to this id + + host VARCHAR(255) NOT NULL DEFAULT '', + script VARCHAR(255) NOT NULL DEFAULT '', + pid INT UNSIGNED NOT NULL DEFAULT 0, + expires INT UNSIGNED NOT NULL DEFAULT 0, -- expiration in seconds + + add_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + add_users_id INT UNSIGNED DEFAULT 0, + upd_time DATETIME NULL ON UPDATE CURRENT_TIMESTAMP, + upd_users_id INT UNSIGNED DEFAULT 0, + + PRIMARY KEY (icode, environment, item_id) +) ENGINE = InnoDB; + -- upload categories DROP TABLE IF EXISTS att_categories; CREATE TABLE att_categories @@ -110,7 +131,7 @@ CREATE TABLE att item_id INT UNSIGNED, storage TINYINT UNSIGNED DEFAULT 0, -- 0 - table, 10 - local file (0/0/0/att_id.dat), 20 - s3 (see config: $S3Bucket/$S3Root/att/att_id) - -- raw file data, if storage=0, invisible so not used in regualr selects + -- raw file data, if storage=0, invisible so not used in regular selects raw LONGBLOB INVISIBLE, raw_s LONGBLOB INVISIBLE, -- small thumbnail raw_m LONGBLOB INVISIBLE, -- medium thumbnail @@ -167,9 +188,9 @@ CREATE TABLE users access_level TINYINT NOT NULL, -- 0 - visitor, 1 - usual user, 80 - moderator, 100 - admin is_readonly TINYINT NOT NULL DEFAULT 0, -- 1 if user is readonly - fname VARCHAR(32) NOT NULL DEFAULT '', - lname VARCHAR(32) NOT NULL DEFAULT '', - iname VARCHAR(128) AS (CONCAT(fname, ' ', lname)), -- calculated column + fname VARCHAR(127) NOT NULL DEFAULT '', + lname VARCHAR(127) NOT NULL DEFAULT '', + iname VARCHAR(255) AS (CONCAT(fname, ' ', lname)), -- calculated column title VARCHAR(128) NOT NULL DEFAULT '', @@ -340,7 +361,7 @@ CREATE TABLE activity_logs idate DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, -- default now, but can be different users_id INT UNSIGNED NULL, -- default logged user, but can be different if adding "on behalf of" idesc TEXT, - payload TEXT, -- serialized/json - arbitrary payload + payload LONGTEXT, -- serialized/json - arbitrary payload status TINYINT NOT NULL DEFAULT 0, -- 0-active, 10-inactive/hidden, 20-draft, 127-deleted add_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, diff --git a/db/updates/upd2026-04-30-framework-upgrades.sql b/db/updates/upd2026-04-30-framework-upgrades.sql new file mode 100644 index 0000000..9baca82 --- /dev/null +++ b/db/updates/upd2026-04-30-framework-upgrades.sql @@ -0,0 +1,31 @@ +-- Framework schema updates imported from downstream application hardening. + +ALTER TABLE activity_logs + MODIFY payload LONGTEXT; + +ALTER TABLE users + MODIFY fname VARCHAR(127) NOT NULL DEFAULT '', + MODIFY lname VARCHAR(127) NOT NULL DEFAULT '', + MODIFY iname VARCHAR(255) AS (CONCAT(fname, ' ', lname)); + +CREATE TABLE IF NOT EXISTS locks +( + icode VARCHAR(255) NOT NULL, + environment VARCHAR(16) NOT NULL DEFAULT '', + item_id INT UNSIGNED NOT NULL DEFAULT 0, + + host VARCHAR(255) NOT NULL DEFAULT '', + script VARCHAR(255) NOT NULL DEFAULT '', + pid INT UNSIGNED NOT NULL DEFAULT 0, + expires INT UNSIGNED NOT NULL DEFAULT 0, + + add_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + add_users_id INT UNSIGNED DEFAULT 0, + upd_time DATETIME NULL ON UPDATE CURRENT_TIMESTAMP, + upd_users_id INT UNSIGNED DEFAULT 0, + + PRIMARY KEY (icode, environment, item_id) +) ENGINE = InnoDB; + +ALTER TABLE locks + ADD COLUMN IF NOT EXISTS upd_time DATETIME NULL ON UPDATE CURRENT_TIMESTAMP; diff --git a/docs/agents/heuristics.md b/docs/agents/heuristics.md index 04c2c23..73191b9 100644 --- a/docs/agents/heuristics.md +++ b/docs/agents/heuristics.md @@ -4,6 +4,5 @@ Use this file for dated lessons that are helpful but may become stale. Revisit e - 2026-04-18: For framework docs, prefer generic osafw-php guidance over application-specific rules from downstream projects, even when those downstream projects are useful examples. -- 2026-04-18: When Git reports dubious ownership for this checkout, use - `git -c safe.directory=C:/DOCS_PROJ/github/osafw-php ` for read-only inspection instead of changing global - Git config automatically. +- 2026-04-18: When Git reports dubious ownership for this checkout, run Git from the repository root and prefer a local + command override for read-only inspection instead of changing global Git config automatically. diff --git a/docs/agents/tasks/summary-2026-04-18-agents-guide.md b/docs/agents/tasks/summary-2026-04-18-agents-guide.md index 2d00ace..1f0f68e 100644 --- a/docs/agents/tasks/summary-2026-04-18-agents-guide.md +++ b/docs/agents/tasks/summary-2026-04-18-agents-guide.md @@ -2,7 +2,7 @@ ## What changed -- Added a framework-specific `AGENTS.md` based on the useful workflow patterns from the application project, updated for +- Added a framework-specific `AGENTS.md` based on useful workflow patterns from a downstream application, updated for this osafw-php repository. - Mirrored the same guidance to `.github/copilot-instructions.md`. - Added `docs/agents/code_reviewer.md` with framework-focused review priorities. @@ -11,10 +11,7 @@ ## Scope reviewed -- Source application guide: - `C:\DOCS_PROJ\_my\planplango.com\backend.planplango.com\AGENTS.md` -- Source reviewer guide: - `C:\DOCS_PROJ\_my\planplango.com\backend.planplango.com\docs\agents\code_reviewer.md` +- External application guide and reviewer guide, adapted without recording machine-local source paths. - Framework repository docs and code: `README.md`, `db/README.md`, `www/php/composer.json`, `www/php/fw/FwModel.php`, `www/php/fw/FwController.php`, `www/php/fw/FwAdminController.php`, `www/php/fw/FwApiController.php`, @@ -23,9 +20,9 @@ ## Commands used / verification - `rg --files` -- `git -c safe.directory=C:/DOCS_PROJ/github/osafw-php status --short` +- `git status --short` - `Compare-Object (Get-Content 'AGENTS.md') (Get-Content '.github\copilot-instructions.md')` -- `rg -n "backend/php|PlanPlanGo|planplango|PPG|APP-|billing|destination|push" AGENTS.md docs .github` +- Product-specific term scan across `AGENTS.md`, `docs/`, and `.github/` - `rg -n "code_revewer|code_reviewer" AGENTS.md docs .github` ## Decisions - why @@ -33,14 +30,14 @@ - Kept the task-summary, local-instructions, reviewer-loop, and agent-workspace workflow because those are useful for ongoing framework development. - Made sub-agent guidance conditional on runtime/user permission rather than absolute, so it fits newer agent constraints. -- Removed product-specific observability, billing, push, destination, and PlanPlanGo domain rules from the framework guide. +- Removed product-specific observability, billing, push, destination, and domain rules from the framework guide. - Used PHP 8.3+ as the documented framework target because the current core uses typed class constants. - Used the correct `code_reviewer.md` spelling from the source repository. ## Pitfalls - fixes -- Git status initially failed because this checkout is considered dubious ownership for the current Windows user. Used - `git -c safe.directory=C:/DOCS_PROJ/github/osafw-php ...` for inspection instead of changing global config. +- Git status initially failed because this checkout is considered dubious ownership for the current Windows user. Used a + local command override for inspection instead of changing global config. - The first large patch exceeded the Windows tool runner command length. Split file creation into smaller edits. ## Risks / follow-ups diff --git a/docs/crud.md b/docs/crud.md new file mode 100644 index 0000000..3603c71 --- /dev/null +++ b/docs/crud.md @@ -0,0 +1,108 @@ +# CRUD Patterns + +The framework expects most business entities to be managed through `FwModel`. The model layer gives you a standard place for CRUD rules, audit fields, soft delete behavior, and entity-specific helpers without forcing every screen to hand-write SQL. + +## Default Model Shape + +```php +table_name = 'demo_dicts'; + } + +} +``` + +That is enough for a large amount of basic CRUD behavior. + +## Model Properties + +| Property | Purpose | +| --- | --- | +| `table_name` | Physical table name | +| `field_id` | Primary key column, usually `id` | +| `field_icode` | External/code identifier column, usually `icode` | +| `field_iname` | Human-readable name column, usually `iname` | +| `field_status` | Lifecycle column used for soft delete and active/deleted filtering | +| `field_prio` | Ordering column when the entity has explicit priority | +| add/update audit fields | Timestamp and user columns updated automatically when configured | + +Most modules keep the defaults and only override what differs. + +## Read Helpers + +| Method | Typical use | +| --- | --- | +| `one($id)` | Load one row by numeric id | +| `oneField($id, $field)` | Load one scalar field | +| `oneByIcode($icode)` | Load by external code | +| `oneByIcodeOrFail($icode)` | Load by external code and fail if missing | +| `oneByIname($iname)` | Load by display name | +| `idByIcode($icode)` | Resolve a numeric id from an external code | +| `listByWhere($where, $orderBy = null, ...)` | Load rows by filters | +| `listMulti($ids)` | Load many rows by ids | +| `ilist(...)` | Read data shaped for select/dropdown lists | +| `getCount($where = [])` | Count rows | + +`one()` and `oneByIcode()` use request-scoped caching, so repeated reads for the same row are cheap inside one request. + +## Write Helpers + +| Method | Typical use | +| --- | --- | +| `add($fields)` | Insert one row | +| `update($id, $fields)` | Update one row | +| `delete($id, $isPermanent = false)` | Delete or soft-delete one row | +| `deleteMulti($ids, $isPermanent = false)` | Delete many rows | +| `deleteWithPermanentCheck($id)` | Admin delete that respects permanent-delete rules | + +If the model has `field_status`, `FwModel::delete()` normally soft deletes by setting status to `127`. + +## Controller Save Flow + +For standard admin screens, controller save flow is usually: + +1. Read posted data. +2. Validate it. +3. Filter to allowed save fields. +4. Call the model to add or update. +5. Redirect back to the correct screen. + +The base controllers provide helpers such as `getSaveFields()`, `Validate()`, `modelAddOrUpdate()`, and `afterSave()`. + +```php +public function SaveAction($form_id): ?array { + $this->route_onerror = fw::ACTION_SHOW_FORM; + + $id = intval($form_id); + $item = reqh('item'); + + $this->Validate($id, $item); + + $itemdb = $this->getSaveFields($id, $item); + $id = $this->modelAddOrUpdate($id, $itemdb); + + return $this->afterSave(true, $id, $id == 0); +} +``` + +Consistency across modules is intentional. If the workflow is conventional, use the helpers instead of rebuilding the save path. + +## Practical Guidance + +- Keep controller save logic orchestration-only; move reusable rules into the model. +- Add model methods for real concepts: lifecycle rules, relation syncing, derived fields, custom lookup helpers, or JSON projection. +- Use `$this->db` directly for aggregation-heavy, join-heavy, batch-oriented, or operational queries. +- Do not create shallow wrappers that only forward to another helper. +- Prefer direct field access for guaranteed columns instead of defensive defaults everywhere. +- For schema changes, update `db/fwdatabase.sql` and add a dated script under `db/updates/`. + +## Related Docs + +- [docs/db.md](db.md) for lower-level DB wrapper usage. +- [docs/dynamic.md](dynamic.md) for config-driven admin CRUD screens. diff --git a/docs/db.md b/docs/db.md new file mode 100644 index 0000000..9467156 --- /dev/null +++ b/docs/db.md @@ -0,0 +1,116 @@ +# Database Access + +This framework uses the `DB` wrapper in `www/php/fw/db.php` for MySQL access. The goal is straightforward data access: common CRUD operations are concise, advanced queries are still possible, and existing code can mix table-based helpers with raw SQL where that is clearer. + +## Choosing the Layer + +- Start with `FwModel` methods for normal entity reads and writes. +- Use `$this->db` inside models and controllers for joins, aggregation, or batch work. +- Use procedural helpers such as `db_row`, `db_array`, and `db_exec` only in older code that already follows that style. + +The framework wires a `DB` instance into `FwModel` and controller base classes, so most code uses `$this->db`. + +## Common Reads + +| Method | Use when | Return | +| --- | --- | --- | +| `row($table, $where, $orderBy = null)` | Load one row by simple filters | `array` | +| `rowp($sql, $params = null)` | Load one row with raw SQL | `array` | +| `arr($table, $where = null, $orderBy = null, $limit = null, $fields = '*')` | Load rows by simple filters | `array[]` | +| `arrp($sql, $params = null)` | Load rows with raw SQL | `array[]` | +| `value($table, $where, $field = null, $orderBy = null)` | Load one scalar | `string|null` | +| `valuep($sql, $params = null)` | Load one scalar from raw SQL | `string|null` | +| `col($table, $where, $field = null, $orderBy = null)` | Load one column | `array` | +| `colp($sql, $params = null)` | Load one column from raw SQL | `array` | + +Use the `...p` methods when you need raw SQL plus parameters. Use table helpers when the query is still simple enough to express as table, where, and order. + +## Common Writes + +| Method | Use when | +| --- | --- | +| `insert($table, $fields, $options = [])` | Insert one row | +| `update($table, $fields, $where)` | Update rows | +| `updatep($sql, $params = null)` | Update with raw SQL | +| `delete($table, $where, $orderBy = null, $limit = null)` | Delete rows directly | +| `upsert($table, $fields, $where)` | Update if a row exists, otherwise insert | +| `exec($sql, $params = null)` | Execute non-select SQL | +| `execMultipleSQL($sql)` | Run trusted local schema scripts | + +`FwModel::delete()` often performs soft delete semantics. Direct `DB::delete()` is lower-level and should be used intentionally. + +## Parameters + +Named placeholders use `@name` or `:name`: + +```php +$rows = $this->db->arrp( + 'SELECT * FROM users WHERE status=@status AND email LIKE @email', + [ + 'status' => FwModel::STATUS_ACTIVE, + 'email' => '%@example.test', + ] +); +``` + +Array parameters expand for `IN (...)` filters: + +```php +$rows = $this->db->arrp( + 'SELECT * FROM users WHERE id IN (@ids)', + ['ids' => [10, 20, 30]] +); +``` + +For table helpers, prefer operator helpers: + +```php +$rows = $this->db->arr('users', [ + 'id' => $this->db->opIN([10, 20, 30]), + 'status' => $this->db->opNOT(FwModel::STATUS_DELETED), +], 'id desc'); +``` + +## Operator Helpers + +| Helper | Meaning | +| --- | --- | +| `opEQ($value)` | equality | +| `opNOT($value)` | not equal | +| `opLT($value)`, `opLE($value)` | less than, less or equal | +| `opGT($value)`, `opGE($value)` | greater than, greater or equal | +| `opLIKE($value)`, `opNOTLIKE($value)` | `LIKE`, `NOT LIKE` | +| `opIN($values)`, `opNOTIN($values)` | `IN`, `NOT IN` | +| `opBETWEEN([$from, $to])` | `BETWEEN` | +| `opISNULL()`, `opISNOTNULL()` | null checks | + +## Transactions + +Use explicit transactions when a workflow updates multiple tables or must keep side effects consistent. + +```php +$this->db->transaction(); + +try { + $this->db->insert('example_parent', $parentFields); + $this->db->insert('example_child', $childFields); + $this->db->commit(); +} catch (Throwable $e) { + $this->db->rollback(); + throw $e; +} +``` + +The wrapper includes reconnect logic and deadlock retries. That helps with resilience, but it does not replace correct transaction boundaries. + +## Caveats + +- `qid()` is for one SQL identifier. Do not pass dotted expressions or full SQL fragments to it. +- `execMultipleSQL()` is for trusted local scripts such as schema/bootstrap work, not user input. +- Prefer `arrp()` or `valuep()` over string-concatenated SQL. +- If the query is entity CRUD, prefer `FwModel` first so audit fields, caching, and soft delete stay consistent. + +## Related Docs + +- [docs/crud.md](crud.md) for model-level CRUD patterns. +- [docs/dynamic.md](dynamic.md) for config-driven admin CRUD screens. diff --git a/docs/dynamic.md b/docs/dynamic.md new file mode 100644 index 0000000..6a46dbd --- /dev/null +++ b/docs/dynamic.md @@ -0,0 +1,202 @@ +# Dynamic Controllers + +`FwDynamicController` is the framework's config-driven path for admin CRUD screens. It is best for conventional management screens: list rows, show one record, edit one record, and render fields consistently. + +## When to Use It + +Use `FwDynamicController` when: + +- the screen is mostly field configuration, +- list/search/sort behavior is conventional, +- show and edit screens can be described as field arrays, +- shared admin/list/form templates cover most markup. + +Prefer a manual `FwAdminController` when the screen is workflow-heavy or the save flow is more custom than configuration can express. + +## Minimal Controller + +```php +loadControllerConfig(); + $this->model_related = DemoDicts::i(); + } +} +``` + +Set `base_url`, set the model, load config, and add explicit overrides only when needed. + +## Configuration Location + +The controller loads `config.json` from the template base directory: + +```text +www/template/admin/demosdynamic/config.json +``` + +That file is the main contract for list behavior, save fields, validation, show fields, showform fields, tabs, and custom rendering. + +## Core Config Keys + +| Key | Purpose | +| --- | --- | +| `model` | Model class name | +| `required_fields` | Required field list | +| `save_fields` | Allowed fields to save | +| `save_fields_checkboxes` | Checkbox defaults when absent from request | +| `save_fields_nullable` | Fields normalized to `NULL` when empty | +| `form_new_defaults` | Defaults for new-record forms | +| `is_dynamic_index` | Turn on config-driven list rendering | +| `search_fields` | Fields used by text search | +| `list_sortdef` | Default sort | +| `list_sortmap` | Map UI sort names to SQL fields | +| `list_view` | Custom SQL view/table source for list | +| `view_list_defaults` | Default visible columns | +| `view_list_map` | Column label mapping | +| `view_list_custom` | Columns rendered by custom templates | +| `related_field_name` | Parent/child relation field | +| `is_userlists` | Turn on user-list support | +| `is_dynamic_show` | Turn on config-driven show screen | +| `is_dynamic_showform` | Turn on config-driven edit/new screen | +| `show_fields` | Field layout for view screen | +| `showform_fields` | Field layout for edit screen | +| `form_tabs` | Optional tab definitions | +| `route_return` | Optional return target override | +| `header_links` | Optional Vue list-header links | + +## Field Layout + +Structural types: + +- `row` +- `row_end` +- `col` +- `col_end` + +Common show-field types: + +- `plaintext` +- `plaintext_json` +- `plaintext_url` +- `plaintext_link` +- `plaintext_autocomplete` +- `noescape` +- `markdown` +- `float` +- `date` +- `date_long` +- `checkbox` +- `multi` +- `multi_prio` +- `att` +- `att_links` +- `att_files` +- `subtable` +- `added` +- `updated` + +Common showform-field types: + +- `input` +- `textarea` +- `select` +- `radio` +- `yesno` +- `cb` +- `email` +- `number` +- `password` +- `time` +- `date_popup` +- `datetime_popup` +- `autocomplete` +- `multicb` +- `multicb_prio` +- `att_edit` +- `att_links_edit` +- `att_files_edit` +- `subtable_edit` + +The shared markup lives under: + +- `www/template/common/form/show/` +- `www/template/common/form/showform/` +- `www/template/common/vue/` + +## Minimal Config Example + +```json +{ + "model": "Demos", + "save_fields": ["iname", "demo_dicts_id", "status"], + "required_fields": "iname", + "search_fields": "!id iname", + "list_sortdef": "iname asc", + + "is_dynamic_index": true, + "view_list_defaults": "iname demo_dicts_id status", + "view_list_map": { + "iname": "Title", + "demo_dicts_id": "Dictionary", + "status": "Status" + }, + + "is_dynamic_show": true, + "show_fields": [ + { "field": "iname", "label": "Title", "type": "plaintext" }, + { "field": "status", "label": "Status", "type": "plaintext", "lookup_tpl": "/common/sel/status.sel" } + ], + + "is_dynamic_showform": true, + "showform_fields": [ + { "field": "iname", "label": "Title", "type": "input", "required": true }, + { "field": "demo_dicts_id", "label": "Dictionary", "type": "select", "lookup_model": "DemoDicts", "is_option0": true }, + { "field": "status", "label": "Status", "type": "select", "lookup_tpl": "/common/sel/status.sel" } + ] +} +``` + +## Validation + +The framework handles: + +- required-field validation from `required_fields`, +- required inference from `showform_fields` when `required` is true, +- simple validators declared via `validate`, +- the standard save/update flow in `SaveAction()`. + +Simple validation codes include `exists`, `isemail`, `isphone`, `isdate`, and `isfloat`. + +## Custom Rendering + +Mark a field as custom when a standard shared template does not fit: + +```json +{ + "is_custom": true, + "field": "some_custom_field" +} +``` + +Then handle it in the screen template while leaving standard fields on shared rendering: + +```html +<~fields repeat inline> + <~/common/form/show/one_field unless="is_custom"> + <~custom_fields inline if="is_custom"> + <~custom_field ifeq="field" value="some_custom_field"> + Custom markup + + + +``` diff --git a/docs/templates.md b/docs/templates.md new file mode 100644 index 0000000..009e377 --- /dev/null +++ b/docs/templates.md @@ -0,0 +1,174 @@ +# Templates and ParsePage + +The framework renders HTML through ParsePage templates under `www/template/`. The template layer is intentionally lightweight: it handles composition, repetition, small conditionals, and output formatting. Business rules belong in controllers and models. + +## Mental Model + +1. Controllers decide what data the screen needs. +2. Models prepare the data. +3. ParsePage merges that data into HTML fragments. +4. A layout wraps the screen output into the final page shell. + +## Folder Structure + +A standard admin screen usually looks like this: + +```text +www/template/admin// + config.json + index/ + main.html + title.html + load_script.html + show/ + main.html + form.html + showform/ + main.html + form.html +``` + +Shared building blocks live under: + +- `www/template/common/list/` +- `www/template/common/form/` +- `www/template/common/vue/` +- `www/template/layout/` + +## ParsePage Blocks + +ParsePage conditions do not work on plain HTML tags. + +Wrong: + +```html +
...
+``` + +Correct: + +```html +<~items_block if="items" inline> +
...
+ +``` + +If the condition is false, the whole ParsePage block is removed before HTML is sent. + +## Output + +```html +

<~title>

+<~item[iname]> +<~GLOBAL[SITE_NAME]> +<~SESSION[user_name]> +``` + +Use global/session roots sparingly. In most screens, the controller should pass data directly. + +## Includes and Repeats + +```html +<~/common/list/empty> +<~/layout/sidebar> +``` + +```html +<~rows repeat inline> + + <~repeat.iteration> + <~iname> + + +``` + +Repeat metadata includes `repeat.index`, `repeat.iteration`, `repeat.first`, `repeat.last`, `repeat.total`, `repeat.even`, `repeat.odd`, and `repeat.key`. + +## Conditions + +Supported comparison attributes include: + +- `if` +- `unless` +- `ifeq` +- `ifne` +- `ifgt` +- `iflt` +- `ifge` +- `ifle` + +Use exact comparisons when needed: + +```html +<~active_cl ifeq="GLOBAL[controller]" value="AdminDemos" inline>active +``` + +Truthiness matters: a non-empty string such as `"false"` still counts as true. + +## Select Helpers + +```html + +``` + +```html +<~/common/sel/status.sel selvalue="i[status]"> +``` + +## Output Modifiers + +Common modifiers implemented in `www/php/fw/ParsePage.php` include: + +- `date` +- `number_format` +- `currency` +- `string_format` +- `sprintf` +- `htmlescape` +- `truncate` +- `strip_tags` +- `trim` +- `nl2br` +- `count` +- `lower` +- `upper` +- `default` +- `urlencode` +- `url` +- `json` +- `noparse` + +Examples: + +```html +<~amount currency="USD"> +<~idate date="short"> +<~payload json> +<~GLOBAL[request_url] urlencode> +<~website url> +``` + +## Common Fragments + +These shared fragments are intended for reuse: + +- `www/template/common/list/form_list.html` +- `www/template/common/list/thead.html` +- `www/template/common/list/tbody.html` +- `www/template/common/list/pagination.html` +- `www/template/common/form/show/` +- `www/template/common/form/showform/` +- `www/template/common/form/tabs.html` +- `www/template/common/vue/` + +Recent generic hooks: + +- `form_list_hidden_fields` injects extra hidden fields into shared list forms. +- `tbody_row_attrs` appends row-level attributes to shared list rows. +- `plaintext_json` renders show values in a monospace block. +- `plaintext_url` renders a clickable URL. +- Vue list headers support `header_links` entries with `url`, `label`, and optional Bootstrap icon class. + +When a screen-specific block needs JavaScript, prefer `load_script.html` or the relevant Vue component include over large inline scripts in `main.html`. diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..6a09e52 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,14 @@ + + + + + www/php/tests/unit + + + www/php/tests/models + + + www/php/tests/controllers + + + diff --git a/www/php/composer.json b/www/php/composer.json index b2b7bf8..e081d7c 100644 --- a/www/php/composer.json +++ b/www/php/composer.json @@ -2,5 +2,14 @@ "require": { "phpmailer/phpmailer": "^6.9", "google/apiclient": "^2.18" + }, + "require-dev": { + "phpunit/phpunit": "^12.3" + }, + "scripts": { + "test": "php tests/run-local-phpunit.php", + "test:unit": "php tests/run-local-phpunit.php --testsuite Unit", + "test:models": "php tests/run-local-phpunit.php --testsuite Models", + "test:controllers": "php tests/run-local-phpunit.php --testsuite Controllers" } } diff --git a/www/php/composer.lock b/www/php/composer.lock index 95d35b9..e0695f7 100644 --- a/www/php/composer.lock +++ b/www/php/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1e6bd4116b62d063ff68e7ff57324dd2", + "content-hash": "affed4c7eb7e893e453596d05317b729", "packages": [ { "name": "firebase/php-jwt", @@ -1350,13 +1350,1684 @@ "time": "2024-09-25T14:20:29+00:00" } ], - "packages-dev": [], + "packages-dev": [ + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "12.5.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "876099a072646c7745f673d7aeab5382c4439691" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/876099a072646c7745f673d7aeab5382c4439691", + "reference": "876099a072646c7745f673d7aeab5382c4439691", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.3", + "phpunit/php-text-template": "^5.0", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.0.3", + "sebastian/lines-of-code": "^4.0", + "sebastian/version": "^6.0", + "theseer/tokenizer": "^2.0.1" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2026-04-15T08:23:17+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-02-02T14:04:18+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:58+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:16+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:38+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "12.5.23", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "c54fcf3d6bcb6e96ac2f7e40097dc37b5f139969" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c54fcf3d6bcb6e96ac2f7e40097dc37b5f139969", + "reference": "c54fcf3d6bcb6e96ac2f7e40097dc37b5f139969", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.3", + "phpunit/php-code-coverage": "^12.5.6", + "phpunit/php-file-iterator": "^6.0.1", + "phpunit/php-invoker": "^6.0.0", + "phpunit/php-text-template": "^5.0.0", + "phpunit/php-timer": "^8.0.0", + "sebastian/cli-parser": "^4.2.0", + "sebastian/comparator": "^7.1.6", + "sebastian/diff": "^7.0.0", + "sebastian/environment": "^8.1.0", + "sebastian/exporter": "^7.0.2", + "sebastian/global-state": "^8.0.2", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/recursion-context": "^7.0.1", + "sebastian/type": "^6.0.3", + "sebastian/version": "^6.0.0", + "staabm/side-effects-detector": "^1.0.5" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.23" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsoring.html", + "type": "other" + } + ], + "time": "2026-04-18T06:12:49+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "4.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/90f41072d220e5c40df6e8635f5dafba2d9d4d04", + "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", + "type": "tidelift" + } + ], + "time": "2025-09-14T09:36:45+00:00" + }, + { + "name": "sebastian/comparator", + "version": "7.1.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "c769009dee98f494e0edc3fd4f4087501688f11e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/c769009dee98f494e0edc3fd4f4087501688f11e", + "reference": "c769009dee98f494e0edc3fd4f4087501688f11e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.2" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-04-14T08:23:15+00:00" + }, + { + "name": "sebastian/complexity", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "8.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "b121608b28a13f721e76ffbbd386d08eff58f3f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/b121608b28a13f721e76ffbbd386d08eff58f3f6", + "reference": "b121608b28a13f721e76ffbbd386d08eff58f3f6", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/8.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2026-04-15T12:13:01+00:00" + }, + { + "name": "sebastian/exporter", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "016951ae10980765e4e7aee491eb288c64e505b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/016951ae10980765e4e7aee491eb288c64e505b7", + "reference": "016951ae10980765e4e7aee491eb288c64e505b7", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:16:11+00:00" + }, + { + "name": "sebastian/global-state", + "version": "8.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-29T11:29:25+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:57:28+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:57:48+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:17+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:44:59+00:00" + }, + { + "name": "sebastian/type", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/6.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" + } + ], + "time": "2025-08-09T06:57:12+00:00" + }, + { + "name": "sebastian/version", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T05:00:38+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^8.1" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-12-08T11:19:18+00:00" + } + ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/www/php/configs/config.php b/www/php/configs/config.php index 6e00b01..438bee3 100644 --- a/www/php/configs/config.php +++ b/www/php/configs/config.php @@ -68,13 +68,14 @@ 'IS_LOG' => true, #enable logging via fw ), - 'LOG_DESTINATION' => $site_root_offline . '/logs/osafw.log', - 'LOG_MESSAGE_TYPE' => 3, #3 - default to LOG_DESTINATION - 'LOG_LEVEL' => 'DEBUG', #ALL|TRACE|DEBUG|INFO|WARN|ERROR|FATAL|OFF. Use WARN|ERROR|FATAL|OFF for production, ALL|TRACE|DEBUG for dev - 'LOG_LIMIT' => 4096, #size limit for log messages - 'LOG_SENTRY_DSN' => '', #if set - log to Sentry - 'IS_DEV' => false, #NEVER set to true on live environments - 'IS_TEST' => false, #if true - test mode, emails sent to current user or test_email + 'LOG_DESTINATION' => $site_root_offline . '/logs/osafw.log', + 'LOG_DESTINATION_OFFLINE' => '', #optional separate log destination for CLI/offline scripts + 'LOG_MESSAGE_TYPE' => 3, #3 - default to LOG_DESTINATION + 'LOG_LEVEL' => 'DEBUG', #ALL|TRACE|DEBUG|INFO|WARN|ERROR|FATAL|OFF. Use WARN|ERROR|FATAL|OFF for production, ALL|TRACE|DEBUG for dev + 'LOG_LIMIT' => 4096, #size limit for log messages + 'LOG_SENTRY_DSN' => '', #if set - log to Sentry + 'IS_DEV' => false, #NEVER set to true on live environments + 'IS_TEST' => false, #if true - test mode, emails sent to current user or test_email 'IS_SIGNUP' => false, #set to true to enable Sign Up module 'UNLOGGED_DEFAULT_URL' => '/', @@ -104,6 +105,8 @@ '/Dev', '/v1', #API version 1 ), + 'ROUTE_ID_REGEX' => '', #optional override for REST ID matching; default keeps numeric IDs and 32+ char UUID-like strings + 'AUTOLOAD_MODELS' => array(), #optional /models subfolders to autoload, e.g. array('/Extra') #prefixes without XSS check (without slash) 'NO_XSS_PREFIXES' => array( 'v1' => true, # no need to check for API @@ -122,7 +125,8 @@ '/AdminAtt/Select' => 1, ), - 'IS_API' => null, #null - disable, true - only allow API controllers, false - only allow NON-API controllers (to enable same code deployment for API and NON-API) + 'IS_API' => null, #null - disable, true - only allow API controllers, false - only allow NON-API controllers (to enable same code deployment for API and NON-API) + 'PERM_COOKIE_ENV_SUFFIX' => '', #optional suffix for remember-me cookie names across shared domains #multilanguage support settings 'LANG_DEF' => 'en', #default language - en, ru, ua, ... diff --git a/www/php/controllers/AdminReports.php b/www/php/controllers/AdminReports.php index ff8d755..29ef7a5 100644 --- a/www/php/controllers/AdminReports.php +++ b/www/php/controllers/AdminReports.php @@ -39,7 +39,7 @@ public function ShowAction($repcode): ?array { $f = $this->initFilter("AdminReports." . $repcode); #get format directly form request as we don't need to remember format - $f["format"] = reqh("f")["format"]; + $f["format"] = reqh("f")["format"] ?? 'html'; if (!$f["format"]) { $f["format"] = "html"; } @@ -49,6 +49,8 @@ public function ShowAction($repcode): ?array { $ps["f"] = $report->getReportFilters(); if ($ps["is_run"]) { $ps["rep"] = $report->getReportData(); + } else { + $ps["rep"] = array(); } #show or output report according format diff --git a/www/php/controllers/Att.php b/www/php/controllers/Att.php index ced7cb7..b8923c7 100644 --- a/www/php/controllers/Att.php +++ b/www/php/controllers/Att.php @@ -27,7 +27,7 @@ public function DownloadAction($form_id): void { if (!$item) { throw new ApplicationException("404 File Not Found"); } - if ($item['is_s3']) { + if (intval($item['storage'] ?? 0) === Att::STORAGE_S3) { $this->model->redirectS3($id, $size); } else { $this->model->transmitFile($id, $size); @@ -46,7 +46,7 @@ public function ShowAction($form_id): void { if (!$item) { throw new ApplicationException("404 File Not Found"); } - if ($item['is_s3']) { + if (intval($item['storage'] ?? 0) === Att::STORAGE_S3) { $this->model->redirectS3($id, $size); return; } diff --git a/www/php/controllers/DevManage.php b/www/php/controllers/DevManage.php index 10c6edc..f2885cf 100644 --- a/www/php/controllers/DevManage.php +++ b/www/php/controllers/DevManage.php @@ -45,7 +45,15 @@ public function IndexAction(): ?array { } public function ResetCacheAction() { - $this->fw->flash("error", "Not applicable in PHP framework. Yet."); + if (function_exists('opcache_reset')) { + $res = opcache_reset() + ? "OPcache reset successfully" + : "OPcache reset failed or not enabled"; + $this->fw->flash("success", $res); + } else { + $this->fw->flash("error", "OPcache not enabled"); + } + fw::redirect($this->base_url); } @@ -291,15 +299,28 @@ public function SelfTestAction(): void { } private function _models() { - $result = array(); - $dir = $this->fw->config->SITE_ROOT . '/php/models'; - $files = scandir($dir); - foreach ($files as $value) { - if (!preg_match('/^(.+)\.php$/', $value, $m)) { + $result = []; + + $dirs = method_exists($this->fw, 'getAutoloadModelDirs') + ? $this->fw->getAutoloadModelDirs() + : [$this->fw->config->SITE_ROOT . '/php/models']; + + foreach ($dirs as $dir) { + if (!is_dir($dir)) { continue; } - $result[] = $m[1]; + + foreach (scandir($dir) as $value) { + if (!preg_match('/^(.+)\.php$/', $value, $m)) { + continue; + } + + $result[$m[1]] = true; + } } + + $result = array_keys($result); + sort($result); return $result; } diff --git a/www/php/controllers/Login.php b/www/php/controllers/Login.php index 674e16a..c7340c9 100644 --- a/www/php/controllers/Login.php +++ b/www/php/controllers/Login.php @@ -24,6 +24,10 @@ public function __construct() { } public function IndexAction(): ?array { + if ($this->fw->isLogged()) { + fw::redirect($this->fw->config->LOGGED_DEFAULT_URL); + } + $item = reqh('item'); if (!$item) { #defaults @@ -46,8 +50,9 @@ public function SaveAction(): void { if (($item["chpwd"] ?? '') == "1") { $pwd = $item['pwd']; } - $pwd = substr(trim($pwd), 0, 64); - $gourl = reqs('gourl'); + $pwd = substr(trim($pwd), 0, 64); + $gourl = reqs('gourl'); + $remember = $item['remember'] ?? ''; #for dev only - login as first admin $is_dev_login = false; @@ -56,6 +61,11 @@ public function SaveAction(): void { $login = $dev['email']; $is_dev_login = true; } + #for normal logins - have a delay up to 2s to slow down any brute force attempts + if (!$is_dev_login) { + $delay = intval((mt_rand() / mt_getrandmax() * 2 + 0.5) * 1000); + usleep($delay * 1000); + } if (!strlen($login) || !strlen($pwd)) { $this->setError("REGISTER"); @@ -65,11 +75,12 @@ public function SaveAction(): void { $user = Users::i()->oneByEmail($login); if (!$is_dev_login) { if (!$user || $user['status'] != 0 || !$this->model->checkPwd($pwd, $user['pwd'])) { + $this->fw->logActivity(FwLogTypes::ICODE_USERS_LOGIN_FAIL, FwEntities::ICODE_USERS, 0, $login); throw new ApplicationException("User Authentication Error"); } } - $this->model->doLogin($user['id']); + $this->model->doLogin($user['id'], !empty($remember)); if ($gourl && !preg_match("/^http/i", $gourl)) { #if url set and not external url (hack!) given fw::redirect($gourl); diff --git a/www/php/fw/DateUtils.php b/www/php/fw/DateUtils.php index 9744c56..c175944 100644 --- a/www/php/fw/DateUtils.php +++ b/www/php/fw/DateUtils.php @@ -150,8 +150,8 @@ public static function date2iso(mixed $date): ?string { return $dt?->format('c'); } - // from date string to YYYY-MM-DD - public static function Str2SQL($s): ?string { + // from date string to YYYY-MM-DD or YYYY-MM-DD HH:MM:SS + public static function Str2SQL($s, bool $is_hms = false): ?string { if (is_null($s)) { return null; #keep passed null as is, so it will go to db as NULL } @@ -160,7 +160,7 @@ public static function Str2SQL($s): ?string { } $dt = self::f2date($s); - return $dt?->format('Y-m-d'); + return $dt?->format($is_hms ? 'Y-m-d H:i:s' : 'Y-m-d'); // // list($day, $month, $year) = self::ParseStrToDate($s); // if (strlen($day) < 2) { @@ -341,4 +341,85 @@ public static function differenceDays($from, $to): false|int { return $result; } + /** + * Return time difference in minutes between two datetimes, always positive. + */ + public static function differenceMinutes($from, $to): int { + if (!($from instanceof DateTime)) { + $from = new DateTime($from); + } + if (!($to instanceof DateTime)) { + $to = new DateTime($to); + } + + $interval = $from->diff($to, true); + return $interval->y * 365 * 24 * 60 + $interval->m * 30 * 24 * 60 + $interval->d * 24 * 60 + $interval->h * 60 + $interval->i; + } + + /** + * Guess the nearest IANA time-zone name for coordinates. + */ + public static function timezoneFromCoords(float $lat, float $lon, ?string $country_iso2 = null): string { + $lat = max(-90, min(90, $lat)); + $lon = fmod($lon + 540, 360) - 180; + + static $cache = []; + $key = null; + + if ($country_iso2) { + $country_iso2 = strtoupper(trim($country_iso2)); + if (preg_match('/^[A-Z]{2}$/', $country_iso2)) { + $key = $country_iso2; + } + } + + $key = $key ?? '*'; + $hasCountryKey = $key !== '*'; + if (!isset($cache[$key])) { + $ids = $hasCountryKey + ? DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, $key) + : DateTimeZone::listIdentifiers(); + if ($ids === false) { + $ids = DateTimeZone::listIdentifiers(); + } + + $cache[$key] = array_map( + static function (string $id): array { + $loc = (new DateTimeZone($id))->getLocation(); + return ['id' => $id, 'lat' => $loc['latitude'], 'lon' => $loc['longitude']]; + }, + $ids + ); + } + + $bestId = 'UTC'; + $bestKm = INF; + foreach ($cache[$key] as $zone) { + $km = self::haversineKM($lat, $lon, $zone['lat'], $zone['lon']); + if ($km < $bestKm) { + $bestKm = $km; + $bestId = $zone['id']; + } + } + + return $bestId; + } + + public static function haversineKM(float $lat1, float $lon1, float $lat2, float $lon2): float { + static $R = 6371.0088; + $dLat = deg2rad($lat2 - $lat1); + $dLon = deg2rad($lon2 - $lon1); + $a = sin($dLat / 2) ** 2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon / 2) ** 2; + return 2 * $R * asin(min(1, sqrt($a))); + } + + /** + * Return UTC offset minutes for an IANA time-zone at a timestamp. + */ + public static function utcOffsetMinutesForTZ(string $tzId, ?int $timestamp = null): int { + $dt = new DateTime('@' . ($timestamp ?? time())); + $dt->setTimezone(new DateTimeZone($tzId)); + + return (int)round($dt->getOffset() / self::MINUTE_SECONDS); + } } diff --git a/www/php/fw/FormUtils.php b/www/php/fw/FormUtils.php index 2fd39ab..8a9148b 100644 --- a/www/php/fw/FormUtils.php +++ b/www/php/fw/FormUtils.php @@ -6,6 +6,18 @@ class FormUtils { public const int MAX_PAGE_ITEMS = 25; //default max number of items on list screen + public const string AUTOCOMPLETE_SEPARATOR = ' ::: '; + + /** + * Convert mixed values to integers, drop empty values and duplicates, and return a packed array. + * Useful for sanitizing IDs from request arrays. + */ + public static function intUniqueArray(array $values): array { + $values = array_map('intval', $values); + $values = array_filter($values); + + return array_values(array_unique($values)); + } #simple email check public static function isEmail($email): bool { @@ -184,7 +196,7 @@ public static function getPager($count, $pagenum, $pagesize = NULL): array { * * "id" key is optional, if not present - iname will be used for values too */ - public static function selectOptions(array $rows, string $selected_id = NULL): string { + public static function selectOptions(array $rows, ?string $selected_id = null): string { $result = ''; if (is_null($selected_id)) { $selected_id = ''; @@ -506,4 +518,34 @@ public static function sqlOrderBy(string $sortby, string $sortdir, array $sortma } return implode(", ", $aorderby); } + + /** + * Format autocomplete value from label and id. + */ + public static function formatAutocomplete(string $label, string $id = ''): string { + $label = trim($label); + $id = trim($id); + + if ($label === '' && ($id === '' || $id === '0')) { + return ''; + } + + return $label . ($id !== '' ? self::AUTOCOMPLETE_SEPARATOR . $id : ''); + } + + /** + * Parse autocomplete value into [label, id]. + */ + public static function parseAutocomplete(?string $value): array { + $value = trim((string)$value); + if ($value === '') { + return ['', '']; + } + + $parts = Utils::split2(trim(self::AUTOCOMPLETE_SEPARATOR), $value); + return [ + trim($parts[0]), + trim($parts[1]), + ]; + } } diff --git a/www/php/fw/FwActivityLogs.php b/www/php/fw/FwActivityLogs.php index b62894d..cf2a9ee 100644 --- a/www/php/fw/FwActivityLogs.php +++ b/www/php/fw/FwActivityLogs.php @@ -18,6 +18,7 @@ class FwActivityLogs extends FwModel { public const string TAB_ALL = "all"; public const string TAB_COMMENTS = "comments"; public const string TAB_HISTORY = "history"; + public const int PAYLOAD_MAX_BYTES = 1000000; public function __construct() { parent::__construct(); @@ -37,7 +38,7 @@ public function __construct() { * @throws ApplicationException * @throws NoModelException */ - public function addSimple(string $log_types_icode, string $entity_icode, int $item_id = 0, string $idesc = "", array $payload = null): int { + public function addSimple(string $log_types_icode, string $entity_icode, int $item_id = 0, string $idesc = "", ?array $payload = null): int { $lt = FwLogTypes::i()->oneByIcode($log_types_icode); if (empty($lt)) { throw new ApplicationException("Log type not found for icode=[" . $log_types_icode . "]"); @@ -53,7 +54,11 @@ public function addSimple(string $log_types_icode, string $entity_icode, int $it $fields["item_id"] = $item_id; } if ($payload) { - $fields["payload"] = json_encode($payload); + $payload_encoded = json_encode($payload); + if ($payload_encoded !== false && strlen($payload_encoded) > self::PAYLOAD_MAX_BYTES) { + $payload_encoded = substr($payload_encoded, 0, self::PAYLOAD_MAX_BYTES - 14) . "... [truncated]"; + } + $fields["payload"] = $payload_encoded === false ? '' : $payload_encoded; } return $this->add($fields); } @@ -66,7 +71,7 @@ public function addSimple(string $log_types_icode, string $entity_icode, int $it * @return array * @throws NoModelException|DBException */ - public function listByEntity(string $entity_icode, int $id, array $log_types_icodes = null): array { + public function listByEntity(string $entity_icode, int $id, ?array $log_types_icodes = null): array { $fwentities_id = FwEntities::i()->idByIcodeOrAdd($entity_icode); $where = [ "fwentities_id" => $fwentities_id, @@ -189,7 +194,7 @@ public function listByEntityForUI(string $entity_icode, int $id, string $tab = " return $result; } - public function getCountByLogIType(int $log_itype, array $statuses = null, int $since_days = null): int { + public function getCountByLogIType(int $log_itype, ?array $statuses = null, ?int $since_days = null): int { $logtypes = FwLogTypes::i()->qTable(); $sql = "SELECT count(*) from {$this->qTable()} al diff --git a/www/php/fw/FwAdminController.php b/www/php/fw/FwAdminController.php index d52699c..4ee2e22 100644 --- a/www/php/fw/FwAdminController.php +++ b/www/php/fw/FwAdminController.php @@ -62,10 +62,7 @@ public function IndexAction(): ?array { public function ShowAction($form_id): ?array { $id = intval($form_id); - $item = $this->model->one($id); - if (!$item) { - throw new ApplicationException("Not Found", 404); - } + $item = $this->modelOneOrFail($id); $ps = array( 'id' => $id, @@ -94,7 +91,7 @@ public function ShowFormAction($form_id): ?array { if ($this->isGet()) { if ($id > 0) { // edit screen - $item = $this->model->one($id); + $item = $this->modelOneOrFail($id); } else { // add new screen $item_new = []; @@ -103,7 +100,7 @@ public function ShowFormAction($form_id): ?array { $item = $item_new; } } else { - $itemdb = $this->model->one($id); + $itemdb = $this->modelOne($id); $item = array_merge($itemdb, $item); } @@ -166,7 +163,7 @@ public function ShowDeleteAction($form_id): ?array { $id = intval($form_id); $ps = array( - 'i' => $this->model->one($id), + 'i' => $this->modelOneOrFail($id), 'return_url' => $this->return_url, 'related_id' => $this->related_id, 'base_url' => $this->base_url, #override default template url, remove if you created custom /showdelete templates diff --git a/www/php/fw/FwController.php b/www/php/fw/FwController.php index 3cabad5..2a84857 100644 --- a/www/php/fw/FwController.php +++ b/www/php/fw/FwController.php @@ -157,7 +157,7 @@ public function loadControllerConfig(array|string $config_or_filename = 'config. $this->form_new_defaults = $this->config["form_new_defaults"] ?? []; #save_fields_checkboxes could be defined as qw string - check and convert - if (is_array($this->config["save_fields_checkboxes"])) { + if (is_array($this->config["save_fields_checkboxes"] ?? '')) { $this->save_fields_checkboxes = Utils::qhRevert($this->config["save_fields_checkboxes"]); #not optimal, but simplest for now } else { $this->save_fields_checkboxes = $this->config["save_fields_checkboxes"] ?? ''; @@ -661,7 +661,7 @@ public function getSaveFields(int $id, array $item): array { * @return bool true if all required field names non-empty * also set global fw.FormErrors[REQUIRED]=true in case of validation error if no form_errors defined */ - public function validateRequired(mixed $id, array $item, array|string $afields, array &$form_errors = null): bool { + public function validateRequired(mixed $id, array $item, array|string $afields, ?array &$form_errors = null): bool { $result = true; $afields = Utils::qw($afields); @@ -773,6 +773,30 @@ public function modelAddOrUpdate(int $id, array $fields): int { return $id; } + /** + * Return one item from controller's model. For simpler overriding in child controllers. + * @param int $id + * @return array + */ + public function modelOne(int $id): array { + return $this->model->one($id); + } + + /** + * Return one item from controller's model or throw a standard not-found exception. + * @param int $id + * @return array + * @throws NotFoundException + */ + public function modelOneOrFail(int $id): array { + $item = $this->modelOne($id); + if (empty($item)) { + throw new NotFoundException(); + } + + return $item; + } + /** * return URL for location after successful Save action * if return_url set (and no add new form requested) - go to return_url @@ -859,7 +883,7 @@ public function afterSaveLocation(string $id = ''): string { * @param array|null $more_json added to json response * @return array|null ps array of json response or null (will be redirected to new location or ShowForm) */ - public function afterSave(bool $success, string $id = '', bool $is_new = false, string $action = FW::ACTION_SHOW_FORM, string $location = '', array $more_json = null): ?array { + public function afterSave(bool $success, string $id = '', bool $is_new = false, string $action = FW::ACTION_SHOW_FORM, string $location = '', ?array $more_json = null): ?array { if (!$location) { $location = $this->afterSaveLocation($id); } @@ -903,7 +927,7 @@ public function afterSave(bool $success, string $id = '', bool $is_new = false, return null; } - public function afterSaveJson(bool $success, array $more_json = null): array { + public function afterSaveJson(bool $success, ?array $more_json = null): array { return $this->afterSave($success, "", false, "no_action", "", $more_json); } @@ -943,7 +967,7 @@ public function checkAccess(): void { } } - public function setPS(array &$ps = null): array { + public function setPS(?array &$ps = null): array { if (empty($ps)) { $ps = array(); } diff --git a/www/php/fw/FwDynamicController.php b/www/php/fw/FwDynamicController.php index 5ab8ba0..9a58fbf 100644 --- a/www/php/fw/FwDynamicController.php +++ b/www/php/fw/FwDynamicController.php @@ -140,10 +140,7 @@ public function NextAction($form_id): ?array { public function ShowAction($form_id): ?array { $id = intval($form_id); - $item = $this->model->one($id); - if (!$item) { - throw new ApplicationException("Not Found", 404); - } + $item = $this->modelOneOrFail($id); $ps = array( 'id' => $id, @@ -195,7 +192,7 @@ public function ShowFormAction($form_id): ?array { if ($this->isGet()) { if ($id > 0) { // edit screen - $item = $this->model->one($id); + $item = $this->modelOneOrFail($id); } else { // add new screen $item_new = []; @@ -204,7 +201,7 @@ public function ShowFormAction($form_id): ?array { $item = $item_new; } } else { - $itemdb = $id ? $this->model->one($id) : array(); + $itemdb = $id ? $this->modelOne($id) : array(); $item = array_merge($itemdb, $item); } @@ -213,6 +210,7 @@ public function ShowFormAction($form_id): ?array { 'i' => $item, 'return_url' => $this->return_url, 'related_id' => $this->related_id, + 'base_url' => $this->base_url, 'is_readonly' => $this->is_readonly, 'tab' => $this->form_tab, 'is_showform' => true // flag for template that we are in show form @@ -261,7 +259,7 @@ public function SaveAction($form_id): ?array { $this->Validate($id, $item); // load old record if necessary - // $item_old = $this->model0->one($id); + // $item_old = $this->modelOne($id); $itemdb = $this->getSaveFields($id, $item); @@ -299,7 +297,7 @@ public function Validate(int $id, array $item): void { * @param string|null $tab optional tab code, if ommited - form_tab used * @return array */ - protected function getConfigShowFormFieldsByTab(string $prefix, string $tab = null): array { + protected function getConfigShowFormFieldsByTab(string $prefix, ?string $tab = null): array { $tab = $tab ?? $this->form_tab; $key = $prefix . ($tab ? "_" . $tab : ""); return $this->config[$key] ?? []; @@ -463,7 +461,7 @@ public function ShowDeleteAction($form_id): ?array { $id = intval($form_id); $ps = array( - 'i' => $this->model->one($id), + 'i' => $this->modelOneOrFail($id), 'return_url' => $this->return_url, 'related_id' => $this->related_id, 'base_url' => $this->base_url, #override default template url, remove if you created custom /showdelete templates @@ -547,17 +545,19 @@ public function AutocompleteAction(): ?array { $id = reqi("id"); //specific id, if just need iname for it (used to preload existing id/label for edit form) $model_name = reqs("model"); $ac_model = null; + $ac_def = null; if (empty($model_name)) { //if no model passed - use model_related $ac_model = $this->model_related; } else { //validation - only allow models from showform_fields type=autocomplete - $form_tabs = $this->config["form_tabs"] ?? []; + $form_tabs = $this->config["form_tabs"] ?? [['tab' => '']]; foreach ($form_tabs as $form_tab) { $fields = $this->getConfigShowFormFieldsByTab("showform_fields", $form_tab["tab"]); foreach ($fields as $def) { if (($def["type"] ?? '') == "autocomplete" && ($def["lookup_model"] ?? '') == $model_name) { $ac_model = fw::model($model_name); + $ac_def = $def; break; } } @@ -568,7 +568,15 @@ public function AutocompleteAction(): ?array { throw new ApplicationException("No model defined"); } - $items = $id > 0 ? [$ac_model->iname($id)] : $ac_model->listAutocomplete($q); + if ($id > 0) { + $value = $ac_model->iname($id); + if ($ac_def['autocomplete_with_id'] ?? false) { + $value = FormUtils::formatAutocomplete($value, (string)$id); + } + $items = [$value]; + } else { + $items = $ac_model->listAutocompleteByDef($q, $ac_def); + } return ['_json' => $items]; } @@ -589,7 +597,7 @@ public function SaveUserViewsAction(): ?array { $load_id = reqi("load_id"); $is_reset = reqi("is_reset"); $density = reqs("density"); - $is_list_edit = reqb("is_list_edit"); + $is_list_edit = isset($_REQUEST['is_list_edit']) ? reqb("is_list_edit") : $this->is_list_edit; $icode = UserViews::icodeByUrl($this->base_url, $is_list_edit); @@ -728,6 +736,11 @@ public function prepareShowFields(array $item, array $ps): array { } else { #single values + if ($dtype == "plaintext_json") { + $def["value"] = $this->prettifyJsonValue($field_value); + continue; + } + #lookups if (array_key_exists('lookup_table', $def)) { $lookup_key = $def["lookup_key"] ?? "id"; @@ -747,7 +760,7 @@ public function prepareShowFields(array $item, array $ps): array { $def["admin_url"] = "/Admin/" . $def["lookup_model"]; #default admin url from model name } } elseif (array_key_exists('lookup_tpl', $def)) { - $def["value"] = FormUtils::selectTplName($def["lookup_tpl"], $item[$field], strtolower($this->fw->route->controller_path)); + $def["value"] = FormUtils::selectTplName($def["lookup_tpl"], $item[$field] ?? '', strtolower($this->fw->route->controller_path)); } elseif (array_key_exists('options', $def)) { // select options @@ -763,6 +776,9 @@ public function prepareShowFields(array $item, array $ps): array { $def["value"] = DateUtils::int2timestr(intval($def["value"])); } } + if ($dtype == "plaintext_url") { + $def["safe_url"] = Utils::safeUrl((string)$field_value); + } } } unset($def); @@ -906,6 +922,11 @@ public function prepareShowFormFields(array $item, array $ps): array { } else { // single values + $is_json_pretty = ($def['is_json_pretty'] ?? false) && $dtype == "textarea"; + if ($is_json_pretty) { + $field_value = $this->prettifyJsonValue($field_value); + } + // lookups if (array_key_exists('lookup_table', $def)) { $lookup_key = $def["lookup_key"] ?? "id"; @@ -929,7 +950,11 @@ public function prepareShowFormFields(array $item, array $ps): array { $def["lookup_row"] = $lookup_row; $lookup_field = $def["lookup_field"] ?? $lookup_model->field_iname; - $def["value"] = $lookup_row[$lookup_field] ?? ''; + $value = $lookup_row[$lookup_field] ?? ''; + if ($def['autocomplete_with_id'] ?? false) { + $value = FormUtils::formatAutocomplete($value, (string)$def["lookup_id"]); + } + $def["value"] = $value; } else { //when form refreshed - get value from the form if ($dtype == "autocomplete") { @@ -971,7 +996,6 @@ public function prepareShowFormFields(array $item, array $ps): array { $def["value"] = $field_value; } else { $def["value"] = $field_value; - $def["value"] = $field_value; } // convertors @@ -1002,10 +1026,15 @@ protected function processSaveShowFormFields($id, &$fields): void { $def = $showform_fields[$field]; $type = $def["type"]; if ($type == "autocomplete") { - $lookup_model = $this->getModelWithRelated($def["lookup_model"]); - $field_value = $item[$field . '_iname'] ?? ''; // autocomplete value is in "${field}_iname" - $is_added = false; - $fields[$field] = $lookup_model->findOrAddByIname($field_value, $is_added); + $lookup_model = $this->getModelWithRelated($def["lookup_model"]); + $field_value = $item[$field . '_iname'] ?? ''; // autocomplete value is in "${field}_iname" + $is_added = false; + [$ac_label, $ac_id] = FormUtils::parseAutocomplete($field_value); + if ((int)$ac_id > 0) { + $fields[$field] = (int)$ac_id; + } else { + $fields[$field] = $lookup_model->findOrAddByIname($ac_label, $is_added) ?: null; + } } elseif ($type == "date_combo") { $fields[$field] = FormUtils::dateForCombo($item, $field); } elseif ($def['type'] == 'date_popup') { @@ -1019,6 +1048,9 @@ protected function processSaveShowFormFields($id, &$fields): void { } else { $fields[$field] = floatval($value); // number - convert to number (if field empty or non-number - it will become 0) } + } elseif ($type == "textarea" && ($def['is_json_pretty'] ?? false)) { + $is_nullable = array_key_exists($field, $fnullable); + $fields[$field] = $this->encodeJsonCompact($value, $is_nullable); } } } @@ -1179,6 +1211,45 @@ public function modelAddOrUpdateSubtableDynamic(int $main_id, string $row_id, ar return $id; } + protected function prettifyJsonValue($value): string { + if ($value === '' || $value === null) { + return ''; + } + + if (is_array($value) || is_object($value)) { + $json = json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + return $json === false ? '' : $json; + } + + $json = is_string($value) ? trim($value) : $value; + if ($json === '' || $json === null) { + return ''; + } + + $decoded = json_decode((string)$json, true); + if (json_last_error() !== JSON_ERROR_NONE) { + return (string)$value; + } + + $encoded = json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + return $encoded === false ? (string)$value : $encoded; + } + + protected function encodeJsonCompact($value, bool $is_nullable = false): ?string { + $json = is_string($value) ? trim($value) : $value; + if ($json === '' || $json === null) { + return $is_nullable ? null : ''; + } + + $decoded = json_decode((string)$json, true); + if (json_last_error() !== JSON_ERROR_NONE) { + return (string)$value; + } + + $encoded = json_encode($decoded, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + return $encoded === false ? (string)$value : $encoded; + } + /** * return first field definition by field name * @param string $field_name diff --git a/www/php/fw/FwExceptions.php b/www/php/fw/FwExceptions.php index b22391b..552ebbb 100644 --- a/www/php/fw/FwExceptions.php +++ b/www/php/fw/FwExceptions.php @@ -7,32 +7,70 @@ */ //just to be able to distinguish between system exceptions and applicaiton-level exceptions +#code in constructor used as return HTTP code, so you can throw exception with 404 for example class ApplicationException extends Exception { -} #code in constructor used as return HTTP code, so you can throw exception with 404 for example + protected string $localizationString; + protected array $localizationParameters; + protected array $context; + + public function __construct($message = "", $code = 0, ?Throwable $previous = null, string $localizationString = "", array $localizationParameters = [], array $context = []) { + $this->localizationString = $localizationString; + $this->localizationParameters = $localizationParameters; + $this->context = $context; + + parent::__construct($message, (int)$code, $previous); + } + + public function getLocalizationString(): string { + return $this->localizationString; + } + + public function getLocalizationParameters(): array { + return $this->localizationParameters; + } + + public function getContext(): array { + return $this->context; + } + + public function getLocalizedMessage($language = "en-US"): string { + return $this->getMessage(); + } +} #more specific exception - this one should be passed to user class UserException extends ApplicationException { } +class BadRequestException extends UserException { + public function __construct($msg = 'Bad request', string|int|null $msg_local_or_code = '', array $params = [], array $context = []) { + $code = is_int($msg_local_or_code) || (is_string($msg_local_or_code) && ctype_digit($msg_local_or_code)) ? (int)$msg_local_or_code : 400; + $msg_local = is_string($msg_local_or_code) && !ctype_digit($msg_local_or_code) ? $msg_local_or_code : ''; + parent::__construct($msg, $code, null, $msg_local ?: 'errors.bad_request', $params, $context); + } +} + #Validation is even more specific User exception, used for form input validations class ValidationException extends UserException { - public function __construct($msg = '', $code = 400) { - parent::__construct($msg, $code); + public function __construct($msg = '', $code = 400, string $msg_local = '', array $params = [], array $context = []) { + parent::__construct($msg, $code, null, $msg_local, $params, $context); } } class NotFoundException extends UserException { - public function __construct($msg = 'Not found', $code = 404) { - parent::__construct($msg, $code); + public function __construct($msg = 'Not found', string|int|null $msg_local_or_code = '', array $params = [], array $context = []) { + $code = is_int($msg_local_or_code) || (is_string($msg_local_or_code) && ctype_digit($msg_local_or_code)) ? (int)$msg_local_or_code : 404; + $msg_local = is_string($msg_local_or_code) && !ctype_digit($msg_local_or_code) ? $msg_local_or_code : ''; + parent::__construct($msg, $code, null, $msg_local ?: 'errors.not_found', $params, $context); } } class ExitException extends Exception { } -class AuthException extends Exception { - public function __construct($msg = 'Authentication failure', $code = 401) { - parent::__construct($msg, $code); +class AuthException extends ApplicationException { + public function __construct($msg = 'Authentication failure', $code = 401, array $params = [], array $context = []) { + parent::__construct($msg, $code, null, 'errors.authentication_failure', $params, $context); } } diff --git a/www/php/fw/FwModel.php b/www/php/fw/FwModel.php index 1cd0b67..62d9a8b 100644 --- a/www/php/fw/FwModel.php +++ b/www/php/fw/FwModel.php @@ -309,10 +309,11 @@ public function getOrderBy(): string { /** * return standard list of id,iname for all non-deleted OR wtih specified statuses order by by getOrderBy * @param array|null $statuses + * @param array|null $def not used here, for compatibility with dynamic controllers * @return array> * @throws DBException */ - public function ilist(array $statuses = null): array { + public function ilist(?array $statuses = null, ?array $def = null): array { $where = '1=1'; if (strlen($this->field_status)) { if ($statuses && count($statuses) > 0) { @@ -334,7 +335,7 @@ public function ilist(array $statuses = null): array { * @return int * @throws DBException|DateMalformedStringException */ - public function getCount(array $statuses = null, ?int $since_days = null): int { + public function getCount(?array $statuses = null, ?int $since_days = null): int { $where = []; if (strlen($this->field_status)) { if ($statuses) { @@ -624,7 +625,7 @@ public function removeUploadImg(int $id): bool { * @return array * @throws DBException */ - public function listSelectOptions(array $def = null): array { + public function listSelectOptions(?array $def = null): array { $where = ''; if (strlen($this->field_status)) { $where = " WHERE " . $this->db->qid($this->field_status) . "<>" . dbqi(self::STATUS_DELETED); @@ -666,6 +667,12 @@ public function listAutocomplete(string $q, int $limit = 5): array { return $this->db->colp($sql); } + public function listAutocompleteByDef(string $q, ?array $def = null): array { + $limit = intval($def['autocomplete_limit'] ?? 5); + + return $this->listAutocomplete($q, $limit); + } + // @@ -679,7 +686,7 @@ public function listAutocomplete(string $q, int $limit = 5): array { * @throws ApplicationException * @throws DBException */ - public function listByMainId(int $main_id, array $def = null): array { + public function listByMainId(int $main_id, ?array $def = null): array { if (empty($this->junction_field_main_id)) { throw new ApplicationException("Not implemented"); } @@ -694,7 +701,7 @@ public function listByMainId(int $main_id, array $def = null): array { * @throws ApplicationException * @throws DBException */ - public function listByLinkedId(int $linked_id, array $def = null): array { + public function listByLinkedId(int $linked_id, ?array $def = null): array { if (empty($this->junction_field_linked_id)) { throw new ApplicationException("Not implemented"); } @@ -710,11 +717,11 @@ public function listByLinkedId(int $linked_id, array $def = null): array { public function sortByCheckedPrio(array $lookup_rows): array { if (!empty($this->field_prio)) { usort($lookup_rows, function ($a, $b) { - return $b['_link'][$this->field_prio] - $a['_link'][$this->field_prio]; + return ($b['_link'][$this->field_prio] ?? 0) - ($a['_link'][$this->field_prio] ?? 0); }); } else { usort($lookup_rows, function ($a, $b) { - return $b['is_checked'] - $a['is_checked']; + return ($b['is_checked'] ?? 0) - ($a['is_checked'] ?? 0); }); } return $lookup_rows; @@ -728,15 +735,18 @@ public function sortByCheckedPrio(array $lookup_rows): array { * @return array * @throws ApplicationException|DBException */ - public function listLinkedByMainId(int $main_id, array $def = null): array { + public function listLinkedByMainId(int $main_id, ?array $def = null): array { $linked_rows = $this->listByMainId($main_id, $def); - $lookup_rows = $this->junction_model_linked->ilist(); + $lookup_rows = $this->junction_model_linked->ilist(null, $def); + foreach ($lookup_rows as $k => $row) { + $lookup_rows[$k]['is_checked'] = false; + $lookup_rows[$k]['_link'] = []; + } + if ($linked_rows && count($linked_rows) > 0) { foreach ($lookup_rows as $k => $row) { // check if linked_rows contain main id - $lookup_rows[$k]['is_checked'] = false; - $lookup_rows[$k]['_link'] = []; foreach ($linked_rows as $lrow) { // compare LINKED ids if ($row[$this->junction_model_linked->field_id] == $lrow[$this->junction_field_linked_id]) { @@ -747,8 +757,8 @@ public function listLinkedByMainId(int $main_id, array $def = null): array { } } - $lookup_rows = $this->filterAndSortChecked($lookup_rows, $def); } + $lookup_rows = $this->filterAndSortChecked($lookup_rows, $def); return $lookup_rows; } @@ -761,15 +771,18 @@ public function listLinkedByMainId(int $main_id, array $def = null): array { * @throws ApplicationException * @throws DBException */ - public function listMainByLinkedId(int $linked_id, array $def = null): array { + public function listMainByLinkedId(int $linked_id, ?array $def = null): array { $linked_rows = $this->listByLinkedId($linked_id, $def); - $lookup_rows = $this->junction_model_main->ilist(); + $lookup_rows = $this->junction_model_main->ilist(null, $def); + foreach ($lookup_rows as $k => $row) { + $lookup_rows[$k]['is_checked'] = false; + $lookup_rows[$k]['_link'] = []; + } + if ($linked_rows && count($linked_rows) > 0) { foreach ($lookup_rows as $k => $row) { // check if linked_rows contain main id - $lookup_rows[$k]['is_checked'] = false; - $lookup_rows[$k]['_link'] = []; foreach ($linked_rows as $lrow) { // compare MAIN ids if ($row[$this->junction_model_main->field_id] == $lrow[$this->junction_field_main_id]) { @@ -780,8 +793,8 @@ public function listMainByLinkedId(int $linked_id, array $def = null): array { } } - $lookup_rows = $this->filterAndSortChecked($lookup_rows, $def); } + $lookup_rows = $this->filterAndSortChecked($lookup_rows, $def); return $lookup_rows; } @@ -792,7 +805,7 @@ public function listMainByLinkedId(int $linked_id, array $def = null): array { * @param array|null $def def - in dynamic controller - field definition (also contains "i" and "ps", "lookup_params", ...) or you could use it to pass additional params * @return array */ - public function setMultiListChecked(array $rows, array $ids, array $def = null): array { + public function setMultiListChecked(array $rows, array $ids, ?array $def = null): array { $result = $rows; $is_checked_only = $def['lookup_checked_only'] ?? false; @@ -810,12 +823,12 @@ public function setMultiListChecked(array $rows, array $ids, array $def = null): return $result; } - protected function filterAndSortChecked(array $rows, array $def = null): array { + protected function filterAndSortChecked(array $rows, ?array $def = null): array { $is_checked_only = $def['lookup_checked_only'] ?? false; if ($is_checked_only) { $result = []; foreach ($rows as $row) { - if ($row['is_checked']) { + if (!empty($row['is_checked'])) { $result[] = $row; } } @@ -832,11 +845,11 @@ protected function filterAndSortChecked(array $rows, array $def = null): array { * @return array array of hashtables for templates * @throws DBException */ - public function listWithChecked(array|string $hsel_ids, array $def = null): array { + public function listWithChecked(array|string $hsel_ids, ?array $def = null): array { if (!is_array($hsel_ids)) { $hsel_ids = explode(",", $hsel_ids); } - $rows = $this->setMultiListChecked($this->ilist(), $hsel_ids, $def); + $rows = $this->setMultiListChecked($this->ilist(null, $def), $hsel_ids, $def); return $rows; } @@ -1048,7 +1061,7 @@ public function updateJunctionByLinkedId(int $linked_id, array $main_keys): void // // override in your specific models when necessary - public function prepareSubtable(array &$list_rows, int $related_id, array $def = null): void { + public function prepareSubtable(array &$list_rows, int $related_id, ?array $def = null): void { $model_name = $def != null ? (string)$def["model"] : get_class($this); foreach ($list_rows as $k => &$row) { //if row_id starts with "new-" - set flag is_new @@ -1061,7 +1074,7 @@ public function prepareSubtable(array &$list_rows, int $related_id, array $def = unset($row); } - public function prepareSubtableAddNew(array &$list_rows, int $related_id, array $def = null): void { + public function prepareSubtableAddNew(array &$list_rows, int $related_id, ?array $def = null): void { //generate unique id based on time (milliseconds) for sequential adding $t = microtime(true); $id = "new-" . intval($t * 1000); diff --git a/www/php/fw/FwVirtualController.php b/www/php/fw/FwVirtualController.php index e5eba6d..e3c2776 100644 --- a/www/php/fw/FwVirtualController.php +++ b/www/php/fw/FwVirtualController.php @@ -10,12 +10,14 @@ class FwVirtualController extends FwVueController { const int access_level = Users::ACL_SITE_ADMIN; public string $template_basedir = '/common/virtual'; + protected int $virtual_access_level = Users::ACL_SITE_ADMIN; public function __construct(array $fwcontroller) { parent::__construct(); #this will not load config.json as base_url is not yet set - $this->base_url = $fwcontroller['url']; - $controller_basedir = strtolower($this->base_url); + $this->virtual_access_level = intval($fwcontroller['access_level'] ?? Users::ACL_SITE_ADMIN); + $this->base_url = $fwcontroller['url']; + $controller_basedir = strtolower($this->base_url); // if /index subdir of such directory exists - use it, otherwise /common/virtual will be used // we check for /index subdir because we want to be able to have config.json for configs override without all templates if (is_dir($this->fw->config->SITE_TEMPLATES . $controller_basedir . '/index')) { @@ -48,7 +50,7 @@ public function __construct(array $fwcontroller) { logger("WARN", "Error decoding config from $conf_file"); } else { logger("TRACE", "merging config from:", $file_config); - $config = array_replace_recursive($config, $file_config); + $config = $this->mergeVirtualConfig($config, $file_config); } } @@ -61,7 +63,7 @@ public function __construct(array $fwcontroller) { logger("WARN", "Error decoding config from $conf_file"); } else { logger("TRACE", "merging config from:", $file_config); - $config = array_replace_recursive($config, $file_config); + $config = $this->mergeVirtualConfig($config, $file_config); } } } @@ -69,4 +71,45 @@ public function __construct(array $fwcontroller) { $this->loadControllerConfig($config); } + protected function mergeVirtualConfig(array $config, array $file_config): array { + $result = array_replace_recursive($config, $file_config); + + // Layout-driven field arrays use numeric keys, so recursive merge mixes generated + // defs with file overrides by index. Replace them wholesale when provided. + foreach (['show_fields', 'showform_fields'] as $replace_key) { + if (array_key_exists($replace_key, $file_config)) { + $result[$replace_key] = $file_config[$replace_key]; + } + } + + return $result; + } + + public function checkAccess(): void { + $current_user_level = $this->fw->userAccessLevel(); + if ($current_user_level < $this->virtual_access_level) { + throw new AuthException("Access Denied (3)"); + } + + if ($current_user_level >= Users::ACL_VISITOR && $current_user_level < Users::ACL_SITE_ADMIN) { + $action_more = $this->fw->route->action_more; + if ( + ($this->fw->route->action === FW::ACTION_SAVE || $this->fw->route->action === FW::ACTION_SHOW_FORM) + && !$this->fw->route->id + ) { + $action_more = FW::ACTION_MORE_NEW; + } + + if (!Users::i()->isAccessByRolesResourceAction( + $this->fw->userId(), + $this->fw->route->controller, + $this->fw->route->action, + $action_more, + $this->access_actions_to_permissions + )) { + throw new AuthException("Bad access - Not authorized (3)"); + } + } + } + } diff --git a/www/php/fw/FwVueController.php b/www/php/fw/FwVueController.php index 865dc8e..7556cda 100644 --- a/www/php/fw/FwVueController.php +++ b/www/php/fw/FwVueController.php @@ -94,7 +94,7 @@ protected function filterListForJson(): void { * @throws NoModelException */ protected function setScopeInitial(array &$ps): void { - $ps["XSS"] = $_SESSION["XSS"]; + $ps["XSS"] = $_SESSION["XSS"] ?? ''; $ps["access_level"] = $this->fw->userAccessLevel(); $ps["me_id"] = $this->fw->userId(); //some specific from global fw.GLOBAL; @@ -268,10 +268,7 @@ public function ShowAction($form_id): ?array { $mode = reqs("mode"); // view or edit $ps = []; - $item = $this->model->one($id); - if (empty($item)) { - throw new NotFoundException(); - } + $item = $this->modelOneOrFail($id); // additionally, if we have autocomplete fields - preload their values $multi_rows = []; @@ -373,7 +370,7 @@ public function SaveAction($form_id): array { $this->Validate($id, $item); // load old record if necessary - // $item_old = $this->model->one($id); + // $item_old = $this->modelOne($id); $itemdb = FormUtils::filter($item, $this->save_fields); FormUtils::filterCheckboxes($itemdb, $item, $this->save_fields_checkboxes, $this->isPatch()); diff --git a/www/php/fw/ImageUtils.php b/www/php/fw/ImageUtils.php index 52e98f2..c6f9e60 100644 --- a/www/php/fw/ImageUtils.php +++ b/www/php/fw/ImageUtils.php @@ -51,6 +51,8 @@ public static function resize(string $in_file, int $maxw = 0, int $maxh = 0, str $old_w = imagesx($img); $old_h = imagesy($img); # logger(".old: . $old_w,$old_h"); + $new_w = $old_w; + $new_h = $old_h; $ratio = $old_w / $old_h; $w_scale = 1; diff --git a/www/php/fw/ParsePage.php b/www/php/fw/ParsePage.php index 6b21608..8015aa9 100644 --- a/www/php/fw/ParsePage.php +++ b/www/php/fw/ParsePage.php @@ -60,6 +60,7 @@ 2025-01-16 - comments support <~#tag> 2025-01-17 - added support for attributes: currency, url, noparse 2025-03-30 - added support for languageCallback +2025-07-19 - repeat supports associative arrays, added repeat.key ########################################################## tags in templates should be in <~name [attributes]> format @@ -103,6 +104,9 @@ repeat.total (total number of items) repeat.index (0-based) repeat.iteration (1-based) + repeat.even (0-odd, 1-even) + repeat.odd (0-even, 1-odd) + repeat.key (key of current item if array is associative) example: <~/common/comma unless="repeat.last"> - will add comma template for all but last item sub - this attribute tell parser to use sub-hashtable for parse subtemplate ($data hash should contain reference to hash), examples: @@ -228,6 +232,9 @@ public function __construct(array $options = []) { if (isset($options['isLangUpdate'])) { $this->setLangUpdate($options['isLangUpdate']); } + if (isset($options['isLangParse'])) { + $this->setLangParse($options['isLangParse']); + } if (isset($options['languageCallback'])) { $this->setLanguageCallback($options['languageCallback']); } @@ -279,6 +286,11 @@ public function setLangUpdate(bool $flag): self { return $this; } + public function setLangParse(bool $flag): self { + $this->langParse = $flag; + return $this; + } + public function setLanguageCallback(callable $callback): self { $this->languageCallback = $callback; return $this; @@ -348,7 +360,7 @@ public function parseString(string $template, array $data): string { * @return string Parsed template content * @throws Exception */ - private function parseTemplate(string $templateName, array $data, string $inlineContent = '', array $parentData = null, array $parentAttrs = null): string { + private function parseTemplate(string $templateName, array $data, string $inlineContent = '', ?array $parentData = null, ?array $parentAttrs = null): string { #$this->logger("DEBUG", "Parsing template: [$templateName]" . ($inlineContent !== '' ? ' inline' : '')); // Resolve the full template path $templateName = $this->getFullTemplatePath($templateName); @@ -666,7 +678,7 @@ private function isTruthy($val): bool { * @param array|null $parentData * @return mixed The variable value or null if not found */ - private function getVariableValue(string $tagName, array $data = null, array $parentData = null): mixed { + private function getVariableValue(string $tagName, ?array $data = null, ?array $parentData = null): mixed { if ($tagName === '' || is_null($data)) { return null; } @@ -734,7 +746,8 @@ private function processRepeatTag(string $content, string $tagFull, string $tagN $loopedOutput = ''; $items = $tagValue; $total = count($items); - foreach ($items as $index => $item) { + $index = 0; + foreach ($items as $key => $item) { // Add repeat.* variables $item['repeat.first'] = ($index === 0) ? 1 : 0; $item['repeat.last'] = ($index === $total - 1) ? 1 : 0; @@ -743,10 +756,12 @@ private function processRepeatTag(string $content, string $tagFull, string $tagN $item['repeat.total'] = $total; $item['repeat.even'] = ($index % 2) ? 1 : 0; $item['repeat.odd'] = ($index % 2) ? 0 : 1; + $item['repeat.key'] = $key; // Parse template for one item $tagTplPath = $this->getTagTemplatePath($tagName, $templateName, $isInline); $loopedOutput .= $this->parseTemplate($tagTplPath, $item, $inlineTemplate, $data, $attributes); + $index++; } return $this->replaceTagRaw($content, $tagFull, $tagName, $loopedOutput, $isInline); @@ -912,7 +927,7 @@ private function processRadioTag(string $content, string $tagFull, string $tagNa * Show the selected label from .sel file or array (like a read-only version of select/radio). */ private function processSelvalueTag(string $content, string $tagFull, string $tagName, array $attributes, array $data): string { - $selectedValue = $this->getVariableValue($attributes['selvalue'], $data); + $selectedValue = $this->getVariableValue($attributes['selvalue'], $data) ?? ''; $options = $this->loadOptions($tagName, $data); $label = $options[$selectedValue] ?? ''; @@ -1057,18 +1072,33 @@ private function applyModifiers($value, $attributes, bool $is_htmlescape = false #$this->logger("DEBUG", "Applying modifiers to value: ", $value, $attributes); - // If it's not scalar but "json" is requested, we handle that - if (!is_scalar($value)) { - if (isset($attributes['json'])) { - // If "json=pretty" => pretty print - $options = (strtolower($attributes['json']) === 'pretty') ? JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK : JSON_NUMERIC_CHECK; - $result = json_encode($value, $options); + if (isset($attributes['json'])) { + $json_options = JSON_NUMERIC_CHECK | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + if (strtolower((string)$attributes['json']) === 'pretty') { + $json_options |= JSON_PRETTY_PRINT; + } + + if (!is_scalar($value)) { + $result = json_encode($value, $json_options); if ($is_htmlescape && !isset($attributes['noescape'])) { $result = $this->htmlescape($result); } return $result; } - // Non-scalar but no json => default to empty + + if (is_string($value)) { + $decoded = json_decode($value, true); + if (json_last_error() === JSON_ERROR_NONE) { + $value = json_encode($decoded, $json_options); + } else { + $value = json_encode($value, $json_options); + } + } else { + $value = json_encode($value, $json_options); + } + } + + if (!is_scalar($value)) { return ''; } @@ -1153,13 +1183,6 @@ private function applyModifiers($value, $attributes, bool $is_htmlescape = false } } - // json (last pass, if the user wants to re-encode) - if (isset($attributes['json'])) { - // Re-encode if this is a scalar? - $options = (strtolower($attributes['json']) === 'pretty') ? JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK : JSON_NUMERIC_CHECK; - $value = json_encode($value, $options); - } - if ($is_htmlescape && !isset($attributes['noescape'])) { $value = $this->htmlescape($value); } diff --git a/www/php/fw/UploadUtils.php b/www/php/fw/UploadUtils.php index 1ea22e1..e4b4054 100644 --- a/www/php/fw/UploadUtils.php +++ b/www/php/fw/UploadUtils.php @@ -137,9 +137,9 @@ public static function uploadFile(int $item_id, string $module_basedir, array $f self::cleanupUpload($item_id, $module_basedir); $ext = self::uploadExt($file['name']); - if ($opt['ext']) { - $ext = $opt['ext']; - } #if required to save images in particular format - do this + if ($opt['ext'] ?? false) { + $ext = $opt['ext']; #if required to save images in particular format - do this + } $file_path_orig = self::getUploadPath($item_id, $module_basedir, $ext); @@ -171,7 +171,7 @@ public static function mkdirTree($dir): void { * @param int|null $level optional, default in $DEFAULT_ID2DIR_LEVEL, dir deep level * @return string path without trailing / */ - public static function id2dir(int $id, int $level = NULL): string { + public static function id2dir(int $id, ?int $level = NULL): string { if (is_null($level)) { $level = self::$DEFAULT_ID2DIR_LEVEL; } diff --git a/www/php/fw/Utils.php b/www/php/fw/Utils.php index b20afa6..a90767b 100644 --- a/www/php/fw/Utils.php +++ b/www/php/fw/Utils.php @@ -184,15 +184,28 @@ public static function br2n(string $str): string { * @return string */ public static function html2text(string $str): string { - $str = preg_replace("/\n+/", " ", $str); - $str = preg_replace("//", "\n", $str); - $str = preg_replace("/(?:<[^>]*>)+/", " ", $str); - return $str; - } - - public static function dehtml(string $str): string { - $aaa = preg_replace("/<[^>]*>/", "", $str); - return preg_replace("/%%[^%]*%%/", "", $aaa); //remove special tags too + $str = preg_replace('//i', "\n", $str); + $str = preg_replace('##i', "\n", $str); + $str = preg_replace('#<[^>]+>#', ' ', $str); + $str = html_entity_decode($str, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $str = str_replace("\xc2\xa0", ' ', $str); + $str = preg_replace('/[ \t]+/', ' ', $str); + $str = preg_replace("/\r?\n[\r\n]+/", "\n\n", $str); + return trim($str); + } + + public static function dehtml(mixed $str): string { + $str = strval($str); + $str = str_replace(["\r\n", "\r"], "\n", $str); + $str = preg_replace('//i', "\n", $str); + $str = preg_replace('##i', "\n", $str); + $str = preg_replace('#<[^>]+>#', ' ', $str); + $str = preg_replace("/%%[^%]*%%/", "", $str); //remove special tags too + $str = html_entity_decode($str, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $str = str_replace("\xc2\xa0", ' ', $str); + $str = preg_replace('/[ \t]+/', ' ', $str); + $str = preg_replace("/\n{3,}/", "\n\n", $str); + return trim($str); } /** @@ -204,7 +217,7 @@ public static function dehtml(string $str): string { * "other value" - use this value * @return array */ - public static function commastr2hash(string $sel_ids, string $value = null): array { + public static function commastr2hash(string $sel_ids, ?string $value = null): array { $result = array(); $ids = explode(",", $sel_ids); foreach ($ids as $i => $v) { @@ -494,6 +507,19 @@ public static function uuid(): string { return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); } + /** + * Return nanoID (2.1 trillion unique IDs with the default length/alphabet). + */ + public static function nanoID(): string { + $alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-'; + $size = 21; + $id = ''; + for ($i = 0; $i < $size; $i++) { + $id .= $alphabet[random_int(0, 63)]; + } + return $id; + } + /** * return random ID (2.18 x 10^14 = 218.3 trillion unique IDs for 8 chars, useful up to 15 million generations because of birthday paradox) * @param int $length @@ -779,25 +805,23 @@ public static function jsonDecode(string|null $str) { } /** - * load content from url - * @param string $url url to get info from - * @param string|array|null $params optional, if set - post will be used, instead of get. Can be string or array - * @param array|null $headers optional, add headers - * @param string $to_file optional, save response to file (for file downloads) - * @param array $curlinfo optional, return misc curl info by ref - * @param bool $report_errors - * @return false|string content received. FALSE if error + * Create and configure a cURL handle with shared defaults used by single and batch requests. */ - public static function loadUrl(string $url, string|array $params = null, array $headers = null, string $to_file = '', array &$curlinfo = array(), bool $report_errors = true): false|string { - logger("CURL load from: [$url]", $params, $headers, $to_file); + private static function createCurlHandle(string $url, string|array|null $params = null, ?array $headers = null, string $to_file = '', ?int $timeout_seconds = null): array|false { $cu = curl_init(); + if ($cu === false) { + return false; + } curl_setopt($cu, CURLOPT_URL, $url); - curl_setopt($cu, CURLOPT_TIMEOUT, 60); + $default_timeout = $to_file > '' ? 3600 : 60; + $timeout_to_set = is_null($timeout_seconds) ? $default_timeout : max(1, $timeout_seconds); + curl_setopt($cu, CURLOPT_TIMEOUT, $timeout_to_set); + curl_setopt($cu, CURLOPT_CONNECTTIMEOUT, min(10, $timeout_to_set)); curl_setopt($cu, CURLOPT_RETURNTRANSFER, true); - curl_setopt($cu, CURLOPT_FAILONERROR, true); #cause fail on >=400 errors - curl_setopt($cu, CURLOPT_FOLLOWLOCATION, true); #follow redirects - curl_setopt($cu, CURLOPT_MAXREDIRS, 8); #max redirects + curl_setopt($cu, CURLOPT_FAILONERROR, false); + curl_setopt($cu, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($cu, CURLOPT_MAXREDIRS, 8); if (is_array($headers)) { curl_setopt($cu, CURLOPT_HTTPHEADER, $headers); } @@ -806,13 +830,70 @@ public static function loadUrl(string $url, string|array $params = null, array $ curl_setopt($cu, CURLOPT_POST, 1); curl_setopt($cu, CURLOPT_POSTFIELDS, $params); } + + $tmp_file = ''; + $fh_to_file = null; if ($to_file > '') { - #downloading to tmp file first $tmp_file = $to_file . '.download'; $fh_to_file = fopen($tmp_file, 'wb'); + if ($fh_to_file === false) { + curl_close($cu); + return false; + } curl_setopt($cu, CURLOPT_FILE, $fh_to_file); - curl_setopt($cu, CURLOPT_TIMEOUT, 3600); #1h timeout } + + return [ + 'handle' => $cu, + 'file_handle' => $fh_to_file, + 'tmp_file' => $tmp_file, + ]; + } + + /** + * Build a stable array key for curl handles across PHP versions. + */ + private static function curlHandleKey($handle): string { + if (is_object($handle)) { + return 'o:' . spl_object_id($handle); + } + + return 'r:' . intval($handle); + } + + /** + * load content from url + * @param string $url url to get info from + * @param string|array|null $params optional, if set - post will be used, instead of get. Can be string or array + * @param array|null $headers optional, add headers + * @param string $to_file optional, save response to file (for file downloads) + * @param array $curlinfo optional, return misc curl info by ref + * @param bool $report_errors + * @param int|null $timeout_seconds optional timeout in seconds. If null - default used. + * @return false|string content received. FALSE if error + */ + public static function loadUrl(string $url, string|array|null $params = null, ?array $headers = null, string $to_file = '', array &$curlinfo = array(), bool $report_errors = true, ?int $timeout_seconds = null): false|string { + if (!is_null($params)) { + logger("NOTICE", "CURL POST [$url]"); + } else { + logger("NOTICE", "CURL GET [$url]", $params); + } + + $handle_data = self::createCurlHandle($url, $params, $headers, $to_file, $timeout_seconds); + if ($handle_data === false) { + $curlinfo = [ + 'error' => 'Failed to initialize curl handle', + ]; + if ($report_errors) { + logger('ERROR', 'CURL error: Failed to initialize curl handle'); + } + return false; + } + + $cu = $handle_data['handle']; + $fh_to_file = $handle_data['file_handle']; + $tmp_file = $handle_data['tmp_file']; + #curl_setopt($cu, CURLOPT_VERBOSE,true); ##curl_setopt($cu, CURLINFO_HEADER_OUT, 1); @@ -820,10 +901,12 @@ public static function loadUrl(string $url, string|array $params = null, array $ logger('TRACE', 'RESULT:', $result); $curlinfo = curl_getinfo($cu); #logger('TRACE', 'CURL INFO:', $curlinfo); - if (curl_error($cu)) { - $curlinfo['error'] = curl_error($cu); + $curl_error = trim((string)curl_error($cu)); + $http_code = intval($curlinfo['http_code'] ?? 0); + if ($curl_error !== '' || $http_code >= 400) { + $curlinfo['error'] = $curl_error !== '' ? $curl_error : 'HTTP error ' . $http_code; if ($report_errors) { - logger('ERROR', 'CURL error: ' . curl_error($cu)); + logger('ERROR', 'CURL error: ' . $curlinfo['error']); } $result = false; } @@ -831,19 +914,199 @@ public static function loadUrl(string $url, string|array $params = null, array $ #logger("CURL RESULT:", $result); if ($to_file > '') { - fclose($fh_to_file); + if (is_resource($fh_to_file)) { + fclose($fh_to_file); + } #if file download successfull - rename to destination #if failed - just remove tmp file - if ($result !== false) { - rename($tmp_file, $to_file); - } else { - unlink($tmp_file); + if ($tmp_file > '' && file_exists($tmp_file)) { + if ($result !== false) { + rename($tmp_file, $to_file); + $result = ''; + } else { + unlink($tmp_file); + } } } return $result; } + /** + * Load multiple URLs in parallel with bounded in-process curl_multi concurrency. + */ + public static function loadUrlBatch(array $requests, int $parallelMax = 4, bool $report_errors = true): false|array { + if (empty($requests)) { + return []; + } + + $parallelMax = max(1, $parallelMax); + $results = array_fill(0, count($requests), ['result' => false, 'curlinfo' => []]); + + $multiHandle = curl_multi_init(); + if ($multiHandle === false) { + return false; + } + + $activeHandles = []; + $pendingIndex = 0; + + $queueRequest = static function (int $request_index) use (&$requests, &$results, &$activeHandles, $multiHandle, $report_errors): void { + $request = is_array($requests[$request_index] ?? null) ? $requests[$request_index] : []; + $url = trim((string)($request['url'] ?? '')); + + if ($url === '') { + $results[$request_index] = [ + 'result' => false, + 'curlinfo' => ['error' => 'CURL batch request URL is required.'], + ]; + if ($report_errors) { + logger('ERROR', 'CURL error: CURL batch request URL is required.'); + } + return; + } + + $params = $request['params'] ?? null; + if (!is_null($params) && !is_string($params) && !is_array($params)) { + $results[$request_index] = [ + 'result' => false, + 'curlinfo' => ['error' => 'CURL batch request params must be string|array|null.'], + ]; + if ($report_errors) { + logger('ERROR', 'CURL error: CURL batch request params must be string|array|null.'); + } + return; + } + + $headers = is_array($request['headers'] ?? null) ? $request['headers'] : null; + $timeout = null; + if (array_key_exists('timeout_seconds', $request) && $request['timeout_seconds'] !== null) { + $timeout = max(1, intval($request['timeout_seconds'])); + } + + $handle_data = self::createCurlHandle($url, $params, $headers, '', $timeout); + if ($handle_data === false) { + $results[$request_index] = [ + 'result' => false, + 'curlinfo' => ['error' => 'Failed to initialize curl handle'], + ]; + if ($report_errors) { + logger('ERROR', 'CURL error: Failed to initialize curl handle'); + } + return; + } + + $handle = $handle_data['handle']; + $add_result = curl_multi_add_handle($multiHandle, $handle); + if ($add_result !== CURLM_OK) { + $error = function_exists('curl_multi_strerror') ? curl_multi_strerror($add_result) : 'Failed to queue CURL batch request.'; + $results[$request_index] = [ + 'result' => false, + 'curlinfo' => ['error' => $error], + ]; + if ($report_errors) { + logger('ERROR', 'CURL error: ' . $error); + } + curl_close($handle); + return; + } + + $activeHandles[self::curlHandleKey($handle)] = [ + 'handle' => $handle, + 'result_index' => $request_index, + ]; + }; + + try { + while (!empty($activeHandles) || $pendingIndex < count($requests)) { + while (count($activeHandles) < $parallelMax && $pendingIndex < count($requests)) { + $queueRequest($pendingIndex); + $pendingIndex++; + } + + if (empty($activeHandles)) { + continue; + } + + do { + $status = curl_multi_exec($multiHandle, $running); + } while ($status === CURLM_CALL_MULTI_PERFORM); + + if ($status !== CURLM_OK) { + $error = function_exists('curl_multi_strerror') ? curl_multi_strerror($status) : 'cURL multi execution failed.'; + foreach ($activeHandles as $meta) { + $results[$meta['result_index']] = [ + 'result' => false, + 'curlinfo' => ['error' => $error], + ]; + if ($report_errors) { + logger('ERROR', 'CURL error: ' . $error); + } + $handle = $meta['handle']; + curl_multi_remove_handle($multiHandle, $handle); + curl_close($handle); + } + $activeHandles = []; + break; + } + + while ($info = curl_multi_info_read($multiHandle)) { + $doneHandle = $info['handle']; + $handleId = self::curlHandleKey($doneHandle); + $meta = $activeHandles[$handleId] ?? null; + + if ($meta) { + $result = curl_multi_getcontent($doneHandle); + $curlinfo = curl_getinfo($doneHandle); + $curlError = trim((string)curl_error($doneHandle)); + $infoCode = intval($info['result'] ?? CURLE_OK); + $httpCode = intval($curlinfo['http_code'] ?? 0); + if ($curlError === '' && $infoCode !== CURLE_OK) { + $curlError = function_exists('curl_strerror') ? curl_strerror($infoCode) : 'cURL request failed.'; + } + if ($curlError === '' && $httpCode >= 400) { + $curlError = 'HTTP error ' . $httpCode; + } + + if ($curlError !== '') { + $curlinfo['error'] = $curlError; + if ($report_errors) { + logger('ERROR', 'CURL error: ' . $curlError); + } + $result = false; + } + + $results[$meta['result_index']] = [ + 'result' => $result === false ? false : strval($result), + 'curlinfo' => is_array($curlinfo) ? $curlinfo : [], + ]; + + unset($activeHandles[$handleId]); + } + + curl_multi_remove_handle($multiHandle, $doneHandle); + curl_close($doneHandle); + } + + if (!empty($activeHandles) && intval($running ?? 0) > 0) { + $select = curl_multi_select($multiHandle, 1.0); + if ($select === -1) { + usleep(10000); + } + } + } + } finally { + foreach ($activeHandles as $meta) { + $handle = $meta['handle']; + curl_multi_remove_handle($multiHandle, $handle); + curl_close($handle); + } + curl_multi_close($multiHandle); + } + + return $results; + } + /** * send file to URL with optional params using curl * @param string $url url to post file to @@ -852,7 +1115,7 @@ public static function loadUrl(string $url, string|array $params = null, array $ * @param array|null $headers optional, additional headers * @return bool|string content received. FALSE if error */ - public static function sendFileToUrl(string $url, string $from_file, array $params = null, array $headers = null): bool|string { + public static function sendFileToUrl(string $url, string $from_file, ?array $params = null, ?array $headers = null): bool|string { logger('TRACE', "CURL post file [$from_file] to: [$url]", $params, $headers); $cu = curl_init(); @@ -901,9 +1164,11 @@ public static function sendFileToUrl(string $url, string $from_file, array $para * @param array $json * @param array $headers * @param string $to_file optional, save response to file (for file downloads) + * @param array $curlinfo + * @param int|null $timeout_seconds optional timeout in seconds. If null - default used. * @return false|array json data received. FALSE if error */ - public static function postJson(string $url, array $json, array $headers = [], string $to_file = ''): false|array { + public static function postJson(string $url, array $json, array $headers = [], string $to_file = '', array &$curlinfo = array(), ?int $timeout_seconds = null): false|array { $jsonstr = json_encode($json); $headers = array_merge(array( @@ -912,7 +1177,7 @@ public static function postJson(string $url, array $json, array $headers = [], s 'Content-Length: ' . strlen($jsonstr), ), $headers); - $result = self::loadUrl($url, $jsonstr, $headers, $to_file); + $result = self::loadUrl($url, $jsonstr, $headers, $to_file, $curlinfo, true, $timeout_seconds); if ($result !== false) { if ($to_file > '') { #if it was file transfer, just construct successful response @@ -934,7 +1199,7 @@ public static function postJson(string $url, array $json, array $headers = [], s * @param array|null $headers optional, additional headers * @return false|array json data received. FALSE if error */ - public static function getJson(string $url, string $to_file = '', array $headers = null): false|array { + public static function getJson(string $url, string $to_file = '', ?array $headers = null, array &$curlinfo = array(), ?int $timeout_seconds = null): false|array { $headers2 = array( 'Accept: application/json', ); @@ -942,7 +1207,7 @@ public static function getJson(string $url, string $to_file = '', array $headers $headers2 = array_merge($headers2, $headers); } - $result = self::loadUrl($url, null, $headers2, $to_file); + $result = self::loadUrl($url, null, $headers2, $to_file, $curlinfo, true, $timeout_seconds); if ($result !== false) { if ($to_file > '') { #if it was file transfer, just construct successful response @@ -1032,6 +1297,32 @@ public static function str2url(string $str): string { return $str; } + /** + * Normalize a user-provided URL for safe href output. + * Empty result means the value should be rendered as plain text. + */ + public static function safeUrl(string $str): string { + $url = trim(preg_replace('/[\x00-\x1F\x7F]+/', '', $str) ?? ''); + if ($url === '' || str_starts_with($url, '//')) { + return ''; + } + + if (!preg_match('/^[a-z][a-z0-9+.-]*:/i', $url)) { + $url = self::str2url($url); + } + + $scheme = strtolower((string)parse_url($url, PHP_URL_SCHEME)); + if (!in_array($scheme, ['http', 'https', 'mailto', 'tel'], true)) { + return ''; + } + + if (($scheme === 'http' || $scheme === 'https') && !parse_url($url, PHP_URL_HOST)) { + return ''; + } + + return $url; + } + /** * capitalize string: * - if mode='all' - capitalize all words diff --git a/www/php/fw/db.php b/www/php/fw/db.php index 94ee44e..54f8bce 100644 --- a/www/php/fw/db.php +++ b/www/php/fw/db.php @@ -164,7 +164,7 @@ function dbqi(string $value): int { * @param string|null $field_type 's'(string, default if empty), 'i'(int), 'x'(no quote) * @return string, integer or 'NULL' string (if $field_type is not defined and $value is null) */ -function dbq(string $value, string $field_type = null): string { +function dbq(string $value, ?string $field_type = null): string { return DB::i()->quote($value, $field_type); } @@ -181,7 +181,7 @@ function dbqid(string $value): string { * @return string|null * @throws DBException */ -function db_value(string $table, array $where, string $field_name = null, string $order_by = null): ?string { +function db_value(string $table, array $where, ?string $field_name = null, ?string $order_by = null): ?string { return DB::i()->value($table, $where, $field_name, $order_by); } @@ -192,7 +192,7 @@ function db_value(string $table, array $where, string $field_name = null, string * @return string|null * @throws DBException */ -function db_valuep(string $sql, array $params = null): ?string { +function db_valuep(string $sql, ?array $params = null): ?string { return DB::i()->valuep($sql, $params); } @@ -204,7 +204,7 @@ function db_valuep(string $sql, array $params = null): ?string { * @return array assoc array (has keys as field names and values as field values) * @throws DBException */ -function db_row(string $table, array $where, string $order_by = null): array { +function db_row(string $table, array $where, ?string $order_by = null): array { return DB::i()->row($table, $where, $order_by); } @@ -215,7 +215,7 @@ function db_row(string $table, array $where, string $order_by = null): array { * @return array * @throws DBException */ -function db_rowp(string $sql, array $params = null): array { +function db_rowp(string $sql, ?array $params = null): array { return DB::i()->rowp($sql, $params); } @@ -228,7 +228,7 @@ function db_rowp(string $sql, array $params = null): array { * @return array * @throws DBException */ -function db_col(string $table, array $where, string $field_name = null, string $order_by = null): array { +function db_col(string $table, array $where, ?string $field_name = null, ?string $order_by = null): array { return DB::i()->col($table, $where, $field_name, $order_by); } @@ -239,7 +239,7 @@ function db_col(string $table, array $where, string $field_name = null, string $ * @return array * @throws DBException */ -function db_colp(string $sql, array $params = null): array { +function db_colp(string $sql, ?array $params = null): array { return DB::i()->colp($sql, $params); } @@ -253,7 +253,7 @@ function db_colp(string $sql, array $params = null): array { * @return array * @throws DBException */ -function db_array(string $table, array $where = null, string $order_by = null, int $limit = null, array|string $selected_fields = "*"): array { +function db_array(string $table, ?array $where = null, ?string $order_by = null, ?int $limit = null, array|string $selected_fields = "*"): array { return DB::i()->arr($table, $where, $order_by, $limit, $selected_fields); } @@ -264,7 +264,7 @@ function db_array(string $table, array $where = null, string $order_by = null, i * @return array * @throws DBException */ -function db_arrayp(string $sql, array $params = null): array { +function db_arrayp(string $sql, ?array $params = null): array { return DB::i()->arrp($sql, $params); } @@ -275,7 +275,7 @@ function db_arrayp(string $sql, array $params = null): array { * @return mysqli_result|bool object * @throws DBException */ -function db_query(string $sql, array $params = null): mysqli_result|bool { +function db_query(string $sql, ?array $params = null): mysqli_result|bool { return DB::i()->query($sql, $params); } @@ -286,7 +286,7 @@ function db_query(string $sql, array $params = null): mysqli_result|bool { * @return void * @throws DBException */ -function db_exec(string $sql, array $params = null): void { +function db_exec(string $sql, ?array $params = null): void { DB::i()->exec($sql, $params); } @@ -309,7 +309,7 @@ function db_identity(): int { * @return int number of deleted rows * @throws DBException */ -function db_delete(string $table, array $where, string $order_by = null, string $limit = null): int { +function db_delete(string $table, array $where, ?string $order_by = null, ?string $limit = null): int { return DB::i()->delete($table, $where, $order_by, $limit); } @@ -344,7 +344,7 @@ function db_update(string $table, array $fields, array $where): int { * @return int * @throws DBException */ -function db_updatep(string $sql, array $params = null): int { +function db_updatep(string $sql, ?array $params = null): int { return DB::i()->updatep($sql, $params); } @@ -358,7 +358,7 @@ function db_updatep(string $sql, array $params = null): int { * @return bool true if record exists or false if not * @throws DBException */ -function db_is_record_exists(string $table_name, string $uniq_value, string $column, string $not_id = null, string $not_id_column = 'id'): bool { +function db_is_record_exists(string $table_name, string $uniq_value, string $column, ?string $not_id = null, string $not_id_column = 'id'): bool { return DB::i()->isRecordExists($table_name, $uniq_value, $column, $not_id, $not_id_column); } @@ -563,6 +563,7 @@ class DB { #can also contain: # CONNECT_ATTEMPTS - how many times to try to connect (default CONNECT_ATTEMPTS) # CONNECT_TIMEOUT - how many seconds to wait for connection (default 0 - no timeout) + # LOCAL_INFILE - if true, enable client-side LOAD DATA LOCAL INFILE for this connection # WAIT_TIMEOUT - how many seconds to wait for query (default 0 - no timeout) # DEADLOCK_RETRY_ATTEMPTS - how many times to execute query+retry on deadlock (default DEADLOCK_RETRY_ATTEMPTS retries before exception, min=1 - no retries) # IS_LOG - if true - log all queries (default false) @@ -627,10 +628,15 @@ public function connect(): void { try { $last_exception = null; - if (isset($this->config['CONNECT_TIMEOUT'])) { - #if need to set connect timeout - initialize in a different way + if (isset($this->config['CONNECT_TIMEOUT']) || !empty($this->config['LOCAL_INFILE'])) { + #if need to set connect timeout or client-side LOAD DATA LOCAL INFILE - initialize in a different way $this->dbh = mysqli_init(); - $this->dbh->options(MYSQLI_OPT_CONNECT_TIMEOUT, $this->config['CONNECT_TIMEOUT']); + if (isset($this->config['CONNECT_TIMEOUT'])) { + $this->dbh->options(MYSQLI_OPT_CONNECT_TIMEOUT, $this->config['CONNECT_TIMEOUT']); + } + if (!empty($this->config['LOCAL_INFILE'])) { + $this->dbh->options(MYSQLI_OPT_LOCAL_INFILE, true); + } $this->dbh->real_connect($this->config['HOST'], $this->config['USER'], $this->config['PWD'], $this->config['DBNAME'], ($this->config['PORT'] > '' ? (int)$this->config['PORT'] : null)); } else { #@ hides connection warning, which is unnecessary as exception thrown anyway @@ -679,18 +685,9 @@ public function connect(): void { * @throws DBException */ public function checkConnect(): void { - $is_reconnect = !$this->is_connected || is_null($this->dbh); - - if (!$is_reconnect) { - try { - $is_reconnect = !@$this->dbh->ping(); #we don't need Warning: mysqli::ping(): MySQL server has gone away - } catch (mysqli_sql_exception) { - //if ping fails - MySQL server has gone away - $is_reconnect = true; - } - } - - if ($is_reconnect) { + # Do not probe live handles here: this runs before every query. + # Stale handles are reconnected once when the actual query fails with ERROR_GONE_AWAY. + if (!$this->is_connected || is_null($this->dbh)) { $this->connect(); } } @@ -726,7 +723,7 @@ public function handleError(Exception $ex) { * @return mysqli_result|bool * @throws DBException */ - public function query(string $sql, array $params = null): mysqli_result|bool { + public function query(string $sql, ?array $params = null): mysqli_result|bool { $result = null; $this->checkConnect(); @@ -734,7 +731,8 @@ public function query(string $sql, array $params = null): mysqli_result|bool { DB::$SQL_QUERY_CTR++; $deadlock_attempts = $this->config['DEADLOCK_RETRY_ATTEMPTS'] ?? self::DEADLOCK_RETRY_ATTEMPTS; #max deadlock retry attempts - $last_ex = null; + $last_ex = null; + $is_reconnect_retry = true; while ($deadlock_attempts--) { try { @@ -744,7 +742,14 @@ public function query(string $sql, array $params = null): mysqli_result|bool { } catch (DBException $ex) { $last_ex = $ex; $err_msg = $ex->getMessage(); - if (preg_match("/deadlock/i", $err_msg)) { + if ($is_reconnect_retry && $ex->getCode() === self::ERROR_GONE_AWAY) { + $is_reconnect_retry = false; + $this->logger('NOTICE', "Reconnect/retry on lost DB connection", $err_msg); + $this->dbh = null; + $this->is_connected = false; + $this->connect(); + $deadlock_attempts++; + } elseif (preg_match("/deadlock/i", $err_msg)) { $this->logger('NOTICE', "Sleep/retry on deadlock", "attempts left:" . $deadlock_attempts . $err_msg); sleep(rand(self::SLEEP_RETRY_MIN, self::SLEEP_RETRY_MAX)); #if got deadlock - sleep 1-3s before repeat } else { @@ -767,7 +772,7 @@ public function query(string $sql, array $params = null): mysqli_result|bool { * @return null|bool|mysqli_result object * @throws DBException */ - public function queryInner(string $sql, array $params = null): null|bool|mysqli_result { + public function queryInner(string $sql, ?array $params = null): null|bool|mysqli_result { $result = null; try { @@ -866,7 +871,7 @@ protected function paramsExpand(string &$sql, array &$params): void { * @param array|null $params * @return void */ - protected function loggerInner(string $sql, array $params = null): void { + protected function loggerInner(string $sql, ?array $params = null): void { $host = $this->config['HOST']; #for logger - just leave first name in domain $dbhost_info = substr($host, 0, strpos($host, '.')) . '(' . $this->config['USER'] . '):' . $this->config['DBNAME'] . ' '; @@ -959,7 +964,7 @@ public function queryPrepared(mysqli_stmt $st, array $params): null|array { * @return int number of affected rows or last inserted id * @throws DBException */ - public function exec(string $sql, array $params = null, bool $is_get_identity = false): int { + public function exec(string $sql, ?array $params = null, bool $is_get_identity = false): int { $this->query($sql, $params); $this->lastRows = $this->dbh->affected_rows; if ($is_get_identity) { @@ -1010,7 +1015,7 @@ public function execMultipleSQL(string $sql, bool $is_ignore_errors = false): in * @return array * @throws DBException */ - public function row(string $table, array $where, string $order_by = null): array { + public function row(string $table, array $where, ?string $order_by = null): array { $qp = $this->buildSelect($table, $where, $order_by, 1); return $this->rowp($qp->sql, $qp->params); } @@ -1022,7 +1027,7 @@ public function row(string $table, array $where, string $order_by = null): array * @return array * @throws DBException */ - public function rowp(string $sql, array $params = null): array { + public function rowp(string $sql, ?array $params = null): array { $res = $this->query($sql, $params); #we only need a first row from the result $row = $res->fetch_assoc(); @@ -1040,7 +1045,7 @@ public function rowp(string $sql, array $params = null): array { * @return array> * @throws DBException */ - public function arr(string $table, array $where, string $order_by = null, string $limit = null, array|string $select_fields = '*'): array { + public function arr(string $table, ?array $where = null, ?string $order_by = null, ?string $limit = null, array|string $select_fields = '*'): array { if (is_array($select_fields)) { $select_fields = implode(',', array_map(fn($v) => $this->qid($v), $select_fields)); // quote all fields } @@ -1055,7 +1060,7 @@ public function arr(string $table, array $where, string $order_by = null, string * @return array> * @throws DBException */ - public function arrp(string $sql, array $params = null): array { + public function arrp(string $sql, ?array $params = null): array { $res = $this->query($sql, $params); $result = $res->fetch_all(MYSQLI_ASSOC); $res->free(); @@ -1071,7 +1076,7 @@ public function arrp(string $sql, array $params = null): array { * @return string|null * @throws DBException */ - public function value(string $table, array $where, string $field_name = null, string $order_by = null): string|null { + public function value(string $table, array $where, ?string $field_name = null, ?string $order_by = null): string|null { if (is_null($field_name)) { $field_name = '*'; } elseif ($field_name === '1' || str_starts_with(strtolower($field_name), 'count(')) { @@ -1092,7 +1097,7 @@ public function value(string $table, array $where, string $field_name = null, st * @return string|null * @throws DBException */ - public function valuep(string $sql, array $params = null): string|null { + public function valuep(string $sql, ?array $params = null): string|null { $res = $this->query($sql, $params); $result = $res->fetch_row(); $res->free(); @@ -1108,7 +1113,7 @@ public function valuep(string $sql, array $params = null): string|null { * @return array * @throws DBException */ - public function col(string $table, array $where, string $field_name = null, string $order_by = null): array { + public function col(string $table, array $where, ?string $field_name = null, ?string $order_by = null): array { if (is_null($field_name)) { $field_name = '*'; } else { @@ -1125,7 +1130,7 @@ public function col(string $table, array $where, string $field_name = null, stri * @return array * @throws DBException */ - public function colp(string $sql, array $params = null): array { + public function colp(string $sql, ?array $params = null): array { $res = $this->query($sql, $params); $result = $res->fetch_all(); $res->free(); @@ -1158,7 +1163,7 @@ public function selectRaw(string $fields, string $from, string $where, array $wh * @param string|null $limit * @return DBQueryAndParams */ - public function buildDelete(string $table, array $where, string $order_by = null, string $limit = null): DBQueryAndParams { + public function buildDelete(string $table, array $where, ?string $order_by = null, ?string $limit = null): DBQueryAndParams { $result = new DBQueryAndParams(); $result->sql = 'DELETE FROM ' . $this->qid($table); if ($where) { @@ -1184,7 +1189,7 @@ public function buildDelete(string $table, array $where, string $order_by = null * @return int number of affected rows * @throws DBException */ - public function delete(string $table, array $where, string $order_by = null, string $limit = null): int { + public function delete(string $table, array $where, ?string $order_by = null, ?string $limit = null): int { $qp = $this->buildDelete($table, $where, $order_by, $limit); return $this->exec($qp->sql, $qp->params); } @@ -1278,6 +1283,8 @@ public function buildUpdate(string $table, array $fields, array $where): DBQuery $qp_where = $this->prepareParams($table, $where); $result->sql .= ' WHERE ' . $qp_where->sql; $result->params = array_merge($qp_set->params, $qp_where->params); + } else { + $result->params = $qp_set->params; } return $result; @@ -1303,7 +1310,7 @@ public function update(string $table, array $fields, array $where): int { * @return int number of affected rows * @throws DBException */ - public function updatep(string $sql, array $params = null): int { + public function updatep(string $sql, ?array $params = null): int { return $this->exec($sql, $params); } @@ -1336,7 +1343,7 @@ public function upsert(string $table, array $fields, array $where): int { * @return bool true if record exists or false if not * @throws DBException */ - public function isRecordExists(string $table_name, mixed $uniq_value, string $column, string $not_id = null, string $not_id_column = 'id'): bool { + public function isRecordExists(string $table_name, mixed $uniq_value, string $column, ?string $not_id = null, string $not_id_column = 'id'): bool { $not_sql = ''; $params = array($uniq_value); if (!is_null($not_id)) { @@ -1707,12 +1714,12 @@ public function insqli(array $values): string { * @param string $select_fields comma separated fields to select or '*' * @return DBQueryAndParams */ - public function buildSelect(string $table, array $where, string $order_by = null, string $limit = null, string $select_fields = "*"): DBQueryAndParams { + public function buildSelect(string $table, ?array $where = null, ?string $order_by = null, ?string $limit = null, string $select_fields = "*"): DBQueryAndParams { $result = new DBQueryAndParams(); $result->sql = "SELECT " . $select_fields . " FROM " . $this->qid($table); - if (count($where) > 0) { + if (!empty($where)) { $qp = $this->prepareParams($table, $where); if ($qp->sql > '') { $result->sql .= ' WHERE ' . $qp->sql; diff --git a/www/php/fw/dispatcher.php b/www/php/fw/dispatcher.php index 9891819..137a0b9 100644 --- a/www/php/fw/dispatcher.php +++ b/www/php/fw/dispatcher.php @@ -80,11 +80,13 @@ class Dispatcher { public string $ROOT_URL; public array $ROUTE_PREFIXES; #array('/Admin', '/My', ...) public string $request_url; #last url processed by uriToRoute + public string $ROUTE_ID_REGEX; - public function __construct(array $ROUTES = array(), string $ROOT_URL = '', array $ROUTE_PREFIXES = array()) { + public function __construct(array $ROUTES = array(), string $ROOT_URL = '', array $ROUTE_PREFIXES = array(), string $ROUTE_ID_REGEX = '') { $this->ROUTES = $ROUTES; $this->ROOT_URL = $ROOT_URL; $this->ROUTE_PREFIXES = $ROUTE_PREFIXES; + $this->ROUTE_ID_REGEX = $ROUTE_ID_REGEX ?: '\d+|[\w_]{32,}'; } /** @@ -147,7 +149,7 @@ public function runController(string $controller_name, string $action_name, arra $ps = $controller->$method_name(...$aparams); // check/override _basedir from controller for non-json requests - if (!fw::i()->isJsonExpected() && !isset($ps['_basedir_controller']) && !empty($controller->template_basedir)) { + if (is_array($ps) && !fw::i()->isJsonExpected() && !isset($ps['_basedir_controller']) && !empty($controller->template_basedir)) { logger("TRACE", "Controller [$controller_name] template_basedir override to [$controller->template_basedir]"); $ps['_basedir_controller'] = $controller->template_basedir; } @@ -259,7 +261,7 @@ public function detectOperation(string $method, string $id, string $action_more) if (!$result) { #just respond with 405 and exit immediately header("HTTP/1.0 405 Method Not Allowed", true, 405); - header("Allow: GET, POST, PUT, DELETE, OPTIONS"); #instruct client what methods are allowed + header("Allow: GET, POST, PUT, PATCH, DELETE, OPTIONS"); #instruct client what methods are allowed exit; #throw new Exception('Unsupported REST params combination'); #405 Method Not Allowed } @@ -345,7 +347,7 @@ public function uriToRoute(string $method, string $uri, array $ROUTES): stdClass if (!$is_route_found) { #if no special ROUTES found - try to detect default RESTful URLs $RX_CONTROLLER = '[^/]+'; - $RX_ID = '\d+|[\w_]{32,}'; // Match numeric IDs or UUID-like IDs (at least 32 chars, no dashes). I.e. custom action names should be less than 32 chars + $RX_ID = $this->ROUTE_ID_REGEX; // Match numeric IDs or UUID-like IDs by default. I.e. custom action names should be less than 32 chars #get RESTful URI $is_match = preg_match("!^/($RX_CONTROLLER)(?:/(new|\.\w+)|/($RX_ID)(?:\.(\w+))?(?:/(edit|delete))?)?/?$!i", $uri, $m); diff --git a/www/php/fw/fw.php b/www/php/fw/fw.php index 927bbcd..66b4082 100644 --- a/www/php/fw/fw.php +++ b/www/php/fw/fw.php @@ -101,7 +101,7 @@ class fw { public static function run(array $ROUTES = []): void { $uri = strtok($_SERVER["REQUEST_URI"] ?? '', '?'); - logger('*** REQUEST START [' . $uri . ']'); + logger('*** REQUEST START [' . ($_SERVER['REQUEST_METHOD'] ?? 'GET') . ' ' . $uri . ']'); self::$start_time = microtime(true); $fw = fw::i(); @@ -114,7 +114,8 @@ public static function run(array $ROUTES = []): void { FwHooks::initRequest($fw, $uri); #if request content type is "application/json" - parse JSON and put to $_REQUEST - if (str_contains($_SERVER['CONTENT_TYPE'] ?? '', 'application/json')) { + $content_type = $_SERVER['CONTENT_TYPE'] ?? $_SERVER['HTTP_CONTENT_TYPE'] ?? ''; + if (str_contains($content_type, 'application/json')) { $fw->postedJson = Utils::parsePostedJson(); } @@ -138,7 +139,7 @@ public static function run(array $ROUTES = []): void { $_SESSION['_flash'] = array(); } - $fw->dispatcher = new Dispatcher($ROUTES, $fw->config->ROOT_URL, $fw->config->ROUTE_PREFIXES); + $fw->dispatcher = new Dispatcher($ROUTES, $fw->config->ROOT_URL, $fw->config->ROUTE_PREFIXES, $fw->config->ROUTE_ID_REGEX ?? ''); $fw->route = $fw->dispatcher->getRoute(); $fw->request_url = $fw->dispatcher->request_url; @@ -289,6 +290,25 @@ public static function controller(string $controller_class): FwController { return $object; } + /** + * Clear cached controllers, models, and optionally request-scoped cache values between tests. + */ + public function resetRuntimeCaches(bool $clearRequestCache = true): void { + $this->controllers_cache = []; + $this->models_cache = []; + + if ($clearRequestCache && isset($this->cache) && method_exists($this->cache, 'clearRequest')) { + $this->cache->clearRequest(); + } + } + + /** + * Allow tests to inject a model singleton instance without reflection. + */ + public function setModelInstanceForTest(string $modelClass, FwModel $instance): void { + $this->models_cache[strtolower($modelClass)] = $instance; + } + public function __construct() { global $CONFIG; spl_autoload_register(array($this, 'autoload')); @@ -334,6 +354,17 @@ public function autoload(string $class_name): void { } else { // For models $dirs[] = $bdir . '../models/'; + foreach (($this->config->AUTOLOAD_MODELS ?? []) as $subfolder) { + $subfolder = trim((string)$subfolder); + if ($subfolder === '' || !str_starts_with($subfolder, '/')) { + continue; + } + + $subfolder = trim($subfolder, '/'); + if ($subfolder !== '') { + $dirs[] = $bdir . '../models/' . $subfolder . '/'; + } + } // If not a framework’s own model - remove the suffix if ($class_name !== 'FwModel') { @@ -527,7 +558,9 @@ public function routeRedirect(string $action, ?string $controller = null, ?array #throw AuthException if request XSS is not passed or not equal to session's value public function checkXSS(bool $is_die = true): bool { - if ($_SESSION["XSS"] != reqs("XSS")) { + $session_xss = $_SESSION["XSS"] ?? ''; + $request_xss = reqs("XSS"); + if ($session_xss === '' || $session_xss !== $request_xss) { #logger("WARN", "XSS CHECK FAIL"); #too excessive logging if ($is_die) { throw new AuthException("XSS Error. Reload the page or try to re-login"); @@ -561,7 +594,7 @@ private function auth(stdClass $route): void { || $route->action == self::ACTION_DELETE || $route->action == self::ACTION_SAVE_MULTI ) - && $session_xss > "" && $session_xss != $request_xss + && ($session_xss === '' || $session_xss !== $request_xss) ) { throw new AuthException("XSS Error"); } @@ -1091,7 +1124,7 @@ public function sendEmailTpl(string|array $to_email, string $tpl, array $ps, arr return $this->sendEmail($to_email, $msg_subj, $msg_body, $options); } - public function logActivity(string $log_types_icode, string $entity_icode, int $item_id = 0, string $iname = "", array $changed_fields = null): void { + public function logActivity(string $log_types_icode, string $entity_icode, int $item_id = 0, string $iname = "", ?array $changed_fields = null): void { if (!$this->is_log_events) { return; } @@ -1230,6 +1263,34 @@ function reqb(string $name, bool $default = false): bool { } } +/** + * Safely export values to JSON for logs/debugging with limited depth. + */ +function dumper($data, int $depth = 16): string { + try { + if ($data instanceof Throwable) { + $data = [ + 'type' => get_class($data), + 'message' => $data->getMessage(), + 'code' => $data->getCode(), + 'file' => $data->getFile(), + 'line' => $data->getLine(), + 'trace' => $data->getTrace(), + ]; + } + + $json = json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT, $depth); + } catch (Throwable) { + $json = '"[unserializable data]"'; + } + + if ($json === false) { + $json = '"[json encoding error]"'; + } + + return $json; +} + function reqjson(string $name): array { $value = _req($name, []); if (is_array($value)) { @@ -1277,9 +1338,7 @@ function logger(mixed ...$params): void { // 3) Skip logging if current config doesn't permit it $currentLogLevel = fw::$LOG_LEVELS[$CONFIG['LOG_LEVEL']] ?? fw::$LOG_LEVELS['INFO']; $requestedLogLevel = fw::$LOG_LEVELS[$logType]; - if ($requestedLogLevel > $currentLogLevel) { - return; - } + $isLogLevelSkipped = $requestedLogLevel > $currentLogLevel; // 4) Build the combined log message from remaining arguments // The first of $params is considered the "main message" if scalar, else var_export @@ -1287,7 +1346,7 @@ function logger(mixed ...$params): void { if (!empty($params)) { // Start with the first item $main = $params[0]; - $message = is_scalar($main) ? (string)$main : @var_export($main, true); + $message = is_scalar($main) ? (string)$main : dumper($main); // Append everything else for ($i = 1, $count = count($params); $i < $count; $i++) { @@ -1295,19 +1354,25 @@ function logger(mixed ...$params): void { if (is_scalar($item)) { $message .= ' ' . $item; } else { - $message .= "\n" . @var_export($item, true); + $message .= "\n" . dumper($item); } } } - $isLogEnabled = !empty($CONFIG['LOG_DESTINATION']); + $logDestination = (string)($CONFIG['LOG_DESTINATION'] ?? ''); + if (!empty($CONFIG['LOG_DESTINATION_OFFLINE']) && PHP_SAPI === 'cli') { + $logDestination = (string)$CONFIG['LOG_DESTINATION_OFFLINE']; + } + + $isLogEnabled = $logDestination !== ''; + $isSentryEnabled = !empty($CONFIG['LOG_SENTRY_DSN']) && !$isLogLevelSkipped; // 5) If logging to file is on, gather file/line info via debug_backtrace. // (We skip it if we’re not writing anywhere, to save overhead.) $fileName = ''; $funcName = ''; $line = ''; - if ($isLogEnabled) { + if ($isLogEnabled || $isSentryEnabled) { [$fileName, $funcName, $line] = getCallerInfo(); } @@ -1319,7 +1384,7 @@ function logger(mixed ...$params): void { } // 7) If LOG_DESTINATION is set, write to error_log - if ($isLogEnabled) { + if ($isLogEnabled && !$isLogLevelSkipped) { $now = (new DateTime())->format('Y-m-d H:i:s.v'); $pid = getmypid(); $prefix = "$now $pid $logType $fileName::$funcName($line) "; @@ -1327,11 +1392,11 @@ function logger(mixed ...$params): void { if (!str_ends_with($message, "\n")) { $message .= "\n"; } - error_log($prefix . $message, $CONFIG['LOG_MESSAGE_TYPE'] ?? 0, $CONFIG['LOG_DESTINATION']); + error_log($prefix . $message, $CONFIG['LOG_MESSAGE_TYPE'] ?? 0, $logDestination); } // 8) If Sentry is configured, push logs there - if (!empty($CONFIG['LOG_SENTRY_DSN'])) { + if ($isSentryEnabled) { try { //add extra params for Sentry $params[] = [ diff --git a/www/php/models/Att.php b/www/php/models/Att.php index bee9b8b..4500d2e 100644 --- a/www/php/models/Att.php +++ b/www/php/models/Att.php @@ -109,6 +109,49 @@ public function uploadMulti(array $item): array { return $result; } + public function uploadString(array $item, string $filename, string $content): array { + $result = []; + if (!strlen($content)) { + return $result; + } + + $itemdb = $item; + $itemdb['status'] = self::STATUS_UNDER_UPDATE; + $id = $this->add($itemdb); + + $ext = UploadUtils::uploadExt($filename); + $filepath = $this->getUploadPath($id, $ext, self::SIZE_ORIGINAL); + file_put_contents($filepath, $content); + + logger("uploaded to [" . $filepath . "]"); + + $fields = [ + 'fname' => $filename, + 'fsize' => strlen($content), + 'ext' => $ext, + 'storage' => self::STORAGE_FILE, + 'status' => self::STATUS_ACTIVE, + ]; + + if (UploadUtils::isImgExt($ext)) { + $fields["is_image"] = 1; + + ImageUtils::resize($filepath, self::MAX_THUMB_W_S, self::MAX_THUMB_H_S, $this->getUploadImgPath($id, self::SIZE_SMALL, $ext)); + ImageUtils::resize($filepath, self::MAX_THUMB_W_M, self::MAX_THUMB_H_M, $this->getUploadImgPath($id, self::SIZE_MEDIUM, $ext)); + ImageUtils::resize($filepath, self::MAX_THUMB_W_L, self::MAX_THUMB_H_L, $this->getUploadImgPath($id, self::SIZE_LARGE, $ext)); + } + + $this->update($id, $fields); + + $result = $fields; + $result["id"] = $id; + $result["filepath"] = $filepath; + + $this->moveToStorage($id); + + return $result; + } + /** * Update tmp uploads to be linked to specific entity id * @param string $entity_icode @@ -159,7 +202,7 @@ public function getUrl(int $id, string $size = ''): string { } if ($item['storage'] == self::STORAGE_S3) { - $result = S3::i()->getSignedUrl($this->getS3KeyByID($id), $size); + $result = S3::i()->getSignedUrl($this->getS3KeyByID($id, $size)); } else { #if /Att need to be on offline folder $result = $this->fw->GLOBAL['ROOT_URL'] . '/Att/' . $id; @@ -181,7 +224,7 @@ public function getUrlDirect(array|int $id_or_item, string $size = ''): string { if (is_array($id_or_item)) { $item = $id_or_item; if ($item['storage'] == self::STORAGE_S3) { - return S3::i()->getSignedUrl($this->getS3KeyByID($item['id']), $size); + return S3::i()->getSignedUrl($this->getS3KeyByID($item['id'], $size)); } elseif ($item['storage'] == self::STORAGE_TABLE) { return $this->getUrl($item['id'], $size); // don't have direct url for table storage } else { @@ -213,12 +256,7 @@ public function delete(int $id, bool $is_perm = false): bool { S3::i()->deleteObject($this->table_name . "/" . $item["id"]); } elseif ($item["storage"] == self::STORAGE_TABLE) { - $this->update($id, [ - 'raw' => null, - 'raw_s' => null, - 'raw_m' => null, - 'raw_l' => null, - ]); + // no need to clear raw fields as the whole record is deleted below } else { // local storage @@ -262,6 +300,11 @@ public function checkAccess(int|string $id = 0, string $action = ""): void { } } + public function oneRaw(int $id, string $size = ""): ?string { + $suffix = $size == self::SIZE_ORIGINAL ? "" : "_" . $size; + return $this->db->value($this->table_name, ["id" => $id], "raw" . $suffix); + } + /** * transmit file by id/size to user's browser, optional disposition - attachment(default)/inline * also check access rights - throws ApplicationException if file not accessible by cur user @@ -286,8 +329,16 @@ public function transmitFile(int $id, string $size = '', string $disposition = ' $size = UploadUtils::checkSize($size); - $filepath = $this->getUploadPath($id, $item['ext'], $size); - $filetime = filemtime($filepath); + $is_file_storage = ($item['storage'] == self::STORAGE_FILE); + if ($is_file_storage) { + $filepath = $this->getUploadPath($id, $item['ext'], $size); + $filetime = filemtime($filepath); + $filesize = filesize($filepath); + } else { + $content = (string)$this->oneRaw($id, $size); + $filesize = strlen($content); + $filetime = strtotime($item['add_time'] ?? '') ?: time(); + } $cache_time = 2592000; #30 days header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', time() + $cache_time)); @@ -306,12 +357,17 @@ public function transmitFile(int $id, string $size = '', string $disposition = ' $filename = str_replace('"', "'", $item['iname']); #quote filename header('Content-type: ' . UploadUtils::ext2mime($item['ext'])); - header("Content-Length: " . filesize($filepath)); + header("Content-Length: " . $filesize); header('Content-Disposition: ' . $disposition . '; filename="' . $filename . '"'); - logger('TRACE', "transmit file [$filepath] $id, $size, $disposition, " . UploadUtils::ext2mime($item['ext'])); - $fp = fopen($filepath, 'rb'); - fpassthru($fp); + if ($is_file_storage) { + logger('TRACE', "transmit file [$filepath] $id, $size, $disposition, " . UploadUtils::ext2mime($item['ext'])); + $fp = fopen($filepath, 'rb'); + fpassthru($fp); + } else { + logger('TRACE', "transmit file [table storage] $id, $size, $disposition, " . UploadUtils::ext2mime($item['ext'])); + echo $content; + } } /** @@ -457,19 +513,19 @@ public function oneWithEntityCheck(int $id, string $entity_icode): array { /** * return one att record by entity and item_id - * @param int $entity_icode + * @param string $entity_icode * @param int $item_id * @return array * @throws DBException * @throws NoModelException */ - public function oneByEntity(int $entity_icode, int $item_id): array { + public function oneByEntity(string $entity_icode, int $item_id): array { $fwentities_id = FwEntities::i()->idByIcodeOrAdd($entity_icode); return $this->db->row($this->table_name, array( 'fwentities_id' => $fwentities_id, 'item_id' => $item_id, - )); + ), 'id desc'); } // *********************** S3 support - only works with S3 model and Amazon SDK installed *********************** @@ -531,7 +587,8 @@ public function moveToDB(int $id): bool { foreach (self::ALL_SIZES as $size) { $path = $this->getUploadPath($id, $item["ext"], $size); if (file_exists($path)) { - $fields["raw_" . $size] = file_get_contents($path); + $suffix = $size == self::SIZE_ORIGINAL ? "" : "_" . $size; + $fields["raw" . $suffix] = file_get_contents($path); } } diff --git a/www/php/models/DemosItems.php b/www/php/models/DemosItems.php index f8820e6..fe1db12 100644 --- a/www/php/models/DemosItems.php +++ b/www/php/models/DemosItems.php @@ -14,7 +14,7 @@ public function __construct() { $this->junction_field_main_id = "demos_id"; } - public function prepareSubtable(array &$list_rows, int $related_id, array $def = null): void { + public function prepareSubtable(array &$list_rows, int $related_id, ?array $def = null): void { parent::prepareSubtable($list_rows, $related_id, $def); // add select options diff --git a/www/php/models/Locks.php b/www/php/models/Locks.php index a86c9a1..d9d2972 100644 --- a/www/php/models/Locks.php +++ b/www/php/models/Locks.php @@ -25,9 +25,61 @@ public function __construct() { #$this->db = SiteUtils::connectDBHostQuick($this->fw); #this model works with Quick DB } + protected function lockTimeField(): string { + $schema = $this->schema(); + $fields = array_column($schema, 'name'); + if (in_array('upd_time', $fields, true)) { + return 'upd_time'; + } + if (in_array('updated', $fields, true)) { + return 'updated'; + } + return 'add_time'; + } + + protected function lockTimeSql(): string { + $schema = $this->schema(); + $fields = array_column($schema, 'name'); + $qadd = $this->db->qid('add_time'); + $hasUpdTime = in_array('upd_time', $fields, true); + $hasUpdated = in_array('updated', $fields, true); + + if ($hasUpdTime && $hasUpdated) { + return 'IFNULL(' . $this->db->qid('upd_time') . ', IFNULL(' . $this->db->qid('updated') . ', ' . $qadd . '))'; + } + if ($hasUpdTime) { + return 'IFNULL(' . $this->db->qid('upd_time') . ', ' . $qadd . ')'; + } + if ($hasUpdated) { + return 'IFNULL(' . $this->db->qid('updated') . ', ' . $qadd . ')'; + } + return $qadd; + } + #cleanup - auto-expire locks public function cleanup(): void { - $this->db->exec("delete from $this->table_name where DATE_ADD(updated, INTERVAL expires SECOND)db->exec("delete from " . $this->qTable() . " where DATE_ADD(" . $this->lockTimeSql() . ", INTERVAL " . $this->db->qid('expires') . " SECOND)db->row($this->table_name, [ + 'icode' => $icode, + 'environment' => $environment, + 'item_id' => $item_id + ]); + if ($row) { + $expires = $row['expires'] ?? 3600; + $upd_time = $row['upd_time'] ?? $row['updated'] ?? $row['add_time']; + $expiration_time = strtotime($upd_time) + $expires; + return $expiration_time > time(); + } + + return false; } /** @@ -38,7 +90,7 @@ public function cleanup(): void { * @param string|null $environment optional, particular environment, if not set - SiteUtils::getEnvironment() used * @return boolean true if lock obtained */ - public function lock(string $icode, int $item_id = 0, int $expires = 3600, string $environment = null): bool { + public function lock(string $icode, int $item_id = 0, int $expires = 3600, ?string $environment = null): bool { $result = false; if (empty($environment)) { @@ -74,7 +126,7 @@ public function lock(string $icode, int $item_id = 0, int $expires = 3600, strin * @param string|null $environment * @return bool false if failed to unlock (like db error) */ - public function unlock(string $icode, int $item_id = 0, string $environment = null): bool { + public function unlock(string $icode, int $item_id = 0, ?string $environment = null): bool { $result = true; if (empty($environment)) { @@ -103,7 +155,7 @@ public function unlock(string $icode, int $item_id = 0, string $environment = nu * @param string|null $environment * @return bool */ - public function extend(string $icode, int $item_id = 0, string $environment = null): bool { + public function extend(string $icode, int $item_id = 0, ?string $environment = null): bool { $result = true; if (empty($environment)) { @@ -111,8 +163,12 @@ public function extend(string $icode, int $item_id = 0, string $environment = nu } try { + $time_field = $this->lockTimeField(); + if ($time_field === 'add_time') { + return false; + } $rows_updated_ctr = $this->db->update($this->table_name, [ - 'updated' => DB::NOW() + $time_field => DB::NOW() ], [ 'icode' => $icode, 'environment' => $environment, diff --git a/www/php/models/S3.php b/www/php/models/S3.php index ef4b751..c82e27e 100644 --- a/www/php/models/S3.php +++ b/www/php/models/S3.php @@ -35,6 +35,7 @@ */ use Aws\Result; +use Aws\S3\Exception\S3Exception; use Aws\S3\S3Client; class S3 extends FwModel { @@ -43,39 +44,87 @@ class S3 extends FwModel { public string $region = ''; public string $bucket = ''; public string $root = ''; + protected array $credentials = []; public S3Client $client; - public function __construct() { - parent::__construct(); + public function __construct($param_fw = null) { + $bucket_config = is_array($param_fw) ? $param_fw : null; + + parent::__construct($bucket_config === null ? $param_fw : null); $this->table_name = ''; + $this->initConfig($bucket_config ?? $this->fw->config->AWS); $this->initClient(); // automatically init client on start } + protected function initConfig(array $bucket_config): void { + $this->region = $bucket_config['REGION'] ?? $this->fw->config->AWS['REGION'] ?? ''; + $this->bucket = $bucket_config['BUCKET'] ?? $this->fw->config->AWS['BUCKET'] ?? ''; + $root = $bucket_config['S3_ROOT'] ?? $this->fw->config->AWS['S3_ROOT'] ?? ''; + $root = rtrim($root, '/'); + $this->root = $root === '' ? '' : $root . '/'; + + $this->credentials = $bucket_config['CREDENTIALS'] ?? []; + if (empty($this->credentials['key']) && !empty($bucket_config['ACCESS_KEY']) && !empty($bucket_config['SECRET_KEY'])) { + $this->credentials = [ + 'key' => $bucket_config['ACCESS_KEY'], + 'secret' => $bucket_config['SECRET_KEY'] + ]; + } + } + + public static function withBucket(array $bucket_config): S3 { + return new self($bucket_config); + } + public function initClient(): void { - $this->bucket = $this->fw->config->AWS['BUCKET']; - $this->root = $this->fw->config->AWS['S3_ROOT']; - - //throw exception if region/bucket/akey/skey is not defined - if (empty($this->fw->config->AWS['ACCESS_KEY']) - || empty($this->fw->config->AWS['SECRET_KEY']) - || empty($this->region) - || empty($this->bucket) - || empty($this->root) - ) { + if (empty($this->region) || empty($this->bucket)) { throw new Exception('S3 region/bucket/root is not configured'); } - $this->client = new S3Client([ - 'region' => $this->region, - 'version' => 'latest', - 'credentials' => [ - 'key' => $this->fw->config->AWS['ACCESS_KEY'], - 'secret' => $this->fw->config->AWS['SECRET_KEY'] - ], - ]); + $client_options = [ + 'region' => $this->region, + 'version' => 'latest', + ]; + + if (!empty($this->credentials['key'])) { + $client_options['credentials'] = $this->credentials; + } + + $this->client = new S3Client($client_options); + } + + /** + * return S3 URL for the key + * @param string $key + * @return string + */ + public function getURL(string $key): string { + return 'https://s3.' . $this->region . '.amazonaws.com/' . $this->bucket . '/' . $this->root . $key; + } + + /** + * check if object actually exists in the bucket + */ + public function isKeyExists(string $key): bool { + try { + $this->client->headObject([ + 'Bucket' => $this->bucket, + 'Key' => $this->root . $key, + ]); + return true; + } catch (S3Exception $e) { + if ($e->getStatusCode() == 404) { + return false; + } + logger('NOTICE', 'S3 exists error', $e->getMessage()); + return false; + } catch (Exception $e) { + logger('NOTICE', 'S3 exists error', $e->getMessage()); + return false; + } } /** @@ -122,7 +171,7 @@ public function getSignedUrl(string $key, int $expires = 600, int $max_age = 315 $request = $request->withHeader('Cache-Control', 'private, max-age=' . $max_age . ', immutable'); $url = (string)$request->getUri(); - $url .= '?max-age=' . $max_age; + $url .= (str_contains($url, '?') ? '&' : '?') . 'max-age=' . $max_age; // help CDN/browser cache heuristics return $url; } @@ -138,24 +187,47 @@ public function deleteObject(string $key, bool $is_add_root = true, bool $is_fol logger("deleteObject: [$key] (is_add_root=$is_add_root, is_folder_check=$is_folder_check)"); if ($is_folder_check && str_ends_with($key, '/')) { - // it's subfolder - delete all content first - $list = $this->client->listObjectsV2([ - 'Bucket' => $this->bucket, - 'Prefix' => ($is_add_root ? $this->root : '') . $key, - 'Delimiter' => '/', + $prefix = ($is_add_root ? $this->root : '') . $key; + $continuationToken = null; + + do { + $params = [ + 'Bucket' => $this->bucket, + 'Prefix' => $prefix, + 'MaxKeys' => 1000, + ]; + if ($continuationToken) { + $params['ContinuationToken'] = $continuationToken; + } + + $list = $this->client->listObjectsV2($params); + $objects = []; + foreach ($list['Contents'] ?? [] as $entry) { + if ($entry['Key'] === $prefix) { + continue; + } + $objects[] = ['Key' => $entry['Key']]; + } + + if ($objects) { + $this->client->deleteObjects([ + 'Bucket' => $this->bucket, + 'Delete' => [ + 'Objects' => $objects, + 'Quiet' => true, + ], + ]); + } + + $continuationToken = (!empty($list['IsTruncated']) && !empty($list['NextContinuationToken'])) + ? $list['NextContinuationToken'] + : null; + } while ($continuationToken); + + return $this->client->deleteObject([ + 'Bucket' => $this->bucket, + 'Key' => $prefix, ]); - - //delete objects in folder first. Note: object can be a folder itself with a zero size if - // it was created separately with no body and key name ending with "/", - // so set "is_folder_check" to False here to delete an object and avoid an infinite loop - foreach ($list['Contents'] as $entry) { - $this->deleteObject($entry['Key'], false, false); - } - - //delete subfolders if any - foreach ($list['CommonPrefixes'] as $subfolder) { - $this->deleteObject($subfolder['Prefix'], false); - } } return $this->client->deleteObject([ 'Bucket' => $this->bucket, @@ -169,36 +241,45 @@ public function deleteObject(string $key, bool $is_add_root = true, bool $is_fol * @param string $filepath file path on the local disk to upload * @param string $disposition if defined (ex: inline) - Content-Disposition with file.FileName added * @param string $filename optional filename to include in disposition header - * @param string|null $storage_class S3 Storage Class, default is STANDARD or use 5 times cheaper GLACIER_IR + * @param array|string|null $options additional options to pass to putObject, or legacy storage class string * @return bool * * alternative way for upload - use Aws\S3\Transfer https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/s3-transfer.html */ - public function uploadLocalFile(string $key, string $filepath, string $disposition = '', string $filename = '', string $storage_class = null): bool { + public function uploadLocalFile(string $key, string $filepath, string $disposition = '', string $filename = '', array|string|null $options = []): bool { logger("uploading to S3: key=[$key], filepath=[$filepath]"); + if (!is_array($options)) { + $options = ['StorageClass' => $options ?? 'STANDARD']; + } $request = [ 'Bucket' => $this->bucket, 'Key' => $this->root . $key, 'SourceFile' => $filepath, - 'StorageClass' => $storage_class ?? 'STANDARD' + 'StorageClass' => $options['StorageClass'] ?? 'STANDARD' ]; $request['ContentType'] = UploadUtils::ext2mime(pathinfo($filepath, PATHINFO_EXTENSION)); - if ($disposition != '') { - if ($filename == '') { + if ($options) { + $request = array_merge($request, $options); + } + + if ($disposition !== '') { + if ($filename === '') { $filename = basename($filepath); } - $filename = str_replace('"', "'", $filename); // replace quotes + $filename = str_replace('"', "'", $filename); $request['ContentDisposition'] = "$disposition; filename=\"$filename\""; } - $result = $this->client->putObject($request); - if ($result['@metadata']['statusCode'] != 200) { - logger('WARN', "HttpStatusCode=", $result['@metadata']['statusCode']); + $result = $this->client->putObject($request); + $meta = $result['@metadata'] ?? []; + $statusCode = $meta['statusCode'] ?? null; + if ($statusCode !== 200) { + logger('WARN', 'HttpStatusCode', $statusCode, $meta); } - return ($result['@metadata']['statusCode'] == 200); + return $statusCode === 200; } /** @@ -207,11 +288,12 @@ public function uploadLocalFile(string $key, string $filepath, string $dispositi * @param array $file single file from http upload $_FILES[xxx] * @param string $disposition if defined (ex: inline) - Content-Disposition with file.FileName added * @param string $filename optional filename to include in disposition header + * @param array $options additional options to pass to putObject * @return Result * alternative way for disposition - override response header in GET */ - public function uploadPostedFile(string $key, array $file, string $disposition = '', string $filename = ''): Result { - logger("uploading to S3: key=[$key], file=[$file]"); + public function uploadPostedFile(string $key, array $file, string $disposition = '', string $filename = '', array $options = []): Result { + logger("uploading to S3: key=[$key], file name=[{$file['name']}] size=[{$file['size']}] error=[{$file['error']}] "); $request = [ 'Bucket' => $this->bucket, @@ -220,17 +302,72 @@ public function uploadPostedFile(string $key, array $file, string $disposition = ]; $request['ContentType'] = UploadUtils::ext2mime($file['type']); - if ($disposition != '') { - if ($filename == '') { + if ($options) { + $request = array_merge($request, $options); + } + + if ($disposition !== '') { + if ($filename === '') { $filename = basename($file['name']); } - $filename = str_replace('"', "'", $filename); // replace quotes + $filename = str_replace('"', "'", $filename); + $request['ContentDisposition'] = "$disposition; filename=\"$filename\""; + } + + return $this->client->putObject($request); + } + + public function uploadContent(string $key, string $fileContent, string $contentType = 'application/octet-stream', string $disposition = '', string $filename = '', array $more_request = []): Result { + $request = [ + 'Bucket' => $this->bucket, + 'Key' => $this->root . $key, + 'Body' => $fileContent, + 'ContentType' => $contentType, + ]; + $request = array_merge($request, $more_request); + + if ($disposition !== '') { + if ($filename === '') { + $filename = basename($key); + } + $filename = str_replace('"', "'", $filename); $request['ContentDisposition'] = "$disposition; filename=\"$filename\""; } return $this->client->putObject($request); } + public function uploadFromURL(string $key, string $url, string $disposition = '', string $filename = '', array $more_request = []): Result { + logger("uploading to S3: key=[$key], url=[$url]"); + + $stream = fopen($url, 'rb'); + if ($stream === false) { + throw new Exception("Failed to read file from URL: " . $url); + } + + $meta = stream_get_meta_data($stream); + $fileContent = stream_get_contents($stream); + fclose($stream); + if ($fileContent === false) { + throw new Exception("Failed to read file from URL: " . $url); + } + + $contentType = 'application/octet-stream'; + $response_headers = is_array($meta['wrapper_data'] ?? null) ? $meta['wrapper_data'] : []; + foreach (array_reverse($response_headers) as $hdr) { + if (stripos($hdr, 'Content-Type:') === 0) { + $contentType = trim(substr($hdr, 13)); + break; + } + } + + if ($filename === '') { + $filename = basename($url); + } + + return $this->uploadContent($key, $fileContent, $contentType, $disposition, $filename, $more_request); + } + /** * download file from S3 to specific local filepath * @param string $key key relative to the S3Root @@ -245,9 +382,11 @@ public function download(string $key, string $filepath): string { 'Key' => $this->root . $key, ]; - $result = $this->client->getObject($request); - if ($result['@metadata']['statusCode'] != 200) { - logger('WARN', "HttpStatusCode=", $result['@metadata']['statusCode']); + $result = $this->client->getObject($request); + $meta = $result['@metadata'] ?? []; + $statusCode = $meta['statusCode'] ?? null; + if ($statusCode !== 200) { + logger('WARN', 'HttpStatusCode', $statusCode, $meta); return ''; } diff --git a/www/php/models/Spages.php b/www/php/models/Spages.php index 2590582..1e0a940 100644 --- a/www/php/models/Spages.php +++ b/www/php/models/Spages.php @@ -215,7 +215,7 @@ public function getFullUrl(int $id): string { } #return correct url - TODO - public function getUrl(int $id, string $icode, string $url = null): string { + public function getUrl(int $id, string $icode, ?string $url = null): string { if ($url > '') { if (str_starts_with($url, "/")) { $url = $this->fw->config->ROOT_URL . $url; diff --git a/www/php/models/Users.php b/www/php/models/Users.php index da3ca25..8b80851 100644 --- a/www/php/models/Users.php +++ b/www/php/models/Users.php @@ -97,14 +97,14 @@ public function getOrderBy(): string { } #return standard list of id,iname where status=0 order by iname - public function ilist(array $statuses = null): array { + public function ilist(?array $statuses = null, ?array $def = null): array { if (is_null($statuses)) { $statuses = [FwModel::STATUS_ACTIVE]; } - return parent::ilist($statuses); + return parent::ilist($statuses, $def); } - public function ilistByACL(int $min_acl = null): array { + public function ilistByACL(?int $min_acl = null): array { $where = ''; if (!is_null($min_acl)) { $where = ' and access_level>=' . dbqi($min_acl); @@ -115,7 +115,7 @@ public function ilistByACL(int $min_acl = null): array { return $this->db->arrp($sql, ['status' => FwModel::STATUS_ACTIVE]); } - public function listSelectOptions(array $def = null): array { + public function listSelectOptions(?array $def = null): array { $where = ''; // if (($def['lookup_params'] ?? '') == 'account') { // $where .= " AND accounts_id=" . intval($def['i']['id'] ?? 0); @@ -217,7 +217,7 @@ public function scorePwd(string $pwd): float { $chars = []; for ($i = 0; $i <= strlen($pwd) - 1; $i++) { $chars[$pwd[$i]] = intval($chars[$pwd[$i]]) + 1; - $result += (int)(5.0 / (double)$chars[$pwd[$i]]); + $result += (int)(5.0 / (float)$chars[$pwd[$i]]); } // bonus points for mixing it up @@ -321,7 +321,7 @@ public function checkMFARecovery(int $id, string $code): bool { // - public function doLogin(int $id): void { + public function doLogin(int $id, bool $is_remember = false): void { $is_just_registered = intval($_SESSION['is_just_registered'] ?? 0); @session_destroy(); @@ -338,8 +338,11 @@ public function doLogin(int $id): void { $this->fw->logActivity(FwLogTypes::ICODE_USERS_LOGIN, FwEntities::ICODE_USERS, $id); //set permanent login if requested - //if ($_REQUEST['is_remember']) createPermCookie($id); - $this->createPermCookie($id); #in this project no need is_remember + if ($is_remember) { + $this->createPermCookie($id); + } else { + $this->removePermCookie(); + } $this->updateAfterLogin($id); } @@ -409,8 +412,31 @@ private function updateAfterLogin(int $id): void { // + private function permCookieName(): string { + if (empty($this->fw->config->PERM_COOKIE_ENV_SUFFIX)) { + return self::$PERM_COOKIE_NAME; + } + + $environment = $this->fw->config->environment ?? $this->fw->config->ENVIRONMENT ?? ''; + if (!$environment || $environment === 'production') { + return self::$PERM_COOKIE_NAME; + } + + $suffix = preg_replace('/[^a-z0-9_-]/i', '', (string)$environment); + if (!$suffix) { + return self::$PERM_COOKIE_NAME; + } + + return self::$PERM_COOKIE_NAME . '_' . $suffix; + } + + private function permCookieDomain(string $root_domain0): string { + return (preg_match('/\./', $root_domain0)) ? '.' . $root_domain0 : ''; + } + public function createPermCookie(int $id): string { $root_domain0 = $this->fw->config->ROOT_DOMAIN0; + $cookie_name = $this->permCookieName(); $cookie_id = substr(Utils::getRandStr(16) . time(), 0, 32); @@ -420,8 +446,8 @@ public function createPermCookie(int $id): string { ); $this->db->insert('users_cookies', $vars, array('replace' => 1)); - setcookie(self::$PERM_COOKIE_NAME, $cookie_id, time() + 60 * 60 * 24 * self::$PERM_COOKIE_DAYS, "/", (preg_match('/\./', $root_domain0)) ? '.' . $root_domain0 : ''); - #rwe("[$root_domain0] ".self::$PERM_COOKIE_NAME.", $cookie_id, ".(time()+60*60*24*self::$PERM_COOKIE_DAYS)); + setcookie($cookie_name, $cookie_id, time() + 60 * 60 * 24 * self::$PERM_COOKIE_DAYS, "/", $this->permCookieDomain($root_domain0)); + #rwe("[$root_domain0] ".$cookie_name.", $cookie_id, ".(time()+60*60*24*self::$PERM_COOKIE_DAYS)); return $cookie_id; } @@ -429,10 +455,11 @@ public function createPermCookie(int $id): string { # check for permanent login cookie and if it's present - doLogin public function checkPermanentLogin(): bool { $root_domain0 = $this->fw->config->ROOT_DOMAIN0; + $cookie_name = $this->permCookieName(); $result = false; - $cookie_id = @$_COOKIE[self::$PERM_COOKIE_NAME]; + $cookie_id = @$_COOKIE[$cookie_name]; #rw("cookies:"); #print_r($_COOKIE); #exit; @@ -450,19 +477,21 @@ public function checkPermanentLogin(): bool { if ($u_id > 0) { $result = true; #logger("PERMANENT LOGIN"); - $this->doLogin($u_id); + $this->doLogin($u_id, true); } else { #cookie is not found in DB - clean it (so it will not put load on DB during next pages) - setcookie(self::$PERM_COOKIE_NAME, FALSE, -1, "/", (preg_match('/\./', $root_domain0)) ? '.' . $root_domain0 : ''); + setcookie($cookie_name, FALSE, -1, "/", $this->permCookieDomain($root_domain0)); } } return $result; } public function removePermCookie() { - $cookie_id = $_COOKIE[self::$PERM_COOKIE_NAME] ?? ''; + $root_domain0 = $this->fw->config->ROOT_DOMAIN0; + $cookie_name = $this->permCookieName(); + $cookie_id = $_COOKIE[$cookie_name] ?? ''; - setcookie(self::$PERM_COOKIE_NAME, FALSE, -1, "/"); + setcookie($cookie_name, FALSE, -1, "/", $this->permCookieDomain($root_domain0)); #cleanup in DB (user's cookie and ALL old cookies) $this->db->query("DELETE FROM users_cookies @@ -587,7 +616,7 @@ public function isRoles(): bool { * edit => true if user has edit permission * del => true if user has delete permission */ - public function getRBAC(?int $users_id = null, string $resource_icode = null): array { + public function getRBAC(?int $users_id = null, ?string $resource_icode = null): array { if (!$this->isRoles()) { return $this->allPermissions(); //if no Roles support - always allow } @@ -675,7 +704,7 @@ public function allPermissions(): array { * @return bool * @throws ApplicationException|DBException|NoModelException */ - public function isAccessByRolesResourceAction(int $users_id, string $resource_icode, string $resource_action, string $resource_action_more = "", array $access_actions_to_permissions = null): bool { + public function isAccessByRolesResourceAction(int $users_id, string $resource_icode, string $resource_action, string $resource_action_more = "", ?array $access_actions_to_permissions = null): bool { if ($this->isRoles()) { // determine permission by resource action $permission_icode = Permissions::i()->mapActionToPermission($resource_action, $resource_action_more); diff --git a/www/php/tests/DatabaseTestCase.php b/www/php/tests/DatabaseTestCase.php new file mode 100644 index 0000000..8e576dc --- /dev/null +++ b/www/php/tests/DatabaseTestCase.php @@ -0,0 +1,90 @@ +databaseUnavailableReason(); + if (!is_null($skipReason)) { + $this->markTestSkipped($skipReason); + } + + $this->db = $this->fw->db; + $this->db->transaction(); + } + + protected function tearDown(): void { + if (isset($this->db)) { + try { + $this->db->rollback(); + } catch (Throwable) { + // Ignore rollback errors when the connection was already closed by the test. + } + } + + parent::tearDown(); + } + + private function databaseUnavailableReason(): ?string { + if (self::$dbAvailabilityResolved) { + return self::$dbUnavailableReason; + } + + self::$dbAvailabilityResolved = true; + + $dbConfig = $this->fw->config->DB ?? null; + $dbName = is_array($dbConfig) ? ($dbConfig['DBNAME'] ?? '') : ($dbConfig->DBNAME ?? ''); + if (trim((string)$dbName) === '') { + self::$dbUnavailableReason = 'Database not configured for host [' . ($_SERVER['HTTP_HOST'] ?? '') . ']'; + return self::$dbUnavailableReason; + } + + try { + $this->fw->db->arrp('SELECT 1 AS ok'); + self::$dbUnavailableReason = null; + } catch (Throwable $throwable) { + self::$dbUnavailableReason = 'Database not available: ' . $throwable->getMessage(); + } + + return self::$dbUnavailableReason; + } + + protected function withoutActivityLogging(callable $callback): mixed { + $previous = $this->fw->is_log_events; + $this->fw->is_log_events = false; + + try { + return $callback(); + } finally { + $this->fw->is_log_events = $previous; + } + } + + /** + * @param array $fields + * @return array + */ + protected function createUser(array $fields = []): array { + return $this->withoutActivityLogging(function () use ($fields): array { + $defaults = [ + 'email' => uniqid('user', true) . '@example.test', + 'fname' => 'Test', + 'lname' => 'User', + 'access_level' => Users::ACL_USER, + ]; + + $id = Users::i()->add(array_merge($defaults, $fields)); + return Users::i()->one($id); + }); + } +} diff --git a/www/php/tests/FrameworkTestCase.php b/www/php/tests/FrameworkTestCase.php new file mode 100644 index 0000000..654b1ad --- /dev/null +++ b/www/php/tests/FrameworkTestCase.php @@ -0,0 +1,61 @@ +fw = fw::i(); + $this->fw->resetRuntimeCaches(); + $this->resetRequestState(); + } + + protected function tearDown(): void { + $this->resetRequestState(); + + parent::tearDown(); + } + + protected function resetRequestState(): void { + $_GET = []; + $_POST = []; + $_REQUEST = []; + $this->fw->postedJson = []; + $this->fw->FormErrors = []; + } + + protected function newInstanceWithoutConstructor(string $className): object { + $reflection = new ReflectionClass($className); + return $reflection->newInstanceWithoutConstructor(); + } + + protected function invokeProtected(object $object, string $method, array $args = []): mixed { + $reflection = new ReflectionMethod($object, $method); + return $reflection->invokeArgs($object, $args); + } + + protected function setProtectedProperty(object $object, string $propertyName, mixed $value): void { + $reflection = new ReflectionProperty($object, $propertyName); + $reflection->setValue($object, $value); + } + + protected function getProtectedProperty(object $object, string $propertyName): mixed { + $reflection = new ReflectionProperty($object, $propertyName); + return $reflection->getValue($object); + } + + protected function setModelDb(FwModel $model, DB $db): void { + $this->setProtectedProperty($model, 'db', $db); + } + + /** + * @param class-string $class + */ + protected function registerModel(string $class, FwModel $instance): void { + $this->fw->setModelInstanceForTest($class, $instance); + } +} diff --git a/www/php/tests/TestHostResolver.php b/www/php/tests/TestHostResolver.php new file mode 100644 index 0000000..0795a1f --- /dev/null +++ b/www/php/tests/TestHostResolver.php @@ -0,0 +1,206 @@ + $argv + * @return array + */ + public static function stripCliHostArgument(array $argv): array { + if (self::extractCliHostArgument($argv) === null) { + return $argv; + } + + array_splice($argv, 1, 1); + return $argv; + } + + /** + * @param array $argv + */ + public static function extractCliHostArgument(array $argv): ?string { + $candidate = $argv[1] ?? null; + if (!is_string($candidate)) { + return null; + } + + $candidate = trim($candidate); + if ($candidate === '' || !self::isValidHost($candidate)) { + return null; + } + + return $candidate; + } + + private static function envFilePath(): string { + return dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . '.env.test.local'; + } + + private static function configsDir(): string { + return dirname(__DIR__) . DIRECTORY_SEPARATOR . 'configs'; + } + + private static function autoDetectHost(): ?string { + $configPaths = glob(self::configsDir() . DIRECTORY_SEPARATOR . '*.php') ?: []; + if (!$configPaths) { + return null; + } + + $configFiles = []; + foreach ($configPaths as $configPath) { + $configFile = basename($configPath); + if (strtolower($configFile) === 'config.php') { + continue; + } + $configFiles[] = $configFile; + } + + sort($configFiles, SORT_NATURAL | SORT_FLAG_CASE); + + foreach (self::AUTO_DETECT_PATTERNS as $pattern) { + foreach ($configFiles as $configFile) { + if (preg_match($pattern, $configFile)) { + return substr($configFile, 0, -4); + } + } + } + + return null; + } + + /** + * @return array + */ + private static function readEnvFile(string $path): array { + if (!is_file($path)) { + return []; + } + + $lines = file($path, FILE_IGNORE_NEW_LINES); + if ($lines === false) { + return []; + } + + $values = []; + foreach ($lines as $line) { + $line = trim($line); + if ($line === '' || str_starts_with($line, '#')) { + continue; + } + + [$key, $value] = array_pad(explode('=', $line, 2), 2, ''); + $key = trim($key); + if ($key === '') { + continue; + } + + $values[$key] = self::trimEnvValue(trim($value)); + } + + return $values; + } + + private static function trimEnvValue(string $value): string { + if (strlen($value) >= 2) { + $first = $value[0]; + $last = $value[strlen($value) - 1]; + if (($first === '"' && $last === '"') || ($first === "'" && $last === "'")) { + return substr($value, 1, -1); + } + } + + return $value; + } + + private static function validateHost(string $host, string $source): string { + if (self::isValidHost($host)) { + return $host; + } + + throw new RuntimeException("Invalid test host [{$host}] configured in {$source}."); + } + + private static function isValidHost(string $host): bool { + return (bool)filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME); + } + + private static function missingHostMessage(): string { + $envFile = self::envFilePath(); + + return implode(PHP_EOL, [ + 'Unable to resolve a local PHPUnit host config.', + 'Resolution order:', + '1. CLI hostname argument.', + '2. OSAFW_TEST_HOST or OSAFW_CLI_HOST environment variable.', + "3. {$envFile}.", + '4. Auto-detect from www/php/configs/ using localhost.php, osafw-php.lo.php, or lo-* local configs.', + '', + 'Create /.env.test.local with:', + 'OSAFW_TEST_HOST=localhost', + '', + 'Or export OSAFW_TEST_HOST before running PHPUnit.', + ]); + } +} diff --git a/www/php/tests/bootstrap.php b/www/php/tests/bootstrap.php new file mode 100644 index 0000000..6c575c9 --- /dev/null +++ b/www/php/tests/bootstrap.php @@ -0,0 +1,34 @@ +getMessage() . PHP_EOL); + exit(1); +} + +TestHostResolver::applyToEnvironment($testHost); + +require_once __DIR__ . '/../fw/fw.php'; +require_once __DIR__ . '/../fw/FwController.php'; +require_once __DIR__ . '/../fw/FwAdminController.php'; +require_once __DIR__ . '/../fw/FwDynamicController.php'; +require_once __DIR__ . '/../fw/FwApiController.php'; +require_once __DIR__ . '/../fw/FwVueController.php'; + +fw::initOffline(__FILE__); + +$fw = fw::i(); +$logFile = dirname(__DIR__, 3) . '/logs/test.log'; +$fw->config->LOG_DESTINATION = $logFile; +$fw->config->LOG_LEVEL = 'DEBUG'; + +global $CONFIG; +$CONFIG['LOG_DESTINATION'] = $logFile; +$CONFIG['LOG_LEVEL'] = 'DEBUG'; +ini_set('error_log', $logFile); + +logger('NOTICE', 'PHPUnit bootstrap initialized'); diff --git a/www/php/tests/controllers/ControllerTestCase.php b/www/php/tests/controllers/ControllerTestCase.php new file mode 100644 index 0000000..ecc377f --- /dev/null +++ b/www/php/tests/controllers/ControllerTestCase.php @@ -0,0 +1,70 @@ +fw->dispatcher)) { + $this->fw->dispatcher = new Dispatcher([], $this->fw->config->ROOT_URL, $this->fw->config->ROUTE_PREFIXES); + } + } + + /** + * @param array $params + * @return array|null + */ + protected function runController(string $controller, string $action, array $params = []): ?array { + $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; + if (in_array($action, [fw::ACTION_SAVE, fw::ACTION_DELETE, fw::ACTION_SAVE_MULTI], true)) { + $method = 'POST'; + } + + $this->fw->route = (object)[ + 'method' => $method, + 'prefix' => str_starts_with($controller, 'v1') ? '/v1' : '', + 'controller' => $controller, + 'controller_path' => strtolower(preg_replace('/^Admin/', '', $controller)), + 'action' => $action, + 'action_more' => '', + 'id' => (string)($params[0] ?? ''), + 'params' => $params, + 'format' => '', + ]; + + http_response_code(200); + ob_start(); + try { + $result = $this->fw->dispatcher->runController($controller, $action, $params); + $output = trim((string)ob_get_contents()); + + if ($result === null && $output !== '') { + $decoded = json_decode($output, true); + if (is_array($decoded)) { + return ['_json' => $decoded]; + } + } + + return $result; + } finally { + ob_end_clean(); + } + } + + /** + * @param array $user + */ + protected function loginUser(array $user): void { + $_SESSION['user_id'] = $user['id']; + $_SESSION['access_level'] = intval($user['access_level'] ?? Users::ACL_USER); + $_SESSION['XSS'] ??= Utils::getRandStr(16); + } +} diff --git a/www/php/tests/controllers/TestControllerTest.php b/www/php/tests/controllers/TestControllerTest.php new file mode 100644 index 0000000..3610ea5 --- /dev/null +++ b/www/php/tests/controllers/TestControllerTest.php @@ -0,0 +1,17 @@ +createUser([ + 'access_level' => Users::ACL_SITE_ADMIN, + ]); + $this->loginUser($user); + + $result = $this->runController('Test', 'Bench2'); + + $this->assertIsArray($result); + } +} diff --git a/www/php/tests/models/UsersModelTest.php b/www/php/tests/models/UsersModelTest.php new file mode 100644 index 0000000..6b8b320 --- /dev/null +++ b/www/php/tests/models/UsersModelTest.php @@ -0,0 +1,17 @@ +createUser([ + 'fname' => 'Framework', + 'lname' => 'Tester', + ]); + + $this->assertSame('Framework', $user['fname']); + $this->assertSame('Tester', $user['lname']); + $this->assertNotEmpty($user['id']); + } +} diff --git a/www/php/tests/run-local-phpunit.php b/www/php/tests/run-local-phpunit.php new file mode 100644 index 0000000..0056ede --- /dev/null +++ b/www/php/tests/run-local-phpunit.php @@ -0,0 +1,48 @@ +getMessage() . PHP_EOL); + exit(1); +} + +TestHostResolver::applyToEnvironment($testHost, false); + +$cliArgs = TestHostResolver::stripCliHostArgument($cliArgs); +if (count($cliArgs) > 1) { + $phpunitArgs = array_merge($phpunitArgs, array_slice($cliArgs, 1)); +} + +$_SERVER['argv'] = $phpunitArgs; +$GLOBALS['argv'] = $phpunitArgs; +chdir($rootDir); + +$autoload = $phpDir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; +if (!is_file($autoload)) { + fwrite(STDERR, 'Composer dependencies are not installed. Run composer install from www/php with dev dependencies enabled.' . PHP_EOL); + exit(1); +} + +require $autoload; + +if (!class_exists(PHPUnit\TextUI\Application::class)) { + fwrite(STDERR, 'PHPUnit is not installed. Run composer install from www/php with dev dependencies enabled.' . PHP_EOL); + exit(1); +} + +exit((new PHPUnit\TextUI\Application())->run($phpunitArgs)); diff --git a/www/php/tests/unit/FrameworkBootstrapTest.php b/www/php/tests/unit/FrameworkBootstrapTest.php new file mode 100644 index 0000000..5f34159 --- /dev/null +++ b/www/php/tests/unit/FrameworkBootstrapTest.php @@ -0,0 +1,26 @@ +assertSame($_SERVER['HTTP_HOST'], $this->fw->config->ROOT_DOMAIN0); + $this->assertIsArray($this->fw->GLOBAL); + } + + public function testRouteParserMapsStandardRestListRoute(): void { + $dispatcher = new Dispatcher([], $this->fw->config->ROOT_URL, $this->fw->config->ROUTE_PREFIXES); + $route = $dispatcher->uriToRoute('GET', '/Admin/Demos', []); + + $this->assertSame('AdminDemos', $route->controller); + $this->assertSame(fw::ACTION_INDEX, $route->action); + } + + public function testSafeUrlAllowsOnlyClickableSchemes(): void { + $this->assertSame('https://example.com/path', Utils::safeUrl('example.com/path')); + $this->assertSame('mailto:test@example.com', Utils::safeUrl('mailto:test@example.com')); + $this->assertSame('', Utils::safeUrl('javascript:alert(1)')); + $this->assertSame('', Utils::safeUrl('//example.com/path')); + } +} diff --git a/www/template/common/form/show/one_fieldsel.html b/www/template/common/form/show/one_fieldsel.html index 1c036fc..a659d3c 100644 --- a/www/template/common/form/show/one_fieldsel.html +++ b/www/template/common/form/show/one_fieldsel.html @@ -1,5 +1,7 @@ <~/common/form/show/plaintext ifeq="type" value="plaintext"> +<~/common/form/show/plaintext_json ifeq="type" value="plaintext_json"> <~/common/form/show/plaintext_link ifeq="type" value="plaintext_link"> +<~/common/form/show/plaintext_url ifeq="type" value="plaintext_url"> <~/common/form/show/plaintext_autocomplete ifeq="type" value="plaintext_autocomplete"> <~/common/form/show/plaintext_yesno ifeq="type" value="plaintext_yesno"> <~/common/form/show/markdown ifeq="type" value="markdown"> @@ -14,4 +16,4 @@ <~/common/form/show/att_files ifeq="type" value="att_files"> <~/common/form/show/added ifeq="type" value="added"> <~/common/form/show/updated ifeq="type" value="updated"> -<~/common/form/show/subtable ifeq="type" value="subtable"> \ No newline at end of file +<~/common/form/show/subtable ifeq="type" value="subtable"> diff --git a/www/template/common/form/show/plaintext.html b/www/template/common/form/show/plaintext.html index 4a231c3..3f5fa86 100644 --- a/www/template/common/form/show/plaintext.html +++ b/www/template/common/form/show/plaintext.html @@ -1 +1 @@ -

<~value nl2br>

\ No newline at end of file +

<~value>

diff --git a/www/template/common/form/show/plaintext_json.html b/www/template/common/form/show/plaintext_json.html new file mode 100644 index 0000000..e156b3b --- /dev/null +++ b/www/template/common/form/show/plaintext_json.html @@ -0,0 +1 @@ +
<~value>
diff --git a/www/template/common/form/show/plaintext_link.html b/www/template/common/form/show/plaintext_link.html index be69a94..968d474 100644 --- a/www/template/common/form/show/plaintext_link.html +++ b/www/template/common/form/show/plaintext_link.html @@ -1 +1,2 @@ -

<~value><~lookup_id unless="value">

+<~has_lookup if="lookup_row" inline>

<~value><~lookup_id unless="value" if="lookup_id">

+<~no_lookup unless="lookup_row" inline>

<~value><~lookup_id unless="value" if="lookup_id">

diff --git a/www/template/common/form/show/plaintext_url.html b/www/template/common/form/show/plaintext_url.html new file mode 100644 index 0000000..3d053ce --- /dev/null +++ b/www/template/common/form/show/plaintext_url.html @@ -0,0 +1 @@ +

<~safe if="safe_url" inline><~value><~unsafe unless="safe_url" inline><~value>

diff --git a/www/template/common/list/form_list.html b/www/template/common/list/form_list.html index 5fe8ed7..a588394 100644 --- a/www/template/common/list/form_list.html +++ b/www/template/common/list/form_list.html @@ -1,6 +1,7 @@
+ <~form_list_hidden_fields> <~/common/form/relid>
@@ -16,4 +17,4 @@ <~/common/list/table_under if="list_rows"> -
\ No newline at end of file +
diff --git a/www/template/common/list/tbody.html b/www/template/common/list/tbody.html index 9d39c82..f44cacd 100644 --- a/www/template/common/list/tbody.html +++ b/www/template/common/list/tbody.html @@ -1,6 +1,6 @@ <~list_rows repeat inline> - + > <~/common/list/list_row_td_btn if="GLOBAL[is_list_btn_left]"> <~cols repeat inline><~data var unless="is_custom"><~col_custom if="is_custom"> diff --git a/www/template/common/virtual/index/main.html b/www/template/common/virtual/index/main.html index 749d5c2..0ec33b2 100644 --- a/www/template/common/virtual/index/main.html +++ b/www/template/common/virtual/index/main.html @@ -5,6 +5,7 @@ data-related_id="<~related_id>" data-return_url="<~return_url>" data-is_list_edit="<~is_list_edit json>" + data-header_links="<~controller_config[header_links] json>" data-list_title="<~title>" data-view_title="<~controller_config[view_title] default="View Record">" data-edit_title="<~controller_config[edit_title] default="Edit Record">" diff --git a/www/template/common/vue/form-one-control.html b/www/template/common/vue/form-one-control.html index 04ad9da..569e8ab 100644 --- a/www/template/common/vue/form-one-control.html +++ b/www/template/common/vue/form-one-control.html @@ -6,11 +6,16 @@

{{def.lookup_model ? lookupValue : value}}

+
{{value}}

{{lookupValue}}

+

+ {{value}} + +

{{item[def.field+'_iname']??'-'}}

@@ -397,6 +402,21 @@ second: '2-digit' }); }, + safeUrl(str) { + let url = String(str ?? '').trim().replace(/[\x00-\x1F\x7F]+/g, ''); + if (!url || url.startsWith('//')) return ''; + if (!/^[a-z][a-z0-9+.-]*:/i.test(url)) url = 'https://' + url; + + let parsed; + try { + parsed = new URL(url); + } catch (e) { + return ''; + } + + const allowed = ['http:', 'https:', 'mailto:', 'tel:']; + return allowed.includes(parsed.protocol) ? url : ''; + }, async autocompleteOptions(query, select$) { let id = 0; if (query === null) { diff --git a/www/template/common/vue/list-header.html b/www/template/common/vue/list-header.html index 04640c8..89c0286 100644 --- a/www/template/common/vue/list-header.html +++ b/www/template/common/vue/list-header.html @@ -20,6 +20,11 @@

{{fwStore.list_title}} > `Add New`

+ {{link.label}} @@ -37,6 +42,13 @@

{{fwStore.list_title}} ...mapStores(useFwStore), //accessible via this.fwStore uioptions() { return this.fwStore.uioptions.list.header ?? false; + }, + headerLinks() { + if (!Array.isArray(this.fwStore.header_links)) { + return []; + } + + return this.fwStore.header_links.filter(link => link && link.url && link.label); } }, methods: { diff --git a/www/template/common/vue/store.js b/www/template/common/vue/store.js index aa74352..8975687 100644 --- a/www/template/common/vue/store.js +++ b/www/template/common/vue/store.js @@ -97,6 +97,7 @@ let state = { related_id: 0, // related model id return_url: '', // return url if controller called from other place expecting user's return + header_links: [], // extra header links configured by the controller field_id: 'id', // model's id field name list_headers: [], // list headers, array of {field_name:"", field_name_visible:"", is_sortable:bool, is_checked:bool, search_value:null|"", is_ro:bool, input_type:"input|select|date"} is_list_search_open: false, // true if list search is open by user @@ -322,7 +323,19 @@ let actions = { //save to store each key from data if such key exists in store saveToStore(data) { Object.keys(data).forEach(key => { - if (this.$state[key] !== undefined) this.$state[key] = data[key]; + if (this.$state[key] === undefined) return; + + let value = data[key]; + if (key === 'header_links' && typeof value === 'string' && value.length) { + try { + value = JSON.parse(value); + } catch (e) { + console.warn('Failed to parse header_links', e); + value = []; + } + } + + this.$state[key] = value; }); }, From a1ad4f52c07eedb8276ffc038845934dce8fefa5 Mon Sep 17 00:00:00 2001 From: Oleg Savchuk Date: Thu, 30 Apr 2026 16:28:51 -0500 Subject: [PATCH 2/3] Locks --- .../upd2026-04-30-framework-upgrades.sql | 6 +-- www/php/models/Locks.php | 41 ++----------------- 2 files changed, 5 insertions(+), 42 deletions(-) diff --git a/db/updates/upd2026-04-30-framework-upgrades.sql b/db/updates/upd2026-04-30-framework-upgrades.sql index 9baca82..661518c 100644 --- a/db/updates/upd2026-04-30-framework-upgrades.sql +++ b/db/updates/upd2026-04-30-framework-upgrades.sql @@ -8,7 +8,8 @@ ALTER TABLE users MODIFY lname VARCHAR(127) NOT NULL DEFAULT '', MODIFY iname VARCHAR(255) AS (CONCAT(fname, ' ', lname)); -CREATE TABLE IF NOT EXISTS locks +DROP TABLE IF EXISTS locks; +CREATE TABLE locks ( icode VARCHAR(255) NOT NULL, environment VARCHAR(16) NOT NULL DEFAULT '', @@ -26,6 +27,3 @@ CREATE TABLE IF NOT EXISTS locks PRIMARY KEY (icode, environment, item_id) ) ENGINE = InnoDB; - -ALTER TABLE locks - ADD COLUMN IF NOT EXISTS upd_time DATETIME NULL ON UPDATE CURRENT_TIMESTAMP; diff --git a/www/php/models/Locks.php b/www/php/models/Locks.php index d9d2972..dd2ef9e 100644 --- a/www/php/models/Locks.php +++ b/www/php/models/Locks.php @@ -25,40 +25,9 @@ public function __construct() { #$this->db = SiteUtils::connectDBHostQuick($this->fw); #this model works with Quick DB } - protected function lockTimeField(): string { - $schema = $this->schema(); - $fields = array_column($schema, 'name'); - if (in_array('upd_time', $fields, true)) { - return 'upd_time'; - } - if (in_array('updated', $fields, true)) { - return 'updated'; - } - return 'add_time'; - } - - protected function lockTimeSql(): string { - $schema = $this->schema(); - $fields = array_column($schema, 'name'); - $qadd = $this->db->qid('add_time'); - $hasUpdTime = in_array('upd_time', $fields, true); - $hasUpdated = in_array('updated', $fields, true); - - if ($hasUpdTime && $hasUpdated) { - return 'IFNULL(' . $this->db->qid('upd_time') . ', IFNULL(' . $this->db->qid('updated') . ', ' . $qadd . '))'; - } - if ($hasUpdTime) { - return 'IFNULL(' . $this->db->qid('upd_time') . ', ' . $qadd . ')'; - } - if ($hasUpdated) { - return 'IFNULL(' . $this->db->qid('updated') . ', ' . $qadd . ')'; - } - return $qadd; - } - #cleanup - auto-expire locks public function cleanup(): void { - $this->db->exec("delete from " . $this->qTable() . " where DATE_ADD(" . $this->lockTimeSql() . ", INTERVAL " . $this->db->qid('expires') . " SECOND)db->exec("delete from " . $this->qTable() . " where DATE_ADD(IFNULL(" . $this->db->qid('upd_time') . ", " . $this->db->qid('add_time') . "), INTERVAL " . $this->db->qid('expires') . " SECOND) time(); } @@ -163,12 +132,8 @@ public function extend(string $icode, int $item_id = 0, ?string $environment = n } try { - $time_field = $this->lockTimeField(); - if ($time_field === 'add_time') { - return false; - } $rows_updated_ctr = $this->db->update($this->table_name, [ - $time_field => DB::NOW() + 'upd_time' => DB::NOW() ], [ 'icode' => $icode, 'environment' => $environment, From 592f3cddcb9f3cd23436139fa1970f44235a3d30 Mon Sep 17 00:00:00 2001 From: Oleg Savchuk Date: Thu, 30 Apr 2026 17:02:53 -0500 Subject: [PATCH 3/3] reorganize folders --- .github/copilot-instructions.md | 45 ++++++----- .gitignore | 9 +-- AGENTS.md | 45 ++++++----- README.md | 46 +++++------ bin/libman.php | 6 +- bin/phpunit-local.bat | 2 +- docs/agents/code_reviewer.md | 10 +-- docs/agents/domain.md | 9 ++- .../tasks/summary-2026-04-18-agents-guide.md | 6 +- ...summary-2026-04-30-public-root-minimize.md | 79 +++++++++++++++++++ docs/db.md | 2 +- docs/dynamic.md | 8 +- docs/templates.md | 30 +++---- {www/php => php}/FwHooks.php | 0 {www/php => php}/SiteUtils.php | 0 {www/php => php}/composer.json | 0 {www/php => php}/composer.lock | 0 {www/php => php}/configs/config.php | 15 ++-- {www/php => php}/configs/localhost.php | 6 +- {www/php => php}/configs/site.php | 6 +- {www/php => php}/controllers/AdminAtt.php | 0 php/controllers/AdminDB.php | 14 ++++ .../controllers/AdminDemoDicts.php | 0 {www/php => php}/controllers/AdminDemos.php | 0 .../controllers/AdminDemosDynamic.php | 0 .../php => php}/controllers/AdminDemosVue.php | 0 .../controllers/AdminLookupManager.php | 0 {www/php => php}/controllers/AdminLookups.php | 0 {www/php => php}/controllers/AdminReports.php | 0 .../php => php}/controllers/AdminSettings.php | 0 {www/php => php}/controllers/AdminSpages.php | 0 {www/php => php}/controllers/AdminUsers.php | 0 {www/php => php}/controllers/Att.php | 0 {www/php => php}/controllers/BaseApi.php | 0 {www/php => php}/controllers/Contact.php | 0 {www/php => php}/controllers/DevConfigure.php | 0 {www/php => php}/controllers/DevManage.php | 12 +-- {www/php => php}/controllers/Home.php | 0 {www/php => php}/controllers/Login.php | 0 {www/php => php}/controllers/Main.php | 0 {www/php => php}/controllers/MyLists.php | 0 {www/php => php}/controllers/MyPassword.php | 0 {www/php => php}/controllers/MySettings.php | 0 {www/php => php}/controllers/Password.php | 0 {www/php => php}/controllers/Signup.php | 0 {www/php => php}/controllers/Test.php | 0 .../php => php}/controllers/WebFormMailer.php | 0 .../php => php}/controllers/v1/v1DemosApi.php | 0 {www/php => php}/controllers/v1/v1Noop.php | 0 {www/php => php}/cron.php | 0 {www/php => php}/fw/DateUtils.php | 0 {www/php => php}/fw/FormUtils.php | 0 {www/php => php}/fw/FwActivityLogs.php | 0 {www/php => php}/fw/FwAdminController.php | 0 {www/php => php}/fw/FwApiController.php | 0 {www/php => php}/fw/FwCache.php | 0 {www/php => php}/fw/FwController.php | 0 {www/php => php}/fw/FwControllers.php | 0 {www/php => php}/fw/FwDev.php | 0 {www/php => php}/fw/FwDynamicController.php | 0 {www/php => php}/fw/FwEntities.php | 0 {www/php => php}/fw/FwExceptions.php | 0 {www/php => php}/fw/FwLogTypes.php | 0 {www/php => php}/fw/FwModel.php | 0 {www/php => php}/fw/FwUpdates.php | 0 {www/php => php}/fw/FwVirtualController.php | 0 {www/php => php}/fw/FwVueController.php | 0 {www/php => php}/fw/ImageUtils.php | 0 {www/php => php}/fw/ParsePage.php | 0 {www/php => php}/fw/UploadUtils.php | 0 {www/php => php}/fw/Utils.php | 0 {www/php => php}/fw/db.php | 0 {www/php => php}/fw/dispatcher.php | 0 {www/php => php}/fw/fw.php | 8 +- {www/php => php}/fw/lock.php | 0 {www/php => php}/fw/phplibman.php | 0 {www/php => php}/libman.json | 0 {www/php => php}/models/Att.php | 2 +- {www/php => php}/models/AttCategories.php | 0 {www/php => php}/models/AttLinks.php | 0 {www/php => php}/models/DemoDicts.php | 0 {www/php => php}/models/Demos.php | 0 {www/php => php}/models/DemosDemoDicts.php | 0 {www/php => php}/models/DemosItems.php | 0 {www/php => php}/models/Locks.php | 0 .../models/LookupManagerTables.php | 0 {www/php => php}/models/Reports.php | 4 +- {www/php => php}/models/Reports/Sample.php | 0 {www/php => php}/models/Roles/Permissions.php | 0 {www/php => php}/models/Roles/Resources.php | 0 {www/php => php}/models/Roles/Roles.php | 0 .../Roles/RolesResourcesPermissions.php | 0 {www/php => php}/models/Roles/UsersRoles.php | 0 {www/php => php}/models/S3.php | 0 {www/php => php}/models/Settings.php | 0 {www/php => php}/models/Spages.php | 0 {www/php => php}/models/UserFilters.php | 0 {www/php => php}/models/UserLists.php | 0 {www/php => php}/models/UserViews.php | 0 {www/php => php}/models/Users.php | 0 {www/php => php}/tests/DatabaseTestCase.php | 0 {www/php => php}/tests/FrameworkTestCase.php | 0 {www/php => php}/tests/TestHostResolver.php | 4 +- {www/php => php}/tests/bootstrap.php | 2 +- .../tests/controllers/ControllerTestCase.php | 0 .../tests/controllers/TestControllerTest.php | 0 .../tests/models/UsersModelTest.php | 0 {www/php => php}/tests/run-local-phpunit.php | 4 +- .../tests/unit/FrameworkBootstrapTest.php | 0 {www => php/tools}/phpminiadmin.php | 0 php/tools/phpminiconfig.php | 36 +++++++++ {www/php => php}/vendor/autoload.php | 0 .../vendor/composer/ClassLoader.php | 0 .../vendor/composer/InstalledVersions.php | 0 {www/php => php}/vendor/composer/LICENSE | 0 .../vendor/composer/autoload_classmap.php | 0 .../vendor/composer/autoload_namespaces.php | 0 .../vendor/composer/autoload_psr4.php | 0 .../vendor/composer/autoload_real.php | 0 .../vendor/composer/autoload_static.php | 0 .../vendor/composer/installed.json | 0 .../php => php}/vendor/composer/installed.php | 0 .../vendor/composer/platform_check.php | 0 .../vendor/phpmailer/phpmailer/.editorconfig | 0 .../vendor/phpmailer/phpmailer/COMMITMENT | 0 .../vendor/phpmailer/phpmailer/LICENSE | 0 .../vendor/phpmailer/phpmailer/README.md | 0 .../vendor/phpmailer/phpmailer/SECURITY.md | 0 .../vendor/phpmailer/phpmailer/VERSION | 0 .../vendor/phpmailer/phpmailer/composer.json | 0 .../phpmailer/phpmailer/get_oauth_token.php | 0 .../phpmailer/language/phpmailer.lang-af.php | 0 .../phpmailer/language/phpmailer.lang-ar.php | 0 .../phpmailer/language/phpmailer.lang-as.php | 0 .../phpmailer/language/phpmailer.lang-az.php | 0 .../phpmailer/language/phpmailer.lang-ba.php | 0 .../phpmailer/language/phpmailer.lang-be.php | 0 .../phpmailer/language/phpmailer.lang-bg.php | 0 .../phpmailer/language/phpmailer.lang-bn.php | 0 .../phpmailer/language/phpmailer.lang-ca.php | 0 .../phpmailer/language/phpmailer.lang-cs.php | 0 .../phpmailer/language/phpmailer.lang-da.php | 0 .../phpmailer/language/phpmailer.lang-de.php | 0 .../phpmailer/language/phpmailer.lang-el.php | 0 .../phpmailer/language/phpmailer.lang-eo.php | 0 .../phpmailer/language/phpmailer.lang-es.php | 0 .../phpmailer/language/phpmailer.lang-et.php | 0 .../phpmailer/language/phpmailer.lang-fa.php | 0 .../phpmailer/language/phpmailer.lang-fi.php | 0 .../phpmailer/language/phpmailer.lang-fo.php | 0 .../phpmailer/language/phpmailer.lang-fr.php | 0 .../phpmailer/language/phpmailer.lang-gl.php | 0 .../phpmailer/language/phpmailer.lang-he.php | 0 .../phpmailer/language/phpmailer.lang-hi.php | 0 .../phpmailer/language/phpmailer.lang-hr.php | 0 .../phpmailer/language/phpmailer.lang-hu.php | 0 .../phpmailer/language/phpmailer.lang-hy.php | 0 .../phpmailer/language/phpmailer.lang-id.php | 0 .../phpmailer/language/phpmailer.lang-it.php | 0 .../phpmailer/language/phpmailer.lang-ja.php | 0 .../phpmailer/language/phpmailer.lang-ka.php | 0 .../phpmailer/language/phpmailer.lang-ko.php | 0 .../phpmailer/language/phpmailer.lang-ku.php | 0 .../phpmailer/language/phpmailer.lang-lt.php | 0 .../phpmailer/language/phpmailer.lang-lv.php | 0 .../phpmailer/language/phpmailer.lang-mg.php | 0 .../phpmailer/language/phpmailer.lang-mn.php | 0 .../phpmailer/language/phpmailer.lang-ms.php | 0 .../phpmailer/language/phpmailer.lang-nb.php | 0 .../phpmailer/language/phpmailer.lang-nl.php | 0 .../phpmailer/language/phpmailer.lang-pl.php | 0 .../phpmailer/language/phpmailer.lang-pt.php | 0 .../language/phpmailer.lang-pt_br.php | 0 .../phpmailer/language/phpmailer.lang-ro.php | 0 .../phpmailer/language/phpmailer.lang-ru.php | 0 .../phpmailer/language/phpmailer.lang-si.php | 0 .../phpmailer/language/phpmailer.lang-sk.php | 0 .../phpmailer/language/phpmailer.lang-sl.php | 0 .../phpmailer/language/phpmailer.lang-sr.php | 0 .../language/phpmailer.lang-sr_latn.php | 0 .../phpmailer/language/phpmailer.lang-sv.php | 0 .../phpmailer/language/phpmailer.lang-tl.php | 0 .../phpmailer/language/phpmailer.lang-tr.php | 0 .../phpmailer/language/phpmailer.lang-uk.php | 0 .../phpmailer/language/phpmailer.lang-ur.php | 0 .../phpmailer/language/phpmailer.lang-vi.php | 0 .../phpmailer/language/phpmailer.lang-zh.php | 0 .../language/phpmailer.lang-zh_cn.php | 0 .../phpmailer/src/DSNConfigurator.php | 0 .../phpmailer/phpmailer/src/Exception.php | 0 .../vendor/phpmailer/phpmailer/src/OAuth.php | 0 .../phpmailer/src/OAuthTokenProvider.php | 0 .../phpmailer/phpmailer/src/PHPMailer.php | 0 .../vendor/phpmailer/phpmailer/src/POP3.php | 0 .../vendor/phpmailer/phpmailer/src/SMTP.php | 0 phpunit.xml | 8 +- .../admin/activitylogs/url.html | 0 .../admin/att/index/list_filter_more.html | 0 .../admin/att/index/list_table.html | 0 .../admin/att/index/main.html | 0 .../admin/att/index/onload.js | 0 .../admin/att/index/quick_upload.html | 0 .../admin/att/index/row_click_url.html | 0 .../admin/att/index/title.html | 0 .../admin/att/select/main.html | 0 .../admin/att/select/modal.html | 0 .../admin/att/select/onload.js | 0 .../admin/att/select/title.html | 0 .../admin/att/showform/form.html | 0 .../admin/att/showform/form_left.html | 0 .../admin/att/showform/form_right.html | 0 .../admin/att/showform/main.html | 0 .../admin/att/showform/title.html | 0 .../admin/att/status.sel | 0 {www/template => template}/admin/att/url.html | 0 .../admin/db/index/a_end.html | 0 .../admin/db/index/main.html | 0 .../admin/db/index/results.html | 0 .../admin/db/index/select_link.html | 0 .../admin/db/index/title.html | 0 .../admin/db/index/url.html | 0 {www/template => template}/admin/db/url.html | 0 .../demodicts/index/list_filter_more.html | 0 .../admin/demodicts/index/list_table.html | 0 .../admin/demodicts/index/main.html | 0 .../admin/demodicts/index/row_click_url.html | 0 .../admin/demodicts/index/title.html | 0 .../admin/demodicts/showform/form.html | 0 .../admin/demodicts/showform/main.html | 0 .../admin/demodicts/showform/title.html | 0 .../admin/demodicts/url.html | 0 .../admin/demos/index/btn_multidel_more.html | 0 .../admin/demos/index/list_filter_more.html | 0 .../admin/demos/index/list_table.html | 0 .../admin/demos/index/load_script.html | 0 .../admin/demos/index/main.html | 0 .../admin/demos/index/onload.js | 0 .../admin/demos/index/return_url.html | 0 .../admin/demos/index/row_click_url.html | 0 .../admin/demos/index/title.html | 0 .../admin/demos/index/userlist_entity.html | 0 .../admin/demos/show/btn_std_more.html | 0 .../admin/demos/show/form.html | 0 .../admin/demos/show/form_left.html | 0 .../admin/demos/show/form_right.html | 0 .../admin/demos/show/load_script.html | 0 .../admin/demos/show/main.html | 0 .../admin/demos/show/onload.js | 0 .../admin/demos/show/return_url.html | 0 .../admin/demos/show/title.html | 0 .../admin/demos/show/userlist_entity.html | 0 .../admin/demos/showform/btn_std_more.html | 0 .../admin/demos/showform/form.html | 0 .../admin/demos/showform/form_left.html | 0 .../admin/demos/showform/form_right.html | 0 .../admin/demos/showform/load_script.html | 0 .../admin/demos/showform/main.html | 0 .../admin/demos/showform/onload.js | 0 .../admin/demos/showform/title.html | 0 .../admin/demos/url.html | 0 .../admin/demosdynamic/config.json | 0 .../demosdynamic/index/btn_multidel_more.html | 0 .../admin/demosdynamic/index/col_custom.html | 0 .../demosdynamic/index/list_filter_more.html | 0 .../demosdynamic/index/list_row_btn.html | 0 .../admin/demosdynamic/index/list_table.html | 0 .../admin/demosdynamic/index/load_script.html | 0 .../admin/demosdynamic/index/main.html | 0 .../admin/demosdynamic/index/onload.js | 0 .../admin/demosdynamic/index/return_url.html | 0 .../demosdynamic/index/row_click_url.html | 0 .../admin/demosdynamic/index/title.html | 0 .../demosdynamic/index/userlist_entity.html | 0 .../admin/demosdynamic/show/btn_std_more.html | 0 .../admin/demosdynamic/show/custom_field.html | 0 .../admin/demosdynamic/show/form.html | 0 .../admin/demosdynamic/show/load_script.html | 0 .../admin/demosdynamic/show/main.html | 0 .../admin/demosdynamic/show/onload.js | 0 .../admin/demosdynamic/show/return_url.html | 0 .../admin/demosdynamic/show/subtable.html | 0 .../show/subtable_demos_items.html | 0 .../admin/demosdynamic/show/title.html | 0 .../demosdynamic/show/userlist_entity.html | 0 .../demosdynamic/showform/btn_std_more.html | 0 .../admin/demosdynamic/showform/form.html | 0 .../demosdynamic/showform/load_script.html | 0 .../admin/demosdynamic/showform/main.html | 0 .../admin/demosdynamic/showform/onload.js | 0 .../admin/demosdynamic/showform/subtable.html | 0 .../showform/subtable_demos_items.html | 0 .../admin/demosdynamic/showform/title.html | 0 .../admin/demosdynamic/url.html | 0 .../admin/demosvue/config.json | 0 .../admin/demosvue/index/app.js | 0 .../admin/demosvue/index/main.html | 0 .../admin/demosvue/index/store.js | 0 .../admin/demosvue/index/title.html | 0 .../index/vue/subtable_demos_items.html | 0 .../admin/demosvue/index/vue_components.html | 0 .../admin/demosvue/url.html | 0 .../admin/fwupdates/config.json | 0 .../admin/lookups/index/head.css | 0 .../admin/lookups/index/list_table.html | 0 .../admin/lookups/index/main.html | 0 .../admin/lookups/index/row_click_url.html | 0 .../admin/lookups/index/title.html | 0 .../admin/lookups/url.html | 0 .../admin/reports/common/btn_apply_print.html | 0 .../admin/reports/common/btn_download.html | 0 .../admin/reports/common/btn_download2.html | 0 .../admin/reports/common/btn_download3.html | 0 .../admin/reports/common/btn_reset.html | 0 .../admin/reports/common/ctr.html | 0 .../admin/reports/common/docx.html | 0 .../admin/reports/common/filter_buttons.html | 0 .../admin/reports/common/filter_buttons2.html | 0 .../admin/reports/common/filter_buttons3.html | 0 .../admin/reports/common/filter_dates.html | 0 .../admin/reports/common/filter_year.html | 0 .../admin/reports/common/head.css | 0 .../admin/reports/common/onload.js | 0 .../admin/reports/common/pdf.html | 0 .../admin/reports/common/xls.html | 0 .../admin/reports/index/main.html | 0 .../admin/reports/index/title.html | 0 .../admin/reports/sample/head.css | 0 .../admin/reports/sample/list_filter.html | 0 .../admin/reports/sample/load_script.html | 0 .../admin/reports/sample/main.html | 0 .../admin/reports/sample/onload.js | 0 .../admin/reports/sample/report_html.html | 0 .../admin/reports/sample/title.html | 0 .../admin/reports/url.html | 0 .../admin/roles/config.json | 0 .../admin/roles/index/btn_multidel_more.html | 0 .../admin/roles/index/col_custom.html | 0 .../admin/roles/index/list_filter_more.html | 0 .../admin/roles/index/list_row_btn.html | 0 .../admin/roles/index/list_table.html | 0 .../admin/roles/index/load_script.html | 0 .../admin/roles/index/main.html | 0 .../admin/roles/index/onload.js | 0 .../admin/roles/index/return_url.html | 0 .../admin/roles/index/row_click_url.html | 0 .../admin/roles/index/title.html | 0 .../admin/roles/index/userlist_entity.html | 0 .../admin/roles/show/btn_std_more.html | 0 .../admin/roles/show/form.html | 0 .../admin/roles/show/load_script.html | 0 .../admin/roles/show/main.html | 0 .../admin/roles/show/onload.js | 0 .../admin/roles/show/return_url.html | 0 .../show/roles_resources_permissions.html | 0 .../admin/roles/show/title.html | 0 .../admin/roles/show/userlist_entity.html | 0 .../admin/roles/showform/btn_std_more.html | 0 .../admin/roles/showform/form.html | 0 .../admin/roles/showform/load_script.html | 0 .../admin/roles/showform/main.html | 0 .../admin/roles/showform/onload.js | 0 .../showform/roles_resources_permissions.html | 0 .../admin/roles/showform/title.html | 0 .../admin/roles/url.html | 0 .../admin/sendemail/save/main.html | 0 .../admin/sendemail/save/title.html | 0 .../sendemail/showform/btn_std_more.html | 0 .../admin/sendemail/showform/form.html | 0 .../admin/sendemail/showform/form_left.html | 0 .../admin/sendemail/showform/form_right.html | 0 .../admin/sendemail/showform/load_script.html | 0 .../admin/sendemail/showform/main.html | 0 .../admin/sendemail/showform/onload.js | 0 .../admin/sendemail/showform/title.html | 0 .../admin/sendemail/url.html | 0 .../admin/settings/index/list_table.html | 0 .../admin/settings/index/main.html | 0 .../admin/settings/index/row_click_url.html | 0 .../admin/settings/index/title.html | 0 .../admin/settings/showform/form.html | 0 .../settings/showform/input_checkbox.html | 0 .../admin/settings/showform/input_date.html | 0 .../admin/settings/showform/input_input.html | 0 .../admin/settings/showform/input_radio.html | 0 .../admin/settings/showform/input_select.html | 0 .../settings/showform/input_selectmulti.html | 0 .../settings/showform/input_textarea.html | 0 .../admin/settings/showform/load_script.html | 0 .../admin/settings/showform/main.html | 0 .../admin/settings/showform/title.html | 0 .../admin/settings/url.html | 0 .../admin/spages/index/list_filter_more.html | 0 .../admin/spages/index/list_table.html | 0 .../admin/spages/index/main.html | 0 .../admin/spages/index/row_click_url.html | 0 .../admin/spages/index/title.html | 0 .../admin/spages/showform/form.html | 0 .../admin/spages/showform/load_script.html | 0 .../admin/spages/showform/main.html | 0 .../admin/spages/showform/onload.js | 0 .../admin/spages/showform/page_content.html | 0 .../admin/spages/showform/page_settings.html | 0 .../admin/spages/showform/title.html | 0 .../admin/spages/status.sel | 0 .../admin/spages/template.sel | 0 .../admin/spages/url.html | 0 .../admin/users/config.json | 0 .../admin/users/index/btn_std_more.html | 0 .../admin/users/index/col_custom.html | 0 .../admin/users/index/col_is_mfa.html | 0 .../admin/users/index/col_status.html | 0 .../admin/users/index/list_filter_more.html | 0 .../admin/users/index/list_row_btn.html | 0 .../admin/users/index/list_table.html | 0 .../admin/users/index/load_script.html | 0 .../admin/users/index/main.html | 0 .../admin/users/index/onload.js | 0 .../admin/users/index/return_url.html | 0 .../admin/users/index/row_click_url.html | 0 .../admin/users/index/title.html | 0 .../admin/users/show/btn_std_more.html | 0 .../admin/users/show/form.html | 0 .../admin/users/show/main.html | 0 .../admin/users/show/onload.js | 0 .../admin/users/show/return_url.html | 0 .../admin/users/show/title.html | 0 .../admin/users/showform/btn_std_more.html | 0 .../admin/users/showform/form.html | 0 .../admin/users/showform/form_left.html | 0 .../admin/users/showform/form_status.html | 0 .../admin/users/showform/load_script.html | 0 .../admin/users/showform/main.html | 0 .../admin/users/showform/onload.js | 0 .../admin/users/showform/roles_block.html | 0 .../admin/users/showform/title.html | 0 .../admin/users/status.sel | 0 .../admin/users/status_bgcolor.sel | 0 .../admin/users/statusf.sel | 0 .../admin/users/url.html | 0 {www/template => template}/att/url.html | 0 {www/template => template}/common/active.html | 0 .../common/activitylogs/avatar.html | 0 .../common/activitylogs/edited.html | 0 .../common/activitylogs/fields.html | 0 .../common/activitylogs/initials.html | 0 .../common/activitylogs/logtype.html | 0 .../common/activitylogs/main.html | 0 .../common/activitylogs/onload.js | 0 .../common/ajaxform.html | 0 {www/template => template}/common/att.html | 0 .../common/autocomplete.html | 0 .../common/bootstrap_select.html | 0 .../common/calendar.html | 0 .../common/char/darr.html | 0 .../common/char/del.html | 0 .../common/char/down.html | 0 .../template => template}/common/char/gt.html | 0 .../common/char/larr.html | 0 .../common/char/uarr.html | 0 .../template => template}/common/char/up.html | 0 .../template => template}/common/checked.html | 0 .../common/clactive.html | 0 {www/template => template}/common/comma.html | 0 {www/template => template}/common/delim.html | 0 .../common/disabled.html | 0 .../common/disabledcl.html | 0 .../common/display_none.html | 0 {www/template => template}/common/dot.html | 0 {www/template => template}/common/error.html | 0 {www/template => template}/common/even.html | 0 .../common/form/actions_std.html | 0 .../common/form/actions_std_new.html | 0 .../common/form/actions_std_responsive.html | 0 .../common/form/added.html | 0 .../common/form/btn_del.html | 0 .../common/form/btn_edit.html | 0 .../common/form/btn_std.html | 0 .../common/form/btn_top_save.html | 0 .../common/form/btn_top_save_addnew.html | 0 .../common/form/btn_userlists.html | 0 .../common/form/btn_view.html | 0 .../common/form/cancel_url.html | 0 .../common/form/data_autosave.html | 0 .../common/form/group_id.html | 0 .../common/form/group_id_new.html | 0 .../common/form/group_status.html | 0 .../template => template}/common/form/id.html | 0 .../common/form/idnew.html | 0 .../common/form/msg.html | 0 .../common/form/nav_title.html | 0 .../common/form/nav_title_edit.html | 0 .../common/form/prev_next.html | 0 .../common/form/prev_next_edit.html | 0 .../common/form/prio.html | 0 .../common/form/relid.html | 0 .../common/form/ret_url.html | 0 .../common/form/show/added.html | 0 .../common/form/show/att.html | 0 .../common/form/show/att_files.html | 0 .../common/form/show/att_links.html | 0 .../common/form/show/checkbox.html | 0 .../common/form/show/col.html | 0 .../common/form/show/col_end.html | 0 .../common/form/show/date.html | 0 .../common/form/show/date_long.html | 0 .../common/form/show/extract.html | 0 .../common/form/show/extract/checkbox.html | 0 .../common/form/show/extract/form.html | 0 .../common/form/show/extract/one_field.html | 0 .../form/show/extract/one_fieldgroup.html | 0 .../form/show/extract/one_fieldsel.html | 0 .../common/form/show/extract/value.html | 0 .../common/form/show/float.html | 0 .../common/form/show/help_block.html | 0 .../common/form/show/help_block_label.html | 0 .../form/show/help_block_label_html.html | 0 .../common/form/show/markdown.html | 0 .../common/form/show/multi.html | 0 .../common/form/show/noescape.html | 0 .../common/form/show/one_field.html | 0 .../common/form/show/one_fieldgroup.html | 0 .../common/form/show/one_fieldsel.html | 0 .../common/form/show/one_structure.html | 0 .../common/form/show/plaintext.html | 0 .../form/show/plaintext_autocomplete.html | 0 .../common/form/show/plaintext_json.html | 0 .../common/form/show/plaintext_link.html | 0 .../common/form/show/plaintext_url.html | 0 .../common/form/show/plaintext_yesno.html | 0 .../common/form/show/row.html | 0 .../common/form/show/row_end.html | 0 .../common/form/show/subtable.html | 0 .../common/form/show/updated.html | 0 .../form/showdelete/actions_delete.html | 0 .../common/form/showdelete/cancel_url.html | 0 .../common/form/showdelete/delete_std.html | 0 .../common/form/showdelete/main.html | 0 .../common/form/showdelete/title.html | 0 .../common/form/showform/att.html | 0 .../common/form/showform/att_files.html | 0 .../common/form/showform/att_links.html | 0 .../common/form/showform/autocomplete.html | 0 .../common/form/showform/cb.html | 0 .../common/form/showform/cc_inline.html | 0 .../common/form/showform/col.html | 0 .../common/form/showform/col_end.html | 0 .../common/form/showform/date_popup.html | 0 .../common/form/showform/datetime_popup.html | 0 .../common/form/showform/email.html | 0 .../form/showform/extract/checkbox.html | 0 .../common/form/showform/extract/form.html | 0 .../form/showform/extract/group_id.html | 0 .../common/form/showform/extract/id.html | 0 .../form/showform/extract/one_field.html | 0 .../form/showform/extract/one_fieldgroup.html | 0 .../form/showform/extract/one_fieldsel.html | 0 .../common/form/showform/extract/select.html | 0 .../form/showform/extract/select_np.html | 0 .../common/form/showform/extract/value.html | 0 .../common/form/showform/group_id.html | 0 .../common/form/showform/group_id_addnew.html | 0 .../common/form/showform/help_block.html | 0 .../form/showform/help_block_label.html | 0 .../form/showform/help_block_label_html.html | 0 .../common/form/showform/id.html | 0 .../common/form/showform/input.html | 0 .../common/form/showform/max.html | 0 .../common/form/showform/maxlength.html | 0 .../common/form/showform/min.html | 0 .../common/form/showform/multi.html | 0 .../common/form/showform/multi_prio.html | 0 .../common/form/showform/number.html | 0 .../common/form/showform/one_field.html | 0 .../common/form/showform/one_fieldgroup.html | 0 .../common/form/showform/one_fieldsel.html | 0 .../common/form/showform/one_structure.html | 0 .../common/form/showform/password.html | 0 .../common/form/showform/placeholder.html | 0 .../common/form/showform/radio.html | 0 .../common/form/showform/row.html | 0 .../common/form/showform/row_end.html | 0 .../common/form/showform/rows.html | 0 .../common/form/showform/select.html | 0 .../common/form/showform/step.html | 0 .../common/form/showform/subtable.html | 0 .../common/form/showform/textarea.html | 0 .../common/form/showform/time.html | 0 .../form/showform/validation_errors.html | 0 .../common/form/showform/yesno.html | 0 .../common/form/status.html | 0 .../common/form/tabs.html | 0 .../common/form/updated.html | 0 .../common/form/url_edit.html | 0 {www/template => template}/common/hide.html | 0 .../common/html_editor.html | 0 .../common/html_editor_local.html | 0 .../common/icons/plusb.html | 0 .../template => template}/common/invalid.html | 0 .../common/list/btn_multidel.html | 0 .../common/list/btn_std.html | 0 .../common/list/btn_userfilters.html | 0 .../common/list/btn_userlists.html | 0 .../common/list/col_iname.html | 0 .../common/list/col_prio.html | 0 .../common/list/col_status.html | 0 .../common/list/col_status_badge.html | 0 .../common/list/empty.html | 0 .../common/list/export/rows.html | 0 .../common/list/export/xls.html | 0 .../common/list/export/xls_foot.html | 0 .../common/list/export/xls_head.html | 0 .../common/list/export/xls_rows.html | 0 .../common/list/fexport.html | 0 .../common/list/filter_search.html | 0 .../common/list/filter_std.html | 0 .../common/list/filter_std_status.html | 0 .../common/list/filter_std_status_export.html | 0 .../common/list/filter_std_su.html | 0 .../common/list/form_delete.html | 0 .../common/list/form_list.html | 0 .../common/list/fsearch.html | 0 .../common/list/fstatus.html | 0 .../common/list/fuserlists.html | 0 .../common/list/fwsortable.html | 0 .../common/list/list_row_td_btn.html | 0 .../common/list/pagination.html | 0 .../common/list/pagination_always.html | 0 .../common/list/relid.html | 0 .../common/list/relidq.html | 0 .../common/list/reset_filter_relid.html | 0 .../common/list/row_btn_del.html | 0 .../common/list/row_btn_edit.html | 0 .../common/list/row_btn_std.html | 0 .../common/list/row_btn_view.html | 0 .../common/list/table_under.html | 0 .../common/list/tbody.html | 0 .../common/list/th_chkall.html | 0 .../common/list/th_empty.html | 0 .../common/list/th_filter_customize.html | 0 .../common/list/thead.html | 0 .../common/list/userviews/main.html | 0 .../common/list/userviews/modal.html | 0 .../common/list/userviews/onload.js | 0 .../common/list/userviews/title.html | 0 .../common/markdown_editor.html | 0 .../common/multiple.html | 0 {www/template => template}/common/odd.html | 0 .../common/readonly.html | 0 .../common/redirect_js.html | 0 .../common/required.html | 0 .../common/sel/access_level.sel | 0 .../common/sel/added_since.sel | 0 .../common/sel/country.sel | 0 .../common/sel/date_day.sel | 0 .../common/sel/date_mon.sel | 0 .../common/sel/date_year.sel | 0 .../common/sel/fcombo.sel | 0 .../common/sel/gender.sel | 0 .../template => template}/common/sel/lang.sel | 0 .../common/sel/pagesize.sel | 0 .../common/sel/sortby.sel | 0 .../common/sel/state.sel | 0 .../common/sel/status.sel | 0 .../common/sel/status3.sel | 0 .../common/sel/status_bgcolor.sel | 0 .../common/sel/statusf.sel | 0 .../common/sel/statusf_admin.sel | 0 .../common/sel/ui_mode.sel | 0 .../common/sel/ui_mode_data.sel | 0 .../common/sel/ui_theme.sel | 0 {www/template => template}/common/sel/yn.sel | 0 .../common/sel/yn_bool.sel | 0 .../common/sel/yn_char.sel | 0 .../common/sel/yn_char_rev.sel | 0 .../common/selected.html | 0 .../common/selected0.html | 0 .../common/sortable.html | 0 .../common/text_end.html | 0 .../common/uploader.html | 0 .../common/virtual/index/app.js | 0 .../common/virtual/index/main.html | 0 .../common/virtual/index/store.js | 0 .../common/virtual/index/title.html | 0 .../index/vue/subtable_demos_items.html | 0 .../common/virtual/index/vue_components.html | 0 .../common/virtual/url.html | 0 .../common/vue/_sample.html | 0 .../common/vue/activity-logs.html | 0 {www/template => template}/common/vue/app.js | 0 .../common/vue/att-select.html | 0 .../common/vue/autocomplete.html | 0 .../common/vue/date-picker.html | 0 .../common/vue/edit-form.html | 0 .../common/vue/edit-header.html | 0 .../common/vue/form-control-help-block.html | 0 .../common/vue/form-one-col.html | 0 .../common/vue/form-one-control.html | 0 .../common/vue/form-one-def.html | 0 .../common/vue/form-one-form-row.html | 0 .../common/vue/form-one-group.html | 0 .../common/vue/form-one-row.html | 0 .../common/vue/list-btn-multi.html | 0 .../common/vue/list-btn-userlists.html | 0 .../common/vue/list-cell-checkbox.html | 0 .../common/vue/list-cell-input.html | 0 .../common/vue/list-cell-ro.html | 0 .../common/vue/list-cell-select.html | 0 .../common/vue/list-customize-columns.html | 0 .../common/vue/list-edit-pane.html | 0 .../common/vue/list-filters-more.html | 0 .../common/vue/list-filters-table-btn.html | 0 .../common/vue/list-filters.html | 0 .../common/vue/list-header.html | 0 .../common/vue/list-pagination.html | 0 .../common/vue/list-row-btn.html | 0 .../common/vue/list-table.html | 0 .../common/vue/screens.html | 0 .../template => template}/common/vue/store.js | 0 .../common/vue/view-form.html | 0 .../common/vue/view-header.html | 0 .../contact/index/form.html | 0 .../contact/index/head.css | 0 .../contact/index/head_script.html | 0 .../contact/index/main.html | 0 .../contact/index/sub_header.html | 0 .../contact/index/title.html | 0 .../contact/sent/main.html | 0 .../contact/sent/title.html | 0 {www/template => template}/contact/url.html | 0 .../dev/configure/index/fail.html | 0 .../dev/configure/index/main.html | 0 .../dev/configure/index/ok.html | 0 .../dev/configure/index/title.html | 0 .../dev/configure/index/warn.html | 0 .../dev/configure/initdb/main.html | 0 .../dev/configure/initdb/title.html | 0 .../dev/configure/url.html | 0 .../dev/manage/apitester/csp_script.html | 0 .../dev/manage/apitester/main.html | 0 .../dev/manage/apitester/onload.js | 0 .../dev/manage/apitester/title.html | 0 .../dev/manage/appcreator/main.html | 0 .../dev/manage/appcreator/onload.js | 0 .../dev/manage/appcreator/title.html | 0 .../dev/manage/dbanalyzer/main.html | 0 .../dev/manage/dbanalyzer/title.html | 0 .../dev/manage/dbinitializer/main.html | 0 .../dev/manage/dbinitializer/title.html | 0 .../dev/manage/entitybuilder/main.html | 0 .../dev/manage/entitybuilder/title.html | 0 .../dev/manage/index/ctype.sel | 0 .../dev/manage/index/main.html | 0 .../dev/manage/index/onload.js | 0 .../dev/manage/index/title.html | 0 .../dev/manage/showdbupdates/main.html | 0 .../dev/manage/showdbupdates/title.html | 0 .../template => template}/dev/manage/url.html | 0 {www/template => template}/dev/url.html | 0 .../emails/email_actcode.txt | 0 .../emails/email_confirm.txt | 0 .../emails/email_invite.txt | 0 .../emails/email_pwd.txt | 0 .../template => template}/emails/feedback.txt | 0 {www/template => template}/emails/sign_en.txt | 0 {www/template => template}/emails/signup.txt | 0 .../template => template}/error/404/main.html | 0 .../error/404/title.html | 0 .../template => template}/error/4xx/main.html | 0 .../error/4xx/title.html | 0 {www/template => template}/error/main.html | 0 {www/template => template}/error/onload.js | 0 {www/template => template}/files/url.html | 0 .../template => template}/home/about/head.css | 0 .../home/about/main.html | 0 .../home/about/sub_header.html | 0 .../home/about/title.html | 0 .../template => template}/home/index/head.css | 0 .../home/index/main.html | 0 .../home/index/onload.js | 0 .../home/index/sub_header.html | 0 .../template => template}/home/spage/head.css | 0 {www/template => template}/home/spage/head.js | 0 .../home/spage/head_script.html | 0 .../home/spage/main.html | 0 .../home/spage/one_col.html | 0 .../home/spage/sidebar.html | 0 .../home/spage/three_col.html | 0 .../home/spage/title.html | 0 .../home/spage/two_col.html | 0 .../home/spage/two_left_col.html | 0 .../home/spage/two_right_col.html | 0 {www/template => template}/home/url.html | 0 {www/template => template}/lang/de.txt | 0 {www/template => template}/lang/en.txt | 0 {www/template => template}/lang/ru.txt | 0 {www/template => template}/lang/ua.txt | 0 {www/template => template}/lang/zh.txt | 0 {www/template => template}/layout.html | 0 {www/template => template}/layout/admin.js | 0 {www/template => template}/layout/beta.html | 0 {www/template => template}/layout/common.js | 0 .../layout/content_security_policy.html | 0 .../layout/counters/ga.html | 0 {www/template => template}/layout/footer.html | 0 .../layout/head_common.js | 0 {www/template => template}/layout/header.html | 0 .../layout/header_public.html | 0 .../layout/main_no_sidebar.html | 0 .../layout/main_with_sidebar.html | 0 .../template => template}/layout/sidebar.html | 0 .../layout/sidebar_my.html | 0 .../layout/sys_footer.html | 0 .../layout/sys_footer_print.html | 0 .../layout/sys_head.html | 0 .../layout/sys_head_print.html | 0 .../layout/theme_link.html | 0 .../template => template}/layout/ui_mode.html | 0 .../layout/user_avatar.html | 0 .../layout/vue/apputils.js | 0 .../layout/vue/common.js | 0 .../layout/vue/importmaps.html | 0 .../layout/vue/sys_footer.html | 0 .../layout/vue/sys_head.html | 0 {www/template => template}/layout_email.html | 0 {www/template => template}/layout_min.html | 0 {www/template => template}/layout_pjax.html | 0 .../layout_pjax_nojs.html | 0 {www/template => template}/layout_print.html | 0 {www/template => template}/layout_public.html | 0 {www/template => template}/layout_vue.html | 0 .../login/index/form.html | 0 .../login/index/form_logged.html | 0 .../login/index/google.html | 0 .../login/index/head.css | 0 .../login/index/login_problem.html | 0 .../login/index/main.html | 0 .../login/index/onload.js | 0 .../login/index/title.html | 0 .../template => template}/login/mfa/form.html | 0 {www/template => template}/login/mfa/head.css | 0 .../template => template}/login/mfa/main.html | 0 .../login/mfa/title.html | 0 {www/template => template}/login/url.html | 0 .../login/url_logoff.html | 0 .../template => template}/main/index/head.css | 0 {www/template => template}/main/index/head.js | 0 .../main/index/load_script.html | 0 .../main/index/main.html | 0 .../main/index/onload.js | 0 .../main/index/pane_title.html | 0 .../main/index/std_pane.html | 0 .../main/index/theme1.js | 0 .../main/index/theme2.js | 0 .../main/index/theme30.js | 0 .../main/index/title.html | 0 .../main/index/type_barchart.html | 0 .../main/index/type_html.html | 0 .../main/index/type_linechart.html | 0 .../main/index/type_piechart.html | 0 .../main/index/type_table.html | 0 {www/template => template}/main/url.html | 0 .../my/feedback/showform/modal.html | 0 .../my/feedback/showform/title.html | 0 .../my/feedback/url.html | 0 .../my/filters/create/modal.html | 0 .../my/filters/create/title.html | 0 .../my/filters/entities.sel | 0 .../my/filters/index/list_filter_more.html | 0 .../my/filters/index/list_table.html | 0 .../my/filters/index/main.html | 0 .../my/filters/index/row_click_url.html | 0 .../my/filters/index/title.html | 0 .../my/filters/showform/form.html | 0 .../my/filters/showform/main.html | 0 .../my/filters/showform/title.html | 0 .../template => template}/my/filters/url.html | 0 .../my/lists/create/modal.html | 0 .../my/lists/create/title.html | 0 .../my/lists/entities.sel | 0 .../my/lists/index/list_filter_more.html | 0 .../my/lists/index/list_table.html | 0 .../my/lists/index/main.html | 0 .../my/lists/index/row_click_url.html | 0 .../my/lists/index/title.html | 0 .../my/lists/showform/form.html | 0 .../my/lists/showform/main.html | 0 .../my/lists/showform/title.html | 0 {www/template => template}/my/lists/url.html | 0 .../my/mfa/save/main.html | 0 .../my/mfa/save/onload.js | 0 .../my/mfa/save/title.html | 0 .../my/mfa/showform/main.html | 0 .../my/mfa/showform/title.html | 0 {www/template => template}/my/mfa/url.html | 0 .../my/password/showform/form.html | 0 .../my/password/showform/main.html | 0 .../my/password/showform/onload.js | 0 .../my/password/showform/title.html | 0 .../my/password/url.html | 0 .../my/settings/showform/form.html | 0 .../my/settings/showform/main.html | 0 .../my/settings/showform/title.html | 0 .../my/settings/url.html | 0 .../my/views/index/list_filter_more.html | 0 .../my/views/index/list_table.html | 0 .../my/views/index/main.html | 0 .../my/views/index/row_click_url.html | 0 .../my/views/index/title.html | 0 .../my/views/showform/form.html | 0 .../my/views/showform/main.html | 0 .../my/views/showform/title.html | 0 {www/template => template}/my/views/url.html | 0 .../password/index/main.html | 0 .../password/index/onload.js | 0 .../password/index/title.html | 0 .../password/reset/main.html | 0 .../password/reset/onload.js | 0 .../password/reset/title.html | 0 .../password/sent/main.html | 0 .../password/sent/title.html | 0 {www/template => template}/password/url.html | 0 .../signup/showform/form.html | 0 .../signup/showform/head.css | 0 .../signup/showform/main.html | 0 .../signup/showform/onload.js | 0 .../signup/showform/title.html | 0 {www/template => template}/signup/url.html | 0 {www/template => template}/site_name.html | 0 .../sitemap/index/children.html | 0 .../sitemap/index/head.css | 0 .../sitemap/index/main.html | 0 .../sitemap/index/pages_children.html | 0 .../sitemap/index/pages_tree.html | 0 .../sitemap/index/title.html | 0 {www/template => template}/sitemap/url.html | 0 .../sys/backup/index/main.html | 0 .../sys/backup/index/title.html | 0 .../template => template}/sys/backup/url.html | 0 .../template => template}/test/index/head.css | 0 .../test/index/main.html | 0 .../test/index/nolang_test.html | 0 .../test/index/onload.js | 0 .../test/index/subfolder/1.html | 0 .../test/index/subfolder/2.html | 0 .../test/index/subfolder/3.html | 0 .../test/index/subfolder/4.txt | 0 .../test/index/subfolder/5.txt | 0 .../test/index/title.html | 0 .../test/index/true_value.html | 0 www/.htaccess | 5 +- www/index.php | 2 +- www/php/controllers/AdminDB.php | 13 --- www/phpminiconfig.php | 27 ------- www/web.config | 2 +- 954 files changed, 282 insertions(+), 185 deletions(-) create mode 100644 docs/agents/tasks/summary-2026-04-30-public-root-minimize.md rename {www/php => php}/FwHooks.php (100%) rename {www/php => php}/SiteUtils.php (100%) rename {www/php => php}/composer.json (100%) rename {www/php => php}/composer.lock (100%) rename {www/php => php}/configs/config.php (93%) rename {www/php => php}/configs/localhost.php (90%) rename {www/php => php}/configs/site.php (91%) rename {www/php => php}/controllers/AdminAtt.php (100%) create mode 100644 php/controllers/AdminDB.php rename {www/php => php}/controllers/AdminDemoDicts.php (100%) rename {www/php => php}/controllers/AdminDemos.php (100%) rename {www/php => php}/controllers/AdminDemosDynamic.php (100%) rename {www/php => php}/controllers/AdminDemosVue.php (100%) rename {www/php => php}/controllers/AdminLookupManager.php (100%) rename {www/php => php}/controllers/AdminLookups.php (100%) rename {www/php => php}/controllers/AdminReports.php (100%) rename {www/php => php}/controllers/AdminSettings.php (100%) rename {www/php => php}/controllers/AdminSpages.php (100%) rename {www/php => php}/controllers/AdminUsers.php (100%) rename {www/php => php}/controllers/Att.php (100%) rename {www/php => php}/controllers/BaseApi.php (100%) rename {www/php => php}/controllers/Contact.php (100%) rename {www/php => php}/controllers/DevConfigure.php (100%) rename {www/php => php}/controllers/DevManage.php (97%) rename {www/php => php}/controllers/Home.php (100%) rename {www/php => php}/controllers/Login.php (100%) rename {www/php => php}/controllers/Main.php (100%) rename {www/php => php}/controllers/MyLists.php (100%) rename {www/php => php}/controllers/MyPassword.php (100%) rename {www/php => php}/controllers/MySettings.php (100%) rename {www/php => php}/controllers/Password.php (100%) rename {www/php => php}/controllers/Signup.php (100%) rename {www/php => php}/controllers/Test.php (100%) rename {www/php => php}/controllers/WebFormMailer.php (100%) rename {www/php => php}/controllers/v1/v1DemosApi.php (100%) rename {www/php => php}/controllers/v1/v1Noop.php (100%) rename {www/php => php}/cron.php (100%) rename {www/php => php}/fw/DateUtils.php (100%) rename {www/php => php}/fw/FormUtils.php (100%) rename {www/php => php}/fw/FwActivityLogs.php (100%) rename {www/php => php}/fw/FwAdminController.php (100%) rename {www/php => php}/fw/FwApiController.php (100%) rename {www/php => php}/fw/FwCache.php (100%) rename {www/php => php}/fw/FwController.php (100%) rename {www/php => php}/fw/FwControllers.php (100%) rename {www/php => php}/fw/FwDev.php (100%) rename {www/php => php}/fw/FwDynamicController.php (100%) rename {www/php => php}/fw/FwEntities.php (100%) rename {www/php => php}/fw/FwExceptions.php (100%) rename {www/php => php}/fw/FwLogTypes.php (100%) rename {www/php => php}/fw/FwModel.php (100%) rename {www/php => php}/fw/FwUpdates.php (100%) rename {www/php => php}/fw/FwVirtualController.php (100%) rename {www/php => php}/fw/FwVueController.php (100%) rename {www/php => php}/fw/ImageUtils.php (100%) rename {www/php => php}/fw/ParsePage.php (100%) rename {www/php => php}/fw/UploadUtils.php (100%) rename {www/php => php}/fw/Utils.php (100%) rename {www/php => php}/fw/db.php (100%) rename {www/php => php}/fw/dispatcher.php (100%) rename {www/php => php}/fw/fw.php (99%) rename {www/php => php}/fw/lock.php (100%) rename {www/php => php}/fw/phplibman.php (100%) rename {www/php => php}/libman.json (100%) rename {www/php => php}/models/Att.php (99%) rename {www/php => php}/models/AttCategories.php (100%) rename {www/php => php}/models/AttLinks.php (100%) rename {www/php => php}/models/DemoDicts.php (100%) rename {www/php => php}/models/Demos.php (100%) rename {www/php => php}/models/DemosDemoDicts.php (100%) rename {www/php => php}/models/DemosItems.php (100%) rename {www/php => php}/models/Locks.php (100%) rename {www/php => php}/models/LookupManagerTables.php (100%) rename {www/php => php}/models/Reports.php (98%) rename {www/php => php}/models/Reports/Sample.php (100%) rename {www/php => php}/models/Roles/Permissions.php (100%) rename {www/php => php}/models/Roles/Resources.php (100%) rename {www/php => php}/models/Roles/Roles.php (100%) rename {www/php => php}/models/Roles/RolesResourcesPermissions.php (100%) rename {www/php => php}/models/Roles/UsersRoles.php (100%) rename {www/php => php}/models/S3.php (100%) rename {www/php => php}/models/Settings.php (100%) rename {www/php => php}/models/Spages.php (100%) rename {www/php => php}/models/UserFilters.php (100%) rename {www/php => php}/models/UserLists.php (100%) rename {www/php => php}/models/UserViews.php (100%) rename {www/php => php}/models/Users.php (100%) rename {www/php => php}/tests/DatabaseTestCase.php (100%) rename {www/php => php}/tests/FrameworkTestCase.php (100%) rename {www/php => php}/tests/TestHostResolver.php (97%) rename {www/php => php}/tests/bootstrap.php (94%) rename {www/php => php}/tests/controllers/ControllerTestCase.php (100%) rename {www/php => php}/tests/controllers/TestControllerTest.php (100%) rename {www/php => php}/tests/models/UsersModelTest.php (100%) rename {www/php => php}/tests/run-local-phpunit.php (90%) rename {www/php => php}/tests/unit/FrameworkBootstrapTest.php (100%) rename {www => php/tools}/phpminiadmin.php (100%) create mode 100644 php/tools/phpminiconfig.php rename {www/php => php}/vendor/autoload.php (100%) rename {www/php => php}/vendor/composer/ClassLoader.php (100%) rename {www/php => php}/vendor/composer/InstalledVersions.php (100%) rename {www/php => php}/vendor/composer/LICENSE (100%) rename {www/php => php}/vendor/composer/autoload_classmap.php (100%) rename {www/php => php}/vendor/composer/autoload_namespaces.php (100%) rename {www/php => php}/vendor/composer/autoload_psr4.php (100%) rename {www/php => php}/vendor/composer/autoload_real.php (100%) rename {www/php => php}/vendor/composer/autoload_static.php (100%) rename {www/php => php}/vendor/composer/installed.json (100%) rename {www/php => php}/vendor/composer/installed.php (100%) rename {www/php => php}/vendor/composer/platform_check.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/.editorconfig (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/COMMITMENT (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/LICENSE (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/README.md (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/SECURITY.md (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/VERSION (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/composer.json (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/get_oauth_token.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-ar.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-ba.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-da.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-el.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-eo.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-fo.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-gl.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-hy.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-ms.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-nl.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-uk.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/src/DSNConfigurator.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/src/Exception.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/src/OAuth.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/src/PHPMailer.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/src/POP3.php (100%) rename {www/php => php}/vendor/phpmailer/phpmailer/src/SMTP.php (100%) rename {www/template => template}/admin/activitylogs/url.html (100%) rename {www/template => template}/admin/att/index/list_filter_more.html (100%) rename {www/template => template}/admin/att/index/list_table.html (100%) rename {www/template => template}/admin/att/index/main.html (100%) rename {www/template => template}/admin/att/index/onload.js (100%) rename {www/template => template}/admin/att/index/quick_upload.html (100%) rename {www/template => template}/admin/att/index/row_click_url.html (100%) rename {www/template => template}/admin/att/index/title.html (100%) rename {www/template => template}/admin/att/select/main.html (100%) rename {www/template => template}/admin/att/select/modal.html (100%) rename {www/template => template}/admin/att/select/onload.js (100%) rename {www/template => template}/admin/att/select/title.html (100%) rename {www/template => template}/admin/att/showform/form.html (100%) rename {www/template => template}/admin/att/showform/form_left.html (100%) rename {www/template => template}/admin/att/showform/form_right.html (100%) rename {www/template => template}/admin/att/showform/main.html (100%) rename {www/template => template}/admin/att/showform/title.html (100%) rename {www/template => template}/admin/att/status.sel (100%) rename {www/template => template}/admin/att/url.html (100%) rename {www/template => template}/admin/db/index/a_end.html (100%) rename {www/template => template}/admin/db/index/main.html (100%) rename {www/template => template}/admin/db/index/results.html (100%) rename {www/template => template}/admin/db/index/select_link.html (100%) rename {www/template => template}/admin/db/index/title.html (100%) rename {www/template => template}/admin/db/index/url.html (100%) rename {www/template => template}/admin/db/url.html (100%) rename {www/template => template}/admin/demodicts/index/list_filter_more.html (100%) rename {www/template => template}/admin/demodicts/index/list_table.html (100%) rename {www/template => template}/admin/demodicts/index/main.html (100%) rename {www/template => template}/admin/demodicts/index/row_click_url.html (100%) rename {www/template => template}/admin/demodicts/index/title.html (100%) rename {www/template => template}/admin/demodicts/showform/form.html (100%) rename {www/template => template}/admin/demodicts/showform/main.html (100%) rename {www/template => template}/admin/demodicts/showform/title.html (100%) rename {www/template => template}/admin/demodicts/url.html (100%) rename {www/template => template}/admin/demos/index/btn_multidel_more.html (100%) rename {www/template => template}/admin/demos/index/list_filter_more.html (100%) rename {www/template => template}/admin/demos/index/list_table.html (100%) rename {www/template => template}/admin/demos/index/load_script.html (100%) rename {www/template => template}/admin/demos/index/main.html (100%) rename {www/template => template}/admin/demos/index/onload.js (100%) rename {www/template => template}/admin/demos/index/return_url.html (100%) rename {www/template => template}/admin/demos/index/row_click_url.html (100%) rename {www/template => template}/admin/demos/index/title.html (100%) rename {www/template => template}/admin/demos/index/userlist_entity.html (100%) rename {www/template => template}/admin/demos/show/btn_std_more.html (100%) rename {www/template => template}/admin/demos/show/form.html (100%) rename {www/template => template}/admin/demos/show/form_left.html (100%) rename {www/template => template}/admin/demos/show/form_right.html (100%) rename {www/template => template}/admin/demos/show/load_script.html (100%) rename {www/template => template}/admin/demos/show/main.html (100%) rename {www/template => template}/admin/demos/show/onload.js (100%) rename {www/template => template}/admin/demos/show/return_url.html (100%) rename {www/template => template}/admin/demos/show/title.html (100%) rename {www/template => template}/admin/demos/show/userlist_entity.html (100%) rename {www/template => template}/admin/demos/showform/btn_std_more.html (100%) rename {www/template => template}/admin/demos/showform/form.html (100%) rename {www/template => template}/admin/demos/showform/form_left.html (100%) rename {www/template => template}/admin/demos/showform/form_right.html (100%) rename {www/template => template}/admin/demos/showform/load_script.html (100%) rename {www/template => template}/admin/demos/showform/main.html (100%) rename {www/template => template}/admin/demos/showform/onload.js (100%) rename {www/template => template}/admin/demos/showform/title.html (100%) rename {www/template => template}/admin/demos/url.html (100%) rename {www/template => template}/admin/demosdynamic/config.json (100%) rename {www/template => template}/admin/demosdynamic/index/btn_multidel_more.html (100%) rename {www/template => template}/admin/demosdynamic/index/col_custom.html (100%) rename {www/template => template}/admin/demosdynamic/index/list_filter_more.html (100%) rename {www/template => template}/admin/demosdynamic/index/list_row_btn.html (100%) rename {www/template => template}/admin/demosdynamic/index/list_table.html (100%) rename {www/template => template}/admin/demosdynamic/index/load_script.html (100%) rename {www/template => template}/admin/demosdynamic/index/main.html (100%) rename {www/template => template}/admin/demosdynamic/index/onload.js (100%) rename {www/template => template}/admin/demosdynamic/index/return_url.html (100%) rename {www/template => template}/admin/demosdynamic/index/row_click_url.html (100%) rename {www/template => template}/admin/demosdynamic/index/title.html (100%) rename {www/template => template}/admin/demosdynamic/index/userlist_entity.html (100%) rename {www/template => template}/admin/demosdynamic/show/btn_std_more.html (100%) rename {www/template => template}/admin/demosdynamic/show/custom_field.html (100%) rename {www/template => template}/admin/demosdynamic/show/form.html (100%) rename {www/template => template}/admin/demosdynamic/show/load_script.html (100%) rename {www/template => template}/admin/demosdynamic/show/main.html (100%) rename {www/template => template}/admin/demosdynamic/show/onload.js (100%) rename {www/template => template}/admin/demosdynamic/show/return_url.html (100%) rename {www/template => template}/admin/demosdynamic/show/subtable.html (100%) rename {www/template => template}/admin/demosdynamic/show/subtable_demos_items.html (100%) rename {www/template => template}/admin/demosdynamic/show/title.html (100%) rename {www/template => template}/admin/demosdynamic/show/userlist_entity.html (100%) rename {www/template => template}/admin/demosdynamic/showform/btn_std_more.html (100%) rename {www/template => template}/admin/demosdynamic/showform/form.html (100%) rename {www/template => template}/admin/demosdynamic/showform/load_script.html (100%) rename {www/template => template}/admin/demosdynamic/showform/main.html (100%) rename {www/template => template}/admin/demosdynamic/showform/onload.js (100%) rename {www/template => template}/admin/demosdynamic/showform/subtable.html (100%) rename {www/template => template}/admin/demosdynamic/showform/subtable_demos_items.html (100%) rename {www/template => template}/admin/demosdynamic/showform/title.html (100%) rename {www/template => template}/admin/demosdynamic/url.html (100%) rename {www/template => template}/admin/demosvue/config.json (100%) rename {www/template => template}/admin/demosvue/index/app.js (100%) rename {www/template => template}/admin/demosvue/index/main.html (100%) rename {www/template => template}/admin/demosvue/index/store.js (100%) rename {www/template => template}/admin/demosvue/index/title.html (100%) rename {www/template => template}/admin/demosvue/index/vue/subtable_demos_items.html (100%) rename {www/template => template}/admin/demosvue/index/vue_components.html (100%) rename {www/template => template}/admin/demosvue/url.html (100%) rename {www/template => template}/admin/fwupdates/config.json (100%) rename {www/template => template}/admin/lookups/index/head.css (100%) rename {www/template => template}/admin/lookups/index/list_table.html (100%) rename {www/template => template}/admin/lookups/index/main.html (100%) rename {www/template => template}/admin/lookups/index/row_click_url.html (100%) rename {www/template => template}/admin/lookups/index/title.html (100%) rename {www/template => template}/admin/lookups/url.html (100%) rename {www/template => template}/admin/reports/common/btn_apply_print.html (100%) rename {www/template => template}/admin/reports/common/btn_download.html (100%) rename {www/template => template}/admin/reports/common/btn_download2.html (100%) rename {www/template => template}/admin/reports/common/btn_download3.html (100%) rename {www/template => template}/admin/reports/common/btn_reset.html (100%) rename {www/template => template}/admin/reports/common/ctr.html (100%) rename {www/template => template}/admin/reports/common/docx.html (100%) rename {www/template => template}/admin/reports/common/filter_buttons.html (100%) rename {www/template => template}/admin/reports/common/filter_buttons2.html (100%) rename {www/template => template}/admin/reports/common/filter_buttons3.html (100%) rename {www/template => template}/admin/reports/common/filter_dates.html (100%) rename {www/template => template}/admin/reports/common/filter_year.html (100%) rename {www/template => template}/admin/reports/common/head.css (100%) rename {www/template => template}/admin/reports/common/onload.js (100%) rename {www/template => template}/admin/reports/common/pdf.html (100%) rename {www/template => template}/admin/reports/common/xls.html (100%) rename {www/template => template}/admin/reports/index/main.html (100%) rename {www/template => template}/admin/reports/index/title.html (100%) rename {www/template => template}/admin/reports/sample/head.css (100%) rename {www/template => template}/admin/reports/sample/list_filter.html (100%) rename {www/template => template}/admin/reports/sample/load_script.html (100%) rename {www/template => template}/admin/reports/sample/main.html (100%) rename {www/template => template}/admin/reports/sample/onload.js (100%) rename {www/template => template}/admin/reports/sample/report_html.html (100%) rename {www/template => template}/admin/reports/sample/title.html (100%) rename {www/template => template}/admin/reports/url.html (100%) rename {www/template => template}/admin/roles/config.json (100%) rename {www/template => template}/admin/roles/index/btn_multidel_more.html (100%) rename {www/template => template}/admin/roles/index/col_custom.html (100%) rename {www/template => template}/admin/roles/index/list_filter_more.html (100%) rename {www/template => template}/admin/roles/index/list_row_btn.html (100%) rename {www/template => template}/admin/roles/index/list_table.html (100%) rename {www/template => template}/admin/roles/index/load_script.html (100%) rename {www/template => template}/admin/roles/index/main.html (100%) rename {www/template => template}/admin/roles/index/onload.js (100%) rename {www/template => template}/admin/roles/index/return_url.html (100%) rename {www/template => template}/admin/roles/index/row_click_url.html (100%) rename {www/template => template}/admin/roles/index/title.html (100%) rename {www/template => template}/admin/roles/index/userlist_entity.html (100%) rename {www/template => template}/admin/roles/show/btn_std_more.html (100%) rename {www/template => template}/admin/roles/show/form.html (100%) rename {www/template => template}/admin/roles/show/load_script.html (100%) rename {www/template => template}/admin/roles/show/main.html (100%) rename {www/template => template}/admin/roles/show/onload.js (100%) rename {www/template => template}/admin/roles/show/return_url.html (100%) rename {www/template => template}/admin/roles/show/roles_resources_permissions.html (100%) rename {www/template => template}/admin/roles/show/title.html (100%) rename {www/template => template}/admin/roles/show/userlist_entity.html (100%) rename {www/template => template}/admin/roles/showform/btn_std_more.html (100%) rename {www/template => template}/admin/roles/showform/form.html (100%) rename {www/template => template}/admin/roles/showform/load_script.html (100%) rename {www/template => template}/admin/roles/showform/main.html (100%) rename {www/template => template}/admin/roles/showform/onload.js (100%) rename {www/template => template}/admin/roles/showform/roles_resources_permissions.html (100%) rename {www/template => template}/admin/roles/showform/title.html (100%) rename {www/template => template}/admin/roles/url.html (100%) rename {www/template => template}/admin/sendemail/save/main.html (100%) rename {www/template => template}/admin/sendemail/save/title.html (100%) rename {www/template => template}/admin/sendemail/showform/btn_std_more.html (100%) rename {www/template => template}/admin/sendemail/showform/form.html (100%) rename {www/template => template}/admin/sendemail/showform/form_left.html (100%) rename {www/template => template}/admin/sendemail/showform/form_right.html (100%) rename {www/template => template}/admin/sendemail/showform/load_script.html (100%) rename {www/template => template}/admin/sendemail/showform/main.html (100%) rename {www/template => template}/admin/sendemail/showform/onload.js (100%) rename {www/template => template}/admin/sendemail/showform/title.html (100%) rename {www/template => template}/admin/sendemail/url.html (100%) rename {www/template => template}/admin/settings/index/list_table.html (100%) rename {www/template => template}/admin/settings/index/main.html (100%) rename {www/template => template}/admin/settings/index/row_click_url.html (100%) rename {www/template => template}/admin/settings/index/title.html (100%) rename {www/template => template}/admin/settings/showform/form.html (100%) rename {www/template => template}/admin/settings/showform/input_checkbox.html (100%) rename {www/template => template}/admin/settings/showform/input_date.html (100%) rename {www/template => template}/admin/settings/showform/input_input.html (100%) rename {www/template => template}/admin/settings/showform/input_radio.html (100%) rename {www/template => template}/admin/settings/showform/input_select.html (100%) rename {www/template => template}/admin/settings/showform/input_selectmulti.html (100%) rename {www/template => template}/admin/settings/showform/input_textarea.html (100%) rename {www/template => template}/admin/settings/showform/load_script.html (100%) rename {www/template => template}/admin/settings/showform/main.html (100%) rename {www/template => template}/admin/settings/showform/title.html (100%) rename {www/template => template}/admin/settings/url.html (100%) rename {www/template => template}/admin/spages/index/list_filter_more.html (100%) rename {www/template => template}/admin/spages/index/list_table.html (100%) rename {www/template => template}/admin/spages/index/main.html (100%) rename {www/template => template}/admin/spages/index/row_click_url.html (100%) rename {www/template => template}/admin/spages/index/title.html (100%) rename {www/template => template}/admin/spages/showform/form.html (100%) rename {www/template => template}/admin/spages/showform/load_script.html (100%) rename {www/template => template}/admin/spages/showform/main.html (100%) rename {www/template => template}/admin/spages/showform/onload.js (100%) rename {www/template => template}/admin/spages/showform/page_content.html (100%) rename {www/template => template}/admin/spages/showform/page_settings.html (100%) rename {www/template => template}/admin/spages/showform/title.html (100%) rename {www/template => template}/admin/spages/status.sel (100%) rename {www/template => template}/admin/spages/template.sel (100%) rename {www/template => template}/admin/spages/url.html (100%) rename {www/template => template}/admin/users/config.json (100%) rename {www/template => template}/admin/users/index/btn_std_more.html (100%) rename {www/template => template}/admin/users/index/col_custom.html (100%) rename {www/template => template}/admin/users/index/col_is_mfa.html (100%) rename {www/template => template}/admin/users/index/col_status.html (100%) rename {www/template => template}/admin/users/index/list_filter_more.html (100%) rename {www/template => template}/admin/users/index/list_row_btn.html (100%) rename {www/template => template}/admin/users/index/list_table.html (100%) rename {www/template => template}/admin/users/index/load_script.html (100%) rename {www/template => template}/admin/users/index/main.html (100%) rename {www/template => template}/admin/users/index/onload.js (100%) rename {www/template => template}/admin/users/index/return_url.html (100%) rename {www/template => template}/admin/users/index/row_click_url.html (100%) rename {www/template => template}/admin/users/index/title.html (100%) rename {www/template => template}/admin/users/show/btn_std_more.html (100%) rename {www/template => template}/admin/users/show/form.html (100%) rename {www/template => template}/admin/users/show/main.html (100%) rename {www/template => template}/admin/users/show/onload.js (100%) rename {www/template => template}/admin/users/show/return_url.html (100%) rename {www/template => template}/admin/users/show/title.html (100%) rename {www/template => template}/admin/users/showform/btn_std_more.html (100%) rename {www/template => template}/admin/users/showform/form.html (100%) rename {www/template => template}/admin/users/showform/form_left.html (100%) rename {www/template => template}/admin/users/showform/form_status.html (100%) rename {www/template => template}/admin/users/showform/load_script.html (100%) rename {www/template => template}/admin/users/showform/main.html (100%) rename {www/template => template}/admin/users/showform/onload.js (100%) rename {www/template => template}/admin/users/showform/roles_block.html (100%) rename {www/template => template}/admin/users/showform/title.html (100%) rename {www/template => template}/admin/users/status.sel (100%) rename {www/template => template}/admin/users/status_bgcolor.sel (100%) rename {www/template => template}/admin/users/statusf.sel (100%) rename {www/template => template}/admin/users/url.html (100%) rename {www/template => template}/att/url.html (100%) rename {www/template => template}/common/active.html (100%) rename {www/template => template}/common/activitylogs/avatar.html (100%) rename {www/template => template}/common/activitylogs/edited.html (100%) rename {www/template => template}/common/activitylogs/fields.html (100%) rename {www/template => template}/common/activitylogs/initials.html (100%) rename {www/template => template}/common/activitylogs/logtype.html (100%) rename {www/template => template}/common/activitylogs/main.html (100%) rename {www/template => template}/common/activitylogs/onload.js (100%) rename {www/template => template}/common/ajaxform.html (100%) rename {www/template => template}/common/att.html (100%) rename {www/template => template}/common/autocomplete.html (100%) rename {www/template => template}/common/bootstrap_select.html (100%) rename {www/template => template}/common/calendar.html (100%) rename {www/template => template}/common/char/darr.html (100%) rename {www/template => template}/common/char/del.html (100%) rename {www/template => template}/common/char/down.html (100%) rename {www/template => template}/common/char/gt.html (100%) rename {www/template => template}/common/char/larr.html (100%) rename {www/template => template}/common/char/uarr.html (100%) rename {www/template => template}/common/char/up.html (100%) rename {www/template => template}/common/checked.html (100%) rename {www/template => template}/common/clactive.html (100%) rename {www/template => template}/common/comma.html (100%) rename {www/template => template}/common/delim.html (100%) rename {www/template => template}/common/disabled.html (100%) rename {www/template => template}/common/disabledcl.html (100%) rename {www/template => template}/common/display_none.html (100%) rename {www/template => template}/common/dot.html (100%) rename {www/template => template}/common/error.html (100%) rename {www/template => template}/common/even.html (100%) rename {www/template => template}/common/form/actions_std.html (100%) rename {www/template => template}/common/form/actions_std_new.html (100%) rename {www/template => template}/common/form/actions_std_responsive.html (100%) rename {www/template => template}/common/form/added.html (100%) rename {www/template => template}/common/form/btn_del.html (100%) rename {www/template => template}/common/form/btn_edit.html (100%) rename {www/template => template}/common/form/btn_std.html (100%) rename {www/template => template}/common/form/btn_top_save.html (100%) rename {www/template => template}/common/form/btn_top_save_addnew.html (100%) rename {www/template => template}/common/form/btn_userlists.html (100%) rename {www/template => template}/common/form/btn_view.html (100%) rename {www/template => template}/common/form/cancel_url.html (100%) rename {www/template => template}/common/form/data_autosave.html (100%) rename {www/template => template}/common/form/group_id.html (100%) rename {www/template => template}/common/form/group_id_new.html (100%) rename {www/template => template}/common/form/group_status.html (100%) rename {www/template => template}/common/form/id.html (100%) rename {www/template => template}/common/form/idnew.html (100%) rename {www/template => template}/common/form/msg.html (100%) rename {www/template => template}/common/form/nav_title.html (100%) rename {www/template => template}/common/form/nav_title_edit.html (100%) rename {www/template => template}/common/form/prev_next.html (100%) rename {www/template => template}/common/form/prev_next_edit.html (100%) rename {www/template => template}/common/form/prio.html (100%) rename {www/template => template}/common/form/relid.html (100%) rename {www/template => template}/common/form/ret_url.html (100%) rename {www/template => template}/common/form/show/added.html (100%) rename {www/template => template}/common/form/show/att.html (100%) rename {www/template => template}/common/form/show/att_files.html (100%) rename {www/template => template}/common/form/show/att_links.html (100%) rename {www/template => template}/common/form/show/checkbox.html (100%) rename {www/template => template}/common/form/show/col.html (100%) rename {www/template => template}/common/form/show/col_end.html (100%) rename {www/template => template}/common/form/show/date.html (100%) rename {www/template => template}/common/form/show/date_long.html (100%) rename {www/template => template}/common/form/show/extract.html (100%) rename {www/template => template}/common/form/show/extract/checkbox.html (100%) rename {www/template => template}/common/form/show/extract/form.html (100%) rename {www/template => template}/common/form/show/extract/one_field.html (100%) rename {www/template => template}/common/form/show/extract/one_fieldgroup.html (100%) rename {www/template => template}/common/form/show/extract/one_fieldsel.html (100%) rename {www/template => template}/common/form/show/extract/value.html (100%) rename {www/template => template}/common/form/show/float.html (100%) rename {www/template => template}/common/form/show/help_block.html (100%) rename {www/template => template}/common/form/show/help_block_label.html (100%) rename {www/template => template}/common/form/show/help_block_label_html.html (100%) rename {www/template => template}/common/form/show/markdown.html (100%) rename {www/template => template}/common/form/show/multi.html (100%) rename {www/template => template}/common/form/show/noescape.html (100%) rename {www/template => template}/common/form/show/one_field.html (100%) rename {www/template => template}/common/form/show/one_fieldgroup.html (100%) rename {www/template => template}/common/form/show/one_fieldsel.html (100%) rename {www/template => template}/common/form/show/one_structure.html (100%) rename {www/template => template}/common/form/show/plaintext.html (100%) rename {www/template => template}/common/form/show/plaintext_autocomplete.html (100%) rename {www/template => template}/common/form/show/plaintext_json.html (100%) rename {www/template => template}/common/form/show/plaintext_link.html (100%) rename {www/template => template}/common/form/show/plaintext_url.html (100%) rename {www/template => template}/common/form/show/plaintext_yesno.html (100%) rename {www/template => template}/common/form/show/row.html (100%) rename {www/template => template}/common/form/show/row_end.html (100%) rename {www/template => template}/common/form/show/subtable.html (100%) rename {www/template => template}/common/form/show/updated.html (100%) rename {www/template => template}/common/form/showdelete/actions_delete.html (100%) rename {www/template => template}/common/form/showdelete/cancel_url.html (100%) rename {www/template => template}/common/form/showdelete/delete_std.html (100%) rename {www/template => template}/common/form/showdelete/main.html (100%) rename {www/template => template}/common/form/showdelete/title.html (100%) rename {www/template => template}/common/form/showform/att.html (100%) rename {www/template => template}/common/form/showform/att_files.html (100%) rename {www/template => template}/common/form/showform/att_links.html (100%) rename {www/template => template}/common/form/showform/autocomplete.html (100%) rename {www/template => template}/common/form/showform/cb.html (100%) rename {www/template => template}/common/form/showform/cc_inline.html (100%) rename {www/template => template}/common/form/showform/col.html (100%) rename {www/template => template}/common/form/showform/col_end.html (100%) rename {www/template => template}/common/form/showform/date_popup.html (100%) rename {www/template => template}/common/form/showform/datetime_popup.html (100%) rename {www/template => template}/common/form/showform/email.html (100%) rename {www/template => template}/common/form/showform/extract/checkbox.html (100%) rename {www/template => template}/common/form/showform/extract/form.html (100%) rename {www/template => template}/common/form/showform/extract/group_id.html (100%) rename {www/template => template}/common/form/showform/extract/id.html (100%) rename {www/template => template}/common/form/showform/extract/one_field.html (100%) rename {www/template => template}/common/form/showform/extract/one_fieldgroup.html (100%) rename {www/template => template}/common/form/showform/extract/one_fieldsel.html (100%) rename {www/template => template}/common/form/showform/extract/select.html (100%) rename {www/template => template}/common/form/showform/extract/select_np.html (100%) rename {www/template => template}/common/form/showform/extract/value.html (100%) rename {www/template => template}/common/form/showform/group_id.html (100%) rename {www/template => template}/common/form/showform/group_id_addnew.html (100%) rename {www/template => template}/common/form/showform/help_block.html (100%) rename {www/template => template}/common/form/showform/help_block_label.html (100%) rename {www/template => template}/common/form/showform/help_block_label_html.html (100%) rename {www/template => template}/common/form/showform/id.html (100%) rename {www/template => template}/common/form/showform/input.html (100%) rename {www/template => template}/common/form/showform/max.html (100%) rename {www/template => template}/common/form/showform/maxlength.html (100%) rename {www/template => template}/common/form/showform/min.html (100%) rename {www/template => template}/common/form/showform/multi.html (100%) rename {www/template => template}/common/form/showform/multi_prio.html (100%) rename {www/template => template}/common/form/showform/number.html (100%) rename {www/template => template}/common/form/showform/one_field.html (100%) rename {www/template => template}/common/form/showform/one_fieldgroup.html (100%) rename {www/template => template}/common/form/showform/one_fieldsel.html (100%) rename {www/template => template}/common/form/showform/one_structure.html (100%) rename {www/template => template}/common/form/showform/password.html (100%) rename {www/template => template}/common/form/showform/placeholder.html (100%) rename {www/template => template}/common/form/showform/radio.html (100%) rename {www/template => template}/common/form/showform/row.html (100%) rename {www/template => template}/common/form/showform/row_end.html (100%) rename {www/template => template}/common/form/showform/rows.html (100%) rename {www/template => template}/common/form/showform/select.html (100%) rename {www/template => template}/common/form/showform/step.html (100%) rename {www/template => template}/common/form/showform/subtable.html (100%) rename {www/template => template}/common/form/showform/textarea.html (100%) rename {www/template => template}/common/form/showform/time.html (100%) rename {www/template => template}/common/form/showform/validation_errors.html (100%) rename {www/template => template}/common/form/showform/yesno.html (100%) rename {www/template => template}/common/form/status.html (100%) rename {www/template => template}/common/form/tabs.html (100%) rename {www/template => template}/common/form/updated.html (100%) rename {www/template => template}/common/form/url_edit.html (100%) rename {www/template => template}/common/hide.html (100%) rename {www/template => template}/common/html_editor.html (100%) rename {www/template => template}/common/html_editor_local.html (100%) rename {www/template => template}/common/icons/plusb.html (100%) rename {www/template => template}/common/invalid.html (100%) rename {www/template => template}/common/list/btn_multidel.html (100%) rename {www/template => template}/common/list/btn_std.html (100%) rename {www/template => template}/common/list/btn_userfilters.html (100%) rename {www/template => template}/common/list/btn_userlists.html (100%) rename {www/template => template}/common/list/col_iname.html (100%) rename {www/template => template}/common/list/col_prio.html (100%) rename {www/template => template}/common/list/col_status.html (100%) rename {www/template => template}/common/list/col_status_badge.html (100%) rename {www/template => template}/common/list/empty.html (100%) rename {www/template => template}/common/list/export/rows.html (100%) rename {www/template => template}/common/list/export/xls.html (100%) rename {www/template => template}/common/list/export/xls_foot.html (100%) rename {www/template => template}/common/list/export/xls_head.html (100%) rename {www/template => template}/common/list/export/xls_rows.html (100%) rename {www/template => template}/common/list/fexport.html (100%) rename {www/template => template}/common/list/filter_search.html (100%) rename {www/template => template}/common/list/filter_std.html (100%) rename {www/template => template}/common/list/filter_std_status.html (100%) rename {www/template => template}/common/list/filter_std_status_export.html (100%) rename {www/template => template}/common/list/filter_std_su.html (100%) rename {www/template => template}/common/list/form_delete.html (100%) rename {www/template => template}/common/list/form_list.html (100%) rename {www/template => template}/common/list/fsearch.html (100%) rename {www/template => template}/common/list/fstatus.html (100%) rename {www/template => template}/common/list/fuserlists.html (100%) rename {www/template => template}/common/list/fwsortable.html (100%) rename {www/template => template}/common/list/list_row_td_btn.html (100%) rename {www/template => template}/common/list/pagination.html (100%) rename {www/template => template}/common/list/pagination_always.html (100%) rename {www/template => template}/common/list/relid.html (100%) rename {www/template => template}/common/list/relidq.html (100%) rename {www/template => template}/common/list/reset_filter_relid.html (100%) rename {www/template => template}/common/list/row_btn_del.html (100%) rename {www/template => template}/common/list/row_btn_edit.html (100%) rename {www/template => template}/common/list/row_btn_std.html (100%) rename {www/template => template}/common/list/row_btn_view.html (100%) rename {www/template => template}/common/list/table_under.html (100%) rename {www/template => template}/common/list/tbody.html (100%) rename {www/template => template}/common/list/th_chkall.html (100%) rename {www/template => template}/common/list/th_empty.html (100%) rename {www/template => template}/common/list/th_filter_customize.html (100%) rename {www/template => template}/common/list/thead.html (100%) rename {www/template => template}/common/list/userviews/main.html (100%) rename {www/template => template}/common/list/userviews/modal.html (100%) rename {www/template => template}/common/list/userviews/onload.js (100%) rename {www/template => template}/common/list/userviews/title.html (100%) rename {www/template => template}/common/markdown_editor.html (100%) rename {www/template => template}/common/multiple.html (100%) rename {www/template => template}/common/odd.html (100%) rename {www/template => template}/common/readonly.html (100%) rename {www/template => template}/common/redirect_js.html (100%) rename {www/template => template}/common/required.html (100%) rename {www/template => template}/common/sel/access_level.sel (100%) rename {www/template => template}/common/sel/added_since.sel (100%) rename {www/template => template}/common/sel/country.sel (100%) rename {www/template => template}/common/sel/date_day.sel (100%) rename {www/template => template}/common/sel/date_mon.sel (100%) rename {www/template => template}/common/sel/date_year.sel (100%) rename {www/template => template}/common/sel/fcombo.sel (100%) rename {www/template => template}/common/sel/gender.sel (100%) rename {www/template => template}/common/sel/lang.sel (100%) rename {www/template => template}/common/sel/pagesize.sel (100%) rename {www/template => template}/common/sel/sortby.sel (100%) rename {www/template => template}/common/sel/state.sel (100%) rename {www/template => template}/common/sel/status.sel (100%) rename {www/template => template}/common/sel/status3.sel (100%) rename {www/template => template}/common/sel/status_bgcolor.sel (100%) rename {www/template => template}/common/sel/statusf.sel (100%) rename {www/template => template}/common/sel/statusf_admin.sel (100%) rename {www/template => template}/common/sel/ui_mode.sel (100%) rename {www/template => template}/common/sel/ui_mode_data.sel (100%) rename {www/template => template}/common/sel/ui_theme.sel (100%) rename {www/template => template}/common/sel/yn.sel (100%) rename {www/template => template}/common/sel/yn_bool.sel (100%) rename {www/template => template}/common/sel/yn_char.sel (100%) rename {www/template => template}/common/sel/yn_char_rev.sel (100%) rename {www/template => template}/common/selected.html (100%) rename {www/template => template}/common/selected0.html (100%) rename {www/template => template}/common/sortable.html (100%) rename {www/template => template}/common/text_end.html (100%) rename {www/template => template}/common/uploader.html (100%) rename {www/template => template}/common/virtual/index/app.js (100%) rename {www/template => template}/common/virtual/index/main.html (100%) rename {www/template => template}/common/virtual/index/store.js (100%) rename {www/template => template}/common/virtual/index/title.html (100%) rename {www/template => template}/common/virtual/index/vue/subtable_demos_items.html (100%) rename {www/template => template}/common/virtual/index/vue_components.html (100%) rename {www/template => template}/common/virtual/url.html (100%) rename {www/template => template}/common/vue/_sample.html (100%) rename {www/template => template}/common/vue/activity-logs.html (100%) rename {www/template => template}/common/vue/app.js (100%) rename {www/template => template}/common/vue/att-select.html (100%) rename {www/template => template}/common/vue/autocomplete.html (100%) rename {www/template => template}/common/vue/date-picker.html (100%) rename {www/template => template}/common/vue/edit-form.html (100%) rename {www/template => template}/common/vue/edit-header.html (100%) rename {www/template => template}/common/vue/form-control-help-block.html (100%) rename {www/template => template}/common/vue/form-one-col.html (100%) rename {www/template => template}/common/vue/form-one-control.html (100%) rename {www/template => template}/common/vue/form-one-def.html (100%) rename {www/template => template}/common/vue/form-one-form-row.html (100%) rename {www/template => template}/common/vue/form-one-group.html (100%) rename {www/template => template}/common/vue/form-one-row.html (100%) rename {www/template => template}/common/vue/list-btn-multi.html (100%) rename {www/template => template}/common/vue/list-btn-userlists.html (100%) rename {www/template => template}/common/vue/list-cell-checkbox.html (100%) rename {www/template => template}/common/vue/list-cell-input.html (100%) rename {www/template => template}/common/vue/list-cell-ro.html (100%) rename {www/template => template}/common/vue/list-cell-select.html (100%) rename {www/template => template}/common/vue/list-customize-columns.html (100%) rename {www/template => template}/common/vue/list-edit-pane.html (100%) rename {www/template => template}/common/vue/list-filters-more.html (100%) rename {www/template => template}/common/vue/list-filters-table-btn.html (100%) rename {www/template => template}/common/vue/list-filters.html (100%) rename {www/template => template}/common/vue/list-header.html (100%) rename {www/template => template}/common/vue/list-pagination.html (100%) rename {www/template => template}/common/vue/list-row-btn.html (100%) rename {www/template => template}/common/vue/list-table.html (100%) rename {www/template => template}/common/vue/screens.html (100%) rename {www/template => template}/common/vue/store.js (100%) rename {www/template => template}/common/vue/view-form.html (100%) rename {www/template => template}/common/vue/view-header.html (100%) rename {www/template => template}/contact/index/form.html (100%) rename {www/template => template}/contact/index/head.css (100%) rename {www/template => template}/contact/index/head_script.html (100%) rename {www/template => template}/contact/index/main.html (100%) rename {www/template => template}/contact/index/sub_header.html (100%) rename {www/template => template}/contact/index/title.html (100%) rename {www/template => template}/contact/sent/main.html (100%) rename {www/template => template}/contact/sent/title.html (100%) rename {www/template => template}/contact/url.html (100%) rename {www/template => template}/dev/configure/index/fail.html (100%) rename {www/template => template}/dev/configure/index/main.html (100%) rename {www/template => template}/dev/configure/index/ok.html (100%) rename {www/template => template}/dev/configure/index/title.html (100%) rename {www/template => template}/dev/configure/index/warn.html (100%) rename {www/template => template}/dev/configure/initdb/main.html (100%) rename {www/template => template}/dev/configure/initdb/title.html (100%) rename {www/template => template}/dev/configure/url.html (100%) rename {www/template => template}/dev/manage/apitester/csp_script.html (100%) rename {www/template => template}/dev/manage/apitester/main.html (100%) rename {www/template => template}/dev/manage/apitester/onload.js (100%) rename {www/template => template}/dev/manage/apitester/title.html (100%) rename {www/template => template}/dev/manage/appcreator/main.html (100%) rename {www/template => template}/dev/manage/appcreator/onload.js (100%) rename {www/template => template}/dev/manage/appcreator/title.html (100%) rename {www/template => template}/dev/manage/dbanalyzer/main.html (100%) rename {www/template => template}/dev/manage/dbanalyzer/title.html (100%) rename {www/template => template}/dev/manage/dbinitializer/main.html (100%) rename {www/template => template}/dev/manage/dbinitializer/title.html (100%) rename {www/template => template}/dev/manage/entitybuilder/main.html (100%) rename {www/template => template}/dev/manage/entitybuilder/title.html (100%) rename {www/template => template}/dev/manage/index/ctype.sel (100%) rename {www/template => template}/dev/manage/index/main.html (100%) rename {www/template => template}/dev/manage/index/onload.js (100%) rename {www/template => template}/dev/manage/index/title.html (100%) rename {www/template => template}/dev/manage/showdbupdates/main.html (100%) rename {www/template => template}/dev/manage/showdbupdates/title.html (100%) rename {www/template => template}/dev/manage/url.html (100%) rename {www/template => template}/dev/url.html (100%) rename {www/template => template}/emails/email_actcode.txt (100%) rename {www/template => template}/emails/email_confirm.txt (100%) rename {www/template => template}/emails/email_invite.txt (100%) rename {www/template => template}/emails/email_pwd.txt (100%) rename {www/template => template}/emails/feedback.txt (100%) rename {www/template => template}/emails/sign_en.txt (100%) rename {www/template => template}/emails/signup.txt (100%) rename {www/template => template}/error/404/main.html (100%) rename {www/template => template}/error/404/title.html (100%) rename {www/template => template}/error/4xx/main.html (100%) rename {www/template => template}/error/4xx/title.html (100%) rename {www/template => template}/error/main.html (100%) rename {www/template => template}/error/onload.js (100%) rename {www/template => template}/files/url.html (100%) rename {www/template => template}/home/about/head.css (100%) rename {www/template => template}/home/about/main.html (100%) rename {www/template => template}/home/about/sub_header.html (100%) rename {www/template => template}/home/about/title.html (100%) rename {www/template => template}/home/index/head.css (100%) rename {www/template => template}/home/index/main.html (100%) rename {www/template => template}/home/index/onload.js (100%) rename {www/template => template}/home/index/sub_header.html (100%) rename {www/template => template}/home/spage/head.css (100%) rename {www/template => template}/home/spage/head.js (100%) rename {www/template => template}/home/spage/head_script.html (100%) rename {www/template => template}/home/spage/main.html (100%) rename {www/template => template}/home/spage/one_col.html (100%) rename {www/template => template}/home/spage/sidebar.html (100%) rename {www/template => template}/home/spage/three_col.html (100%) rename {www/template => template}/home/spage/title.html (100%) rename {www/template => template}/home/spage/two_col.html (100%) rename {www/template => template}/home/spage/two_left_col.html (100%) rename {www/template => template}/home/spage/two_right_col.html (100%) rename {www/template => template}/home/url.html (100%) rename {www/template => template}/lang/de.txt (100%) rename {www/template => template}/lang/en.txt (100%) rename {www/template => template}/lang/ru.txt (100%) rename {www/template => template}/lang/ua.txt (100%) rename {www/template => template}/lang/zh.txt (100%) rename {www/template => template}/layout.html (100%) rename {www/template => template}/layout/admin.js (100%) rename {www/template => template}/layout/beta.html (100%) rename {www/template => template}/layout/common.js (100%) rename {www/template => template}/layout/content_security_policy.html (100%) rename {www/template => template}/layout/counters/ga.html (100%) rename {www/template => template}/layout/footer.html (100%) rename {www/template => template}/layout/head_common.js (100%) rename {www/template => template}/layout/header.html (100%) rename {www/template => template}/layout/header_public.html (100%) rename {www/template => template}/layout/main_no_sidebar.html (100%) rename {www/template => template}/layout/main_with_sidebar.html (100%) rename {www/template => template}/layout/sidebar.html (100%) rename {www/template => template}/layout/sidebar_my.html (100%) rename {www/template => template}/layout/sys_footer.html (100%) rename {www/template => template}/layout/sys_footer_print.html (100%) rename {www/template => template}/layout/sys_head.html (100%) rename {www/template => template}/layout/sys_head_print.html (100%) rename {www/template => template}/layout/theme_link.html (100%) rename {www/template => template}/layout/ui_mode.html (100%) rename {www/template => template}/layout/user_avatar.html (100%) rename {www/template => template}/layout/vue/apputils.js (100%) rename {www/template => template}/layout/vue/common.js (100%) rename {www/template => template}/layout/vue/importmaps.html (100%) rename {www/template => template}/layout/vue/sys_footer.html (100%) rename {www/template => template}/layout/vue/sys_head.html (100%) rename {www/template => template}/layout_email.html (100%) rename {www/template => template}/layout_min.html (100%) rename {www/template => template}/layout_pjax.html (100%) rename {www/template => template}/layout_pjax_nojs.html (100%) rename {www/template => template}/layout_print.html (100%) rename {www/template => template}/layout_public.html (100%) rename {www/template => template}/layout_vue.html (100%) rename {www/template => template}/login/index/form.html (100%) rename {www/template => template}/login/index/form_logged.html (100%) rename {www/template => template}/login/index/google.html (100%) rename {www/template => template}/login/index/head.css (100%) rename {www/template => template}/login/index/login_problem.html (100%) rename {www/template => template}/login/index/main.html (100%) rename {www/template => template}/login/index/onload.js (100%) rename {www/template => template}/login/index/title.html (100%) rename {www/template => template}/login/mfa/form.html (100%) rename {www/template => template}/login/mfa/head.css (100%) rename {www/template => template}/login/mfa/main.html (100%) rename {www/template => template}/login/mfa/title.html (100%) rename {www/template => template}/login/url.html (100%) rename {www/template => template}/login/url_logoff.html (100%) rename {www/template => template}/main/index/head.css (100%) rename {www/template => template}/main/index/head.js (100%) rename {www/template => template}/main/index/load_script.html (100%) rename {www/template => template}/main/index/main.html (100%) rename {www/template => template}/main/index/onload.js (100%) rename {www/template => template}/main/index/pane_title.html (100%) rename {www/template => template}/main/index/std_pane.html (100%) rename {www/template => template}/main/index/theme1.js (100%) rename {www/template => template}/main/index/theme2.js (100%) rename {www/template => template}/main/index/theme30.js (100%) rename {www/template => template}/main/index/title.html (100%) rename {www/template => template}/main/index/type_barchart.html (100%) rename {www/template => template}/main/index/type_html.html (100%) rename {www/template => template}/main/index/type_linechart.html (100%) rename {www/template => template}/main/index/type_piechart.html (100%) rename {www/template => template}/main/index/type_table.html (100%) rename {www/template => template}/main/url.html (100%) rename {www/template => template}/my/feedback/showform/modal.html (100%) rename {www/template => template}/my/feedback/showform/title.html (100%) rename {www/template => template}/my/feedback/url.html (100%) rename {www/template => template}/my/filters/create/modal.html (100%) rename {www/template => template}/my/filters/create/title.html (100%) rename {www/template => template}/my/filters/entities.sel (100%) rename {www/template => template}/my/filters/index/list_filter_more.html (100%) rename {www/template => template}/my/filters/index/list_table.html (100%) rename {www/template => template}/my/filters/index/main.html (100%) rename {www/template => template}/my/filters/index/row_click_url.html (100%) rename {www/template => template}/my/filters/index/title.html (100%) rename {www/template => template}/my/filters/showform/form.html (100%) rename {www/template => template}/my/filters/showform/main.html (100%) rename {www/template => template}/my/filters/showform/title.html (100%) rename {www/template => template}/my/filters/url.html (100%) rename {www/template => template}/my/lists/create/modal.html (100%) rename {www/template => template}/my/lists/create/title.html (100%) rename {www/template => template}/my/lists/entities.sel (100%) rename {www/template => template}/my/lists/index/list_filter_more.html (100%) rename {www/template => template}/my/lists/index/list_table.html (100%) rename {www/template => template}/my/lists/index/main.html (100%) rename {www/template => template}/my/lists/index/row_click_url.html (100%) rename {www/template => template}/my/lists/index/title.html (100%) rename {www/template => template}/my/lists/showform/form.html (100%) rename {www/template => template}/my/lists/showform/main.html (100%) rename {www/template => template}/my/lists/showform/title.html (100%) rename {www/template => template}/my/lists/url.html (100%) rename {www/template => template}/my/mfa/save/main.html (100%) rename {www/template => template}/my/mfa/save/onload.js (100%) rename {www/template => template}/my/mfa/save/title.html (100%) rename {www/template => template}/my/mfa/showform/main.html (100%) rename {www/template => template}/my/mfa/showform/title.html (100%) rename {www/template => template}/my/mfa/url.html (100%) rename {www/template => template}/my/password/showform/form.html (100%) rename {www/template => template}/my/password/showform/main.html (100%) rename {www/template => template}/my/password/showform/onload.js (100%) rename {www/template => template}/my/password/showform/title.html (100%) rename {www/template => template}/my/password/url.html (100%) rename {www/template => template}/my/settings/showform/form.html (100%) rename {www/template => template}/my/settings/showform/main.html (100%) rename {www/template => template}/my/settings/showform/title.html (100%) rename {www/template => template}/my/settings/url.html (100%) rename {www/template => template}/my/views/index/list_filter_more.html (100%) rename {www/template => template}/my/views/index/list_table.html (100%) rename {www/template => template}/my/views/index/main.html (100%) rename {www/template => template}/my/views/index/row_click_url.html (100%) rename {www/template => template}/my/views/index/title.html (100%) rename {www/template => template}/my/views/showform/form.html (100%) rename {www/template => template}/my/views/showform/main.html (100%) rename {www/template => template}/my/views/showform/title.html (100%) rename {www/template => template}/my/views/url.html (100%) rename {www/template => template}/password/index/main.html (100%) rename {www/template => template}/password/index/onload.js (100%) rename {www/template => template}/password/index/title.html (100%) rename {www/template => template}/password/reset/main.html (100%) rename {www/template => template}/password/reset/onload.js (100%) rename {www/template => template}/password/reset/title.html (100%) rename {www/template => template}/password/sent/main.html (100%) rename {www/template => template}/password/sent/title.html (100%) rename {www/template => template}/password/url.html (100%) rename {www/template => template}/signup/showform/form.html (100%) rename {www/template => template}/signup/showform/head.css (100%) rename {www/template => template}/signup/showform/main.html (100%) rename {www/template => template}/signup/showform/onload.js (100%) rename {www/template => template}/signup/showform/title.html (100%) rename {www/template => template}/signup/url.html (100%) rename {www/template => template}/site_name.html (100%) rename {www/template => template}/sitemap/index/children.html (100%) rename {www/template => template}/sitemap/index/head.css (100%) rename {www/template => template}/sitemap/index/main.html (100%) rename {www/template => template}/sitemap/index/pages_children.html (100%) rename {www/template => template}/sitemap/index/pages_tree.html (100%) rename {www/template => template}/sitemap/index/title.html (100%) rename {www/template => template}/sitemap/url.html (100%) rename {www/template => template}/sys/backup/index/main.html (100%) rename {www/template => template}/sys/backup/index/title.html (100%) rename {www/template => template}/sys/backup/url.html (100%) rename {www/template => template}/test/index/head.css (100%) rename {www/template => template}/test/index/main.html (100%) rename {www/template => template}/test/index/nolang_test.html (100%) rename {www/template => template}/test/index/onload.js (100%) rename {www/template => template}/test/index/subfolder/1.html (100%) rename {www/template => template}/test/index/subfolder/2.html (100%) rename {www/template => template}/test/index/subfolder/3.html (100%) rename {www/template => template}/test/index/subfolder/4.txt (100%) rename {www/template => template}/test/index/subfolder/5.txt (100%) rename {www/template => template}/test/index/title.html (100%) rename {www/template => template}/test/index/true_value.html (100%) delete mode 100644 www/php/controllers/AdminDB.php delete mode 100644 www/phpminiconfig.php diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f1c0592..1e6ee60 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -83,13 +83,15 @@ data. - `db/updates/` - dated update scripts. Pair schema changes with a matching update script. - `www/` - public web root. - `www/index.php` - web entrypoint. -- `www/php/` - PHP code, config, Composer metadata, framework core, controllers, models, hooks, and vendor files. -- `www/php/fw/` - framework core (`fw`, dispatcher, controllers, model base, DB wrapper, ParsePage, utilities). -- `www/php/controllers/` - sample/admin/app controllers built on the framework. -- `www/php/controllers/v1/` - sample versioned API controllers. -- `www/php/models/` - sample/admin models and shared framework models. -- `www/php/configs/` - default and host-local config files. Never commit secrets. -- `www/template/` - ParsePage templates and dynamic-controller `config.json` files. +- `php/` - PHP code, config, Composer metadata, framework core, controllers, models, hooks, and vendor files. +- `php/fw/` - framework core (`fw`, dispatcher, controllers, model base, DB wrapper, ParsePage, utilities). +- `php/controllers/` - sample/admin/app controllers built on the framework. +- `php/controllers/v1/` - sample versioned API controllers. +- `php/models/` - sample/admin models and shared framework models. +- `php/tools/` - non-public standalone developer/admin tools invoked through framework routes. +- `php/configs/` - default and host-local config files. Never commit secrets. +- `template/` - ParsePage templates and dynamic-controller `config.json` files. +- `upload/` - default non-public attachment storage served through framework routes. - `www/assets/` - CSS, JavaScript, images, and optional downloaded frontend libraries. - `docs/agents/` - agent workflow notes, task summaries, reusable review instructions, and scratch-space conventions. - `.github/copilot-instructions.md` - mirror of this file for tools that read GitHub Copilot instructions. @@ -117,7 +119,7 @@ data. ## Framework Development Rules -- Core changes under `www/php/fw/` must be generic. Do not assume application-specific models, templates, routes, or +- Core changes under `php/fw/` must be generic. Do not assume application-specific models, templates, routes, or config keys beyond the framework baseline. - Maintain backward compatibility for downstream apps when practical. If a change alters routing, controller contracts, model CRUD behavior, template variables, config names, DB wrapper semantics, or JSON shape, document the break and @@ -130,9 +132,9 @@ data. and templates aligned. Check both controller defaults and template fragments. - For schema changes, update the create-from-scratch SQL (`db/fwdatabase.sql` or the relevant `db/*.sql`) and add a dated update under `db/updates/`. -- Do not commit secrets or machine-only host config. Use local files under `www/php/configs/` for developer machines and +- Do not commit secrets or machine-only host config. Use local files under `php/configs/` for developer machines and keep credentials out of docs and logs. -- Composer dependency changes belong in `www/php/composer.json` and `www/php/composer.lock`. Do not manually edit generated +- Composer dependency changes belong in `php/composer.json` and `php/composer.lock`. Do not manually edit generated vendor metadata unless the repository intentionally tracks that generated output for the task. - Keep downloaded frontend libraries under `www/assets/lib/`; this path is ignored except for `.gitkeep`. @@ -159,26 +161,27 @@ data. ## Common Tasks -- Add or adjust controllers under `www/php/controllers/` or `www/php/controllers/v1/`, then update matching templates or +- Add or adjust controllers under `php/controllers/` or `php/controllers/v1/`, then update matching templates or API response handling. -- Extend models under `www/php/models/` or generic model behavior in `www/php/fw/FwModel.php`. -- Update dynamic CRUD by changing the controller defaults and `www/template//config.json` together. -- Update ParsePage layouts/fragments under `www/template/`; keep template logic minimal. +- Extend models under `php/models/` or generic model behavior in `php/fw/FwModel.php`. +- Update dynamic CRUD by changing the controller defaults and `template//config.json` together. +- Update ParsePage layouts/fragments under `template/`; keep template logic minimal. - Update database install/update scripts under `db/` and `db/updates/`. -- Update Composer dependencies from `www/php/`. +- Update Composer dependencies from `php/`. - Use `logger()` and the configured log destination for debugging instead of dumping output in production paths. ## Verification - Syntax-check changed PHP files: - - `php -l www/php/fw/FwModel.php` - - `php -l www/php/controllers/AdminDemos.php` + - `php -l php/fw/FwModel.php` + - `php -l php/controllers/AdminDemos.php` - Syntax-check all project PHP when a framework-wide change has broad risk: - - `Get-ChildItem -Path www/php -Recurse -Filter *.php | ForEach-Object { php -l $_.FullName }` + - `Get-ChildItem -Path php -Recurse -Filter *.php | Where-Object { $_.FullName -notmatch '\\vendor\\' } | ForEach-Object { php -l $_.FullName }` - Validate Composer metadata after dependency changes: - - `Push-Location www/php; composer validate --no-check-publish; Pop-Location` -- There is no dedicated first-party PHPUnit suite in this repository at the time this guide was written. If tests are - added later, document the canonical commands here and in the task summary. + - `Push-Location php; composer validate --no-check-publish; Pop-Location` +- Run the PHPUnit harness when the change affects test-covered framework behavior: + - `php php/tests/run-local-phpunit.php --testsuite Unit` + - `Push-Location php; composer test; Pop-Location` - For DB changes, bootstrap a local database using `db/README.md`, apply base SQL plus the new update, and manually check the affected controller/model flow. - For web/admin/template changes, exercise the relevant route in a browser when local config and DB are available. diff --git a/.gitignore b/.gitignore index fc9070e..37df263 100644 --- a/.gitignore +++ b/.gitignore @@ -2,15 +2,15 @@ www/assets/lib/* !www/assets/lib/.gitkeep -www/php/vendor/* -!www/php/vendor/.gitkeep +php/vendor/* +!php/vendor/.gitkeep # Local public DB/admin consoles and backups must not be staged. www/ppm.php www/web.config.bak # Machine-local host configs must not be staged. -www/php/configs/*.lo.php +php/configs/*.lo.php doc/ .idea/ @@ -23,9 +23,6 @@ docs/agents/local/* upload/* !upload/.gitkeep -www/upload/* -!www/upload/.gitkeep - # Log files *.log diff --git a/AGENTS.md b/AGENTS.md index f1c0592..1e6ee60 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,13 +83,15 @@ data. - `db/updates/` - dated update scripts. Pair schema changes with a matching update script. - `www/` - public web root. - `www/index.php` - web entrypoint. -- `www/php/` - PHP code, config, Composer metadata, framework core, controllers, models, hooks, and vendor files. -- `www/php/fw/` - framework core (`fw`, dispatcher, controllers, model base, DB wrapper, ParsePage, utilities). -- `www/php/controllers/` - sample/admin/app controllers built on the framework. -- `www/php/controllers/v1/` - sample versioned API controllers. -- `www/php/models/` - sample/admin models and shared framework models. -- `www/php/configs/` - default and host-local config files. Never commit secrets. -- `www/template/` - ParsePage templates and dynamic-controller `config.json` files. +- `php/` - PHP code, config, Composer metadata, framework core, controllers, models, hooks, and vendor files. +- `php/fw/` - framework core (`fw`, dispatcher, controllers, model base, DB wrapper, ParsePage, utilities). +- `php/controllers/` - sample/admin/app controllers built on the framework. +- `php/controllers/v1/` - sample versioned API controllers. +- `php/models/` - sample/admin models and shared framework models. +- `php/tools/` - non-public standalone developer/admin tools invoked through framework routes. +- `php/configs/` - default and host-local config files. Never commit secrets. +- `template/` - ParsePage templates and dynamic-controller `config.json` files. +- `upload/` - default non-public attachment storage served through framework routes. - `www/assets/` - CSS, JavaScript, images, and optional downloaded frontend libraries. - `docs/agents/` - agent workflow notes, task summaries, reusable review instructions, and scratch-space conventions. - `.github/copilot-instructions.md` - mirror of this file for tools that read GitHub Copilot instructions. @@ -117,7 +119,7 @@ data. ## Framework Development Rules -- Core changes under `www/php/fw/` must be generic. Do not assume application-specific models, templates, routes, or +- Core changes under `php/fw/` must be generic. Do not assume application-specific models, templates, routes, or config keys beyond the framework baseline. - Maintain backward compatibility for downstream apps when practical. If a change alters routing, controller contracts, model CRUD behavior, template variables, config names, DB wrapper semantics, or JSON shape, document the break and @@ -130,9 +132,9 @@ data. and templates aligned. Check both controller defaults and template fragments. - For schema changes, update the create-from-scratch SQL (`db/fwdatabase.sql` or the relevant `db/*.sql`) and add a dated update under `db/updates/`. -- Do not commit secrets or machine-only host config. Use local files under `www/php/configs/` for developer machines and +- Do not commit secrets or machine-only host config. Use local files under `php/configs/` for developer machines and keep credentials out of docs and logs. -- Composer dependency changes belong in `www/php/composer.json` and `www/php/composer.lock`. Do not manually edit generated +- Composer dependency changes belong in `php/composer.json` and `php/composer.lock`. Do not manually edit generated vendor metadata unless the repository intentionally tracks that generated output for the task. - Keep downloaded frontend libraries under `www/assets/lib/`; this path is ignored except for `.gitkeep`. @@ -159,26 +161,27 @@ data. ## Common Tasks -- Add or adjust controllers under `www/php/controllers/` or `www/php/controllers/v1/`, then update matching templates or +- Add or adjust controllers under `php/controllers/` or `php/controllers/v1/`, then update matching templates or API response handling. -- Extend models under `www/php/models/` or generic model behavior in `www/php/fw/FwModel.php`. -- Update dynamic CRUD by changing the controller defaults and `www/template//config.json` together. -- Update ParsePage layouts/fragments under `www/template/`; keep template logic minimal. +- Extend models under `php/models/` or generic model behavior in `php/fw/FwModel.php`. +- Update dynamic CRUD by changing the controller defaults and `template//config.json` together. +- Update ParsePage layouts/fragments under `template/`; keep template logic minimal. - Update database install/update scripts under `db/` and `db/updates/`. -- Update Composer dependencies from `www/php/`. +- Update Composer dependencies from `php/`. - Use `logger()` and the configured log destination for debugging instead of dumping output in production paths. ## Verification - Syntax-check changed PHP files: - - `php -l www/php/fw/FwModel.php` - - `php -l www/php/controllers/AdminDemos.php` + - `php -l php/fw/FwModel.php` + - `php -l php/controllers/AdminDemos.php` - Syntax-check all project PHP when a framework-wide change has broad risk: - - `Get-ChildItem -Path www/php -Recurse -Filter *.php | ForEach-Object { php -l $_.FullName }` + - `Get-ChildItem -Path php -Recurse -Filter *.php | Where-Object { $_.FullName -notmatch '\\vendor\\' } | ForEach-Object { php -l $_.FullName }` - Validate Composer metadata after dependency changes: - - `Push-Location www/php; composer validate --no-check-publish; Pop-Location` -- There is no dedicated first-party PHPUnit suite in this repository at the time this guide was written. If tests are - added later, document the canonical commands here and in the task summary. + - `Push-Location php; composer validate --no-check-publish; Pop-Location` +- Run the PHPUnit harness when the change affects test-covered framework behavior: + - `php php/tests/run-local-phpunit.php --testsuite Unit` + - `Push-Location php; composer test; Pop-Location` - For DB changes, bootstrap a local database using `db/README.md`, apply base SQL plus the new update, and manually check the affected controller/model flow. - For web/admin/template changes, exercise the relevant route in a browser when local config and DB are available. diff --git a/README.md b/README.md index c6a4f66..a83e929 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ Created as simplified and lightweight alternative to other PHP frameworks ### Development/Deployment -1. put contents of `/www` into your webserver's public html folder -2. edit `/www/php/config.site.php` (or `config.localhost.php` for development) +1. point your web server document root to `www/` +2. edit `php/configs/site.php` (or `php/configs/localhost.php` for development) 3. create database from `/db/fwdatabase.sql`, `/db/lookups.sql` and others (if needed) 4. open site in your browser and login with credentials as defined in fwdatabase.sql 5. review log in `/logs/osafw.log` @@ -35,14 +35,6 @@ Created as simplified and lightweight alternative to other PHP frameworks /db - initial fwdatabase.sql script and update sql scripts /logs/osafw.log - application log (ensure to enable write rights to /logs dir for webserver) /www - application public root folder - /php - all the PHP code is here - /controllers - your controllers - /fw - framework core libs - /models - your models - /vendor - composer libs - /config.*.php - settings for db connection, mail, logging... - /template - all the html templates - /upload - upload dir for public files /assets - your web frontend assets /css /fonts @@ -50,6 +42,15 @@ Created as simplified and lightweight alternative to other PHP frameworks /js /favicon.ico - change to your favicon! /robots.txt - default robots.txt (empty) +/php - PHP code outside the public web root + /controllers - your controllers + /fw - framework core libs + /models - your models + /tools - non-public developer/admin tools + /vendor - composer libs + /configs - settings for db connection, mail, logging... +/template - ParsePage templates outside the public web root +/upload - non-public attachment storage served through framework routes ``` ### REST mappings @@ -70,7 +71,7 @@ Controllers automatically directly mapped to URLs, so developer doesn't need to case action name should be less than 32 characters For example `GET /Products` will call `ProductsController.IndexAction()` -And this will cause rendering templates from `/www/template/products/index` +And this will cause rendering templates from `template/products/index` ID can be numeric or 32-char string like UUID (without dashes) For example `GET /Products/123` will call `ProductsController.ShowAction(123)` @@ -164,10 +165,10 @@ Most of the global settings defined in `config.*.php`. But there are several cac | hostname | set from server variable HTTP_HOST | osalabs.com | | ROOT_DOMAIN | protocol+hostname | https://osalabs.com | | ROOT_URL | part of the url if Application installed under sub-url | /suburl if App installed under osalabs.com/suburl | -| site_root | physical application path to the root of public directory | C:\inetpub\somesite\www | -| template | physical path to the root of templates directory | C:\inetpub\somesite\www\template | -| log | physical path to application log file | C:\inetpub\somesite\logs\osafw.log | -| tmp | physical path to the system tmp directory | C:\Windows\Temp | +| site_root | physical application path to the root of public directory | `www` | +| template | physical path to the root of templates directory | `template` | +| log | physical path to application log file | `logs/osafw.log` | +| tmp | physical path to the system tmp directory | system temp directory | ### config.json @@ -323,9 +324,10 @@ Another debug functions that might be helpful are: - for example, if for a logged user you need to show detailed data and always skip list view - in the `IndexAction()` just use `fw.routeRedirect("ShowForm")` - uploads - - save all public-readable uploads under `/www/upload` (default, see "UPLOAD_DIR" in `config.*.php`) - - for non-public uploads use `/upload` - - or `S3` model and upload to the cloud + - save attachment files under `upload/` outside the public web root + - access default file/table attachments through `/Att/` URLs + - set `PUBLIC_UPLOAD_URL` only when you intentionally expose a separate upload base through the web server + - or use `S3` model and upload to the cloud - put all validation code into controller's `Validate()`. See usage example in `AdminDemosController` - use `logger()` and review `/logs/osafw.log` if you stuck - make sure you have "LOG_LEVEL" set to "DEBUG" in your `config.*.php` @@ -339,16 +341,16 @@ Another debug functions that might be helpful are: - base report model is `FwReports`, major methods (you may override in the specific report): - `getReportFilters()` - set data for the report filters - `getReportData()` - returns report data, usually based on some sql query (see Sample report) -- `ReportSample` model (in `\www\php\models\Reports` folder) is a sample report implementation, that can be used as a +- `ReportSample` model (in `php/models/Reports` folder) is a sample report implementation, that can be used as a template to build custom reports - basic steps to create a new report: - - copy `\www\php\models\Reports\Sample.php` to `\www\php\models\Reports\Cool.php` (to create Cool report) + - copy `php/models/Reports/Sample.php` to `php/models/Reports/Cool.php` (to create Cool report) - edit `Cool.php` and rename "Sample" to "Cool" - modify `getReportFilters()` to match your report filters - modify `getReportData()` to edit sql query and related post-processing - - copy templates folder `\www\template\reports\sample` to `\www\template\reports\cool` + - copy templates folder `template/reports/sample` to `template/reports/cool` - edit templates: - `title.html` - report title - `list_filter.html` - for filters - `report_html.html` - for report table/layout/appearance - - add link to a new report to `\www\template\reports\index\main.html` + - add link to a new report to `template/reports/index/main.html` diff --git a/bin/libman.php b/bin/libman.php index 40722d2..5b0901a 100644 --- a/bin/libman.php +++ b/bin/libman.php @@ -3,14 +3,14 @@ LibMan install asset libraries */ -require_once dirname(__FILE__) . "/../www/php/fw/fw.php"; +require_once dirname(__FILE__) . "/../php/fw/fw.php"; fw::initOffline(); -$jsonPath = fw::i()->config->SITE_ROOT . '/php/libman.json'; +$jsonPath = fw::i()->config->PHP_ROOT . '/libman.json'; $rootPath = fw::i()->config->SITE_ROOT; $libman = new PhpLibMan($jsonPath, $rootPath); $libman->install(); -fw::endRequest(); \ No newline at end of file +fw::endRequest(); diff --git a/bin/phpunit-local.bat b/bin/phpunit-local.bat index c85a627..7202a9a 100644 --- a/bin/phpunit-local.bat +++ b/bin/phpunit-local.bat @@ -2,6 +2,6 @@ setlocal set "ROOT_DIR=%~dp0.." -php "%ROOT_DIR%\www\php\tests\run-local-phpunit.php" %* +php "%ROOT_DIR%\php\tests\run-local-phpunit.php" %* set "EXIT_CODE=%ERRORLEVEL%" exit /b %EXIT_CODE% diff --git a/docs/agents/code_reviewer.md b/docs/agents/code_reviewer.md index c4eda7e..72d34d7 100644 --- a/docs/agents/code_reviewer.md +++ b/docs/agents/code_reviewer.md @@ -44,7 +44,7 @@ Check, in this order: ## Framework-Specific Checks -- Core files under `www/php/fw/` must remain generic and must not depend on product-specific controllers, models, config +- Core files under `php/fw/` must remain generic and must not depend on product-specific controllers, models, config keys, hosts, secrets, or data. - Preserve PHP 8.3+ compatibility. Typed class constants are already used; do not introduce syntax that raises the target version without documenting it. @@ -54,13 +54,13 @@ Check, in this order: auth alternatives, and standard metadata field names. - For admin controllers, verify `model_name`, `base_url`, `required_fields`, `save_fields`, `list_sortdef`, `list_sortmap`, `search_fields`, and related templates/config remain aligned. -- For dynamic controllers, verify `config.json` fields and templates under `www/template//` agree with controller +- For dynamic controllers, verify `config.json` fields and templates under `template//` agree with controller expectations. - Keep business rules out of ParsePage templates. Templates should render data and small conditional fragments, not own model/query decisions. - When schema changes are present, verify the create-from-scratch SQL and `db/updates/` migration both changed. -- Do not commit credentials or machine-specific host config under `www/php/configs/`. -- Composer dependency changes should update `www/php/composer.json` and `www/php/composer.lock`. Treat vendor/generated +- Do not commit credentials or machine-specific host config under `php/configs/`. +- Composer dependency changes should update `php/composer.json` and `php/composer.lock`. Treat vendor/generated metadata changes skeptically unless the task intentionally refreshes tracked vendor output. - Do not run DB-backed checks in parallel against the same local database. @@ -85,7 +85,7 @@ Use this shape: ## Findings ### High - API auth gate skipped on private action -- Location: `www/php/controllers/v1/Example.php:42` +- Location: `php/controllers/v1/Example.php:42` - Problem: ... - Impact: ... - Fix direction: ... diff --git a/docs/agents/domain.md b/docs/agents/domain.md index 73126e2..19bd0e7 100644 --- a/docs/agents/domain.md +++ b/docs/agents/domain.md @@ -7,8 +7,11 @@ fact is broadly useful for future osafw-php development. - `osafw-php` is the reusable PHP framework and sample app baseline, not a product-specific backend. - The framework target is PHP 8.3+ because the current core uses typed class constants. -- Public web files live under `www/`; PHP framework code lives under `www/php/`; core framework classes live under - `www/php/fw/`. -- ParsePage templates live under `www/template/`; dynamic controller configuration lives in route-level `config.json` +- Public web files live under `www/`; PHP framework code lives under `php/`; core framework classes live under + `php/fw/`. +- ParsePage templates live under `template/`; dynamic controller configuration lives in route-level `config.json` files under that template tree. +- Default attachment file storage lives under `upload/` outside the public web root and is served through framework routes. +- Standalone developer/admin tools that should not be directly served live under `php/tools/` and must be exposed only + through authenticated framework routes when needed. - Database bootstrap and migration SQL lives under `db/`. diff --git a/docs/agents/tasks/summary-2026-04-18-agents-guide.md b/docs/agents/tasks/summary-2026-04-18-agents-guide.md index 1f0f68e..b2c4de1 100644 --- a/docs/agents/tasks/summary-2026-04-18-agents-guide.md +++ b/docs/agents/tasks/summary-2026-04-18-agents-guide.md @@ -13,9 +13,9 @@ - External application guide and reviewer guide, adapted without recording machine-local source paths. - Framework repository docs and code: - `README.md`, `db/README.md`, `www/php/composer.json`, `www/php/fw/FwModel.php`, - `www/php/fw/FwController.php`, `www/php/fw/FwAdminController.php`, `www/php/fw/FwApiController.php`, - `www/php/fw/fw.php`, `www/php/configs/config.php`, `www/php/controllers/v1/v1DemosApi.php` + `README.md`, `db/README.md`, `php/composer.json`, `php/fw/FwModel.php`, + `php/fw/FwController.php`, `php/fw/FwAdminController.php`, `php/fw/FwApiController.php`, + `php/fw/fw.php`, `php/configs/config.php`, `php/controllers/v1/v1DemosApi.php` ## Commands used / verification diff --git a/docs/agents/tasks/summary-2026-04-30-public-root-minimize.md b/docs/agents/tasks/summary-2026-04-30-public-root-minimize.md new file mode 100644 index 0000000..43efea2 --- /dev/null +++ b/docs/agents/tasks/summary-2026-04-30-public-root-minimize.md @@ -0,0 +1,79 @@ +## What changed + +- Moved tracked `www/php` to `php` with `git mv`. +- Moved tracked `www/template` to `template` with `git mv`. +- Moved untracked/ignored `www/upload` contents to `upload` and added `upload/.gitkeep` so fresh checkouts keep the non-public storage root. +- Updated web bootstrap and CLI/test scripts for the new `php/` location. +- Changed default config roots so `SITE_ROOT` is the public `www/`, `SITE_ROOT_OFFLINE` is the repository root, `PHP_ROOT` is `php/`, `SITE_TEMPLATES` is `template/`, and `PUBLIC_UPLOAD_DIR` is `upload/`. +- Changed local attachment direct URLs to route through `/Att/` instead of exposing files from `upload/`. +- Removed public rewrite exceptions for `upload/` and stale template-deny rules because those directories are no longer under `www/`. +- Moved the DB admin standalone tool from `www/` to `php/tools/` and exposed it only through the existing site-admin `/Admin/DB` route. +- Removed the DB admin fallback password from the tool config. +- Redirected DB admin server-side dumps to ignored non-public `upload/dbdumps/` instead of writing beside tool source. +- Updated README, framework docs, agent guidance, review guidance, and upgrade notes for the new layout. + +## Scope reviewed + +- `README.md`, `AGENTS.md`, `.github/copilot-instructions.md`, and `docs/agents/local/instructions.md` check. +- Public web root files under `www/`. +- Config/bootstrap files under `php/configs/`, `www/index.php`, `bin/`, and `phpunit.xml`. +- Attachment URL flow in `php/models/Att.php` and upload helper defaults. +- Admin DB tool routing through `php/controllers/AdminDB.php`. +- DevManage/report code paths that referenced PHP code under the old public root. +- Existing docs that referenced `www/php`, `www/template`, or `www/upload`. + +## Commands used / verification + +- `git status --short` to inspect the dirty worktree before and after moves. +- `Get-ChildItem -Force www` to verify public-root contents. +- `git mv www/php php` and `git mv www/template template`; both required elevated filesystem access after Git index-lock permission errors. +- `Move-Item www/upload upload` for ignored/untracked upload contents because `git mv` had no tracked source files to move. +- `rg` scans for stale `www/php`, `www/template`, `www/upload`, machine-local Markdown paths, and downstream project terms. +- `Compare-Object (Get-Content AGENTS.md) (Get-Content .github/copilot-instructions.md)` confirmed the instruction mirrors match. +- `php -r "require 'php/fw/fw.php'; ..."` confirmed config roots resolve to public `www/`, `php/`, `template/`, and `upload/`. +- `Get-ChildItem -Path php,www,bin -Recurse -Filter *.php | Where-Object { $_.FullName -notmatch '\\vendor\\' } | ForEach-Object { php -l $_.FullName }` - passed for 98 non-vendor PHP files. +- `php -l php/tools/phpminiconfig.php; php -l php/controllers/AdminDB.php; php -l php/tools/phpminiadmin.php` - passed. +- `composer validate --no-check-publish` from `php` - valid; Composer reports existing dubious-ownership messages and a no-license warning. +- `php php/tests/run-local-phpunit.php --testsuite Unit` - blocked because the current installed/tracked Composer autoload metadata does not include PHPUnit dev classes; run `composer install` from `php` before rerunning. +- `git diff --check` - passed; Git reported only CRLF normalization warnings. +- Review loop with `docs/agents/code_reviewer.md` found the public DB admin tool and DB dump-path issues; both were fixed. +- Final follow-up review reported no issues worth another loop. + +## Decisions - why + +- Kept `www/` as only the web server document root. Code, templates, Composer vendor files, configs, and upload storage now live outside it. +- Kept `SITE_ROOT` meaning as public web root and added/used `PHP_ROOT` for framework code paths instead of overloading `SITE_ROOT`. +- Kept `PUBLIC_UPLOAD_URL` as a compatibility setting for projects that intentionally expose a mapped upload base, but default attachment links now use `/Att/`. +- Moved the DB admin tool instead of only blocking it in rewrite config so it cannot be directly served by Apache, IIS, or another web server that honors existing-file bypasses. + +## Pitfalls - fixes + +- `www/upload` was not tracked, so `git mv` could not move it. Moved the directory contents directly and added `upload/.gitkeep`. +- DevManage and report helpers still constructed paths through `SITE_ROOT`; updated those code paths to `PHP_ROOT`. +- PHPUnit bootstrap paths originally walked one directory too far after the move; adjusted them to the repository root. +- Rewrite rules still treated `upload/` as public; removed that exception from Apache and IIS config. +- Review caught that DB admin tooling still lived in `www/`; moved it to `php/tools/`, updated `/Admin/DB`, fixed the config include path, and removed the fallback password. +- Follow-up review caught that the DB admin dump base moved next to tracked source; pointed it at `upload/dbdumps/`. +- Manual code-path review found the moved DB admin config loaded framework DB settings but did not mark the standalone tool session as logged in; set its local `is_logged` flag after the framework site-admin gate. + +## Risks / follow-ups + +- `/Admin/DB` still exposes a powerful DB admin tool to site administrators. Consider replacing it with narrower framework-native DB maintenance screens if broad SQL access is not desired. +- DB admin server-side dumps are non-public and ignored, but can contain sensitive data; clean `upload/dbdumps/` after use. +- `PUBLIC_UPLOAD_URL` remains available for downstream code that uses `FwModel::getUploadUrl()` directly. Default attachment flows no longer depend on it. +- Browser/admin and attachment download checks still require local web server and DB configuration. + +## Heuristics + +- Public-root minimization should preserve `www/assets/`, `www/index.php`, server rewrite config, and simple public metadata, while moving executable framework internals out of `www/`. +- When a config key name describes the public root, add a separate explicit key for non-public code paths rather than changing all call sites to infer repository structure. + +## Testing instructions + +- User-facing/internal: internal framework layout and public-surface hardening. +- Type: refactor/security hardening. +- Affected flows: bootstrap, routing, template loading, dynamic controller config loading, DevManage helpers, Composer/PHPUnit execution, attachment file URLs, and web-server rewrites. +- Run non-vendor PHP syntax checks under `php/`, `www/`, and `bin/`. +- Run `Push-Location php; composer validate --no-check-publish; Pop-Location`. +- Run `php php/tests/run-local-phpunit.php --testsuite Unit` when dev dependencies are installed. +- Manually verify a local web request, a template-rendered page, `/Dev/Manage`, and `/Att/` attachment delivery when local DB/config are available. diff --git a/docs/db.md b/docs/db.md index 9467156..efbff1d 100644 --- a/docs/db.md +++ b/docs/db.md @@ -1,6 +1,6 @@ # Database Access -This framework uses the `DB` wrapper in `www/php/fw/db.php` for MySQL access. The goal is straightforward data access: common CRUD operations are concise, advanced queries are still possible, and existing code can mix table-based helpers with raw SQL where that is clearer. +This framework uses the `DB` wrapper in `php/fw/db.php` for MySQL access. The goal is straightforward data access: common CRUD operations are concise, advanced queries are still possible, and existing code can mix table-based helpers with raw SQL where that is clearer. ## Choosing the Layer diff --git a/docs/dynamic.md b/docs/dynamic.md index 6a46dbd..2d36886 100644 --- a/docs/dynamic.md +++ b/docs/dynamic.md @@ -41,7 +41,7 @@ Set `base_url`, set the model, load config, and add explicit overrides only when The controller loads `config.json` from the template base directory: ```text -www/template/admin/demosdynamic/config.json +template/admin/demosdynamic/config.json ``` That file is the main contract for list behavior, save fields, validation, show fields, showform fields, tabs, and custom rendering. @@ -129,9 +129,9 @@ Common showform-field types: The shared markup lives under: -- `www/template/common/form/show/` -- `www/template/common/form/showform/` -- `www/template/common/vue/` +- `template/common/form/show/` +- `template/common/form/showform/` +- `template/common/vue/` ## Minimal Config Example diff --git a/docs/templates.md b/docs/templates.md index 009e377..fa7e079 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -1,6 +1,6 @@ # Templates and ParsePage -The framework renders HTML through ParsePage templates under `www/template/`. The template layer is intentionally lightweight: it handles composition, repetition, small conditionals, and output formatting. Business rules belong in controllers and models. +The framework renders HTML through ParsePage templates under `template/`. The template layer is intentionally lightweight: it handles composition, repetition, small conditionals, and output formatting. Business rules belong in controllers and models. ## Mental Model @@ -14,7 +14,7 @@ The framework renders HTML through ParsePage templates under `www/template/`. Th A standard admin screen usually looks like this: ```text -www/template/admin// +template/admin// config.json index/ main.html @@ -30,10 +30,10 @@ www/template/admin// Shared building blocks live under: -- `www/template/common/list/` -- `www/template/common/form/` -- `www/template/common/vue/` -- `www/template/layout/` +- `template/common/list/` +- `template/common/form/` +- `template/common/vue/` +- `template/layout/` ## ParsePage Blocks @@ -119,7 +119,7 @@ Truthiness matters: a non-empty string such as `"false"` still counts as true. ## Output Modifiers -Common modifiers implemented in `www/php/fw/ParsePage.php` include: +Common modifiers implemented in `php/fw/ParsePage.php` include: - `date` - `number_format` @@ -154,14 +154,14 @@ Examples: These shared fragments are intended for reuse: -- `www/template/common/list/form_list.html` -- `www/template/common/list/thead.html` -- `www/template/common/list/tbody.html` -- `www/template/common/list/pagination.html` -- `www/template/common/form/show/` -- `www/template/common/form/showform/` -- `www/template/common/form/tabs.html` -- `www/template/common/vue/` +- `template/common/list/form_list.html` +- `template/common/list/thead.html` +- `template/common/list/tbody.html` +- `template/common/list/pagination.html` +- `template/common/form/show/` +- `template/common/form/showform/` +- `template/common/form/tabs.html` +- `template/common/vue/` Recent generic hooks: diff --git a/www/php/FwHooks.php b/php/FwHooks.php similarity index 100% rename from www/php/FwHooks.php rename to php/FwHooks.php diff --git a/www/php/SiteUtils.php b/php/SiteUtils.php similarity index 100% rename from www/php/SiteUtils.php rename to php/SiteUtils.php diff --git a/www/php/composer.json b/php/composer.json similarity index 100% rename from www/php/composer.json rename to php/composer.json diff --git a/www/php/composer.lock b/php/composer.lock similarity index 100% rename from www/php/composer.lock rename to php/composer.lock diff --git a/www/php/configs/config.php b/php/configs/config.php similarity index 93% rename from www/php/configs/config.php rename to php/configs/config.php index 438bee3..6ceeaeb 100644 --- a/www/php/configs/config.php +++ b/php/configs/config.php @@ -19,8 +19,9 @@ } } ######### set all variables to defaults with detection of base dirs -$site_root = dirname(__FILE__, 3);# level up as we are under /configs dir -$site_root_offline = dirname($site_root); +$site_root_offline = dirname(__FILE__, 3); # repository root +$site_root = $site_root_offline . '/www'; # public web root +$php_root = $site_root_offline . '/php'; #!note, these will be empty if script run from command line # X-forwarded are for the load balancer setup. @@ -36,8 +37,9 @@ #global site config $FW_CONFIG = array( - 'SITE_ROOT' => $site_root, #site root usually is parent of this file dir (inc) - 'SITE_ROOT_OFFLINE' => $site_root_offline, #offline dir usually is parent to site_root dir (www) + 'SITE_ROOT' => $site_root, #public web root + 'SITE_ROOT_OFFLINE' => $site_root_offline, #repository/offline root + 'PHP_ROOT' => $php_root, #framework PHP root 'PROTO' => $proto, 'ROOT_DOMAIN0' => $root_domain0, #domain without proto, without port, example: domain.com 'ROOT_DOMAIN' => $root_domain, #full domain url with http or https, example: http://domain.com @@ -81,8 +83,9 @@ 'UNLOGGED_DEFAULT_URL' => '/', 'LOGGED_DEFAULT_URL' => '/Main', - 'SITE_TEMPLATES' => $site_root . '/template', - 'PUBLIC_UPLOAD_DIR' => $site_root . '/upload', + 'SITE_TEMPLATES' => $site_root_offline . '/template', + 'PUBLIC_UPLOAD_DIR' => $site_root_offline . '/upload', + # only for explicitly public-mapped upload roots; default attachments use /Att/ 'PUBLIC_UPLOAD_URL' => $root_url . '/upload', 'ASSETS_URL' => $root_url . '/assets', diff --git a/www/php/configs/localhost.php b/php/configs/localhost.php similarity index 90% rename from www/php/configs/localhost.php rename to php/configs/localhost.php index b665450..d6e2b43 100644 --- a/www/php/configs/localhost.php +++ b/php/configs/localhost.php @@ -1,6 +1,6 @@ $FW_CONFIG['PROTO'].'://DOMAIN.com', #full domain url with http or https, example: http://domain.com #'ROOT_URL' => '', #use only if site installed in subdirectory of domain like domain.com/sub_site, example: /sub_site - #'SITE_ROOT_OFFLINE' => '/var/www/website', - #'SITE_ROOT' => '/var/www/website/public_html', + #'SITE_ROOT_OFFLINE' => dirname(__DIR__, 2), + #'SITE_ROOT' => dirname(__DIR__, 2) . '/www', #'SUPPORT_EMAIL' => 'support@website.com', #'FROM_EMAIL' => 'noreply@website.com', diff --git a/www/php/configs/site.php b/php/configs/site.php similarity index 91% rename from www/php/configs/site.php rename to php/configs/site.php index 82b903a..8efe03a 100644 --- a/www/php/configs/site.php +++ b/php/configs/site.php @@ -1,6 +1,6 @@ $FW_CONFIG['PROTO'].'://DOMAIN.com', #full domain url with http or https, example: http://domain.com #'ROOT_URL' => '', #use only if site installed in subdirectory of domain like domain.com/sub_site, example: /sub_site - #'SITE_ROOT_OFFLINE' => '/var/www/website', - #'SITE_ROOT' => '/var/www/website/public_html', + #'SITE_ROOT_OFFLINE' => dirname(__DIR__, 2), + #'SITE_ROOT' => dirname(__DIR__, 2) . '/www', #'SUPPORT_EMAIL' => 'support@website.com', #'FROM_EMAIL' => 'noreply@website.com', diff --git a/www/php/controllers/AdminAtt.php b/php/controllers/AdminAtt.php similarity index 100% rename from www/php/controllers/AdminAtt.php rename to php/controllers/AdminAtt.php diff --git a/php/controllers/AdminDB.php b/php/controllers/AdminDB.php new file mode 100644 index 0000000..34cab7d --- /dev/null +++ b/php/controllers/AdminDB.php @@ -0,0 +1,14 @@ +fw->config->ROOT_URL . '/Admin/DB'; + + require $this->fw->config->PHP_ROOT . '/tools/phpminiadmin.php'; + exit; + } + +}//end of class diff --git a/www/php/controllers/AdminDemoDicts.php b/php/controllers/AdminDemoDicts.php similarity index 100% rename from www/php/controllers/AdminDemoDicts.php rename to php/controllers/AdminDemoDicts.php diff --git a/www/php/controllers/AdminDemos.php b/php/controllers/AdminDemos.php similarity index 100% rename from www/php/controllers/AdminDemos.php rename to php/controllers/AdminDemos.php diff --git a/www/php/controllers/AdminDemosDynamic.php b/php/controllers/AdminDemosDynamic.php similarity index 100% rename from www/php/controllers/AdminDemosDynamic.php rename to php/controllers/AdminDemosDynamic.php diff --git a/www/php/controllers/AdminDemosVue.php b/php/controllers/AdminDemosVue.php similarity index 100% rename from www/php/controllers/AdminDemosVue.php rename to php/controllers/AdminDemosVue.php diff --git a/www/php/controllers/AdminLookupManager.php b/php/controllers/AdminLookupManager.php similarity index 100% rename from www/php/controllers/AdminLookupManager.php rename to php/controllers/AdminLookupManager.php diff --git a/www/php/controllers/AdminLookups.php b/php/controllers/AdminLookups.php similarity index 100% rename from www/php/controllers/AdminLookups.php rename to php/controllers/AdminLookups.php diff --git a/www/php/controllers/AdminReports.php b/php/controllers/AdminReports.php similarity index 100% rename from www/php/controllers/AdminReports.php rename to php/controllers/AdminReports.php diff --git a/www/php/controllers/AdminSettings.php b/php/controllers/AdminSettings.php similarity index 100% rename from www/php/controllers/AdminSettings.php rename to php/controllers/AdminSettings.php diff --git a/www/php/controllers/AdminSpages.php b/php/controllers/AdminSpages.php similarity index 100% rename from www/php/controllers/AdminSpages.php rename to php/controllers/AdminSpages.php diff --git a/www/php/controllers/AdminUsers.php b/php/controllers/AdminUsers.php similarity index 100% rename from www/php/controllers/AdminUsers.php rename to php/controllers/AdminUsers.php diff --git a/www/php/controllers/Att.php b/php/controllers/Att.php similarity index 100% rename from www/php/controllers/Att.php rename to php/controllers/Att.php diff --git a/www/php/controllers/BaseApi.php b/php/controllers/BaseApi.php similarity index 100% rename from www/php/controllers/BaseApi.php rename to php/controllers/BaseApi.php diff --git a/www/php/controllers/Contact.php b/php/controllers/Contact.php similarity index 100% rename from www/php/controllers/Contact.php rename to php/controllers/Contact.php diff --git a/www/php/controllers/DevConfigure.php b/php/controllers/DevConfigure.php similarity index 100% rename from www/php/controllers/DevConfigure.php rename to php/controllers/DevConfigure.php diff --git a/www/php/controllers/DevManage.php b/php/controllers/DevManage.php similarity index 97% rename from www/php/controllers/DevManage.php rename to php/controllers/DevManage.php index f2885cf..2b9c245 100644 --- a/www/php/controllers/DevManage.php +++ b/php/controllers/DevManage.php @@ -81,7 +81,7 @@ public function CreateModelAction() { } #copy DemoDicts.vb to model_name.vb - $path = $this->fw->config->SITE_ROOT . "/php/models"; + $path = $this->fw->config->PHP_ROOT . "/models"; #replace: DemoDicts => ModelName, demo_dicts => table_name $replacements = array( @@ -112,7 +112,7 @@ public function CreateControllerAction() { } #copy DemoDicts.php to $model_name.php - $path = $this->fw->config->SITE_ROOT . "/php/controllers"; + $path = $this->fw->config->PHP_ROOT . "/controllers"; $path_to = $path; if ($controller_type == "vue") { @@ -228,7 +228,7 @@ public function ExtractController() { } public function LibManAction() { - $jsonPath = $this->fw->config->SITE_ROOT . '/php/libman.json'; + $jsonPath = $this->fw->config->PHP_ROOT . '/libman.json'; $rootPath = $this->fw->config->SITE_ROOT; rw("started libman install"); @@ -246,7 +246,7 @@ public function SelfTestAction(): void { // get all controller classes from /php/controllers folder including subfolders // for each controller call IndexAction using fw->runController, check if it returns array (OK) or emtpty|null (warning) - $path = $this->fw->config->SITE_ROOT . '/php/controllers'; + $path = $this->fw->config->PHP_ROOT . '/controllers'; $controllers = array(); $dir = new RecursiveDirectoryIterator($path); $it = new RecursiveIteratorIterator($dir); @@ -303,7 +303,7 @@ private function _models() { $dirs = method_exists($this->fw, 'getAutoloadModelDirs') ? $this->fw->getAutoloadModelDirs() - : [$this->fw->config->SITE_ROOT . '/php/models']; + : [$this->fw->config->PHP_ROOT . '/models']; foreach ($dirs as $dir) { if (!is_dir($dir)) { @@ -326,7 +326,7 @@ private function _models() { private function _controllers() { $result = array(); - $dir = $this->fw->config->SITE_ROOT . '/php/controllers'; + $dir = $this->fw->config->PHP_ROOT . '/controllers'; $files = scandir($dir); foreach ($files as $value) { if (!preg_match('/^(.+)\.php$/', $value, $m)) { diff --git a/www/php/controllers/Home.php b/php/controllers/Home.php similarity index 100% rename from www/php/controllers/Home.php rename to php/controllers/Home.php diff --git a/www/php/controllers/Login.php b/php/controllers/Login.php similarity index 100% rename from www/php/controllers/Login.php rename to php/controllers/Login.php diff --git a/www/php/controllers/Main.php b/php/controllers/Main.php similarity index 100% rename from www/php/controllers/Main.php rename to php/controllers/Main.php diff --git a/www/php/controllers/MyLists.php b/php/controllers/MyLists.php similarity index 100% rename from www/php/controllers/MyLists.php rename to php/controllers/MyLists.php diff --git a/www/php/controllers/MyPassword.php b/php/controllers/MyPassword.php similarity index 100% rename from www/php/controllers/MyPassword.php rename to php/controllers/MyPassword.php diff --git a/www/php/controllers/MySettings.php b/php/controllers/MySettings.php similarity index 100% rename from www/php/controllers/MySettings.php rename to php/controllers/MySettings.php diff --git a/www/php/controllers/Password.php b/php/controllers/Password.php similarity index 100% rename from www/php/controllers/Password.php rename to php/controllers/Password.php diff --git a/www/php/controllers/Signup.php b/php/controllers/Signup.php similarity index 100% rename from www/php/controllers/Signup.php rename to php/controllers/Signup.php diff --git a/www/php/controllers/Test.php b/php/controllers/Test.php similarity index 100% rename from www/php/controllers/Test.php rename to php/controllers/Test.php diff --git a/www/php/controllers/WebFormMailer.php b/php/controllers/WebFormMailer.php similarity index 100% rename from www/php/controllers/WebFormMailer.php rename to php/controllers/WebFormMailer.php diff --git a/www/php/controllers/v1/v1DemosApi.php b/php/controllers/v1/v1DemosApi.php similarity index 100% rename from www/php/controllers/v1/v1DemosApi.php rename to php/controllers/v1/v1DemosApi.php diff --git a/www/php/controllers/v1/v1Noop.php b/php/controllers/v1/v1Noop.php similarity index 100% rename from www/php/controllers/v1/v1Noop.php rename to php/controllers/v1/v1Noop.php diff --git a/www/php/cron.php b/php/cron.php similarity index 100% rename from www/php/cron.php rename to php/cron.php diff --git a/www/php/fw/DateUtils.php b/php/fw/DateUtils.php similarity index 100% rename from www/php/fw/DateUtils.php rename to php/fw/DateUtils.php diff --git a/www/php/fw/FormUtils.php b/php/fw/FormUtils.php similarity index 100% rename from www/php/fw/FormUtils.php rename to php/fw/FormUtils.php diff --git a/www/php/fw/FwActivityLogs.php b/php/fw/FwActivityLogs.php similarity index 100% rename from www/php/fw/FwActivityLogs.php rename to php/fw/FwActivityLogs.php diff --git a/www/php/fw/FwAdminController.php b/php/fw/FwAdminController.php similarity index 100% rename from www/php/fw/FwAdminController.php rename to php/fw/FwAdminController.php diff --git a/www/php/fw/FwApiController.php b/php/fw/FwApiController.php similarity index 100% rename from www/php/fw/FwApiController.php rename to php/fw/FwApiController.php diff --git a/www/php/fw/FwCache.php b/php/fw/FwCache.php similarity index 100% rename from www/php/fw/FwCache.php rename to php/fw/FwCache.php diff --git a/www/php/fw/FwController.php b/php/fw/FwController.php similarity index 100% rename from www/php/fw/FwController.php rename to php/fw/FwController.php diff --git a/www/php/fw/FwControllers.php b/php/fw/FwControllers.php similarity index 100% rename from www/php/fw/FwControllers.php rename to php/fw/FwControllers.php diff --git a/www/php/fw/FwDev.php b/php/fw/FwDev.php similarity index 100% rename from www/php/fw/FwDev.php rename to php/fw/FwDev.php diff --git a/www/php/fw/FwDynamicController.php b/php/fw/FwDynamicController.php similarity index 100% rename from www/php/fw/FwDynamicController.php rename to php/fw/FwDynamicController.php diff --git a/www/php/fw/FwEntities.php b/php/fw/FwEntities.php similarity index 100% rename from www/php/fw/FwEntities.php rename to php/fw/FwEntities.php diff --git a/www/php/fw/FwExceptions.php b/php/fw/FwExceptions.php similarity index 100% rename from www/php/fw/FwExceptions.php rename to php/fw/FwExceptions.php diff --git a/www/php/fw/FwLogTypes.php b/php/fw/FwLogTypes.php similarity index 100% rename from www/php/fw/FwLogTypes.php rename to php/fw/FwLogTypes.php diff --git a/www/php/fw/FwModel.php b/php/fw/FwModel.php similarity index 100% rename from www/php/fw/FwModel.php rename to php/fw/FwModel.php diff --git a/www/php/fw/FwUpdates.php b/php/fw/FwUpdates.php similarity index 100% rename from www/php/fw/FwUpdates.php rename to php/fw/FwUpdates.php diff --git a/www/php/fw/FwVirtualController.php b/php/fw/FwVirtualController.php similarity index 100% rename from www/php/fw/FwVirtualController.php rename to php/fw/FwVirtualController.php diff --git a/www/php/fw/FwVueController.php b/php/fw/FwVueController.php similarity index 100% rename from www/php/fw/FwVueController.php rename to php/fw/FwVueController.php diff --git a/www/php/fw/ImageUtils.php b/php/fw/ImageUtils.php similarity index 100% rename from www/php/fw/ImageUtils.php rename to php/fw/ImageUtils.php diff --git a/www/php/fw/ParsePage.php b/php/fw/ParsePage.php similarity index 100% rename from www/php/fw/ParsePage.php rename to php/fw/ParsePage.php diff --git a/www/php/fw/UploadUtils.php b/php/fw/UploadUtils.php similarity index 100% rename from www/php/fw/UploadUtils.php rename to php/fw/UploadUtils.php diff --git a/www/php/fw/Utils.php b/php/fw/Utils.php similarity index 100% rename from www/php/fw/Utils.php rename to php/fw/Utils.php diff --git a/www/php/fw/db.php b/php/fw/db.php similarity index 100% rename from www/php/fw/db.php rename to php/fw/db.php diff --git a/www/php/fw/dispatcher.php b/php/fw/dispatcher.php similarity index 100% rename from www/php/fw/dispatcher.php rename to php/fw/dispatcher.php diff --git a/www/php/fw/fw.php b/php/fw/fw.php similarity index 99% rename from www/php/fw/fw.php rename to php/fw/fw.php index 66b4082..188ab3b 100644 --- a/www/php/fw/fw.php +++ b/php/fw/fw.php @@ -329,7 +329,7 @@ public function __construct() { //autoload controller/model classes public function autoload(string $class_name): void { - // bdir points to "/www/php/fw" directory + // bdir points to "/php/fw" directory $bdir = __DIR__ . '/'; // Decide if class is a controller @@ -372,9 +372,9 @@ public function autoload(string $class_name): void { } } - // Also check fw core directory itself, then "/www/php" - $dirs[] = $bdir; // "/www/php/fw" - $dirs[] = $bdir . '../'; // "/www/php" + // Also check fw core directory itself, then "/php" + $dirs[] = $bdir; // "/php/fw" + $dirs[] = $bdir . '../'; // "/php" // Check if OS is Linux for case sensitivity checks // (store this in a property if you want to avoid checking on every autoload) diff --git a/www/php/fw/lock.php b/php/fw/lock.php similarity index 100% rename from www/php/fw/lock.php rename to php/fw/lock.php diff --git a/www/php/fw/phplibman.php b/php/fw/phplibman.php similarity index 100% rename from www/php/fw/phplibman.php rename to php/fw/phplibman.php diff --git a/www/php/libman.json b/php/libman.json similarity index 100% rename from www/php/libman.json rename to php/libman.json diff --git a/www/php/models/Att.php b/php/models/Att.php similarity index 99% rename from www/php/models/Att.php rename to php/models/Att.php index 4500d2e..1132bd4 100644 --- a/www/php/models/Att.php +++ b/php/models/Att.php @@ -228,7 +228,7 @@ public function getUrlDirect(array|int $id_or_item, string $size = ''): string { } elseif ($item['storage'] == self::STORAGE_TABLE) { return $this->getUrl($item['id'], $size); // don't have direct url for table storage } else { - return $this->getUploadUrl($item['id'], $item['ext'], $size); + return $this->getUrl($item['id'], $size); } } else { $id = intval($id_or_item); diff --git a/www/php/models/AttCategories.php b/php/models/AttCategories.php similarity index 100% rename from www/php/models/AttCategories.php rename to php/models/AttCategories.php diff --git a/www/php/models/AttLinks.php b/php/models/AttLinks.php similarity index 100% rename from www/php/models/AttLinks.php rename to php/models/AttLinks.php diff --git a/www/php/models/DemoDicts.php b/php/models/DemoDicts.php similarity index 100% rename from www/php/models/DemoDicts.php rename to php/models/DemoDicts.php diff --git a/www/php/models/Demos.php b/php/models/Demos.php similarity index 100% rename from www/php/models/Demos.php rename to php/models/Demos.php diff --git a/www/php/models/DemosDemoDicts.php b/php/models/DemosDemoDicts.php similarity index 100% rename from www/php/models/DemosDemoDicts.php rename to php/models/DemosDemoDicts.php diff --git a/www/php/models/DemosItems.php b/php/models/DemosItems.php similarity index 100% rename from www/php/models/DemosItems.php rename to php/models/DemosItems.php diff --git a/www/php/models/Locks.php b/php/models/Locks.php similarity index 100% rename from www/php/models/Locks.php rename to php/models/Locks.php diff --git a/www/php/models/LookupManagerTables.php b/php/models/LookupManagerTables.php similarity index 100% rename from www/php/models/LookupManagerTables.php rename to php/models/LookupManagerTables.php diff --git a/www/php/models/Reports.php b/php/models/Reports.php similarity index 98% rename from www/php/models/Reports.php rename to php/models/Reports.php index cf30770..0b17c62 100644 --- a/www/php/models/Reports.php +++ b/php/models/Reports.php @@ -139,7 +139,7 @@ public function render($ps): void { } else { ### if Dompdf - require_once $this->fw->config->SITE_ROOT . '/php/dompdf/autoload.inc.php'; + require_once $this->fw->config->PHP_ROOT . '/dompdf/autoload.inc.php'; #use Dompdf\Dompdf; $dompdf = new Dompdf\Dompdf(); $dompdf->loadHtml($html); @@ -160,7 +160,7 @@ public function render($ps): void { // // ini_set('display_errors', '0'); #disable "Strict Standards" errors in VsWord // error_reporting(0); - // require_once $this->fw->config->SITE_ROOT . '/php/vsword/VsWord.php'; + // require_once $this->fw->config->PHP_ROOT . '/vsword/VsWord.php'; // VsWord::autoLoad(); // // $html = $this->fw->parsePage($base_dir, $common_dir . '/docx.html', $ps); diff --git a/www/php/models/Reports/Sample.php b/php/models/Reports/Sample.php similarity index 100% rename from www/php/models/Reports/Sample.php rename to php/models/Reports/Sample.php diff --git a/www/php/models/Roles/Permissions.php b/php/models/Roles/Permissions.php similarity index 100% rename from www/php/models/Roles/Permissions.php rename to php/models/Roles/Permissions.php diff --git a/www/php/models/Roles/Resources.php b/php/models/Roles/Resources.php similarity index 100% rename from www/php/models/Roles/Resources.php rename to php/models/Roles/Resources.php diff --git a/www/php/models/Roles/Roles.php b/php/models/Roles/Roles.php similarity index 100% rename from www/php/models/Roles/Roles.php rename to php/models/Roles/Roles.php diff --git a/www/php/models/Roles/RolesResourcesPermissions.php b/php/models/Roles/RolesResourcesPermissions.php similarity index 100% rename from www/php/models/Roles/RolesResourcesPermissions.php rename to php/models/Roles/RolesResourcesPermissions.php diff --git a/www/php/models/Roles/UsersRoles.php b/php/models/Roles/UsersRoles.php similarity index 100% rename from www/php/models/Roles/UsersRoles.php rename to php/models/Roles/UsersRoles.php diff --git a/www/php/models/S3.php b/php/models/S3.php similarity index 100% rename from www/php/models/S3.php rename to php/models/S3.php diff --git a/www/php/models/Settings.php b/php/models/Settings.php similarity index 100% rename from www/php/models/Settings.php rename to php/models/Settings.php diff --git a/www/php/models/Spages.php b/php/models/Spages.php similarity index 100% rename from www/php/models/Spages.php rename to php/models/Spages.php diff --git a/www/php/models/UserFilters.php b/php/models/UserFilters.php similarity index 100% rename from www/php/models/UserFilters.php rename to php/models/UserFilters.php diff --git a/www/php/models/UserLists.php b/php/models/UserLists.php similarity index 100% rename from www/php/models/UserLists.php rename to php/models/UserLists.php diff --git a/www/php/models/UserViews.php b/php/models/UserViews.php similarity index 100% rename from www/php/models/UserViews.php rename to php/models/UserViews.php diff --git a/www/php/models/Users.php b/php/models/Users.php similarity index 100% rename from www/php/models/Users.php rename to php/models/Users.php diff --git a/www/php/tests/DatabaseTestCase.php b/php/tests/DatabaseTestCase.php similarity index 100% rename from www/php/tests/DatabaseTestCase.php rename to php/tests/DatabaseTestCase.php diff --git a/www/php/tests/FrameworkTestCase.php b/php/tests/FrameworkTestCase.php similarity index 100% rename from www/php/tests/FrameworkTestCase.php rename to php/tests/FrameworkTestCase.php diff --git a/www/php/tests/TestHostResolver.php b/php/tests/TestHostResolver.php similarity index 97% rename from www/php/tests/TestHostResolver.php rename to php/tests/TestHostResolver.php index 0795a1f..c0d1f56 100644 --- a/www/php/tests/TestHostResolver.php +++ b/php/tests/TestHostResolver.php @@ -95,7 +95,7 @@ public static function extractCliHostArgument(array $argv): ?string { } private static function envFilePath(): string { - return dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . '.env.test.local'; + return dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . '.env.test.local'; } private static function configsDir(): string { @@ -195,7 +195,7 @@ private static function missingHostMessage(): string { '1. CLI hostname argument.', '2. OSAFW_TEST_HOST or OSAFW_CLI_HOST environment variable.', "3. {$envFile}.", - '4. Auto-detect from www/php/configs/ using localhost.php, osafw-php.lo.php, or lo-* local configs.', + '4. Auto-detect from php/configs/ using localhost.php, osafw-php.lo.php, or lo-* local configs.', '', 'Create /.env.test.local with:', 'OSAFW_TEST_HOST=localhost', diff --git a/www/php/tests/bootstrap.php b/php/tests/bootstrap.php similarity index 94% rename from www/php/tests/bootstrap.php rename to php/tests/bootstrap.php index 6c575c9..6b02892 100644 --- a/www/php/tests/bootstrap.php +++ b/php/tests/bootstrap.php @@ -22,7 +22,7 @@ fw::initOffline(__FILE__); $fw = fw::i(); -$logFile = dirname(__DIR__, 3) . '/logs/test.log'; +$logFile = dirname(__DIR__, 2) . '/logs/test.log'; $fw->config->LOG_DESTINATION = $logFile; $fw->config->LOG_LEVEL = 'DEBUG'; diff --git a/www/php/tests/controllers/ControllerTestCase.php b/php/tests/controllers/ControllerTestCase.php similarity index 100% rename from www/php/tests/controllers/ControllerTestCase.php rename to php/tests/controllers/ControllerTestCase.php diff --git a/www/php/tests/controllers/TestControllerTest.php b/php/tests/controllers/TestControllerTest.php similarity index 100% rename from www/php/tests/controllers/TestControllerTest.php rename to php/tests/controllers/TestControllerTest.php diff --git a/www/php/tests/models/UsersModelTest.php b/php/tests/models/UsersModelTest.php similarity index 100% rename from www/php/tests/models/UsersModelTest.php rename to php/tests/models/UsersModelTest.php diff --git a/www/php/tests/run-local-phpunit.php b/php/tests/run-local-phpunit.php similarity index 90% rename from www/php/tests/run-local-phpunit.php rename to php/tests/run-local-phpunit.php index 0056ede..86fecca 100644 --- a/www/php/tests/run-local-phpunit.php +++ b/php/tests/run-local-phpunit.php @@ -34,14 +34,14 @@ $autoload = $phpDir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; if (!is_file($autoload)) { - fwrite(STDERR, 'Composer dependencies are not installed. Run composer install from www/php with dev dependencies enabled.' . PHP_EOL); + fwrite(STDERR, 'Composer dependencies are not installed. Run composer install from php with dev dependencies enabled.' . PHP_EOL); exit(1); } require $autoload; if (!class_exists(PHPUnit\TextUI\Application::class)) { - fwrite(STDERR, 'PHPUnit is not installed. Run composer install from www/php with dev dependencies enabled.' . PHP_EOL); + fwrite(STDERR, 'PHPUnit is not installed. Run composer install from php with dev dependencies enabled.' . PHP_EOL); exit(1); } diff --git a/www/php/tests/unit/FrameworkBootstrapTest.php b/php/tests/unit/FrameworkBootstrapTest.php similarity index 100% rename from www/php/tests/unit/FrameworkBootstrapTest.php rename to php/tests/unit/FrameworkBootstrapTest.php diff --git a/www/phpminiadmin.php b/php/tools/phpminiadmin.php similarity index 100% rename from www/phpminiadmin.php rename to php/tools/phpminiadmin.php diff --git a/php/tools/phpminiconfig.php b/php/tools/phpminiconfig.php new file mode 100644 index 0000000..8bbe1cd --- /dev/null +++ b/php/tools/phpminiconfig.php @@ -0,0 +1,36 @@ + $CONFIG['DB']['USER'], #required + 'pwd' => $CONFIG['DB']['PWD'], #required + 'db' => $CONFIG['DB']['DBNAME'], #optional, default DB + 'host' => $CONFIG['DB']['HOST'], #optional + 'port' => $CONFIG['DB']['PORT'], #optional + 'chset' => "utf8", #optional, default charset + ); + $_SESSION['is_logged'] = true; + loadcfg(); + if (!isset($_REQUEST['q'])) { + $_REQUEST['XSS'] = $_SESSION['XSS']; + $_REQUEST['q'] = b64e('SHOW TABLE STATUS'); + } +} else { + $ACCESS_PWD = ''; #set Access password here to enable access to database as non-logged admin + if (!$ACCESS_PWD) { + rw("Set \$ACCESS_PWD or login to site as an Administrator"); + exit; + } +} + +?> diff --git a/www/php/vendor/autoload.php b/php/vendor/autoload.php similarity index 100% rename from www/php/vendor/autoload.php rename to php/vendor/autoload.php diff --git a/www/php/vendor/composer/ClassLoader.php b/php/vendor/composer/ClassLoader.php similarity index 100% rename from www/php/vendor/composer/ClassLoader.php rename to php/vendor/composer/ClassLoader.php diff --git a/www/php/vendor/composer/InstalledVersions.php b/php/vendor/composer/InstalledVersions.php similarity index 100% rename from www/php/vendor/composer/InstalledVersions.php rename to php/vendor/composer/InstalledVersions.php diff --git a/www/php/vendor/composer/LICENSE b/php/vendor/composer/LICENSE similarity index 100% rename from www/php/vendor/composer/LICENSE rename to php/vendor/composer/LICENSE diff --git a/www/php/vendor/composer/autoload_classmap.php b/php/vendor/composer/autoload_classmap.php similarity index 100% rename from www/php/vendor/composer/autoload_classmap.php rename to php/vendor/composer/autoload_classmap.php diff --git a/www/php/vendor/composer/autoload_namespaces.php b/php/vendor/composer/autoload_namespaces.php similarity index 100% rename from www/php/vendor/composer/autoload_namespaces.php rename to php/vendor/composer/autoload_namespaces.php diff --git a/www/php/vendor/composer/autoload_psr4.php b/php/vendor/composer/autoload_psr4.php similarity index 100% rename from www/php/vendor/composer/autoload_psr4.php rename to php/vendor/composer/autoload_psr4.php diff --git a/www/php/vendor/composer/autoload_real.php b/php/vendor/composer/autoload_real.php similarity index 100% rename from www/php/vendor/composer/autoload_real.php rename to php/vendor/composer/autoload_real.php diff --git a/www/php/vendor/composer/autoload_static.php b/php/vendor/composer/autoload_static.php similarity index 100% rename from www/php/vendor/composer/autoload_static.php rename to php/vendor/composer/autoload_static.php diff --git a/www/php/vendor/composer/installed.json b/php/vendor/composer/installed.json similarity index 100% rename from www/php/vendor/composer/installed.json rename to php/vendor/composer/installed.json diff --git a/www/php/vendor/composer/installed.php b/php/vendor/composer/installed.php similarity index 100% rename from www/php/vendor/composer/installed.php rename to php/vendor/composer/installed.php diff --git a/www/php/vendor/composer/platform_check.php b/php/vendor/composer/platform_check.php similarity index 100% rename from www/php/vendor/composer/platform_check.php rename to php/vendor/composer/platform_check.php diff --git a/www/php/vendor/phpmailer/phpmailer/.editorconfig b/php/vendor/phpmailer/phpmailer/.editorconfig similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/.editorconfig rename to php/vendor/phpmailer/phpmailer/.editorconfig diff --git a/www/php/vendor/phpmailer/phpmailer/COMMITMENT b/php/vendor/phpmailer/phpmailer/COMMITMENT similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/COMMITMENT rename to php/vendor/phpmailer/phpmailer/COMMITMENT diff --git a/www/php/vendor/phpmailer/phpmailer/LICENSE b/php/vendor/phpmailer/phpmailer/LICENSE similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/LICENSE rename to php/vendor/phpmailer/phpmailer/LICENSE diff --git a/www/php/vendor/phpmailer/phpmailer/README.md b/php/vendor/phpmailer/phpmailer/README.md similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/README.md rename to php/vendor/phpmailer/phpmailer/README.md diff --git a/www/php/vendor/phpmailer/phpmailer/SECURITY.md b/php/vendor/phpmailer/phpmailer/SECURITY.md similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/SECURITY.md rename to php/vendor/phpmailer/phpmailer/SECURITY.md diff --git a/www/php/vendor/phpmailer/phpmailer/VERSION b/php/vendor/phpmailer/phpmailer/VERSION similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/VERSION rename to php/vendor/phpmailer/phpmailer/VERSION diff --git a/www/php/vendor/phpmailer/phpmailer/composer.json b/php/vendor/phpmailer/phpmailer/composer.json similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/composer.json rename to php/vendor/phpmailer/phpmailer/composer.json diff --git a/www/php/vendor/phpmailer/phpmailer/get_oauth_token.php b/php/vendor/phpmailer/phpmailer/get_oauth_token.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/get_oauth_token.php rename to php/vendor/phpmailer/phpmailer/get_oauth_token.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ar.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ar.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ar.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ar.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ba.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ba.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ba.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ba.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-da.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-da.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-da.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-da.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-el.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-el.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-el.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-el.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-eo.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-eo.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-eo.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-eo.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fo.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fo.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fo.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fo.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-gl.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-gl.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-gl.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-gl.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hy.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hy.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hy.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-hy.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ms.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ms.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ms.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ms.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-nl.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-nl.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-nl.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-nl.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-uk.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-uk.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-uk.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-uk.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php diff --git a/www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php b/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php rename to php/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php diff --git a/www/php/vendor/phpmailer/phpmailer/src/DSNConfigurator.php b/php/vendor/phpmailer/phpmailer/src/DSNConfigurator.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/src/DSNConfigurator.php rename to php/vendor/phpmailer/phpmailer/src/DSNConfigurator.php diff --git a/www/php/vendor/phpmailer/phpmailer/src/Exception.php b/php/vendor/phpmailer/phpmailer/src/Exception.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/src/Exception.php rename to php/vendor/phpmailer/phpmailer/src/Exception.php diff --git a/www/php/vendor/phpmailer/phpmailer/src/OAuth.php b/php/vendor/phpmailer/phpmailer/src/OAuth.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/src/OAuth.php rename to php/vendor/phpmailer/phpmailer/src/OAuth.php diff --git a/www/php/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php b/php/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php rename to php/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php diff --git a/www/php/vendor/phpmailer/phpmailer/src/PHPMailer.php b/php/vendor/phpmailer/phpmailer/src/PHPMailer.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/src/PHPMailer.php rename to php/vendor/phpmailer/phpmailer/src/PHPMailer.php diff --git a/www/php/vendor/phpmailer/phpmailer/src/POP3.php b/php/vendor/phpmailer/phpmailer/src/POP3.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/src/POP3.php rename to php/vendor/phpmailer/phpmailer/src/POP3.php diff --git a/www/php/vendor/phpmailer/phpmailer/src/SMTP.php b/php/vendor/phpmailer/phpmailer/src/SMTP.php similarity index 100% rename from www/php/vendor/phpmailer/phpmailer/src/SMTP.php rename to php/vendor/phpmailer/phpmailer/src/SMTP.php diff --git a/phpunit.xml b/phpunit.xml index 6a09e52..115f865 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,14 +1,14 @@ - + - www/php/tests/unit + php/tests/unit - www/php/tests/models + php/tests/models - www/php/tests/controllers + php/tests/controllers diff --git a/www/template/admin/activitylogs/url.html b/template/admin/activitylogs/url.html similarity index 100% rename from www/template/admin/activitylogs/url.html rename to template/admin/activitylogs/url.html diff --git a/www/template/admin/att/index/list_filter_more.html b/template/admin/att/index/list_filter_more.html similarity index 100% rename from www/template/admin/att/index/list_filter_more.html rename to template/admin/att/index/list_filter_more.html diff --git a/www/template/admin/att/index/list_table.html b/template/admin/att/index/list_table.html similarity index 100% rename from www/template/admin/att/index/list_table.html rename to template/admin/att/index/list_table.html diff --git a/www/template/admin/att/index/main.html b/template/admin/att/index/main.html similarity index 100% rename from www/template/admin/att/index/main.html rename to template/admin/att/index/main.html diff --git a/www/template/admin/att/index/onload.js b/template/admin/att/index/onload.js similarity index 100% rename from www/template/admin/att/index/onload.js rename to template/admin/att/index/onload.js diff --git a/www/template/admin/att/index/quick_upload.html b/template/admin/att/index/quick_upload.html similarity index 100% rename from www/template/admin/att/index/quick_upload.html rename to template/admin/att/index/quick_upload.html diff --git a/www/template/admin/att/index/row_click_url.html b/template/admin/att/index/row_click_url.html similarity index 100% rename from www/template/admin/att/index/row_click_url.html rename to template/admin/att/index/row_click_url.html diff --git a/www/template/admin/att/index/title.html b/template/admin/att/index/title.html similarity index 100% rename from www/template/admin/att/index/title.html rename to template/admin/att/index/title.html diff --git a/www/template/admin/att/select/main.html b/template/admin/att/select/main.html similarity index 100% rename from www/template/admin/att/select/main.html rename to template/admin/att/select/main.html diff --git a/www/template/admin/att/select/modal.html b/template/admin/att/select/modal.html similarity index 100% rename from www/template/admin/att/select/modal.html rename to template/admin/att/select/modal.html diff --git a/www/template/admin/att/select/onload.js b/template/admin/att/select/onload.js similarity index 100% rename from www/template/admin/att/select/onload.js rename to template/admin/att/select/onload.js diff --git a/www/template/admin/att/select/title.html b/template/admin/att/select/title.html similarity index 100% rename from www/template/admin/att/select/title.html rename to template/admin/att/select/title.html diff --git a/www/template/admin/att/showform/form.html b/template/admin/att/showform/form.html similarity index 100% rename from www/template/admin/att/showform/form.html rename to template/admin/att/showform/form.html diff --git a/www/template/admin/att/showform/form_left.html b/template/admin/att/showform/form_left.html similarity index 100% rename from www/template/admin/att/showform/form_left.html rename to template/admin/att/showform/form_left.html diff --git a/www/template/admin/att/showform/form_right.html b/template/admin/att/showform/form_right.html similarity index 100% rename from www/template/admin/att/showform/form_right.html rename to template/admin/att/showform/form_right.html diff --git a/www/template/admin/att/showform/main.html b/template/admin/att/showform/main.html similarity index 100% rename from www/template/admin/att/showform/main.html rename to template/admin/att/showform/main.html diff --git a/www/template/admin/att/showform/title.html b/template/admin/att/showform/title.html similarity index 100% rename from www/template/admin/att/showform/title.html rename to template/admin/att/showform/title.html diff --git a/www/template/admin/att/status.sel b/template/admin/att/status.sel similarity index 100% rename from www/template/admin/att/status.sel rename to template/admin/att/status.sel diff --git a/www/template/admin/att/url.html b/template/admin/att/url.html similarity index 100% rename from www/template/admin/att/url.html rename to template/admin/att/url.html diff --git a/www/template/admin/db/index/a_end.html b/template/admin/db/index/a_end.html similarity index 100% rename from www/template/admin/db/index/a_end.html rename to template/admin/db/index/a_end.html diff --git a/www/template/admin/db/index/main.html b/template/admin/db/index/main.html similarity index 100% rename from www/template/admin/db/index/main.html rename to template/admin/db/index/main.html diff --git a/www/template/admin/db/index/results.html b/template/admin/db/index/results.html similarity index 100% rename from www/template/admin/db/index/results.html rename to template/admin/db/index/results.html diff --git a/www/template/admin/db/index/select_link.html b/template/admin/db/index/select_link.html similarity index 100% rename from www/template/admin/db/index/select_link.html rename to template/admin/db/index/select_link.html diff --git a/www/template/admin/db/index/title.html b/template/admin/db/index/title.html similarity index 100% rename from www/template/admin/db/index/title.html rename to template/admin/db/index/title.html diff --git a/www/template/admin/db/index/url.html b/template/admin/db/index/url.html similarity index 100% rename from www/template/admin/db/index/url.html rename to template/admin/db/index/url.html diff --git a/www/template/admin/db/url.html b/template/admin/db/url.html similarity index 100% rename from www/template/admin/db/url.html rename to template/admin/db/url.html diff --git a/www/template/admin/demodicts/index/list_filter_more.html b/template/admin/demodicts/index/list_filter_more.html similarity index 100% rename from www/template/admin/demodicts/index/list_filter_more.html rename to template/admin/demodicts/index/list_filter_more.html diff --git a/www/template/admin/demodicts/index/list_table.html b/template/admin/demodicts/index/list_table.html similarity index 100% rename from www/template/admin/demodicts/index/list_table.html rename to template/admin/demodicts/index/list_table.html diff --git a/www/template/admin/demodicts/index/main.html b/template/admin/demodicts/index/main.html similarity index 100% rename from www/template/admin/demodicts/index/main.html rename to template/admin/demodicts/index/main.html diff --git a/www/template/admin/demodicts/index/row_click_url.html b/template/admin/demodicts/index/row_click_url.html similarity index 100% rename from www/template/admin/demodicts/index/row_click_url.html rename to template/admin/demodicts/index/row_click_url.html diff --git a/www/template/admin/demodicts/index/title.html b/template/admin/demodicts/index/title.html similarity index 100% rename from www/template/admin/demodicts/index/title.html rename to template/admin/demodicts/index/title.html diff --git a/www/template/admin/demodicts/showform/form.html b/template/admin/demodicts/showform/form.html similarity index 100% rename from www/template/admin/demodicts/showform/form.html rename to template/admin/demodicts/showform/form.html diff --git a/www/template/admin/demodicts/showform/main.html b/template/admin/demodicts/showform/main.html similarity index 100% rename from www/template/admin/demodicts/showform/main.html rename to template/admin/demodicts/showform/main.html diff --git a/www/template/admin/demodicts/showform/title.html b/template/admin/demodicts/showform/title.html similarity index 100% rename from www/template/admin/demodicts/showform/title.html rename to template/admin/demodicts/showform/title.html diff --git a/www/template/admin/demodicts/url.html b/template/admin/demodicts/url.html similarity index 100% rename from www/template/admin/demodicts/url.html rename to template/admin/demodicts/url.html diff --git a/www/template/admin/demos/index/btn_multidel_more.html b/template/admin/demos/index/btn_multidel_more.html similarity index 100% rename from www/template/admin/demos/index/btn_multidel_more.html rename to template/admin/demos/index/btn_multidel_more.html diff --git a/www/template/admin/demos/index/list_filter_more.html b/template/admin/demos/index/list_filter_more.html similarity index 100% rename from www/template/admin/demos/index/list_filter_more.html rename to template/admin/demos/index/list_filter_more.html diff --git a/www/template/admin/demos/index/list_table.html b/template/admin/demos/index/list_table.html similarity index 100% rename from www/template/admin/demos/index/list_table.html rename to template/admin/demos/index/list_table.html diff --git a/www/template/admin/demos/index/load_script.html b/template/admin/demos/index/load_script.html similarity index 100% rename from www/template/admin/demos/index/load_script.html rename to template/admin/demos/index/load_script.html diff --git a/www/template/admin/demos/index/main.html b/template/admin/demos/index/main.html similarity index 100% rename from www/template/admin/demos/index/main.html rename to template/admin/demos/index/main.html diff --git a/www/template/admin/demos/index/onload.js b/template/admin/demos/index/onload.js similarity index 100% rename from www/template/admin/demos/index/onload.js rename to template/admin/demos/index/onload.js diff --git a/www/template/admin/demos/index/return_url.html b/template/admin/demos/index/return_url.html similarity index 100% rename from www/template/admin/demos/index/return_url.html rename to template/admin/demos/index/return_url.html diff --git a/www/template/admin/demos/index/row_click_url.html b/template/admin/demos/index/row_click_url.html similarity index 100% rename from www/template/admin/demos/index/row_click_url.html rename to template/admin/demos/index/row_click_url.html diff --git a/www/template/admin/demos/index/title.html b/template/admin/demos/index/title.html similarity index 100% rename from www/template/admin/demos/index/title.html rename to template/admin/demos/index/title.html diff --git a/www/template/admin/demos/index/userlist_entity.html b/template/admin/demos/index/userlist_entity.html similarity index 100% rename from www/template/admin/demos/index/userlist_entity.html rename to template/admin/demos/index/userlist_entity.html diff --git a/www/template/admin/demos/show/btn_std_more.html b/template/admin/demos/show/btn_std_more.html similarity index 100% rename from www/template/admin/demos/show/btn_std_more.html rename to template/admin/demos/show/btn_std_more.html diff --git a/www/template/admin/demos/show/form.html b/template/admin/demos/show/form.html similarity index 100% rename from www/template/admin/demos/show/form.html rename to template/admin/demos/show/form.html diff --git a/www/template/admin/demos/show/form_left.html b/template/admin/demos/show/form_left.html similarity index 100% rename from www/template/admin/demos/show/form_left.html rename to template/admin/demos/show/form_left.html diff --git a/www/template/admin/demos/show/form_right.html b/template/admin/demos/show/form_right.html similarity index 100% rename from www/template/admin/demos/show/form_right.html rename to template/admin/demos/show/form_right.html diff --git a/www/template/admin/demos/show/load_script.html b/template/admin/demos/show/load_script.html similarity index 100% rename from www/template/admin/demos/show/load_script.html rename to template/admin/demos/show/load_script.html diff --git a/www/template/admin/demos/show/main.html b/template/admin/demos/show/main.html similarity index 100% rename from www/template/admin/demos/show/main.html rename to template/admin/demos/show/main.html diff --git a/www/template/admin/demos/show/onload.js b/template/admin/demos/show/onload.js similarity index 100% rename from www/template/admin/demos/show/onload.js rename to template/admin/demos/show/onload.js diff --git a/www/template/admin/demos/show/return_url.html b/template/admin/demos/show/return_url.html similarity index 100% rename from www/template/admin/demos/show/return_url.html rename to template/admin/demos/show/return_url.html diff --git a/www/template/admin/demos/show/title.html b/template/admin/demos/show/title.html similarity index 100% rename from www/template/admin/demos/show/title.html rename to template/admin/demos/show/title.html diff --git a/www/template/admin/demos/show/userlist_entity.html b/template/admin/demos/show/userlist_entity.html similarity index 100% rename from www/template/admin/demos/show/userlist_entity.html rename to template/admin/demos/show/userlist_entity.html diff --git a/www/template/admin/demos/showform/btn_std_more.html b/template/admin/demos/showform/btn_std_more.html similarity index 100% rename from www/template/admin/demos/showform/btn_std_more.html rename to template/admin/demos/showform/btn_std_more.html diff --git a/www/template/admin/demos/showform/form.html b/template/admin/demos/showform/form.html similarity index 100% rename from www/template/admin/demos/showform/form.html rename to template/admin/demos/showform/form.html diff --git a/www/template/admin/demos/showform/form_left.html b/template/admin/demos/showform/form_left.html similarity index 100% rename from www/template/admin/demos/showform/form_left.html rename to template/admin/demos/showform/form_left.html diff --git a/www/template/admin/demos/showform/form_right.html b/template/admin/demos/showform/form_right.html similarity index 100% rename from www/template/admin/demos/showform/form_right.html rename to template/admin/demos/showform/form_right.html diff --git a/www/template/admin/demos/showform/load_script.html b/template/admin/demos/showform/load_script.html similarity index 100% rename from www/template/admin/demos/showform/load_script.html rename to template/admin/demos/showform/load_script.html diff --git a/www/template/admin/demos/showform/main.html b/template/admin/demos/showform/main.html similarity index 100% rename from www/template/admin/demos/showform/main.html rename to template/admin/demos/showform/main.html diff --git a/www/template/admin/demos/showform/onload.js b/template/admin/demos/showform/onload.js similarity index 100% rename from www/template/admin/demos/showform/onload.js rename to template/admin/demos/showform/onload.js diff --git a/www/template/admin/demos/showform/title.html b/template/admin/demos/showform/title.html similarity index 100% rename from www/template/admin/demos/showform/title.html rename to template/admin/demos/showform/title.html diff --git a/www/template/admin/demos/url.html b/template/admin/demos/url.html similarity index 100% rename from www/template/admin/demos/url.html rename to template/admin/demos/url.html diff --git a/www/template/admin/demosdynamic/config.json b/template/admin/demosdynamic/config.json similarity index 100% rename from www/template/admin/demosdynamic/config.json rename to template/admin/demosdynamic/config.json diff --git a/www/template/admin/demosdynamic/index/btn_multidel_more.html b/template/admin/demosdynamic/index/btn_multidel_more.html similarity index 100% rename from www/template/admin/demosdynamic/index/btn_multidel_more.html rename to template/admin/demosdynamic/index/btn_multidel_more.html diff --git a/www/template/admin/demosdynamic/index/col_custom.html b/template/admin/demosdynamic/index/col_custom.html similarity index 100% rename from www/template/admin/demosdynamic/index/col_custom.html rename to template/admin/demosdynamic/index/col_custom.html diff --git a/www/template/admin/demosdynamic/index/list_filter_more.html b/template/admin/demosdynamic/index/list_filter_more.html similarity index 100% rename from www/template/admin/demosdynamic/index/list_filter_more.html rename to template/admin/demosdynamic/index/list_filter_more.html diff --git a/www/template/admin/demosdynamic/index/list_row_btn.html b/template/admin/demosdynamic/index/list_row_btn.html similarity index 100% rename from www/template/admin/demosdynamic/index/list_row_btn.html rename to template/admin/demosdynamic/index/list_row_btn.html diff --git a/www/template/admin/demosdynamic/index/list_table.html b/template/admin/demosdynamic/index/list_table.html similarity index 100% rename from www/template/admin/demosdynamic/index/list_table.html rename to template/admin/demosdynamic/index/list_table.html diff --git a/www/template/admin/demosdynamic/index/load_script.html b/template/admin/demosdynamic/index/load_script.html similarity index 100% rename from www/template/admin/demosdynamic/index/load_script.html rename to template/admin/demosdynamic/index/load_script.html diff --git a/www/template/admin/demosdynamic/index/main.html b/template/admin/demosdynamic/index/main.html similarity index 100% rename from www/template/admin/demosdynamic/index/main.html rename to template/admin/demosdynamic/index/main.html diff --git a/www/template/admin/demosdynamic/index/onload.js b/template/admin/demosdynamic/index/onload.js similarity index 100% rename from www/template/admin/demosdynamic/index/onload.js rename to template/admin/demosdynamic/index/onload.js diff --git a/www/template/admin/demosdynamic/index/return_url.html b/template/admin/demosdynamic/index/return_url.html similarity index 100% rename from www/template/admin/demosdynamic/index/return_url.html rename to template/admin/demosdynamic/index/return_url.html diff --git a/www/template/admin/demosdynamic/index/row_click_url.html b/template/admin/demosdynamic/index/row_click_url.html similarity index 100% rename from www/template/admin/demosdynamic/index/row_click_url.html rename to template/admin/demosdynamic/index/row_click_url.html diff --git a/www/template/admin/demosdynamic/index/title.html b/template/admin/demosdynamic/index/title.html similarity index 100% rename from www/template/admin/demosdynamic/index/title.html rename to template/admin/demosdynamic/index/title.html diff --git a/www/template/admin/demosdynamic/index/userlist_entity.html b/template/admin/demosdynamic/index/userlist_entity.html similarity index 100% rename from www/template/admin/demosdynamic/index/userlist_entity.html rename to template/admin/demosdynamic/index/userlist_entity.html diff --git a/www/template/admin/demosdynamic/show/btn_std_more.html b/template/admin/demosdynamic/show/btn_std_more.html similarity index 100% rename from www/template/admin/demosdynamic/show/btn_std_more.html rename to template/admin/demosdynamic/show/btn_std_more.html diff --git a/www/template/admin/demosdynamic/show/custom_field.html b/template/admin/demosdynamic/show/custom_field.html similarity index 100% rename from www/template/admin/demosdynamic/show/custom_field.html rename to template/admin/demosdynamic/show/custom_field.html diff --git a/www/template/admin/demosdynamic/show/form.html b/template/admin/demosdynamic/show/form.html similarity index 100% rename from www/template/admin/demosdynamic/show/form.html rename to template/admin/demosdynamic/show/form.html diff --git a/www/template/admin/demosdynamic/show/load_script.html b/template/admin/demosdynamic/show/load_script.html similarity index 100% rename from www/template/admin/demosdynamic/show/load_script.html rename to template/admin/demosdynamic/show/load_script.html diff --git a/www/template/admin/demosdynamic/show/main.html b/template/admin/demosdynamic/show/main.html similarity index 100% rename from www/template/admin/demosdynamic/show/main.html rename to template/admin/demosdynamic/show/main.html diff --git a/www/template/admin/demosdynamic/show/onload.js b/template/admin/demosdynamic/show/onload.js similarity index 100% rename from www/template/admin/demosdynamic/show/onload.js rename to template/admin/demosdynamic/show/onload.js diff --git a/www/template/admin/demosdynamic/show/return_url.html b/template/admin/demosdynamic/show/return_url.html similarity index 100% rename from www/template/admin/demosdynamic/show/return_url.html rename to template/admin/demosdynamic/show/return_url.html diff --git a/www/template/admin/demosdynamic/show/subtable.html b/template/admin/demosdynamic/show/subtable.html similarity index 100% rename from www/template/admin/demosdynamic/show/subtable.html rename to template/admin/demosdynamic/show/subtable.html diff --git a/www/template/admin/demosdynamic/show/subtable_demos_items.html b/template/admin/demosdynamic/show/subtable_demos_items.html similarity index 100% rename from www/template/admin/demosdynamic/show/subtable_demos_items.html rename to template/admin/demosdynamic/show/subtable_demos_items.html diff --git a/www/template/admin/demosdynamic/show/title.html b/template/admin/demosdynamic/show/title.html similarity index 100% rename from www/template/admin/demosdynamic/show/title.html rename to template/admin/demosdynamic/show/title.html diff --git a/www/template/admin/demosdynamic/show/userlist_entity.html b/template/admin/demosdynamic/show/userlist_entity.html similarity index 100% rename from www/template/admin/demosdynamic/show/userlist_entity.html rename to template/admin/demosdynamic/show/userlist_entity.html diff --git a/www/template/admin/demosdynamic/showform/btn_std_more.html b/template/admin/demosdynamic/showform/btn_std_more.html similarity index 100% rename from www/template/admin/demosdynamic/showform/btn_std_more.html rename to template/admin/demosdynamic/showform/btn_std_more.html diff --git a/www/template/admin/demosdynamic/showform/form.html b/template/admin/demosdynamic/showform/form.html similarity index 100% rename from www/template/admin/demosdynamic/showform/form.html rename to template/admin/demosdynamic/showform/form.html diff --git a/www/template/admin/demosdynamic/showform/load_script.html b/template/admin/demosdynamic/showform/load_script.html similarity index 100% rename from www/template/admin/demosdynamic/showform/load_script.html rename to template/admin/demosdynamic/showform/load_script.html diff --git a/www/template/admin/demosdynamic/showform/main.html b/template/admin/demosdynamic/showform/main.html similarity index 100% rename from www/template/admin/demosdynamic/showform/main.html rename to template/admin/demosdynamic/showform/main.html diff --git a/www/template/admin/demosdynamic/showform/onload.js b/template/admin/demosdynamic/showform/onload.js similarity index 100% rename from www/template/admin/demosdynamic/showform/onload.js rename to template/admin/demosdynamic/showform/onload.js diff --git a/www/template/admin/demosdynamic/showform/subtable.html b/template/admin/demosdynamic/showform/subtable.html similarity index 100% rename from www/template/admin/demosdynamic/showform/subtable.html rename to template/admin/demosdynamic/showform/subtable.html diff --git a/www/template/admin/demosdynamic/showform/subtable_demos_items.html b/template/admin/demosdynamic/showform/subtable_demos_items.html similarity index 100% rename from www/template/admin/demosdynamic/showform/subtable_demos_items.html rename to template/admin/demosdynamic/showform/subtable_demos_items.html diff --git a/www/template/admin/demosdynamic/showform/title.html b/template/admin/demosdynamic/showform/title.html similarity index 100% rename from www/template/admin/demosdynamic/showform/title.html rename to template/admin/demosdynamic/showform/title.html diff --git a/www/template/admin/demosdynamic/url.html b/template/admin/demosdynamic/url.html similarity index 100% rename from www/template/admin/demosdynamic/url.html rename to template/admin/demosdynamic/url.html diff --git a/www/template/admin/demosvue/config.json b/template/admin/demosvue/config.json similarity index 100% rename from www/template/admin/demosvue/config.json rename to template/admin/demosvue/config.json diff --git a/www/template/admin/demosvue/index/app.js b/template/admin/demosvue/index/app.js similarity index 100% rename from www/template/admin/demosvue/index/app.js rename to template/admin/demosvue/index/app.js diff --git a/www/template/admin/demosvue/index/main.html b/template/admin/demosvue/index/main.html similarity index 100% rename from www/template/admin/demosvue/index/main.html rename to template/admin/demosvue/index/main.html diff --git a/www/template/admin/demosvue/index/store.js b/template/admin/demosvue/index/store.js similarity index 100% rename from www/template/admin/demosvue/index/store.js rename to template/admin/demosvue/index/store.js diff --git a/www/template/admin/demosvue/index/title.html b/template/admin/demosvue/index/title.html similarity index 100% rename from www/template/admin/demosvue/index/title.html rename to template/admin/demosvue/index/title.html diff --git a/www/template/admin/demosvue/index/vue/subtable_demos_items.html b/template/admin/demosvue/index/vue/subtable_demos_items.html similarity index 100% rename from www/template/admin/demosvue/index/vue/subtable_demos_items.html rename to template/admin/demosvue/index/vue/subtable_demos_items.html diff --git a/www/template/admin/demosvue/index/vue_components.html b/template/admin/demosvue/index/vue_components.html similarity index 100% rename from www/template/admin/demosvue/index/vue_components.html rename to template/admin/demosvue/index/vue_components.html diff --git a/www/template/admin/demosvue/url.html b/template/admin/demosvue/url.html similarity index 100% rename from www/template/admin/demosvue/url.html rename to template/admin/demosvue/url.html diff --git a/www/template/admin/fwupdates/config.json b/template/admin/fwupdates/config.json similarity index 100% rename from www/template/admin/fwupdates/config.json rename to template/admin/fwupdates/config.json diff --git a/www/template/admin/lookups/index/head.css b/template/admin/lookups/index/head.css similarity index 100% rename from www/template/admin/lookups/index/head.css rename to template/admin/lookups/index/head.css diff --git a/www/template/admin/lookups/index/list_table.html b/template/admin/lookups/index/list_table.html similarity index 100% rename from www/template/admin/lookups/index/list_table.html rename to template/admin/lookups/index/list_table.html diff --git a/www/template/admin/lookups/index/main.html b/template/admin/lookups/index/main.html similarity index 100% rename from www/template/admin/lookups/index/main.html rename to template/admin/lookups/index/main.html diff --git a/www/template/admin/lookups/index/row_click_url.html b/template/admin/lookups/index/row_click_url.html similarity index 100% rename from www/template/admin/lookups/index/row_click_url.html rename to template/admin/lookups/index/row_click_url.html diff --git a/www/template/admin/lookups/index/title.html b/template/admin/lookups/index/title.html similarity index 100% rename from www/template/admin/lookups/index/title.html rename to template/admin/lookups/index/title.html diff --git a/www/template/admin/lookups/url.html b/template/admin/lookups/url.html similarity index 100% rename from www/template/admin/lookups/url.html rename to template/admin/lookups/url.html diff --git a/www/template/admin/reports/common/btn_apply_print.html b/template/admin/reports/common/btn_apply_print.html similarity index 100% rename from www/template/admin/reports/common/btn_apply_print.html rename to template/admin/reports/common/btn_apply_print.html diff --git a/www/template/admin/reports/common/btn_download.html b/template/admin/reports/common/btn_download.html similarity index 100% rename from www/template/admin/reports/common/btn_download.html rename to template/admin/reports/common/btn_download.html diff --git a/www/template/admin/reports/common/btn_download2.html b/template/admin/reports/common/btn_download2.html similarity index 100% rename from www/template/admin/reports/common/btn_download2.html rename to template/admin/reports/common/btn_download2.html diff --git a/www/template/admin/reports/common/btn_download3.html b/template/admin/reports/common/btn_download3.html similarity index 100% rename from www/template/admin/reports/common/btn_download3.html rename to template/admin/reports/common/btn_download3.html diff --git a/www/template/admin/reports/common/btn_reset.html b/template/admin/reports/common/btn_reset.html similarity index 100% rename from www/template/admin/reports/common/btn_reset.html rename to template/admin/reports/common/btn_reset.html diff --git a/www/template/admin/reports/common/ctr.html b/template/admin/reports/common/ctr.html similarity index 100% rename from www/template/admin/reports/common/ctr.html rename to template/admin/reports/common/ctr.html diff --git a/www/template/admin/reports/common/docx.html b/template/admin/reports/common/docx.html similarity index 100% rename from www/template/admin/reports/common/docx.html rename to template/admin/reports/common/docx.html diff --git a/www/template/admin/reports/common/filter_buttons.html b/template/admin/reports/common/filter_buttons.html similarity index 100% rename from www/template/admin/reports/common/filter_buttons.html rename to template/admin/reports/common/filter_buttons.html diff --git a/www/template/admin/reports/common/filter_buttons2.html b/template/admin/reports/common/filter_buttons2.html similarity index 100% rename from www/template/admin/reports/common/filter_buttons2.html rename to template/admin/reports/common/filter_buttons2.html diff --git a/www/template/admin/reports/common/filter_buttons3.html b/template/admin/reports/common/filter_buttons3.html similarity index 100% rename from www/template/admin/reports/common/filter_buttons3.html rename to template/admin/reports/common/filter_buttons3.html diff --git a/www/template/admin/reports/common/filter_dates.html b/template/admin/reports/common/filter_dates.html similarity index 100% rename from www/template/admin/reports/common/filter_dates.html rename to template/admin/reports/common/filter_dates.html diff --git a/www/template/admin/reports/common/filter_year.html b/template/admin/reports/common/filter_year.html similarity index 100% rename from www/template/admin/reports/common/filter_year.html rename to template/admin/reports/common/filter_year.html diff --git a/www/template/admin/reports/common/head.css b/template/admin/reports/common/head.css similarity index 100% rename from www/template/admin/reports/common/head.css rename to template/admin/reports/common/head.css diff --git a/www/template/admin/reports/common/onload.js b/template/admin/reports/common/onload.js similarity index 100% rename from www/template/admin/reports/common/onload.js rename to template/admin/reports/common/onload.js diff --git a/www/template/admin/reports/common/pdf.html b/template/admin/reports/common/pdf.html similarity index 100% rename from www/template/admin/reports/common/pdf.html rename to template/admin/reports/common/pdf.html diff --git a/www/template/admin/reports/common/xls.html b/template/admin/reports/common/xls.html similarity index 100% rename from www/template/admin/reports/common/xls.html rename to template/admin/reports/common/xls.html diff --git a/www/template/admin/reports/index/main.html b/template/admin/reports/index/main.html similarity index 100% rename from www/template/admin/reports/index/main.html rename to template/admin/reports/index/main.html diff --git a/www/template/admin/reports/index/title.html b/template/admin/reports/index/title.html similarity index 100% rename from www/template/admin/reports/index/title.html rename to template/admin/reports/index/title.html diff --git a/www/template/admin/reports/sample/head.css b/template/admin/reports/sample/head.css similarity index 100% rename from www/template/admin/reports/sample/head.css rename to template/admin/reports/sample/head.css diff --git a/www/template/admin/reports/sample/list_filter.html b/template/admin/reports/sample/list_filter.html similarity index 100% rename from www/template/admin/reports/sample/list_filter.html rename to template/admin/reports/sample/list_filter.html diff --git a/www/template/admin/reports/sample/load_script.html b/template/admin/reports/sample/load_script.html similarity index 100% rename from www/template/admin/reports/sample/load_script.html rename to template/admin/reports/sample/load_script.html diff --git a/www/template/admin/reports/sample/main.html b/template/admin/reports/sample/main.html similarity index 100% rename from www/template/admin/reports/sample/main.html rename to template/admin/reports/sample/main.html diff --git a/www/template/admin/reports/sample/onload.js b/template/admin/reports/sample/onload.js similarity index 100% rename from www/template/admin/reports/sample/onload.js rename to template/admin/reports/sample/onload.js diff --git a/www/template/admin/reports/sample/report_html.html b/template/admin/reports/sample/report_html.html similarity index 100% rename from www/template/admin/reports/sample/report_html.html rename to template/admin/reports/sample/report_html.html diff --git a/www/template/admin/reports/sample/title.html b/template/admin/reports/sample/title.html similarity index 100% rename from www/template/admin/reports/sample/title.html rename to template/admin/reports/sample/title.html diff --git a/www/template/admin/reports/url.html b/template/admin/reports/url.html similarity index 100% rename from www/template/admin/reports/url.html rename to template/admin/reports/url.html diff --git a/www/template/admin/roles/config.json b/template/admin/roles/config.json similarity index 100% rename from www/template/admin/roles/config.json rename to template/admin/roles/config.json diff --git a/www/template/admin/roles/index/btn_multidel_more.html b/template/admin/roles/index/btn_multidel_more.html similarity index 100% rename from www/template/admin/roles/index/btn_multidel_more.html rename to template/admin/roles/index/btn_multidel_more.html diff --git a/www/template/admin/roles/index/col_custom.html b/template/admin/roles/index/col_custom.html similarity index 100% rename from www/template/admin/roles/index/col_custom.html rename to template/admin/roles/index/col_custom.html diff --git a/www/template/admin/roles/index/list_filter_more.html b/template/admin/roles/index/list_filter_more.html similarity index 100% rename from www/template/admin/roles/index/list_filter_more.html rename to template/admin/roles/index/list_filter_more.html diff --git a/www/template/admin/roles/index/list_row_btn.html b/template/admin/roles/index/list_row_btn.html similarity index 100% rename from www/template/admin/roles/index/list_row_btn.html rename to template/admin/roles/index/list_row_btn.html diff --git a/www/template/admin/roles/index/list_table.html b/template/admin/roles/index/list_table.html similarity index 100% rename from www/template/admin/roles/index/list_table.html rename to template/admin/roles/index/list_table.html diff --git a/www/template/admin/roles/index/load_script.html b/template/admin/roles/index/load_script.html similarity index 100% rename from www/template/admin/roles/index/load_script.html rename to template/admin/roles/index/load_script.html diff --git a/www/template/admin/roles/index/main.html b/template/admin/roles/index/main.html similarity index 100% rename from www/template/admin/roles/index/main.html rename to template/admin/roles/index/main.html diff --git a/www/template/admin/roles/index/onload.js b/template/admin/roles/index/onload.js similarity index 100% rename from www/template/admin/roles/index/onload.js rename to template/admin/roles/index/onload.js diff --git a/www/template/admin/roles/index/return_url.html b/template/admin/roles/index/return_url.html similarity index 100% rename from www/template/admin/roles/index/return_url.html rename to template/admin/roles/index/return_url.html diff --git a/www/template/admin/roles/index/row_click_url.html b/template/admin/roles/index/row_click_url.html similarity index 100% rename from www/template/admin/roles/index/row_click_url.html rename to template/admin/roles/index/row_click_url.html diff --git a/www/template/admin/roles/index/title.html b/template/admin/roles/index/title.html similarity index 100% rename from www/template/admin/roles/index/title.html rename to template/admin/roles/index/title.html diff --git a/www/template/admin/roles/index/userlist_entity.html b/template/admin/roles/index/userlist_entity.html similarity index 100% rename from www/template/admin/roles/index/userlist_entity.html rename to template/admin/roles/index/userlist_entity.html diff --git a/www/template/admin/roles/show/btn_std_more.html b/template/admin/roles/show/btn_std_more.html similarity index 100% rename from www/template/admin/roles/show/btn_std_more.html rename to template/admin/roles/show/btn_std_more.html diff --git a/www/template/admin/roles/show/form.html b/template/admin/roles/show/form.html similarity index 100% rename from www/template/admin/roles/show/form.html rename to template/admin/roles/show/form.html diff --git a/www/template/admin/roles/show/load_script.html b/template/admin/roles/show/load_script.html similarity index 100% rename from www/template/admin/roles/show/load_script.html rename to template/admin/roles/show/load_script.html diff --git a/www/template/admin/roles/show/main.html b/template/admin/roles/show/main.html similarity index 100% rename from www/template/admin/roles/show/main.html rename to template/admin/roles/show/main.html diff --git a/www/template/admin/roles/show/onload.js b/template/admin/roles/show/onload.js similarity index 100% rename from www/template/admin/roles/show/onload.js rename to template/admin/roles/show/onload.js diff --git a/www/template/admin/roles/show/return_url.html b/template/admin/roles/show/return_url.html similarity index 100% rename from www/template/admin/roles/show/return_url.html rename to template/admin/roles/show/return_url.html diff --git a/www/template/admin/roles/show/roles_resources_permissions.html b/template/admin/roles/show/roles_resources_permissions.html similarity index 100% rename from www/template/admin/roles/show/roles_resources_permissions.html rename to template/admin/roles/show/roles_resources_permissions.html diff --git a/www/template/admin/roles/show/title.html b/template/admin/roles/show/title.html similarity index 100% rename from www/template/admin/roles/show/title.html rename to template/admin/roles/show/title.html diff --git a/www/template/admin/roles/show/userlist_entity.html b/template/admin/roles/show/userlist_entity.html similarity index 100% rename from www/template/admin/roles/show/userlist_entity.html rename to template/admin/roles/show/userlist_entity.html diff --git a/www/template/admin/roles/showform/btn_std_more.html b/template/admin/roles/showform/btn_std_more.html similarity index 100% rename from www/template/admin/roles/showform/btn_std_more.html rename to template/admin/roles/showform/btn_std_more.html diff --git a/www/template/admin/roles/showform/form.html b/template/admin/roles/showform/form.html similarity index 100% rename from www/template/admin/roles/showform/form.html rename to template/admin/roles/showform/form.html diff --git a/www/template/admin/roles/showform/load_script.html b/template/admin/roles/showform/load_script.html similarity index 100% rename from www/template/admin/roles/showform/load_script.html rename to template/admin/roles/showform/load_script.html diff --git a/www/template/admin/roles/showform/main.html b/template/admin/roles/showform/main.html similarity index 100% rename from www/template/admin/roles/showform/main.html rename to template/admin/roles/showform/main.html diff --git a/www/template/admin/roles/showform/onload.js b/template/admin/roles/showform/onload.js similarity index 100% rename from www/template/admin/roles/showform/onload.js rename to template/admin/roles/showform/onload.js diff --git a/www/template/admin/roles/showform/roles_resources_permissions.html b/template/admin/roles/showform/roles_resources_permissions.html similarity index 100% rename from www/template/admin/roles/showform/roles_resources_permissions.html rename to template/admin/roles/showform/roles_resources_permissions.html diff --git a/www/template/admin/roles/showform/title.html b/template/admin/roles/showform/title.html similarity index 100% rename from www/template/admin/roles/showform/title.html rename to template/admin/roles/showform/title.html diff --git a/www/template/admin/roles/url.html b/template/admin/roles/url.html similarity index 100% rename from www/template/admin/roles/url.html rename to template/admin/roles/url.html diff --git a/www/template/admin/sendemail/save/main.html b/template/admin/sendemail/save/main.html similarity index 100% rename from www/template/admin/sendemail/save/main.html rename to template/admin/sendemail/save/main.html diff --git a/www/template/admin/sendemail/save/title.html b/template/admin/sendemail/save/title.html similarity index 100% rename from www/template/admin/sendemail/save/title.html rename to template/admin/sendemail/save/title.html diff --git a/www/template/admin/sendemail/showform/btn_std_more.html b/template/admin/sendemail/showform/btn_std_more.html similarity index 100% rename from www/template/admin/sendemail/showform/btn_std_more.html rename to template/admin/sendemail/showform/btn_std_more.html diff --git a/www/template/admin/sendemail/showform/form.html b/template/admin/sendemail/showform/form.html similarity index 100% rename from www/template/admin/sendemail/showform/form.html rename to template/admin/sendemail/showform/form.html diff --git a/www/template/admin/sendemail/showform/form_left.html b/template/admin/sendemail/showform/form_left.html similarity index 100% rename from www/template/admin/sendemail/showform/form_left.html rename to template/admin/sendemail/showform/form_left.html diff --git a/www/template/admin/sendemail/showform/form_right.html b/template/admin/sendemail/showform/form_right.html similarity index 100% rename from www/template/admin/sendemail/showform/form_right.html rename to template/admin/sendemail/showform/form_right.html diff --git a/www/template/admin/sendemail/showform/load_script.html b/template/admin/sendemail/showform/load_script.html similarity index 100% rename from www/template/admin/sendemail/showform/load_script.html rename to template/admin/sendemail/showform/load_script.html diff --git a/www/template/admin/sendemail/showform/main.html b/template/admin/sendemail/showform/main.html similarity index 100% rename from www/template/admin/sendemail/showform/main.html rename to template/admin/sendemail/showform/main.html diff --git a/www/template/admin/sendemail/showform/onload.js b/template/admin/sendemail/showform/onload.js similarity index 100% rename from www/template/admin/sendemail/showform/onload.js rename to template/admin/sendemail/showform/onload.js diff --git a/www/template/admin/sendemail/showform/title.html b/template/admin/sendemail/showform/title.html similarity index 100% rename from www/template/admin/sendemail/showform/title.html rename to template/admin/sendemail/showform/title.html diff --git a/www/template/admin/sendemail/url.html b/template/admin/sendemail/url.html similarity index 100% rename from www/template/admin/sendemail/url.html rename to template/admin/sendemail/url.html diff --git a/www/template/admin/settings/index/list_table.html b/template/admin/settings/index/list_table.html similarity index 100% rename from www/template/admin/settings/index/list_table.html rename to template/admin/settings/index/list_table.html diff --git a/www/template/admin/settings/index/main.html b/template/admin/settings/index/main.html similarity index 100% rename from www/template/admin/settings/index/main.html rename to template/admin/settings/index/main.html diff --git a/www/template/admin/settings/index/row_click_url.html b/template/admin/settings/index/row_click_url.html similarity index 100% rename from www/template/admin/settings/index/row_click_url.html rename to template/admin/settings/index/row_click_url.html diff --git a/www/template/admin/settings/index/title.html b/template/admin/settings/index/title.html similarity index 100% rename from www/template/admin/settings/index/title.html rename to template/admin/settings/index/title.html diff --git a/www/template/admin/settings/showform/form.html b/template/admin/settings/showform/form.html similarity index 100% rename from www/template/admin/settings/showform/form.html rename to template/admin/settings/showform/form.html diff --git a/www/template/admin/settings/showform/input_checkbox.html b/template/admin/settings/showform/input_checkbox.html similarity index 100% rename from www/template/admin/settings/showform/input_checkbox.html rename to template/admin/settings/showform/input_checkbox.html diff --git a/www/template/admin/settings/showform/input_date.html b/template/admin/settings/showform/input_date.html similarity index 100% rename from www/template/admin/settings/showform/input_date.html rename to template/admin/settings/showform/input_date.html diff --git a/www/template/admin/settings/showform/input_input.html b/template/admin/settings/showform/input_input.html similarity index 100% rename from www/template/admin/settings/showform/input_input.html rename to template/admin/settings/showform/input_input.html diff --git a/www/template/admin/settings/showform/input_radio.html b/template/admin/settings/showform/input_radio.html similarity index 100% rename from www/template/admin/settings/showform/input_radio.html rename to template/admin/settings/showform/input_radio.html diff --git a/www/template/admin/settings/showform/input_select.html b/template/admin/settings/showform/input_select.html similarity index 100% rename from www/template/admin/settings/showform/input_select.html rename to template/admin/settings/showform/input_select.html diff --git a/www/template/admin/settings/showform/input_selectmulti.html b/template/admin/settings/showform/input_selectmulti.html similarity index 100% rename from www/template/admin/settings/showform/input_selectmulti.html rename to template/admin/settings/showform/input_selectmulti.html diff --git a/www/template/admin/settings/showform/input_textarea.html b/template/admin/settings/showform/input_textarea.html similarity index 100% rename from www/template/admin/settings/showform/input_textarea.html rename to template/admin/settings/showform/input_textarea.html diff --git a/www/template/admin/settings/showform/load_script.html b/template/admin/settings/showform/load_script.html similarity index 100% rename from www/template/admin/settings/showform/load_script.html rename to template/admin/settings/showform/load_script.html diff --git a/www/template/admin/settings/showform/main.html b/template/admin/settings/showform/main.html similarity index 100% rename from www/template/admin/settings/showform/main.html rename to template/admin/settings/showform/main.html diff --git a/www/template/admin/settings/showform/title.html b/template/admin/settings/showform/title.html similarity index 100% rename from www/template/admin/settings/showform/title.html rename to template/admin/settings/showform/title.html diff --git a/www/template/admin/settings/url.html b/template/admin/settings/url.html similarity index 100% rename from www/template/admin/settings/url.html rename to template/admin/settings/url.html diff --git a/www/template/admin/spages/index/list_filter_more.html b/template/admin/spages/index/list_filter_more.html similarity index 100% rename from www/template/admin/spages/index/list_filter_more.html rename to template/admin/spages/index/list_filter_more.html diff --git a/www/template/admin/spages/index/list_table.html b/template/admin/spages/index/list_table.html similarity index 100% rename from www/template/admin/spages/index/list_table.html rename to template/admin/spages/index/list_table.html diff --git a/www/template/admin/spages/index/main.html b/template/admin/spages/index/main.html similarity index 100% rename from www/template/admin/spages/index/main.html rename to template/admin/spages/index/main.html diff --git a/www/template/admin/spages/index/row_click_url.html b/template/admin/spages/index/row_click_url.html similarity index 100% rename from www/template/admin/spages/index/row_click_url.html rename to template/admin/spages/index/row_click_url.html diff --git a/www/template/admin/spages/index/title.html b/template/admin/spages/index/title.html similarity index 100% rename from www/template/admin/spages/index/title.html rename to template/admin/spages/index/title.html diff --git a/www/template/admin/spages/showform/form.html b/template/admin/spages/showform/form.html similarity index 100% rename from www/template/admin/spages/showform/form.html rename to template/admin/spages/showform/form.html diff --git a/www/template/admin/spages/showform/load_script.html b/template/admin/spages/showform/load_script.html similarity index 100% rename from www/template/admin/spages/showform/load_script.html rename to template/admin/spages/showform/load_script.html diff --git a/www/template/admin/spages/showform/main.html b/template/admin/spages/showform/main.html similarity index 100% rename from www/template/admin/spages/showform/main.html rename to template/admin/spages/showform/main.html diff --git a/www/template/admin/spages/showform/onload.js b/template/admin/spages/showform/onload.js similarity index 100% rename from www/template/admin/spages/showform/onload.js rename to template/admin/spages/showform/onload.js diff --git a/www/template/admin/spages/showform/page_content.html b/template/admin/spages/showform/page_content.html similarity index 100% rename from www/template/admin/spages/showform/page_content.html rename to template/admin/spages/showform/page_content.html diff --git a/www/template/admin/spages/showform/page_settings.html b/template/admin/spages/showform/page_settings.html similarity index 100% rename from www/template/admin/spages/showform/page_settings.html rename to template/admin/spages/showform/page_settings.html diff --git a/www/template/admin/spages/showform/title.html b/template/admin/spages/showform/title.html similarity index 100% rename from www/template/admin/spages/showform/title.html rename to template/admin/spages/showform/title.html diff --git a/www/template/admin/spages/status.sel b/template/admin/spages/status.sel similarity index 100% rename from www/template/admin/spages/status.sel rename to template/admin/spages/status.sel diff --git a/www/template/admin/spages/template.sel b/template/admin/spages/template.sel similarity index 100% rename from www/template/admin/spages/template.sel rename to template/admin/spages/template.sel diff --git a/www/template/admin/spages/url.html b/template/admin/spages/url.html similarity index 100% rename from www/template/admin/spages/url.html rename to template/admin/spages/url.html diff --git a/www/template/admin/users/config.json b/template/admin/users/config.json similarity index 100% rename from www/template/admin/users/config.json rename to template/admin/users/config.json diff --git a/www/template/admin/users/index/btn_std_more.html b/template/admin/users/index/btn_std_more.html similarity index 100% rename from www/template/admin/users/index/btn_std_more.html rename to template/admin/users/index/btn_std_more.html diff --git a/www/template/admin/users/index/col_custom.html b/template/admin/users/index/col_custom.html similarity index 100% rename from www/template/admin/users/index/col_custom.html rename to template/admin/users/index/col_custom.html diff --git a/www/template/admin/users/index/col_is_mfa.html b/template/admin/users/index/col_is_mfa.html similarity index 100% rename from www/template/admin/users/index/col_is_mfa.html rename to template/admin/users/index/col_is_mfa.html diff --git a/www/template/admin/users/index/col_status.html b/template/admin/users/index/col_status.html similarity index 100% rename from www/template/admin/users/index/col_status.html rename to template/admin/users/index/col_status.html diff --git a/www/template/admin/users/index/list_filter_more.html b/template/admin/users/index/list_filter_more.html similarity index 100% rename from www/template/admin/users/index/list_filter_more.html rename to template/admin/users/index/list_filter_more.html diff --git a/www/template/admin/users/index/list_row_btn.html b/template/admin/users/index/list_row_btn.html similarity index 100% rename from www/template/admin/users/index/list_row_btn.html rename to template/admin/users/index/list_row_btn.html diff --git a/www/template/admin/users/index/list_table.html b/template/admin/users/index/list_table.html similarity index 100% rename from www/template/admin/users/index/list_table.html rename to template/admin/users/index/list_table.html diff --git a/www/template/admin/users/index/load_script.html b/template/admin/users/index/load_script.html similarity index 100% rename from www/template/admin/users/index/load_script.html rename to template/admin/users/index/load_script.html diff --git a/www/template/admin/users/index/main.html b/template/admin/users/index/main.html similarity index 100% rename from www/template/admin/users/index/main.html rename to template/admin/users/index/main.html diff --git a/www/template/admin/users/index/onload.js b/template/admin/users/index/onload.js similarity index 100% rename from www/template/admin/users/index/onload.js rename to template/admin/users/index/onload.js diff --git a/www/template/admin/users/index/return_url.html b/template/admin/users/index/return_url.html similarity index 100% rename from www/template/admin/users/index/return_url.html rename to template/admin/users/index/return_url.html diff --git a/www/template/admin/users/index/row_click_url.html b/template/admin/users/index/row_click_url.html similarity index 100% rename from www/template/admin/users/index/row_click_url.html rename to template/admin/users/index/row_click_url.html diff --git a/www/template/admin/users/index/title.html b/template/admin/users/index/title.html similarity index 100% rename from www/template/admin/users/index/title.html rename to template/admin/users/index/title.html diff --git a/www/template/admin/users/show/btn_std_more.html b/template/admin/users/show/btn_std_more.html similarity index 100% rename from www/template/admin/users/show/btn_std_more.html rename to template/admin/users/show/btn_std_more.html diff --git a/www/template/admin/users/show/form.html b/template/admin/users/show/form.html similarity index 100% rename from www/template/admin/users/show/form.html rename to template/admin/users/show/form.html diff --git a/www/template/admin/users/show/main.html b/template/admin/users/show/main.html similarity index 100% rename from www/template/admin/users/show/main.html rename to template/admin/users/show/main.html diff --git a/www/template/admin/users/show/onload.js b/template/admin/users/show/onload.js similarity index 100% rename from www/template/admin/users/show/onload.js rename to template/admin/users/show/onload.js diff --git a/www/template/admin/users/show/return_url.html b/template/admin/users/show/return_url.html similarity index 100% rename from www/template/admin/users/show/return_url.html rename to template/admin/users/show/return_url.html diff --git a/www/template/admin/users/show/title.html b/template/admin/users/show/title.html similarity index 100% rename from www/template/admin/users/show/title.html rename to template/admin/users/show/title.html diff --git a/www/template/admin/users/showform/btn_std_more.html b/template/admin/users/showform/btn_std_more.html similarity index 100% rename from www/template/admin/users/showform/btn_std_more.html rename to template/admin/users/showform/btn_std_more.html diff --git a/www/template/admin/users/showform/form.html b/template/admin/users/showform/form.html similarity index 100% rename from www/template/admin/users/showform/form.html rename to template/admin/users/showform/form.html diff --git a/www/template/admin/users/showform/form_left.html b/template/admin/users/showform/form_left.html similarity index 100% rename from www/template/admin/users/showform/form_left.html rename to template/admin/users/showform/form_left.html diff --git a/www/template/admin/users/showform/form_status.html b/template/admin/users/showform/form_status.html similarity index 100% rename from www/template/admin/users/showform/form_status.html rename to template/admin/users/showform/form_status.html diff --git a/www/template/admin/users/showform/load_script.html b/template/admin/users/showform/load_script.html similarity index 100% rename from www/template/admin/users/showform/load_script.html rename to template/admin/users/showform/load_script.html diff --git a/www/template/admin/users/showform/main.html b/template/admin/users/showform/main.html similarity index 100% rename from www/template/admin/users/showform/main.html rename to template/admin/users/showform/main.html diff --git a/www/template/admin/users/showform/onload.js b/template/admin/users/showform/onload.js similarity index 100% rename from www/template/admin/users/showform/onload.js rename to template/admin/users/showform/onload.js diff --git a/www/template/admin/users/showform/roles_block.html b/template/admin/users/showform/roles_block.html similarity index 100% rename from www/template/admin/users/showform/roles_block.html rename to template/admin/users/showform/roles_block.html diff --git a/www/template/admin/users/showform/title.html b/template/admin/users/showform/title.html similarity index 100% rename from www/template/admin/users/showform/title.html rename to template/admin/users/showform/title.html diff --git a/www/template/admin/users/status.sel b/template/admin/users/status.sel similarity index 100% rename from www/template/admin/users/status.sel rename to template/admin/users/status.sel diff --git a/www/template/admin/users/status_bgcolor.sel b/template/admin/users/status_bgcolor.sel similarity index 100% rename from www/template/admin/users/status_bgcolor.sel rename to template/admin/users/status_bgcolor.sel diff --git a/www/template/admin/users/statusf.sel b/template/admin/users/statusf.sel similarity index 100% rename from www/template/admin/users/statusf.sel rename to template/admin/users/statusf.sel diff --git a/www/template/admin/users/url.html b/template/admin/users/url.html similarity index 100% rename from www/template/admin/users/url.html rename to template/admin/users/url.html diff --git a/www/template/att/url.html b/template/att/url.html similarity index 100% rename from www/template/att/url.html rename to template/att/url.html diff --git a/www/template/common/active.html b/template/common/active.html similarity index 100% rename from www/template/common/active.html rename to template/common/active.html diff --git a/www/template/common/activitylogs/avatar.html b/template/common/activitylogs/avatar.html similarity index 100% rename from www/template/common/activitylogs/avatar.html rename to template/common/activitylogs/avatar.html diff --git a/www/template/common/activitylogs/edited.html b/template/common/activitylogs/edited.html similarity index 100% rename from www/template/common/activitylogs/edited.html rename to template/common/activitylogs/edited.html diff --git a/www/template/common/activitylogs/fields.html b/template/common/activitylogs/fields.html similarity index 100% rename from www/template/common/activitylogs/fields.html rename to template/common/activitylogs/fields.html diff --git a/www/template/common/activitylogs/initials.html b/template/common/activitylogs/initials.html similarity index 100% rename from www/template/common/activitylogs/initials.html rename to template/common/activitylogs/initials.html diff --git a/www/template/common/activitylogs/logtype.html b/template/common/activitylogs/logtype.html similarity index 100% rename from www/template/common/activitylogs/logtype.html rename to template/common/activitylogs/logtype.html diff --git a/www/template/common/activitylogs/main.html b/template/common/activitylogs/main.html similarity index 100% rename from www/template/common/activitylogs/main.html rename to template/common/activitylogs/main.html diff --git a/www/template/common/activitylogs/onload.js b/template/common/activitylogs/onload.js similarity index 100% rename from www/template/common/activitylogs/onload.js rename to template/common/activitylogs/onload.js diff --git a/www/template/common/ajaxform.html b/template/common/ajaxform.html similarity index 100% rename from www/template/common/ajaxform.html rename to template/common/ajaxform.html diff --git a/www/template/common/att.html b/template/common/att.html similarity index 100% rename from www/template/common/att.html rename to template/common/att.html diff --git a/www/template/common/autocomplete.html b/template/common/autocomplete.html similarity index 100% rename from www/template/common/autocomplete.html rename to template/common/autocomplete.html diff --git a/www/template/common/bootstrap_select.html b/template/common/bootstrap_select.html similarity index 100% rename from www/template/common/bootstrap_select.html rename to template/common/bootstrap_select.html diff --git a/www/template/common/calendar.html b/template/common/calendar.html similarity index 100% rename from www/template/common/calendar.html rename to template/common/calendar.html diff --git a/www/template/common/char/darr.html b/template/common/char/darr.html similarity index 100% rename from www/template/common/char/darr.html rename to template/common/char/darr.html diff --git a/www/template/common/char/del.html b/template/common/char/del.html similarity index 100% rename from www/template/common/char/del.html rename to template/common/char/del.html diff --git a/www/template/common/char/down.html b/template/common/char/down.html similarity index 100% rename from www/template/common/char/down.html rename to template/common/char/down.html diff --git a/www/template/common/char/gt.html b/template/common/char/gt.html similarity index 100% rename from www/template/common/char/gt.html rename to template/common/char/gt.html diff --git a/www/template/common/char/larr.html b/template/common/char/larr.html similarity index 100% rename from www/template/common/char/larr.html rename to template/common/char/larr.html diff --git a/www/template/common/char/uarr.html b/template/common/char/uarr.html similarity index 100% rename from www/template/common/char/uarr.html rename to template/common/char/uarr.html diff --git a/www/template/common/char/up.html b/template/common/char/up.html similarity index 100% rename from www/template/common/char/up.html rename to template/common/char/up.html diff --git a/www/template/common/checked.html b/template/common/checked.html similarity index 100% rename from www/template/common/checked.html rename to template/common/checked.html diff --git a/www/template/common/clactive.html b/template/common/clactive.html similarity index 100% rename from www/template/common/clactive.html rename to template/common/clactive.html diff --git a/www/template/common/comma.html b/template/common/comma.html similarity index 100% rename from www/template/common/comma.html rename to template/common/comma.html diff --git a/www/template/common/delim.html b/template/common/delim.html similarity index 100% rename from www/template/common/delim.html rename to template/common/delim.html diff --git a/www/template/common/disabled.html b/template/common/disabled.html similarity index 100% rename from www/template/common/disabled.html rename to template/common/disabled.html diff --git a/www/template/common/disabledcl.html b/template/common/disabledcl.html similarity index 100% rename from www/template/common/disabledcl.html rename to template/common/disabledcl.html diff --git a/www/template/common/display_none.html b/template/common/display_none.html similarity index 100% rename from www/template/common/display_none.html rename to template/common/display_none.html diff --git a/www/template/common/dot.html b/template/common/dot.html similarity index 100% rename from www/template/common/dot.html rename to template/common/dot.html diff --git a/www/template/common/error.html b/template/common/error.html similarity index 100% rename from www/template/common/error.html rename to template/common/error.html diff --git a/www/template/common/even.html b/template/common/even.html similarity index 100% rename from www/template/common/even.html rename to template/common/even.html diff --git a/www/template/common/form/actions_std.html b/template/common/form/actions_std.html similarity index 100% rename from www/template/common/form/actions_std.html rename to template/common/form/actions_std.html diff --git a/www/template/common/form/actions_std_new.html b/template/common/form/actions_std_new.html similarity index 100% rename from www/template/common/form/actions_std_new.html rename to template/common/form/actions_std_new.html diff --git a/www/template/common/form/actions_std_responsive.html b/template/common/form/actions_std_responsive.html similarity index 100% rename from www/template/common/form/actions_std_responsive.html rename to template/common/form/actions_std_responsive.html diff --git a/www/template/common/form/added.html b/template/common/form/added.html similarity index 100% rename from www/template/common/form/added.html rename to template/common/form/added.html diff --git a/www/template/common/form/btn_del.html b/template/common/form/btn_del.html similarity index 100% rename from www/template/common/form/btn_del.html rename to template/common/form/btn_del.html diff --git a/www/template/common/form/btn_edit.html b/template/common/form/btn_edit.html similarity index 100% rename from www/template/common/form/btn_edit.html rename to template/common/form/btn_edit.html diff --git a/www/template/common/form/btn_std.html b/template/common/form/btn_std.html similarity index 100% rename from www/template/common/form/btn_std.html rename to template/common/form/btn_std.html diff --git a/www/template/common/form/btn_top_save.html b/template/common/form/btn_top_save.html similarity index 100% rename from www/template/common/form/btn_top_save.html rename to template/common/form/btn_top_save.html diff --git a/www/template/common/form/btn_top_save_addnew.html b/template/common/form/btn_top_save_addnew.html similarity index 100% rename from www/template/common/form/btn_top_save_addnew.html rename to template/common/form/btn_top_save_addnew.html diff --git a/www/template/common/form/btn_userlists.html b/template/common/form/btn_userlists.html similarity index 100% rename from www/template/common/form/btn_userlists.html rename to template/common/form/btn_userlists.html diff --git a/www/template/common/form/btn_view.html b/template/common/form/btn_view.html similarity index 100% rename from www/template/common/form/btn_view.html rename to template/common/form/btn_view.html diff --git a/www/template/common/form/cancel_url.html b/template/common/form/cancel_url.html similarity index 100% rename from www/template/common/form/cancel_url.html rename to template/common/form/cancel_url.html diff --git a/www/template/common/form/data_autosave.html b/template/common/form/data_autosave.html similarity index 100% rename from www/template/common/form/data_autosave.html rename to template/common/form/data_autosave.html diff --git a/www/template/common/form/group_id.html b/template/common/form/group_id.html similarity index 100% rename from www/template/common/form/group_id.html rename to template/common/form/group_id.html diff --git a/www/template/common/form/group_id_new.html b/template/common/form/group_id_new.html similarity index 100% rename from www/template/common/form/group_id_new.html rename to template/common/form/group_id_new.html diff --git a/www/template/common/form/group_status.html b/template/common/form/group_status.html similarity index 100% rename from www/template/common/form/group_status.html rename to template/common/form/group_status.html diff --git a/www/template/common/form/id.html b/template/common/form/id.html similarity index 100% rename from www/template/common/form/id.html rename to template/common/form/id.html diff --git a/www/template/common/form/idnew.html b/template/common/form/idnew.html similarity index 100% rename from www/template/common/form/idnew.html rename to template/common/form/idnew.html diff --git a/www/template/common/form/msg.html b/template/common/form/msg.html similarity index 100% rename from www/template/common/form/msg.html rename to template/common/form/msg.html diff --git a/www/template/common/form/nav_title.html b/template/common/form/nav_title.html similarity index 100% rename from www/template/common/form/nav_title.html rename to template/common/form/nav_title.html diff --git a/www/template/common/form/nav_title_edit.html b/template/common/form/nav_title_edit.html similarity index 100% rename from www/template/common/form/nav_title_edit.html rename to template/common/form/nav_title_edit.html diff --git a/www/template/common/form/prev_next.html b/template/common/form/prev_next.html similarity index 100% rename from www/template/common/form/prev_next.html rename to template/common/form/prev_next.html diff --git a/www/template/common/form/prev_next_edit.html b/template/common/form/prev_next_edit.html similarity index 100% rename from www/template/common/form/prev_next_edit.html rename to template/common/form/prev_next_edit.html diff --git a/www/template/common/form/prio.html b/template/common/form/prio.html similarity index 100% rename from www/template/common/form/prio.html rename to template/common/form/prio.html diff --git a/www/template/common/form/relid.html b/template/common/form/relid.html similarity index 100% rename from www/template/common/form/relid.html rename to template/common/form/relid.html diff --git a/www/template/common/form/ret_url.html b/template/common/form/ret_url.html similarity index 100% rename from www/template/common/form/ret_url.html rename to template/common/form/ret_url.html diff --git a/www/template/common/form/show/added.html b/template/common/form/show/added.html similarity index 100% rename from www/template/common/form/show/added.html rename to template/common/form/show/added.html diff --git a/www/template/common/form/show/att.html b/template/common/form/show/att.html similarity index 100% rename from www/template/common/form/show/att.html rename to template/common/form/show/att.html diff --git a/www/template/common/form/show/att_files.html b/template/common/form/show/att_files.html similarity index 100% rename from www/template/common/form/show/att_files.html rename to template/common/form/show/att_files.html diff --git a/www/template/common/form/show/att_links.html b/template/common/form/show/att_links.html similarity index 100% rename from www/template/common/form/show/att_links.html rename to template/common/form/show/att_links.html diff --git a/www/template/common/form/show/checkbox.html b/template/common/form/show/checkbox.html similarity index 100% rename from www/template/common/form/show/checkbox.html rename to template/common/form/show/checkbox.html diff --git a/www/template/common/form/show/col.html b/template/common/form/show/col.html similarity index 100% rename from www/template/common/form/show/col.html rename to template/common/form/show/col.html diff --git a/www/template/common/form/show/col_end.html b/template/common/form/show/col_end.html similarity index 100% rename from www/template/common/form/show/col_end.html rename to template/common/form/show/col_end.html diff --git a/www/template/common/form/show/date.html b/template/common/form/show/date.html similarity index 100% rename from www/template/common/form/show/date.html rename to template/common/form/show/date.html diff --git a/www/template/common/form/show/date_long.html b/template/common/form/show/date_long.html similarity index 100% rename from www/template/common/form/show/date_long.html rename to template/common/form/show/date_long.html diff --git a/www/template/common/form/show/extract.html b/template/common/form/show/extract.html similarity index 100% rename from www/template/common/form/show/extract.html rename to template/common/form/show/extract.html diff --git a/www/template/common/form/show/extract/checkbox.html b/template/common/form/show/extract/checkbox.html similarity index 100% rename from www/template/common/form/show/extract/checkbox.html rename to template/common/form/show/extract/checkbox.html diff --git a/www/template/common/form/show/extract/form.html b/template/common/form/show/extract/form.html similarity index 100% rename from www/template/common/form/show/extract/form.html rename to template/common/form/show/extract/form.html diff --git a/www/template/common/form/show/extract/one_field.html b/template/common/form/show/extract/one_field.html similarity index 100% rename from www/template/common/form/show/extract/one_field.html rename to template/common/form/show/extract/one_field.html diff --git a/www/template/common/form/show/extract/one_fieldgroup.html b/template/common/form/show/extract/one_fieldgroup.html similarity index 100% rename from www/template/common/form/show/extract/one_fieldgroup.html rename to template/common/form/show/extract/one_fieldgroup.html diff --git a/www/template/common/form/show/extract/one_fieldsel.html b/template/common/form/show/extract/one_fieldsel.html similarity index 100% rename from www/template/common/form/show/extract/one_fieldsel.html rename to template/common/form/show/extract/one_fieldsel.html diff --git a/www/template/common/form/show/extract/value.html b/template/common/form/show/extract/value.html similarity index 100% rename from www/template/common/form/show/extract/value.html rename to template/common/form/show/extract/value.html diff --git a/www/template/common/form/show/float.html b/template/common/form/show/float.html similarity index 100% rename from www/template/common/form/show/float.html rename to template/common/form/show/float.html diff --git a/www/template/common/form/show/help_block.html b/template/common/form/show/help_block.html similarity index 100% rename from www/template/common/form/show/help_block.html rename to template/common/form/show/help_block.html diff --git a/www/template/common/form/show/help_block_label.html b/template/common/form/show/help_block_label.html similarity index 100% rename from www/template/common/form/show/help_block_label.html rename to template/common/form/show/help_block_label.html diff --git a/www/template/common/form/show/help_block_label_html.html b/template/common/form/show/help_block_label_html.html similarity index 100% rename from www/template/common/form/show/help_block_label_html.html rename to template/common/form/show/help_block_label_html.html diff --git a/www/template/common/form/show/markdown.html b/template/common/form/show/markdown.html similarity index 100% rename from www/template/common/form/show/markdown.html rename to template/common/form/show/markdown.html diff --git a/www/template/common/form/show/multi.html b/template/common/form/show/multi.html similarity index 100% rename from www/template/common/form/show/multi.html rename to template/common/form/show/multi.html diff --git a/www/template/common/form/show/noescape.html b/template/common/form/show/noescape.html similarity index 100% rename from www/template/common/form/show/noescape.html rename to template/common/form/show/noescape.html diff --git a/www/template/common/form/show/one_field.html b/template/common/form/show/one_field.html similarity index 100% rename from www/template/common/form/show/one_field.html rename to template/common/form/show/one_field.html diff --git a/www/template/common/form/show/one_fieldgroup.html b/template/common/form/show/one_fieldgroup.html similarity index 100% rename from www/template/common/form/show/one_fieldgroup.html rename to template/common/form/show/one_fieldgroup.html diff --git a/www/template/common/form/show/one_fieldsel.html b/template/common/form/show/one_fieldsel.html similarity index 100% rename from www/template/common/form/show/one_fieldsel.html rename to template/common/form/show/one_fieldsel.html diff --git a/www/template/common/form/show/one_structure.html b/template/common/form/show/one_structure.html similarity index 100% rename from www/template/common/form/show/one_structure.html rename to template/common/form/show/one_structure.html diff --git a/www/template/common/form/show/plaintext.html b/template/common/form/show/plaintext.html similarity index 100% rename from www/template/common/form/show/plaintext.html rename to template/common/form/show/plaintext.html diff --git a/www/template/common/form/show/plaintext_autocomplete.html b/template/common/form/show/plaintext_autocomplete.html similarity index 100% rename from www/template/common/form/show/plaintext_autocomplete.html rename to template/common/form/show/plaintext_autocomplete.html diff --git a/www/template/common/form/show/plaintext_json.html b/template/common/form/show/plaintext_json.html similarity index 100% rename from www/template/common/form/show/plaintext_json.html rename to template/common/form/show/plaintext_json.html diff --git a/www/template/common/form/show/plaintext_link.html b/template/common/form/show/plaintext_link.html similarity index 100% rename from www/template/common/form/show/plaintext_link.html rename to template/common/form/show/plaintext_link.html diff --git a/www/template/common/form/show/plaintext_url.html b/template/common/form/show/plaintext_url.html similarity index 100% rename from www/template/common/form/show/plaintext_url.html rename to template/common/form/show/plaintext_url.html diff --git a/www/template/common/form/show/plaintext_yesno.html b/template/common/form/show/plaintext_yesno.html similarity index 100% rename from www/template/common/form/show/plaintext_yesno.html rename to template/common/form/show/plaintext_yesno.html diff --git a/www/template/common/form/show/row.html b/template/common/form/show/row.html similarity index 100% rename from www/template/common/form/show/row.html rename to template/common/form/show/row.html diff --git a/www/template/common/form/show/row_end.html b/template/common/form/show/row_end.html similarity index 100% rename from www/template/common/form/show/row_end.html rename to template/common/form/show/row_end.html diff --git a/www/template/common/form/show/subtable.html b/template/common/form/show/subtable.html similarity index 100% rename from www/template/common/form/show/subtable.html rename to template/common/form/show/subtable.html diff --git a/www/template/common/form/show/updated.html b/template/common/form/show/updated.html similarity index 100% rename from www/template/common/form/show/updated.html rename to template/common/form/show/updated.html diff --git a/www/template/common/form/showdelete/actions_delete.html b/template/common/form/showdelete/actions_delete.html similarity index 100% rename from www/template/common/form/showdelete/actions_delete.html rename to template/common/form/showdelete/actions_delete.html diff --git a/www/template/common/form/showdelete/cancel_url.html b/template/common/form/showdelete/cancel_url.html similarity index 100% rename from www/template/common/form/showdelete/cancel_url.html rename to template/common/form/showdelete/cancel_url.html diff --git a/www/template/common/form/showdelete/delete_std.html b/template/common/form/showdelete/delete_std.html similarity index 100% rename from www/template/common/form/showdelete/delete_std.html rename to template/common/form/showdelete/delete_std.html diff --git a/www/template/common/form/showdelete/main.html b/template/common/form/showdelete/main.html similarity index 100% rename from www/template/common/form/showdelete/main.html rename to template/common/form/showdelete/main.html diff --git a/www/template/common/form/showdelete/title.html b/template/common/form/showdelete/title.html similarity index 100% rename from www/template/common/form/showdelete/title.html rename to template/common/form/showdelete/title.html diff --git a/www/template/common/form/showform/att.html b/template/common/form/showform/att.html similarity index 100% rename from www/template/common/form/showform/att.html rename to template/common/form/showform/att.html diff --git a/www/template/common/form/showform/att_files.html b/template/common/form/showform/att_files.html similarity index 100% rename from www/template/common/form/showform/att_files.html rename to template/common/form/showform/att_files.html diff --git a/www/template/common/form/showform/att_links.html b/template/common/form/showform/att_links.html similarity index 100% rename from www/template/common/form/showform/att_links.html rename to template/common/form/showform/att_links.html diff --git a/www/template/common/form/showform/autocomplete.html b/template/common/form/showform/autocomplete.html similarity index 100% rename from www/template/common/form/showform/autocomplete.html rename to template/common/form/showform/autocomplete.html diff --git a/www/template/common/form/showform/cb.html b/template/common/form/showform/cb.html similarity index 100% rename from www/template/common/form/showform/cb.html rename to template/common/form/showform/cb.html diff --git a/www/template/common/form/showform/cc_inline.html b/template/common/form/showform/cc_inline.html similarity index 100% rename from www/template/common/form/showform/cc_inline.html rename to template/common/form/showform/cc_inline.html diff --git a/www/template/common/form/showform/col.html b/template/common/form/showform/col.html similarity index 100% rename from www/template/common/form/showform/col.html rename to template/common/form/showform/col.html diff --git a/www/template/common/form/showform/col_end.html b/template/common/form/showform/col_end.html similarity index 100% rename from www/template/common/form/showform/col_end.html rename to template/common/form/showform/col_end.html diff --git a/www/template/common/form/showform/date_popup.html b/template/common/form/showform/date_popup.html similarity index 100% rename from www/template/common/form/showform/date_popup.html rename to template/common/form/showform/date_popup.html diff --git a/www/template/common/form/showform/datetime_popup.html b/template/common/form/showform/datetime_popup.html similarity index 100% rename from www/template/common/form/showform/datetime_popup.html rename to template/common/form/showform/datetime_popup.html diff --git a/www/template/common/form/showform/email.html b/template/common/form/showform/email.html similarity index 100% rename from www/template/common/form/showform/email.html rename to template/common/form/showform/email.html diff --git a/www/template/common/form/showform/extract/checkbox.html b/template/common/form/showform/extract/checkbox.html similarity index 100% rename from www/template/common/form/showform/extract/checkbox.html rename to template/common/form/showform/extract/checkbox.html diff --git a/www/template/common/form/showform/extract/form.html b/template/common/form/showform/extract/form.html similarity index 100% rename from www/template/common/form/showform/extract/form.html rename to template/common/form/showform/extract/form.html diff --git a/www/template/common/form/showform/extract/group_id.html b/template/common/form/showform/extract/group_id.html similarity index 100% rename from www/template/common/form/showform/extract/group_id.html rename to template/common/form/showform/extract/group_id.html diff --git a/www/template/common/form/showform/extract/id.html b/template/common/form/showform/extract/id.html similarity index 100% rename from www/template/common/form/showform/extract/id.html rename to template/common/form/showform/extract/id.html diff --git a/www/template/common/form/showform/extract/one_field.html b/template/common/form/showform/extract/one_field.html similarity index 100% rename from www/template/common/form/showform/extract/one_field.html rename to template/common/form/showform/extract/one_field.html diff --git a/www/template/common/form/showform/extract/one_fieldgroup.html b/template/common/form/showform/extract/one_fieldgroup.html similarity index 100% rename from www/template/common/form/showform/extract/one_fieldgroup.html rename to template/common/form/showform/extract/one_fieldgroup.html diff --git a/www/template/common/form/showform/extract/one_fieldsel.html b/template/common/form/showform/extract/one_fieldsel.html similarity index 100% rename from www/template/common/form/showform/extract/one_fieldsel.html rename to template/common/form/showform/extract/one_fieldsel.html diff --git a/www/template/common/form/showform/extract/select.html b/template/common/form/showform/extract/select.html similarity index 100% rename from www/template/common/form/showform/extract/select.html rename to template/common/form/showform/extract/select.html diff --git a/www/template/common/form/showform/extract/select_np.html b/template/common/form/showform/extract/select_np.html similarity index 100% rename from www/template/common/form/showform/extract/select_np.html rename to template/common/form/showform/extract/select_np.html diff --git a/www/template/common/form/showform/extract/value.html b/template/common/form/showform/extract/value.html similarity index 100% rename from www/template/common/form/showform/extract/value.html rename to template/common/form/showform/extract/value.html diff --git a/www/template/common/form/showform/group_id.html b/template/common/form/showform/group_id.html similarity index 100% rename from www/template/common/form/showform/group_id.html rename to template/common/form/showform/group_id.html diff --git a/www/template/common/form/showform/group_id_addnew.html b/template/common/form/showform/group_id_addnew.html similarity index 100% rename from www/template/common/form/showform/group_id_addnew.html rename to template/common/form/showform/group_id_addnew.html diff --git a/www/template/common/form/showform/help_block.html b/template/common/form/showform/help_block.html similarity index 100% rename from www/template/common/form/showform/help_block.html rename to template/common/form/showform/help_block.html diff --git a/www/template/common/form/showform/help_block_label.html b/template/common/form/showform/help_block_label.html similarity index 100% rename from www/template/common/form/showform/help_block_label.html rename to template/common/form/showform/help_block_label.html diff --git a/www/template/common/form/showform/help_block_label_html.html b/template/common/form/showform/help_block_label_html.html similarity index 100% rename from www/template/common/form/showform/help_block_label_html.html rename to template/common/form/showform/help_block_label_html.html diff --git a/www/template/common/form/showform/id.html b/template/common/form/showform/id.html similarity index 100% rename from www/template/common/form/showform/id.html rename to template/common/form/showform/id.html diff --git a/www/template/common/form/showform/input.html b/template/common/form/showform/input.html similarity index 100% rename from www/template/common/form/showform/input.html rename to template/common/form/showform/input.html diff --git a/www/template/common/form/showform/max.html b/template/common/form/showform/max.html similarity index 100% rename from www/template/common/form/showform/max.html rename to template/common/form/showform/max.html diff --git a/www/template/common/form/showform/maxlength.html b/template/common/form/showform/maxlength.html similarity index 100% rename from www/template/common/form/showform/maxlength.html rename to template/common/form/showform/maxlength.html diff --git a/www/template/common/form/showform/min.html b/template/common/form/showform/min.html similarity index 100% rename from www/template/common/form/showform/min.html rename to template/common/form/showform/min.html diff --git a/www/template/common/form/showform/multi.html b/template/common/form/showform/multi.html similarity index 100% rename from www/template/common/form/showform/multi.html rename to template/common/form/showform/multi.html diff --git a/www/template/common/form/showform/multi_prio.html b/template/common/form/showform/multi_prio.html similarity index 100% rename from www/template/common/form/showform/multi_prio.html rename to template/common/form/showform/multi_prio.html diff --git a/www/template/common/form/showform/number.html b/template/common/form/showform/number.html similarity index 100% rename from www/template/common/form/showform/number.html rename to template/common/form/showform/number.html diff --git a/www/template/common/form/showform/one_field.html b/template/common/form/showform/one_field.html similarity index 100% rename from www/template/common/form/showform/one_field.html rename to template/common/form/showform/one_field.html diff --git a/www/template/common/form/showform/one_fieldgroup.html b/template/common/form/showform/one_fieldgroup.html similarity index 100% rename from www/template/common/form/showform/one_fieldgroup.html rename to template/common/form/showform/one_fieldgroup.html diff --git a/www/template/common/form/showform/one_fieldsel.html b/template/common/form/showform/one_fieldsel.html similarity index 100% rename from www/template/common/form/showform/one_fieldsel.html rename to template/common/form/showform/one_fieldsel.html diff --git a/www/template/common/form/showform/one_structure.html b/template/common/form/showform/one_structure.html similarity index 100% rename from www/template/common/form/showform/one_structure.html rename to template/common/form/showform/one_structure.html diff --git a/www/template/common/form/showform/password.html b/template/common/form/showform/password.html similarity index 100% rename from www/template/common/form/showform/password.html rename to template/common/form/showform/password.html diff --git a/www/template/common/form/showform/placeholder.html b/template/common/form/showform/placeholder.html similarity index 100% rename from www/template/common/form/showform/placeholder.html rename to template/common/form/showform/placeholder.html diff --git a/www/template/common/form/showform/radio.html b/template/common/form/showform/radio.html similarity index 100% rename from www/template/common/form/showform/radio.html rename to template/common/form/showform/radio.html diff --git a/www/template/common/form/showform/row.html b/template/common/form/showform/row.html similarity index 100% rename from www/template/common/form/showform/row.html rename to template/common/form/showform/row.html diff --git a/www/template/common/form/showform/row_end.html b/template/common/form/showform/row_end.html similarity index 100% rename from www/template/common/form/showform/row_end.html rename to template/common/form/showform/row_end.html diff --git a/www/template/common/form/showform/rows.html b/template/common/form/showform/rows.html similarity index 100% rename from www/template/common/form/showform/rows.html rename to template/common/form/showform/rows.html diff --git a/www/template/common/form/showform/select.html b/template/common/form/showform/select.html similarity index 100% rename from www/template/common/form/showform/select.html rename to template/common/form/showform/select.html diff --git a/www/template/common/form/showform/step.html b/template/common/form/showform/step.html similarity index 100% rename from www/template/common/form/showform/step.html rename to template/common/form/showform/step.html diff --git a/www/template/common/form/showform/subtable.html b/template/common/form/showform/subtable.html similarity index 100% rename from www/template/common/form/showform/subtable.html rename to template/common/form/showform/subtable.html diff --git a/www/template/common/form/showform/textarea.html b/template/common/form/showform/textarea.html similarity index 100% rename from www/template/common/form/showform/textarea.html rename to template/common/form/showform/textarea.html diff --git a/www/template/common/form/showform/time.html b/template/common/form/showform/time.html similarity index 100% rename from www/template/common/form/showform/time.html rename to template/common/form/showform/time.html diff --git a/www/template/common/form/showform/validation_errors.html b/template/common/form/showform/validation_errors.html similarity index 100% rename from www/template/common/form/showform/validation_errors.html rename to template/common/form/showform/validation_errors.html diff --git a/www/template/common/form/showform/yesno.html b/template/common/form/showform/yesno.html similarity index 100% rename from www/template/common/form/showform/yesno.html rename to template/common/form/showform/yesno.html diff --git a/www/template/common/form/status.html b/template/common/form/status.html similarity index 100% rename from www/template/common/form/status.html rename to template/common/form/status.html diff --git a/www/template/common/form/tabs.html b/template/common/form/tabs.html similarity index 100% rename from www/template/common/form/tabs.html rename to template/common/form/tabs.html diff --git a/www/template/common/form/updated.html b/template/common/form/updated.html similarity index 100% rename from www/template/common/form/updated.html rename to template/common/form/updated.html diff --git a/www/template/common/form/url_edit.html b/template/common/form/url_edit.html similarity index 100% rename from www/template/common/form/url_edit.html rename to template/common/form/url_edit.html diff --git a/www/template/common/hide.html b/template/common/hide.html similarity index 100% rename from www/template/common/hide.html rename to template/common/hide.html diff --git a/www/template/common/html_editor.html b/template/common/html_editor.html similarity index 100% rename from www/template/common/html_editor.html rename to template/common/html_editor.html diff --git a/www/template/common/html_editor_local.html b/template/common/html_editor_local.html similarity index 100% rename from www/template/common/html_editor_local.html rename to template/common/html_editor_local.html diff --git a/www/template/common/icons/plusb.html b/template/common/icons/plusb.html similarity index 100% rename from www/template/common/icons/plusb.html rename to template/common/icons/plusb.html diff --git a/www/template/common/invalid.html b/template/common/invalid.html similarity index 100% rename from www/template/common/invalid.html rename to template/common/invalid.html diff --git a/www/template/common/list/btn_multidel.html b/template/common/list/btn_multidel.html similarity index 100% rename from www/template/common/list/btn_multidel.html rename to template/common/list/btn_multidel.html diff --git a/www/template/common/list/btn_std.html b/template/common/list/btn_std.html similarity index 100% rename from www/template/common/list/btn_std.html rename to template/common/list/btn_std.html diff --git a/www/template/common/list/btn_userfilters.html b/template/common/list/btn_userfilters.html similarity index 100% rename from www/template/common/list/btn_userfilters.html rename to template/common/list/btn_userfilters.html diff --git a/www/template/common/list/btn_userlists.html b/template/common/list/btn_userlists.html similarity index 100% rename from www/template/common/list/btn_userlists.html rename to template/common/list/btn_userlists.html diff --git a/www/template/common/list/col_iname.html b/template/common/list/col_iname.html similarity index 100% rename from www/template/common/list/col_iname.html rename to template/common/list/col_iname.html diff --git a/www/template/common/list/col_prio.html b/template/common/list/col_prio.html similarity index 100% rename from www/template/common/list/col_prio.html rename to template/common/list/col_prio.html diff --git a/www/template/common/list/col_status.html b/template/common/list/col_status.html similarity index 100% rename from www/template/common/list/col_status.html rename to template/common/list/col_status.html diff --git a/www/template/common/list/col_status_badge.html b/template/common/list/col_status_badge.html similarity index 100% rename from www/template/common/list/col_status_badge.html rename to template/common/list/col_status_badge.html diff --git a/www/template/common/list/empty.html b/template/common/list/empty.html similarity index 100% rename from www/template/common/list/empty.html rename to template/common/list/empty.html diff --git a/www/template/common/list/export/rows.html b/template/common/list/export/rows.html similarity index 100% rename from www/template/common/list/export/rows.html rename to template/common/list/export/rows.html diff --git a/www/template/common/list/export/xls.html b/template/common/list/export/xls.html similarity index 100% rename from www/template/common/list/export/xls.html rename to template/common/list/export/xls.html diff --git a/www/template/common/list/export/xls_foot.html b/template/common/list/export/xls_foot.html similarity index 100% rename from www/template/common/list/export/xls_foot.html rename to template/common/list/export/xls_foot.html diff --git a/www/template/common/list/export/xls_head.html b/template/common/list/export/xls_head.html similarity index 100% rename from www/template/common/list/export/xls_head.html rename to template/common/list/export/xls_head.html diff --git a/www/template/common/list/export/xls_rows.html b/template/common/list/export/xls_rows.html similarity index 100% rename from www/template/common/list/export/xls_rows.html rename to template/common/list/export/xls_rows.html diff --git a/www/template/common/list/fexport.html b/template/common/list/fexport.html similarity index 100% rename from www/template/common/list/fexport.html rename to template/common/list/fexport.html diff --git a/www/template/common/list/filter_search.html b/template/common/list/filter_search.html similarity index 100% rename from www/template/common/list/filter_search.html rename to template/common/list/filter_search.html diff --git a/www/template/common/list/filter_std.html b/template/common/list/filter_std.html similarity index 100% rename from www/template/common/list/filter_std.html rename to template/common/list/filter_std.html diff --git a/www/template/common/list/filter_std_status.html b/template/common/list/filter_std_status.html similarity index 100% rename from www/template/common/list/filter_std_status.html rename to template/common/list/filter_std_status.html diff --git a/www/template/common/list/filter_std_status_export.html b/template/common/list/filter_std_status_export.html similarity index 100% rename from www/template/common/list/filter_std_status_export.html rename to template/common/list/filter_std_status_export.html diff --git a/www/template/common/list/filter_std_su.html b/template/common/list/filter_std_su.html similarity index 100% rename from www/template/common/list/filter_std_su.html rename to template/common/list/filter_std_su.html diff --git a/www/template/common/list/form_delete.html b/template/common/list/form_delete.html similarity index 100% rename from www/template/common/list/form_delete.html rename to template/common/list/form_delete.html diff --git a/www/template/common/list/form_list.html b/template/common/list/form_list.html similarity index 100% rename from www/template/common/list/form_list.html rename to template/common/list/form_list.html diff --git a/www/template/common/list/fsearch.html b/template/common/list/fsearch.html similarity index 100% rename from www/template/common/list/fsearch.html rename to template/common/list/fsearch.html diff --git a/www/template/common/list/fstatus.html b/template/common/list/fstatus.html similarity index 100% rename from www/template/common/list/fstatus.html rename to template/common/list/fstatus.html diff --git a/www/template/common/list/fuserlists.html b/template/common/list/fuserlists.html similarity index 100% rename from www/template/common/list/fuserlists.html rename to template/common/list/fuserlists.html diff --git a/www/template/common/list/fwsortable.html b/template/common/list/fwsortable.html similarity index 100% rename from www/template/common/list/fwsortable.html rename to template/common/list/fwsortable.html diff --git a/www/template/common/list/list_row_td_btn.html b/template/common/list/list_row_td_btn.html similarity index 100% rename from www/template/common/list/list_row_td_btn.html rename to template/common/list/list_row_td_btn.html diff --git a/www/template/common/list/pagination.html b/template/common/list/pagination.html similarity index 100% rename from www/template/common/list/pagination.html rename to template/common/list/pagination.html diff --git a/www/template/common/list/pagination_always.html b/template/common/list/pagination_always.html similarity index 100% rename from www/template/common/list/pagination_always.html rename to template/common/list/pagination_always.html diff --git a/www/template/common/list/relid.html b/template/common/list/relid.html similarity index 100% rename from www/template/common/list/relid.html rename to template/common/list/relid.html diff --git a/www/template/common/list/relidq.html b/template/common/list/relidq.html similarity index 100% rename from www/template/common/list/relidq.html rename to template/common/list/relidq.html diff --git a/www/template/common/list/reset_filter_relid.html b/template/common/list/reset_filter_relid.html similarity index 100% rename from www/template/common/list/reset_filter_relid.html rename to template/common/list/reset_filter_relid.html diff --git a/www/template/common/list/row_btn_del.html b/template/common/list/row_btn_del.html similarity index 100% rename from www/template/common/list/row_btn_del.html rename to template/common/list/row_btn_del.html diff --git a/www/template/common/list/row_btn_edit.html b/template/common/list/row_btn_edit.html similarity index 100% rename from www/template/common/list/row_btn_edit.html rename to template/common/list/row_btn_edit.html diff --git a/www/template/common/list/row_btn_std.html b/template/common/list/row_btn_std.html similarity index 100% rename from www/template/common/list/row_btn_std.html rename to template/common/list/row_btn_std.html diff --git a/www/template/common/list/row_btn_view.html b/template/common/list/row_btn_view.html similarity index 100% rename from www/template/common/list/row_btn_view.html rename to template/common/list/row_btn_view.html diff --git a/www/template/common/list/table_under.html b/template/common/list/table_under.html similarity index 100% rename from www/template/common/list/table_under.html rename to template/common/list/table_under.html diff --git a/www/template/common/list/tbody.html b/template/common/list/tbody.html similarity index 100% rename from www/template/common/list/tbody.html rename to template/common/list/tbody.html diff --git a/www/template/common/list/th_chkall.html b/template/common/list/th_chkall.html similarity index 100% rename from www/template/common/list/th_chkall.html rename to template/common/list/th_chkall.html diff --git a/www/template/common/list/th_empty.html b/template/common/list/th_empty.html similarity index 100% rename from www/template/common/list/th_empty.html rename to template/common/list/th_empty.html diff --git a/www/template/common/list/th_filter_customize.html b/template/common/list/th_filter_customize.html similarity index 100% rename from www/template/common/list/th_filter_customize.html rename to template/common/list/th_filter_customize.html diff --git a/www/template/common/list/thead.html b/template/common/list/thead.html similarity index 100% rename from www/template/common/list/thead.html rename to template/common/list/thead.html diff --git a/www/template/common/list/userviews/main.html b/template/common/list/userviews/main.html similarity index 100% rename from www/template/common/list/userviews/main.html rename to template/common/list/userviews/main.html diff --git a/www/template/common/list/userviews/modal.html b/template/common/list/userviews/modal.html similarity index 100% rename from www/template/common/list/userviews/modal.html rename to template/common/list/userviews/modal.html diff --git a/www/template/common/list/userviews/onload.js b/template/common/list/userviews/onload.js similarity index 100% rename from www/template/common/list/userviews/onload.js rename to template/common/list/userviews/onload.js diff --git a/www/template/common/list/userviews/title.html b/template/common/list/userviews/title.html similarity index 100% rename from www/template/common/list/userviews/title.html rename to template/common/list/userviews/title.html diff --git a/www/template/common/markdown_editor.html b/template/common/markdown_editor.html similarity index 100% rename from www/template/common/markdown_editor.html rename to template/common/markdown_editor.html diff --git a/www/template/common/multiple.html b/template/common/multiple.html similarity index 100% rename from www/template/common/multiple.html rename to template/common/multiple.html diff --git a/www/template/common/odd.html b/template/common/odd.html similarity index 100% rename from www/template/common/odd.html rename to template/common/odd.html diff --git a/www/template/common/readonly.html b/template/common/readonly.html similarity index 100% rename from www/template/common/readonly.html rename to template/common/readonly.html diff --git a/www/template/common/redirect_js.html b/template/common/redirect_js.html similarity index 100% rename from www/template/common/redirect_js.html rename to template/common/redirect_js.html diff --git a/www/template/common/required.html b/template/common/required.html similarity index 100% rename from www/template/common/required.html rename to template/common/required.html diff --git a/www/template/common/sel/access_level.sel b/template/common/sel/access_level.sel similarity index 100% rename from www/template/common/sel/access_level.sel rename to template/common/sel/access_level.sel diff --git a/www/template/common/sel/added_since.sel b/template/common/sel/added_since.sel similarity index 100% rename from www/template/common/sel/added_since.sel rename to template/common/sel/added_since.sel diff --git a/www/template/common/sel/country.sel b/template/common/sel/country.sel similarity index 100% rename from www/template/common/sel/country.sel rename to template/common/sel/country.sel diff --git a/www/template/common/sel/date_day.sel b/template/common/sel/date_day.sel similarity index 100% rename from www/template/common/sel/date_day.sel rename to template/common/sel/date_day.sel diff --git a/www/template/common/sel/date_mon.sel b/template/common/sel/date_mon.sel similarity index 100% rename from www/template/common/sel/date_mon.sel rename to template/common/sel/date_mon.sel diff --git a/www/template/common/sel/date_year.sel b/template/common/sel/date_year.sel similarity index 100% rename from www/template/common/sel/date_year.sel rename to template/common/sel/date_year.sel diff --git a/www/template/common/sel/fcombo.sel b/template/common/sel/fcombo.sel similarity index 100% rename from www/template/common/sel/fcombo.sel rename to template/common/sel/fcombo.sel diff --git a/www/template/common/sel/gender.sel b/template/common/sel/gender.sel similarity index 100% rename from www/template/common/sel/gender.sel rename to template/common/sel/gender.sel diff --git a/www/template/common/sel/lang.sel b/template/common/sel/lang.sel similarity index 100% rename from www/template/common/sel/lang.sel rename to template/common/sel/lang.sel diff --git a/www/template/common/sel/pagesize.sel b/template/common/sel/pagesize.sel similarity index 100% rename from www/template/common/sel/pagesize.sel rename to template/common/sel/pagesize.sel diff --git a/www/template/common/sel/sortby.sel b/template/common/sel/sortby.sel similarity index 100% rename from www/template/common/sel/sortby.sel rename to template/common/sel/sortby.sel diff --git a/www/template/common/sel/state.sel b/template/common/sel/state.sel similarity index 100% rename from www/template/common/sel/state.sel rename to template/common/sel/state.sel diff --git a/www/template/common/sel/status.sel b/template/common/sel/status.sel similarity index 100% rename from www/template/common/sel/status.sel rename to template/common/sel/status.sel diff --git a/www/template/common/sel/status3.sel b/template/common/sel/status3.sel similarity index 100% rename from www/template/common/sel/status3.sel rename to template/common/sel/status3.sel diff --git a/www/template/common/sel/status_bgcolor.sel b/template/common/sel/status_bgcolor.sel similarity index 100% rename from www/template/common/sel/status_bgcolor.sel rename to template/common/sel/status_bgcolor.sel diff --git a/www/template/common/sel/statusf.sel b/template/common/sel/statusf.sel similarity index 100% rename from www/template/common/sel/statusf.sel rename to template/common/sel/statusf.sel diff --git a/www/template/common/sel/statusf_admin.sel b/template/common/sel/statusf_admin.sel similarity index 100% rename from www/template/common/sel/statusf_admin.sel rename to template/common/sel/statusf_admin.sel diff --git a/www/template/common/sel/ui_mode.sel b/template/common/sel/ui_mode.sel similarity index 100% rename from www/template/common/sel/ui_mode.sel rename to template/common/sel/ui_mode.sel diff --git a/www/template/common/sel/ui_mode_data.sel b/template/common/sel/ui_mode_data.sel similarity index 100% rename from www/template/common/sel/ui_mode_data.sel rename to template/common/sel/ui_mode_data.sel diff --git a/www/template/common/sel/ui_theme.sel b/template/common/sel/ui_theme.sel similarity index 100% rename from www/template/common/sel/ui_theme.sel rename to template/common/sel/ui_theme.sel diff --git a/www/template/common/sel/yn.sel b/template/common/sel/yn.sel similarity index 100% rename from www/template/common/sel/yn.sel rename to template/common/sel/yn.sel diff --git a/www/template/common/sel/yn_bool.sel b/template/common/sel/yn_bool.sel similarity index 100% rename from www/template/common/sel/yn_bool.sel rename to template/common/sel/yn_bool.sel diff --git a/www/template/common/sel/yn_char.sel b/template/common/sel/yn_char.sel similarity index 100% rename from www/template/common/sel/yn_char.sel rename to template/common/sel/yn_char.sel diff --git a/www/template/common/sel/yn_char_rev.sel b/template/common/sel/yn_char_rev.sel similarity index 100% rename from www/template/common/sel/yn_char_rev.sel rename to template/common/sel/yn_char_rev.sel diff --git a/www/template/common/selected.html b/template/common/selected.html similarity index 100% rename from www/template/common/selected.html rename to template/common/selected.html diff --git a/www/template/common/selected0.html b/template/common/selected0.html similarity index 100% rename from www/template/common/selected0.html rename to template/common/selected0.html diff --git a/www/template/common/sortable.html b/template/common/sortable.html similarity index 100% rename from www/template/common/sortable.html rename to template/common/sortable.html diff --git a/www/template/common/text_end.html b/template/common/text_end.html similarity index 100% rename from www/template/common/text_end.html rename to template/common/text_end.html diff --git a/www/template/common/uploader.html b/template/common/uploader.html similarity index 100% rename from www/template/common/uploader.html rename to template/common/uploader.html diff --git a/www/template/common/virtual/index/app.js b/template/common/virtual/index/app.js similarity index 100% rename from www/template/common/virtual/index/app.js rename to template/common/virtual/index/app.js diff --git a/www/template/common/virtual/index/main.html b/template/common/virtual/index/main.html similarity index 100% rename from www/template/common/virtual/index/main.html rename to template/common/virtual/index/main.html diff --git a/www/template/common/virtual/index/store.js b/template/common/virtual/index/store.js similarity index 100% rename from www/template/common/virtual/index/store.js rename to template/common/virtual/index/store.js diff --git a/www/template/common/virtual/index/title.html b/template/common/virtual/index/title.html similarity index 100% rename from www/template/common/virtual/index/title.html rename to template/common/virtual/index/title.html diff --git a/www/template/common/virtual/index/vue/subtable_demos_items.html b/template/common/virtual/index/vue/subtable_demos_items.html similarity index 100% rename from www/template/common/virtual/index/vue/subtable_demos_items.html rename to template/common/virtual/index/vue/subtable_demos_items.html diff --git a/www/template/common/virtual/index/vue_components.html b/template/common/virtual/index/vue_components.html similarity index 100% rename from www/template/common/virtual/index/vue_components.html rename to template/common/virtual/index/vue_components.html diff --git a/www/template/common/virtual/url.html b/template/common/virtual/url.html similarity index 100% rename from www/template/common/virtual/url.html rename to template/common/virtual/url.html diff --git a/www/template/common/vue/_sample.html b/template/common/vue/_sample.html similarity index 100% rename from www/template/common/vue/_sample.html rename to template/common/vue/_sample.html diff --git a/www/template/common/vue/activity-logs.html b/template/common/vue/activity-logs.html similarity index 100% rename from www/template/common/vue/activity-logs.html rename to template/common/vue/activity-logs.html diff --git a/www/template/common/vue/app.js b/template/common/vue/app.js similarity index 100% rename from www/template/common/vue/app.js rename to template/common/vue/app.js diff --git a/www/template/common/vue/att-select.html b/template/common/vue/att-select.html similarity index 100% rename from www/template/common/vue/att-select.html rename to template/common/vue/att-select.html diff --git a/www/template/common/vue/autocomplete.html b/template/common/vue/autocomplete.html similarity index 100% rename from www/template/common/vue/autocomplete.html rename to template/common/vue/autocomplete.html diff --git a/www/template/common/vue/date-picker.html b/template/common/vue/date-picker.html similarity index 100% rename from www/template/common/vue/date-picker.html rename to template/common/vue/date-picker.html diff --git a/www/template/common/vue/edit-form.html b/template/common/vue/edit-form.html similarity index 100% rename from www/template/common/vue/edit-form.html rename to template/common/vue/edit-form.html diff --git a/www/template/common/vue/edit-header.html b/template/common/vue/edit-header.html similarity index 100% rename from www/template/common/vue/edit-header.html rename to template/common/vue/edit-header.html diff --git a/www/template/common/vue/form-control-help-block.html b/template/common/vue/form-control-help-block.html similarity index 100% rename from www/template/common/vue/form-control-help-block.html rename to template/common/vue/form-control-help-block.html diff --git a/www/template/common/vue/form-one-col.html b/template/common/vue/form-one-col.html similarity index 100% rename from www/template/common/vue/form-one-col.html rename to template/common/vue/form-one-col.html diff --git a/www/template/common/vue/form-one-control.html b/template/common/vue/form-one-control.html similarity index 100% rename from www/template/common/vue/form-one-control.html rename to template/common/vue/form-one-control.html diff --git a/www/template/common/vue/form-one-def.html b/template/common/vue/form-one-def.html similarity index 100% rename from www/template/common/vue/form-one-def.html rename to template/common/vue/form-one-def.html diff --git a/www/template/common/vue/form-one-form-row.html b/template/common/vue/form-one-form-row.html similarity index 100% rename from www/template/common/vue/form-one-form-row.html rename to template/common/vue/form-one-form-row.html diff --git a/www/template/common/vue/form-one-group.html b/template/common/vue/form-one-group.html similarity index 100% rename from www/template/common/vue/form-one-group.html rename to template/common/vue/form-one-group.html diff --git a/www/template/common/vue/form-one-row.html b/template/common/vue/form-one-row.html similarity index 100% rename from www/template/common/vue/form-one-row.html rename to template/common/vue/form-one-row.html diff --git a/www/template/common/vue/list-btn-multi.html b/template/common/vue/list-btn-multi.html similarity index 100% rename from www/template/common/vue/list-btn-multi.html rename to template/common/vue/list-btn-multi.html diff --git a/www/template/common/vue/list-btn-userlists.html b/template/common/vue/list-btn-userlists.html similarity index 100% rename from www/template/common/vue/list-btn-userlists.html rename to template/common/vue/list-btn-userlists.html diff --git a/www/template/common/vue/list-cell-checkbox.html b/template/common/vue/list-cell-checkbox.html similarity index 100% rename from www/template/common/vue/list-cell-checkbox.html rename to template/common/vue/list-cell-checkbox.html diff --git a/www/template/common/vue/list-cell-input.html b/template/common/vue/list-cell-input.html similarity index 100% rename from www/template/common/vue/list-cell-input.html rename to template/common/vue/list-cell-input.html diff --git a/www/template/common/vue/list-cell-ro.html b/template/common/vue/list-cell-ro.html similarity index 100% rename from www/template/common/vue/list-cell-ro.html rename to template/common/vue/list-cell-ro.html diff --git a/www/template/common/vue/list-cell-select.html b/template/common/vue/list-cell-select.html similarity index 100% rename from www/template/common/vue/list-cell-select.html rename to template/common/vue/list-cell-select.html diff --git a/www/template/common/vue/list-customize-columns.html b/template/common/vue/list-customize-columns.html similarity index 100% rename from www/template/common/vue/list-customize-columns.html rename to template/common/vue/list-customize-columns.html diff --git a/www/template/common/vue/list-edit-pane.html b/template/common/vue/list-edit-pane.html similarity index 100% rename from www/template/common/vue/list-edit-pane.html rename to template/common/vue/list-edit-pane.html diff --git a/www/template/common/vue/list-filters-more.html b/template/common/vue/list-filters-more.html similarity index 100% rename from www/template/common/vue/list-filters-more.html rename to template/common/vue/list-filters-more.html diff --git a/www/template/common/vue/list-filters-table-btn.html b/template/common/vue/list-filters-table-btn.html similarity index 100% rename from www/template/common/vue/list-filters-table-btn.html rename to template/common/vue/list-filters-table-btn.html diff --git a/www/template/common/vue/list-filters.html b/template/common/vue/list-filters.html similarity index 100% rename from www/template/common/vue/list-filters.html rename to template/common/vue/list-filters.html diff --git a/www/template/common/vue/list-header.html b/template/common/vue/list-header.html similarity index 100% rename from www/template/common/vue/list-header.html rename to template/common/vue/list-header.html diff --git a/www/template/common/vue/list-pagination.html b/template/common/vue/list-pagination.html similarity index 100% rename from www/template/common/vue/list-pagination.html rename to template/common/vue/list-pagination.html diff --git a/www/template/common/vue/list-row-btn.html b/template/common/vue/list-row-btn.html similarity index 100% rename from www/template/common/vue/list-row-btn.html rename to template/common/vue/list-row-btn.html diff --git a/www/template/common/vue/list-table.html b/template/common/vue/list-table.html similarity index 100% rename from www/template/common/vue/list-table.html rename to template/common/vue/list-table.html diff --git a/www/template/common/vue/screens.html b/template/common/vue/screens.html similarity index 100% rename from www/template/common/vue/screens.html rename to template/common/vue/screens.html diff --git a/www/template/common/vue/store.js b/template/common/vue/store.js similarity index 100% rename from www/template/common/vue/store.js rename to template/common/vue/store.js diff --git a/www/template/common/vue/view-form.html b/template/common/vue/view-form.html similarity index 100% rename from www/template/common/vue/view-form.html rename to template/common/vue/view-form.html diff --git a/www/template/common/vue/view-header.html b/template/common/vue/view-header.html similarity index 100% rename from www/template/common/vue/view-header.html rename to template/common/vue/view-header.html diff --git a/www/template/contact/index/form.html b/template/contact/index/form.html similarity index 100% rename from www/template/contact/index/form.html rename to template/contact/index/form.html diff --git a/www/template/contact/index/head.css b/template/contact/index/head.css similarity index 100% rename from www/template/contact/index/head.css rename to template/contact/index/head.css diff --git a/www/template/contact/index/head_script.html b/template/contact/index/head_script.html similarity index 100% rename from www/template/contact/index/head_script.html rename to template/contact/index/head_script.html diff --git a/www/template/contact/index/main.html b/template/contact/index/main.html similarity index 100% rename from www/template/contact/index/main.html rename to template/contact/index/main.html diff --git a/www/template/contact/index/sub_header.html b/template/contact/index/sub_header.html similarity index 100% rename from www/template/contact/index/sub_header.html rename to template/contact/index/sub_header.html diff --git a/www/template/contact/index/title.html b/template/contact/index/title.html similarity index 100% rename from www/template/contact/index/title.html rename to template/contact/index/title.html diff --git a/www/template/contact/sent/main.html b/template/contact/sent/main.html similarity index 100% rename from www/template/contact/sent/main.html rename to template/contact/sent/main.html diff --git a/www/template/contact/sent/title.html b/template/contact/sent/title.html similarity index 100% rename from www/template/contact/sent/title.html rename to template/contact/sent/title.html diff --git a/www/template/contact/url.html b/template/contact/url.html similarity index 100% rename from www/template/contact/url.html rename to template/contact/url.html diff --git a/www/template/dev/configure/index/fail.html b/template/dev/configure/index/fail.html similarity index 100% rename from www/template/dev/configure/index/fail.html rename to template/dev/configure/index/fail.html diff --git a/www/template/dev/configure/index/main.html b/template/dev/configure/index/main.html similarity index 100% rename from www/template/dev/configure/index/main.html rename to template/dev/configure/index/main.html diff --git a/www/template/dev/configure/index/ok.html b/template/dev/configure/index/ok.html similarity index 100% rename from www/template/dev/configure/index/ok.html rename to template/dev/configure/index/ok.html diff --git a/www/template/dev/configure/index/title.html b/template/dev/configure/index/title.html similarity index 100% rename from www/template/dev/configure/index/title.html rename to template/dev/configure/index/title.html diff --git a/www/template/dev/configure/index/warn.html b/template/dev/configure/index/warn.html similarity index 100% rename from www/template/dev/configure/index/warn.html rename to template/dev/configure/index/warn.html diff --git a/www/template/dev/configure/initdb/main.html b/template/dev/configure/initdb/main.html similarity index 100% rename from www/template/dev/configure/initdb/main.html rename to template/dev/configure/initdb/main.html diff --git a/www/template/dev/configure/initdb/title.html b/template/dev/configure/initdb/title.html similarity index 100% rename from www/template/dev/configure/initdb/title.html rename to template/dev/configure/initdb/title.html diff --git a/www/template/dev/configure/url.html b/template/dev/configure/url.html similarity index 100% rename from www/template/dev/configure/url.html rename to template/dev/configure/url.html diff --git a/www/template/dev/manage/apitester/csp_script.html b/template/dev/manage/apitester/csp_script.html similarity index 100% rename from www/template/dev/manage/apitester/csp_script.html rename to template/dev/manage/apitester/csp_script.html diff --git a/www/template/dev/manage/apitester/main.html b/template/dev/manage/apitester/main.html similarity index 100% rename from www/template/dev/manage/apitester/main.html rename to template/dev/manage/apitester/main.html diff --git a/www/template/dev/manage/apitester/onload.js b/template/dev/manage/apitester/onload.js similarity index 100% rename from www/template/dev/manage/apitester/onload.js rename to template/dev/manage/apitester/onload.js diff --git a/www/template/dev/manage/apitester/title.html b/template/dev/manage/apitester/title.html similarity index 100% rename from www/template/dev/manage/apitester/title.html rename to template/dev/manage/apitester/title.html diff --git a/www/template/dev/manage/appcreator/main.html b/template/dev/manage/appcreator/main.html similarity index 100% rename from www/template/dev/manage/appcreator/main.html rename to template/dev/manage/appcreator/main.html diff --git a/www/template/dev/manage/appcreator/onload.js b/template/dev/manage/appcreator/onload.js similarity index 100% rename from www/template/dev/manage/appcreator/onload.js rename to template/dev/manage/appcreator/onload.js diff --git a/www/template/dev/manage/appcreator/title.html b/template/dev/manage/appcreator/title.html similarity index 100% rename from www/template/dev/manage/appcreator/title.html rename to template/dev/manage/appcreator/title.html diff --git a/www/template/dev/manage/dbanalyzer/main.html b/template/dev/manage/dbanalyzer/main.html similarity index 100% rename from www/template/dev/manage/dbanalyzer/main.html rename to template/dev/manage/dbanalyzer/main.html diff --git a/www/template/dev/manage/dbanalyzer/title.html b/template/dev/manage/dbanalyzer/title.html similarity index 100% rename from www/template/dev/manage/dbanalyzer/title.html rename to template/dev/manage/dbanalyzer/title.html diff --git a/www/template/dev/manage/dbinitializer/main.html b/template/dev/manage/dbinitializer/main.html similarity index 100% rename from www/template/dev/manage/dbinitializer/main.html rename to template/dev/manage/dbinitializer/main.html diff --git a/www/template/dev/manage/dbinitializer/title.html b/template/dev/manage/dbinitializer/title.html similarity index 100% rename from www/template/dev/manage/dbinitializer/title.html rename to template/dev/manage/dbinitializer/title.html diff --git a/www/template/dev/manage/entitybuilder/main.html b/template/dev/manage/entitybuilder/main.html similarity index 100% rename from www/template/dev/manage/entitybuilder/main.html rename to template/dev/manage/entitybuilder/main.html diff --git a/www/template/dev/manage/entitybuilder/title.html b/template/dev/manage/entitybuilder/title.html similarity index 100% rename from www/template/dev/manage/entitybuilder/title.html rename to template/dev/manage/entitybuilder/title.html diff --git a/www/template/dev/manage/index/ctype.sel b/template/dev/manage/index/ctype.sel similarity index 100% rename from www/template/dev/manage/index/ctype.sel rename to template/dev/manage/index/ctype.sel diff --git a/www/template/dev/manage/index/main.html b/template/dev/manage/index/main.html similarity index 100% rename from www/template/dev/manage/index/main.html rename to template/dev/manage/index/main.html diff --git a/www/template/dev/manage/index/onload.js b/template/dev/manage/index/onload.js similarity index 100% rename from www/template/dev/manage/index/onload.js rename to template/dev/manage/index/onload.js diff --git a/www/template/dev/manage/index/title.html b/template/dev/manage/index/title.html similarity index 100% rename from www/template/dev/manage/index/title.html rename to template/dev/manage/index/title.html diff --git a/www/template/dev/manage/showdbupdates/main.html b/template/dev/manage/showdbupdates/main.html similarity index 100% rename from www/template/dev/manage/showdbupdates/main.html rename to template/dev/manage/showdbupdates/main.html diff --git a/www/template/dev/manage/showdbupdates/title.html b/template/dev/manage/showdbupdates/title.html similarity index 100% rename from www/template/dev/manage/showdbupdates/title.html rename to template/dev/manage/showdbupdates/title.html diff --git a/www/template/dev/manage/url.html b/template/dev/manage/url.html similarity index 100% rename from www/template/dev/manage/url.html rename to template/dev/manage/url.html diff --git a/www/template/dev/url.html b/template/dev/url.html similarity index 100% rename from www/template/dev/url.html rename to template/dev/url.html diff --git a/www/template/emails/email_actcode.txt b/template/emails/email_actcode.txt similarity index 100% rename from www/template/emails/email_actcode.txt rename to template/emails/email_actcode.txt diff --git a/www/template/emails/email_confirm.txt b/template/emails/email_confirm.txt similarity index 100% rename from www/template/emails/email_confirm.txt rename to template/emails/email_confirm.txt diff --git a/www/template/emails/email_invite.txt b/template/emails/email_invite.txt similarity index 100% rename from www/template/emails/email_invite.txt rename to template/emails/email_invite.txt diff --git a/www/template/emails/email_pwd.txt b/template/emails/email_pwd.txt similarity index 100% rename from www/template/emails/email_pwd.txt rename to template/emails/email_pwd.txt diff --git a/www/template/emails/feedback.txt b/template/emails/feedback.txt similarity index 100% rename from www/template/emails/feedback.txt rename to template/emails/feedback.txt diff --git a/www/template/emails/sign_en.txt b/template/emails/sign_en.txt similarity index 100% rename from www/template/emails/sign_en.txt rename to template/emails/sign_en.txt diff --git a/www/template/emails/signup.txt b/template/emails/signup.txt similarity index 100% rename from www/template/emails/signup.txt rename to template/emails/signup.txt diff --git a/www/template/error/404/main.html b/template/error/404/main.html similarity index 100% rename from www/template/error/404/main.html rename to template/error/404/main.html diff --git a/www/template/error/404/title.html b/template/error/404/title.html similarity index 100% rename from www/template/error/404/title.html rename to template/error/404/title.html diff --git a/www/template/error/4xx/main.html b/template/error/4xx/main.html similarity index 100% rename from www/template/error/4xx/main.html rename to template/error/4xx/main.html diff --git a/www/template/error/4xx/title.html b/template/error/4xx/title.html similarity index 100% rename from www/template/error/4xx/title.html rename to template/error/4xx/title.html diff --git a/www/template/error/main.html b/template/error/main.html similarity index 100% rename from www/template/error/main.html rename to template/error/main.html diff --git a/www/template/error/onload.js b/template/error/onload.js similarity index 100% rename from www/template/error/onload.js rename to template/error/onload.js diff --git a/www/template/files/url.html b/template/files/url.html similarity index 100% rename from www/template/files/url.html rename to template/files/url.html diff --git a/www/template/home/about/head.css b/template/home/about/head.css similarity index 100% rename from www/template/home/about/head.css rename to template/home/about/head.css diff --git a/www/template/home/about/main.html b/template/home/about/main.html similarity index 100% rename from www/template/home/about/main.html rename to template/home/about/main.html diff --git a/www/template/home/about/sub_header.html b/template/home/about/sub_header.html similarity index 100% rename from www/template/home/about/sub_header.html rename to template/home/about/sub_header.html diff --git a/www/template/home/about/title.html b/template/home/about/title.html similarity index 100% rename from www/template/home/about/title.html rename to template/home/about/title.html diff --git a/www/template/home/index/head.css b/template/home/index/head.css similarity index 100% rename from www/template/home/index/head.css rename to template/home/index/head.css diff --git a/www/template/home/index/main.html b/template/home/index/main.html similarity index 100% rename from www/template/home/index/main.html rename to template/home/index/main.html diff --git a/www/template/home/index/onload.js b/template/home/index/onload.js similarity index 100% rename from www/template/home/index/onload.js rename to template/home/index/onload.js diff --git a/www/template/home/index/sub_header.html b/template/home/index/sub_header.html similarity index 100% rename from www/template/home/index/sub_header.html rename to template/home/index/sub_header.html diff --git a/www/template/home/spage/head.css b/template/home/spage/head.css similarity index 100% rename from www/template/home/spage/head.css rename to template/home/spage/head.css diff --git a/www/template/home/spage/head.js b/template/home/spage/head.js similarity index 100% rename from www/template/home/spage/head.js rename to template/home/spage/head.js diff --git a/www/template/home/spage/head_script.html b/template/home/spage/head_script.html similarity index 100% rename from www/template/home/spage/head_script.html rename to template/home/spage/head_script.html diff --git a/www/template/home/spage/main.html b/template/home/spage/main.html similarity index 100% rename from www/template/home/spage/main.html rename to template/home/spage/main.html diff --git a/www/template/home/spage/one_col.html b/template/home/spage/one_col.html similarity index 100% rename from www/template/home/spage/one_col.html rename to template/home/spage/one_col.html diff --git a/www/template/home/spage/sidebar.html b/template/home/spage/sidebar.html similarity index 100% rename from www/template/home/spage/sidebar.html rename to template/home/spage/sidebar.html diff --git a/www/template/home/spage/three_col.html b/template/home/spage/three_col.html similarity index 100% rename from www/template/home/spage/three_col.html rename to template/home/spage/three_col.html diff --git a/www/template/home/spage/title.html b/template/home/spage/title.html similarity index 100% rename from www/template/home/spage/title.html rename to template/home/spage/title.html diff --git a/www/template/home/spage/two_col.html b/template/home/spage/two_col.html similarity index 100% rename from www/template/home/spage/two_col.html rename to template/home/spage/two_col.html diff --git a/www/template/home/spage/two_left_col.html b/template/home/spage/two_left_col.html similarity index 100% rename from www/template/home/spage/two_left_col.html rename to template/home/spage/two_left_col.html diff --git a/www/template/home/spage/two_right_col.html b/template/home/spage/two_right_col.html similarity index 100% rename from www/template/home/spage/two_right_col.html rename to template/home/spage/two_right_col.html diff --git a/www/template/home/url.html b/template/home/url.html similarity index 100% rename from www/template/home/url.html rename to template/home/url.html diff --git a/www/template/lang/de.txt b/template/lang/de.txt similarity index 100% rename from www/template/lang/de.txt rename to template/lang/de.txt diff --git a/www/template/lang/en.txt b/template/lang/en.txt similarity index 100% rename from www/template/lang/en.txt rename to template/lang/en.txt diff --git a/www/template/lang/ru.txt b/template/lang/ru.txt similarity index 100% rename from www/template/lang/ru.txt rename to template/lang/ru.txt diff --git a/www/template/lang/ua.txt b/template/lang/ua.txt similarity index 100% rename from www/template/lang/ua.txt rename to template/lang/ua.txt diff --git a/www/template/lang/zh.txt b/template/lang/zh.txt similarity index 100% rename from www/template/lang/zh.txt rename to template/lang/zh.txt diff --git a/www/template/layout.html b/template/layout.html similarity index 100% rename from www/template/layout.html rename to template/layout.html diff --git a/www/template/layout/admin.js b/template/layout/admin.js similarity index 100% rename from www/template/layout/admin.js rename to template/layout/admin.js diff --git a/www/template/layout/beta.html b/template/layout/beta.html similarity index 100% rename from www/template/layout/beta.html rename to template/layout/beta.html diff --git a/www/template/layout/common.js b/template/layout/common.js similarity index 100% rename from www/template/layout/common.js rename to template/layout/common.js diff --git a/www/template/layout/content_security_policy.html b/template/layout/content_security_policy.html similarity index 100% rename from www/template/layout/content_security_policy.html rename to template/layout/content_security_policy.html diff --git a/www/template/layout/counters/ga.html b/template/layout/counters/ga.html similarity index 100% rename from www/template/layout/counters/ga.html rename to template/layout/counters/ga.html diff --git a/www/template/layout/footer.html b/template/layout/footer.html similarity index 100% rename from www/template/layout/footer.html rename to template/layout/footer.html diff --git a/www/template/layout/head_common.js b/template/layout/head_common.js similarity index 100% rename from www/template/layout/head_common.js rename to template/layout/head_common.js diff --git a/www/template/layout/header.html b/template/layout/header.html similarity index 100% rename from www/template/layout/header.html rename to template/layout/header.html diff --git a/www/template/layout/header_public.html b/template/layout/header_public.html similarity index 100% rename from www/template/layout/header_public.html rename to template/layout/header_public.html diff --git a/www/template/layout/main_no_sidebar.html b/template/layout/main_no_sidebar.html similarity index 100% rename from www/template/layout/main_no_sidebar.html rename to template/layout/main_no_sidebar.html diff --git a/www/template/layout/main_with_sidebar.html b/template/layout/main_with_sidebar.html similarity index 100% rename from www/template/layout/main_with_sidebar.html rename to template/layout/main_with_sidebar.html diff --git a/www/template/layout/sidebar.html b/template/layout/sidebar.html similarity index 100% rename from www/template/layout/sidebar.html rename to template/layout/sidebar.html diff --git a/www/template/layout/sidebar_my.html b/template/layout/sidebar_my.html similarity index 100% rename from www/template/layout/sidebar_my.html rename to template/layout/sidebar_my.html diff --git a/www/template/layout/sys_footer.html b/template/layout/sys_footer.html similarity index 100% rename from www/template/layout/sys_footer.html rename to template/layout/sys_footer.html diff --git a/www/template/layout/sys_footer_print.html b/template/layout/sys_footer_print.html similarity index 100% rename from www/template/layout/sys_footer_print.html rename to template/layout/sys_footer_print.html diff --git a/www/template/layout/sys_head.html b/template/layout/sys_head.html similarity index 100% rename from www/template/layout/sys_head.html rename to template/layout/sys_head.html diff --git a/www/template/layout/sys_head_print.html b/template/layout/sys_head_print.html similarity index 100% rename from www/template/layout/sys_head_print.html rename to template/layout/sys_head_print.html diff --git a/www/template/layout/theme_link.html b/template/layout/theme_link.html similarity index 100% rename from www/template/layout/theme_link.html rename to template/layout/theme_link.html diff --git a/www/template/layout/ui_mode.html b/template/layout/ui_mode.html similarity index 100% rename from www/template/layout/ui_mode.html rename to template/layout/ui_mode.html diff --git a/www/template/layout/user_avatar.html b/template/layout/user_avatar.html similarity index 100% rename from www/template/layout/user_avatar.html rename to template/layout/user_avatar.html diff --git a/www/template/layout/vue/apputils.js b/template/layout/vue/apputils.js similarity index 100% rename from www/template/layout/vue/apputils.js rename to template/layout/vue/apputils.js diff --git a/www/template/layout/vue/common.js b/template/layout/vue/common.js similarity index 100% rename from www/template/layout/vue/common.js rename to template/layout/vue/common.js diff --git a/www/template/layout/vue/importmaps.html b/template/layout/vue/importmaps.html similarity index 100% rename from www/template/layout/vue/importmaps.html rename to template/layout/vue/importmaps.html diff --git a/www/template/layout/vue/sys_footer.html b/template/layout/vue/sys_footer.html similarity index 100% rename from www/template/layout/vue/sys_footer.html rename to template/layout/vue/sys_footer.html diff --git a/www/template/layout/vue/sys_head.html b/template/layout/vue/sys_head.html similarity index 100% rename from www/template/layout/vue/sys_head.html rename to template/layout/vue/sys_head.html diff --git a/www/template/layout_email.html b/template/layout_email.html similarity index 100% rename from www/template/layout_email.html rename to template/layout_email.html diff --git a/www/template/layout_min.html b/template/layout_min.html similarity index 100% rename from www/template/layout_min.html rename to template/layout_min.html diff --git a/www/template/layout_pjax.html b/template/layout_pjax.html similarity index 100% rename from www/template/layout_pjax.html rename to template/layout_pjax.html diff --git a/www/template/layout_pjax_nojs.html b/template/layout_pjax_nojs.html similarity index 100% rename from www/template/layout_pjax_nojs.html rename to template/layout_pjax_nojs.html diff --git a/www/template/layout_print.html b/template/layout_print.html similarity index 100% rename from www/template/layout_print.html rename to template/layout_print.html diff --git a/www/template/layout_public.html b/template/layout_public.html similarity index 100% rename from www/template/layout_public.html rename to template/layout_public.html diff --git a/www/template/layout_vue.html b/template/layout_vue.html similarity index 100% rename from www/template/layout_vue.html rename to template/layout_vue.html diff --git a/www/template/login/index/form.html b/template/login/index/form.html similarity index 100% rename from www/template/login/index/form.html rename to template/login/index/form.html diff --git a/www/template/login/index/form_logged.html b/template/login/index/form_logged.html similarity index 100% rename from www/template/login/index/form_logged.html rename to template/login/index/form_logged.html diff --git a/www/template/login/index/google.html b/template/login/index/google.html similarity index 100% rename from www/template/login/index/google.html rename to template/login/index/google.html diff --git a/www/template/login/index/head.css b/template/login/index/head.css similarity index 100% rename from www/template/login/index/head.css rename to template/login/index/head.css diff --git a/www/template/login/index/login_problem.html b/template/login/index/login_problem.html similarity index 100% rename from www/template/login/index/login_problem.html rename to template/login/index/login_problem.html diff --git a/www/template/login/index/main.html b/template/login/index/main.html similarity index 100% rename from www/template/login/index/main.html rename to template/login/index/main.html diff --git a/www/template/login/index/onload.js b/template/login/index/onload.js similarity index 100% rename from www/template/login/index/onload.js rename to template/login/index/onload.js diff --git a/www/template/login/index/title.html b/template/login/index/title.html similarity index 100% rename from www/template/login/index/title.html rename to template/login/index/title.html diff --git a/www/template/login/mfa/form.html b/template/login/mfa/form.html similarity index 100% rename from www/template/login/mfa/form.html rename to template/login/mfa/form.html diff --git a/www/template/login/mfa/head.css b/template/login/mfa/head.css similarity index 100% rename from www/template/login/mfa/head.css rename to template/login/mfa/head.css diff --git a/www/template/login/mfa/main.html b/template/login/mfa/main.html similarity index 100% rename from www/template/login/mfa/main.html rename to template/login/mfa/main.html diff --git a/www/template/login/mfa/title.html b/template/login/mfa/title.html similarity index 100% rename from www/template/login/mfa/title.html rename to template/login/mfa/title.html diff --git a/www/template/login/url.html b/template/login/url.html similarity index 100% rename from www/template/login/url.html rename to template/login/url.html diff --git a/www/template/login/url_logoff.html b/template/login/url_logoff.html similarity index 100% rename from www/template/login/url_logoff.html rename to template/login/url_logoff.html diff --git a/www/template/main/index/head.css b/template/main/index/head.css similarity index 100% rename from www/template/main/index/head.css rename to template/main/index/head.css diff --git a/www/template/main/index/head.js b/template/main/index/head.js similarity index 100% rename from www/template/main/index/head.js rename to template/main/index/head.js diff --git a/www/template/main/index/load_script.html b/template/main/index/load_script.html similarity index 100% rename from www/template/main/index/load_script.html rename to template/main/index/load_script.html diff --git a/www/template/main/index/main.html b/template/main/index/main.html similarity index 100% rename from www/template/main/index/main.html rename to template/main/index/main.html diff --git a/www/template/main/index/onload.js b/template/main/index/onload.js similarity index 100% rename from www/template/main/index/onload.js rename to template/main/index/onload.js diff --git a/www/template/main/index/pane_title.html b/template/main/index/pane_title.html similarity index 100% rename from www/template/main/index/pane_title.html rename to template/main/index/pane_title.html diff --git a/www/template/main/index/std_pane.html b/template/main/index/std_pane.html similarity index 100% rename from www/template/main/index/std_pane.html rename to template/main/index/std_pane.html diff --git a/www/template/main/index/theme1.js b/template/main/index/theme1.js similarity index 100% rename from www/template/main/index/theme1.js rename to template/main/index/theme1.js diff --git a/www/template/main/index/theme2.js b/template/main/index/theme2.js similarity index 100% rename from www/template/main/index/theme2.js rename to template/main/index/theme2.js diff --git a/www/template/main/index/theme30.js b/template/main/index/theme30.js similarity index 100% rename from www/template/main/index/theme30.js rename to template/main/index/theme30.js diff --git a/www/template/main/index/title.html b/template/main/index/title.html similarity index 100% rename from www/template/main/index/title.html rename to template/main/index/title.html diff --git a/www/template/main/index/type_barchart.html b/template/main/index/type_barchart.html similarity index 100% rename from www/template/main/index/type_barchart.html rename to template/main/index/type_barchart.html diff --git a/www/template/main/index/type_html.html b/template/main/index/type_html.html similarity index 100% rename from www/template/main/index/type_html.html rename to template/main/index/type_html.html diff --git a/www/template/main/index/type_linechart.html b/template/main/index/type_linechart.html similarity index 100% rename from www/template/main/index/type_linechart.html rename to template/main/index/type_linechart.html diff --git a/www/template/main/index/type_piechart.html b/template/main/index/type_piechart.html similarity index 100% rename from www/template/main/index/type_piechart.html rename to template/main/index/type_piechart.html diff --git a/www/template/main/index/type_table.html b/template/main/index/type_table.html similarity index 100% rename from www/template/main/index/type_table.html rename to template/main/index/type_table.html diff --git a/www/template/main/url.html b/template/main/url.html similarity index 100% rename from www/template/main/url.html rename to template/main/url.html diff --git a/www/template/my/feedback/showform/modal.html b/template/my/feedback/showform/modal.html similarity index 100% rename from www/template/my/feedback/showform/modal.html rename to template/my/feedback/showform/modal.html diff --git a/www/template/my/feedback/showform/title.html b/template/my/feedback/showform/title.html similarity index 100% rename from www/template/my/feedback/showform/title.html rename to template/my/feedback/showform/title.html diff --git a/www/template/my/feedback/url.html b/template/my/feedback/url.html similarity index 100% rename from www/template/my/feedback/url.html rename to template/my/feedback/url.html diff --git a/www/template/my/filters/create/modal.html b/template/my/filters/create/modal.html similarity index 100% rename from www/template/my/filters/create/modal.html rename to template/my/filters/create/modal.html diff --git a/www/template/my/filters/create/title.html b/template/my/filters/create/title.html similarity index 100% rename from www/template/my/filters/create/title.html rename to template/my/filters/create/title.html diff --git a/www/template/my/filters/entities.sel b/template/my/filters/entities.sel similarity index 100% rename from www/template/my/filters/entities.sel rename to template/my/filters/entities.sel diff --git a/www/template/my/filters/index/list_filter_more.html b/template/my/filters/index/list_filter_more.html similarity index 100% rename from www/template/my/filters/index/list_filter_more.html rename to template/my/filters/index/list_filter_more.html diff --git a/www/template/my/filters/index/list_table.html b/template/my/filters/index/list_table.html similarity index 100% rename from www/template/my/filters/index/list_table.html rename to template/my/filters/index/list_table.html diff --git a/www/template/my/filters/index/main.html b/template/my/filters/index/main.html similarity index 100% rename from www/template/my/filters/index/main.html rename to template/my/filters/index/main.html diff --git a/www/template/my/filters/index/row_click_url.html b/template/my/filters/index/row_click_url.html similarity index 100% rename from www/template/my/filters/index/row_click_url.html rename to template/my/filters/index/row_click_url.html diff --git a/www/template/my/filters/index/title.html b/template/my/filters/index/title.html similarity index 100% rename from www/template/my/filters/index/title.html rename to template/my/filters/index/title.html diff --git a/www/template/my/filters/showform/form.html b/template/my/filters/showform/form.html similarity index 100% rename from www/template/my/filters/showform/form.html rename to template/my/filters/showform/form.html diff --git a/www/template/my/filters/showform/main.html b/template/my/filters/showform/main.html similarity index 100% rename from www/template/my/filters/showform/main.html rename to template/my/filters/showform/main.html diff --git a/www/template/my/filters/showform/title.html b/template/my/filters/showform/title.html similarity index 100% rename from www/template/my/filters/showform/title.html rename to template/my/filters/showform/title.html diff --git a/www/template/my/filters/url.html b/template/my/filters/url.html similarity index 100% rename from www/template/my/filters/url.html rename to template/my/filters/url.html diff --git a/www/template/my/lists/create/modal.html b/template/my/lists/create/modal.html similarity index 100% rename from www/template/my/lists/create/modal.html rename to template/my/lists/create/modal.html diff --git a/www/template/my/lists/create/title.html b/template/my/lists/create/title.html similarity index 100% rename from www/template/my/lists/create/title.html rename to template/my/lists/create/title.html diff --git a/www/template/my/lists/entities.sel b/template/my/lists/entities.sel similarity index 100% rename from www/template/my/lists/entities.sel rename to template/my/lists/entities.sel diff --git a/www/template/my/lists/index/list_filter_more.html b/template/my/lists/index/list_filter_more.html similarity index 100% rename from www/template/my/lists/index/list_filter_more.html rename to template/my/lists/index/list_filter_more.html diff --git a/www/template/my/lists/index/list_table.html b/template/my/lists/index/list_table.html similarity index 100% rename from www/template/my/lists/index/list_table.html rename to template/my/lists/index/list_table.html diff --git a/www/template/my/lists/index/main.html b/template/my/lists/index/main.html similarity index 100% rename from www/template/my/lists/index/main.html rename to template/my/lists/index/main.html diff --git a/www/template/my/lists/index/row_click_url.html b/template/my/lists/index/row_click_url.html similarity index 100% rename from www/template/my/lists/index/row_click_url.html rename to template/my/lists/index/row_click_url.html diff --git a/www/template/my/lists/index/title.html b/template/my/lists/index/title.html similarity index 100% rename from www/template/my/lists/index/title.html rename to template/my/lists/index/title.html diff --git a/www/template/my/lists/showform/form.html b/template/my/lists/showform/form.html similarity index 100% rename from www/template/my/lists/showform/form.html rename to template/my/lists/showform/form.html diff --git a/www/template/my/lists/showform/main.html b/template/my/lists/showform/main.html similarity index 100% rename from www/template/my/lists/showform/main.html rename to template/my/lists/showform/main.html diff --git a/www/template/my/lists/showform/title.html b/template/my/lists/showform/title.html similarity index 100% rename from www/template/my/lists/showform/title.html rename to template/my/lists/showform/title.html diff --git a/www/template/my/lists/url.html b/template/my/lists/url.html similarity index 100% rename from www/template/my/lists/url.html rename to template/my/lists/url.html diff --git a/www/template/my/mfa/save/main.html b/template/my/mfa/save/main.html similarity index 100% rename from www/template/my/mfa/save/main.html rename to template/my/mfa/save/main.html diff --git a/www/template/my/mfa/save/onload.js b/template/my/mfa/save/onload.js similarity index 100% rename from www/template/my/mfa/save/onload.js rename to template/my/mfa/save/onload.js diff --git a/www/template/my/mfa/save/title.html b/template/my/mfa/save/title.html similarity index 100% rename from www/template/my/mfa/save/title.html rename to template/my/mfa/save/title.html diff --git a/www/template/my/mfa/showform/main.html b/template/my/mfa/showform/main.html similarity index 100% rename from www/template/my/mfa/showform/main.html rename to template/my/mfa/showform/main.html diff --git a/www/template/my/mfa/showform/title.html b/template/my/mfa/showform/title.html similarity index 100% rename from www/template/my/mfa/showform/title.html rename to template/my/mfa/showform/title.html diff --git a/www/template/my/mfa/url.html b/template/my/mfa/url.html similarity index 100% rename from www/template/my/mfa/url.html rename to template/my/mfa/url.html diff --git a/www/template/my/password/showform/form.html b/template/my/password/showform/form.html similarity index 100% rename from www/template/my/password/showform/form.html rename to template/my/password/showform/form.html diff --git a/www/template/my/password/showform/main.html b/template/my/password/showform/main.html similarity index 100% rename from www/template/my/password/showform/main.html rename to template/my/password/showform/main.html diff --git a/www/template/my/password/showform/onload.js b/template/my/password/showform/onload.js similarity index 100% rename from www/template/my/password/showform/onload.js rename to template/my/password/showform/onload.js diff --git a/www/template/my/password/showform/title.html b/template/my/password/showform/title.html similarity index 100% rename from www/template/my/password/showform/title.html rename to template/my/password/showform/title.html diff --git a/www/template/my/password/url.html b/template/my/password/url.html similarity index 100% rename from www/template/my/password/url.html rename to template/my/password/url.html diff --git a/www/template/my/settings/showform/form.html b/template/my/settings/showform/form.html similarity index 100% rename from www/template/my/settings/showform/form.html rename to template/my/settings/showform/form.html diff --git a/www/template/my/settings/showform/main.html b/template/my/settings/showform/main.html similarity index 100% rename from www/template/my/settings/showform/main.html rename to template/my/settings/showform/main.html diff --git a/www/template/my/settings/showform/title.html b/template/my/settings/showform/title.html similarity index 100% rename from www/template/my/settings/showform/title.html rename to template/my/settings/showform/title.html diff --git a/www/template/my/settings/url.html b/template/my/settings/url.html similarity index 100% rename from www/template/my/settings/url.html rename to template/my/settings/url.html diff --git a/www/template/my/views/index/list_filter_more.html b/template/my/views/index/list_filter_more.html similarity index 100% rename from www/template/my/views/index/list_filter_more.html rename to template/my/views/index/list_filter_more.html diff --git a/www/template/my/views/index/list_table.html b/template/my/views/index/list_table.html similarity index 100% rename from www/template/my/views/index/list_table.html rename to template/my/views/index/list_table.html diff --git a/www/template/my/views/index/main.html b/template/my/views/index/main.html similarity index 100% rename from www/template/my/views/index/main.html rename to template/my/views/index/main.html diff --git a/www/template/my/views/index/row_click_url.html b/template/my/views/index/row_click_url.html similarity index 100% rename from www/template/my/views/index/row_click_url.html rename to template/my/views/index/row_click_url.html diff --git a/www/template/my/views/index/title.html b/template/my/views/index/title.html similarity index 100% rename from www/template/my/views/index/title.html rename to template/my/views/index/title.html diff --git a/www/template/my/views/showform/form.html b/template/my/views/showform/form.html similarity index 100% rename from www/template/my/views/showform/form.html rename to template/my/views/showform/form.html diff --git a/www/template/my/views/showform/main.html b/template/my/views/showform/main.html similarity index 100% rename from www/template/my/views/showform/main.html rename to template/my/views/showform/main.html diff --git a/www/template/my/views/showform/title.html b/template/my/views/showform/title.html similarity index 100% rename from www/template/my/views/showform/title.html rename to template/my/views/showform/title.html diff --git a/www/template/my/views/url.html b/template/my/views/url.html similarity index 100% rename from www/template/my/views/url.html rename to template/my/views/url.html diff --git a/www/template/password/index/main.html b/template/password/index/main.html similarity index 100% rename from www/template/password/index/main.html rename to template/password/index/main.html diff --git a/www/template/password/index/onload.js b/template/password/index/onload.js similarity index 100% rename from www/template/password/index/onload.js rename to template/password/index/onload.js diff --git a/www/template/password/index/title.html b/template/password/index/title.html similarity index 100% rename from www/template/password/index/title.html rename to template/password/index/title.html diff --git a/www/template/password/reset/main.html b/template/password/reset/main.html similarity index 100% rename from www/template/password/reset/main.html rename to template/password/reset/main.html diff --git a/www/template/password/reset/onload.js b/template/password/reset/onload.js similarity index 100% rename from www/template/password/reset/onload.js rename to template/password/reset/onload.js diff --git a/www/template/password/reset/title.html b/template/password/reset/title.html similarity index 100% rename from www/template/password/reset/title.html rename to template/password/reset/title.html diff --git a/www/template/password/sent/main.html b/template/password/sent/main.html similarity index 100% rename from www/template/password/sent/main.html rename to template/password/sent/main.html diff --git a/www/template/password/sent/title.html b/template/password/sent/title.html similarity index 100% rename from www/template/password/sent/title.html rename to template/password/sent/title.html diff --git a/www/template/password/url.html b/template/password/url.html similarity index 100% rename from www/template/password/url.html rename to template/password/url.html diff --git a/www/template/signup/showform/form.html b/template/signup/showform/form.html similarity index 100% rename from www/template/signup/showform/form.html rename to template/signup/showform/form.html diff --git a/www/template/signup/showform/head.css b/template/signup/showform/head.css similarity index 100% rename from www/template/signup/showform/head.css rename to template/signup/showform/head.css diff --git a/www/template/signup/showform/main.html b/template/signup/showform/main.html similarity index 100% rename from www/template/signup/showform/main.html rename to template/signup/showform/main.html diff --git a/www/template/signup/showform/onload.js b/template/signup/showform/onload.js similarity index 100% rename from www/template/signup/showform/onload.js rename to template/signup/showform/onload.js diff --git a/www/template/signup/showform/title.html b/template/signup/showform/title.html similarity index 100% rename from www/template/signup/showform/title.html rename to template/signup/showform/title.html diff --git a/www/template/signup/url.html b/template/signup/url.html similarity index 100% rename from www/template/signup/url.html rename to template/signup/url.html diff --git a/www/template/site_name.html b/template/site_name.html similarity index 100% rename from www/template/site_name.html rename to template/site_name.html diff --git a/www/template/sitemap/index/children.html b/template/sitemap/index/children.html similarity index 100% rename from www/template/sitemap/index/children.html rename to template/sitemap/index/children.html diff --git a/www/template/sitemap/index/head.css b/template/sitemap/index/head.css similarity index 100% rename from www/template/sitemap/index/head.css rename to template/sitemap/index/head.css diff --git a/www/template/sitemap/index/main.html b/template/sitemap/index/main.html similarity index 100% rename from www/template/sitemap/index/main.html rename to template/sitemap/index/main.html diff --git a/www/template/sitemap/index/pages_children.html b/template/sitemap/index/pages_children.html similarity index 100% rename from www/template/sitemap/index/pages_children.html rename to template/sitemap/index/pages_children.html diff --git a/www/template/sitemap/index/pages_tree.html b/template/sitemap/index/pages_tree.html similarity index 100% rename from www/template/sitemap/index/pages_tree.html rename to template/sitemap/index/pages_tree.html diff --git a/www/template/sitemap/index/title.html b/template/sitemap/index/title.html similarity index 100% rename from www/template/sitemap/index/title.html rename to template/sitemap/index/title.html diff --git a/www/template/sitemap/url.html b/template/sitemap/url.html similarity index 100% rename from www/template/sitemap/url.html rename to template/sitemap/url.html diff --git a/www/template/sys/backup/index/main.html b/template/sys/backup/index/main.html similarity index 100% rename from www/template/sys/backup/index/main.html rename to template/sys/backup/index/main.html diff --git a/www/template/sys/backup/index/title.html b/template/sys/backup/index/title.html similarity index 100% rename from www/template/sys/backup/index/title.html rename to template/sys/backup/index/title.html diff --git a/www/template/sys/backup/url.html b/template/sys/backup/url.html similarity index 100% rename from www/template/sys/backup/url.html rename to template/sys/backup/url.html diff --git a/www/template/test/index/head.css b/template/test/index/head.css similarity index 100% rename from www/template/test/index/head.css rename to template/test/index/head.css diff --git a/www/template/test/index/main.html b/template/test/index/main.html similarity index 100% rename from www/template/test/index/main.html rename to template/test/index/main.html diff --git a/www/template/test/index/nolang_test.html b/template/test/index/nolang_test.html similarity index 100% rename from www/template/test/index/nolang_test.html rename to template/test/index/nolang_test.html diff --git a/www/template/test/index/onload.js b/template/test/index/onload.js similarity index 100% rename from www/template/test/index/onload.js rename to template/test/index/onload.js diff --git a/www/template/test/index/subfolder/1.html b/template/test/index/subfolder/1.html similarity index 100% rename from www/template/test/index/subfolder/1.html rename to template/test/index/subfolder/1.html diff --git a/www/template/test/index/subfolder/2.html b/template/test/index/subfolder/2.html similarity index 100% rename from www/template/test/index/subfolder/2.html rename to template/test/index/subfolder/2.html diff --git a/www/template/test/index/subfolder/3.html b/template/test/index/subfolder/3.html similarity index 100% rename from www/template/test/index/subfolder/3.html rename to template/test/index/subfolder/3.html diff --git a/www/template/test/index/subfolder/4.txt b/template/test/index/subfolder/4.txt similarity index 100% rename from www/template/test/index/subfolder/4.txt rename to template/test/index/subfolder/4.txt diff --git a/www/template/test/index/subfolder/5.txt b/template/test/index/subfolder/5.txt similarity index 100% rename from www/template/test/index/subfolder/5.txt rename to template/test/index/subfolder/5.txt diff --git a/www/template/test/index/title.html b/template/test/index/title.html similarity index 100% rename from www/template/test/index/title.html rename to template/test/index/title.html diff --git a/www/template/test/index/true_value.html b/template/test/index/true_value.html similarity index 100% rename from www/template/test/index/true_value.html rename to template/test/index/true_value.html diff --git a/www/.htaccess b/www/.htaccess index 7a9b6bc..90c66b2 100644 --- a/www/.htaccess +++ b/www/.htaccess @@ -4,11 +4,8 @@ RewriteBase / #RewriteRule ^css/site_\d+.css /css/site.css [L] #RewriteRule ^js/site_\d+.js /js/site.js [L] -#deny access to templates -RewriteRule ^template/ - [F,L,NC] - #ignore assets dirs by fw engine -RewriteRule ^(?:assets|upload)/ - [L] +RewriteRule ^assets/ - [L] #fw engine RewriteCond %{REQUEST_FILENAME} !-l diff --git a/www/index.php b/www/index.php index 592ffcc..356d094 100644 --- a/www/index.php +++ b/www/index.php @@ -1,6 +1,6 @@ '/Login/(Logoff)', //special case for Logoff diff --git a/www/php/controllers/AdminDB.php b/www/php/controllers/AdminDB.php deleted file mode 100644 index b3f9459..0000000 --- a/www/php/controllers/AdminDB.php +++ /dev/null @@ -1,13 +0,0 @@ -$CONFIG['DB']['USER'],#required - 'pwd'=>$CONFIG['DB']['PWD'], #required - 'db'=>$CONFIG['DB']['DBNAME'], #optional, default DB - 'host'=>$CONFIG['DB']['HOST'],#optional - 'port'=>$CONFIG['DB']['PORT'],#optional - 'chset'=>"utf8",#optional, default charset - ); - loadcfg(); - if (!isset($_REQUEST['q'])) { - $_REQUEST['XSS']=$_SESSION['XSS']; - $_REQUEST['q']=b64e('SHOW TABLE STATUS'); - } - }else{ - $ACCESS_PWD='321'; #set Access password here to enable access to database as non-logged admin - if (!$ACCESS_PWD){ - rw("Set \$ACCESS_PWD or login to site as an Administrator"); - exit; - } - } - -?> \ No newline at end of file diff --git a/www/web.config b/www/web.config index ffa52b5..7caac10 100644 --- a/www/web.config +++ b/www/web.config @@ -13,7 +13,7 @@ - +