Architecture
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.
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:
- Core/ — Plugin bootstrap, activation/deactivation, asset enqueueing, and the template loader.
- Admin/ — Settings pages, meta boxes, admin columns, and the demo data generator.
- Listing/ — The
apd_listingpost type (PostType.php) and the query builder (ListingQueryBuilder.php). - Fields/ — The field registry, 24 built-in field types, the renderer, and the validator.
- Taxonomy/ — Registration and management of
apd_categoryandapd_tagtaxonomies. - Search/ — AJAX-powered search with the filter registry, query builder, and orderby support.
- Frontend/ — Submission forms, display views, and the user dashboard.
- Shortcode/ — 8 shortcodes managed through a shortcode registry.
- Blocks/ — 3 Gutenberg blocks with server-side rendering.
- User/ — Favorites system (logged-in and guest), user profiles, and avatars.
- Review/ — Star ratings, review moderation, and review display.
- Contact/ — Contact forms with spam protection and inquiry tracking.
- Email/ — 7 HTML email templates with placeholder replacement.
- Api/ — REST API controller with 15+ endpoints under
apd/v1. - CLI/ — WP-CLI commands for demo data generation and management.
- Module/ — Module registry,
ModuleInterface, and the modules admin page. - Contracts/ — Interfaces for field types, views, filters, and modules.
Key Patterns
The plugin relies on a few standard design patterns:
- Singleton Pattern — Core manager classes expose a
get_instance()method to ensure a single instance per request. This applies to the main plugin class, the field registry, the review manager, and similar orchestration objects. - Registry Pattern — Fields, filters, views, shortcodes, blocks, and modules are all managed through registries. Each registry provides
register(),unregister(),get(), andget_all()methods, and fires hooks on registration changes. - Template Override — Every template in the
templates/directory can be overridden by placing a copy in your theme underdamdir-directory/. The template loader checks the child theme first, then the parent theme, then falls back to the plugin. - Hook System — 100+ action hooks and 100+ filter hooks provide entry points into every subsystem. Hooks follow a consistent naming pattern:
apd_{subsystem}_{event}.
// 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
- Follow the WordPress PHP Coding Standards.
- Use strict types in every file:
declare(strict_types=1); - PHP 8.0+ features are allowed (named arguments, match expressions, union types, constructor promotion).
- All functions and classes must have complete PHPDoc blocks.
<?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
- Follow the WordPress JavaScript Coding Standards.
- ES6+ features are allowed (arrow functions, template literals, destructuring, async/await).
- Use
wp.i18nfor all user-facing strings to enable translation. - All frontend scripts use
deferloading and are only enqueued on pages that need them.
CSS Standards
- Follow the WordPress CSS Coding Standards.
- Use BEM naming convention:
.apd-block__element--modifier - Prefix all CSS classes with
apd-to avoid conflicts with themes and other plugins. - Use CSS custom properties for theming (colors, spacing, typography).
/* 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 |
// 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
)
);