=== Partner Map Locator ===
Contributors:      desk9
Tags:              map, store locator, locations, google maps, openstreetmap
Requires at least: 6.2
Tested up to:      7.0
Requires PHP:      7.4
Stable tag:        1.0.0
License:           GPLv2 or later
License URI:       https://www.gnu.org/licenses/gpl-2.0.html

Display partners, retailers, locations or company branches on a responsive interactive map powered by Google Maps or OpenStreetMap.

== Description ==

Partner Map Locator is a lightweight WordPress plugin for showing your partner network, dealers, retail stores or company branches on a responsive interactive map. Locations are managed as a custom post type so they sit alongside the rest of your WordPress content.

**Using the settings:** you pick **one map type for the whole site** (Google Maps, OpenStreetMap or list-only). That controls which scripts load. You can still hide the map on a **single** page with the shortcode or block list-only option. Under **List & map visibility**, *Description* has **separate** checkboxes for the list and the popup. Under **Features**, **List card description (max. characters)** controls list excerpts (with optional “Read more”); map popups always show the **full** description. Under **Map defaults**, **Map popup width** sets how wide the location card is on the map.

Choose Google Maps as the map type and enter your Google Maps JavaScript API key, pick OpenStreetMap for a keyless map rendered with the bundled Leaflet library, or use the list-only mode for a map-free, filterable location list.

= Features =

* Custom post type "Locations" with proper capabilities and admin UI
* Optional category taxonomy to group locations (regions, types, brands, ...)
* `[partner_map_locator]` shortcode with optional category, height, zoom, coordinates, layout, design and map attributes
* Multiple maps on the same page are fully supported
* Accessible list rendering of all locations alongside the map
* Optional "Show my location" button (browser geolocation, off by default)
* Optional admin geocoder using the Google Geocoding API (off by default, with a clear opt-in toggle)
* Server-side cache so the location query only runs once per data change
* CSV import / export and a one-click import tool for locations from a previous installation
* Translation ready (text domain `partner-map-locator`)
* Map provider of your choice: Google Maps (incl. optional Map ID / Advanced Markers) or OpenStreetMap (bundled Leaflet, no account or API key needed)
* Four ready-made designs (Classic, Tab view, Mixed view, Stack view) plus per-page design/layout via shortcode or block
* Geolocation with radius search and "near me" out of the box
* Schema.org LocalBusiness markup and WPML/Polylang compatibility
* No statistics or marketing scripts are bundled; outbound traffic is limited to the selected map service and, if you turn it on, the optional geocoder

= Privacy =

The plugin does not load advertising or visitor-profiling scripts. By default the map type is set to "List only", so no external map service is contacted until the site owner opts in by choosing a map provider under Settings → Map. With the Google Maps provider, the visitor's browser loads the Google Maps JavaScript API and tiles from Google, which may transmit data such as the visitor's IP address to Google. With the OpenStreetMap provider, the Leaflet library is bundled and served from your own site; only the map tiles are loaded from the configured tile server — openstreetmap.org by default, or Carto (carto.com) when one of the Light / Dark / More-colors map color schemes is selected — which transmits the visitor's IP address to that tile provider. Use the built-in consent gate (Settings → Integration) if you need visitor consent before the map loads; it covers both providers. The browser geolocation feature, when enabled, only triggers after the visitor clicks the dedicated button and is handled entirely client-side by the browser. The optional admin geocoder, when enabled, sends address data server-side from your site to the Google Geocoding API to retrieve coordinates.

Web fonts are self-hosted by default — the optional Google fonts are bundled with the plugin and served from your own server, so no request is made to Google. Under Settings → Style → "Web font loading" an administrator may instead choose "Load from Google", which loads the selected font from `https://fonts.googleapis.com` and therefore sends the visitor's IP address to Google; this option is off by default.

== External services ==

This plugin can connect to the third-party services listed below. Every one of them is optional and off by default: the plugin ships with the map type set to "List only", so a fresh install loads no map tiles and contacts no external service at all. A map (and its tile service) is only loaded after the site owner explicitly selects a provider under Settings → Map; the Google API features additionally require you to enable the matching option and enter your own API key. A built-in map consent gate (Settings → Integration) can require visitor consent before any tiles load.

**OpenStreetMap tile server** (tile.openstreetmap.org)
Active only if the site owner selects "OpenStreetMap" as the map type (the plugin defaults to "List only", which loads no tiles). It is the keyless map option — no account or API key is required. When a visitor then opens a page that contains the map, their browser requests the map tile images from the OpenStreetMap tile server, which transmits the visitor's IP address (and the requested map area) to the OpenStreetMap Foundation. No other data is sent. You can avoid this by keeping the "List only" map type, or by enabling the built-in consent gate (Settings → Integration) so tiles load only after the visitor agrees.
Tile usage policy: https://operations.osmfoundation.org/policies/tiles/ — Privacy: https://wiki.osmfoundation.org/wiki/Privacy_Policy

**CARTO basemap tiles** (basemaps.cartocdn.com)
Used only if you pick one of the "Light", "Dark" or "More colors" map color schemes while using the OpenStreetMap provider. The visitor's browser then loads the map tiles from CARTO instead of OpenStreetMap, which transmits the visitor's IP address (and the requested map area) to CARTO. The default "Classic" scheme uses OpenStreetMap tiles and does not contact CARTO.
Terms: https://carto.com/legal/ — Privacy: https://carto.com/privacy/

**Google Maps JavaScript API** (maps.googleapis.com)
Used only if you set the map type to "Google Maps" (Settings → Map). When a visitor opens a page with the map, their browser loads the Google Maps script and map tiles using the API key you entered, which transmits data such as the visitor's IP address to Google. The same script is loaded in the location editor if you use the optional "Import from Google Maps" search.
Terms: https://cloud.google.com/maps-platform/terms — Privacy: https://policies.google.com/privacy

**Google Places API** (places.googleapis.com, maps.googleapis.com)
Used only if you enable Google ratings sync or use the Google business search / Maps-link import in the location editor, and have entered your own API key. The plugin sends the place ID or the search text / business name you provide (and never any visitor data) to retrieve business details such as name, address, phone, opening hours, rating and review count. Requests are made when you save a location, when you run a search or link import in the editor, or on the schedule you configure for ratings sync; depending on your key restrictions they run from your server or from your logged-in browser session in wp-admin. In addition, if you paste a shortened Google Maps share link (e.g. maps.app.goo.gl or goo.gl) into the "Import from Google Maps link" field, the plugin follows that link server-side to Google to resolve the full map URL before extracting the location data. All of this happens in wp-admin and only with links/searches an administrator enters; no visitor data is involved.
Terms: https://cloud.google.com/maps-platform/terms — Privacy: https://policies.google.com/privacy

**Google Geocoding API** (maps.googleapis.com)
Used only if you enable the optional admin geocoder (Settings → General) and have entered an API key. When an administrator looks up coordinates while editing a location, the entered address is sent from your server to Google to obtain latitude/longitude. No visitor data is sent.
Terms: https://cloud.google.com/maps-platform/terms — Privacy: https://policies.google.com/privacy

**Google Fonts** (fonts.googleapis.com, fonts.gstatic.com)
Web fonts are self-hosted by default and no request is made to Google. Only if you change "Web font loading" to "Load from Google" (Settings → Style) does the visitor's browser load the selected font's stylesheet and font files from Google, which transmits the visitor's IP address to Google.
Terms: https://developers.google.com/fonts/faq — Privacy: https://policies.google.com/privacy

== Bundled libraries & source code ==

The plugin bundles the following third-party libraries in their official minified distribution form. The human-readable, unminified source code is publicly available in the projects' repositories:

* **Leaflet 1.9.4** (BSD-2-Clause) — `public/js/leaflet.js` — source: https://github.com/Leaflet/Leaflet
* **Leaflet.markercluster** (MIT) — `public/js/leaflet.markercluster.js` — source: https://github.com/Leaflet/Leaflet.markercluster
* **Lucide icons** (ISC) and **Heroicons** (MIT) — bundled as SVG data in `admin/data/icon-library.php` — sources: https://github.com/lucide-icons/lucide and https://github.com/tailwindlabs/heroicons

The plugin's own minified assets (`*.min.js` / `*.min.css`) ship alongside their readable source files in the same directories.

== Installation ==

1. Upload the `partner-map-locator` folder to `/wp-content/plugins/`.
2. Activate the plugin through the **Plugins** screen.
3. Add a few locations under **Partner Map &raquo; All Locations**.
4. Drop the shortcode on any page or post:
   `[partner_map_locator]`

== Frequently Asked Questions ==

= How do I display only one category? =

Use the `category` attribute with the slug of the term:
`[partner_map_locator category="region-north"]`

= Can I display several maps on one page? =

Yes. Each shortcode renders its own map with a unique container id. They do not share state.

= Can I show a different design per page? =

Yes. Add the `design` attribute to pick a design just for that map, independent of the global design under Partner Map → Settings:

`[partner_map_locator design="classic"]`
`[partner_map_locator design="tab_view"]`

Valid values are `classic`, `tab_view`, `mixed_view` and `stack_view`. The design’s own layout is applied automatically (you can still override it with an explicit `layout` attribute). This is ideal for a showcase page where each subpage demonstrates one design. Note: the design’s **layout, toolbar and styling classes** switch per page, but the **colour scheme and marker palette** are emitted once site-wide, so all maps share the global colours configured under the Style tab.

= Where is the data stored? =

Each location is a regular WordPress post in the `pmloc_location` custom post type with metadata (address, coordinates, contact details) stored as post meta with the `pmloc_` prefix.

= Does it work without internet access? =

The plugin's PHP and the Leaflet library are local, but the map imagery always loads from the selected provider (Google Maps, or the configured tile server for OpenStreetMap). If your visitors cannot reach that service the map area stays empty, but the marker list still renders.

= Do I need a Google account? =

Only for the Google Maps provider and the optional Google integrations (geocoder, ratings sync, business search, Maps-link import). Choose OpenStreetMap as the map type and the map renders with the bundled Leaflet library against openstreetmap.org tiles — no account, no API key.

= How do I move data from another plugin? =

The Tools page (**Partner Map &raquo; Tools**) includes CSV import and export plus a one-click option to copy locations that still exist in the database from an older store-locator setup into Partner Map Locator.

= Can one page show only the list while another shows the map? =

Yes in two ways: (1) Set **Map type** to “List only” under Partner Map → Settings so the whole site has no map. (2) Keep your map provider globally and add **`map="list"`** to the shortcode on specific pages, or choose **List only (no map)** under **Map for this block** in the block sidebar.

= How do list vs. map popup description settings relate? =

Under **List & map visibility**, the **Description** row has separate checkboxes for the list and the popup. Under **Features**, **List card description (max. characters)** sets list excerpts (optional “Read more”). When the popup shows the description, it always includes the **full** text (scrollable inside the card). Use **Map popup width** under **Map defaults** to change the card width.

= Which Google API keys do I need? =

Two scenarios:

1. **One key, browser-only (simplest)** — store your Maps JavaScript API key under **Google Maps API key**. Leave the **Google Places API key** field empty. In Google Cloud Console: enable **Maps JavaScript API** *and* **Places API (New)** for that key, and under HTTP-referrer restrictions add your domain plus `…/wp-admin/*` so the location editor can use it. The Maps-link import, search and Autocomplete all run in the browser; ratings sync (cron) is unavailable in this configuration.

2. **Two keys (recommended for ratings sync)** — keep the Maps key as in (1) for the browser, *and* add a second key with **IP restriction** (your server's IP) under **Google Places API key**. This second key powers server-side Place Details for the optional “Sync Google ratings” cron job and the Maps-link import, and never sends a referrer.

If you see “API key restricted to websites — use a separate IP-restricted key”, you are in scenario 1 and the message can be ignored — the browser flow handles everything except the cron sync.

= Can the importer read Google Business Profile data? =

Yes. Both the “Import from Google Maps link” and the live search use the **Places API (New)**, which exposes the same data Google shows on Business Profiles: name, address, phone, website, opening hours, average rating and review count. Individual review texts and Q&A are not exposed by the API. Pasting a `https://g.page/…` Business Profile share link or a `maps.app.goo.gl` link works the same as a regular Maps URL.

== Screenshots ==

1. The frontend map with markers and an accessible list of locations.
2. The location editor with address, coordinates and contact details.
3. Plugin settings: map defaults, designs and optional features.
4. Tools page with CSV import / export and migration assistant.

== Changelog ==

= 1.0.0 =
* Initial public release.
* Custom post type for locations with four taxonomies (categories, product types, partner levels, highlights), multi-select frontend filters and badge colors.
* `[partner_map_locator]` shortcode and Gutenberg block with per-page design, layout, category, height, zoom and list options; multiple maps per page.
* Map providers: Google Maps (incl. optional Map ID / Advanced Markers) or OpenStreetMap (bundled Leaflet); list-only mode is the privacy-friendly default.
* Four ready-made designs: Classic, Tab view, Mixed view and Stack view.
* Geolocation with radius search and "near me", opening hours, Schema.org LocalBusiness markup, WPML/Polylang support.
* CSV import / export, Google-Maps-link and business-search import in the location editor, optional admin geocoder, optional map consent gate.
* Optional scheduled Google ratings sync (star rating + review count via the Places API, with your own key).
* Server-side location cache, accessible list rendering and `<noscript>` fallback.

== Upgrade Notice ==

= 1.0.0 =
First public release.
