182 lines
12 KiB
Markdown
182 lines
12 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## What this is
|
|
|
|
**SoftSPM** — a legacy PHP web application for monitoring and reporting on mine-dewatering
|
|
infrastructure (wells, pumps, engines, piezometers, reservoirs) for PGE Bełchatów/Szczerców
|
|
lignite mines. It ingests live readings from the **SYNDIS** SCADA system over SOAP and presents
|
|
dashboards, history, and printable reports. The domain language throughout the code (variables,
|
|
comments, UI labels) is **Polish**.
|
|
|
|
There is **no build system, no package manager, no test suite, and no dependency manifest**.
|
|
Third-party libraries are vendored as folders (`PHPExcel/`, `PHPWord/`, `fpdf/`, `tcpdf/`,
|
|
`pChart/`, `ckEditor/`, `libmail/`). Code is edited and served in place.
|
|
|
|
## Running / environment
|
|
|
|
- Served under **XAMPP** (Apache + MariaDB) from `htdocs/`. Hit `index.php` in a browser.
|
|
- Target runtime is **PHP 5.4.35 / MariaDB 10.6.23** and is treated as fixed. The code uses the
|
|
**removed `mysql_*` extension** (`mysql_connect`, `mysql_query`), so PHP lives at
|
|
`C:\Programy\xampp_old\php\php.exe`. The Eclipse facet claiming PHP 8.2
|
|
(`.settings/...facet.core.xml`) is misleading; do not assume PHP 5.5+ features
|
|
(no `password_hash`, no `finally`, no `::class`, no short-array-only shortcuts on old files).
|
|
- DB credentials live in `dbaccess.php`. Most scripts open their own connection with the
|
|
boilerplate at the top of `index.php`, `soap.php`, and `ajax_func.php`
|
|
(`mysql_connect` → `mysql_select_db` → `SET NAMES utf8`).
|
|
- Scheduled/cron jobs are run via Windows `.bat` files invoking the CLI, e.g.
|
|
`reports/rep_003_hours.bat` calls `php.exe -r "parse_str('dep=B',$_GET); include '...rep_003_hours.php';"`
|
|
once per department (`B`/`S`/`D`). `soap_update5min.php` is the SYNDIS poll meant to run every 5 minutes.
|
|
- **Parallel deployments:** `htdocs/` holds several copies of this app (`trzeciTestowy`,
|
|
`pierwszyPodstawowy`, `newPGE`, `wellsctrl*`, …). `reports/rep_003_hours.bat` in *this* tree
|
|
points its `SCRIPT_PATH` at `htdocs\trzeciTestowy\` — check hard-coded paths before assuming a
|
|
script here is the one actually running.
|
|
- **Not a git repository.** History lives in dated filename copies (`*_20180426.php`,
|
|
`naprawa_20210312.php`, `20170130_soap.php`) and in `sites/backup/`.
|
|
|
|
## Architecture
|
|
|
|
### Front controller + page fragments
|
|
`index.php` is the single entry point and template shell. It dispatches on the `pg` query param:
|
|
`?pg=siteNNN` simply `include`s `sites/siteNNN.php` (default `site401`). The top menu, auth gate,
|
|
and DB connection all live in `index.php`; the `sites/*.php` files are page bodies that assume an
|
|
open connection and an active session.
|
|
|
|
There are ~200 fragments in `sites/`. The naming is conventional, not arbitrary — a base
|
|
`siteNNN.php` has variants by suffix, e.g. for `site401`: `E`=edit, `inf`=info/detail panel,
|
|
`his`/`ahis`/`mhis`/`offhis`=various history views, `charts`, `det`/`pdet`=details, `mark`=map
|
|
marker view. When adding a feature, follow the existing suffix pattern for that site number.
|
|
|
|
### Auth & per-page access control (`usrLoginAndSiteAccess` in `func.php`)
|
|
Two login paths in `index.php`: a normal form post, and **SYNDIS single-sign-on** via
|
|
`?auth=<md5>` (matched against `md5(USR_USERNAME)`), which can deep-link to a specific object and
|
|
redirect to its page. After login, the user's accessible pages are loaded from
|
|
`sys_user_profiles → sys_profile_site → sys_site_access` and flattened into session flags:
|
|
`$_SESSION['siteNNN']` gates menu/page visibility and `$_SESSION['siteNNN_edit']` gates write
|
|
actions. **Pages and AJAX handlers must check these flags themselves** — the router does not.
|
|
`access_level` is currently hard-coded to `1` (BASIC tier).
|
|
|
|
### Shared library (`func.php`, ~3400 lines, ~140 functions)
|
|
The catch-all helper file `require`d almost everywhere. Key categories:
|
|
- **DB scalar/array wrappers:** `sqlValue()`, `sqlStringValue()`, `sqlStringArray()`,
|
|
`sqlGetResAsArray()` — prefer these over raw `mysql_query` for reads.
|
|
- **Dictionaries / dropdowns:** `selectFromDictionary()`, `setDicName()`, etc., backed by
|
|
`sys_dictionaries` (`DIC_TYPE` + `DIC_NAME`).
|
|
- **Domain math:** pump/well evaluation (`wellEvaluation4_*`), polynomial curve fits
|
|
(`getPolynomial*`, with the `polynomial/` folder), working-time and pumped-water aggregation
|
|
(`getGlobalWorkingTimeInDate`, `getGlobalPumpedWater*`, `liczCzasu`).
|
|
- **History access:** `getHistoricalDataFrom_*`, plus the two helpers that resolve which sharded
|
|
table a date lives in — see *Three databases and history sharding* below
|
|
(`getTableNameFrom_everyday_his`, `getTableNameFrom_resources_det`).
|
|
|
|
### AJAX (`ajax_func.php`, `ajax_getdata4well.php`)
|
|
POST endpoints dispatched by inspecting `$_POST['action']` / `$_POST['post_site']` /
|
|
`$_POST['post_action']`. This is where most dynamic-form writes happen (insert/update into
|
|
`rep_his_data_everyday`, `seg_dev_resources_det`, etc.).
|
|
|
|
### SOAP — two distinct roles
|
|
1. **Client of SYNDIS** (pulling SCADA data *in*): `soap.php` defines `class SyndisConnector`
|
|
wrapping a `SoapClient` against `SyndisSpm.wsdl`. The many top-level `soap_*.php` scripts are
|
|
one-off or scheduled importers/updaters (`soap_update5min.php`, `soap_importwells.php`,
|
|
`soap_importpiezo.php`, `soap_of_while*.php`, etc.). These set the SYNDIS endpoint IP/port near
|
|
the top — those are environment-specific and must be updated per deployment.
|
|
2. **SOAP server** (exposing data *out* to SYNDIS): the `webservice/` folder. `webservice.php`
|
|
boots a `PhpWsdl` server (`lib/`); **business logic goes in `webservice/class.soap.php`** and
|
|
types in `class.complextypes.php`. Fetch the WSDL at `webservice.php?WSDL`. The `cache/` folder
|
|
needs write permission. See `webservice/readme.txt`.
|
|
|
|
### Reporting & export
|
|
- `reports/` — report definitions `rep_NNN_{print,pdf,xls}.php` (HTML print, FPDF/TCPDF PDF, PHPExcel XLS).
|
|
- `export/` — per-page exports `siteNNN_{print,pdf,xls}.php`, including the large `site810_*`
|
|
equipment-order document generators.
|
|
- Charts via `pChart/` and `charts/`; generated PNGs are written per-user and cleaned by
|
|
`delChartFiles()` on login.
|
|
|
|
## Database
|
|
|
|
### Three databases and history sharding
|
|
The connection opens `softspm`, but queries reach two more by **hard-coded prefix** — ~240
|
|
occurrences of `softspm_rep.` and ~90 of `softspm_his.` across `func.php`, `sites/*`, `reports/*`.
|
|
All three must live on the same server; cross-database `JOIN`/`UNION` is normal here.
|
|
|
|
- `softspm` — live/operational tables (assets, dictionaries, users, current readings).
|
|
- `softspm_rep` — precomputed reporting tables (`rep_002_*` per Polish month abbreviation
|
|
`styc…grud`, `rep_003_data`, `rep_015_*`, `*_prev_years` comparatives, `syndis_quality_data`).
|
|
- `softspm_his` — archive: **quarterly shards** `rep_his_data_everyday_YYYY_qN` and
|
|
`seg_dev_resources_det_YYYY_qN`, plus per-year piezometer tables `rec_dev_resources_piezo_NN`.
|
|
|
|
`getTableNameFrom_everyday_his($date)` / `getTableNameFrom_resources_det($date)`
|
|
([func.php:3842](func.php#L3842), [func.php:3888](func.php#L3888)) encode the whole routing rule —
|
|
**always go through them instead of naming a history table literally**:
|
|
1. Date in the current year → base table (`rep_his_data_everyday` / `seg_dev_resources_det`), but
|
|
switches to the `_his` suffixed table once the date is **more than 180 days old**.
|
|
2. Otherwise → `softspm_his.<base>_<year>_q<quarter>`, with a quirk: a date in the **first 3 days
|
|
of a month** is rolled back into the *previous* month (so early-January reads hit the prior
|
|
year's Q4).
|
|
3. Anything before 2014 is clamped to the `2014_q1` shard.
|
|
|
|
### Schema reference
|
|
`db_structure/*.sql` holds structure-only dumps of all three databases
|
|
(`20260602_softspm_nodata.sql`, `20260604_softspm_rep_nd.sql`, `20260604_softspm_his_nd.sql`) —
|
|
grep these instead of guessing at columns. The main DB is ~122 tables, almost all **InnoDB**
|
|
(5 MyISAM), so transactions *are* available despite the code never using them.
|
|
|
|
### Naming conventions
|
|
Table/column prefixes encode the subsystem; columns are uppercase prefixed by an abbreviation of
|
|
the table:
|
|
- `seg_*` — assets & device data. `seg_wells` (`WLL_*`), `seg_pumps` (`PUP_*`),
|
|
`seg_engines` (`ENG_*`), `seg_reservoirs_piezometers` (`SRP_*`).
|
|
- `seg_dev_resources` (`DRS_*`) is the central device-instance table. **`DRS_DEV_TYPE` codes:**
|
|
`10`=pump, `11`=engine, `12`=well, `601`=piezometer; `DRS_DEV_ID` joins to the typed table above.
|
|
- `seg_dev_resources_det` (`DRD_*`) is the time-series readings table: `DRD_RESOURCES_DETAIL` is a
|
|
dictionary id selecting the measured quantity, `DRD_VALUE` the value, `DRD_AUDIT_DATE` the
|
|
timestamp. `_det_his` and the quarterly shards hold history.
|
|
- `sys_*` — system: `sys_users`, `sys_dictionaries`, `sys_setup_parameters`, access tables above.
|
|
- `rep_*` — denormalized reporting tables, some rebuilt via `TRUNCATE`+`INSERT…SELECT`
|
|
(see the `refresh_departments` action in `ajax_func.php`).
|
|
- `tst_*` — pump test results (`tst_tests`).
|
|
|
|
Passwords are `md5()` hashes (`USR_PASSWORD`). SYNDIS-imported rows are stamped
|
|
`DRD_AUDIT_USR='SYNDIS'`.
|
|
|
|
## Working in this codebase
|
|
|
|
- Match the surrounding style: queries are built by string concatenation inline; reads go through
|
|
the `func.php` SQL wrappers. There is no ORM and no prepared-statement layer.
|
|
- New UI pages = add `sites/siteNNN<suffix>.php`, wire a menu entry / `$_SESSION['siteNNN']` gate
|
|
in `index.php`, and (if it writes) a handler branch in `ajax_func.php`.
|
|
- UI strings come from `language/{polish,english,german}.php` as `$lab_*` variables, keyed by site
|
|
number (e.g. `$lab_SubMenu_401`). `polish.php` is the source of truth and is roughly twice the
|
|
size of the other two — expect missing keys in `english.php`/`german.php`.
|
|
- `error.log` and `custom_log` at the repo root are runtime logs, not source.
|
|
|
|
### Dead code — don't trust every grep hit
|
|
A large share of the tree is superseded copies. Before editing a file a search turned up, confirm
|
|
it is the live one:
|
|
- `sites/backup/` (23 files) — old page versions, still full of matching code.
|
|
- Date-stamped duplicates at the root: `20161228_soap.php`, `20170130_soap.php`,
|
|
`20210926_soap.php`, `28557a_soap.php`, plus `20170130_SyndisSpm.wsdl` next to the live
|
|
`SyndisSpm.wsdl`.
|
|
- One-shot repair/diagnostic scripts: `naprawa_20210312.php`, `repair_20210313_data.php`,
|
|
`rollback.php`, `test.php`, `test_dat.php`, `clean20Off.php`, `dell_bledne_wylaczenia.php`.
|
|
- Five parallel variants of the same importer: `soap_of_while{,_back,_last,_manual,_new}.php`.
|
|
- Per-month report clones in `reports/` (e.g. `rep_002_wrze_print.php`).
|
|
|
|
## Known hazards
|
|
|
|
`AUDYT.md` (Polish) is a technical audit of this code with a prioritized P0/P1/P2 remediation
|
|
plan — **read it before proposing any refactor**, and don't re-derive its findings. The headline
|
|
items, which you will hit while editing:
|
|
|
|
- The `func.php` SQL wrappers do **no escaping**; `mysql_real_escape_string` appears nowhere in
|
|
application code. `getFloatValue()`/`getFullFloatValue()` are the only (partial, numeric-only)
|
|
guard. New code touching `$_GET`/`$_POST` should at minimum cast ids to `(int)`.
|
|
- [index.php:342](index.php#L342) does `include './sites/'.$_GET['pg'].'.php'` unvalidated (LFI).
|
|
- SYNDIS SSO tokens are plain `md5(USR_USERNAME)` and the SSO path logs in with the password
|
|
pattern `username.".123"` ([index.php:86-102](index.php#L86-L102)). Passwords are unsalted MD5.
|
|
- `dbaccess.php` is inside the webroot and contains credentials plus commented-out production hosts.
|
|
- Bulk rebuilds use `TRUNCATE` + `INSERT…SELECT` with no transaction (e.g. the
|
|
`refresh_departments` action at [ajax_func.php:17](ajax_func.php#L17)) — an error mid-way leaves
|
|
the table empty.
|