# Features — CCID

Evidence date: 2026-10-19. Source: `git@bitbucket.org:overdrive/ccid.git`, `master` at `1d3e6bf` (2026-04-16).

There is no features list in the repository. Status below is from the code and from commit subjects. "Current" means the code path is reachable from a root script. It does not mean a menu row still points at that script. Menu rows live in the database.

## Current features

### Login and account access

| Feature | Where | Status |
|---|---|---|
| Email and password login | `login.php`, `NemoPassword::checkPassword` | Current. Success opens `contact2024.php` |
| Inactive user and inactive security group rejected | `login.php` | Current |
| Forgot password | `login.php` plus `ajaxfunctions.php` `SendResetEmail` | Current. Tells the browser whether the email exists |
| Reset password from the emailed link | `resetPassword.php` | Current. Link lifetime is setting `PASSWORD_Reset_Interval` |
| My profile: change own password if the old one matches and it is not in recent history | `my.profile.php`, `User::SaveMyProfile` | Current. Display name and email are read-only |
| Logout | Header link to `LogoutRedirect` | Current. Target URL is a setting |
| Login audit row | `tblLoginInsert` → `sysLogin` | Current. See business rules for the username and password columns |
| Five-failure warning email | `CheckSecurityLock` | Current code path. The account flag save is commented out, and the login page does not populate the username the counter joins on |

### Contact directory (2024)

| Feature | Where | Status |
|---|---|---|
| Contact list with search, precinct, and active/inactive filters | `contact2024.php`, `includes/contact2024.cls.php` `getList` | Current. Default filter is Active |
| Multi-word search (every word must match) | same `getList` | Current. Commit `3aeb09f` / `e6d5dc5` era search work; the contact list search is in this class |
| Contact detail generated from `tblContact2024` | `contact2024.php` | Current |
| Company fields edited on the contact form | `contact2024.php`, `Contact::Save` | Current |
| Category type dropdown loads categories by AJAX | `jsGetCategoryItems` → `ajaxfunctions.php` `getCategoryItemsXML` | Current |
| Group checkboxes on the contact | `Contact::renderGroupList`, saved in `Contact::Save` | Current |
| Other contacts at the same company, with Remove | `Contact::getList($CompanyID)` | Current when the contact has a company |
| Make Inactive | `Contact::MakeInactive` | Current. Also used for the `Action=Delete` case |
| Excel export of the contact list | `Action=Export` | Current |
| Hard delete of a contact and its category and group rows | `Contact::Delete` | Present in the class. `contact2024.php` does not call it. `Action=Delete` calls `MakeInactive` |

### Companies

| Feature | Where | Status |
|---|---|---|
| Company list and detail from `tblCompany` | `company.php`, `includes/company.cls.php` | Current |
| Many categories via checkboxes | `Company::renderCategoryList`, `Company::Save` | Current |
| Add an existing active contact with a free-text position | `Company::AddContact` | Current. Browser and server both require contact and position |
| Remove a contact from a company by position string | `Company::DeletefromCompany` | Current |
| Make Inactive, hidden for security group `4` | `company.php` | Current |
| Excel export | `Action=Export` | Current |

### Saved search

| Feature | Where | Status |
|---|---|---|
| List, save, and delete saved searches | `search.php`, `includes/search.cls.php` | Current. The list defaults to the signed-in user and can be switched to all users. Delete removes `tblSearch` and `tblSearchField` |
| New search owned by the current user | `Search::Save` | Current |
| Contact columns: show, sort, contains, not-contain, is-empty | `Search::renderContactList` | Current |
| Category show/filter, including sub-category wildcards | `Search::renderCategoryList`, `SearchResult::getList` | Current |
| Group show/filter | `Search::renderGroupList` | Current |
| Run one selected search | `search.php` "Run Selected Query" | Current |
| Results from `vieCompanyContact` | `includes/search.result.cls.php` | Current, provided the view exists |
| Several category filters combined with OR | `SearchResult::getList` | Current. Commit `3aeb09f` |
| Select-all on list checkboxes | `js/scripts.js` `jsToggleSelect` | Current. Commit `1d3e6bf` "Add global select checkboxes" |

### Lookups

| Feature | Where | Status |
|---|---|---|
| Categories and category types, with report alias | `category.php`, `includes/category.cls.php` | Current. Delete sets inactive |
| Groups and group types, with report alias | `group.php`, `includes/group.cls.php` | Current. Delete sets inactive |
| Precincts | `precinct.php`, `includes/precinct.cls.php` | Current. Delete sets inactive. Export and reset-password methods are commented out |
| Positions lookup | `positions.php`, `includes/position.cls.php` | Current since commit `c26913b` "Add positions CRUD". The contact and company forms do not use this table; they store a typed position |

### Administration

| Feature | Where | Status |
|---|---|---|
| Users, with last login from `sysLogin` | `user.php`, `includes/user.cls.php` | Current |
| New-user reset email | `User::ResetPassword` | Current |
| Profile picture upload on user save | `User::Save` when `UserID` is already set and a file is posted as `Profile:PicturePath` | Current. Stored under setting `ProfileImageDirAdmin`. The contact screens do not show the picture |
| Security groups and per-entity flags (view, new, save, delete, special) | `security.group.php` | Current. Delete is a hard delete. Saving the group also rewrites menu entity text |
| System settings editor | `settings.php` | Current |
| Create database views from settings whose names start with `vie` | `Settings::CreateViews` | Current |
| Language switcher | Header, when `$_LANGUAGE` is populated; `ajaxfunctions.php` `setLanguage` | Present. Whether CCID loads a language table is **Unknown / Requires Confirmation** (`_nemo.translations.inc.php`) |

## Present, disconnected

| Item | Evidence | Notes |
|---|---|---|
| Older contact screen on `tblContact` | `contact.php`, `includes/contact.cls.php` | Categories and precinct sit on the contact, not on the company. Login no longer opens this page. `contact.php` still hides Make Inactive for group `4` |
| Report screen | `report.php`, `includes/engine.rpt.php` | Defaults to `timesheet.rpt`. Filters reference `tblLocation`, `tblStudyType`, `tblStudy`. No `.rpt` file is in the repository except the engine include |
| Active-projects PDF | `project.functions.inc.php` `ActiveProjectListPDF` | Uses Dompdf and tables `tblLocation`, `tblProgramme`, `tblProject`. No page calls the function |
| SMS | `SendSMS` in `_framework/system.functions.inc.php` | POST to MyMobileAPI. No caller in the CCID pages |
| Email campaign classes | `includes/email.cls.php`, `includes/email.template.cls.php` | No root `email.php`. Password reset uses `NemoEmail` and template name "Reset Password" directly |
| Placeholder counters | `getNewEmails` returns `6`, `getNewTickets` returns `1` | Not used by the contact pages |
| Sidebar toggle | `ajaxfunctions.php` `SetSidebarDisplay` | `Nemo::SideBar` exists. The back layout used by lists calls `Menu()`, not this sidebar. Whether the sidebar is shown is **Unknown / Requires Confirmation** |
| `phpinfo.php` | repository root | Calls `phpinfo()` with no session check |

## Unused libraries in this app

`mpdf/`, `dompdf/` (except the uncalled PDF helper), `PHPExcel/`, `Excel/`, `fpdi/`, `html2pdf/`. List export does not use PHPExcel. It prints the HTML table with an Excel content type.

`custom_controls/` has no PHP files.

`documents/` and `profilepictures/` are directories for uploaded or generated files. Contents were not treated as requirements.

## Removed / inactive

| Item | Evidence |
|---|---|
| Login branch that sent non-group-4 users to search | Commented in `login.php`. Everyone goes to `contact2024.php` |
| Group-4 exception on the 2024 contact list | Commented in `contact2024.php`. Make Inactive shows for every user who can open the list |
| Contact "Add To Company" action | Commented in `contact2024.php`. Company attachment is done inside `Contact::Save` and from `company.php` |
| Company group checkboxes | Commented in `Company::Save` |
| `Contact::Delete` hard delete from the 2024 page | The method remains. The page action does not call it |
| Security lock that would set `blnLogin = 0` | The assignment is followed by a commented `Save()` |
| Precinct export and precinct reset-password | Commented in `precinct.cls.php` |

## Changed across recent commits

| Commit | Subject | Product effect |
|---|---|---|
| `1d3e6bf` | Add global select checkboxes | `jsToggleSelect` in `js/scripts.js` toggles every checkbox with a given id. Default id is `chkSelect` |
| `3aeb09f` | Improve search sql a little and change category search filters to be ORs | Saved-search category filters match any selected category |
| `da992f2` | pagination fixes | List paging |
| `e6d5dc5` | search, contact & company latest update | Contact, company, and search behaviour leading to the current save and list code |
| `c26913b` | Add positions CRUD | `positions.php` |
| `9eeaaf3` | Change mail to use phpmailer | Outbound mail goes through PHPMailer |
| `370d935` | Add db connection for Vhost | Host switch in `system.php` |
| `39b0e54` | MariaDB NULL/enum handling | Detail forms survive a MariaDB upgrade |

## Planned features

None are described in the repository. **Unknown / Requires Confirmation.**

## Deprecated

No file is marked deprecated. The older `tblContact` screen and the timesheet report are the practical deprecated surface, if the menu no longer links them. That menu state is **Unknown / Requires Confirmation**.
