feat: complete store locator implementation (Phases 1-3) #53
@@ -114,6 +114,7 @@ build/
|
||||
dist/
|
||||
out/
|
||||
site/
|
||||
!source/packages/*/site/
|
||||
*.map
|
||||
*.css.map
|
||||
*.js.map
|
||||
|
||||
+38
-8
@@ -5,15 +5,45 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Removed
|
||||
- Removed deploy-manual.yml workflow — switching to Joomla update server method for extension distribution
|
||||
## [1.0.0] - 2026-06-23
|
||||
|
||||
### Added
|
||||
- Admin `LocationController` (FormController) for single-record save/cancel/apply
|
||||
- Admin `LocationsController` (AdminController) for bulk publish/unpublish/delete
|
||||
- Admin location edit view and tabbed template (Details, Address, Contact)
|
||||
- Admin locations list renders data rows with edit links and published toggle
|
||||
- `LocationTable::check()` validation: required title, auto-alias, lat/lng range, timestamps
|
||||
- `LocationsModel::populateState()` for filter persistence
|
||||
- Search filter across title, city, state, address
|
||||
- Published state filter and sort ordering support
|
||||
- Filter form XML (`filter_locations.xml`) with search tools bar
|
||||
- Language strings for filters, sort options, and save messages
|
||||
- Site frontend `DisplayController` routing to list and detail views
|
||||
- Site `LocationsModel` — published locations with search, city, and state filters
|
||||
- Site `LocationModel` — single location by ID (published only)
|
||||
- Site locations list view with Schema.org `LocalBusiness` markup and pagination
|
||||
- Site location detail view with address, contact, hours, and map placeholder
|
||||
- SEF URL router (`Service\Router`) with menu/standard/nomenu rules
|
||||
- Menu item types: "All Locations" list and "Location Detail" with location picker
|
||||
- Site language strings for frontend views and menu items
|
||||
- Router registered in service provider and component extension class
|
||||
- Map module dispatcher loads published locations with coordinates from DB
|
||||
- Leaflet.js/OpenStreetMap integration with markers, popups, and auto-fit bounds
|
||||
- Leaflet CSS/JS loaded via Joomla Web Asset Manager (`registerAndUseStyle`/`registerAndUseScript`)
|
||||
- Search module dispatcher loads distinct cities/states and builds radius options
|
||||
- City dropdown filter on search form (populated from DB, toggled by module param)
|
||||
- Radius dropdown filter with configurable distance values and unit (miles/km)
|
||||
- Geolocation "Use My Location" button with browser geolocation API
|
||||
- Hidden lat/lng fields passed to component for proximity search
|
||||
- Language strings for search module (city, radius, geolocation states)
|
||||
|
||||
### Removed
|
||||
- Makefile (no longer used)
|
||||
- deploy-manual.yml workflow
|
||||
|
||||
### Previous (scaffold)
|
||||
- Initial package scaffold with component, map module, and search module
|
||||
- Database schema for locations table with coordinates
|
||||
- Admin MVC for location CRUD
|
||||
- Frontend location listing view with Schema.org markup
|
||||
- Map module with Leaflet/Google Maps provider support
|
||||
- Search module with city and radius filter options
|
||||
- Admin MVC skeleton for location CRUD
|
||||
- Map module with Leaflet/Google Maps provider support (stub)
|
||||
- Search module with city and radius filter options (stub)
|
||||
|
||||
@@ -4,37 +4,29 @@ This file provides guidance to Claude Code when working with this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
**MokoJoomStoreLocator** -- A Joomla 4/5 package providing a store locator listing component with coordinating map and search modules.
|
||||
**MokoSuiteStoreLocator** -- A Joomla 5/6 package providing a store locator listing component with coordinating map and search modules.
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| **Platform** | joomla |
|
||||
| **Extension type** | package (component + modules) |
|
||||
| **Element** | `pkg_mokojoomstorelocator` |
|
||||
| **Element** | `pkg_mokosuitestorelocator` |
|
||||
| **Language** | PHP |
|
||||
| **Default branch** | main |
|
||||
| **License** | GPL-3.0-or-later |
|
||||
| **Wiki** | [MokoJoomStoreLocator Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoJoomStoreLocator/wiki) |
|
||||
| **Wiki** | [MokoSuiteStoreLocator Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteStoreLocator/wiki) |
|
||||
| **Standards** | [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home) |
|
||||
|
||||
## Package Contents
|
||||
|
||||
| Extension | Type | Element |
|
||||
|---|---|---|
|
||||
| Store Locator Component | component | `com_mokojoomstorelocator` |
|
||||
| Store Locator Map | module (site) | `mod_mokojoomstorelocator_map` |
|
||||
| Store Locator Search | module (site) | `mod_mokojoomstorelocator_search` |
|
||||
| Store Locator Component | component | `com_mokosuitestorelocator` |
|
||||
| Store Locator Map | module (site) | `mod_mokosuitestorelocator_map` |
|
||||
| Store Locator Search | module (site) | `mod_mokosuitestorelocator_search` |
|
||||
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
make build # Build package ZIP containing all sub-extensions
|
||||
make lint # Run PHP linter
|
||||
make validate # Lint + validation checks
|
||||
make release # Validate + build
|
||||
make clean # Clean build artifacts
|
||||
```
|
||||
|
||||
```bash
|
||||
composer install # Install PHP dev dependencies
|
||||
```
|
||||
@@ -43,19 +35,18 @@ composer install # Install PHP dev dependencies
|
||||
|
||||
This is a Joomla package. Key layout:
|
||||
|
||||
- `src/pkg_mokojoomstorelocator.xml` -- package manifest
|
||||
- `src/script.php` -- package install/upgrade/uninstall script
|
||||
- `src/packages/com_mokojoomstorelocator/` -- main component
|
||||
- `source/pkg_mokosuitestorelocator.xml` -- package manifest
|
||||
- `source/script.php` -- package install/upgrade/uninstall script
|
||||
- `source/packages/com_mokosuitestorelocator/` -- main component
|
||||
- `admin/` -- admin MVC (controllers, models, views, forms, tables, SQL)
|
||||
- `site/` -- frontend MVC (controllers, models, views, templates)
|
||||
- `mokojoomstorelocator.xml` -- component manifest
|
||||
- `src/packages/mod_mokojoomstorelocator_map/` -- map display module
|
||||
- `src/packages/mod_mokojoomstorelocator_search/` -- search/filter module
|
||||
- `updates.xml` -- Joomla update server manifest
|
||||
- `mokosuitestorelocator.xml` -- component manifest
|
||||
- `source/packages/mod_mokosuitestorelocator_map/` -- map display module
|
||||
- `source/packages/mod_mokosuitestorelocator_search/` -- search/filter module
|
||||
|
||||
## Database Table
|
||||
|
||||
`#__mokojoomstorelocator_locations` -- stores location data including coordinates, address, contact info, and business hours.
|
||||
`#__mokosuitestorelocator_locations` -- stores location data including coordinates, address, contact info, and business hours.
|
||||
|
||||
## Rules
|
||||
|
||||
@@ -66,6 +57,7 @@ This is a Joomla package. Key layout:
|
||||
- **Branch strategy**: develop on `dev/`, merge to `main` for release
|
||||
- **Wiki**: documentation lives in the Gitea wiki, not in `docs/` files
|
||||
- **Standards**: this repo follows [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)
|
||||
- **PHP minimum**: 8.1
|
||||
- **PHP minimum**: 8.2
|
||||
- **Joomla minimum**: 5.0
|
||||
- **Joomla table operations**: always use bind() -> check() -> store(), never save()
|
||||
- **Namespace**: `Moko\Component\MokoJoomStoreLocator` for the component
|
||||
- **Namespace**: `Moko\Component\MokoSuiteStoreLocator` for the component
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
# Makefile for Joomla Extensions
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# This is a reference Makefile for building Joomla extensions.
|
||||
# Copy this to your repository root as "Makefile" and customize as needed.
|
||||
#
|
||||
# Supports: Modules, Plugins, Components, Packages, Templates
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION - Customize these for your extension
|
||||
# ==============================================================================
|
||||
|
||||
# Extension Configuration
|
||||
EXTENSION_NAME := mokojoomstorelocator
|
||||
EXTENSION_TYPE := package
|
||||
# Options: module, plugin, component, package, template
|
||||
EXTENSION_VERSION := 1.0.0
|
||||
|
||||
# Module Configuration (for modules only)
|
||||
MODULE_TYPE := site
|
||||
# Options: site, admin
|
||||
|
||||
# Plugin Configuration (for plugins only)
|
||||
PLUGIN_GROUP := system
|
||||
# Options: system, content, user, authentication, etc.
|
||||
|
||||
# Directories
|
||||
SRC_DIR := .
|
||||
BUILD_DIR := build
|
||||
DIST_DIR := dist
|
||||
DOCS_DIR := docs
|
||||
|
||||
# Joomla Installation (for local testing - customize paths)
|
||||
JOOMLA_ROOT := /var/www/html/joomla
|
||||
JOOMLA_VERSION := 5
|
||||
|
||||
# Tools
|
||||
PHP := php
|
||||
COMPOSER := composer
|
||||
NPM := npm
|
||||
PHPCS := vendor/bin/phpcs
|
||||
PHPCBF := vendor/bin/phpcbf
|
||||
PHPUNIT := vendor/bin/phpunit
|
||||
ZIP := zip
|
||||
|
||||
# Coding Standards
|
||||
PHPCS_STANDARD := Joomla
|
||||
|
||||
# Colors for output
|
||||
COLOR_RESET := \033[0m
|
||||
COLOR_GREEN := \033[32m
|
||||
COLOR_YELLOW := \033[33m
|
||||
COLOR_BLUE := \033[34m
|
||||
COLOR_RED := \033[31m
|
||||
|
||||
# ==============================================================================
|
||||
# TARGETS
|
||||
# ==============================================================================
|
||||
|
||||
.PHONY: help
|
||||
help: ## Show this help message
|
||||
@echo "$(COLOR_BLUE)╔════════════════════════════════════════════════════════════╗$(COLOR_RESET)"
|
||||
@echo "$(COLOR_BLUE)║ Joomla Extension Makefile ║$(COLOR_RESET)"
|
||||
@echo "$(COLOR_BLUE)╚════════════════════════════════════════════════════════════╝$(COLOR_RESET)"
|
||||
@echo ""
|
||||
@echo "Extension: $(EXTENSION_NAME) ($(EXTENSION_TYPE)) v$(EXTENSION_VERSION)"
|
||||
@echo ""
|
||||
@echo "$(COLOR_GREEN)Available targets:$(COLOR_RESET)"
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(COLOR_BLUE)%-20s$(COLOR_RESET) %s\n", $$1, $$2}'
|
||||
@echo ""
|
||||
|
||||
.PHONY: lint
|
||||
lint: ## Run PHP linter (syntax check)
|
||||
@echo "$(COLOR_BLUE)Running PHP linter...$(COLOR_RESET)"
|
||||
@find . -name "*.php" ! -path "./vendor/*" ! -path "./node_modules/*" ! -path "./$(BUILD_DIR)/*" \
|
||||
-exec $(PHP) -l {} \; | grep -v "No syntax errors" || true
|
||||
@echo "$(COLOR_GREEN)✓ PHP linting complete$(COLOR_RESET)"
|
||||
|
||||
.PHONY: validate
|
||||
validate: lint ## Run all validation checks
|
||||
@echo "$(COLOR_GREEN)✓ All validation checks passed$(COLOR_RESET)"
|
||||
|
||||
.PHONY: clean
|
||||
clean: ## Clean build artifacts
|
||||
@echo "$(COLOR_BLUE)Cleaning build artifacts...$(COLOR_RESET)"
|
||||
@rm -rf $(BUILD_DIR) $(DIST_DIR)
|
||||
@echo "$(COLOR_GREEN)✓ Build artifacts cleaned$(COLOR_RESET)"
|
||||
|
||||
MOKO_PLATFORM ?= $(or $(wildcard ../moko-platform),$(wildcard $(HOME)/moko-platform),$(wildcard /opt/moko-platform))
|
||||
MINIFY_SCRIPT := $(MOKO_PLATFORM)/build/minify.js
|
||||
|
||||
.PHONY: minify
|
||||
minify: ## Minify CSS/JS assets
|
||||
@echo "Minifying assets..."
|
||||
@if [ -f "$(MINIFY_SCRIPT)" ]; then \
|
||||
node "$(MINIFY_SCRIPT)" $(SRC_DIR); \
|
||||
elif [ -f "scripts/minify.js" ]; then \
|
||||
node scripts/minify.js; \
|
||||
else \
|
||||
echo "No minify script found"; \
|
||||
fi
|
||||
|
||||
.PHONY: build
|
||||
build: clean validate ## Build package ZIP containing all sub-extensions
|
||||
@echo "$(COLOR_BLUE)Building Joomla package...$(COLOR_RESET)"
|
||||
@mkdir -p $(DIST_DIR) $(BUILD_DIR)/pkg_$(EXTENSION_NAME)
|
||||
@# Build each sub-extension into its own ZIP
|
||||
@for ext in src/packages/*/; do \
|
||||
EXT_NAME=$$(basename $$ext); \
|
||||
echo " Packaging $$EXT_NAME..."; \
|
||||
mkdir -p $(BUILD_DIR)/$$EXT_NAME; \
|
||||
rsync -a --exclude='.git*' "$$ext" "$(BUILD_DIR)/$$EXT_NAME/"; \
|
||||
cd $(BUILD_DIR) && $(ZIP) -r "pkg_$(EXTENSION_NAME)/$$EXT_NAME.zip" "$$EXT_NAME" && cd ..; \
|
||||
done
|
||||
@# Copy the package manifest
|
||||
@cp src/pkg_mokojoomstorelocator.xml $(BUILD_DIR)/pkg_$(EXTENSION_NAME)/
|
||||
@if [ -f "src/script.php" ]; then cp src/script.php $(BUILD_DIR)/pkg_$(EXTENSION_NAME)/; fi
|
||||
@# Create the final package ZIP
|
||||
@cd $(BUILD_DIR) && $(ZIP) -r "../$(DIST_DIR)/pkg_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip" "pkg_$(EXTENSION_NAME)"
|
||||
@echo "$(COLOR_GREEN)✓ Package created: $(DIST_DIR)/pkg_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
|
||||
|
||||
.PHONY: release
|
||||
release: validate build ## Create a release (validate + build)
|
||||
@echo "$(COLOR_GREEN)✓ Release package ready$(COLOR_RESET)"
|
||||
|
||||
.PHONY: version
|
||||
version: ## Display version information
|
||||
@echo "$(COLOR_BLUE)Extension Information:$(COLOR_RESET)"
|
||||
@echo " Name: $(EXTENSION_NAME)"
|
||||
@echo " Type: $(EXTENSION_TYPE)"
|
||||
@echo " Version: $(EXTENSION_VERSION)"
|
||||
|
||||
# Default target
|
||||
.DEFAULT_GOAL := help
|
||||
@@ -1,34 +1,54 @@
|
||||
# MokoJoomStoreLocator
|
||||
# MokoSuiteStoreLocator
|
||||
|
||||
A Joomla 4/5 package providing a store locator listing component with coordinating map and search modules.
|
||||
|
||||
## Package Contents
|
||||
|
||||
| Extension | Description |
|
||||
|---|---|
|
||||
| `com_mokojoomstorelocator` | Component for managing store locations (admin CRUD + frontend listing) |
|
||||
| `mod_mokojoomstorelocator_map` | Site module displaying an interactive map with location markers |
|
||||
| `mod_mokojoomstorelocator_search` | Site module providing search/filter form for finding locations |
|
||||
| Extension | Type | Element |
|
||||
|---|---|---|
|
||||
| Store Locator Component | component | `com_mokosuitestorelocator` |
|
||||
| Store Locator Map | module (site) | `mod_mokosuitestorelocator_map` |
|
||||
| Store Locator Search | module (site) | `mod_mokosuitestorelocator_search` |
|
||||
|
||||
## Requirements
|
||||
|
||||
- Joomla 4.4+ or 5.x
|
||||
- PHP 8.1+
|
||||
- MySQL 5.7+ / MariaDB 10.3+
|
||||
- Joomla 5.x or 6.x
|
||||
- PHP 8.2+
|
||||
- MySQL 8.0+ / MariaDB 10.4+
|
||||
|
||||
## Installation
|
||||
|
||||
1. Download the latest `pkg_mokojoomstorelocator-x.x.x.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoJoomStoreLocator/releases)
|
||||
1. Download the latest `pkg_mokosuitestorelocator-x.x.x.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteStoreLocator/releases)
|
||||
2. In Joomla Administrator, go to **System > Install > Extensions**
|
||||
3. Upload the package ZIP — all extensions install automatically
|
||||
|
||||
## Features
|
||||
|
||||
- Manage store locations with address, coordinates, contact info, and business hours
|
||||
- Interactive map display (OpenStreetMap/Leaflet or Google Maps)
|
||||
- Location search by city, postcode, or radius
|
||||
- Schema.org LocalBusiness structured data markup
|
||||
- Category support for grouping locations
|
||||
### Implemented
|
||||
- **Admin CRUD** — full location management with tabbed edit form (details, address, coordinates, contact, image)
|
||||
- **Admin list** — searchable, filterable, sortable locations list with bulk publish/unpublish/delete
|
||||
- **Site frontend** — locations list and detail views with pagination
|
||||
- **Schema.org** — LocalBusiness structured data markup on all frontend templates
|
||||
- **SEF URLs** — router with menu, standard, and nomenu rules
|
||||
- **Menu items** — "All Locations" list and single "Location Detail" picker
|
||||
- **Interactive map** — Leaflet.js with OpenStreetMap tiles, markers with popups, auto-fit bounds
|
||||
- **Location search** — city dropdown, radius filter, and browser geolocation ("Use My Location")
|
||||
|
||||
### Planned
|
||||
- Proximity search (Haversine distance filtering)
|
||||
- Marker clustering for dense location areas
|
||||
- Multi-category support with custom map markers
|
||||
- ACL permissions and SQL upgrade schema
|
||||
- REST API via Joomla Web Services plugin
|
||||
- MokoSuiteShop integration for multi-store ecommerce
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
composer install # Install PHP dev dependencies
|
||||
```
|
||||
|
||||
Source code lives in `source/packages/` — one directory per sub-extension.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<form>
|
||||
<fields name="filter">
|
||||
<field
|
||||
name="search"
|
||||
type="text"
|
||||
label="COM_MOKOJOOMSTORELOCATOR_FILTER_SEARCH_LABEL"
|
||||
hint="JSEARCH_FILTER"
|
||||
inputmode="search"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="published"
|
||||
type="status"
|
||||
label="JOPTION_SELECT_PUBLISHED"
|
||||
onchange="this.form.submit();"
|
||||
>
|
||||
<option value="">JOPTION_SELECT_PUBLISHED</option>
|
||||
</field>
|
||||
</fields>
|
||||
|
||||
<fields name="list">
|
||||
<field
|
||||
name="fullordering"
|
||||
type="list"
|
||||
label="JGLOBAL_SORT_BY"
|
||||
default="a.title ASC"
|
||||
onchange="this.form.submit();"
|
||||
>
|
||||
<option value="a.title ASC">JGLOBAL_TITLE_ASC</option>
|
||||
<option value="a.title DESC">JGLOBAL_TITLE_DESC</option>
|
||||
<option value="a.city ASC">COM_MOKOJOOMSTORELOCATOR_CITY_ASC</option>
|
||||
<option value="a.city DESC">COM_MOKOJOOMSTORELOCATOR_CITY_DESC</option>
|
||||
<option value="a.published ASC">JSTATUS_ASC</option>
|
||||
<option value="a.published DESC">JSTATUS_DESC</option>
|
||||
<option value="a.id ASC">JGRID_HEADING_ID_ASC</option>
|
||||
<option value="a.id DESC">JGRID_HEADING_ID_DESC</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="limit"
|
||||
type="limitbox"
|
||||
label="JGLOBAL_LIST_LIMIT"
|
||||
default="25"
|
||||
onchange="this.form.submit();"
|
||||
/>
|
||||
</fields>
|
||||
</form>
|
||||
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Location edit form -->
|
||||
<form>
|
||||
<fieldset name="details" addfieldprefix="Moko\Component\MokoSuiteStoreLocator\Administrator\Field">
|
||||
<field
|
||||
name="id"
|
||||
type="hidden"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="title"
|
||||
type="text"
|
||||
label="JGLOBAL_TITLE"
|
||||
required="true"
|
||||
size="40"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="alias"
|
||||
type="text"
|
||||
label="JFIELD_ALIAS_LABEL"
|
||||
size="40"
|
||||
hint="JFIELD_ALIAS_PLACEHOLDER"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="description"
|
||||
type="editor"
|
||||
label="JGLOBAL_DESCRIPTION"
|
||||
filter="safehtml"
|
||||
buttons="true"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="published"
|
||||
type="list"
|
||||
label="JSTATUS"
|
||||
default="1"
|
||||
>
|
||||
<option value="1">JPUBLISHED</option>
|
||||
<option value="0">JUNPUBLISHED</option>
|
||||
<option value="-2">JTRASHED</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
|
||||
<fieldset name="address" label="COM_MOKOJOOMSTORELOCATOR_FIELDSET_ADDRESS">
|
||||
<field
|
||||
name="address"
|
||||
type="text"
|
||||
label="COM_MOKOJOOMSTORELOCATOR_FIELD_ADDRESS"
|
||||
size="60"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="city"
|
||||
type="text"
|
||||
label="COM_MOKOJOOMSTORELOCATOR_FIELD_CITY"
|
||||
size="40"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="state"
|
||||
type="text"
|
||||
label="COM_MOKOJOOMSTORELOCATOR_FIELD_STATE"
|
||||
size="40"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="postcode"
|
||||
type="text"
|
||||
label="COM_MOKOJOOMSTORELOCATOR_FIELD_POSTCODE"
|
||||
size="20"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="country"
|
||||
type="text"
|
||||
label="COM_MOKOJOOMSTORELOCATOR_FIELD_COUNTRY"
|
||||
size="40"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset name="coordinates" label="COM_MOKOJOOMSTORELOCATOR_FIELDSET_COORDINATES">
|
||||
<field
|
||||
name="latitude"
|
||||
type="number"
|
||||
label="COM_MOKOJOOMSTORELOCATOR_FIELD_LATITUDE"
|
||||
step="0.00000001"
|
||||
min="-90"
|
||||
max="90"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="longitude"
|
||||
type="number"
|
||||
label="COM_MOKOJOOMSTORELOCATOR_FIELD_LONGITUDE"
|
||||
step="0.00000001"
|
||||
min="-180"
|
||||
max="180"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset name="contact" label="COM_MOKOJOOMSTORELOCATOR_FIELDSET_CONTACT">
|
||||
<field
|
||||
name="phone"
|
||||
type="tel"
|
||||
label="COM_MOKOJOOMSTORELOCATOR_FIELD_PHONE"
|
||||
size="30"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="email"
|
||||
type="email"
|
||||
label="JGLOBAL_EMAIL"
|
||||
size="40"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="website"
|
||||
type="url"
|
||||
label="COM_MOKOJOOMSTORELOCATOR_FIELD_WEBSITE"
|
||||
size="60"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="hours"
|
||||
type="textarea"
|
||||
label="COM_MOKOJOOMSTORELOCATOR_FIELD_HOURS"
|
||||
rows="5"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset name="image" label="COM_MOKOJOOMSTORELOCATOR_FIELDSET_IMAGE">
|
||||
<field
|
||||
name="image"
|
||||
type="media"
|
||||
label="COM_MOKOJOOMSTORELOCATOR_FIELD_IMAGE"
|
||||
/>
|
||||
</fieldset>
|
||||
</form>
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
; MokoSuiteStoreLocator - Admin language strings
|
||||
; Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
; License: GNU General Public License version 3 or later; see LICENSE
|
||||
|
||||
COM_MOKOJOOMSTORELOCATOR="Store Locator"
|
||||
COM_MOKOJOOMSTORELOCATOR_DESC="A store locator component for managing and displaying location listings."
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATIONS="Locations"
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATION_NEW="New Location"
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATION_EDIT="Edit Location"
|
||||
COM_MOKOJOOMSTORELOCATOR_TABLE_CAPTION="Store Location List"
|
||||
|
||||
COM_MOKOJOOMSTORELOCATOR_CITY="City"
|
||||
COM_MOKOJOOMSTORELOCATOR_STATE="State"
|
||||
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELDSET_ADDRESS="Address"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELDSET_COORDINATES="Coordinates"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELDSET_CONTACT="Contact Information"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELDSET_IMAGE="Image"
|
||||
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_ADDRESS="Street Address"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_CITY="City"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_STATE="State / Province"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_POSTCODE="Postal Code"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_COUNTRY="Country"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_LATITUDE="Latitude"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_LONGITUDE="Longitude"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_PHONE="Phone"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_WEBSITE="Website"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_HOURS="Business Hours"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_IMAGE="Location Image"
|
||||
|
||||
COM_MOKOJOOMSTORELOCATOR_FILTER_SEARCH_LABEL="Search Locations"
|
||||
COM_MOKOJOOMSTORELOCATOR_CITY_ASC="City ascending"
|
||||
COM_MOKOJOOMSTORELOCATOR_CITY_DESC="City descending"
|
||||
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATION_SAVE_SUCCESS="Location saved successfully."
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATIONS_N_ITEMS_PUBLISHED="%d location(s) published."
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATIONS_N_ITEMS_UNPUBLISHED="%d location(s) unpublished."
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATIONS_N_ITEMS_DELETED="%d location(s) deleted."
|
||||
|
||||
COM_MOKOJOOMSTORELOCATOR_ERROR_TITLE_REQUIRED="A location title is required."
|
||||
COM_MOKOJOOMSTORELOCATOR_ERROR_LATITUDE_RANGE="Latitude must be between -90 and 90."
|
||||
COM_MOKOJOOMSTORELOCATOR_ERROR_LONGITUDE_RANGE="Longitude must be between -180 and 180."
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
; MokoSuiteStoreLocator - System language strings
|
||||
; Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
; License: GNU General Public License version 3 or later; see LICENSE
|
||||
|
||||
COM_MOKOJOOMSTORELOCATOR="Store Locator"
|
||||
COM_MOKOJOOMSTORELOCATOR_DESC="A store locator component for managing and displaying location listings."
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATIONS="Locations"
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Component\Router\RouterFactoryInterface;
|
||||
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
|
||||
use Joomla\CMS\Extension\ComponentInterface;
|
||||
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
|
||||
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
|
||||
use Joomla\CMS\Extension\Service\Provider\RouterFactory;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Moko\Component\MokoSuiteStoreLocator\Administrator\Extension\MokoSuiteStoreLocatorComponent;
|
||||
|
||||
/**
|
||||
* The store locator service provider.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
return new class implements ServiceProviderInterface
|
||||
{
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function register(Container $container): void
|
||||
{
|
||||
$container->registerServiceProvider(new MVCFactory('\\Moko\\Component\\MokoSuiteStoreLocator'));
|
||||
$container->registerServiceProvider(new ComponentDispatcherFactory('\\Moko\\Component\\MokoSuiteStoreLocator'));
|
||||
$container->registerServiceProvider(new RouterFactory('\\Moko\\Component\\MokoSuiteStoreLocator'));
|
||||
|
||||
$container->set(
|
||||
ComponentInterface::class,
|
||||
function (Container $container) {
|
||||
$component = new MokoSuiteStoreLocatorComponent(
|
||||
$container->get(ComponentDispatcherFactoryInterface::class)
|
||||
);
|
||||
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
|
||||
$component->setRouterFactory($container->get(RouterFactoryInterface::class));
|
||||
|
||||
return $component;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
-- =========================================================================
|
||||
-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
-- SPDX-License-Identifier: GPL-3.0-or-later
|
||||
--
|
||||
-- MokoSuiteStoreLocator - Store locations table
|
||||
-- =========================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `#__mokosuitestorelocator_locations` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(255) NOT NULL DEFAULT '',
|
||||
`alias` varchar(400) NOT NULL DEFAULT '',
|
||||
`description` text NOT NULL,
|
||||
`address` varchar(255) NOT NULL DEFAULT '',
|
||||
`city` varchar(100) NOT NULL DEFAULT '',
|
||||
`state` varchar(100) NOT NULL DEFAULT '',
|
||||
`postcode` varchar(20) NOT NULL DEFAULT '',
|
||||
`country` varchar(100) NOT NULL DEFAULT '',
|
||||
`latitude` decimal(10, 8) DEFAULT NULL,
|
||||
`longitude` decimal(11, 8) DEFAULT NULL,
|
||||
`phone` varchar(50) NOT NULL DEFAULT '',
|
||||
`email` varchar(255) NOT NULL DEFAULT '',
|
||||
`website` varchar(255) NOT NULL DEFAULT '',
|
||||
`hours` text NOT NULL,
|
||||
`image` varchar(255) NOT NULL DEFAULT '',
|
||||
`published` tinyint(4) NOT NULL DEFAULT 0,
|
||||
`ordering` int(11) NOT NULL DEFAULT 0,
|
||||
`catid` int(11) NOT NULL DEFAULT 0,
|
||||
`params` text NOT NULL,
|
||||
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`created_by` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`modified_by` int(10) unsigned NOT NULL DEFAULT 0,
|
||||
`checked_out` int(10) unsigned DEFAULT NULL,
|
||||
`checked_out_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_published` (`published`),
|
||||
KEY `idx_catid` (`catid`),
|
||||
KEY `idx_alias` (`alias`(191)),
|
||||
KEY `idx_coordinates` (`latitude`, `longitude`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- =========================================================================
|
||||
-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
-- SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-- =========================================================================
|
||||
|
||||
DROP TABLE IF EXISTS `#__mokosuitestorelocator_locations`;
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Administrator\Controller;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
|
||||
/**
|
||||
* Default controller for the admin side of the component.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class DisplayController extends BaseController
|
||||
{
|
||||
/**
|
||||
* The default view.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $default_view = 'locations';
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Administrator\Controller;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\Controller\FormController;
|
||||
|
||||
/**
|
||||
* Controller for a single location form.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class LocationController extends FormController
|
||||
{
|
||||
/**
|
||||
* The prefix to use with controller messages.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $text_prefix = 'COM_MOKOJOOMSTORELOCATOR_LOCATION';
|
||||
|
||||
/**
|
||||
* The view list to redirect to after save.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $view_list = 'locations';
|
||||
|
||||
/**
|
||||
* The view item for edit.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $view_item = 'location';
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Administrator\Controller;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\Controller\AdminController;
|
||||
|
||||
/**
|
||||
* Locations list controller — handles bulk publish/unpublish/delete.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class LocationsController extends AdminController
|
||||
{
|
||||
/**
|
||||
* The prefix to use with controller messages.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $text_prefix = 'COM_MOKOJOOMSTORELOCATOR_LOCATIONS';
|
||||
|
||||
/**
|
||||
* Proxy for getModel.
|
||||
*
|
||||
* @param string $name The model name.
|
||||
* @param string $prefix The model prefix.
|
||||
* @param array $config Configuration array for model.
|
||||
*
|
||||
* @return \Joomla\CMS\MVC\Model\BaseDatabaseModel
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function getModel($name = 'Location', $prefix = 'Administrator', $config = ['ignore_request' => true])
|
||||
{
|
||||
return parent::getModel($name, $prefix, $config);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Administrator\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Component\Router\RouterServiceInterface;
|
||||
use Joomla\CMS\Component\Router\RouterServiceTrait;
|
||||
use Joomla\CMS\Extension\MVCComponent;
|
||||
|
||||
/**
|
||||
* Component class for com_mokosuitestorelocator.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class MokoSuiteStoreLocatorComponent extends MVCComponent implements RouterServiceInterface
|
||||
{
|
||||
use RouterServiceTrait;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Administrator\Model;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\Model\AdminModel;
|
||||
use Joomla\CMS\Form\Form;
|
||||
use Joomla\CMS\Table\Table;
|
||||
|
||||
/**
|
||||
* Single location edit model.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class LocationModel extends AdminModel
|
||||
{
|
||||
/**
|
||||
* The type alias for this content type.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $typeAlias = 'com_mokosuitestorelocator.location';
|
||||
|
||||
/**
|
||||
* Get the form for this model.
|
||||
*
|
||||
* @param array $data Data for the form.
|
||||
* @param boolean $loadData True if the form is to load its own data.
|
||||
*
|
||||
* @return Form|boolean A Form object on success, false on failure.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function getForm($data = [], $loadData = true)
|
||||
{
|
||||
$form = $this->loadForm(
|
||||
'com_mokosuitestorelocator.location',
|
||||
'location',
|
||||
['control' => 'jform', 'load_data' => $loadData]
|
||||
);
|
||||
|
||||
if (empty($form))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the data for the form.
|
||||
*
|
||||
* @return mixed The data for the form.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function loadFormData()
|
||||
{
|
||||
$data = $this->getItem();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table for this model.
|
||||
*
|
||||
* @param string $name The table name.
|
||||
* @param string $prefix The table prefix.
|
||||
* @param array $options Configuration array for the table.
|
||||
*
|
||||
* @return Table
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function getTable($name = 'Location', $prefix = 'Administrator', $options = [])
|
||||
{
|
||||
return parent::getTable($name, $prefix, $options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Administrator\Model;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\Model\ListModel;
|
||||
use Joomla\Database\QueryInterface;
|
||||
|
||||
/**
|
||||
* Locations list model for the admin view.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class LocationsModel extends ListModel
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $config An optional associative array of configuration settings.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct($config = [])
|
||||
{
|
||||
if (empty($config['filter_fields']))
|
||||
{
|
||||
$config['filter_fields'] = [
|
||||
'id', 'a.id',
|
||||
'title', 'a.title',
|
||||
'city', 'a.city',
|
||||
'state', 'a.state',
|
||||
'published', 'a.published',
|
||||
'ordering', 'a.ordering',
|
||||
];
|
||||
}
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the model state.
|
||||
*
|
||||
* @param string $ordering Default ordering column.
|
||||
* @param string $direction Default ordering direction.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function populateState($ordering = 'a.title', $direction = 'ASC')
|
||||
{
|
||||
$search = $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string');
|
||||
$this->setState('filter.search', $search);
|
||||
|
||||
$published = $this->getUserStateFromRequest($this->context . '.filter.published', 'filter_published', '', 'string');
|
||||
$this->setState('filter.published', $published);
|
||||
|
||||
parent::populateState($ordering, $direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an SQL query to load the list data.
|
||||
*
|
||||
* @return QueryInterface
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function getListQuery(): QueryInterface
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
$query->select('a.*')
|
||||
->from($db->quoteName('#__mokosuitestorelocator_locations', 'a'));
|
||||
|
||||
// Filter by published state
|
||||
$published = $this->getState('filter.published');
|
||||
|
||||
if (is_numeric($published))
|
||||
{
|
||||
$query->where($db->quoteName('a.published') . ' = :published')
|
||||
->bind(':published', $published, \Joomla\Database\ParameterType::INTEGER);
|
||||
}
|
||||
elseif ($published === '')
|
||||
{
|
||||
$query->where($db->quoteName('a.published') . ' IN (0, 1)');
|
||||
}
|
||||
|
||||
// Search filter
|
||||
$search = $this->getState('filter.search');
|
||||
|
||||
if (!empty($search))
|
||||
{
|
||||
$search = '%' . trim($search) . '%';
|
||||
$query->where(
|
||||
'(' . $db->quoteName('a.title') . ' LIKE :search'
|
||||
. ' OR ' . $db->quoteName('a.city') . ' LIKE :search2'
|
||||
. ' OR ' . $db->quoteName('a.state') . ' LIKE :search3'
|
||||
. ' OR ' . $db->quoteName('a.address') . ' LIKE :search4)'
|
||||
)
|
||||
->bind(':search', $search)
|
||||
->bind(':search2', $search)
|
||||
->bind(':search3', $search)
|
||||
->bind(':search4', $search);
|
||||
}
|
||||
|
||||
// Ordering
|
||||
$orderCol = $this->state->get('list.ordering', 'a.title');
|
||||
$orderDir = $this->state->get('list.direction', 'ASC');
|
||||
$query->order($db->escape($orderCol) . ' ' . $db->escape($orderDir));
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Administrator\Table;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Filter\OutputFilter;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Table\Table;
|
||||
use Joomla\Database\DatabaseDriver;
|
||||
|
||||
/**
|
||||
* Location table class.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class LocationTable extends Table
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DatabaseDriver $db Database driver object.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct(DatabaseDriver $db)
|
||||
{
|
||||
parent::__construct('#__mokosuitestorelocator_locations', 'id', $db);
|
||||
|
||||
$this->setColumnAlias('published', 'published');
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloaded check method to ensure data integrity.
|
||||
*
|
||||
* @return boolean True if the data is valid.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function check(): bool
|
||||
{
|
||||
if (trim($this->title) === '')
|
||||
{
|
||||
$this->setError(Text::_('COM_MOKOJOOMSTORELOCATOR_ERROR_TITLE_REQUIRED'));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trim($this->alias) === '')
|
||||
{
|
||||
$this->alias = $this->title;
|
||||
}
|
||||
|
||||
$this->alias = OutputFilter::stringURLSafe($this->alias);
|
||||
|
||||
if ($this->latitude !== null && ($this->latitude < -90 || $this->latitude > 90))
|
||||
{
|
||||
$this->setError(Text::_('COM_MOKOJOOMSTORELOCATOR_ERROR_LATITUDE_RANGE'));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->longitude !== null && ($this->longitude < -180 || $this->longitude > 180))
|
||||
{
|
||||
$this->setError(Text::_('COM_MOKOJOOMSTORELOCATOR_ERROR_LONGITUDE_RANGE'));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = Factory::getDate()->toSql();
|
||||
$user = Factory::getApplication()->getIdentity();
|
||||
|
||||
if (!(int) $this->id)
|
||||
{
|
||||
if (!$this->created || $this->created === '0000-00-00 00:00:00')
|
||||
{
|
||||
$this->created = $now;
|
||||
}
|
||||
|
||||
if (!$this->created_by)
|
||||
{
|
||||
$this->created_by = $user->id;
|
||||
}
|
||||
}
|
||||
|
||||
$this->modified = $now;
|
||||
$this->modified_by = $user->id;
|
||||
|
||||
return parent::check();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Administrator\View\Location;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
/**
|
||||
* Location edit view.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* The form object.
|
||||
*
|
||||
* @var \Joomla\CMS\Form\Form
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $form;
|
||||
|
||||
/**
|
||||
* The item being edited.
|
||||
*
|
||||
* @var object
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $item;
|
||||
|
||||
/**
|
||||
* Display the view.
|
||||
*
|
||||
* @param string $tpl The template name.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
$this->form = $this->get('Form');
|
||||
$this->item = $this->get('Item');
|
||||
|
||||
$this->addToolbar();
|
||||
|
||||
parent::display($tpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the page title and toolbar.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function addToolbar(): void
|
||||
{
|
||||
Factory::getApplication()->input->set('hidemainmenu', true);
|
||||
|
||||
$isNew = ($this->item->id == 0);
|
||||
|
||||
ToolbarHelper::title(
|
||||
Text::_('COM_MOKOJOOMSTORELOCATOR_LOCATION_' . ($isNew ? 'NEW' : 'EDIT')),
|
||||
'location'
|
||||
);
|
||||
|
||||
ToolbarHelper::apply('location.apply');
|
||||
ToolbarHelper::save('location.save');
|
||||
ToolbarHelper::save2new('location.save2new');
|
||||
|
||||
if (!$isNew)
|
||||
{
|
||||
ToolbarHelper::save2copy('location.save2copy');
|
||||
}
|
||||
|
||||
ToolbarHelper::cancel('location.cancel', $isNew ? 'JTOOLBAR_CANCEL' : 'JTOOLBAR_CLOSE');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Administrator\View\Locations;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
/**
|
||||
* Locations list view for the admin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* @var \Joomla\CMS\Pagination\Pagination
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $pagination;
|
||||
|
||||
/**
|
||||
* @var \Joomla\Registry\Registry
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* @var \Joomla\CMS\Form\Form
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $filterForm;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public $activeFilters;
|
||||
|
||||
/**
|
||||
* Display the view.
|
||||
*
|
||||
* @param string $tpl The name of the template file to parse.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
$this->items = $this->get('Items');
|
||||
$this->pagination = $this->get('Pagination');
|
||||
$this->state = $this->get('State');
|
||||
$this->filterForm = $this->get('FilterForm');
|
||||
$this->activeFilters = $this->get('ActiveFilters');
|
||||
|
||||
$this->addToolbar();
|
||||
|
||||
parent::display($tpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the page title and toolbar.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function addToolbar(): void
|
||||
{
|
||||
ToolbarHelper::title(Text::_('COM_MOKOJOOMSTORELOCATOR_LOCATIONS'), 'location');
|
||||
ToolbarHelper::addNew('location.add');
|
||||
ToolbarHelper::publish('locations.publish', 'JTOOLBAR_PUBLISH', true);
|
||||
ToolbarHelper::unpublish('locations.unpublish', 'JTOOLBAR_UNPUBLISH', true);
|
||||
ToolbarHelper::deleteList('', 'locations.delete', 'JTOOLBAR_DELETE');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Layout\LayoutHelper;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
/** @var \Moko\Component\MokoSuiteStoreLocator\Administrator\View\Location\HtmlView $this */
|
||||
|
||||
HTMLHelper::_('behavior.formvalidator');
|
||||
HTMLHelper::_('behavior.keepalive');
|
||||
?>
|
||||
<form action="<?php echo Route::_('index.php?option=com_mokosuitestorelocator&layout=edit&id=' . (int) $this->item->id); ?>"
|
||||
method="post" name="adminForm" id="adminForm" class="form-validate">
|
||||
|
||||
<?php echo HTMLHelper::_('uitab.startTabSet', 'myTab', ['active' => 'details', 'recall' => true, 'breakpoint' => 768]); ?>
|
||||
|
||||
<?php echo HTMLHelper::_('uitab.addTab', 'myTab', 'details', Text::_('JDETAILS')); ?>
|
||||
<div class="row">
|
||||
<div class="col-lg-9">
|
||||
<?php echo $this->form->renderField('title'); ?>
|
||||
<?php echo $this->form->renderField('alias'); ?>
|
||||
<?php echo $this->form->renderField('description'); ?>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<?php echo $this->form->renderField('published'); ?>
|
||||
<?php echo $this->form->renderField('image'); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php echo HTMLHelper::_('uitab.endTab'); ?>
|
||||
|
||||
<?php echo HTMLHelper::_('uitab.addTab', 'myTab', 'address', Text::_('COM_MOKOJOOMSTORELOCATOR_FIELDSET_ADDRESS')); ?>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<?php echo $this->form->renderField('address'); ?>
|
||||
<?php echo $this->form->renderField('city'); ?>
|
||||
<?php echo $this->form->renderField('state'); ?>
|
||||
<?php echo $this->form->renderField('postcode'); ?>
|
||||
<?php echo $this->form->renderField('country'); ?>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<?php echo $this->form->renderField('latitude'); ?>
|
||||
<?php echo $this->form->renderField('longitude'); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php echo HTMLHelper::_('uitab.endTab'); ?>
|
||||
|
||||
<?php echo HTMLHelper::_('uitab.addTab', 'myTab', 'contact', Text::_('COM_MOKOJOOMSTORELOCATOR_FIELDSET_CONTACT')); ?>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<?php echo $this->form->renderField('phone'); ?>
|
||||
<?php echo $this->form->renderField('email'); ?>
|
||||
<?php echo $this->form->renderField('website'); ?>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<?php echo $this->form->renderField('hours'); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php echo HTMLHelper::_('uitab.endTab'); ?>
|
||||
|
||||
<?php echo HTMLHelper::_('uitab.endTabSet'); ?>
|
||||
|
||||
<input type="hidden" name="task" value="">
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
</form>
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Layout\LayoutHelper;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
/** @var \Moko\Component\MokoSuiteStoreLocator\Administrator\View\Locations\HtmlView $this */
|
||||
?>
|
||||
<form action="<?php echo Route::_('index.php?option=com_mokosuitestorelocator&view=locations'); ?>"
|
||||
method="post" name="adminForm" id="adminForm">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="j-main-container" class="j-main-container">
|
||||
<?php echo LayoutHelper::render('joomla.searchtools.default', ['view' => $this]); ?>
|
||||
|
||||
<?php if (empty($this->items)) : ?>
|
||||
<div class="alert alert-info">
|
||||
<span class="icon-info-circle" aria-hidden="true"></span>
|
||||
<?php echo Text::_('JGLOBAL_NO_MATCHING_RESULTS'); ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<table class="table" id="locationList">
|
||||
<caption class="visually-hidden">
|
||||
<?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_TABLE_CAPTION'); ?>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="w-1 text-center">
|
||||
<?php echo HTMLHelper::_('grid.checkall'); ?>
|
||||
</td>
|
||||
<th scope="col">
|
||||
<?php echo Text::_('JGLOBAL_TITLE'); ?>
|
||||
</th>
|
||||
<th scope="col" class="w-10 d-none d-md-table-cell">
|
||||
<?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_CITY'); ?>
|
||||
</th>
|
||||
<th scope="col" class="w-10 d-none d-md-table-cell">
|
||||
<?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_STATE'); ?>
|
||||
</th>
|
||||
<th scope="col" class="w-5 text-center">
|
||||
<?php echo Text::_('JSTATUS'); ?>
|
||||
</th>
|
||||
<th scope="col" class="w-5 text-center">
|
||||
<?php echo Text::_('JGRID_HEADING_ID'); ?>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($this->items as $i => $item) : ?>
|
||||
<tr class="row<?php echo $i % 2; ?>">
|
||||
<td class="w-1 text-center">
|
||||
<?php echo HTMLHelper::_('grid.id', $i, $item->id, false, 'cid', 'cb', $item->title); ?>
|
||||
</td>
|
||||
<th scope="row">
|
||||
<a href="<?php echo Route::_('index.php?option=com_mokosuitestorelocator&task=location.edit&id=' . (int) $item->id); ?>">
|
||||
<?php echo $this->escape($item->title); ?>
|
||||
</a>
|
||||
<?php if ($item->alias) : ?>
|
||||
<div class="small"><?php echo $this->escape($item->alias); ?></div>
|
||||
<?php endif; ?>
|
||||
</th>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<?php echo $this->escape($item->city); ?>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<?php echo $this->escape($item->state); ?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<?php echo HTMLHelper::_('jgrid.published', $item->published, $i, 'locations.', true, 'cb'); ?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<?php echo (int) $item->id; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php echo $this->pagination->getListFooter(); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<input type="hidden" name="task" value="">
|
||||
<input type="hidden" name="boxchecked" value="0">
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- =========================================================================
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
=========================================================================
|
||||
FILE INFORMATION
|
||||
DEFGROUP: MokoSuiteStoreLocator
|
||||
INGROUP: com_mokosuitestorelocator
|
||||
PATH: src/packages/com_mokosuitestorelocator/mokosuitestorelocator.xml
|
||||
VERSION: 01.00.00
|
||||
BRIEF: Component manifest for the store locator component
|
||||
=========================================================================
|
||||
-->
|
||||
<extension type="component" method="upgrade">
|
||||
<name>com_mokosuitestorelocator</name>
|
||||
<version>1.0.0</version>
|
||||
<creationDate>2026-06-23</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||
<description>COM_MOKOJOOMSTORELOCATOR_DESC</description>
|
||||
|
||||
<namespace path="src">Moko\Component\MokoSuiteStoreLocator</namespace>
|
||||
|
||||
<install>
|
||||
<sql>
|
||||
<file driver="mysql" charset="utf8">sql/install.mysql.sql</file>
|
||||
</sql>
|
||||
</install>
|
||||
|
||||
<uninstall>
|
||||
<sql>
|
||||
<file driver="mysql" charset="utf8">sql/uninstall.mysql.sql</file>
|
||||
</sql>
|
||||
</uninstall>
|
||||
|
||||
<files folder="site">
|
||||
<folder>language</folder>
|
||||
<folder>src</folder>
|
||||
<folder>tmpl</folder>
|
||||
</files>
|
||||
|
||||
<administration>
|
||||
<files folder="admin">
|
||||
<folder>forms</folder>
|
||||
<folder>language</folder>
|
||||
<folder>services</folder>
|
||||
<folder>sql</folder>
|
||||
<folder>src</folder>
|
||||
<folder>tmpl</folder>
|
||||
</files>
|
||||
|
||||
<menu>COM_MOKOJOOMSTORELOCATOR</menu>
|
||||
<submenu>
|
||||
<menu link="option=com_mokosuitestorelocator&view=locations">COM_MOKOJOOMSTORELOCATOR_LOCATIONS</menu>
|
||||
</submenu>
|
||||
</administration>
|
||||
</extension>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
; MokoSuiteStoreLocator - Site language strings
|
||||
; Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
; License: GNU General Public License version 3 or later; see LICENSE
|
||||
|
||||
COM_MOKOJOOMSTORELOCATOR="Store Locator"
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATIONS="Locations"
|
||||
COM_MOKOJOOMSTORELOCATOR_NO_LOCATIONS="No locations found."
|
||||
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELDSET_ADDRESS="Address"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELDSET_CONTACT="Contact Information"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_PHONE="Phone"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_WEBSITE="Website"
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_HOURS="Business Hours"
|
||||
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATIONS_VIEW_DEFAULT_TITLE="All Locations"
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATIONS_VIEW_DEFAULT_DESC="Displays a list of all store locations."
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATION_VIEW_DEFAULT_TITLE="Location Detail"
|
||||
COM_MOKOJOOMSTORELOCATOR_LOCATION_VIEW_DEFAULT_DESC="Displays a single store location with full details."
|
||||
COM_MOKOJOOMSTORELOCATOR_FIELD_LOCATION="Select Location"
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Site\Controller;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
|
||||
/**
|
||||
* Default site controller.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class DisplayController extends BaseController
|
||||
{
|
||||
/**
|
||||
* The default view.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $default_view = 'locations';
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Site\Model;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\Model\ItemModel;
|
||||
use Joomla\Database\ParameterType;
|
||||
|
||||
/**
|
||||
* Single location model for the site frontend.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class LocationModel extends ItemModel
|
||||
{
|
||||
/**
|
||||
* The location item.
|
||||
*
|
||||
* @var object|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $_item = null;
|
||||
|
||||
/**
|
||||
* Get a single location item.
|
||||
*
|
||||
* @param integer $pk The item primary key. If null, uses the model state.
|
||||
*
|
||||
* @return object|null The location object or null if not found.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function getItem($pk = null)
|
||||
{
|
||||
$pk = $pk ?: (int) $this->getState('location.id');
|
||||
|
||||
if ($this->_item === null)
|
||||
{
|
||||
$this->_item = [];
|
||||
}
|
||||
|
||||
if (!isset($this->_item[$pk]))
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
$query->select('a.*')
|
||||
->from($db->quoteName('#__mokosuitestorelocator_locations', 'a'))
|
||||
->where($db->quoteName('a.id') . ' = :pk')
|
||||
->where($db->quoteName('a.published') . ' = 1')
|
||||
->bind(':pk', $pk, ParameterType::INTEGER);
|
||||
|
||||
$db->setQuery($query);
|
||||
$this->_item[$pk] = $db->loadObject();
|
||||
}
|
||||
|
||||
return $this->_item[$pk] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the model state.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function populateState()
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
$id = $app->input->getInt('id', 0);
|
||||
$this->setState('location.id', $id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Site\Model;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\Model\ListModel;
|
||||
use Joomla\Database\ParameterType;
|
||||
use Joomla\Database\QueryInterface;
|
||||
|
||||
/**
|
||||
* Locations list model for the site frontend.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class LocationsModel extends ListModel
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $config Configuration settings.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct($config = [])
|
||||
{
|
||||
if (empty($config['filter_fields']))
|
||||
{
|
||||
$config['filter_fields'] = [
|
||||
'id', 'a.id',
|
||||
'title', 'a.title',
|
||||
'city', 'a.city',
|
||||
'state', 'a.state',
|
||||
'ordering', 'a.ordering',
|
||||
];
|
||||
}
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the model state.
|
||||
*
|
||||
* @param string $ordering Default ordering column.
|
||||
* @param string $direction Default ordering direction.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function populateState($ordering = 'a.ordering', $direction = 'ASC')
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
$search = $app->input->getString('search', '');
|
||||
$this->setState('filter.search', $search);
|
||||
|
||||
$city = $app->input->getString('city', '');
|
||||
$this->setState('filter.city', $city);
|
||||
|
||||
$state = $app->input->getString('state', '');
|
||||
$this->setState('filter.state', $state);
|
||||
|
||||
parent::populateState($ordering, $direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the query for the locations list.
|
||||
*
|
||||
* @return QueryInterface
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function getListQuery(): QueryInterface
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
$query->select('a.*')
|
||||
->from($db->quoteName('#__mokosuitestorelocator_locations', 'a'))
|
||||
->where($db->quoteName('a.published') . ' = 1');
|
||||
|
||||
// Search filter
|
||||
$search = $this->getState('filter.search');
|
||||
|
||||
if (!empty($search))
|
||||
{
|
||||
$search = '%' . trim($search) . '%';
|
||||
$query->where(
|
||||
'(' . $db->quoteName('a.title') . ' LIKE :search'
|
||||
. ' OR ' . $db->quoteName('a.city') . ' LIKE :search2'
|
||||
. ' OR ' . $db->quoteName('a.state') . ' LIKE :search3'
|
||||
. ' OR ' . $db->quoteName('a.address') . ' LIKE :search4)'
|
||||
)
|
||||
->bind(':search', $search)
|
||||
->bind(':search2', $search)
|
||||
->bind(':search3', $search)
|
||||
->bind(':search4', $search);
|
||||
}
|
||||
|
||||
// City filter
|
||||
$city = $this->getState('filter.city');
|
||||
|
||||
if (!empty($city))
|
||||
{
|
||||
$query->where($db->quoteName('a.city') . ' = :city')
|
||||
->bind(':city', $city);
|
||||
}
|
||||
|
||||
// State filter
|
||||
$state = $this->getState('filter.state');
|
||||
|
||||
if (!empty($state))
|
||||
{
|
||||
$query->where($db->quoteName('a.state') . ' = :state')
|
||||
->bind(':state', $state);
|
||||
}
|
||||
|
||||
// Ordering
|
||||
$orderCol = $this->state->get('list.ordering', 'a.ordering');
|
||||
$orderDir = $this->state->get('list.direction', 'ASC');
|
||||
$query->order($db->escape($orderCol) . ' ' . $db->escape($orderDir));
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Site\Service;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Application\SiteApplication;
|
||||
use Joomla\CMS\Component\Router\RouterView;
|
||||
use Joomla\CMS\Component\Router\RouterViewConfiguration;
|
||||
use Joomla\CMS\Component\Router\Rules\MenuRules;
|
||||
use Joomla\CMS\Component\Router\Rules\NomenuRules;
|
||||
use Joomla\CMS\Component\Router\Rules\StandardRules;
|
||||
use Joomla\CMS\Menu\AbstractMenu;
|
||||
|
||||
/**
|
||||
* SEF URL router for the store locator component.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Router extends RouterView
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param SiteApplication $app The application object.
|
||||
* @param AbstractMenu $menu The menu object.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct(SiteApplication $app, AbstractMenu $menu)
|
||||
{
|
||||
$locations = new RouterViewConfiguration('locations');
|
||||
$this->registerView($locations);
|
||||
|
||||
$location = new RouterViewConfiguration('location');
|
||||
$location->setKey('id')->setParent($locations);
|
||||
$this->registerView($location);
|
||||
|
||||
parent::__construct($app, $menu);
|
||||
|
||||
$this->attachRule(new MenuRules($this));
|
||||
$this->attachRule(new StandardRules($this));
|
||||
$this->attachRule(new NomenuRules($this));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Site\View\Location;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
|
||||
/**
|
||||
* Single location detail view for the site frontend.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* @var object|null
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $item;
|
||||
|
||||
/**
|
||||
* Display the view.
|
||||
*
|
||||
* @param string $tpl The template name.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
$this->item = $this->get('Item');
|
||||
|
||||
if ($this->item === null)
|
||||
{
|
||||
throw new \Exception('Location not found', 404);
|
||||
}
|
||||
|
||||
parent::display($tpl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoSuiteStoreLocator\Site\View\Locations;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
|
||||
/**
|
||||
* Locations list view for the site frontend.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* @var \Joomla\CMS\Pagination\Pagination
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $pagination;
|
||||
|
||||
/**
|
||||
* @var \Joomla\Registry\Registry
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Display the view.
|
||||
*
|
||||
* @param string $tpl The template name.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
$this->items = $this->get('Items');
|
||||
$this->pagination = $this->get('Pagination');
|
||||
$this->state = $this->get('State');
|
||||
|
||||
parent::display($tpl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
/** @var \Moko\Component\MokoSuiteStoreLocator\Site\View\Location\HtmlView $this */
|
||||
|
||||
$item = $this->item;
|
||||
?>
|
||||
<div class="com-mokosuitestorelocator-location" itemscope itemtype="https://schema.org/LocalBusiness">
|
||||
<h2 itemprop="name"><?php echo $this->escape($item->title); ?></h2>
|
||||
|
||||
<?php if ($item->image) : ?>
|
||||
<div class="com-mokosuitestorelocator-location__image">
|
||||
<img src="<?php echo $this->escape($item->image); ?>"
|
||||
alt="<?php echo $this->escape($item->title); ?>"
|
||||
itemprop="image">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item->description) : ?>
|
||||
<div class="com-mokosuitestorelocator-location__description" itemprop="description">
|
||||
<?php echo HTMLHelper::_('content.prepare', $item->description); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="com-mokosuitestorelocator-location__details">
|
||||
<div class="com-mokosuitestorelocator-location__address" itemprop="address" itemscope itemtype="https://schema.org/PostalAddress">
|
||||
<h3><?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_FIELDSET_ADDRESS'); ?></h3>
|
||||
<?php if ($item->address) : ?>
|
||||
<span itemprop="streetAddress"><?php echo $this->escape($item->address); ?></span><br>
|
||||
<?php endif; ?>
|
||||
<?php if ($item->city) : ?>
|
||||
<span itemprop="addressLocality"><?php echo $this->escape($item->city); ?></span>,
|
||||
<?php endif; ?>
|
||||
<?php if ($item->state) : ?>
|
||||
<span itemprop="addressRegion"><?php echo $this->escape($item->state); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($item->postcode) : ?>
|
||||
<span itemprop="postalCode"><?php echo $this->escape($item->postcode); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($item->country) : ?>
|
||||
<br><span itemprop="addressCountry"><?php echo $this->escape($item->country); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="com-mokosuitestorelocator-location__contact">
|
||||
<h3><?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_FIELDSET_CONTACT'); ?></h3>
|
||||
<?php if ($item->phone) : ?>
|
||||
<div>
|
||||
<strong><?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_FIELD_PHONE'); ?>:</strong>
|
||||
<a href="tel:<?php echo $this->escape($item->phone); ?>" itemprop="telephone">
|
||||
<?php echo $this->escape($item->phone); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item->email) : ?>
|
||||
<div>
|
||||
<strong><?php echo Text::_('JGLOBAL_EMAIL'); ?>:</strong>
|
||||
<a href="mailto:<?php echo $this->escape($item->email); ?>" itemprop="email">
|
||||
<?php echo $this->escape($item->email); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item->website) : ?>
|
||||
<div>
|
||||
<strong><?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_FIELD_WEBSITE'); ?>:</strong>
|
||||
<a href="<?php echo $this->escape($item->website); ?>" itemprop="url" target="_blank" rel="noopener">
|
||||
<?php echo $this->escape($item->website); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($item->hours) : ?>
|
||||
<div class="com-mokosuitestorelocator-location__hours">
|
||||
<h3><?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_FIELD_HOURS'); ?></h3>
|
||||
<div itemprop="openingHours">
|
||||
<?php echo nl2br($this->escape($item->hours)); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($item->latitude && $item->longitude) : ?>
|
||||
<meta itemprop="latitude" content="<?php echo $this->escape($item->latitude); ?>">
|
||||
<meta itemprop="longitude" content="<?php echo $this->escape($item->longitude); ?>">
|
||||
<div class="com-mokosuitestorelocator-location__map"
|
||||
data-lat="<?php echo $this->escape($item->latitude); ?>"
|
||||
data-lng="<?php echo $this->escape($item->longitude); ?>"
|
||||
data-title="<?php echo $this->escape($item->title); ?>"
|
||||
style="height: 300px;">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<metadata>
|
||||
<layout title="COM_MOKOJOOMSTORELOCATOR_LOCATION_VIEW_DEFAULT_TITLE"
|
||||
option="COM_MOKOJOOMSTORELOCATOR_LOCATION_VIEW_DEFAULT_DESC">
|
||||
<message>
|
||||
<![CDATA[COM_MOKOJOOMSTORELOCATOR_LOCATION_VIEW_DEFAULT_DESC]]>
|
||||
</message>
|
||||
</layout>
|
||||
<fields name="request">
|
||||
<fieldset name="request">
|
||||
<field
|
||||
name="id"
|
||||
type="sql"
|
||||
label="COM_MOKOJOOMSTORELOCATOR_FIELD_LOCATION"
|
||||
query="SELECT id, title FROM #__mokosuitestorelocator_locations WHERE published = 1 ORDER BY title"
|
||||
key_field="id"
|
||||
value_field="title"
|
||||
required="true"
|
||||
/>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</metadata>
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage com_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
/** @var \Moko\Component\MokoSuiteStoreLocator\Site\View\Locations\HtmlView $this */
|
||||
?>
|
||||
<div class="com-mokosuitestorelocator-locations">
|
||||
<h2><?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_LOCATIONS'); ?></h2>
|
||||
|
||||
<?php if (empty($this->items)) : ?>
|
||||
<p><?php echo Text::_('COM_MOKOJOOMSTORELOCATOR_NO_LOCATIONS'); ?></p>
|
||||
<?php else : ?>
|
||||
<div class="com-mokosuitestorelocator-locations__list">
|
||||
<?php foreach ($this->items as $item) : ?>
|
||||
<div class="com-mokosuitestorelocator-location-card" itemscope itemtype="https://schema.org/LocalBusiness">
|
||||
<?php if ($item->image) : ?>
|
||||
<div class="com-mokosuitestorelocator-location-card__image">
|
||||
<img src="<?php echo $this->escape($item->image); ?>"
|
||||
alt="<?php echo $this->escape($item->title); ?>"
|
||||
itemprop="image" loading="lazy">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="com-mokosuitestorelocator-location-card__body">
|
||||
<h3 itemprop="name">
|
||||
<a href="<?php echo Route::_('index.php?option=com_mokosuitestorelocator&view=location&id=' . (int) $item->id); ?>">
|
||||
<?php echo $this->escape($item->title); ?>
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<div itemprop="address" itemscope itemtype="https://schema.org/PostalAddress">
|
||||
<?php if ($item->address) : ?>
|
||||
<span itemprop="streetAddress"><?php echo $this->escape($item->address); ?></span><br>
|
||||
<?php endif; ?>
|
||||
<?php if ($item->city) : ?>
|
||||
<span itemprop="addressLocality"><?php echo $this->escape($item->city); ?></span>,
|
||||
<?php endif; ?>
|
||||
<?php if ($item->state) : ?>
|
||||
<span itemprop="addressRegion"><?php echo $this->escape($item->state); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($item->postcode) : ?>
|
||||
<span itemprop="postalCode"><?php echo $this->escape($item->postcode); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($item->phone) : ?>
|
||||
<div class="com-mokosuitestorelocator-location-card__phone">
|
||||
<a href="tel:<?php echo $this->escape($item->phone); ?>" itemprop="telephone">
|
||||
<?php echo $this->escape($item->phone); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php echo $this->pagination->getListFooter(); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<metadata>
|
||||
<layout title="COM_MOKOJOOMSTORELOCATOR_LOCATIONS_VIEW_DEFAULT_TITLE"
|
||||
option="COM_MOKOJOOMSTORELOCATOR_LOCATIONS_VIEW_DEFAULT_DESC">
|
||||
<message>
|
||||
<![CDATA[COM_MOKOJOOMSTORELOCATOR_LOCATIONS_VIEW_DEFAULT_DESC]]>
|
||||
</message>
|
||||
</layout>
|
||||
</metadata>
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
; MokoSuiteStoreLocator Map Module - Language strings
|
||||
; Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
; License: GNU General Public License version 3 or later; see LICENSE
|
||||
|
||||
MOD_MOKOJOOMSTORELOCATOR_MAP="Store Locator Map"
|
||||
MOD_MOKOJOOMSTORELOCATOR_MAP_DESC="Displays an interactive map with store location markers."
|
||||
MOD_MOKOJOOMSTORELOCATOR_MAP_HEIGHT="Map Height"
|
||||
MOD_MOKOJOOMSTORELOCATOR_MAP_ZOOM="Default Zoom Level"
|
||||
MOD_MOKOJOOMSTORELOCATOR_MAP_PROVIDER="Map Provider"
|
||||
MOD_MOKOJOOMSTORELOCATOR_MAP_API_KEY="API Key"
|
||||
MOD_MOKOJOOMSTORELOCATOR_MAP_API_KEY_DESC="Required for Google Maps. Not needed for OpenStreetMap."
|
||||
MOD_MOKOJOOMSTORELOCATOR_MAP_NOSCRIPT="JavaScript is required to display the map."
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
; MokoSuiteStoreLocator Map Module - System language strings
|
||||
; Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
; License: GNU General Public License version 3 or later; see LICENSE
|
||||
|
||||
MOD_MOKOJOOMSTORELOCATOR_MAP="Store Locator Map"
|
||||
MOD_MOKOJOOMSTORELOCATOR_MAP_DESC="Displays an interactive map with store location markers."
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- =========================================================================
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
=========================================================================
|
||||
FILE INFORMATION
|
||||
DEFGROUP: MokoSuiteStoreLocator
|
||||
INGROUP: mod_mokosuitestorelocator_map
|
||||
PATH: src/packages/mod_mokosuitestorelocator_map/mod_mokosuitestorelocator_map.xml
|
||||
VERSION: 01.00.00
|
||||
BRIEF: Module manifest for the store locator map module
|
||||
=========================================================================
|
||||
-->
|
||||
<extension type="module" client="site" method="upgrade">
|
||||
<name>mod_mokosuitestorelocator_map</name>
|
||||
<version>1.0.0</version>
|
||||
<creationDate>2026-06-23</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||
<description>MOD_MOKOJOOMSTORELOCATOR_MAP_DESC</description>
|
||||
|
||||
<namespace path="src">Moko\Module\MokoSuiteStoreLocatorMap</namespace>
|
||||
|
||||
<files>
|
||||
<folder>src</folder>
|
||||
<folder>tmpl</folder>
|
||||
<folder>language</folder>
|
||||
</files>
|
||||
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="map_height"
|
||||
type="text"
|
||||
label="MOD_MOKOJOOMSTORELOCATOR_MAP_HEIGHT"
|
||||
default="400px"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="map_zoom"
|
||||
type="number"
|
||||
label="MOD_MOKOJOOMSTORELOCATOR_MAP_ZOOM"
|
||||
default="10"
|
||||
min="1"
|
||||
max="20"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="map_provider"
|
||||
type="list"
|
||||
label="MOD_MOKOJOOMSTORELOCATOR_MAP_PROVIDER"
|
||||
default="leaflet"
|
||||
>
|
||||
<option value="leaflet">OpenStreetMap (Leaflet)</option>
|
||||
<option value="google">Google Maps</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="api_key"
|
||||
type="text"
|
||||
label="MOD_MOKOJOOMSTORELOCATOR_MAP_API_KEY"
|
||||
description="MOD_MOKOJOOMSTORELOCATOR_MAP_API_KEY_DESC"
|
||||
/>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage mod_mokosuitestorelocator_map
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Module\MokoSuiteStoreLocatorMap\Dispatcher;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Dispatcher\AbstractModuleDispatcher;
|
||||
use Joomla\CMS\Helper\HelperFactoryAwareInterface;
|
||||
use Joomla\CMS\Helper\HelperFactoryAwareTrait;
|
||||
use Joomla\Database\DatabaseAwareInterface;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* Dispatcher for mod_mokosuitestorelocator_map.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Dispatcher extends AbstractModuleDispatcher implements HelperFactoryAwareInterface, DatabaseAwareInterface
|
||||
{
|
||||
use HelperFactoryAwareTrait;
|
||||
use DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* Returns the layout data.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function getLayoutData(): array
|
||||
{
|
||||
$data = parent::getLayoutData();
|
||||
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
$query->select([
|
||||
$db->quoteName('id'),
|
||||
$db->quoteName('title'),
|
||||
$db->quoteName('address'),
|
||||
$db->quoteName('city'),
|
||||
$db->quoteName('state'),
|
||||
$db->quoteName('postcode'),
|
||||
$db->quoteName('phone'),
|
||||
$db->quoteName('latitude'),
|
||||
$db->quoteName('longitude'),
|
||||
])
|
||||
->from($db->quoteName('#__mokosuitestorelocator_locations'))
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
->where($db->quoteName('latitude') . ' IS NOT NULL')
|
||||
->where($db->quoteName('longitude') . ' IS NOT NULL');
|
||||
|
||||
$db->setQuery($query);
|
||||
$locations = $db->loadObjectList() ?: [];
|
||||
|
||||
$markers = [];
|
||||
|
||||
foreach ($locations as $loc)
|
||||
{
|
||||
$markers[] = [
|
||||
'id' => (int) $loc->id,
|
||||
'title' => $loc->title,
|
||||
'address' => trim($loc->address . ', ' . $loc->city . ', ' . $loc->state . ' ' . $loc->postcode, ', '),
|
||||
'phone' => $loc->phone,
|
||||
'lat' => (float) $loc->latitude,
|
||||
'lng' => (float) $loc->longitude,
|
||||
];
|
||||
}
|
||||
|
||||
$data['locations'] = $markers;
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage mod_mokosuitestorelocator_map
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
/** @var array $displayData */
|
||||
$params = $displayData['params'];
|
||||
$locations = $displayData['locations'] ?? [];
|
||||
$moduleId = $displayData['module']->id;
|
||||
$mapHeight = $params->get('map_height', '400px');
|
||||
$mapZoom = (int) $params->get('map_zoom', 10);
|
||||
$provider = $params->get('map_provider', 'leaflet');
|
||||
$apiKey = $params->get('api_key', '');
|
||||
|
||||
/** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||
$wa = $displayData['app']->getDocument()->getWebAssetManager();
|
||||
|
||||
if ($provider === 'leaflet')
|
||||
{
|
||||
$wa->registerAndUseStyle('leaflet', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', [], ['integrity' => 'sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=', 'crossorigin' => '']);
|
||||
$wa->registerAndUseScript('leaflet', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', [], ['integrity' => 'sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=', 'crossorigin' => '', 'defer' => true]);
|
||||
}
|
||||
?>
|
||||
<div class="mod-mokosuitestorelocator-map"
|
||||
id="mokosuitestorelocator-map-<?php echo (int) $moduleId; ?>"
|
||||
style="height: <?php echo $this->escape($mapHeight); ?>;"
|
||||
data-locations='<?php echo json_encode($locations, JSON_HEX_APOS | JSON_HEX_TAG); ?>'
|
||||
data-zoom="<?php echo $mapZoom; ?>"
|
||||
data-provider="<?php echo $this->escape($provider); ?>"
|
||||
<?php if ($apiKey) : ?>data-api-key="<?php echo $this->escape($apiKey); ?>"<?php endif; ?>>
|
||||
<noscript>
|
||||
<p><?php echo Text::_('MOD_MOKOJOOMSTORELOCATOR_MAP_NOSCRIPT'); ?></p>
|
||||
</noscript>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var el = document.getElementById('mokosuitestorelocator-map-<?php echo (int) $moduleId; ?>');
|
||||
if (!el || typeof L === 'undefined') return;
|
||||
|
||||
var locations = JSON.parse(el.getAttribute('data-locations') || '[]');
|
||||
var zoom = parseInt(el.getAttribute('data-zoom') || '10', 10);
|
||||
|
||||
var map = L.map(el.id);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
maxZoom: 19
|
||||
}).addTo(map);
|
||||
|
||||
if (locations.length === 0) {
|
||||
map.setView([39.8283, -98.5795], 4);
|
||||
return;
|
||||
}
|
||||
|
||||
var bounds = L.latLngBounds();
|
||||
|
||||
function esc(str) {
|
||||
var d = document.createElement('div');
|
||||
d.appendChild(document.createTextNode(str || ''));
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
locations.forEach(function(loc) {
|
||||
var marker = L.marker([loc.lat, loc.lng]).addTo(map);
|
||||
var popup = '<strong>' + esc(loc.title) + '</strong>';
|
||||
if (loc.address) popup += '<br>' + esc(loc.address);
|
||||
if (loc.phone) popup += '<br><a href="tel:' + esc(loc.phone) + '">' + esc(loc.phone) + '</a>';
|
||||
marker.bindPopup(popup);
|
||||
bounds.extend([loc.lat, loc.lng]);
|
||||
});
|
||||
|
||||
map.fitBounds(bounds, { padding: [30, 30], maxZoom: zoom });
|
||||
});
|
||||
</script>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
; MokoSuiteStoreLocator Search Module - Language strings
|
||||
; Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
; License: GNU General Public License version 3 or later; see LICENSE
|
||||
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH="Store Locator Search"
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_DESC="Provides a search/filter form for finding store locations."
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_LABEL="Find a Store"
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_PLACEHOLDER="Enter city, postcode, or address..."
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_SHOW_CITY="Show City Filter"
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_SHOW_RADIUS="Show Radius Filter"
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_RADIUS_UNIT="Distance Unit"
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_RADIUS_OPTIONS="Radius Options"
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_RADIUS_OPTIONS_DESC="Comma-separated list of radius values (e.g., 5,10,25,50,100)"
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_CITY="City"
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_ALL_CITIES="All Cities"
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_RADIUS="Distance"
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_ANY_DISTANCE="Any Distance"
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_USE_LOCATION="Use My Location"
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_LOCATING="Locating..."
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_LOCATION_SET="Location Set"
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
; MokoSuiteStoreLocator Search Module - System language strings
|
||||
; Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
; License: GNU General Public License version 3 or later; see LICENSE
|
||||
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH="Store Locator Search"
|
||||
MOD_MOKOJOOMSTORELOCATOR_SEARCH_DESC="Provides a search/filter form for finding store locations."
|
||||
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- =========================================================================
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
=========================================================================
|
||||
FILE INFORMATION
|
||||
DEFGROUP: MokoSuiteStoreLocator
|
||||
INGROUP: mod_mokosuitestorelocator_search
|
||||
PATH: src/packages/mod_mokosuitestorelocator_search/mod_mokosuitestorelocator_search.xml
|
||||
VERSION: 01.00.00
|
||||
BRIEF: Module manifest for the store locator search module
|
||||
=========================================================================
|
||||
-->
|
||||
<extension type="module" client="site" method="upgrade">
|
||||
<name>mod_mokosuitestorelocator_search</name>
|
||||
<version>1.0.0</version>
|
||||
<creationDate>2026-06-23</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||
<description>MOD_MOKOJOOMSTORELOCATOR_SEARCH_DESC</description>
|
||||
|
||||
<namespace path="src">Moko\Module\MokoSuiteStoreLocatorSearch</namespace>
|
||||
|
||||
<files>
|
||||
<folder>src</folder>
|
||||
<folder>tmpl</folder>
|
||||
<folder>language</folder>
|
||||
</files>
|
||||
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="show_city_filter"
|
||||
type="radio"
|
||||
label="MOD_MOKOJOOMSTORELOCATOR_SEARCH_SHOW_CITY"
|
||||
default="1"
|
||||
class="btn-group"
|
||||
>
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="show_radius_filter"
|
||||
type="radio"
|
||||
label="MOD_MOKOJOOMSTORELOCATOR_SEARCH_SHOW_RADIUS"
|
||||
default="1"
|
||||
class="btn-group"
|
||||
>
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="radius_unit"
|
||||
type="list"
|
||||
label="MOD_MOKOJOOMSTORELOCATOR_SEARCH_RADIUS_UNIT"
|
||||
default="miles"
|
||||
>
|
||||
<option value="miles">Miles</option>
|
||||
<option value="km">Kilometres</option>
|
||||
</field>
|
||||
|
||||
<field
|
||||
name="radius_options"
|
||||
type="text"
|
||||
label="MOD_MOKOJOOMSTORELOCATOR_SEARCH_RADIUS_OPTIONS"
|
||||
default="5,10,25,50,100"
|
||||
description="MOD_MOKOJOOMSTORELOCATOR_SEARCH_RADIUS_OPTIONS_DESC"
|
||||
/>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage mod_mokosuitestorelocator_search
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Module\MokoSuiteStoreLocatorSearch\Dispatcher;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Dispatcher\AbstractModuleDispatcher;
|
||||
use Joomla\CMS\Helper\HelperFactoryAwareInterface;
|
||||
use Joomla\CMS\Helper\HelperFactoryAwareTrait;
|
||||
use Joomla\Database\DatabaseAwareInterface;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* Dispatcher for mod_mokosuitestorelocator_search.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Dispatcher extends AbstractModuleDispatcher implements HelperFactoryAwareInterface, DatabaseAwareInterface
|
||||
{
|
||||
use HelperFactoryAwareTrait;
|
||||
use DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* Returns the layout data.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function getLayoutData(): array
|
||||
{
|
||||
$data = parent::getLayoutData();
|
||||
$params = $data['params'];
|
||||
|
||||
$db = $this->getDatabase();
|
||||
|
||||
// Load distinct cities
|
||||
$query = $db->getQuery(true)
|
||||
->select('DISTINCT ' . $db->quoteName('city'))
|
||||
->from($db->quoteName('#__mokosuitestorelocator_locations'))
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
->where($db->quoteName('city') . " != ''")
|
||||
->order($db->quoteName('city') . ' ASC');
|
||||
|
||||
$db->setQuery($query);
|
||||
$data['cities'] = $db->loadColumn() ?: [];
|
||||
|
||||
// Load distinct states
|
||||
$query = $db->getQuery(true)
|
||||
->select('DISTINCT ' . $db->quoteName('state'))
|
||||
->from($db->quoteName('#__mokosuitestorelocator_locations'))
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
->where($db->quoteName('state') . " != ''")
|
||||
->order($db->quoteName('state') . ' ASC');
|
||||
|
||||
$db->setQuery($query);
|
||||
$data['states'] = $db->loadColumn() ?: [];
|
||||
|
||||
// Build radius options from params
|
||||
$radiusStr = $params->get('radius_options', '5,10,25,50,100');
|
||||
$data['radiusOptions'] = array_map('intval', array_filter(explode(',', $radiusStr)));
|
||||
$data['radiusUnit'] = $params->get('radius_unit', 'miles');
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage mod_mokosuitestorelocator_search
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
/** @var array $displayData */
|
||||
$params = $displayData['params'];
|
||||
$cities = $displayData['cities'] ?? [];
|
||||
$states = $displayData['states'] ?? [];
|
||||
$radiusOptions = $displayData['radiusOptions'] ?? [];
|
||||
$radiusUnit = $displayData['radiusUnit'] ?? 'miles';
|
||||
$showCity = (int) $params->get('show_city_filter', 1);
|
||||
$showRadius = (int) $params->get('show_radius_filter', 1);
|
||||
$moduleId = $displayData['module']->id;
|
||||
?>
|
||||
<div class="mod-mokosuitestorelocator-search">
|
||||
<form action="<?php echo Route::_('index.php?option=com_mokosuitestorelocator&view=locations'); ?>"
|
||||
method="get" class="mokosuitestorelocator-search-form" id="mokosuitestorelocator-search-<?php echo (int) $moduleId; ?>">
|
||||
|
||||
<div class="mokosuitestorelocator-search-field">
|
||||
<label for="mokosuitestorelocator-query-<?php echo (int) $moduleId; ?>">
|
||||
<?php echo Text::_('MOD_MOKOJOOMSTORELOCATOR_SEARCH_LABEL'); ?>
|
||||
</label>
|
||||
<input type="text"
|
||||
id="mokosuitestorelocator-query-<?php echo (int) $moduleId; ?>"
|
||||
name="search"
|
||||
placeholder="<?php echo Text::_('MOD_MOKOJOOMSTORELOCATOR_SEARCH_PLACEHOLDER'); ?>"
|
||||
class="form-control"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<?php if ($showCity && !empty($cities)) : ?>
|
||||
<div class="mokosuitestorelocator-search-field">
|
||||
<label for="mokosuitestorelocator-city-<?php echo (int) $moduleId; ?>">
|
||||
<?php echo Text::_('MOD_MOKOJOOMSTORELOCATOR_SEARCH_CITY'); ?>
|
||||
</label>
|
||||
<select id="mokosuitestorelocator-city-<?php echo (int) $moduleId; ?>"
|
||||
name="city" class="form-select">
|
||||
<option value=""><?php echo Text::_('MOD_MOKOJOOMSTORELOCATOR_SEARCH_ALL_CITIES'); ?></option>
|
||||
<?php foreach ($cities as $city) : ?>
|
||||
<option value="<?php echo $this->escape($city); ?>">
|
||||
<?php echo $this->escape($city); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($showRadius && !empty($radiusOptions)) : ?>
|
||||
<div class="mokosuitestorelocator-search-field">
|
||||
<label for="mokosuitestorelocator-radius-<?php echo (int) $moduleId; ?>">
|
||||
<?php echo Text::_('MOD_MOKOJOOMSTORELOCATOR_SEARCH_RADIUS'); ?>
|
||||
</label>
|
||||
<select id="mokosuitestorelocator-radius-<?php echo (int) $moduleId; ?>"
|
||||
name="radius" class="form-select">
|
||||
<option value=""><?php echo Text::_('MOD_MOKOJOOMSTORELOCATOR_SEARCH_ANY_DISTANCE'); ?></option>
|
||||
<?php foreach ($radiusOptions as $radius) : ?>
|
||||
<option value="<?php echo (int) $radius; ?>">
|
||||
<?php echo (int) $radius . ' ' . $this->escape($radiusUnit); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="lat" id="mokosuitestorelocator-lat-<?php echo (int) $moduleId; ?>" value="" />
|
||||
<input type="hidden" name="lng" id="mokosuitestorelocator-lng-<?php echo (int) $moduleId; ?>" value="" />
|
||||
|
||||
<button type="button" class="btn btn-outline-secondary mokosuitestorelocator-geolocation-btn"
|
||||
id="mokosuitestorelocator-geolocate-<?php echo (int) $moduleId; ?>">
|
||||
<?php echo Text::_('MOD_MOKOJOOMSTORELOCATOR_SEARCH_USE_LOCATION'); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<?php echo Text::_('JSEARCH_FILTER_SUBMIT'); ?>
|
||||
</button>
|
||||
|
||||
<input type="hidden" name="option" value="com_mokosuitestorelocator" />
|
||||
<input type="hidden" name="view" value="locations" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php if ($showRadius) : ?>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var btn = document.getElementById('mokosuitestorelocator-geolocate-<?php echo (int) $moduleId; ?>');
|
||||
if (!btn || !navigator.geolocation) {
|
||||
if (btn) btn.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
btn.addEventListener('click', function() {
|
||||
btn.disabled = true;
|
||||
btn.textContent = '<?php echo Text::_('MOD_MOKOJOOMSTORELOCATOR_SEARCH_LOCATING', true); ?>';
|
||||
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
function(pos) {
|
||||
document.getElementById('mokosuitestorelocator-lat-<?php echo (int) $moduleId; ?>').value = pos.coords.latitude;
|
||||
document.getElementById('mokosuitestorelocator-lng-<?php echo (int) $moduleId; ?>').value = pos.coords.longitude;
|
||||
btn.textContent = '<?php echo Text::_('MOD_MOKOJOOMSTORELOCATOR_SEARCH_LOCATION_SET', true); ?>';
|
||||
btn.disabled = false;
|
||||
},
|
||||
function() {
|
||||
btn.textContent = '<?php echo Text::_('MOD_MOKOJOOMSTORELOCATOR_SEARCH_USE_LOCATION', true); ?>';
|
||||
btn.disabled = false;
|
||||
},
|
||||
{ enableHighAccuracy: false, timeout: 10000 }
|
||||
);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- =========================================================================
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
=========================================================================
|
||||
FILE INFORMATION
|
||||
DEFGROUP: MokoSuiteStoreLocator
|
||||
INGROUP: pkg_mokosuitestorelocator
|
||||
PATH: src/pkg_mokosuitestorelocator.xml
|
||||
VERSION: 01.00.00
|
||||
BRIEF: Package manifest for the MokoSuiteStoreLocator package
|
||||
=========================================================================
|
||||
-->
|
||||
<extension type="package" method="upgrade">
|
||||
<name>pkg_mokosuitestorelocator</name>
|
||||
<packagename>mokosuitestorelocator</packagename>
|
||||
<version>1.0.0</version>
|
||||
<creationDate>2026-06-23</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||
<description>PKG_MOKOJOOMSTORELOCATOR_DESC</description>
|
||||
<scriptfile>script.php</scriptfile>
|
||||
|
||||
<files>
|
||||
<file type="component" id="com_mokosuitestorelocator">com_mokosuitestorelocator.zip</file>
|
||||
<file type="module" id="mod_mokosuitestorelocator_map" client="site">mod_mokosuitestorelocator_map.zip</file>
|
||||
<file type="module" id="mod_mokosuitestorelocator_search" client="site">mod_mokosuitestorelocator_search.zip</file>
|
||||
</files>
|
||||
|
||||
<updateservers>
|
||||
<server type="extension" name="MokoSuiteStoreLocator Updates">https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteStoreLocator/updates.xml</server>
|
||||
</updateservers>
|
||||
<dlid prefix="dlid=" suffix=""/>
|
||||
<blockChildUninstall>true</blockChildUninstall>
|
||||
</extension>
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteStoreLocator
|
||||
* @subpackage pkg_mokosuitestorelocator
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Installer\InstallerAdapter;
|
||||
use Joomla\CMS\Installer\InstallerScriptInterface;
|
||||
use Joomla\CMS\Log\Log;
|
||||
|
||||
/**
|
||||
* Package installation script for MokoSuiteStoreLocator.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Pkg_MokosuitestorelocatorInstallerScript implements InstallerScriptInterface
|
||||
{
|
||||
/**
|
||||
* Minimum PHP version required.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected string $minimumPhp = '8.2';
|
||||
|
||||
/**
|
||||
* Minimum Joomla version required.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected string $minimumJoomla = '5.0.0';
|
||||
|
||||
/**
|
||||
* Called before any type of action.
|
||||
*
|
||||
* @param string $type Installation type (install, update, discover_install).
|
||||
* @param InstallerAdapter $parent The parent installer object.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function preflight(string $type, InstallerAdapter $parent): bool
|
||||
{
|
||||
if (version_compare(PHP_VERSION, $this->minimumPhp, '<'))
|
||||
{
|
||||
Log::add(
|
||||
'MokoSuiteStoreLocator requires PHP ' . $this->minimumPhp . ' or later.',
|
||||
Log::WARNING,
|
||||
'jerror'
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (version_compare(JVERSION, $this->minimumJoomla, '<'))
|
||||
{
|
||||
Log::add(
|
||||
'MokoSuiteStoreLocator requires Joomla ' . $this->minimumJoomla . ' or later.',
|
||||
Log::WARNING,
|
||||
'jerror'
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on installation.
|
||||
*
|
||||
* @param InstallerAdapter $parent The parent installer object.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function install(InstallerAdapter $parent): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on update.
|
||||
*
|
||||
* @param InstallerAdapter $parent The parent installer object.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function update(InstallerAdapter $parent): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on uninstallation.
|
||||
*
|
||||
* @param InstallerAdapter $parent The parent installer object.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function uninstall(InstallerAdapter $parent): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after any type of action.
|
||||
*
|
||||
* @param string $type Installation type.
|
||||
* @param InstallerAdapter $parent The parent installer object.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function postflight(string $type, InstallerAdapter $parent): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user