Browse

Legacy rebuild · 2022

Replaced

Legacy CMS Modernization

  • PHP
  • GetSimple CMS
  • JSON regeneration
  • Admin workflows
  • Flat-file
record
projects/legacy-cms
status
replaced
type
legacy_extension
role
solo -- product + backend
constraint
no_database
stack
PHP, GetSimple CMS, JSON regeneration, Admin workflows, Flat-file
Seven-plus years of copy-pasted HTML fixture tables and manually updated <pre> tags were replaced with structured admin workflows and automatic page regeneration, built on top of the existing flat-file CMS without a database, a migration, or downtime.

"Rondom het Stroomkanaal" has been running a regional billiards competition for over a decade. The site had been live on GetSimple, a flat-file CMS, for seven-plus years before this work started. It worked, in the sense that it stayed online. But weekly operations were brittle.

The club secretary, who did not work with HTML, was responsible for updating the competition schedule and results every week. In practice that meant opening the CMS page editor, finding the right place in a wall of HTML, and copy-pasting new fixture rows into <pre> tags or hand-assembled HTML tables. Results for each round meant updating multiple pages manually. One wrong tag, one missed column, one paste in the wrong place, and the public page broke or showed wrong data.

This happened regularly. The errors were not catastrophic and no data was permanently lost, but they required a developer to diagnose and fix, which brought the same dependency problem back through the correction loop.

Replacing the CMS entirely was off the table. The site had ten years of operational history, a hosting setup that assumed flat files, and no appetite for a full migration. The constraint was explicit: make it safe to operate without touching the underlying system.


Four custom admin pages were added to the existing CMS, sitting alongside the standard GetSimple admin without replacing or modifying it. The CMS core is unchanged. The deployment model (FTP, flat files, no server-side tooling) is unchanged. What changed is how competition data moves through the system.

Competition data now lives in typed JSON files, not in page content. When the club secretary saves a round of results through the weekly entry form, two public pages are regenerated automatically from that JSON. The operator submits a form; the site updates. There is no HTML to edit, no table structure to maintain, no page to find in the CMS editor.

Season rollover accepts pasted Excel fixture data, validates team names against the active snapshot, shows a full preview, and on confirmation archives the previous season to a frozen JSON file and creates a new active one. What was previously a developer task, including manually copying pages, renaming files, and resetting data, became an operator action taking a few minutes.

Team management, archive browsing, and result entry all follow the same pattern: form in, structured data written, page content regenerated. The operator never touches the output. Every write is backed up before it happens.

The weekly results admin page showing a form for entering match scores per round. Dutch-language interface with team name fields, score inputs, and a submit button.
/admin/weekuitslagen.php is the weekly result entry form the club secretary uses. Submitting regenerates the public competition schedule and standings pages automatically.

Mechanism Behaviour
JSON as canonical layer Competition data such as fixtures, scores, and team snapshots lives in typed JSON files, not in page content. Public pages are generated from the JSON. Operators never edit HTML to update competition data.
Automatic page regeneration Saving a round of results regenerates the public competition schedule and results pages automatically. The operator saves a form; the site updates. No manual HTML required at any step.
Backup before write Before any page XML is overwritten, the previous version is copied to a .bak.xml file. Every result save is recoverable. Full-site ZIP snapshots are available on demand.
Team name validation Result entry validates submitted team names against a frozen snapshot stored in the active season JSON. Names not in the snapshot are rejected before writing. Typos that previously went unnoticed are now hard errors.
Season rollover isolation Season rollover accepts pasted Excel fixture data, validates team references, and shows a full preview before writing anything. The previous season is archived to a frozen JSON file before the new one is created.
Atomic file writes All JSON writes use file_put_contents with LOCK_EX. Concurrent write collisions cannot produce a half-written data file. All POST handlers are CSRF-protected with nonces.

Item Detail
Live since 2022 Ran for three years in this form; the site itself has over 10 years of operational history
~3,533 lines added Four custom admin pages (weekuitslagen.php, teams.php, seizoen.php, archief.php), with CMS core files unchanged
2 pages regenerated per save competitieprogramma.xml and uitslagen-stand.xml rebuilt automatically on every result entry, with no manual page editing
Zero SQL No database connection anywhere in the codebase. All persistence runs through file_put_contents with LOCK_EX and GetSimple XMLsave()
2 months to production Incremental development on a live site; no migration window, no downtime, FTP-deployed throughout

Three years of the extended CMS running in production surfaced the limits of the flat-file model clearly enough to make a full rebuild worth scoping. BiljartV2, a Laravel 12 rebuild of the same competition's platform, replaced this system with a typed import pipeline, a Filament admin, a relational data model, and a full audit trail. The constraint that shaped this project ("no database, no migration") was the reason the rebuild was eventually possible: by the time it happened, there was a working system to replace, not a broken one.


record: projects/legacy-cms | status: replaced | type: legacy_extension

Rebuilds, workflow systems, and the backend work that keeps running after launch.

LinkedIn GitHub © 2026 Gerben van der Velde