toga-ai 1.0.67 → 1.0.68
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/togadesk/INDEX.md +7 -0
- package/knowledge/1.0/apps/togadesk/features/smb-contract-editing.md +90 -0
- package/knowledge/1.0/apps/togadesk/features/ticket-lifecycle.md +102 -0
- package/knowledge/1.0/apps/togadesk/workflows/standalone-test-scripts.md +49 -0
- package/knowledge/1.0/apps/togaview/INDEX.md +7 -0
- package/knowledge/1.0/apps/togaview/features/login-flows.md +65 -0
- package/knowledge/1.0/apps/togaview/features/msp-dashboard.md +84 -0
- package/knowledge/1.0/apps/togaview/features/ticket-detail-page.md +76 -0
- package/knowledge/INDEX.md +2 -0
- package/knowledge/registry.json +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# togadesk (TogaDesk) — 1.0 knowledge
|
|
2
|
+
|
|
3
|
+
| Doc | Summary | Files |
|
|
4
|
+
|-----|---------|-------|
|
|
5
|
+
| [SMB Contract Editing & the clientMspId Corruption Trap](features/smb-contract-editing.md) | The SMB contracts page (`/desk/?route=toga/smbcontracts&togaClientId=<id>`) edits `TOGA_*.SMBContracts` rows via a modal. | desk/template/modals/toga/smbcontracts/smbContract.php, desk/includes/controllers/modals/toga/smbcontracts/smbContract.php, desk/includes/controllers/actions/toga/smbcontracts/smbContract.php, desk/includes/controllers/actions/toga/smbcontracts/edit.php |
|
|
6
|
+
| [Ticket Lifecycle (class.ticket.php)](features/ticket-lifecycle.md) | All TogaDesk ticket creation and reply handling funnels through `Ticket` in `desk/includes/classes/class.ticket.php`. | desk/includes/classes/class.ticket.php, desk/includes/controllers/actions.php, desk/api/resources/tickets.php, crons/tickets.php, desk/includes/controllers/actions/tickets/merge.php |
|
|
7
|
+
| [Standalone PHP Test Script Bootstrap (TogaDesk)](workflows/standalone-test-scripts.md) | How to write a standalone CLI PHP script that bootstraps the TogaDesk framework for read-only testing of desk classes (e.g. | |
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: SMB Contract Editing & the clientMspId Corruption Trap
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: togadesk
|
|
5
|
+
project: TogaDesk
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-12
|
|
10
|
+
owners: ["mhammontree"]
|
|
11
|
+
files:
|
|
12
|
+
- desk/template/modals/toga/smbcontracts/smbContract.php
|
|
13
|
+
- desk/includes/controllers/modals/toga/smbcontracts/smbContract.php
|
|
14
|
+
- desk/includes/controllers/actions/toga/smbcontracts/smbContract.php
|
|
15
|
+
- desk/includes/controllers/actions/toga/smbcontracts/edit.php
|
|
16
|
+
related:
|
|
17
|
+
- 1.0/apps/togadesk/features/ticket-lifecycle.md
|
|
18
|
+
- 1.0/apps/togaview/features/msp-dashboard.md
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Summary
|
|
22
|
+
The SMB contracts page (`/desk/?route=toga/smbcontracts&togaClientId=<id>`) edits
|
|
23
|
+
`TOGA_*.SMBContracts` rows via a modal. Because multiple TOGA clients can share one TOGA_*
|
|
24
|
+
database, this page historically corrupted `clientMspId` on save — the root cause of MSP
|
|
25
|
+
portal users seeing "You have no assigned clients" in TogaView. Fixed June 2026.
|
|
26
|
+
|
|
27
|
+
## Key files / entry points
|
|
28
|
+
- Modal template: `desk/template/modals/toga/smbcontracts/smbContract.php`
|
|
29
|
+
- Modal controller: `desk/includes/controllers/modals/toga/smbcontracts/smbContract.php`
|
|
30
|
+
- Submit action: `desk/includes/controllers/actions/toga/smbcontracts/smbContract.php`
|
|
31
|
+
- `edit.php` in the same actions folder is a separate, partly-legacy path that does NOT touch
|
|
32
|
+
`clientMspId`.
|
|
33
|
+
- The same controller serves the CSP variant (`csp=true` → `type='reseller'`).
|
|
34
|
+
|
|
35
|
+
## How it works
|
|
36
|
+
The bug pattern (now guarded against):
|
|
37
|
+
1. Multiple TOGA clients share one TOGA_* database (e.g. TOGA clients 9 "True" and 20
|
|
38
|
+
"craftex" both use `TOGA_True`), so the contracts page under client A lists client B's
|
|
39
|
+
contracts too.
|
|
40
|
+
2. The modal's MSP dropdown was scoped to client A's `ClientMsp` rows; the contract's real MSP
|
|
41
|
+
wasn't an option; no option got `selected`; **the browser silently auto-selects the first
|
|
42
|
+
option**; saving wrote the wrong `clientMspId`.
|
|
43
|
+
3. TogaView msp_dashboard's `SMBContracts WHERE clientMspId = <session clientMspId>` then
|
|
44
|
+
matched nothing for the real MSP's users.
|
|
45
|
+
|
|
46
|
+
Fixes in place (June 2026):
|
|
47
|
+
- The modal appends the contract's current MSP to the option list when missing.
|
|
48
|
+
- The action only assigns a posted `clientMspId` that is `> 0` AND exists in `ClientMsp`
|
|
49
|
+
(`App_Model_TogaDesk_ClientMsp::exists()`); otherwise it keeps the stored value.
|
|
50
|
+
- `SMBContracts.clientMspId` is nullable — NEW + "None" saves NULL (fine).
|
|
51
|
+
|
|
52
|
+
## Data model
|
|
53
|
+
```
|
|
54
|
+
TOGaDeskSupport.clients (togadesk client)
|
|
55
|
+
▲ clientId
|
|
56
|
+
TOGaDeskSupport.ClientMsp (id, clientId, type ENUM 'msp'|'reseller',
|
|
57
|
+
▲ ticketDepartmentId, companyName)
|
|
58
|
+
│ clientMspId
|
|
59
|
+
TOGA.Clients (toga client registry; togadeskClientId, databaseName)
|
|
60
|
+
TOGA_<databaseName>.SMBContracts (clientMspId → ClientMsp.id, mainContactId,
|
|
61
|
+
serviceSeats, dateContractEnd)
|
|
62
|
+
```
|
|
63
|
+
- A togadesk client can own **multiple ClientMsp rows** (clients 18 and 26 do — mixes of msp
|
|
64
|
+
and reseller). Code must not assume exactly one.
|
|
65
|
+
- **Multiple TOGA clients can share one TOGA_* database** — any page listing `SMBContracts`
|
|
66
|
+
for a `togaClientId` actually lists every tenant's contracts in that database.
|
|
67
|
+
|
|
68
|
+
Integrity audit query (run per shared TOGA_* db, eyeball contractNumber vs companyName
|
|
69
|
+
mismatches):
|
|
70
|
+
```sql
|
|
71
|
+
SELECT sc.id, sc.contractNumber, sc.clientMspId, cm.companyName, cm.clientId
|
|
72
|
+
FROM SMBContracts sc
|
|
73
|
+
LEFT JOIN TOGaDeskSupport.ClientMsp cm ON cm.id = sc.clientMspId
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Client variations
|
|
77
|
+
None in code — uniform. Craftex reference values: togadesk clientid 169, ClientMsp 42, TOGA
|
|
78
|
+
client 20 (`TOGA_True`), SMBContract 230 (Customer 932297).
|
|
79
|
+
|
|
80
|
+
## Gotchas / known issues
|
|
81
|
+
- `dateContractEnd` in the past is display-only — it does not hide tickets in the MSP portal.
|
|
82
|
+
- `ClientMsp.ticketDepartmentId` holds only ONE department even when the client has several —
|
|
83
|
+
never build department logic on it (see TogaView msp-dashboard doc).
|
|
84
|
+
|
|
85
|
+
## Change history
|
|
86
|
+
- 2026-06-12 — documented; modal + action guards shipped to togadesk dev branch (mhammontree)
|
|
87
|
+
|
|
88
|
+
## Related docs
|
|
89
|
+
- [Ticket lifecycle](ticket-lifecycle.md)
|
|
90
|
+
- [TogaView MSP dashboard](../../togaview/features/msp-dashboard.md)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ticket Lifecycle (class.ticket.php)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: togadesk
|
|
5
|
+
project: TogaDesk
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-12
|
|
10
|
+
owners: ["mhammontree"]
|
|
11
|
+
files:
|
|
12
|
+
- desk/includes/classes/class.ticket.php
|
|
13
|
+
- desk/includes/controllers/actions.php
|
|
14
|
+
- desk/api/resources/tickets.php
|
|
15
|
+
- crons/tickets.php
|
|
16
|
+
- desk/includes/controllers/actions/tickets/merge.php
|
|
17
|
+
related:
|
|
18
|
+
- 1.0/apps/togadesk/features/smb-contract-editing.md
|
|
19
|
+
- 1.0/apps/togaview/features/msp-dashboard.md
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Summary
|
|
23
|
+
All TogaDesk ticket creation and reply handling funnels through `Ticket` in
|
|
24
|
+
`desk/includes/classes/class.ticket.php`. This doc covers the creation paths, the
|
|
25
|
+
`deriveCustomerId()` resolution added June 2026, and the `addReply()` status-transition
|
|
26
|
+
switch — including its known gaps.
|
|
27
|
+
|
|
28
|
+
⚠ There are TWO `class.ticket.php` files. The live one is
|
|
29
|
+
`desk/includes/classes/class.ticket.php`. The root `includes/classes/class.ticket.php` is an
|
|
30
|
+
ancient variant (random 6-digit ticket numbers, schema that no longer matches) — never extend it.
|
|
31
|
+
|
|
32
|
+
## Key files / entry points
|
|
33
|
+
Creation paths — all funnel through `Ticket::add($data)`:
|
|
34
|
+
- Staff UI: `desk/includes/controllers/actions.php` → `Ticket::add($_POST)`
|
|
35
|
+
- API: `desk/api/resources/tickets.php`
|
|
36
|
+
- Email intake: `crons/tickets.php` (IMAP poll per `tickets_departments.email`, called via
|
|
37
|
+
`tickets_<ENV>.php` wrappers; loads desk classes via `appClassAutoload`) →
|
|
38
|
+
`Ticket::emailToTicket()` → parses "Ticket #NNNNNNN" from subject → matched ticket ⇒
|
|
39
|
+
`addReply()`, else ⇒ `add()`.
|
|
40
|
+
- Ticket merge (`actions/tickets/merge.php`) inserts a master ticket directly — it copies
|
|
41
|
+
fields from the child, including `customerid` (since June 2026).
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
### `Ticket::deriveCustomerId(int, string): ?int` (added June 2026)
|
|
46
|
+
Called from `add()` when no `customerId` was supplied — fixes MSP-portal-invisible tickets
|
|
47
|
+
(togaview MSP pages filter on `tickets.customerid`, which only togaview-side creation set;
|
|
48
|
+
staff/API/email tickets left it NULL).
|
|
49
|
+
|
|
50
|
+
Logic: collect ALL ClientMsp ids for the clientid (non-MSP ⇒ null) →
|
|
51
|
+
`TOGA.Clients.databaseName` → try email match against contracted end users
|
|
52
|
+
(`SMBContracts→SMBContractItems→ServiceRequests→Contacts`) → fallback to the MSP's **sole
|
|
53
|
+
distinct** customer via `SMBContracts.mainContactId`; multiple customers + no email match ⇒
|
|
54
|
+
null (never guess). Exceptions are logged and swallowed — the email cron must never be blocked.
|
|
55
|
+
|
|
56
|
+
Read-only test harness pattern: see the standalone-test-scripts workflow doc.
|
|
57
|
+
|
|
58
|
+
### `Ticket::addReply($data)` status transitions
|
|
59
|
+
When the caller supplies no status, a `switch ($ticket['status'])` decides. Pattern: staff
|
|
60
|
+
reply preserves "waiting" states; client reply moves to `Open`; Closed/Review → `Reopened`.
|
|
61
|
+
|
|
62
|
+
**The status write is gated on `isset($data['status'])`** — a status missing from the switch
|
|
63
|
+
means the reply lands but the ticket status silently never changes (no history entry, no queue
|
|
64
|
+
re-entry). That was the On Hold SLA bug; `case 'On Hold'` (admin ⇒ stays On Hold, client ⇒
|
|
65
|
+
Open) was added June 2026.
|
|
66
|
+
|
|
67
|
+
- Special case: an admin replying to their OWN ticket is treated as a user reply.
|
|
68
|
+
- Auto-assign on first staff reply requires the role perm `allowAutoAssign` via
|
|
69
|
+
profiles/profile_departments.
|
|
70
|
+
- Side integrations on reply: Syncro status push (`referenceId='SYNCRO'`), Talos AI summaries
|
|
71
|
+
(TogaIQ), merged-ticket fan-out (replies propagate to child tickets).
|
|
72
|
+
|
|
73
|
+
## Data model
|
|
74
|
+
- `TOGaDeskSupport.tickets` — `clientid`, `customerid` (TOGA `Customers.id`, drives MSP portal
|
|
75
|
+
visibility), `departmentid`, `email`, `status`. **No closed-date column.**
|
|
76
|
+
- `tickets_replies` (`newStatus`), `tickets_history`.
|
|
77
|
+
|
|
78
|
+
## Client variations
|
|
79
|
+
None — uniform. Pre-fix tickets for a given MSP client may need a one-time `customerid`
|
|
80
|
+
backfill (e.g. craftex:
|
|
81
|
+
`UPDATE TOGaDeskSupport.tickets SET customerid = 932297 WHERE clientid = 169 AND customerid IS NULL;`).
|
|
82
|
+
|
|
83
|
+
## Gotchas / known issues
|
|
84
|
+
- `'Resolved'` is STILL missing from the `addReply()` switch — a client reply to a Resolved
|
|
85
|
+
ticket changes nothing. Known gap, awaiting product decision.
|
|
86
|
+
- **There is NO 72-hour reopen window in code**, despite SME belief. `Closed` → `Reopened` is
|
|
87
|
+
unconditional, email intake has no age check, and the `tickets` table has no closed-date
|
|
88
|
+
column (close time would have to come from `tickets_replies.newStatus='Closed'` or
|
|
89
|
+
`tickets_history`). Implementing it is a product decision: what happens to a lapsed reply
|
|
90
|
+
(attach-but-closed / new ticket / bounce)?
|
|
91
|
+
- Legacy `togadesk/includes/class.ticket.php` has active email-reopen logic; the live
|
|
92
|
+
`desk/includes` version has it commented out — email replies never set status to "Reopened"
|
|
93
|
+
via `emailToTicket()`.
|
|
94
|
+
|
|
95
|
+
## Change history
|
|
96
|
+
- 2026-06-12 — documented from craftex MSP portal debugging session (mhammontree)
|
|
97
|
+
- 2026-06 — added `deriveCustomerId()`; added `On Hold` case to addReply() switch; merge
|
|
98
|
+
copies `customerid` (mhammontree)
|
|
99
|
+
|
|
100
|
+
## Related docs
|
|
101
|
+
- [SMB contract editing](smb-contract-editing.md)
|
|
102
|
+
- [TogaView MSP dashboard](../../togaview/features/msp-dashboard.md)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Standalone PHP Test Script Bootstrap (TogaDesk)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: togadesk
|
|
5
|
+
project: TogaDesk
|
|
6
|
+
client: shared
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-12
|
|
10
|
+
owners: ["mhammontree"]
|
|
11
|
+
files: []
|
|
12
|
+
related:
|
|
13
|
+
- 1.0/apps/togadesk/features/ticket-lifecycle.md
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Summary
|
|
17
|
+
How to write a standalone CLI PHP script that bootstraps the TogaDesk framework for
|
|
18
|
+
read-only testing of desk classes (e.g. `Ticket::deriveCustomerId()`), without running the
|
|
19
|
+
web stack. Example harness: `C:\WWW\test\@Mark\TOGaDeskSupport\test_derive_customer_id.php`
|
|
20
|
+
(local path — adapt per machine).
|
|
21
|
+
|
|
22
|
+
## Steps
|
|
23
|
+
1. `chdir()` to the desk app root (`<togadesk>/desk`).
|
|
24
|
+
2. Fake `$_SERVER['SCRIPT_FILENAME']` so the framework resolves paths.
|
|
25
|
+
3. `require` the 1.0 framework bootstrap `<library>/_.php`, then desk `functions.php` +
|
|
26
|
+
`config.php`.
|
|
27
|
+
4. Register `vendorClassAutoload` / `appClassAutoload`, plus composer autoload.
|
|
28
|
+
5. Set the `ENVIRONMENT` env var BEFORE bootstrap (or default it in the script). If unset it
|
|
29
|
+
defaults to `worker` and the script dies looking for `config.worker.ini`.
|
|
30
|
+
6. Wrap `App_Framework_TOGaDesk::initialize()` in `ob_start()` … `ob_end_clean()` — the
|
|
31
|
+
framework echoes a Sentry warning during init, which marks headers as sent and makes the
|
|
32
|
+
session `ini_set()` throw.
|
|
33
|
+
7. Restore error/exception handlers after init to see real errors instead of the HTML error
|
|
34
|
+
page.
|
|
35
|
+
8. Skip medoo unless needed (PHP 8.2 deprecation noise); `App_Database` is enough.
|
|
36
|
+
|
|
37
|
+
## Systems involved
|
|
38
|
+
- `library` (1.0 framework core), togadesk desk classes, legacy MySQL cluster.
|
|
39
|
+
- DB link names (`db_togadesk`, `db_toga`, …) resolve to `[database_X]` sections in
|
|
40
|
+
`config.<ENVIRONMENT>.ini`.
|
|
41
|
+
|
|
42
|
+
## Edge cases & escalation
|
|
43
|
+
- "Sentry is not installed" output followed by an `ini_set` session error = you missed the
|
|
44
|
+
output-buffer wrap (step 6).
|
|
45
|
+
- Scripts should stay read-only against shared/mirrored data unless explicitly doing a data
|
|
46
|
+
fix.
|
|
47
|
+
|
|
48
|
+
## Change history
|
|
49
|
+
- 2026-06-12 — captured from deriveCustomerId test harness work (mhammontree)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# togaview (TogaView) — 1.0 knowledge
|
|
2
|
+
|
|
3
|
+
| Doc | Summary | Files |
|
|
4
|
+
|-----|---------|-------|
|
|
5
|
+
| [TogaView Login Flows & Session Variables](features/login-flows.md) | `mvc/login/post.php` tries login flows in order; the first match wins. | mvc/login/post.php, _/app/framework.php |
|
|
6
|
+
| [MSP Dashboard & Ticket Visibility Rules](features/msp-dashboard.md) | Why tickets "disappear" in the TogaView client portal: different pages scope tickets **differently**, and the MSP pages depend on `tickets.customerid` and `SMBC | common/togaview/msp_dashboard.php, mvc/msp_client_dashboard, mvc/enterprise_dashboard, mvc/support/support.php |
|
|
7
|
+
| [Ticket Detail Page Security (common/togaview/ticket.php)](features/ticket-detail-page.md) | `common/togaview/ticket.php` is the ticket detail page for nearly ALL hosts — only towfoundation/newcenturyholdingsllc have their own variants; every other clie | common/togaview/ticket.php |
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: TogaView Login Flows & Session Variables
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: togaview
|
|
5
|
+
project: TogaView
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-12
|
|
10
|
+
owners: ["mhammontree"]
|
|
11
|
+
files:
|
|
12
|
+
- mvc/login/post.php
|
|
13
|
+
- _/app/framework.php
|
|
14
|
+
related:
|
|
15
|
+
- 1.0/apps/togaview/features/msp-dashboard.md
|
|
16
|
+
- 1.0/apps/togaview/features/ticket-detail-page.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
`mvc/login/post.php` tries login flows in order; the first match wins. Each flow sets a
|
|
21
|
+
DIFFERENT session shape — any code reading session vars must know which flows set what.
|
|
22
|
+
Several production bugs (ownership checks rejecting valid users) came from assuming a
|
|
23
|
+
uniform session.
|
|
24
|
+
|
|
25
|
+
## How it works
|
|
26
|
+
|
|
27
|
+
| Flow | userType | Key session vars |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| Retail (walmart/walmartplus) | `RETAIL` | `togadeskClientId=11` (hardcoded), `emailAddress`, `supportSkuExists=true` |
|
|
30
|
+
| MSP contact (ClientMspContacts join) | `MSP` | `clientMspId`, `togadeskClientId` (=ClientMsp.clientId), `clientDepartmentId` (=ClientMsp.ticketDepartmentId), `emailAddress`, `customerId` → redirects `/msp_dashboard` |
|
|
31
|
+
| SMB admin (SMBContracts.mainContactId) | `CUSTOMER` | same family as MSP, `togadeskClientId` from ClientMsp.clientId |
|
|
32
|
+
| End user (SMBContractItems→ServiceRequests→Contacts) | `ENDUSER` | `emailAddress`, `clientMspId`, `togadeskClientId`, `clientDepartmentId`, `supportSkuExists` → `/enterprise_dashboard` |
|
|
33
|
+
| staplesprotection (inline, AIG-backed) | `ENDUSER` | ⚠ sets `$_SESSION['email']` **not** `emailAddress`, and **no `togadeskClientId`** |
|
|
34
|
+
| rumcsi / newcenturyholdingsllc (SAML in framework.php) | `ENDUSER`/`ADMIN` | `togadeskClientId` = their togadesk clientid; newcentury auto-creates `people` rows from SAML |
|
|
35
|
+
|
|
36
|
+
- `$_SESSION['supportSkuExists']` gates the entire ticket block on enterprise_dashboard. Set
|
|
37
|
+
by checking the contract's service SKU against a **hardcoded whitelist** in login/post.php
|
|
38
|
+
(`smbContractSkus` = [190001, 190002, 190003, 210001, 210002, 210003, 24477039, 160202,
|
|
39
|
+
160203]). New support SKUs must be added there.
|
|
40
|
+
- MSP logins join through `TOGaDeskSupport.ClientMspContacts` →
|
|
41
|
+
`TOGaDeskSupport.contacts` — a malformed contact email breaks the match (e.g. contact 76
|
|
42
|
+
"Wendy Williams" had `Wendy@craftex` missing `.com`).
|
|
43
|
+
|
|
44
|
+
## Data model
|
|
45
|
+
`TOGaDeskSupport.ClientMsp` / `ClientMspContacts` / `contacts`; TOGA-side
|
|
46
|
+
`SMBContracts` / `SMBContractItems` / `ServiceRequests` / `Contacts` for SMB admin and
|
|
47
|
+
end-user flows.
|
|
48
|
+
|
|
49
|
+
## Client variations
|
|
50
|
+
- walmart/walmartplus: hardcoded `togadeskClientId=11`.
|
|
51
|
+
- staplesprotection: inline branch, end-user style, divergent session keys (see table).
|
|
52
|
+
- rumcsi / newcenturyholdingsllc: SAML handled in `_/app/framework.php`, not login/post.php.
|
|
53
|
+
|
|
54
|
+
## Gotchas / known issues
|
|
55
|
+
- Code consuming the session must read `emailAddress` with an `email` fallback (and tolerate
|
|
56
|
+
missing `togadeskClientId`) or it rejects all staplesprotection users — this broke the
|
|
57
|
+
ticket.php ownership check until the fallback was added (June 2026).
|
|
58
|
+
- The flows are order-dependent: a contact matching an earlier flow never reaches later ones.
|
|
59
|
+
|
|
60
|
+
## Change history
|
|
61
|
+
- 2026-06-12 — documented from craftex MSP portal debugging session (mhammontree)
|
|
62
|
+
|
|
63
|
+
## Related docs
|
|
64
|
+
- [MSP dashboard & ticket visibility](msp-dashboard.md)
|
|
65
|
+
- [Ticket detail page](ticket-detail-page.md)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: MSP Dashboard & Ticket Visibility Rules
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: togaview
|
|
5
|
+
project: TogaView
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-12
|
|
10
|
+
owners: ["mhammontree"]
|
|
11
|
+
files:
|
|
12
|
+
- common/togaview/msp_dashboard.php
|
|
13
|
+
- mvc/msp_client_dashboard
|
|
14
|
+
- mvc/enterprise_dashboard
|
|
15
|
+
- mvc/support/support.php
|
|
16
|
+
related:
|
|
17
|
+
- 1.0/apps/togaview/features/login-flows.md
|
|
18
|
+
- 1.0/apps/togadesk/features/ticket-lifecycle.md
|
|
19
|
+
- 1.0/apps/togadesk/features/smb-contract-editing.md
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Summary
|
|
23
|
+
Why tickets "disappear" in the TogaView client portal: different pages scope tickets
|
|
24
|
+
**differently**, and the MSP pages depend on `tickets.customerid` and `SMBContracts`
|
|
25
|
+
integrity. Covers the per-page filters and the June 2026 multi-department stats fix.
|
|
26
|
+
|
|
27
|
+
## How it works
|
|
28
|
+
|
|
29
|
+
### Ticket filters per page
|
|
30
|
+
| Page | Ticket filter |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `common/togaview/msp_dashboard.php` (MSP/CUSTOMER) | per-customer counts: `clientid` + `customerid` + `departmentid IN (client's departments)`; customer list driven by `SMBContracts WHERE clientMspId = session clientMspId` — **zero contracts ⇒ "You have no assigned clients" and no ticket query at all** |
|
|
33
|
+
| `mvc/msp_client_dashboard` (ticket list) | `tickets.customerid IN (<customers>) AND tickets.clientid = <session togadeskClientId>` — **no department filter** |
|
|
34
|
+
| `mvc/enterprise_dashboard` (ENDUSER) | `tickets.email = session emailAddress AND tickets.departmentid = session clientDepartmentId AND status <> 'Closed'`, gated by `supportSkuExists` |
|
|
35
|
+
| `mvc/support/support.php` | `tickets.email = <email>` only — no client filter |
|
|
36
|
+
|
|
37
|
+
**The #1 trap: `tickets.customerid`.** The MSP portal can only show tickets whose
|
|
38
|
+
`customerid` matches a TOGA `Customers.id` under the MSP's SMBContracts. Only togaview-side
|
|
39
|
+
ticket creation set it; tickets created in togadesk (staff UI, API, email intake)
|
|
40
|
+
historically left it NULL ⇒ invisible to the MSP portal. Fixed June 2026 by
|
|
41
|
+
`Ticket::deriveCustomerId()` (see togadesk ticket-lifecycle doc). Pre-fix tickets need a
|
|
42
|
+
one-time backfill per client.
|
|
43
|
+
|
|
44
|
+
`$disableTickets` on msp_client_dashboard is seat-arithmetic only
|
|
45
|
+
(`serviceSeats < activeUsers`) — it hides Create-Ticket buttons, never the list.
|
|
46
|
+
|
|
47
|
+
### Multi-department stats (fixed June 2026)
|
|
48
|
+
`common/togaview/msp_dashboard.php` used to filter every stat on the single
|
|
49
|
+
`ClientMsp.ticketDepartmentId`. It now derives **all** of the client's departments once
|
|
50
|
+
(`tickets_departments WHERE clientId = <togadeskClientId>`) into `$departmentIdList` and uses
|
|
51
|
+
`departmentid IN (...)` everywhere (status counts, priority counts, per-day chart, KB
|
|
52
|
+
category resolution).
|
|
53
|
+
- The department fetch is **MSP-level on purpose** — don't move it inside the customer loop.
|
|
54
|
+
- Empty department list → `'0'` sentinel (valid SQL, matches nothing) + `error_log`.
|
|
55
|
+
- A ticket with `departmentid = 0` is excluded from stats by design.
|
|
56
|
+
|
|
57
|
+
## Data model
|
|
58
|
+
- `TOGaDeskSupport.tickets` (`clientid`, `customerid`, `departmentid`, `email`, `status`)
|
|
59
|
+
- `TOGaDeskSupport.tickets_departments.clientId` maps support departments to a togadesk
|
|
60
|
+
client — a client can have several (craftex: 317, 318, 319), but
|
|
61
|
+
`ClientMsp.ticketDepartmentId` holds only ONE. Never build department logic on it.
|
|
62
|
+
- An MSP's "customers" are **TOGA-side `Customers` rows**, NOT togadesk clients. All of an
|
|
63
|
+
MSP's tickets carry the MSP's own `tickets.clientid`.
|
|
64
|
+
|
|
65
|
+
## Client variations
|
|
66
|
+
- The single-department pattern still exists in the `newcenturyholdingsllc` and
|
|
67
|
+
`towfoundation` dashboard variants (not yet fixed there) and in enterprise_dashboard's
|
|
68
|
+
`clientDepartmentId` filter for end users.
|
|
69
|
+
- Only towfoundation / newcenturyholdingsllc have their own dashboard variants; everyone else
|
|
70
|
+
runs the generic `common/togaview/` pages.
|
|
71
|
+
|
|
72
|
+
## Gotchas / known issues
|
|
73
|
+
- "You have no assigned clients" usually means the MSP's `SMBContracts.clientMspId` was
|
|
74
|
+
corrupted by the togadesk contract modal (see smb-contract-editing doc) — check data before
|
|
75
|
+
debugging code.
|
|
76
|
+
- An expired `SMBContracts.dateContractEnd` is display-only; it does not hide tickets.
|
|
77
|
+
|
|
78
|
+
## Change history
|
|
79
|
+
- 2026-06-12 — documented; multi-department stats fix shipped to dev (mhammontree)
|
|
80
|
+
|
|
81
|
+
## Related docs
|
|
82
|
+
- [Login flows](login-flows.md)
|
|
83
|
+
- [TogaDesk ticket lifecycle](../../togadesk/features/ticket-lifecycle.md)
|
|
84
|
+
- [SMB contract editing](../../togadesk/features/smb-contract-editing.md)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ticket Detail Page Security (common/togaview/ticket.php)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: togaview
|
|
5
|
+
project: TogaView
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-12
|
|
10
|
+
owners: ["mhammontree"]
|
|
11
|
+
files:
|
|
12
|
+
- common/togaview/ticket.php
|
|
13
|
+
related:
|
|
14
|
+
- 1.0/apps/togaview/features/login-flows.md
|
|
15
|
+
- 1.0/apps/togaview/features/msp-dashboard.md
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Summary
|
|
19
|
+
`common/togaview/ticket.php` is the ticket detail page for nearly ALL hosts — only
|
|
20
|
+
towfoundation/newcenturyholdingsllc have their own variants; every other client's style falls
|
|
21
|
+
back to it. Hardened June 2026 against IDOR (ticket view + file attachment download) and a
|
|
22
|
+
"Deactivated Seat" false positive that blocked MSP admins.
|
|
23
|
+
|
|
24
|
+
## How it works
|
|
25
|
+
|
|
26
|
+
### Ownership check (IDOR fix)
|
|
27
|
+
Applies to BOTH the ticket view AND the `fileId` attachment download (which runs first and
|
|
28
|
+
`exit`s). Access allowed iff:
|
|
29
|
+
- `tickets.clientid == (int) $_SESSION['togadeskClientId']`, OR
|
|
30
|
+
- ticket email == session email (case-insensitive; reads `emailAddress` with `email` fallback
|
|
31
|
+
for staplesprotection sessions).
|
|
32
|
+
|
|
33
|
+
Attachment ownership resolves `files → tickets_replies → tickets`. Unauthorized or missing ⇒
|
|
34
|
+
"Ticket Not Found" + exit.
|
|
35
|
+
|
|
36
|
+
**Do NOT "tighten" the email arm to clientid<=0-only tickets** (a CodeRabbit suggestion,
|
|
37
|
+
reviewed and declined): staplesprotection sessions have no `togadeskClientId`, and
|
|
38
|
+
support.php lists tickets by email with no client filter — the requester is always entitled
|
|
39
|
+
to their own ticket.
|
|
40
|
+
|
|
41
|
+
### Deactivated-seat gate
|
|
42
|
+
Only `USERTYPE_ENDUSER` gets the "Deactivated Seat" block. It fires when the ticket's
|
|
43
|
+
requester email isn't a provisioned SMB seat user (the SMBContractItems JOIN returns empty) —
|
|
44
|
+
which is NORMAL for email-intake tickets, so it must never apply to MSP/CUSTOMER viewers.
|
|
45
|
+
|
|
46
|
+
### Input handling
|
|
47
|
+
`$_GET['ticketId']` is int-cast once at the top — it is interpolated into several queries in
|
|
48
|
+
this file (was a live SQL injection before June 2026).
|
|
49
|
+
|
|
50
|
+
## Data model
|
|
51
|
+
`TOGaDeskSupport.tickets`, `tickets_replies`, `files`; TOGA-side `SMBContractItems` →
|
|
52
|
+
`ServiceRequests` → `Contacts` for the seat check.
|
|
53
|
+
|
|
54
|
+
## Client variations
|
|
55
|
+
- All style variants fall back to this file — no style has its own ticket.php except
|
|
56
|
+
towfoundation/newcenturyholdingsllc.
|
|
57
|
+
- staplesprotection: session uses `email`/`clientId` keys (not
|
|
58
|
+
`emailAddress`/`togadeskClientId`) — the ownership check's email fallback exists for them.
|
|
59
|
+
|
|
60
|
+
## Gotchas / known issues
|
|
61
|
+
- Pre-existing warts (untouched): unescaped `$emailAddress` in the walmart branch query;
|
|
62
|
+
unguarded `explode(' ', name)` in rumcsi/towfoundation branches.
|
|
63
|
+
- Pending as of 2026-06-12: MSP/CUSTOMER bypass for the ownership check (MSP session's
|
|
64
|
+
`togadeskClientId` is the MSP's own client id — matches their customers' tickets since those
|
|
65
|
+
carry the MSP's clientid, but verify when an MSP views a ticket whose clientid differs);
|
|
66
|
+
togaview changes were uncommitted at session end.
|
|
67
|
+
|
|
68
|
+
## Change history
|
|
69
|
+
- 2026-06-12 — file-attachment IDOR fix (ownership check now covers fileId branch);
|
|
70
|
+
email-key fallback for staplesprotection (mhammontree)
|
|
71
|
+
- 2026-06 — ownership check added (ticket view), deactivated-seat gate restricted to
|
|
72
|
+
ENDUSER, ticketId int-cast (mhammontree)
|
|
73
|
+
|
|
74
|
+
## Related docs
|
|
75
|
+
- [Login flows](login-flows.md)
|
|
76
|
+
- [MSP dashboard & ticket visibility](msp-dashboard.md)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -6,6 +6,8 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
6
6
|
|
|
7
7
|
- **library** (Library) _(framework core)_ — 4 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
8
|
- **worker** (Worker) — 4 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
|
+
- **togadesk** (TogaDesk) — 3 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
10
|
+
- **togaview** (TogaView) — 3 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
|
|
9
11
|
|
|
10
12
|
## 2.0 framework
|
|
11
13
|
|
package/knowledge/registry.json
CHANGED
|
@@ -7,5 +7,7 @@
|
|
|
7
7
|
{ "repo": "worker", "project": "Worker", "framework": "1.0", "role": "app", "dependsOn": [] },
|
|
8
8
|
{ "repo": "toga2-supply", "project": "TOGa Supply", "framework": "2.0", "role": "app", "dependsOn": ["api2"] },
|
|
9
9
|
{ "repo": "saml", "project": "SAML SSO Gateway", "framework": "2.0", "role": "app", "dependsOn": [] },
|
|
10
|
-
{ "repo": "toga2-view", "project": "TOGa View Frontend", "framework": "2.0", "role": "app", "dependsOn": ["api2"] }
|
|
10
|
+
{ "repo": "toga2-view", "project": "TOGa View Frontend", "framework": "2.0", "role": "app", "dependsOn": ["api2"] },
|
|
11
|
+
{ "repo": "togadesk", "project": "TogaDesk", "framework": "1.0", "role": "app", "dependsOn": [] },
|
|
12
|
+
{ "repo": "togaview", "project": "TogaView", "framework": "1.0", "role": "app", "dependsOn": [] }
|
|
11
13
|
]
|
package/package.json
CHANGED