toga-ai 1.0.838 → 1.0.839
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/knowledge/1.0/apps/togaview/INDEX.md +1 -0
- package/knowledge/1.0/apps/togaview/architecture.md +5 -2
- package/knowledge/1.0/apps/togaview/features/login-flows.md +5 -1
- package/knowledge/1.0/apps/togaview/features/msp-dashboard.md +7 -2
- package/knowledge/1.0/apps/togaview/features/ticket-export.md +61 -0
- package/knowledge/1.0/standards/backend-php.md +13 -1
- package/knowledge/INDEX.md +1 -1
- package/package.json +1 -1
|
@@ -7,5 +7,6 @@
|
|
|
7
7
|
| [MSP Dashboard & Ticket Visibility Rules](features/msp-dashboard.md) | Why tickets "disappear" in the TOGa View client portal — the per-page ticket scoping and the `tickets.customerid` / `SMBContracts` dependencies; open when an MS |
|
|
8
8
|
| [Retail Commerce Lifecycle (winback, purchase, claims, activation)](features/retail-commerce-lifecycle.md) | TOGa View retail tech-support commerce flows (winback renewal, purchase, service activation, AIG claims — chiefly ODP, Walmart B2B, Staples Protection); open fo |
|
|
9
9
|
| [Ticket Detail Page Security & Its Three Forks (common/*/ticket.php)](features/ticket-detail-page.md) | Security of the TOGa View ticket detail page `common/*/ticket.php` and its THREE near-duplicate forks (IDOR ownership checks, int-cast, name-split guard, sqlPro |
|
|
10
|
+
| [MSP Ticket Export (.xlsx)](features/ticket-export.md) | The MSP dashboard "Export Tickets" .xlsx download — single-request design, why a two-request (build then download) export cannot work behind the shared load bal |
|
|
10
11
|
| [Git & php -l Tooling Against the `_/` App Root (MSYS path mangling)](workflows/git-and-php-tooling-under-the-underscore-app-root.md) | Git Bash/MSYS on Windows mangles any `_/`-prefixed path argument (empty diff, misfired `php -l`), and togaview's app root IS `_/`; open before running git/php a |
|
|
11
12
|
| [Route 53 Hosted Zone Migration Between AWS Accounts](workflows/route53-cross-account-zone-migration.md) | Playbook to move a Route 53 hosted zone's records between AWS accounts (export → new zone → import → repoint NS); used for `togaview.com`; open for any cross-ac |
|
|
@@ -6,7 +6,7 @@ project: TOGa View
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-18
|
|
10
10
|
owners: ["mhammontree", "jcardinal"]
|
|
11
11
|
files:
|
|
12
12
|
- index.php
|
|
@@ -20,6 +20,7 @@ related:
|
|
|
20
20
|
- 1.0/apps/togadesk/architecture.md
|
|
21
21
|
- 1.0/apps/togaview/features/login-flows.md
|
|
22
22
|
- 1.0/apps/togaview/features/msp-dashboard.md
|
|
23
|
+
- 1.0/apps/togaview/features/ticket-export.md
|
|
23
24
|
- 1.0/apps/togaview/features/ticket-detail-page.md
|
|
24
25
|
- 1.0/apps/togaview/features/retail-commerce-lifecycle.md
|
|
25
26
|
---
|
|
@@ -59,7 +60,7 @@ Architecturally it is a **multi-tenant, single-codebase MVC portal**: one deploy
|
|
|
59
60
|
- `App_MVC::parseRoute()` (in `library`) turns the request URI into route parts and resolves `mvc/<route…>/<method>.php`, where `<method>` is `get` or `post` (from the HTTP verb, overridable via a `_method` POST param). It matches longest-to-shortest, so overlapping paths can surprise — keep route folders distinct.
|
|
60
61
|
- **GET** (`get.php`) selects a template via the fallback chain above and passes data to it (no central master-layout system — each `common/<…>/<page>.php` is self-contained).
|
|
61
62
|
- **POST** (`post.php`) reads input with `App_Page::getVar()/getVarIfSet()`, does the work, then `header("Location: …"); exit;`. Login/auth, ticket creation, user management live here.
|
|
62
|
-
- AJAX requests dispatch through `App_Framework::ajaxHandler()
|
|
63
|
+
- AJAX requests dispatch through `App_Framework::ajaxHandler()`. **`?fileId` is the "skip the page shell" flag, not a filename** — a GET without it renders the shell, which flushes output (`App_Page::flushCapture`, `library/app/page.php:213`) before the route runs, so any `header()` then fails. Every download/binary route must carry a `fileId` (the value can be a fixed marker); with it, the request goes straight to `App_MVC::loadFile()`. See [MSP ticket export](features/ticket-export.md). `mvc/_TEMPLATE/` is the scaffold for new pages.
|
|
63
64
|
|
|
64
65
|
## Browser subsystem (grids & modals)
|
|
65
66
|
`_/browser/`: `Browser_Datatable_*` classes (`initialize()` registers fields/joins/DB link via `setDatabaseLinkString()` / `setTableJoins()`, `Browser_DataTableField` for cell rendering), `Browser_Modal_*` classes (capture HTML via `App_Page::startCapture()/stopCapture()`, e.g. `Browser_Modal_CreateNewTicket`, `CreatenewUser`), and `Browser_Pagination_TogaView`.
|
|
@@ -94,6 +95,7 @@ Read-only client-facing KB sourced from TOGa Desk's `db_togadesk` KB tables, gat
|
|
|
94
95
|
- New client onboarding touches at minimum: the HTTP_HOST switch in `framework.php`, a login flow (or SAML branch), and possibly the `smbContractSkus` whitelist in `login/post.php`.
|
|
95
96
|
|
|
96
97
|
## Gotchas
|
|
98
|
+
- **Never pass a file between two requests via local disk.** togaview runs on several EB instances behind a shared ALB, so `/var/www/cache` is per-instance — request 2 lands on another instance and 404s. Build and return in one request. See [MSP ticket export](features/ticket-export.md).
|
|
97
99
|
- **Template fallback is per-page, not central.** Each `get.php` re-implements the host→stylePath→togaview chain; it's easy to miss a host-specific branch.
|
|
98
100
|
- **`common/togaview/*` is the blast radius** — editing a generic page changes nearly every client at once.
|
|
99
101
|
- **Divergent session shapes** across login flows (see Session model) — always use the `emailAddress`/`email` fallback and tolerate a missing `togadeskClientId`.
|
|
@@ -105,5 +107,6 @@ Read-only client-facing KB sourced from TOGa Desk's `db_togadesk` KB tables, gat
|
|
|
105
107
|
- [TOGa Desk architecture](../togadesk/architecture.md)
|
|
106
108
|
- [Login flows & session variables](features/login-flows.md)
|
|
107
109
|
- [MSP dashboard & ticket visibility](features/msp-dashboard.md)
|
|
110
|
+
- [MSP ticket export (.xlsx)](features/ticket-export.md)
|
|
108
111
|
- [Ticket detail page security](features/ticket-detail-page.md)
|
|
109
112
|
- [Retail commerce lifecycle](features/retail-commerce-lifecycle.md)
|
|
@@ -6,7 +6,7 @@ project: TOGa View
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-18
|
|
10
10
|
owners: ["mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- mvc/login/post.php
|
|
@@ -94,9 +94,13 @@ Also note the rumcsi SAML `acs` case (:459-472) sets **no** session variables on
|
|
|
94
94
|
Only togadesk writes it (`desk/includes/functions.php:588/635/672/689`, plus `actions/user/loginAsAnother.php:17-18`). Both togaview login paths set session state only. So **`lastlogin` cannot measure portal usage** — a portal user logging in daily still shows NULL. See [TOGa Desk staff login](../../togadesk/features/staff-login-authentication.md).
|
|
95
95
|
|
|
96
96
|
### Other
|
|
97
|
+
- **towfoundation has a real password login path despite being a SAML host** — `mvc/login/post.php:82-96` matches `people.password = sha1($pass)` + `isActive = 1` + `clientid` = TOW Foundation; `people.type = 'admin'` maps to `USERTYPE_ADMIN` and lands `/msp_dashboard`. Useful for testing a SAML tenant locally with no IdP.
|
|
97
98
|
- **`people.password` is a bare SHA-1** (`mvc/login/post.php:21/96/471`) and the hash is also carried in `$_SESSION['Contact']['password']`. Known and accepted for now — togaview is migrating to 2.0, so no remediation is planned here; do not "fix" it as a side effect of another ticket.
|
|
98
99
|
- Code consuming the session must read `emailAddress` with an `email` fallback (and tolerate missing `togadeskClientId`) or it rejects all staplesprotection users — this broke the ticket.php ownership check until the fallback was added (June 2026).
|
|
99
100
|
|
|
101
|
+
## Change history
|
|
102
|
+
- 2026-09-18 — Recorded towfoundation's password login path (SAML host with a live sha1 password flow) (mhammontree)
|
|
103
|
+
|
|
100
104
|
## Related
|
|
101
105
|
- [MSP dashboard & ticket visibility](msp-dashboard.md)
|
|
102
106
|
- [Ticket detail page](ticket-detail-page.md)
|
|
@@ -6,7 +6,7 @@ project: TOGa View
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-18
|
|
10
10
|
owners: ["mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- common/togaview/msp_dashboard.php
|
|
@@ -15,6 +15,7 @@ files:
|
|
|
15
15
|
- mvc/support/support.php
|
|
16
16
|
related:
|
|
17
17
|
- 1.0/apps/togaview/features/login-flows.md
|
|
18
|
+
- 1.0/apps/togaview/features/ticket-export.md
|
|
18
19
|
- 1.0/apps/togadesk/features/ticket-lifecycle.md
|
|
19
20
|
- 1.0/apps/togadesk/features/smb-contract-editing.md
|
|
20
21
|
---
|
|
@@ -50,14 +51,18 @@ Different pages scope tickets **differently**, and the MSP pages depend on `tick
|
|
|
50
51
|
- An MSP's "customers" are **TOGA-side `Customers` rows**, NOT togadesk clients. All of an MSP's tickets carry the MSP's own `tickets.clientid`.
|
|
51
52
|
|
|
52
53
|
### Client variations
|
|
53
|
-
- The single-department pattern still exists in the `newcenturyholdingsllc` and `towfoundation` dashboard variants (
|
|
54
|
+
- The single-department pattern still exists in the `newcenturyholdingsllc` and `towfoundation` dashboard variants (`departmentid = $_SESSION['clientDepartmentId']`, towfoundation `:271` + the count queries from `~:287`) and in enterprise_dashboard's `clientDepartmentId` filter for end users. **Those counters are the stale/buggy side — the unfinished half of the June 2026 fix.** The .xlsx export has never filtered by department and deliberately still does not (adding one would spread the bug) — so on those two forks the export legitimately returns more rows than the counters. See [Ticket export](ticket-export.md).
|
|
54
55
|
- Only towfoundation / newcenturyholdingsllc have their own dashboard variants; everyone else runs the generic `common/togaview/` pages.
|
|
55
56
|
|
|
56
57
|
## Gotchas
|
|
57
58
|
- "You have no assigned clients" usually means the MSP's `SMBContracts.clientMspId` was corrupted by the togadesk contract modal (see smb-contract-editing doc) — check data before debugging code.
|
|
58
59
|
- An expired `SMBContracts.dateContractEnd` is display-only; it does not hide tickets.
|
|
59
60
|
|
|
61
|
+
## Change history
|
|
62
|
+
- 2026-09-18 — Noted the towfoundation/newcentury counters still use the single-department filter; export is department-wide by design (mhammontree)
|
|
63
|
+
|
|
60
64
|
## Related
|
|
61
65
|
- [Login flows](login-flows.md)
|
|
66
|
+
- [MSP ticket export (.xlsx)](ticket-export.md)
|
|
62
67
|
- [TOGa Desk ticket lifecycle](../../togadesk/features/ticket-lifecycle.md)
|
|
63
68
|
- [SMB contract editing](../../togadesk/features/smb-contract-editing.md)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: MSP Ticket Export (.xlsx)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: togaview
|
|
5
|
+
project: TOGa View
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-18
|
|
10
|
+
owners: ["mhammontree"]
|
|
11
|
+
files:
|
|
12
|
+
- _/app/export/ticketexport.php
|
|
13
|
+
- mvc/msp_dashboard/export/get.php
|
|
14
|
+
- common/togaview/msp_dashboard.php
|
|
15
|
+
- common/towfoundation/msp_dashboard.php
|
|
16
|
+
- common/newcenturyholdingsllc/msp_dashboard.php
|
|
17
|
+
related:
|
|
18
|
+
- 1.0/apps/togaview/features/msp-dashboard.md
|
|
19
|
+
- 1.0/apps/togaview/architecture.md
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
The MSP dashboard "Export Tickets" .xlsx download — single-request design, why a two-request (build then download) export cannot work behind the shared load balancer, and the identity gate across the three dashboard forks.
|
|
23
|
+
|
|
24
|
+
## Summary
|
|
25
|
+
`App_Export_TicketExport` (`_/app/export/ticketexport.php`) builds the ticket workbook for all three msp_dashboard forks. `mvc/msp_dashboard/export/get.php` builds it **in one request**, reads it fully into memory, then sends download headers. Nothing touches shared disk and the endpoint takes **no** user input.
|
|
26
|
+
|
|
27
|
+
## How it works
|
|
28
|
+
1. Export button hits `mvc/msp_dashboard/export/get.php` with a `fileId` parameter (see gotcha — required, value is only a marker).
|
|
29
|
+
2. Route gates on identity: userType in (CUSTOMER, MSP, ADMIN) **and** `$_SESSION['togadeskClientId']`; failure → `App_JavaScript::jsRedirect('/login')`.
|
|
30
|
+
3. `App_Export_TicketExport` builds the workbook, writes it to `tempnam(sys_get_temp_dir(), …)`, `file_get_contents()` reads it back, `unlink()` in a `finally`.
|
|
31
|
+
4. Only then are download headers + the bytes sent. On failure → redirect to `?exportFailed=1`.
|
|
32
|
+
|
|
33
|
+
**Scope:** the export is deliberately **not** filtered by `departmentid` — it returns all of the client's tickets and therefore more rows than the dashboard counters. Verified on production (clientid 168: 693 tickets across departments 307/312/310/0, all owned by TOW Foundation). Not a leak — see [msp-dashboard](msp-dashboard.md) for the stale single-department counters on two forks.
|
|
34
|
+
|
|
35
|
+
**Shared class, three forks.** Before 2026-09-18 each of `common/{togaview,towfoundation,newcenturyholdingsllc}/msp_dashboard.php` carried its own `getExportFileName()` copy (already drifted). All three now call `App_Export_TicketExport`. Autoload: `App_Export_TicketExport` → `__APPROOT__/app/export/ticketexport.php`, i.e. togaview's `_/` folder (same placement as `App_Framework_TogaView`).
|
|
36
|
+
|
|
37
|
+
## Gotchas
|
|
38
|
+
|
|
39
|
+
### Never pass a file between two requests via local disk (EB + shared ALB)
|
|
40
|
+
The original export was two requests: request 1 wrote the .xlsx to `$config['internal']['cache_folder']` = `/var/www/cache` and echoed the name; request 2 downloaded it. togaview runs on several Elastic Beanstalk instances behind a **shared** Application Load Balancer (`.ebextensions/050_register_instance_to_shared_application_load_balancer.config`), and `/var/www/cache` is **per-instance** local disk (`.ebextensions/007_setup_export_cache_folders.config`). Request 2 usually landed on a different instance → "File not found. Please try exporting again." Intermittent, so it looked flaky, not broken. Applies to **any** 1.0 app on EB behind the shared ALB. The s3fs mount exists but `cache_folder` does not use it. *(Open: 007 + `ebs/setup_export_cache_folders.php` now have zero readers — dead infra, separate cleanup.)*
|
|
41
|
+
|
|
42
|
+
### A download route in togaview MUST carry `fileId`
|
|
43
|
+
`App_Framework_TogaView::renderIndex()` (`_/app/framework.php` ~1265): a GET **without** `fileId` renders the page shell, which calls `App_Page::flushCapture()` (`library/app/page.php:213`) and flushes output **before** the route file runs — any `header()` then fails with "Cannot modify header information". A GET **with** `fileId` skips the shell and goes straight to `App_MVC::loadFile()`. The value can be a fixed marker; it need not be a filename.
|
|
44
|
+
|
|
45
|
+
### Build the whole file, then send headers — never stream
|
|
46
|
+
`App_Error::handleError` turns **every** PHP warning into `handleException`, which `exit`s (`library/app/error.php` ~407 and ~671). Bytes already on the wire → HTTP 200 with a truncated .xlsx plus an error page: a silent corrupt file, worse than a visible error. Buffering first keeps a clean redirect possible.
|
|
47
|
+
|
|
48
|
+
### The vendored PhpSpreadsheet Xlsx writer takes a filename, not a stream
|
|
49
|
+
`library/PhpOffice/PhpSpreadsheet/Writer/Xlsx.php::save($pFilename)` (:173) calls `strtolower($pFilename)` (:181) — a stream resource fails with "strtolower() expects parameter 1 to be string, resource given". Stream support landed in a later release. Use `tempnam()` + `file_get_contents()` + `unlink()`; the temp file lives inside one request, so it cannot recreate the load-balancer bug.
|
|
50
|
+
|
|
51
|
+
### Lint and run togaview with PHP 7.2
|
|
52
|
+
Use `xampp_7_2_33`'s `php.exe`. Linting under PHP 8 passes, but a CLI run dies on a vendored PhpSpreadsheet deprecation (`Worksheet\Iterator::current()` return type), which is fatal under `App_Error` — a misleading failure.
|
|
53
|
+
|
|
54
|
+
### Don't assume one userType across the forks
|
|
55
|
+
`common/togaview/msp_dashboard.php:16` requires CUSTOMER or MSP; `common/towfoundation/msp_dashboard.php:234` and `common/newcenturyholdingsllc/msp_dashboard.php:235` require **ADMIN**. All three also require `togadeskClientId`. A shared gate must be the **union**, or every towfoundation/newcentury admin loses the button they can see.
|
|
56
|
+
|
|
57
|
+
### Anti-pattern this replaced (was live in production)
|
|
58
|
+
The old `mvc/msp_dashboard/export/get.php` gated only on `isset($_GET['fileId'])` — no login check — then `readfile()`d **and** `unlink()`d `$cacheDir . '/' . $fileName`, validated only by `substr($fileName, -5) === '.xlsx'` and `file_exists()`. `$_GET` passes through `App_String::xssProtect()` (= `htmlspecialchars()`), which touches neither `.` nor `/`, so `../` traversal worked: unauthenticated arbitrary read **and** arbitrary delete. Filenames were also `Tickets_Export_<unix timestamp>.xlsx` in one shared folder — a guessable cross-tenant read with no traversal at all. **An extension check plus `file_exists()` is not a traversal defense, and `htmlspecialchars()` is not path sanitization.** Same family as the TRUE-78835 / TRUE-80671 togaview IDORs.
|
|
59
|
+
|
|
60
|
+
## Change history
|
|
61
|
+
- 2026-09-18 — Export collapsed to one request (was broken by the shared ALB), extracted to `App_Export_TicketExport`, identity-gated, and the unauthenticated traversal/delete endpoint removed (TRUE-81955) (mhammontree)
|
|
@@ -5,7 +5,7 @@ project: Library
|
|
|
5
5
|
client: shared
|
|
6
6
|
type: standard
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-09-
|
|
8
|
+
updated: 2026-09-18
|
|
9
9
|
owners: [jcardinal, rgirish, mhammontree, ajean]
|
|
10
10
|
files: []
|
|
11
11
|
related:
|
|
@@ -553,6 +553,18 @@ Note: the 2.0 metadata-driven API features (Record Scripts, API Payload Intercep
|
|
|
553
553
|
- **Remove all `debugVar()` calls before merging.**
|
|
554
554
|
- Don't extend `DOA_`-prefixed code; plan its removal when you touch the area and dependencies allow.
|
|
555
555
|
|
|
556
|
+
## Binary downloads: build the whole file, then send headers
|
|
557
|
+
|
|
558
|
+
Never stream a generated file straight to the browser. `App_Error::handleError` promotes every
|
|
559
|
+
PHP warning to `handleException`, which `exit`s (`library/app/error.php` ~407 and ~671). If bytes
|
|
560
|
+
are already on the wire, the user gets HTTP 200 with a truncated file plus an error page — a
|
|
561
|
+
silent corrupt download, worse than a visible failure. Build it fully in memory (or a per-request
|
|
562
|
+
`tempnam()`), then send headers, then echo. A failure before the headers can still redirect.
|
|
563
|
+
|
|
564
|
+
Never persist a generated file on local disk to hand to a *second* request. Our EB apps run on
|
|
565
|
+
several instances behind a shared load balancer, so per-instance paths like `/var/www/cache`
|
|
566
|
+
are not shared and the follow-up request intermittently 404s. One request, start to finish.
|
|
567
|
+
|
|
556
568
|
## Documentation
|
|
557
569
|
|
|
558
570
|
- Keep code well-documented with DocBlocks and usage examples.
|
package/knowledge/INDEX.md
CHANGED
|
@@ -9,7 +9,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
9
9
|
- **dbchanges** (Database Changes) _(framework core)_ — 1 doc(s) → [1.0/apps/dbchanges/INDEX.md](1.0/apps/dbchanges/INDEX.md)
|
|
10
10
|
- **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
|
|
11
11
|
- **togadesk** (TOGa Desk) — 13 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
12
|
-
- **togaview** (TOGa View) —
|
|
12
|
+
- **togaview** (TOGa View) — 8 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
|
|
13
13
|
- **webhook** (Webhook) — 1 doc(s) → [1.0/apps/webhook/INDEX.md](1.0/apps/webhook/INDEX.md)
|
|
14
14
|
- **walmarttechservices** (Walmart Tech Services) — 1 doc(s) → [1.0/apps/walmarttechservices/INDEX.md](1.0/apps/walmarttechservices/INDEX.md)
|
|
15
15
|
- **test** (Test) — 16 doc(s) → [1.0/apps/test/INDEX.md](1.0/apps/test/INDEX.md)
|
package/package.json
CHANGED