This page is the technical reference for developers who want to extend, customize, or integrate with Local Directory. It covers the plugin architecture, public API surface, database schema, hook system, and asset pipeline.
Architecture
Local Directory uses a singleton + PSR-4-style autoloader pattern. The main plugin file local-directory.php defines the ld_plugin() function, which returns the single instance of LD_Plugin. All subsystems are instantiated during the plugins_loaded hook.
Bootstrap Flow
local-directory.php → ld_plugin() → LD_Plugin::instance()
Inside LD_Plugin::instance(), the following subsystems are created:
new LD_Admin();
new LD_Shortcodes();
new LD_Badges();
new LD_Favorites();
new LD_Claim_Listing();
new LD_Report();
new LD_Email_Manager();
new LD_Schema();
new LD_Open_Graph();
new LD_Analytics_Tracker();
new LD_Search_Query();
new LD_Compare_Controller();
new LD_Blocks();
Autoloader
Classes follow the class-ld-{name}.php naming convention. The autoloader resolves classes from these directories:
includes/includes/admin/includes/frontend/includes/models/includes/features/includes/search/includes/maps/includes/seo/includes/blocks/includes/elementor/includes/elementor/widgets/
Controller Pattern
Shortcode controllers follow a consistent render pipeline:
render($atts) → get_atts() → WP_Query → LD_Template_Loader::load()
Each controller normalizes shortcode attributes via get_atts(), builds a WP_Query for listing data, and passes results to the template loader which supports child-theme overrides.
Constants
These constants are defined in local-directory.php and available globally after the plugin loads.
| Constant | Value | Description |
|---|---|---|
LD_VERSION |
1.0.5 |
Current plugin version string. |
LD_PLUGIN_FILE |
__FILE__ |
Absolute path to the main plugin file. |
LD_PLUGIN_DIR |
plugin_dir_path() |
Absolute filesystem path to the plugin directory (with trailing slash). |
LD_PLUGIN_URL |
plugin_dir_url() |
URL to the plugin directory (with trailing slash). |
LD_PLUGIN_BASENAME |
plugin_basename() |
Relative path from wp-content/plugins/ (e.g., local-directory/local-directory.php). |
Custom Post Type
The plugin registers the ld_listing custom post type during init.
| Property | Value |
|---|---|
| Post type slug | ld_listing |
| Supports | title, editor, thumbnail, excerpt, comments, author |
| REST API | Enabled (show_in_rest = true) |
| Archive | Enabled |
| Rewrite slug | listing |
| Menu icon | dashicons-location |
Querying Listings
$listings = new WP_Query([
'post_type' => 'ld_listing',
'posts_per_page' => 10,
'orderby' => 'date',
'order' => 'DESC',
]);
Taxonomies
Three custom taxonomies are registered for the ld_listing post type.
| Taxonomy | Hierarchical | Rewrite Slug | Description |
|---|---|---|---|
ld_category |
Yes | listing-category |
Business categories (e.g., Restaurants, Hotels, Shopping). Supports icons and custom fields per category. |
ld_location |
Yes | listing-location |
Geographic locations with parent-child hierarchy (Country → State/Region → City). |
ld_tag |
No | listing-tag |
Free-form tags for listings (e.g., "pet-friendly", "free-wifi", "outdoor-seating"). |
Database Tables
Local Directory creates 9 custom tables on activation. All table names are prefixed with $wpdb->prefix (typically wp_).
| Table | Description |
|---|---|
{prefix}ld_field_groups |
Custom field group definitions (tab/section labels, sort order). |
{prefix}ld_fields |
Custom field definitions (type, label, options, visibility, validation rules). |
{prefix}ld_locations_data |
Listing-to-location mapping with coordinates (lat/lng), formatted address, and Google Place ID. |
{prefix}ld_rating_criteria |
Rating criteria definitions per category (e.g., Food, Service, Ambiance). |
{prefix}ld_ratings_summary |
Aggregated rating scores per listing per criterion (count, sum, average). |
{prefix}ld_import_log |
Import history with source type, record count, mapping used, and Google Place IDs for deduplication. |
{prefix}ld_claims |
Ownership claims with verification code, status (pending/approved/rejected), and claimant data. |
{prefix}ld_reports |
User-submitted reports for listings (spam, incorrect info, closed, etc.). |
{prefix}ld_analytics |
Event tracking data (views, clicks, searches, favorites) with timestamps and session info. |
Note: Tables are created using dbDelta() during activation. The plugin checks table versions and runs migrations automatically on update.
Key Models
Models wrap database records and provide a clean API for reading and writing data. All models are in includes/models/.
LD_Listing
Wraps a WP_Post of type ld_listing. The primary model used throughout the plugin for listing display, search results, and data export.
// All public getters on LD_Listing
$listing->get_id()
$listing->get_title()
$listing->get_permalink()
$listing->get_phone()
$listing->get_website()
$listing->get_opening_hours()
$listing->get_location_data()
$listing->get_lat()
$listing->get_lng()
$listing->get_address()
$listing->get_overall_rating()
$listing->get_criteria_ratings()
$listing->get_price_level()
$listing->get_badges()
$listing->is_featured()
$listing->is_verified()
$listing->is_new()
$listing->to_marker_data()
$listing->to_card_data()
$listing->to_compare_data()
LD_Location_Data
Manages the ld_locations_data table. Stores latitude, longitude, formatted address, and Google Place ID per listing.
// Static methods
LD_Location_Data::get_by_listing( $listing_id ) // Returns location row or null
LD_Location_Data::save( $listing_id, $data ) // Upserts location data
LD_Claim
Manages ownership claims in the ld_claims table.
LD_Claim::create( $listing_id, $user_data )
LD_Claim::get( $claim_id )
LD_Claim::verify_code( $claim_id, $code )
LD_Claim::approve( $claim_id )
LD_Claim::reject( $claim_id )
LD_Claim::get_approved_for_listing( $listing_id )
LD_Review
Handles multi-criteria ratings linked to WordPress comments.
LD_Review::get_criteria_for_listing( $listing_id )
LD_Review::get_ratings_for_comment( $comment_id )
LD_Review::save_ratings( $comment_id, $ratings )
LD_Review::recalculate_summary( $listing_id )
LD_Recaptcha
Wraps Google reCAPTCHA v3 verification for contact, claim, and report forms.
LD_Recaptcha::is_enabled() // bool
LD_Recaptcha::get_site_key() // string
LD_Recaptcha::verify( $token, $action ) // bool
Hooks Reference
Local Directory fires custom actions and filters that let you extend every major feature. Use these hooks from your theme's functions.php or a custom plugin.
Actions
| Hook | Parameters | Description |
|---|---|---|
ld_after_import_listing |
$listing_id, $raw_data |
Fires after a listing is created during import. Use it to sync external systems or add custom meta. |
ld_claim_submitted |
$claim_id, $listing_id |
Fires when a new ownership claim is submitted. Useful for custom notifications. |
ld_claim_approved |
$claim_id, $listing_id, $user_id |
Fires when an admin approves a claim. The listing author is changed to $user_id. |
ld_claim_rejected |
$claim_id, $listing_id |
Fires when an admin rejects a claim. |
ld_favorite_toggled |
$listing_id, $user_id, $is_favorite |
Fires when a user adds or removes a listing from favorites. $is_favorite is a boolean. |
ld_listing_reported |
$report_id, $listing_id, $reason |
Fires when a visitor submits a report against a listing. |
Example: Sync After Import
add_action( 'ld_after_import_listing', function( $listing_id, $raw_data ) {
// Push new listing to external CRM
my_crm_sync( $listing_id, $raw_data['name'] );
}, 10, 2 );
Filters
| Hook | Parameters | Description |
|---|---|---|
ld_badge_types |
$types |
Array of badge definitions (featured, verified, new, price). Add custom badges here. |
ld_compare_fields |
$fields |
Array of fields shown in the comparison table. Add or remove comparison rows. |
ld_import_field_mapping |
$mapping, $source_type |
Field mapping array for imports. Modify to map custom source fields to listing meta. |
ld_import_filter_about_data |
$about_data, $raw_entry |
Filter the description/about text before saving during import. Useful for content cleanup. |
ld_map_marker_data |
$marker, $listing |
Marker data array before it is sent to the map JavaScript. Add custom properties. |
ld_schema_data |
$schema, $listing |
JSON-LD structured data array for a single listing. Add extra Schema.org properties. |
ld_schema_category_map |
$map |
Mapping of category slugs to Schema.org types (e.g., 'restaurants' => 'Restaurant'). |
ld_search_query_args |
$args, $params |
WP_Query arguments before search execution. Add custom meta queries or tax queries. |
ld_search_results |
$results, $query |
Search results array after query execution. Modify, re-sort, or inject promoted listings. |
ld_template_path |
$path, $template_name |
Resolved template file path. Override to load templates from a custom location. |
Example: Add a Custom Badge
add_filter( 'ld_badge_types', function( $types ) {
$types['eco_friendly'] = [
'label' => 'Eco-Friendly',
'color' => '#16a34a',
'bg_color' => '#f0fdf4',
'icon' => 'leaf',
'meta_key' => '_ld_eco_friendly',
];
return $types;
} );
AJAX Endpoints
AJAX handlers are registered in LD_Ajax for both authenticated (wp_ajax_) and public (wp_ajax_nopriv_) requests. All requests use admin-ajax.php and require a valid nonce unless noted otherwise.
| Action | Access | Description |
|---|---|---|
ld_search |
Public | Performs a search query and returns listings as JSON (cards + markers). |
ld_track_event |
Public | Records an analytics event (view, click, phone, direction, share). |
ld_toggle_favorite |
Public* | Adds or removes a listing from the user's favorites. *Uses cookies for guests, user meta for logged-in users. |
ld_submit_report |
Public | Submits a report against a listing (spam, incorrect, closed, other). |
ld_submit_claim |
Logged-in | Submits an ownership claim. Sends a verification email to the claimant. |
ld_verify_claim_code |
Logged-in | Verifies a claim using the emailed code. |
ld_get_compare_data |
Public | Returns comparison data for an array of listing IDs. |
ld_toggle_featured |
Admin | Toggles the featured status of a listing (admin only). |
Nonce verification: All AJAX handlers verify a nonce passed as _ajax_nonce or nonce. The nonce action is ld_ajax_nonce and is localized via window.ldData.nonce.
REST API
Local Directory registers 8 REST endpoints under the local-directory/v1 namespace. All endpoints support standard WordPress REST authentication (cookie, Application Password, or OAuth).
| Route | Methods | Description |
|---|---|---|
/local-directory/v1/listings |
GET |
Paginated listing collection. Supports per_page, page, category, location, orderby, search parameters. |
/local-directory/v1/listings/{id} |
GET |
Single listing with all fields, location data, ratings, badges, and opening hours. |
/local-directory/v1/categories |
GET |
Category taxonomy terms with icons, listing counts, and hierarchy. |
/local-directory/v1/locations |
GET |
Location taxonomy terms with hierarchy and listing counts. |
/local-directory/v1/reviews |
GET |
Reviews for a listing (?listing_id=). Includes multi-criteria ratings per review. |
/local-directory/v1/reviews |
POST |
Submit a review with criteria ratings. Requires authentication. |
/local-directory/v1/search |
GET |
Full search with keyword, category, location, radius, price, status, and bounds parameters. |
/local-directory/v1/markers |
GET |
Lightweight marker data (id, lat, lng, title, icon) for map rendering. Supports the same filters as search. |
Example: Fetch Listings
// JavaScript (fetch)
const res = await fetch('/wp-json/local-directory/v1/listings?per_page=10&category=restaurants');
const data = await res.json();
// PHP (internal)
$request = new WP_REST_Request( 'GET', '/local-directory/v1/listings' );
$request->set_param( 'per_page', 10 );
$response = rest_do_request( $request );
Gutenberg Blocks
All shortcodes have matching Gutenberg block equivalents registered under the local-directory/ namespace. Block registration is handled in LD_Blocks.
| Block | Shortcode Equivalent | Description |
|---|---|---|
local-directory/directory |
[local-directory] |
Full directory page with map, search, and listing grid. |
local-directory/listings |
[ld-listings] |
Standalone listing grid or list. |
local-directory/map |
[ld-map] |
Standalone interactive map with markers. |
local-directory/search |
[ld-search] |
Standalone search form. |
local-directory/categories |
[ld-categories] |
Category grid with icons and counts. |
local-directory/compare |
[ld-compare] |
Side-by-side listing comparison table. |
local-directory/recently-viewed |
[ld-recently-viewed] |
Recently viewed listings from localStorage. |
Server-side rendered: All blocks use register_block_type() with a render_callback that delegates to the corresponding shortcode controller. Block attributes map 1:1 to shortcode attributes.
Elementor Widgets
When Elementor is active, Local Directory registers 7 widgets in a dedicated "Local Directory" category. Widget files are in includes/elementor/widgets/.
| Widget | Class | File |
|---|---|---|
| Directory | LD_Elementor_Directory |
class-ld-elementor-directory.php |
| Listings | LD_Elementor_Listings |
class-ld-elementor-listings.php |
| Map | LD_Elementor_Map |
class-ld-elementor-map.php |
| Search | LD_Elementor_Search |
class-ld-elementor-search.php |
| Categories | LD_Elementor_Categories |
class-ld-elementor-categories.php |
| Compare | LD_Elementor_Compare |
class-ld-elementor-compare.php |
| Recently Viewed | LD_Elementor_Recently_Viewed |
class-ld-elementor-recently-viewed.php |
Frontend Assets
All frontend scripts and styles are conditionally enqueued only on pages where directory content is rendered. Handles are prefixed with ld-.
Scripts
| Handle | File | Notes |
|---|---|---|
ld-frontend |
assets/js/frontend.js |
Core frontend logic: cards, favorites, compare tray, analytics tracking. |
ld-map |
assets/js/map-leaflet.js or assets/js/map-google.js |
Map provider implementation. Resolved dynamically based on settings. |
ld-search |
assets/js/search.js |
Search form logic, AJAX search, autocomplete, viewport search. |
ld-compare |
assets/js/compare.js |
Comparison table rendering and localStorage management. |
ld-single |
assets/js/single.js |
Single listing page: gallery, tabs, reviews, contact form, share, QR code. |
ld-recaptcha |
Google reCAPTCHA v3 CDN | Loaded only when reCAPTCHA is enabled and a form is present. |
Styles
| Handle | File | Notes |
|---|---|---|
ld-frontend |
assets/css/frontend.css |
All frontend component styles. Uses CSS custom properties for theming. |
ld-leaflet |
Leaflet CDN | Loaded when Leaflet/OpenStreetMap is the selected map provider. |
ld-markercluster |
Leaflet.markercluster CDN | Marker clustering styles. Loaded with Leaflet provider. |
Localized Data: window.ldData
The ld-frontend script receives a localized JavaScript object via wp_localize_script(). This object is available globally as window.ldData.
window.ldData = {
ajaxUrl: '/wp-admin/admin-ajax.php',
restUrl: '/wp-json/local-directory/v1/',
nonce: 'abc123...',
mapProvider: 'leaflet', // 'leaflet' or 'google'
googleApiKey: '', // Google Maps API key (if set)
mapStyle: 'default', // Map style preset name
defaultLat: 40.7128, // Default map center latitude
defaultLng: -74.0060, // Default map center longitude
defaultZoom: 12, // Default map zoom level
clustering: true, // Whether marker clustering is enabled
directoryPage: '/directory/', // Main directory page URL
comparePage: '/compare/', // Compare page URL
i18n: {
search: 'Search',
noResults: 'No listings found.',
loading: 'Loading...',
viewOnMap: 'View on Map',
addToCompare: 'Add to Compare',
removeCompare: 'Remove from Compare',
directions: 'Get Directions',
openNow: 'Open Now',
closed: 'Closed',
featured: 'Featured',
verified: 'Verified',
claimListing: 'Claim this listing',
reportListing: 'Report this listing'
}
};
Plugin File Structure
Full directory tree of the Local Directory plugin:
local-directory/
├── local-directory.php # Main plugin file, bootstrap
├── uninstall.php # Cleanup on uninstall
│
├── includes/
│ ├── class-ld-plugin.php # Singleton, subsystem init
│ ├── class-ld-activator.php # Activation: tables, defaults
│ ├── class-ld-deactivator.php # Deactivation cleanup
│ ├── class-ld-autoloader.php # PSR-4 style autoloader
│ ├── class-ld-shortcodes.php # Shortcode registration
│ ├── class-ld-template-loader.php # Template resolution + overrides
│ ├── class-ld-ajax.php # AJAX handler registration
│ ├── class-ld-rest-api.php # REST endpoint registration
│ ├── class-ld-email-manager.php # 9 email notification types
│ ├── class-ld-badges.php # Badge system (featured, verified, new, price)
│ ├── class-ld-favorites.php # Favorites (cookie + user meta)
│ ├── class-ld-compare-controller.php
│ │
│ ├── admin/
│ │ ├── class-ld-admin.php # Admin menus, metaboxes
│ │ ├── class-ld-admin-settings.php # Settings page (tabs)
│ │ ├── class-ld-admin-import.php # Import UI + processor
│ │ └── class-ld-admin-analytics.php # Analytics dashboard
│ │
│ ├── frontend/
│ │ ├── class-ld-directory-controller.php # [local-directory]
│ │ ├── class-ld-listings-controller.php # [ld-listings]
│ │ ├── class-ld-map-controller.php # [ld-map]
│ │ ├── class-ld-search-controller.php # [ld-search]
│ │ ├── class-ld-categories-controller.php # [ld-categories]
│ │ ├── class-ld-single-controller.php # [ld-listing-page]
│ │ ├── class-ld-compare-controller.php # [ld-compare]
│ │ └── class-ld-recently-viewed-controller.php
│ │
│ ├── models/
│ │ ├── class-ld-listing.php
│ │ ├── class-ld-location-data.php
│ │ ├── class-ld-claim.php
│ │ ├── class-ld-review.php
│ │ └── class-ld-report.php
│ │
│ ├── features/
│ │ ├── class-ld-claim-listing.php
│ │ ├── class-ld-report.php
│ │ ├── class-ld-recaptcha.php
│ │ └── class-ld-analytics-tracker.php
│ │
│ ├── search/
│ │ └── class-ld-search-query.php
│ │
│ ├── maps/
│ │ └── class-ld-map-provider.php
│ │
│ ├── seo/
│ │ ├── class-ld-schema.php
│ │ └── class-ld-open-graph.php
│ │
│ ├── blocks/
│ │ └── class-ld-blocks.php
│ │
│ └── elementor/
│ ├── class-ld-elementor.php
│ └── widgets/
│ ├── class-ld-elementor-directory.php
│ ├── class-ld-elementor-listings.php
│ ├── class-ld-elementor-map.php
│ ├── class-ld-elementor-search.php
│ ├── class-ld-elementor-categories.php
│ ├── class-ld-elementor-compare.php
│ └── class-ld-elementor-recently-viewed.php
│
├── templates/
│ ├── directory.php
│ ├── listings/
│ │ ├── card-grid.php
│ │ ├── card-list.php
│ │ └── pagination.php
│ ├── single/
│ │ ├── single-listing.php
│ │ ├── gallery.php
│ │ ├── tabs.php
│ │ ├── reviews.php
│ │ ├── contact-form.php
│ │ └── sidebar.php
│ ├── search/
│ │ └── search-form.php
│ ├── map/
│ │ └── map-canvas.php
│ ├── categories/
│ │ └── category-grid.php
│ ├── compare/
│ │ └── compare-table.php
│ └── recently-viewed/
│ └── recently-viewed.php
│
├── assets/
│ ├── css/
│ │ ├── frontend.css
│ │ └── admin.css
│ ├── js/
│ │ ├── frontend.js
│ │ ├── search.js
│ │ ├── map-leaflet.js
│ │ ├── map-google.js
│ │ ├── compare.js
│ │ ├── single.js
│ │ └── admin.js
│ └── images/
│ ├── marker-default.svg
│ └── no-image.svg
│
└── languages/
└── local-directory.pot