Files
kopalnia_2014/CLAUDE.md
T
2026-09-03 11:24:19 +02:00

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/. 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 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 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:3888) 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 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). 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) — an error mid-way leaves the table empty.