# Technical documentation — label_system

Evidence date: 2026-10-10. Source: `git@bitbucket.org:overdrive/label_system.git`, `master` at `c8b07d501119a500952c2644ff2f9085d0aeedab` (2026-06-03, "Revert changes made by Jacques to match QA").

Package name in `composer.json`: `codeigniter4/adminstarter`. Description: "Admin Overdrive". Homepage: `https://www.overdrive.co.za`. License: MIT.

## Stack

| Item | Evidence |
|---|---|
| PHP `^8.0` | `composer.json`, `README.md` |
| CodeIgniter 4 `^4.0` | `composer.json`, `spark`, `app/Config` |
| MySQL via mysqlnd | README server requirements. Default group and SAWIS groups in `app/Config/Database.php` |
| mPDF `^8.1` | Meeting-minute PDFs |
| PhpSpreadsheet `^1.25` | `MasterFile`, `Car` imports. Not used by `Labels` |
| `orhanerday/open-ai` `^3.5` | `ChatGPT` controller |
| `aws/aws-sdk-php` `^3.254` | Declared. No `Aws\` import under `app/` |
| Imagick and Ghostscript | Called from `Labels` upload. Not Composer packages. Windows GS path `C:/Program Files/gs/gs10.05.0/bin` |
| PHPUnit `^9.1` | `require-dev`. Tests under `tests/` are framework examples |
| SonarQube scan | `bitbucket-pipelines.yml`, `sonar-project.properties` |

PHP extensions named in the README: intl, curl, json, mbstring, mysqlnd, xml. Upload also uses fileinfo.

## Layout

```text
app/Controllers     HTTP entry points
app/Models          Query and label state changes
app/Views           Bootstrap-style pages, SAWIS header and menu
app/Database        Migrations and seeds
app/Filters         Authentication, Authorization, Cors
app/Config          Routes, filters, database groups
app/Helpers         Menu helper
app/Common.php      Tamper check, late-submission helper
public              Front controller and uploaded files
tests               Example PHPUnit cases
writable            CI4 writable directory
env                 Sample environment (copy to .env)
bitbucket-pipelines.yml
sonar-project.properties
```

`vendor/` is present in a full install. Auto-route is enabled, so a public method is reachable as `controller/method` when filters allow it. Explicit routes cover settings, analytics, label admin screens, meeting minutes, users, menu management, cron, tracking pixel, and the organisations resource.

## Request path

1. `public/index.php` boots CodeIgniter.
2. Global `isLoggedIn` runs except on the public list in `app/Config/Filters.php`.
3. Global `isGranted` checks `user_menu` and `user_access` for segment 1, with its own exception list (`home` and `Welcome/*` are excluded, so home is login-only).
4. Controller. `BaseController::initController` sets `date_default_timezone_set('Africa/Johannesburg')`.
5. Views extend `layouts/main` and can include `layouts/sawis-header.php` and `layouts/sawis-menu.php`. The generic sidebar (`layouts/sidebar.php`) still shows a FindAndDrive logo image. That branding does not match SAWIS. Provenance: **Implemented**.

CSRF is not applied. Debug toolbar is applied except `chatGPT/*`.

## Environments

Code selects behaviour by substring of `base_url()`:

| URL contains | SAWIS DB group | Upload document root |
|---|---|---|
| `.local` | `sawisOnlineDev` | `DOCUMENT_ROOT` (Windows adds `/public/`) |
| `qa.` | `sawisOnlineQa` | `DOCUMENT_ROOT/label/public` |
| `training.` | `sawisOnlineTRAINING` | `DOCUMENT_ROOT/label/public` |
| otherwise | `sawisOnline` | `DOCUMENT_ROOT/label/public` |

Sample `env` sets `CI_ENVIRONMENT = development` and `app.baseURL = 'http://localhost/label_system/'`. Session driver is the database handler, table `sessions`, cookie `ci_session`, match IP true.

Connection hostnames, database names, and passwords are in `app/Config/Database.php`. They are internal and are not repeated in this document.

`cronJobs?force=1` bypasses the Monday guard only when `ENVIRONMENT !== 'production'`.

## Data

Label product tables created or altered by migrations:

- `label` — designer, member, approver, cultivar, wine type, WSB number, barcode, production area, year, warning, type, alcohol, bottle size, status, active, audit columns. Later migrations add estate, producer, container type, bottle unit, trademark, SAWIS agent, CC list, provisions, meeting group.
- `file` — path, type, version, approved flag.
- `label_note`, `label_log`, `label_status` (parent and group added later), `label_provision`, `label_email_template`.
- `meeting_groups`, `user_groups`.
- `sent_queue` — to, cc, subject, body, tracking code, processed, send date.
- `saved_comments`, `public_holidays`, `container_type`, `cultivar_type`, `blend_cultivars`.
- `meeting_minutes_groups`, `meeting_minutes_notes`, `meeting_minutes_drafts`, `meeting_minutes_docs` (signed-document columns added).

Starter tables still migrated: `users`, `user_role`, `user_menu`, `user_submenu`, `user_menu_category`, `user_access`, `sessions`, `organisations`, `settings`, `language`, `cronjob_actions`, `cronjob_log`, `media_file`, `uploaded_file`, `car` and cost tables, `app_session`, `master_file_model`, `master_file_overland`, `test`, `test_log`.

SAWIS Online tables read, not migrated here: `sysLabelSystemLogin`, `sysUser`, `sysUserCompanyGroup`, `tblMember`, `sysSecurity`, `sysMenuLevel1`, `sysMenuLevel2`.

Status ids used by code are documented in `business-rules.md`. Names are rows in `label_status`, not constants.

## Auth

- Session array: `username`, `role`, `isLoggedIn`, `userID`, `labelID` (set when a form opens).
- Seamless login hashes `email + code` with SHA-256 and compares it to `sysLabelSystemLogin.shareSesh`. The lookup SQL concatenates the code. Provenance: **Implemented**.
- Local passwords use `password_verify`.
- `autoRegistration` creates a local user from SAWIS when the handoff is valid and the email is new.
- Organisations API uses `JwtLibrary` and is excluded from both filters. It is starter code.

`app.sessionMatchIP = true` will drop a session when the client IP changes.

## Files and PDF

Uploads land in `uploads/` relative to the public directory used above. Dispensation uses `uploads/dispensation/` and creates the directory at mode `0777` if missing.

Imagick settings on create: 300 DPI, crop box, white background, flatten, RGB, JPEG quality 80, memory limit set in code (the comment says 256MB while one call passes `521 * 1024 * 1024`). Disk limit 20GB on create and 10GB on resubmit. Temporary directory `tmp_upload/`. `Labels::index` deletes `public/magick-*` files older than 5 minutes.

`labelApprovalPDF` and `remakeCert` build the decision certificate. Meeting minutes use mPDF and write a file under the app's upload area. `checkPdfValidity` and `checkPDFValidation` exist as separate checks.

## Mail and cron

`GET /cronJobs` is unauthenticated. It loads `cronjob_actions` where `active = 1`, finds the latest `cronjob_log` per job, and calls the controller method named in `function` when `checkCronTiming` says the recurrence has elapsed.

Methods on `CronJobs`: `sendDispensationNotifications`, `sendScheduledEmails`, `sendApprovalEmails`, `sendWeeklyAnalyticsReport`, `testCron`.

`Email::sendEmailNotification` only inserts `sent_queue`. Sending is the cron's job (`sendEmailsViaCron`). Tracking pixel route: `GET tracking-pixel` and `GET Tracking-pixel`.

Weekly report recipients are the cron row's `action` string. The README and `NewKnowledgeBase.md` describe the Monday 07:00 window and the year-on-year tables. `CHANGELOG.md` (2026-01-21) matches that behaviour.

Sample `env` comments name `OPENAI_API_KEY`, `SENDGRID_API_KEY`, `GOOGLE_MAP_API_KEY`, and SMTP host, username, and password. Which provider production uses is **Unknown / Requires Confirmation**.

## Integrations

SAWIS menu rendering (`MenuModel::getSawisMenu`) maps label role ids to SAWIS `refSecurityGroupID`:

| Label role | SAWIS group id | Comment in code |
|---|---|---|
| 1 | 1 | Admin Dev |
| 2 | 3 | Sawis User. Comment says three SAWIS roles collide here |
| 3 | 15 | Label Committee |
| 4 | 11 | Member Superuser |
| 5 | 14 | Designer Basic |
| 7 | 2 | Comment text says "Sawis User" while the case label says Sawis Admin |
| 8 | 5 | Designer. Case label says this user cannot use the label system |
| 9 | 16 | WCA Board |
| other | 9999 | TODO |

Menu level 1 id 15 is excluded.

## Deployment and CI

`bitbucket-pipelines.yml` runs `sonarsource/sonarqube-scan:3.0.2` on branches `master` and `qa`. Docker memory is 3072 MB. Clone depth is full. There is no deploy step.

`sonar-project.properties` sets project key `overdrive_label_system_AYr5qv21RY8iIL6fFGD4`, turns coverage off, and excludes large parts of `app/`, `public/`, tests, and views from analysis. Several PHP quality rules are ignored globally (S1764, S1192, S100, S3776, S3801).

`public/deploy.php` exists and is Sonar-excluded. What it does was not treated as a deployment contract. **Unknown** whether it is still called.

README virtual-host example still points at `c:/wamp64/www/adminOverdrive/public` and host `adminoverdrive.local`. Runtime code expects `baseURL` to contain `.local`, `qa.`, or `training.`, or it uses the production SAWIS connection and `/label/public`.

## Security notes from the tree

These are observations, not a pentest.

- Login and producer lookup build SQL by concatenating email, code, and member id lists.
- Cron, tracking pixel, and the organisations resource do not require the session filter.
- CSRF filter is commented out.
- Dispensation directory is created mode `0777`.
- `labelFormsTamperProtection` is session equality only, and upload skips it.
- Sonar exclusions omit filters, helpers, and config from analysis.
- `Database.php` contains live-looking credentials. They were not copied here.

## Development and tests

Setup from the README: copy `env` to `.env`, `php spark db:create`, `php spark migrate`, `php spark db:seed users`, `php spark key:generate`, `php spark serve`.

`tests/` contains `HealthTest`, example session and database tests, and CI4 support fixtures. No label workflow test was found. `phpunit.xml.dist` is present. Composer script `test` runs PHPUnit.

`NewKnowledgeBase.md` documents the cron dispatcher, the Johannesburg timezone, and the weekly report's three comparison tables. `CHANGELOG.md` has the 2026-01-21 analytics notes only.

## Monitoring

No application monitoring agent, health endpoint beyond starter site-health, or log shipper was found. Cron writes through `logCronText` and `cronjob_log`. Upload and login failures append `log.txt`. Imagick cleanup appends `imagick-log.txt`. Those files are written in the working directory. Provenance: **Implemented**.
