# CLAUDE.md

## Project Overview

**Crop Management Platform** — modX CMS → standalone PHP migration
Purpose: Centralise records for irrigation, weather, and soil moisture; supports real-time monitoring for Australian conditions.
Stack: **PHP 8.4**, **MySQL**, Bootstrap 5, jQuery, Chart.js
Repository root: `f:\GIT_REPO\crop_monitor`

---

## Directory Structure

```
crop_monitor/
├── api/                          # REST API endpoints (legacy)
├── client-assets/                # CSS, JS, images, uploads
│   ├── css/                      # Bootstrap 4 + custom styles
│   ├── js/                       # jQuery, Chart.js, custom scripts
│   ├── table/                    # DataTables AJAX helpers (legacy mysqli)
│   ├── FredTemplate/             # Old template assets
│   └── fullcalendar/             # Calendar widget
├── components/                   # Reusable UI components (migrated)
├── config/                       # Database config (PDO)
├── controllers/                  # Form POST handlers (migrated)
├── dashboard/                    # Main application pages
│   ├── crop-analysis/
│   │   ├── soil-test-data/       # Soil analysis entry + display
│   │   ├── plant-test-data/      # Plant tissue analysis
│   │   ├── animal-dietary-balance/
│   │   └── water-test-data/
│   ├── client-settings/
│   ├── crop-cards/
│   ├── inbox.php
│   └── planning-calendar.php
├── layouts/                      # Page templates (header, footer, nav, sidebar)
├── lib/                          # Utility libraries
├── login/                        # Auth pages (still modX templates)
├── cropmonitor.sql               # Full database schema dump
├── index.php                     # Empty — routing via .htaccess
├── .htaccess                     # Front controller rewrite rules
└── .env                          # Empty — credentials currently in config/database.php
```

---

## Database Schema

Schema file: `cropmonitor.sql`

| Table | Purpose |
|-------|---------|
| `client_records` | Client info + weather station API keys (`modx_user_id` FK) |
| `soil_records` | Soil analysis data — 80+ columns (nutrients, base saturations, calculations) |
| `soil_specifications` | Min/max nutrient ranges by soil type (used for recommendations) |
| `soil_comments` | Element-specific recommendation text |
| `plant_records` | Plant tissue analysis |
| `plant_specifications` | Nutrient ranges by crop/growth stage |
| `animal_records` | Animal dietary balance |
| `animal_specifications` | Nutrient requirements by species |
| `water_records` | Water quality analysis |
| `weather_station` | Weather station data ingestion |
| `fertiliser_specifications` | Fertilizer product database |
| `block_info` | Field/block metadata |
| `crop_info` | Crop definitions |
| `calendar_events` | User scheduling |
| `reports` | Comments/notes on analyses |
| `field_sensors` | IoT sensor readings |
| `sensor_id` | Sensor registration |

**Important data notes:**
- Most nutrient values stored as `VARCHAR(10)` — always cast to float in PHP
- `modx_user_id` field links all records to users (legacy modX user system)
- `rand` field on `soil_records` is a secondary verification token for URL access

---

## Migration Status

### Completed (PDO + secure)
| Original modX | Migrated to |
|---------------|------------|
| `[[!clientDetailsFORM]]` | `components/clientDetailsForm.php` |
| `[[!newClientDetails]]` | `components/newClientModal.php` + `controllers/newClientSubmit.php` |
| `[[!soilformSubmit]]` | `controllers/soilTestSubmit.php` |
| `[[!Personalize?]]` + `[[Wayfinder?]]` | `components/navigation.php` |
| `[[$dash-header]]` + `[[$dash-footer]]` | `layouts/header.php`, `layouts/footer.php`, `layouts/navbar.php`, `layouts/sidebar.php` |
| `soil-test-data.php` | Uses include-based layout system |
| `soil-analysis.php` | PDO + Bootstrap 5 + calc functions |
| `[[!Login]]` | `login/login.php` (session auth, CSRF, PDO) |
| `[[!Register]]` | `login/register.php` (validation, auto-login, CSRF) |
| `[[!ForgotPassword]]` | `login/forgot-password.php` + `login/reset-password.php` (token-based, 1hr expiry) |

### Still Needs Migration (priority order)
1. ~~**`login/login.php`**~~ — **DONE** (2026-03-27)
2. ~~**`login/register.php`**~~ — **DONE** (2026-03-27)
3. ~~**`login/forgot-password.php`**~~ — **DONE** (2026-03-27)
4. **`login/change-password.php`** — modX `[[!ChangePassword]]` → rewrite using `changePassword()` in `lib/auth.php`
5. **`dashboard/crop-analysis/soil-test-data/soil-analysis-pdf.php`** — mysqli → PDO, SQL injection fix
6. **`dashboard/crop-analysis/soil-test-data/soil-report.php`** — display page audit
7. **`dashboard/crop-analysis/soil-test-data/soil-report-pdf.php`** — mysqli → PDO
8. **`dashboard/crop-analysis/plant-test-data/`** — full migration
9. **`dashboard/crop-analysis/animal-dietary-balance/`** — full migration
10. **`dashboard/crop-analysis/water-test-data/`** — full migration
11. **`dashboard/client-settings/product-list.php`** — mysqli → PDO
12. **`dashboard/client-settings/updateproduct.php`** — SQL injection fix
13. **`dashboard/crop-cards/index.php`** — mysqli → PDO
14. **`dashboard/dashboard.php`** — replace `[[$widget]]` chunks with PHP components
15. **`api/updateweatherstation.php`** — critical SQL injection fix
16. **`api/api.php`** — remove deprecated `mysql_connect()`, use PDO
17. **`client-assets/table/gettable.php`** — AJAX DataTable endpoint, mysqli → PDO

### Legacy files to delete after migration
- `post.php` (replaced by `components/clientDetailsForm.php`)
- `newClientDetails.php` (replaced by `components/newClientModal.php`)
- `soilAnalysisCalcs.php` (dev/debug file)
- `test-analysis.php` (dev/debug file)
- `dashboard/crop-analysis/soil-test-data/soil-submit.php` (replaced by `controllers/soilTestSubmit.php`)

---

## Security Audit

### Safe (PDO prepared statements)
- `config/database.php`
- `lib/soil_calculations.php`
- `components/clientDetailsForm.php`
- `controllers/newClientSubmit.php`
- `controllers/soilTestSubmit.php`
- `dashboard/crop-analysis/soil-test-data/soil-analysis.php`
- `dashboard/crop-analysis/soil-test-data/soil-report.php`

### Critical vulnerabilities (SQL injection via unescaped params)
| File | Issue |
|------|-------|
| `api/updateweatherstation.php` | 40+ unescaped GET params in INSERT |
| `newClientDetails.php` | POST params concatenated in INSERT |
| `post.php` | `$modx_user` directly in SELECT WHERE |
| `soilAnalysisCalcs.php` | GET `id` directly in SELECT WHERE |
| `dashboard/crop-analysis/soil-test-data/soil-submit.php` | POST params in INSERT |
| `dashboard/crop-analysis/soil-test-data/soil-analysis-pdf.php` | GET params in SELECT |
| `dashboard/crop-analysis/soil-test-data/soil-report-pdf.php` | GET params in SELECT |
| `dashboard/crop-analysis/soil-test-data/base-saturation-pie.php` | GET params in SELECT |
| `dashboard/crop-analysis/animal-dietary-balance/animal-submit.php` | POST params in INSERT |
| `dashboard/crop-analysis/plant-test-data/generating-plant-analysis.php` | POST params in INSERT |
| `dashboard/client-settings/updateproduct.php` | String concatenation in INSERT |
| `dashboard/crop-cards/index.php` | `$client_id` in SELECT |
| `client-assets/table/gettable.php` | Dynamic query from POST params |
| `api/api.php` | Uses removed `mysql_connect()` (PHP 7+: fatal error) |

### Other security issues
- Hardcoded credentials in multiple legacy files (`root`/`R3M0T31` and `cropmonitor`/`brvnCcaEYxlPCS3`)
- `.env` file is empty — credentials should move there
- `lib/auth.php` `hasPermission()` always returns `true` — RBAC not yet implemented

---

## Key Files Reference

### Config & Libraries
| File | Purpose |
|------|---------|
| `config/database.php` | PDO connection, `getDBConnection()` function |
| `lib/auth.php` | `isLoggedIn()`, `requireLogin()`, `getCurrentUserId()` |
| `lib/csrf.php` | `generateCsrfToken()`, `verifyCsrfToken()`, `regenerateCsrfToken()` |
| `lib/validation.php` | `sanitizeString()`, `validateNumeric()`, `ValidationException` |
| `lib/soil_calculations.php` | `soilAnalysisReportCalcs()`, `soilProgramCalcs()` |
| `lib/db.php` | **Empty** — intended for shared DB helpers |
| `lib/flash.php` | **Empty** — intended for flash messages |

### Layouts
| File | Purpose |
|------|---------|
| `layouts/header.php` | HTML `<head>`, Bootstrap 5 CDN, CSS includes. Expects `$pageTitle`, `$siteName` |
| `layouts/navbar.php` | Top navigation bar |
| `layouts/sidebar.php` | Left sidebar navigation |
| `layouts/footer.php` | Closing tags + JS includes |

### Components
| File | Purpose |
|------|---------|
| `components/clientDetailsForm.php` | Client dropdown + auto-fill (PDO, session user filter) |
| `components/newClientModal.php` | Bootstrap modal to add client (CSRF protected) |
| `components/soilAnalysisForm.php` | Full soil test entry form (~50 fields, 7 sections) |
| `components/navigation.php` | Auth-aware navigation |

### Controllers
| File | Purpose |
|------|---------|
| `controllers/newClientSubmit.php` | AJAX POST handler: creates client in `client_records` |
| `controllers/soilTestSubmit.php` | POST handler: validates + inserts soil test, runs calculations, redirects to analysis |

---

## modX Tags Reference

### Replaced
| Tag | Replacement |
|-----|------------|
| `[[!clientDetailsFORM]]` | `components/clientDetailsForm.php` |
| `[[!newClientDetails]]` | `components/newClientModal.php` |
| `[[!soilformSubmit]]` | `controllers/soilTestSubmit.php` |
| `[[!Personalize?]]` + `[[Wayfinder?]]` | `components/navigation.php` |
| `[[$dash-header]]` | `layouts/header.php` + `layouts/navbar.php` |
| `[[$dash-footer]]` | `layouts/footer.php` |

### Still present in codebase (needs replacement)
| Tag | Location | Replace with |
|-----|----------|-------------|
| `[[*longtitle]]` | `login/*.php` | PHP variable `$pageTitle` |
| `[[++site_name]]` | `login/*.php` | `config/constants.php` constant |
| `[[!++site_url]]` | `login/*.php` | Base URL constant |
| `[[++modx_charset]]` | `login/*.php` | `'UTF-8'` |
| `[[*introtext]]`, `[[*description]]` | `login/*.php` | Page-level PHP vars |
| `[[!Login?...]]` | `login/login.php` | Custom PHP session login |
| `[[!Register?...]]` | `login/register.php` | Custom PHP registration |
| `[[!ForgotPassword?...]]` | `login/forgot-password.php` | Custom PHP password reset |
| `[[!ChangePassword?...]]` | `login/change-password.php` | Custom PHP password update |
| `[[!Profile]]` | `login/login.php` | Session user data |
| `[[$test-widget]]`, `[[$client-widget]]`, etc. | `dashboard/dashboard.php` | PHP components |
| `[[~4]]`, `[[~5]]`, `[[~10]]` | `login/*.php` | Hardcoded URL constants |

---

## Coding Patterns to Follow

### Page template pattern
```php
<?php
require_once __DIR__ . '/../../config/database.php';
require_once __DIR__ . '/../../lib/auth.php';
require_once __DIR__ . '/../../lib/csrf.php';

requireLogin();

$pageTitle = 'Page Title';
$siteName  = 'Crop Monitor';

include __DIR__ . '/../../layouts/header.php';
include __DIR__ . '/../../layouts/navbar.php';
include __DIR__ . '/../../layouts/sidebar.php';
?>
<!-- page content -->
<?php include __DIR__ . '/../../layouts/footer.php'; ?>
```

### PDO query pattern
```php
$pdo  = getDBConnection();
$stmt = $pdo->prepare('SELECT * FROM soil_records WHERE id = ? AND rand = ?');
$stmt->execute([$recordId, $randId]);
$row  = $stmt->fetch();
```

### Output escaping
Always use `htmlspecialchars($value, ENT_QUOTES, 'UTF-8')` when echoing user or DB data into HTML.

### CSRF in forms
```php
<input type="hidden" name="csrf_token" value="<?= generateCsrfToken() ?>">
```

### URL parameters used by soil analysis pages
| Param | Type | Meaning |
|-------|------|---------|
| `cid` | int | client_records.id |
| `rid` | int | soil_records.id |
| `rand` | float | soil_records.rand (secondary auth token) |
| `stid` | string | crop/soil type |

---

## Architecture Decisions

- **No framework** — plain PHP, manual includes, no routing library
- **Layout system** — `layouts/` includes (not a templating engine)
- **Folder structure** — keep existing `dashboard/crop-analysis/` paths (no MVC rename needed unless requested)
- **Authentication** — session-based (`$_SESSION['user_id']`); RBAC via `hasPermission()` not yet implemented
- **Asset pipeline** — Bootstrap/jQuery via CDN; custom CSS/JS in `client-assets/`
- **PDF generation** — `pdfchrome.php` (headless Chrome); also FPDF/mPDF patterns in some pages

---

## New auth files (2026-03-27)

| File | Purpose |
|------|---------|
| `database/migrations/001_create_users.sql` | Run once — creates `users` + `password_resets` tables |
| `lib/auth.php` | `loginUser()`, `logoutUser()`, `registerUser()`, `createPasswordResetToken()`, `validatePasswordResetToken()`, `resetPassword()`, `changePassword()` |
| `login/_head.php` | Shared minimal HTML head for auth pages (Bootstrap 5, no sidebar) |
| `login/_foot.php` | Closing tags + Bootstrap JS |
| `login/login.php` | Email + password login with CSRF |
| `login/register.php` | Full registration form with server-side validation |
| `login/forgot-password.php` | Email submission for reset token |
| `login/reset-password.php` | Token-validated password reset |
| `login/logout.php` | Session destruction + redirect |

**Note on email sending:** `forgot-password.php` generates the token and logs it via `error_log()`. No email is sent until SMTP is configured. The reset link format is `/login/reset-password.php?token={token}`.

## Open Questions

1. **URL compatibility**: Must URL slugs matching modX resource IDs (e.g., page 41, 66) be preserved for external links?
2. **Credentials**: Move DB password to `.env` file and load with `vlucas/phpdotenv` or manual `parse_ini_file()`?
3. **Email / SMTP**: Configure SMTP (e.g., PHPMailer + SMTP credentials) to enable password reset emails.
4. **Role-based access**: `hasPermission()` is a stub — what roles/permissions are needed?

---

*Last updated: 2026-03-27*
