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.
@@ -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)
@@ -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
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.67",
3
+ "version": "1.0.68",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",