Interactive map application for drawing, categorizing, and managing building polygons with Leaflet and Leaflet.Draw. PHP + PDO backend, vanilla JS ES-module frontend.
Status: early / pre-1.0. Not production-ready. Several critical security issues are documented in Roadmap and must be addressed before any public deployment.
Note on the name. "Polygon" here refers to geospatial polygons drawn on a map — not the Polygon (MATIC) blockchain. There is no crypto or Web3 code in this project.
WordPress integration. The repo is named
wordpress-polygon-pluginand useswp_-prefixed tables, but the current code is a standalone PHP app. It does not register hooks, admin pages, shortcodes, or REST routes via WordPress APIs. True WordPress integration is a planned item in the roadmap.
- Draw polygons on an interactive Leaflet map (Esri World Imagery tiles by default).
- Save polygons as "buildings" with a name and category.
- Create, edit, and delete color-coded categories.
- Collapsible per-category legend; click a legend entry to fly to the building.
- GeoJSON stored as JSON strings in MySQL; decoded server-side before sending to the client.
- Backend: PHP (PDO / MySQL), a small hand-rolled router (
core/router.php), no framework, no Composer. - Frontend: Vanilla JavaScript ES modules, Leaflet 1.9, Leaflet.Draw 1.0, Tailwind CSS via CDN.
- Dev dependency (declared, unused): Vite 7 — listed in
public/package.jsonbut not currently wired up (novite.config.js,index.htmlloadsmain.jsdirectly).
- PHP 7.4+ with the
pdo_mysqlextension. - MySQL or MariaDB.
- A modern browser with ES module and
<dialog>support. - Node.js 18+ (only needed if you want to install the
leaflet/leaflet-drawnpm packages locally — the shippedindex.htmlloads them from a CDN).
-
Clone the repo into your web root.
git clone <repo-url> wordpress-polygon-plugin
By default the router strips
api-map/from incoming paths (seecore/router.php:21), so the recommended deploy location is something likehttp://localhost/api-map/. If you deploy elsewhere, update that line. -
Create the database schema. There is no migration file in the repo. The two tables are inferred from the handlers (
handler/building-handler.php,handler/categories-handler.php):CREATE TABLE wp_building_category ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, color VARCHAR(32) NOT NULL ); CREATE TABLE wp_buildings ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, geometry JSON NOT NULL, category_id INT NOT NULL, FOREIGN KEY (category_id) REFERENCES wp_building_category(id) );
-
Configure the database connection. Edit
core/config.phpand set$HOST,$DB,$DB_USERNAME,$DB_PASSWORD. The shipped defaults userootwith no password — do not use these outside of local development. -
Point the frontend at the API. Edit
public/assets/js/config.jsand setapi.baseUrlto whereverindex.phpis served. The default ishttp://localhost/api-map. -
Serve it. Any server that can run PHP works. Minimal example:
php -S localhost:8000
Then open
http://localhost:8000/public/index.html. -
(Optional) Install npm dependencies. The UI works without this step because it loads Leaflet from a CDN. If you want local copies:
cd public npm install
All responses are JSON. The router is defined in index.php:21-43.
| Method | Path | Handler | Body |
|---|---|---|---|
| GET | /buildings |
BuildingHandler::getAll |
— |
| POST | /buildings |
BuildingHandler::create |
{ "name": string, "category_id": int, "geometry": GeoJSON-string } |
| DELETE | /buildings/:id |
BuildingHandler::delete |
— |
| GET | /categories |
CategoryHandler::getAll |
— |
| POST | /categories |
CategoryHandler::create |
{ "name": string, "color": "#rrggbb" } |
| PUT | /categories/:id |
CategoryHandler::update |
{ "name": string, "color": "#rrggbb" } |
| DELETE | /categories/:id |
CategoryHandler::delete |
— |
GET /buildings returns categories with their buildings nested:
{
"success": true,
"data": [
{
"id": 1,
"name": "Warehouses",
"color": "#ff6600",
"buildings": [
{ "id": 7, "name": "Aria", "geometry": { "type": "Feature", "...": "..." }, "category_id": 1 }
]
}
]
}Known API gap: the frontend's Building.update() method at public/assets/js/modules/building.js:38 sends PUT /buildings/:id, but no such route is registered in index.php. The update path is effectively dead code — see Roadmap.
.
├── index.php # API entry point: CORS headers, router wiring
├── core/
│ ├── config.php # Hardcoded DB credentials (see Roadmap)
│ └── router.php # Regex-based request router
├── handler/
│ ├── building-handler.php # CRUD for wp_buildings
│ └── categories-handler.php # CRUD for wp_building_category
├── public/
│ ├── index.html # UI shell: header, map, modals, legend
│ ├── package.json # Leaflet + Leaflet.Draw + Vite (dev)
│ └── assets/
│ ├── css/styles.css
│ └── js/
│ ├── main.js # MapApplication orchestrator
│ ├── config.js # API base URL + map defaults
│ ├── services.js # Thin fetch wrapper (ApiService)
│ └── modules/
│ ├── map.js # Leaflet init, rendering, draw events
│ ├── building.js # Buildings API client
│ ├── building-category.js # Categories API client
│ └── ui.js # Modals, forms, toasts, legend DOM
├── LICENSE
├── CONTRIBUTING.md
└── README.md
Grouped by category, roughly in priority order. Line references are against the current main branch.
- Hardcoded DB credentials in
core/config.php:3-6default torootwith no password. Move to environment variables or a gitignored config file. - Wildcard CORS at
index.php:7(Access-Control-Allow-Origin: *) combined with no authentication means any website can create, edit, or delete data against this API. - No authentication or authorization on any endpoint in
index.php. All routes are world-writable. - DOM XSS: user-supplied
building.nameis interpolated into an HTML template atpublic/assets/js/modules/map.js:86;category.coloris inlined into astyleattribute atpublic/assets/js/modules/map.js:157andpublic/assets/js/modules/ui.js:175. Escape or usetextContent/ validated color values. - Inline
onclickatpublic/assets/js/modules/map.js:89relies on a globalwindow.deleteBuildingregistered inmain.js:215. Replace with event delegation and a CSP-friendly handler. - No CSRF protection on state-changing routes.
- Dead PUT route for buildings.
public/assets/js/modules/building.js:38-54callsPUT /buildings/:id, but no such route exists inindex.php. Either register the route and add a handler, or deleteBuilding.update(). - Form validators never called.
UI.validateBuildingForm()andUI.validateCategoryForm()atpublic/assets/js/modules/ui.js:205-235are defined but no call site exists inmain.js. Today the HTMLrequiredattributes do the work; either wire the validators in or remove them. UI.setupKeyboardShortcuts()atpublic/assets/js/modules/ui.js:253-262is never invoked — Escape does not close modals.- Debug
console.logleftovers atpublic/assets/js/main.js:155-156. - Silent failure path:
Building.getAll()atpublic/assets/js/modules/building.js:10-16returns[]on error with no user-visible signal.
- N+1 query in
BuildingHandler::getAll()athandler/building-handler.php:14-19: one categories query, then one buildings query per category. Replace with a singleJOINand group in PHP. - No database indexes documented.
wp_buildings.category_idshould be indexed. - No response caching / ETag on
GETendpoints.
<dialog>elements inpublic/index.html:47,:111,:166have noaria-labelledbyand no focus trap.- Legend uses color-only swatches alongside text; check contrast ratios for user-picked colors.
The repo name implies a WordPress plugin, but there is no plugin header, no hooks, no register_rest_route, no current_user_can checks, no text domain, no readme.txt. Either:
- Rename / rebrand as a standalone map app (simpler), or
- Rewrite the backend against WordPress APIs: register a plugin header in
index.php, move routes toregister_rest_route, usewpdbinstead of a raw PDO connection, add nonces and capability checks, and add areadme.txtin WordPress.org format.
- Vite is declared but unused. Either delete the devDependency from
public/package.jsonor add a real build pipeline. .gitignoreis minimal. Currently ignores only.htaccessand**/node_modules; should also cover.env,.DS_Store,.vscode/,.idea/,*.log.- No tests, no linter, no CI.
See CONTRIBUTING.md for setup, coding style, and PR conventions. The Roadmap section above is a good place to find self-contained first issues.
MIT © 2026 Altaf Syahrastani. See LICENSE.