# Technical documentation — Paterson & Cooke Management System

Evidence date: 2026-10-08. Source: `git@bitbucket.org:overdrive/patersoncooke.git`, `master` at `008c5ac` (2022-02-24, Liam Kleyn).

Passwords and mailbox addresses that appear as literals in the source are not copied here.

## What this repository is

A single PHP application, checked out as flat scripts in the web root. There is no `composer.json`, no `package.json`, no framework, no `public/` split, and no test suite. Four git commits exist, all on `master`, dated 21–24 February 2022. The footer and in-code comments describe a system already in use in 2010.

## Runtime

| Item | Evidence | Status |
|---|---|---|
| Language | PHP, short open tags (`<?`) in class files, `<?php` on most page files | **Implemented** |
| PHP version | `README.md` says 7.4 | **Documented**. Not enforced in the tree |
| Web server | `README.md` says Apache | **Documented** |
| `short_open_tag` | README requires it. Class files will not parse without it | **Documented** and **Implemented** |
| Database | MySQL, selected name `pcce` | **Implemented** default |
| Extensions | `mysqli` for the live connection class. `mail` for reminders. GD-style image calls in `utilisationgraph.cls.php` (`createImage`) | **Implemented** usage. Which extensions are installed is **Unknown** |

The February 2022 commit `0eb150f` replaced `mysql_*` with `mysqli_*` in the live connection class, the menu, and login. `008c5ac` changed `getLastID` to `mysqli_insert_id($this->cConn)`.

## Repository layout

```text
patersoncooke/
  *.php                      page controllers (one URL per file)
  login.php                  sign-in
  index.php                  home
  mysql.cls.php              old mysql_* class, not included by system.inc.php
  leave.cls.php              old leave class, not included by leave.php
  phpinfo.php, temp.php, write.php
  README.md
  include/
    system.inc.php           bootstrap, auth helpers, dates, paging
    version.inc.php          footer
    header.inc.php           HTML head
    header_report.inc.php    report head
    menu.inc.php             menu query
    footer.inc.php
    database/mysql.cls.php   live mysqli class
    database/sql.inc.php     SQL string builders (about 170 KB)
    personnel/               personnel and timesheet classes
    project/                 project class and zzprojectbillingrate.cls.php
    invoice/                 invoice and disbursement classes
    general/                 reference-data classes
    client/                  client and contact classes
    reports/                 one class per report
    javascript/              menu, md5, calendar, scripts.js
    style/                   CSS, including stylePRINT.css
    images/                  logo and icons
    calendar_control/        date picker
    excelwriter.inc.php      unused Excel writer
```

## Request lifecycle

1. The page includes `include/system.inc.php`.
2. Error reporting is `E_ERROR` only.
3. `gpc_extract` copies `$_GET`, `$_POST`, and `$_COOKIE` into `$GLOBALS`. This is a local replacement for the removed PHP `register_globals` behaviour. `PHP_SELF` is copied from `$_SERVER`.
4. `get_magic_quotes_gpc` is called when present. That function was removed in PHP 8. The README names PHP 7.4, where the function still exists and magic quotes are always off. Provenance: **Implemented**.
5. The MySQL connection opens and database `pcce` is selected.
6. `get_system_settings()` loads one settings row into `$arrSys` (rows per page).
7. User id, level, display name, and username are parsed from cookie `ckyPCCE` by splitting on `|`.
8. Some pages handle an XML branch and `die` before `checkLogin`. See the PRD, FR-014.
9. `checkLogin` redirects to `login.php` when the cookie is unset.
10. The page includes a class, switches on `act`, and prints HTML.
11. `menu.inc.php` and `footer.inc.php` close the page.

`act` values are per page. They are not a global enum. Examples are in the PRD workflows.

## Database access

Class: `cMySQL` in `include/database/mysql.cls.php`.

| Method | Behaviour |
|---|---|
| Constructor | Stores server, user, and password. Defaults are localhost, user `root`, and a non-empty password literal |
| `openConn` | `mysqli_connect` when `$cConn` is not a resource. A `mysqli` object is not a resource, so the `is_resource` test does not describe a live mysqli connection. **Inferred** from the PHP types. Whether this reconnects every query was not executed |
| `setDatabase` | `mysqli_select_db`, default `pcce` |
| `doQuery` | `mysqli_query`. No product-level error wrapper |
| `getLastID` | `mysqli_insert_id($this->cConn)` |
| `quote_smart` | Strips magic quotes if any, replaces double quotes with single quotes, then `mysqli_real_escape_string` and wraps in quotes |

The root `mysql.cls.php` is the pre-upgrade class: `mysql_connect`, `mysql_select_db`, `mysql_query` with `die($sql)`, and an empty default password. Nothing in `system.inc.php` includes it.

SQL text lives in functions in `include/database/sql.inc.php`. Many functions read globals (`$strUserName`, `$user_displayname`, and so on) that `gpc_extract` populated. Queries are concatenated. `quote_smart` is used on some string inputs and not on numeric ids taken from the request.

There is no schema file. Tables referenced by SQL:

`tblPersonnel`, `tblSecurityLevel`, `tblOffice`, `tblWorkgroup`, `tblSettings`, `tblClient`, `tblContact`, `tblCurrency`, `tblFeeStructure`, `tblOfficeFeeStructure`, `tblBillingCode`, `tblBillingRate`, `tblActivityCategory`, `tblActivityCode`, `tblActivityBillingCode`, `tblPersonnelBillingCode`, `tblProject`, `tblProjectStatus`, `tblProjectOffice`, `tblProjectBillingRate`, `tblProjectProposal`, `tblProjectProposalItem`, `tblArea`, `tblDeliverable`, `tblWeek`, `tblTimesheet`, `tblTimesheetHours`, `tblInvoice`, `tblInvoiceItem`, `tblInvoiceFee`, `tblTempInvoiceItem`, `tblSiteAllowance`, `tblInvoiceSiteAllowance`, `tblInternalDisbursement`, `tblInvoiceInternalDisbursement`, `tblExternalDisbursement`, `tblInvoiceExternalDisbursement`, `tblmenulevel0`, `tblmenulevel1`.

`tblTimesheetHours` stores one flag per cell (`blnCell0` through `blnCell35`), from `sql_timesheet_hours_insert`.

Column comments inside `sql.inc.php` (for example the project-status comment near line 2578, and the activity-category comment near line 1681) match the fields the forms edit. They are **Implemented** as comments, not as a migrated schema.

## Authentication and session

| Item | Detail |
|---|---|
| Cookie name | `ckyPCCE` |
| Value | `{PersonnelID}\|{strDisplayName}\|{refSecurityLevelID}\|{strUserName}` |
| Set | `setcookie` with no expiry, path, secure, or httponly argument in the success branch of `login.php` |
| Cleared | `login.php` sets the cookie empty with `time() - 3600` before drawing the form |
| Check | `isset` on the extracted cookie variable |
| Password at rest | Personnel form value stored in `strPassword` |
| Password on the wire | MD5 hex computed by `include/javascript/md5.js` |
| Comparison | `md5($row->strPassword) == $strPassword` |

`user.php` is not on this path. See features.

## Authorisation

`checkSecurity($field, $minimum)` loads the security-level row for the level id in the cookie and compares `$row[0]` from `sql_securitylevel_get_detail_get_field`. Failure returns 0. Pages then call `NotSufficientSecurityLevel()`, which prints a fixed message and the menu, then `die`.

Timesheet scope is a second mechanism, described in business rule BR-005.

## Front end

- HTML 4.01 Transitional (`header.inc.php`).
- Layout is tables. Styles are `include/style/style.css`, `menu.css`, and `stylePRINT.css` for print.
- Menu library: `include/javascript/menu.js` and `menu_tpl.js`.
- Shared helpers: `include/javascript/scripts.js` (`trim` and similar, used by validators).
- Date entry: `include/calendar_control/` (CSS and `CalendarControl.js`). A second copy of the calendar script and CSS sits under `include/javascript/` and `include/style/`.
- Timesheet editing is custom JavaScript emitted by `Timesheet::_timesheet_js` (cell paint, row add, XMLHTTP for projects and areas).
- No bundler, no npm, no source map.

## Background work, queues, storage, monitoring

None are in the repository. There is no cron script, no queue consumer, and no log configuration. File storage for uploads was not found. Images in `include/images/` are static.

`phpinfo.php` would expose the server environment to anyone who can request it. It is a file in the web root, not a monitoring integration.

## Email

`CompletedTimesheet::SendMail` builds an HTML message and calls `mail($strEmail, $subject, $message, $headers)`. Headers set MIME, To, CC, and From from personnel rows. No SMTP settings are in git.

`temp.php` is a one-shot `mail()` test and is not part of the timesheet flow.

## Reporting and export

Report pages are separate URLs. The console (`reportconsole.cls.php` `runReport`) builds a query string and either `window.open`s it or assigns `window.location` for Export.

Export sets headers similar to:

- `Content-type: application/x-msexcel`
- a content-disposition filename

The body remains an HTML table. `include/excelwriter.inc.php` is unused.

`utilisationgraph.cls.php` builds a plot image in PHP and has its own scale and colour helpers.

## Security notes (observed, not a penetration test)

These are properties of the code as written.

- Database credentials are default arguments on `cMySQL`.
- Request values become globals, then are interpolated into SQL.
- The session cookie is not signed and is not checked against `tblPersonnel`.
- Login and several report URLs do not share one gate. Four report pages have `checkLogin()` commented out. XML branches on timesheet, project, invoice, and report console run before the login check.
- `phpinfo.php`, `temp.php`, and `write.php` do not call `checkLogin`.
- `quote_smart` replaces `"` with `'` before escaping.
- Personnel delete is a flag update. Invoice delete is a real `DELETE`.
- `error_reporting(E_ERROR)` hides warnings, including undefined-constant notices that the unquoted `checkSecurity(blnTimesheetOffice,2)` call can raise.

## Development and testing

No PHPUnit, no fixture, no seed, and no CI file are in the tree. The README's only operational instruction is to enable short open tags, with a link to a short-open-tag article.

`blnTest` checkboxes on the timesheet and report console print `$_POST` when set. They are hidden unless already checked.

Local run, as far as the code specifies:

1. Apache with PHP 7.4 and `short_open_tag = on`.
2. `mysqli` enabled.
3. A MySQL database named `pcce` containing the tables above, plus menu and security-level rows.
4. The connection defaults in `include/database/mysql.cls.php` must match that server, or the constructor arguments must be changed. The file does not read an environment variable.
5. Document root is this repository root, so that `login.php` and `include/` resolve as written.

No sample database is shipped, so a checkout cannot be exercised end to end from git alone.

## Deployment

**Unknown / Requires Confirmation.** No Dockerfile, pipeline, CodeDeploy, or vhost is in the tree. A comment in `write.php` mentions `http://webdev2/pcce/write.php`. That is the only host string found.

## Dependency list

| Dependency | Role |
|---|---|
| PHP 7.4 with short open tags and mysqli | Runtime named by the README and required by the code |
| Apache | Named by the README |
| MySQL database `pcce` | All persistent state |
| PHP mail | Timesheet reminders |
| Browser JavaScript | Menu, MD5 login, timesheet grid, report window |

Third-party code inside the tree, with the credits the files already carry:

- `include/javascript/md5.js` — MD5 implementation used at login.
- `include/javascript/menu.js` — menu widget.
- `include/calendar_control/` — calendar popup.
- `include/excelwriter.inc.php` — Excel writer credited to Harish Chauhan, 31 Dec 2004. Unused.

## Files that will fatal-error if opened

| URL | Reason |
|---|---|
| `projectbillingrate.php` | Includes `include/project/projectbillingrate.cls.php`, which is not in the tree |
| `user.php` | After the security query, the class calls undefined `sql_user_*` functions. The security call looks up a column named `90`, so it may fail first |

`phpinfo.php` runs. It is not a fatal for a missing include. It should not be treated as a product page.
