Website of a language school
This project consisted in rebuilding the website of a language school inside its existing WordPress, then in writing it an enrolment module with quote, emails and PDF, on shared hosting that offers FTP only.
- Role
- Sole author, on behalf of the school
- Period
- From June to September 2026
- Status
- In service. In service
- Technologies
- WordPress, PHP, MariaDB, Docker Compose, GlitchTip
- Repositories
- Two
Context
The school teaches languages online and in person. The work was done in two stages. A static site of four pages, accompanied by a brand specification, first settled the design; that design was then rebuilt in the school’s WordPress, as a child theme and blocks that its team can edit. An enrolment module was written last, with its price grids, its quote, its emails and its PDF.
The site is in service at iforla.com; the school’s data do not appear in this study, which keeps to the way of working.

Problem
The site’s hosting accepts FTP only: no remote access by keys, no staging environment. A delivery there is therefore an upload of files to a site in service, with no safety net. It also had to be possible to reproduce on the workstation the exact state of production, whereas WordPress keeps its addresses in the database in several forms.
Answer
A controlled upload
Each upload follows the same order: download the live version into a dated backup directory, send the files, download them again, then compare their hash with that of the local files. The site carries a single build number, raised at each delivery, displayed in the administration and recorded in a log with the scope of the delivery and its verification. The scripts that delete data do nothing without an explicit argument: run by default, they merely list what they would do.
A local stack realigned with production
The local stack is rebuilt from a backup of production. The script starts by backing up the local database, so that it is possible to go back, then rewrites the addresses in four passes, since the block editor keeps in its contents addresses whose slashes are escaped.
# The block editor stores slash-escaped URLs inside post_content JSON, so the
# plain pass alone leaves half of them behind. WP-CLI's "Replacements" column
# counts ROWS, not occurrences: a low number does not mean a pass was useless.
echo "==> 3/6 rewriting https://$PROD_HOST -> $SITE_URL (4 passes)"
for from in "https://$PROD_HOST" "http://$PROD_HOST"; do
wp search-replace "$from" "$SITE_URL" --all-tables --skip-columns=guid --report-changed-only
done
for from in "https:\\/\\/$PROD_HOST" "http:\\/\\/$PROD_HOST"; do
wp search-replace "$from" "http:\\/\\/localhost:8080" --all-tables --skip-columns=guid --report-changed-only
doneCode of my own where the plugin was not enough
The forms plugin in place did not display multi-step flows in its free version. The enrolment module replaced it: it is driven by a single schema file, has its own table, keeps the price of an enrolment when the grids change, and applies a retention period through a daily task, in addition to the export and the erasure that WordPress provides for personal data. Two gaps in the platform were filled in the same way, namely a hidden language that remained reachable by its address and the redirects from old addresses that WordPress refuses to pages.
Limits and outlook
FTP, which is the only channel the host offers, is not encrypted, and part of the operations is still typed by hand in the terminal it provides. Deliveries are made by scripts written for each change rather than by a single tool, and no staging environment precedes them.
The theme and the modules have no automated test: verification relies on check scripts run after the upload and on a list of points to go through in the browser. Finally, the old forms plugin and its validator stayed in place after the replacement, and the integration documentation did not keep up with all the language changes.


