# 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=` (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.__q`, 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.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.