feat: v1.1 competitive parity — proximity, directions, geocoding, CSV import #55

Merged
jmiller merged 4 commits from feature/v1.1-parity into main 2026-06-23 17:26:46 +00:00
Owner

Summary

Closes the four critical feature gaps identified in competitive analysis (#54). All competitors (Ideal, JSP, Hotspots, Novacore) have these features — MokoSuite now matches them while remaining free/GPL and API-key-free.

1. Haversine Proximity Search

  • LocationsModel filters by great-circle distance using the Haversine formula
  • populateState() captures lat/lng/radius/radius_unit from search module form
  • Results sorted by distance ASC when proximity filter active
  • Hidden radius_unit field bridges module params to component model

2. Get Directions

  • Google Maps ?api=1 link on location detail page — no API key needed
  • Directions link in every Leaflet map popup marker
  • Works cross-platform: opens native app on mobile, web on desktop

3. Auto-Geocoding (Nominatim/OSM)

  • LocationModel::save() override detects empty coordinates with address present
  • Calls Nominatim API to auto-populate lat/lng
  • Success/failure messages via Joomla message queue
  • Respects rate limits (only fires on individual admin saves)

4. CSV Import

  • ImportController + ImportModel + Import view with upload form
  • Auto-detects column headers (title/name/store, address/street, city, lat, etc.)
  • Per-row validation via LocationTable::bind()->check()->store()
  • Configurable delimiter (comma, semicolon, pipe, tab)
  • Toolbar button + submenu item for quick access

Files Changed (15)

  • 4 new files (ImportController, ImportModel, Import HtmlView, import template)
  • 11 modified files (models, templates, language, manifest, changelog, readme)

Test Plan

  • Search with radius: select radius + use geolocation → results filtered by distance
  • Search without radius: default ordering preserved
  • Detail page: "Get Directions" button visible when coordinates exist
  • Map popup: directions link appears below phone
  • New location with address, no coords: save → coords auto-populated
  • New location with coords already set: save → no geocoding triggered
  • Geocoding API down: save succeeds with warning, coords remain empty
  • CSV import: upload file with title,address,city,state,lat,lng columns → locations created
  • CSV import: missing title → row skipped with error message
  • CSV import: CSRF token validated
## Summary Closes the four critical feature gaps identified in competitive analysis (#54). All competitors (Ideal, JSP, Hotspots, Novacore) have these features — MokoSuite now matches them while remaining free/GPL and API-key-free. ### 1. Haversine Proximity Search - `LocationsModel` filters by great-circle distance using the Haversine formula - `populateState()` captures lat/lng/radius/radius_unit from search module form - Results sorted by distance ASC when proximity filter active - Hidden `radius_unit` field bridges module params to component model ### 2. Get Directions - Google Maps `?api=1` link on location detail page — no API key needed - Directions link in every Leaflet map popup marker - Works cross-platform: opens native app on mobile, web on desktop ### 3. Auto-Geocoding (Nominatim/OSM) - `LocationModel::save()` override detects empty coordinates with address present - Calls Nominatim API to auto-populate lat/lng - Success/failure messages via Joomla message queue - Respects rate limits (only fires on individual admin saves) ### 4. CSV Import - `ImportController` + `ImportModel` + Import view with upload form - Auto-detects column headers (title/name/store, address/street, city, lat, etc.) - Per-row validation via `LocationTable::bind()->check()->store()` - Configurable delimiter (comma, semicolon, pipe, tab) - Toolbar button + submenu item for quick access ## Files Changed (15) - 4 new files (ImportController, ImportModel, Import HtmlView, import template) - 11 modified files (models, templates, language, manifest, changelog, readme) ## Test Plan - [ ] Search with radius: select radius + use geolocation → results filtered by distance - [ ] Search without radius: default ordering preserved - [ ] Detail page: "Get Directions" button visible when coordinates exist - [ ] Map popup: directions link appears below phone - [ ] New location with address, no coords: save → coords auto-populated - [ ] New location with coords already set: save → no geocoding triggered - [ ] Geocoding API down: save succeeds with warning, coords remain empty - [ ] CSV import: upload file with title,address,city,state,lat,lng columns → locations created - [ ] CSV import: missing title → row skipped with error message - [ ] CSV import: CSRF token validated
jmiller added 1 commit 2026-06-23 16:54:06 +00:00
feat: v1.1 competitive parity — proximity search, directions, geocoding, CSV import
Universal: Auto Version Bump / Version Bump (push) Successful in 9s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Generic: Repo Health / Access control (pull_request) Successful in 3s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Universal: PR Check / Secret Scan (pull_request) Successful in 7s
Universal: Build & Release / Promote to RC (pull_request) Failing after 15s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Joomla: Extension CI / Lint & Validate (pull_request) Successful in 38s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 43s
7ef9a23ef8
Haversine proximity search:
- LocationsModel filters by distance using Haversine formula
- populateState captures lat/lng/radius/radius_unit from search form
- Distance-sorted results when proximity filter is active
- Hidden radius_unit field added to search module form

Get Directions:
- Google Maps directions link on location detail page (no API key)
- Directions link in Leaflet popup markers

Auto-geocoding:
- LocationModel::save() override auto-geocodes empty coordinates
- Calls Nominatim/OSM API when address present but coords missing
- Success/failure messages via Joomla enqueueMessage

CSV Import:
- ImportController handles file upload with CSRF token check
- ImportModel parses CSV via SplFileObject with configurable delimiter
- Auto-detects column headers (title/name, address/street, city, etc.)
- Per-row validation via LocationTable::bind()->check()->store()
- Import view with upload form, delimiter picker, and column help
- Toolbar button and submenu item for import access

Addresses #54

Authored-by: Moko Consulting
jmiller added 1 commit 2026-06-23 17:07:12 +00:00
fix: code review fixes — security, 0-coord bug, BOM, ACL
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Joomla: Extension CI / Lint & Validate (pull_request) Successful in 9s
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 4s
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 52s
c8a3c58495
Security:
- ACL check (core.create) in ImportController before processing
- File extension validation (.csv/.txt only) on upload
- Website href restricted to http/https scheme (prevents javascript: XSS)

Bug fixes:
- Fix 0.0 coordinate rejection: use null checks instead of != 0.0
  (coordinates at equator/prime meridian are valid locations)
- Fix Haversine guard using !== null instead of PHP truthiness
- Fix geocoding result check: isset+is_numeric instead of !empty
- Strip UTF-8 BOM from first CSV header (fixes Excel-generated imports)
- Cap radius at 25000 to prevent unreasonable distance queries

Authored-by: Moko Consulting
jmiller added 1 commit 2026-06-23 17:23:12 +00:00
chore: remove legacy source/src/ directory (old MokoJoom naming)
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Generic: Repo Health / Access control (pull_request) Successful in 3s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 8s
Universal: PR Check / Secret Scan (pull_request) Successful in 8s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Universal: Auto Version Bump / Version Bump (push) Successful in 15s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 14s
Joomla: Extension CI / Lint & Validate (pull_request) Successful in 34s
ddb25fa99f
Delete 26 files from the pre-rename MokoJoomStoreLocator structure.
These were superseded by source/packages/ with MokoSuite naming.
No workflow or manifest references remain to the old paths.

Closes #52

Authored-by: Moko Consulting
jmiller added 1 commit 2026-06-23 17:26:22 +00:00
fix: review round 2 — geocode 0-coord, tel: sanitization, Factory ACL
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Failing after 1s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 3s
Universal: PR Check / Validate PR (pull_request) Failing after 4s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 5s
Joomla: Extension CI / Lint & Validate (pull_request) Successful in 7s
Universal: Auto Version Bump / Version Bump (push) Successful in 9s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 30s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 6s
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 17s
c56f3473b1
- Fix geocoding trigger: use isset+is_numeric instead of !empty for
  coordinate detection (same 0.0 bug pattern as Haversine fix)
- Sanitize tel: href to digits/+/-/() only (prevents URI injection)
- Use Factory::getApplication() for ACL check (consistent with codebase)

Authored-by: Moko Consulting
jmiller merged commit cc70e4abc2 into main 2026-06-23 17:26:46 +00:00
jmiller deleted branch feature/v1.1-parity 2026-06-23 17:26:47 +00:00
Sign in to join this conversation.