← Developer Reference

Architecture

This reference targets APD v1.0.x. Last updated: 2026-02-21.

Plugin structure, design patterns, naming conventions, database schema, and coding standards.

Plugin Structure

DamDir Directory is organized by domain. Each subsystem lives in its own namespace under src/, with shared contracts defined as interfaces.

Directory
damdir-directory/
├── src/
│   ├── Core/           # Plugin bootstrap, assets, templates
│   ├── Admin/          # Admin pages, meta boxes, settings
│   ├── Listing/        # Post type, query builder
│   ├── Fields/         # Field registry, renderer, validator, types
│   ├── Taxonomy/       # Categories and tags
│   ├── Search/         # Search query, filters
│   ├── Frontend/       # Submission, display, dashboard
│   ├── Shortcode/      # Shortcode manager and implementations
│   ├── Blocks/         # Gutenberg blocks
│   ├── User/           # Favorites, profile
│   ├── Review/         # Reviews and ratings
│   ├── Contact/        # Contact forms, inquiry tracking
│   ├── Email/          # Email manager, templates
│   ├── Api/            # REST API controller, endpoints
│   ├── CLI/            # WP-CLI commands
│   ├── Module/         # Module registry, interfaces, admin page
│   └── Contracts/      # Interfaces
├── templates/          # Theme-overridable templates
├── assets/             # CSS, JS, images
└── includes/           # Global helper functions

The src/ directory uses PSR-4 autoloading under the APD namespace. Each subdirectory maps to a subsystem:

Key Patterns

The plugin relies on a few standard design patterns:

PHP
// Singleton access
$fields = APD\Fields\FieldRegistry::get_instance();

// Registry usage
$fields->register( 'price', [
    'type'     => 'currency',
    'label'    => 'Price',
    'required' => true,
] );

// Template override (in theme)
// wp-content/themes/my-theme/damdir-directory/listing-card.php

// Hook system
add_action( 'apd_after_listing_save', $callback, 10, 2 );
add_filter( 'apd_listing_fields', $callback );

Naming Conventions

APD uses consistent prefixes and naming patterns throughout the codebase:

Element Convention Example
Function prefix apd_ apd_get_listing_field()
Class namespace APD\ APD\Fields\FieldRegistry
Post type apd_listing register_post_type( 'apd_listing' )
Taxonomies apd_category, apd_tag register_taxonomy( 'apd_category' )
Meta keys _apd_{field_name} _apd_views_count, _apd_price
Options apd_options get_option( 'apd_options' )
Nonces apd_{action}_nonce apd_submission_nonce
Text domain damdir-directory __( 'Label', 'damdir-directory' )
CSS classes apd- (BEM) apd-listing-card__title
Hook names apd_{subsystem}_{event} apd_after_listing_save

Database Schema

APD uses WordPress native tables only — no custom database tables. Data is stored across posts, post meta, terms, term meta, comments, comment meta, user meta, and options.

Posts Table wp_posts

The plugin registers two custom post types:

Post Type Description
apd_listing Directory listings
apd_inquiry Tracked contact form inquiries

Post Meta Table wp_postmeta

All custom field values are stored as post meta with the _apd_ prefix:

Meta Key Type Description
_apd_views_count int Number of times the listing has been viewed
_apd_average_rating float Cached average review rating
_apd_favorite_count int Number of times the listing has been favorited
_apd_{field_name} mixed Values for registered custom fields

Terms Tables wp_terms, wp_term_taxonomy

Two custom taxonomies organize listings:

Taxonomy Description
apd_category Hierarchical categories for listings
apd_tag Flat tags for listings

Term Meta Table wp_termmeta

Meta Key Type Description
_apd_category_icon string Dashicon class name for the category
_apd_category_color string Hex color code for the category

Comments Table wp_comments

Reviews are stored as WordPress comments with a custom comment type:

Comment Type Description
apd_review Listing reviews with star ratings

Comment Meta Table wp_commentmeta

Meta Key Type Description
_apd_rating int Star rating value (1–5)
_apd_review_title string Review title/headline

User Meta Table wp_usermeta

Meta Key Type Description
_apd_favorites array Serialized array of favorited listing IDs
_apd_phone string User phone number
_apd_avatar int Custom avatar attachment ID
_apd_social_* string Social media profile URLs

Options Table wp_options

Option Name Type Description
apd_options array All plugin settings (serialized)
apd_version string Currently installed plugin version

Coding Standards

PHP Standards

PHP
<?php

declare( strict_types=1 );

namespace APD\Fields;

/**
 * Manages field type registration and lookup.
 *
 * @since 1.0.0
 */
final class FieldRegistry {

    /** @var array<string, FieldTypeInterface> */
    private array $types = [];

    /**
     * Register a field type handler.
     *
     * @param FieldTypeInterface $type Field type instance.
     */
    public function register( FieldTypeInterface $type ): void {
        $this->types[ $type->getType() ] = $type;
    }
}

JavaScript Standards

CSS Standards

CSS
/* BEM naming with apd- prefix */
.apd-listing-card {
    border: 1px solid var(--apd-border-color);
    border-radius: var(--apd-radius);
}

.apd-listing-card__title {
    font-size: var(--apd-font-size-lg);
    font-weight: 600;
}

.apd-listing-card--featured {
    border-color: var(--apd-color-primary);
}

Security Standards

All data must be escaped on output, sanitized on input, and authorized before access:

Principle Functions
Escape all output esc_html(), esc_attr(), esc_url(), wp_kses_post()
Sanitize all input sanitize_text_field(), absint(), sanitize_email()
Verify nonces wp_verify_nonce(), check_ajax_referer()
Check capabilities current_user_can(), wp_get_current_user()
Prepared statements $wpdb->prepare() for all direct SQL queries
PHP
// Always escape output
echo esc_html( $listing->post_title );
echo '<a href="' . esc_url( $url ) . '">';

// Always sanitize input
$name  = sanitize_text_field( $_POST['name'] );
$id    = absint( $_GET['id'] );
$email = sanitize_email( $_POST['email'] );

// Always verify nonces
if ( ! wp_verify_nonce( $_POST['_wpnonce'], 'apd_submission_nonce' ) ) {
    wp_die( 'Security check failed.' );
}

// Always check capabilities
if ( ! current_user_can( 'edit_post', $listing_id ) ) {
    wp_die( 'Unauthorized.' );
}

// Always use prepared statements
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value = %d",
        '_apd_views_count',
        $min_views
    )
);