Developer Reference
Hooks, public functions, database schema, transient keys, and internals for Login Delay Shield v2.2.3.
Architecture
Login Delay Shield is a single-plugin WordPress security plugin with four files:
| File | Purpose |
|---|---|
wp-login-delay.php | Main plugin file — all security logic, hooks, database management, dashboard widget |
wldelay-settings.php | Settings registration via WordPress Settings API, sanitization, validation |
wldelay-settings-view.php | LDS_Settings_View class — admin UI rendering (collapsible sections, status badges, summary box) |
admin.js | Admin JavaScript — collapsible sections, tooltips, and UI interactions |
admin.css | Admin styles for the settings page and dashboard widget |
The plugin uses WordPress transients for tracking failed attempts and lockout state, and a custom database table for the failed login log. Custom Login URL uses WordPress rewrite rules. No custom REST API endpoints or post types are registered.
Constants
| Constant | Value | Description |
|---|---|---|
WLDELAY_VERSION | '2.2.3' | Plugin version |
WLDELAY_PLUGIN_FILE | __FILE__ | Main plugin file path |
WLDELAY_OPTION_NAME | 'wldelay_options' | WordPress option name for all settings |
Filter Hooks
wp_authenticate_user
filter · priority 1Main authentication filter. Applies delays and lockouts to login attempts. Parameters: $user (WP_User|WP_Error), $password. Returns WP_Error if locked out, otherwise applies sleep() delay on failure and returns $user.
rest_authentication_errors
filter · priority 20REST API authentication protection. Applies delays and lockout to failed REST API authentication when wldelay_rest_enabled is on. Returns a 403 WP_Error if the IP is locked out. Respects the IP whitelist and skips application-password attempts (handled separately). Since v2.1.2.
authenticate
filter · priority 25Application-password authentication protection. Applies delays and lockout to failed Basic Auth attempts when wldelay_application_password_enabled is on. Detects app-password attempts via PHP_AUTH_USER/PHP_AUTH_PW server headers. Parameters: $user, $username, $password. Since v2.1.2.
authenticate
filter · priority 99Blocks XML-RPC authentication when the “block XML-RPC” setting is enabled. Parameters: $user, $username, $password. Returns WP_Error('wldelay_xmlrpc_blocked') for XML-RPC requests.
wldelay_2fa_providers
filterCustomize the list of recognized 2FA plugins for the settings-page health check. Receives an associative array of slug => [ plugin_basename, … ]. Return the modified array to add or remove providers. The default map includes two-factor, wp-2fa, mini-orange, and google-authenticator. Since v2.1.4.
wldelay_export_login_log_should_exit
filterControls whether the CSV export handler calls exit() after streaming. Default true. Set to false in test environments. Since v2.1.2.
plugin_action_links_{basename}
filterAdds a “Settings” link to the plugin’s entry on the Plugins page.
Action Hooks
wp_login_failed
actionHooked to log failed login attempts to the database. Parameters: $username. Records the IP address, username, timestamp, and source (wp-login, xmlrpc, rest, or application-password).
wldelay_cleanup_logs
action · customCustom cron hook that runs daily. Deletes log entries older than the configured retention period. Processes in batches of 1,000 rows.
wp_dashboard_setup
actionRegisters the “Recent Failed Login Attempts” dashboard widget.
admin_post_wldelay_unlock_current_ip
actionHandles the “Unlock Current IP” button on the settings page. Removes the lockout and failure counters for the requesting admin’s IP address. Nonce-protected and requires manage_options capability.
admin_post_wldelay_export_login_log
actionHandles CSV export of the failed login log. Streams results in 1,000-row batches with formula-injection protection. Reads optional filters from $_GET (source, IP, username, date range). Nonce-protected and requires manage_options capability. Since v2.1.2.
Error Codes
| Error Code | Context | Description |
|---|---|---|
wldelay_ip_locked | Login form | IP or IP+username is locked out. Message includes a countdown. |
wldelay_xmlrpc_blocked | XML-RPC | XML-RPC authentication is disabled on this site. |
wldelay_attempts_remaining | Login form | Shows remaining attempts before lockout (appended to existing error). |
Core Functions
| Function | Returns | Description |
|---|---|---|
wldelay_get_options() | array | Get cached plugin options. Uses a global cache to avoid repeated get_option() calls. |
wldelay_clear_options_cache() | void | Clear the in-memory options cache. Called automatically when settings are updated. |
wldelay_get_delay_value( $failure_count ) | int | Calculate the delay in seconds based on current settings (fixed, random, or progressive) and the number of failures. |
wldelay_get_client_ip() | string | Get the client IP address. Checks proxy headers only if wldelay_trust_proxy_headers is enabled. |
wldelay_get_lockout_attempt_strategy( $options ) | string | Returns 'ip' or 'ip_username'. |
wldelay_normalize_username( $username ) | string | Lowercases and sanitizes a username for consistent tracking. |
wldelay_get_attempt_identifier( $ip, $username, $options ) | string | Returns $ip or $ip|$username based on strategy. |
wldelay_get_requested_login_username() | string | Gets the normalized username from $_POST['log']. |
wldelay_is_xmlrpc_request() | bool | Checks XMLRPC_REQUEST constant and REQUEST_URI. |
wldelay_is_rest_request() | bool | Checks if the current request is a WordPress REST API request. Since v2.1.2. |
wldelay_is_application_password_attempt() | bool | Checks for PHP_AUTH_USER and PHP_AUTH_PW server headers (Basic Auth). Since v2.1.2. |
wldelay_get_login_source() | string | Returns 'xmlrpc', 'rest', 'application-password', or 'wp-login'. |
wldelay_custom_login_is_active() | bool | Whether the Custom Login URL feature is enabled and the slug is valid. Since v2.2.3. |
wldelay_get_custom_login_slug() | string | Returns the configured custom login slug (default 'my-login'). Since v2.2.3. |
Lockout Functions
| Function | Returns | Description |
|---|---|---|
wldelay_is_ip_locked( $ip, $username ) | bool | Check if an IP (or IP+username) is currently locked out. |
wldelay_lock_ip( $ip, $username ) | void | Lock an IP/username. Sets a transient with the lockout duration. |
wldelay_get_failure_count( $ip, $username ) | int | Get the current failure count for an IP/username. |
wldelay_get_lockout_remaining_seconds( $ip, $username ) | int | Seconds remaining on a lockout (0 if not locked). |
wldelay_get_lockout_duration_seconds( $options ) | int | Get the configured lockout duration in seconds. |
wldelay_get_lockout_error_message( $ip, $username ) | string | Build the lockout error message with human-readable countdown. |
wldelay_track_failed_attempt( $username ) | int | Increment failure counter, trigger lockout and/or email if thresholds are met. |
wldelay_send_notification_email( $ip, $username, $attempts ) | void | Send email alert (respects site-wide cooldown). |
Whitelist Functions
| Function | Returns | Description |
|---|---|---|
wldelay_is_ip_whitelisted( $ip ) | bool | Check if an IP is in the whitelist. Returns false if whitelist is disabled. |
wldelay_ip_in_range( $ip, $range ) | bool | Check if an IP matches a single address or CIDR range. Supports both IPv4 and IPv6. |
Recovery Functions
| Function | Returns | Description |
|---|---|---|
wldelay_delete_lockout_for_ip( $ip ) | int | Remove all lockout and failure transients for a specific IP. Returns the count of removed entries. Cleans up both ip and ip_username strategy keys. |
wldelay_flush_lockout_transients() | int | Clear all lockout and failure transients site-wide. Uses the transient registry and falls back to a database scan. Returns total entries removed. |
wldelay_register_transient_key( $key ) | void | Add a transient key to the registry (wldelay_transient_registry option). Called automatically when failure/lockout transients are created. |
wldelay_unregister_transient_key( $key ) | void | Remove a transient key from the registry. Called automatically during cleanup. |
Database Functions
| Function | Returns | Description |
|---|---|---|
wldelay_get_log_table_name() | string | Returns the full table name: {$wpdb->prefix}wldelay_login_log. |
wldelay_create_log_table() | void | Creates or upgrades the log table using dbDelta(). Called on activation and version upgrades. |
wldelay_log_failed_attempt( $ip, $username, $source ) | void | Inserts a row into the log table and invalidates the dashboard cache. |
wldelay_get_recent_failed_attempts( $limit ) | array | Returns recent failed attempts ordered by time (used by the dashboard widget). |
wldelay_cleanup_old_logs() | void | Deletes entries older than the retention period in batches of 1,000. |
Export & Log Query Functions
Added in v2.1.2–2.2.3 for CSV export, filtered log queries, and trend analytics.
| Function | Returns | Description |
|---|---|---|
wldelay_get_login_log_attempts( $limit, $offset, $filters ) | array | Query log entries with optional filters (source, IP, username, date range) and pagination. All filters are applied at the SQL level. |
wldelay_get_export_login_log_url( $filters ) | string | Build a nonce-protected URL for the CSV export endpoint. Accepts optional filter array. |
wldelay_handle_export_login_log() | void | Main CSV export handler. Streams results in 1,000-row batches with flush() after each batch. Sets Content-Type: text/csv and Content-Disposition: attachment headers. |
wldelay_sanitize_login_log_filters( $filters ) | array | Validate and sanitize filter input. Checks IP format, date format (Y-m-d), and auto-swaps reversed date ranges. |
wldelay_get_login_log_filters_from_request() | array | Extract filters from $_GET using expected keys only (wldelay_log_source, wldelay_log_ip, wldelay_log_username, wldelay_log_from, wldelay_log_to). |
wldelay_csv_sanitize_cell( $value ) | string | Mitigate CSV formula injection by prepending a single quote to values starting with =, +, -, @, or whitespace. |
wldelay_get_top_ips( $days, $limit ) | array | Returns the top attacking IP addresses over the last $days days (default 7), limited to $limit results (default 10). Each row has ip_address and attempt_count. Since v2.2.3. |
wldelay_get_top_usernames( $days, $limit ) | array | Returns the most targeted usernames over the last $days days (default 7), limited to $limit results (default 10). Each row has username and attempt_count. Since v2.2.3. |
wldelay_get_daily_attempts( $days ) | array | Returns daily failed attempt totals for the last $days days (default 7). Each row has day (Y-m-d) and attempt_count. Since v2.2.3. |
Database Schema
{$wpdb->prefix}wldelay_login_log
| Column | Type | Key | Description |
|---|---|---|---|
id | bigint(20) unsigned | PRIMARY | Auto-increment ID |
ip_address | varchar(45) | INDEX | IP address (supports IPv4 and IPv6) |
username | varchar(60) | — | Username attempted |
attempted_at | datetime | INDEX | Timestamp of the attempt |
source | varchar(20) | INDEX | wp-login, xmlrpc, rest, or application-password |
Created on plugin activation via dbDelta(). Schema version is tracked in the wldelay_db_version option.
Transient Keys
All transient keys use MD5 hashing of the attempt identifier for security (prevents enumeration).
| Key Pattern | TTL | Value | Description |
|---|---|---|---|
wldelay_fails_{md5(id)} | 1 hour | int (count) | Failed attempt counter per IP or IP+username |
wldelay_lockout_{md5(id)} | Lockout duration | int (timestamp) | Lockout start time per IP or IP+username |
wldelay_email_cooldown | Cooldown minutes | int (timestamp) | Site-wide email rate limiter |
wldelay_dashboard_attempts | 2 minutes | array | Cached dashboard widget data |
The id in failure and lockout keys is the attempt identifier: either the IP address (ip strategy) or IP|username (ip_username strategy).
Options Reference
All settings are stored in a single serialized array under the wldelay_options WordPress option.
| Key | Type | Default | Description |
|---|---|---|---|
wldelay_delay | int | 1 | Fixed delay in seconds (0–10) |
wldelay_delay_random | bool | false | Use random delay |
wldelay_delay_random_min | int | 1 | Min random delay (1–10) |
wldelay_delay_random_max | int | 5 | Max random delay (1–10) |
wldelay_progressive_enabled | bool | false | Enable progressive delay |
wldelay_progressive_increment | int | 1 | Increment per attempt (1–10) |
wldelay_progressive_max | int | 30 | Max progressive delay (5–60) |
wldelay_email_enabled | bool | false | Enable email notifications |
wldelay_email_threshold | int | 5 | Failures before email (1–100) |
wldelay_email_address | string | '' | Notification email (fallback: admin_email) |
wldelay_email_cooldown | int | 5 | Minutes between emails (0–60) |
wldelay_lockout_enabled | bool | false | Enable IP lockout |
wldelay_lockout_threshold | int | 10 | Failures before lockout (1–100) |
wldelay_lockout_duration | int | 60 | Lockout duration in minutes (1–1440) |
wldelay_lockout_attempt_strategy | string | 'ip' | 'ip' or 'ip_username' |
wldelay_trust_proxy_headers | bool | false | Trust X-Forwarded-For headers |
wldelay_whitelist_enabled | bool | false | Enable IP whitelist |
wldelay_whitelist_ips | string | '' | Newline-separated IPs/CIDR ranges |
wldelay_log_retention_days | int | 30 | Log retention in days (0–365, 0 = forever) |
wldelay_xmlrpc_enabled | bool | false | Enable XML-RPC protection |
wldelay_xmlrpc_block | bool | false | Block all XML-RPC auth |
wldelay_rest_enabled | bool | true | Enable REST API protection (v2.1.2+) |
wldelay_application_password_enabled | bool | true | Enable application-password protection (v2.1.2+) |
wldelay_custom_login_enabled | bool | false | Enable custom login URL (v2.2.3+) |
wldelay_custom_login_slug | string | 'my-login' | Custom login URL slug (lowercase, numbers, hyphens only) (v2.2.3+) |
Other WordPress Options
| Option Name | Description |
|---|---|
wldelay_db_version | Database schema version (tracks WLDELAY_VERSION) |
wldelay_plugin_version | Current plugin version |
wldelay_previous_version | Previous version (for upgrade detection) |
wldelay_name_change_notice_dismissed | Whether the rename notice was dismissed |
wldelay_transient_registry | Array of active transient keys (used by flush-lockouts for cleanup) |
Cron Jobs
| Hook | Schedule | Function | Description |
|---|---|---|---|
wldelay_cleanup_logs | Daily | wldelay_cleanup_old_logs() | Deletes log entries older than the retention period. Processes in batches of 1,000 to avoid table locks. Scheduled on wp hook, unscheduled on deactivation. |
WP-CLI
Login Delay Shield registers commands under the login-delay-shield namespace. Available when WP-CLI is installed.
| Command | Arguments | Description |
|---|---|---|
wp login-delay-shield unlock-ip <ip> | <ip> — IPv4 or IPv6 address | Removes lockout and failure transients for the specified IP. Validates IP format. Returns count of removed entries. |
wp login-delay-shield flush-lockouts | — | Clears all lockout and failure transients site-wide. Uses the transient registry and falls back to a database scan. Also clears the registry itself. |