12 KiB
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/. Hitindex.phpin 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 atC:\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 (nopassword_hash, nofinally, 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 ofindex.php,soap.php, andajax_func.php(mysql_connect→mysql_select_db→SET NAMES utf8). - Scheduled/cron jobs are run via Windows
.batfiles invoking the CLI, e.g.reports/rep_003_hours.batcallsphp.exe -r "parse_str('dep=B',$_GET); include '...rep_003_hours.php';"once per department (B/S/D).soap_update5min.phpis 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.batin this tree points itsSCRIPT_PATHathtdocs\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 insites/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 includes 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 required almost everywhere. Key categories:
- DB scalar/array wrappers:
sqlValue(),sqlStringValue(),sqlStringArray(),sqlGetResAsArray()— prefer these over rawmysql_queryfor reads. - Dictionaries / dropdowns:
selectFromDictionary(),setDicName(), etc., backed bysys_dictionaries(DIC_TYPE+DIC_NAME). - Domain math: pump/well evaluation (
wellEvaluation4_*), polynomial curve fits (getPolynomial*, with thepolynomial/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
- Client of SYNDIS (pulling SCADA data in):
soap.phpdefinesclass SyndisConnectorwrapping aSoapClientagainstSyndisSpm.wsdl. The many top-levelsoap_*.phpscripts 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. - SOAP server (exposing data out to SYNDIS): the
webservice/folder.webservice.phpboots aPhpWsdlserver (lib/); business logic goes inwebservice/class.soap.phpand types inclass.complextypes.php. Fetch the WSDL atwebservice.php?WSDL. Thecache/folder needs write permission. Seewebservice/readme.txt.
Reporting & export
reports/— report definitionsrep_NNN_{print,pdf,xls}.php(HTML print, FPDF/TCPDF PDF, PHPExcel XLS).export/— per-page exportssiteNNN_{print,pdf,xls}.php, including the largesite810_*equipment-order document generators.- Charts via
pChart/andcharts/; generated PNGs are written per-user and cleaned bydelChartFiles()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 abbreviationstyc…grud,rep_003_data,rep_015_*,*_prev_yearscomparatives,syndis_quality_data).softspm_his— archive: quarterly shardsrep_his_data_everyday_YYYY_qNandseg_dev_resources_det_YYYY_qN, plus per-year piezometer tablesrec_dev_resources_piezo_NN.
getTableNameFrom_everyday_his($date) / getTableNameFrom_resources_det($date)
(func.php:3842, func.php:3888) encode the whole routing rule —
always go through them instead of naming a history table literally:
- Date in the current year → base table (
rep_his_data_everyday/seg_dev_resources_det), but switches to the_hissuffixed table once the date is more than 180 days old. - 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). - Anything before 2014 is clamped to the
2014_q1shard.
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_TYPEcodes:10=pump,11=engine,12=well,601=piezometer;DRS_DEV_IDjoins to the typed table above.seg_dev_resources_det(DRD_*) is the time-series readings table:DRD_RESOURCES_DETAILis a dictionary id selecting the measured quantity,DRD_VALUEthe value,DRD_AUDIT_DATEthe timestamp._det_hisand the quarterly shards hold history.sys_*— system:sys_users,sys_dictionaries,sys_setup_parameters, access tables above.rep_*— denormalized reporting tables, some rebuilt viaTRUNCATE+INSERT…SELECT(see therefresh_departmentsaction inajax_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.phpSQL 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 inindex.php, and (if it writes) a handler branch inajax_func.php. - UI strings come from
language/{polish,english,german}.phpas$lab_*variables, keyed by site number (e.g.$lab_SubMenu_401).polish.phpis the source of truth and is roughly twice the size of the other two — expect missing keys inenglish.php/german.php. error.logandcustom_logat 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, plus20170130_SyndisSpm.wsdlnext to the liveSyndisSpm.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.phpSQL wrappers do no escaping;mysql_real_escape_stringappears nowhere in application code.getFloatValue()/getFullFloatValue()are the only (partial, numeric-only) guard. New code touching$_GET/$_POSTshould at minimum cast ids to(int). - index.php:342 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 patternusername.".123"(index.php:86-102). Passwords are unsalted MD5. dbaccess.phpis inside the webroot and contains credentials plus commented-out production hosts.- Bulk rebuilds use
TRUNCATE+INSERT…SELECTwith no transaction (e.g. therefresh_departmentsaction at ajax_func.php:17) — an error mid-way leaves the table empty.