array(
'label' => __( 'Safety Analytics', 'admin-safety-guard' ),
'icon' => '',
'class' => '',
),
'security-core' => array(
'label' => __( 'Security Core', 'admin-safety-guard' ),
'icon' => '',
'class' => '',
'sub' => array(
'limit-login-attempts' => array(
'label' => __( 'Limit Login Attempts', 'admin-safety-guard' ),
),
'custom-login-url' => array(
'label' => __( 'Custom Login URL', 'admin-safety-guard' ),
),
'two-factor-auth' => array(
'label' => __( 'Two-Factor Authentication', 'admin-safety-guard' ),
),
'password-protection' => array(
'label' => __( 'Password Protection', 'admin-safety-guard' ),
),
'session-security' => array(
'label' => __( 'Session Security', 'admin-safety-guard' ),
),
'recaptcha' => array(
'label' => __( 'reCAPTCHA', 'admin-safety-guard' ),
),
'admin-bar' => array(
'label' => __( 'Hide Admin Bar', 'admin-safety-guard' ),
),
'passwordless-login' => array(
'label' => __( 'Passwordless Login', 'admin-safety-guard' ),
'is_pro' => true,
),
'social-login' => array(
'label' => __( 'Social Login', 'admin-safety-guard' ),
'is_pro' => true,
),
'2fa-using-mobile-app' => array(
'label' => __( '2FA Using Mobile App', 'admin-safety-guard' ),
'is_pro' => true,
),
'strong-password' => array(
'label' => __( 'Strong Password', 'admin-safety-guard' ),
'is_pro' => true,
),
'table-prefix-check' => array(
'label' => __( 'Table Prefix Check', 'admin-safety-guard' ),
'is_pro' => true,
),
),
),
'firewall-malware' => array(
'label' => __( 'Firewall & Malware', 'admin-safety-guard' ),
'icon' => '',
'class' => '',
),
'login-logs-activity' => array(
'label' => __( 'Monitoring & Analytics', 'admin-safety-guard' ),
'icon' => '',
'class' => '',
),
'privacy-hardening' => array(
'label' => __( 'Privacy & Hardening', 'admin-safety-guard' ),
'icon' => '',
'class' => '',
),
'customize' => array(
'label' => __( 'Customization & Access', 'admin-safety-guard' ),
'icon' => '',
'class' => '',
),
)
);
}
}
if ( !function_exists( 'tpsa_settings_fields' ) ) {
/**
* Returns an associative array of settings fields available for the Secure Admin plugin.
*
* The array is composed of feature slugs as keys and arrays of 'fields' as values.
* Each 'fields' array is composed of field slugs as keys and arrays of 'type', 'label', 'class', 'id', and 'desc' as values.
*
* @since 1.0.0
*
* @filter tpsa_settings_fields
*
* @return array Associative array of available settings fields.
*/
function tpsa_settings_fields() {
return apply_filters(
'tpsa_settings_fields',
array(
'analytics' => array(
'fields' => array(),
),
'admin-bar' => array(
'fields' => array(
'enable' => array(
'type' => 'switch',
'label' => __( 'Hide Admin Bar', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'To enable/disable admin bar', 'admin-safety-guard' ),
'default' => 0,
),
'exclude' => array(
'type' => 'multi-check',
'label' => __( 'Exclude', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Exclude users', 'admin-safety-guard' ),
'default' => 'light',
'options' => get_tps_all_user_roles(),
),
),
),
'custom-login-url' => array(
'fields' => array(
'enable' => array(
'type' => 'switch',
'label' => __( 'Enable Custom Login URL', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Moves sign-in to your own address. wp-login.php and wp-register.php then return 404 for everyone.', 'admin-safety-guard' ),
'default' => 0,
),
'login-url' => array(
'type' => 'text',
'label' => __( 'Login Address', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Your private sign-in slug, e.g. my-secure-login. Bookmark it — it is the only way in once saved.', 'admin-safety-guard' ),
'default' => '',
),
'redirect-url' => array(
'type' => 'text',
'label' => __( 'Send Strangers To', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Where logged-out visitors go if they try to open wp-admin. Leave empty for the home page.', 'admin-safety-guard' ),
'default' => '',
),
'logout-url' => array(
'type' => 'text',
'label' => __( 'After Logout, Go To', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Where users land after signing out. Leave empty for the WordPress default.', 'admin-safety-guard' ),
'default' => '',
),
),
),
'limit-login-attempts' => array(
'fields' => array(
'enable' => array(
'type' => 'switch',
'label' => __( 'Enable Brute-Force Protection', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Locks out an IP address after too many failed sign-ins. Covers wp-login, XML-RPC, Application Passwords and theme login forms.', 'admin-safety-guard' ),
'default' => 0,
),
'max-attempts' => array(
'type' => 'number',
'label' => __( 'Failed Attempts Before Lockout', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Failed attempts allowed before an address is locked out. Recommended: 3-5.', 'admin-safety-guard' ),
'default' => 3,
),
'block-for' => array(
'type' => 'number',
'label' => __( 'Lockout Duration (minutes)', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'How long a locked-out address must wait before trying again. Recommended: 15-60.', 'admin-safety-guard' ),
'default' => 15,
),
'max-lockout' => array(
'type' => 'number',
'label' => __( 'Lockouts Before 24-Hour Block', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Lockouts within 24 hours before the address is blocked for a full day. Recommended: 3.', 'admin-safety-guard' ),
'default' => 3,
),
'show-remaining' => array(
'type' => 'switch',
'label' => __( 'Show Remaining Attempts', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Adds "2 attempts remaining" to the login error, so real users know where they stand.', 'admin-safety-guard' ),
'default' => 1,
),
'notify-admin' => array(
'type' => 'switch',
'label' => __( 'Email Me About Lockouts', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Email the admin on each lockout. Throttled to one message per address per hour.', 'admin-safety-guard' ),
'default' => 0,
),
'block-message' => array(
'type' => 'textarea',
'label' => __( 'Lockout Message', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Shown while an address is locked out. Use {minutes} for the time left.', 'admin-safety-guard' ),
'default' => __( 'You have been locked out due to too many failed sign-in attempts.', 'admin-safety-guard' ),
),
'whitelist-ip' => array(
'type' => 'single-repeater',
'label' => __( 'Trusted IP Addresses', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Never locked out. Add your own IP. Accepts 203.0.113.7, 203.0.113.0/24 or 203.0.113.* (IPv6 too).', 'admin-safety-guard' ),
'default' => array(),
),
'block-ip-address' => array(
'type' => 'single-repeater',
'label' => __( 'Always Blocked IP Addresses', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Never allowed to reach the login page. Same address, CIDR and wildcard formats.', 'admin-safety-guard' ),
'default' => array(),
),
),
),
'session-security' => array(
'fields' => array(
'enable' => array(
'type' => 'switch',
'label' => __( 'Enable Session Security', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Protects the period after sign-in. By default a WordPress session stays valid for 2 days — 14 with "Remember Me" — however long the screen is left unattended.', 'admin-safety-guard' ),
'default' => 0,
),
'idle-minutes' => array(
'type' => 'number',
'label' => __( 'Sign Out After Inactivity (minutes)', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Ends an idle session automatically. Recommended: 30-60. Set 0 to disable.', 'admin-safety-guard' ),
'default' => 0,
),
'session-hours' => array(
'type' => 'number',
'label' => __( 'Maximum Session Length (hours)', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'How long a normal sign-in lasts. WordPress default is 48. Set 0 to leave it alone.', 'admin-safety-guard' ),
'default' => 0,
),
'remember-hours' => array(
'type' => 'number',
'label' => __( '"Remember Me" Length (hours)', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'WordPress default is 336 (14 days), which is a long time for a stolen cookie to stay valid. Set 0 to leave it alone.', 'admin-safety-guard' ),
'default' => 0,
),
'logout-on-password-change' => array(
'type' => 'switch',
'label' => __( 'End Other Sessions On Password Change', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'When someone changes their password, every other device is signed out — so changing it actually locks an intruder out.', 'admin-safety-guard' ),
'default' => 1,
),
'bind-to-ip' => array(
'type' => 'switch',
'label' => __( 'Tie Session To IP Address', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'A stolen session cookie stops working from anywhere else. Signs out users on mobile networks whose IP changes — best for fixed office connections.', 'admin-safety-guard' ),
'default' => 0,
),
),
),
'login-logs-activity' => array(
'fields' => array(
'notify-new-ip' => array(
'type' => 'switch',
'label' => __( 'Alert On New Admin Location', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Email you when an administrator signs in from an IP address never used before — an early sign of a stolen password. One alert per account and address per day.', 'admin-safety-guard' ),
'default' => 0,
),
),
),
'recaptcha' => array(
'fields' => array(
'enable' => array(
'type' => 'switch',
'label' => __( 'Enable', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'To enable/disable reCAPTCHA.', 'admin-safety-guard' ),
'default' => 0,
),
'version' => array(
'type' => 'option',
'label' => __( 'Version', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Select Google reCAPTCHA version', 'admin-safety-guard' ),
'default' => '',
'options' => array(
'v2' => 'v2',
'v3' => 'v3',
),
),
'site-key' => array(
'type' => 'text',
'label' => __( 'Site Key', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => "Enter you Google reCAPTCHA site key. Get site key",
'default' => '',
),
'secret-key' => array(
'type' => 'text',
'label' => __( 'Secret Key', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => "Enter you Google reCAPTCHA screet key. Get secret key",
'default' => '',
),
'theme' => array(
'type' => 'option',
'label' => __( 'Theme', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Select your preferred theme', 'admin-safety-guard' ),
'default' => 'light',
'options' => array(
'light' => 'Light Theme',
'dark' => 'Dark Theme',
),
),
),
),
'two-factor-auth' => array(
'fields' => array(
'otp-email' => array(
'type' => 'switch',
'label' => __( 'Require Email Code At Sign-In', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'After the password, a one-time code is emailed and must be entered. A stolen password alone is then useless.', 'admin-safety-guard' ),
'default' => 0,
),
'otp-roles' => array(
'type' => 'multi-check',
'label' => __( 'Apply To Roles', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Leave all unticked to protect every account. Ticking only Administrator and Editor keeps sign-in simple for customers and subscribers.', 'admin-safety-guard' ),
'default' => array(),
'options' => get_tps_all_user_roles(),
),
'otp-validity' => array(
'type' => 'number',
'label' => __( 'Code Valid For (minutes)', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'How long a code works before it expires. Recommended: 5. Shorter is safer.', 'admin-safety-guard' ),
'default' => 5,
),
'otp-length' => array(
'type' => 'number',
'label' => __( 'Code Length', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Number of digits, between 4 and 8. Recommended: 6.', 'admin-safety-guard' ),
'default' => 6,
),
'otp-max-tries' => array(
'type' => 'number',
'label' => __( 'Attempts Per Code', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Wrong guesses allowed before the code is destroyed and sign-in restarts. Recommended: 5.', 'admin-safety-guard' ),
'default' => 5,
),
'email-subject' => array(
'type' => 'text',
'label' => __( 'Email Subject', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Placeholders: {otp}, {site_name}. Leave empty for the default.', 'admin-safety-guard' ),
'default' => '{site_name} sign-in code: {otp}',
),
'email-body' => array(
'type' => 'textarea',
'label' => __( 'Custom Email Body', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Placeholders: {otp}, {site_name}. Leave empty for the styled default email.', 'admin-safety-guard' ),
'default' => '',
),
),
),
'password-protection' => array(
'fields' => array(
'enable' => array(
'type' => 'switch',
'label' => __( 'Enable', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'To enable/disable password protection.', 'admin-safety-guard' ),
'default' => 0,
),
'password' => array(
'type' => 'password',
'label' => __( 'Set Password', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Password-protect the entire site to hide the content from public view and search engine bots / crawlers.', 'admin-safety-guard' ),
'default' => '',
),
'password-expiry' => array(
'type' => 'number',
'label' => __( 'Password Access Duration', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'How long visitors can access the site after entering the correct password.', 'admin-safety-guard' ),
'default' => '15',
),
'exclude' => array(
'type' => 'multi-check',
'label' => __( 'Exclude', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Exclude user from this password protection', 'admin-safety-guard' ),
'default' => 'light',
'options' => array_merge(
array(
'all-login-user' => 'All logged in users',
),
get_tps_all_user_roles()
),
),
),
),
'privacy-hardening' => array(
'fields' => array(
'xml-rpc-enable' => array(
'type' => 'switch',
'label' => __( 'Disable XML-RPC', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Closes a legacy endpoint used for brute-force and DDoS amplification. Leave off if you use the WordPress mobile app or Jetpack.', 'admin-safety-guard' ),
'default' => 0,
),
'block-author-enum' => array(
'type' => 'switch',
'label' => __( 'Block Username Discovery', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Stops ?author=1 and the REST users list from revealing your usernames — the usual first step before a password attack.', 'admin-safety-guard' ),
'default' => 0,
),
'generic-login-errors' => array(
'type' => 'switch',
'label' => __( 'Hide Which Detail Was Wrong', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'WordPress says whether the username or the password was wrong, confirming which accounts exist. This shows one message for both.', 'admin-safety-guard' ),
'default' => 0,
),
'hide-version' => array(
'type' => 'switch',
'label' => __( 'Hide WordPress Version', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Removes the version from your page source, feeds and core asset URLs, so attackers cannot match your site to published exploits.', 'admin-safety-guard' ),
'default' => 0,
),
'remove-meta' => array(
'type' => 'switch',
'label' => __( 'Remove Unused Meta Tags', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Strips the RSD, Windows Live Writer and shortlink tags. Legacy publishing hints that no modern site uses.', 'admin-safety-guard' ),
'default' => 0,
),
'disable-pingback' => array(
'type' => 'switch',
'label' => __( 'Disable Pingbacks', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Blocks pingback methods and the X-Pingback header, so your site cannot be used to flood someone else\'s.', 'admin-safety-guard' ),
'default' => 0,
),
'disable-file-edit' => array(
'type' => 'switch',
'label' => __( 'Disable Theme & Plugin Editor', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Removes the built-in code editors. A stolen admin session can otherwise be turned into running PHP in two clicks.', 'admin-safety-guard' ),
'default' => 0,
),
'security-headers' => array(
'type' => 'switch',
'label' => __( 'Send Security Headers', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Adds browser-enforced protections to every page: blocks clickjacking, MIME-type sniffing, referrer leaks and unwanted camera/microphone access.', 'admin-safety-guard' ),
'default' => 0,
),
'disable-app-passwords' => array(
'type' => 'switch',
'label' => __( 'Disable Application Passwords', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Turns off the API password feature site-wide. Leave off if any app or integration connects to this site.', 'admin-safety-guard' ),
'default' => 0,
),
),
),
'customize' => array(
'fields' => array(
'enable' => array(
'type' => 'switch',
'label' => __( 'Enable Login Branding', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Replace the WordPress logo and styling on your sign-in screen with your own.', 'admin-safety-guard' ),
'default' => 0,
),
'logo' => array(
'type' => 'upload',
'label' => __( 'Logo', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Shown above the form. A square image around 84x84 px works best; larger images are scaled to fit.', 'admin-safety-guard' ),
'default' => '',
),
'logo-width' => array(
'type' => 'number',
'label' => __( 'Logo Width', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'In pixels, up to 600.', 'admin-safety-guard' ),
'default' => 84,
),
'logo-height' => array(
'type' => 'number',
'label' => __( 'Logo Height', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'In pixels, up to 600.', 'admin-safety-guard' ),
'default' => 84,
),
'logo-url' => array(
'type' => 'text',
'label' => __( 'Logo Links To', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Where clicking the logo goes. Leave empty for your home page (WordPress sends it to wordpress.org by default).', 'admin-safety-guard' ),
'default' => '',
),
'logo-text' => array(
'type' => 'text',
'label' => __( 'Logo Alt Text', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Read aloud by screen readers. Leave empty to use your site title.', 'admin-safety-guard' ),
'default' => '',
),
'bg-color' => array(
'type' => 'color',
'label' => __( 'Page Background', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Behind the sign-in box. Leave empty to keep the WordPress default.', 'admin-safety-guard' ),
'default' => '',
),
'bg-image' => array(
'type' => 'upload',
'label' => __( 'Background Image', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Covers the whole page. Sits on top of the background colour.', 'admin-safety-guard' ),
'default' => '',
),
'form-bg-color' => array(
'type' => 'color',
'label' => __( 'Form Background', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'The white card around the fields.', 'admin-safety-guard' ),
'default' => '',
),
'form-rounded' => array(
'type' => 'switch',
'label' => __( 'Rounded Form Corners', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Softens the corners of the sign-in card.', 'admin-safety-guard' ),
'default' => 0,
),
'text-color' => array(
'type' => 'color',
'label' => __( 'Text Colour', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Field labels and helper text. Useful when using a dark background.', 'admin-safety-guard' ),
'default' => '',
),
'link-color' => array(
'type' => 'color',
'label' => __( 'Link Colour', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'The links below the form.', 'admin-safety-guard' ),
'default' => '',
),
'button-color' => array(
'type' => 'color',
'label' => __( 'Button Colour', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'The Log In button, and the highlight on a focused field.', 'admin-safety-guard' ),
'default' => '',
),
'hide-logo' => array(
'type' => 'switch',
'label' => __( 'Hide Logo Entirely', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'For a minimal, text-free sign-in screen.', 'admin-safety-guard' ),
'default' => 0,
),
'hide-lost-password' => array(
'type' => 'switch',
'label' => __( 'Hide "Lost your password?"', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Hides the link. Password reset still works if someone has the direct URL.', 'admin-safety-guard' ),
'default' => 0,
),
'hide-register' => array(
'type' => 'switch',
'label' => __( 'Hide Register Link', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Hides the sign-up link when registration is open but you link to it yourself.', 'admin-safety-guard' ),
'default' => 0,
),
'hide-back-to-site' => array(
'type' => 'switch',
'label' => __( 'Hide "Back to site"', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Removes the link under the form.', 'admin-safety-guard' ),
'default' => 0,
),
'hide-language-switcher' => array(
'type' => 'switch',
'label' => __( 'Hide Language Switcher', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Only appears on multilingual installs.', 'admin-safety-guard' ),
'default' => 0,
),
'remember-me' => array(
'type' => 'switch',
'label' => __( 'Tick "Remember Me" By Default', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Convenient on trusted devices. Leave off for shared or public computers.', 'admin-safety-guard' ),
'default' => 0,
),
'custom-css' => array(
'type' => 'textarea',
'label' => __( 'Custom CSS', 'admin-safety-guard' ),
'class' => '',
'id' => '',
'desc' => __( 'Applied to the sign-in screen only. Tags and scripts are stripped.', 'admin-safety-guard' ),
'default' => '',
),
'login-template' => array(
'type' => 'login-template',
'class' => '',
'id' => '',
'default' => 0,
),
),
),
)
);
}
}
if ( !function_exists( 'get_tpsa_site_login_path' ) ) {
/**
* Returns the relative path of the WordPress login URL.
*
* @since 1.0.0
*
* @return string The relative path of the WordPress login URL.
*/
function get_tpsa_site_login_path() {
$site_url = get_site_url();
$login_url = wp_login_url();
// Remove site URL from login URL
$relative_path = str_replace( $site_url, '', $login_url );
// Trim any leading/trailing slashes
$relative_path = trim( $relative_path, '/' );
return $relative_path;
}
}
if ( !function_exists( 'get_tpsa_prefix' ) ) {
/**
* Returns the prefix used for options and other identifiers throughout the plugin.
*
* @since 1.0.0
*
* @return string The prefix used by the plugin.
*/
function get_tpsa_prefix() {
return 'tpsa_';
}
}
/**
* Returns the option name used to store the settings for a given screen slug.
*
* @since 1.0.0
*
* @param string $screen_slug The screen slug.
*
* @return string The option name used to store the settings.
*/
if ( !function_exists( 'get_tpsa_settings_option_name' ) ) {
function get_tpsa_settings_option_name( $screen_slug ) {
return get_tpsa_prefix() . $screen_slug . '_settings';
}
}
/**
* Returns the full table name with the TPSA prefix.
*
* This function combines the global WordPress table prefix with the TPSA-specific
* prefix and the provided table name to generate the full table name.
*
* @since 1.0.0
*
* @param string $table_name The base name of the table.
*
* @return string The full table name with the TPSA prefix.
*/
if ( !function_exists( 'get_tpsa_db_table_name' ) ) {
function get_tpsa_db_table_name( $table_name ) {
global $wpdb;
$prefix = $wpdb->prefix . TPSA_PREFIX . '_';
return $prefix . $table_name;
}
}
if ( !function_exists( 'get_tps_all_user_roles' ) ) {
function get_tps_all_user_roles() {
global $wp_roles;
if ( !isset( $wp_roles ) ) {
$wp_roles = new WP_Roles();
}
$roles = $wp_roles->roles;
$role_names = [];
foreach ( $roles as $key => $role ) {
$role_names[$key] = $role['name'];
}
return $role_names;
}
}
if ( !function_exists( 'tpsa_get_client_ip' ) ) {
/**
* Resolve the client IP address in a spoofing-resistant way.
*
* By default only REMOTE_ADDR is trusted, because it is set by the web server
* from the actual TCP connection and cannot be forged by the client. Request
* headers such as X-Forwarded-For / Client-IP ARE attacker-controllable and
* must NOT be trusted unless the site genuinely sits behind a reverse proxy or
* CDN (Cloudflare, load balancer, etc.) that sets them.
*
* Sites behind such a proxy can opt in via the `tpsa_trust_proxy_headers`
* filter, after which the first valid address from the configured header list
* is used. The candidate list is filterable via `tpsa_proxy_ip_headers`.
*
* @since 1.3.0
*
* @return string A validated IP address, or '0.0.0.0' if none could be determined.
*/
function tpsa_get_client_ip() {
$remote_addr = isset( $_SERVER['REMOTE_ADDR'] )
? trim( (string) wp_unslash( $_SERVER['REMOTE_ADDR'] ) )
: '';
// Trust the server-provided connection address by default.
$ip = filter_var( $remote_addr, FILTER_VALIDATE_IP ) ? $remote_addr : '';
/**
* Whether to trust client-supplied forwarding headers.
*
* Leave this false unless the site is behind a trusted reverse proxy / CDN,
* otherwise visitors can spoof their IP and bypass IP-based protections.
*
* @since 1.3.0
*
* @param bool $trust Default false.
*/
if ( apply_filters( 'tpsa_trust_proxy_headers', false ) ) {
/**
* Ordered list of headers to inspect when proxy headers are trusted.
*
* @since 1.3.0
*
* @param string[] $headers Server header keys, highest priority first.
*/
$headers = apply_filters(
'tpsa_proxy_ip_headers',
array(
'HTTP_CF_CONNECTING_IP', // Cloudflare.
'HTTP_X_FORWARDED_FOR',
'HTTP_X_REAL_IP',
'HTTP_CLIENT_IP',
)
);
foreach ( $headers as $header ) {
if ( empty( $_SERVER[$header] ) ) {
continue;
}
// A forwarded header may be a comma-separated chain; take the
// first syntactically valid address.
$candidates = explode( ',', (string) wp_unslash( $_SERVER[$header] ) );
foreach ( $candidates as $candidate ) {
$candidate = trim( $candidate );
if ( filter_var( $candidate, FILTER_VALIDATE_IP ) ) {
$ip = $candidate;
break 2;
}
}
}
}
if ( '' === $ip ) {
$ip = '0.0.0.0';
}
// Normalize IPv6 loopback to IPv4 for consistent storage/matching.
if ( '::1' === $ip ) {
$ip = '127.0.0.1';
}
return $ip;
}
}
if ( !function_exists( 'tpsm_saved_remote_data' ) ) {
/**
* Sends the current user's information to a remote server.
*
* This function retrieves the currently logged-in user's full name,
* email address, and the site URL, and sends this data to a specified
* remote server endpoint using a POST request. This is intended to
* collect user data for integration with a remote service.
*
* @return void
*/
function tpsm_saved_remote_data() {
$current_user = wp_get_current_user();
// Check if a user is logged in
if ( !$current_user || 0 === $current_user->ID ) {
return;
}
// Get full name (first + last or fallback to display name)
$full_name = trim( $current_user->first_name . ' ' . $current_user->last_name );
if ( empty( $full_name ) ) {
$full_name = $current_user->display_name;
}
$email_address = $current_user->user_email;
$site_url = get_site_url();
wp_remote_post( 'https://themepaste.com/wp-json/v2/collect-email/admin-safety-guard', [
'headers' => [
'X-Auth-Token' => 'c7fc312817194d30c79da538204eaec3',
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( [
'email_address' => $email_address,
'full_name' => $full_name,
'site_url' => $site_url,
] ),
] );
}
}
if ( !function_exists( 'login_page_templates' ) ) {
/**
* Returns an associative array of available login page templates.
*
* The array is composed of template slugs as keys and arrays of 'label' and 'css' as values.
*
* @since 1.0.0
*
* @return array Associative array of available login page templates.
*/
/**
* Get login page templates.
*
* @return array
*/
function login_page_templates() {
$templates = [
'default' => ['label' => __( 'WordPress Default', 'admin-safety-guard' ), 'css' => '', 'smalImg' => 'https://placehold.co/200x200', 'bigImg' => 'https://placehold.co/800x600'],
'classic' => ['label' => __( 'Classic Card', 'admin-safety-guard' ), 'css' => 'classic.css', 'smalImg' => 'https://placehold.co/200x200', 'bigImg' => 'https://placehold.co/800x600'],
'glass' => ['label' => __( 'Frosted Glass', 'admin-safety-guard' ), 'css' => 'glass.css', 'smalImg' => 'https://placehold.co/200x200', 'bigImg' => 'https://placehold.co/800x600'],
'split' => ['label' => __( 'Split Hero', 'admin-safety-guard' ), 'css' => 'split.css', 'smalImg' => 'https://placehold.co/200x200', 'bigImg' => 'https://placehold.co/800x600'],
'gradient' => ['label' => __( 'Soft Gradient', 'admin-safety-guard' ), 'css' => 'gradient.css', 'smalImg' => 'https://placehold.co/200x200', 'bigImg' => 'https://placehold.co/800x600'],
];
/**
* Filter: Modify the available login templates.
*
* @param array $templates Associative array of template definitions.
*/
return apply_filters( 'customize/login_templates', $templates );
}
}
if ( !function_exists( 'tp_asg_pro_current_prefix' ) ) {
function tp_asg_pro_current_prefix() {
global $wpdb;
return $wpdb->prefix;
}
}
/**
* Generate a random DB table prefix string.
*
* @param int $len Optional length of the prefix. Default is 5.
* @return string A random DB table prefix string (e.g. 'abcde_').
*/
if ( !function_exists( 'tp_asg_pro_random_prefix' ) ) {
function tp_asg_pro_random_prefix( int $len = 5 ) {
$letters = 'abcdefghijklmnopqrstuvwxyz';
$alphanum = $letters . '0123456789';
// First char must be a letter.
$chars = [ $letters[ random_int( 0, 25 ) ] ];
// One guaranteed digit placed at a random inner position.
$chars[] = (string) random_int( 0, 9 );
// Fill remaining positions with random alphanumeric.
for ( $i = 2; $i < $len; $i++ ) {
$chars[] = $alphanum[ random_int( 0, strlen( $alphanum ) - 1 ) ];
}
// Shuffle positions 1..end so the digit isn't always second.
$tail = array_slice( $chars, 1 );
shuffle( $tail );
return $chars[0] . implode( '', $tail ) . '_';
}
}
if ( !function_exists( 'tp_asg_pro_is_prefix_good' ) ) {
function tp_asg_pro_is_prefix_good( $prefix ) {
// 0) Must end with "_"
if ( substr( $prefix, -1 ) !== '_' ) {
return false;
}
$clean = rtrim( $prefix, '_' );
// 1) Basic charset & shape
if ( $clean === '' ) {
return false;
}
// Only lowercase letters, digits, underscore (allow uppercase but normalize for checks)
if ( !preg_match( '/^[a-zA-Z0-9_]+$/', $clean ) ) {
return false;
}
// Start with a letter (avoid numeric start for readability)
if ( !preg_match( '/^[a-zA-Z]/', $clean ) ) {
return false;
}
// No internal double underscores
if ( strpos( $clean, '__' ) !== false ) {
return false;
}
// Normalize to lowercase for word checks
$lc = strtolower( $clean );
// 2) Length guard (excluding trailing underscore)
$len = strlen( $clean );
if ( $len < 3 || $len > 20 ) { // reasonable bounds; MySQL table name limit is 64 but keep prefixes compact
return false;
}
// 3) Require letters + numbers
if ( !preg_match( '/[a-z]/i', $clean ) || !preg_match( '/[0-9]/', $clean ) ) {
return false;
}
// 4) Block common/guessable words and environments
$bad_words = [
'wp', 'wordpress', 'blog', 'site', 'cms', 'press', 'db', 'data', 'table',
'admin', 'root', 'superadmin', 'secure', 'security', 'guard', 'shield', 'lock', 'safe', 'safety',
'school', 'college', 'university', 'edu',
'user', 'member', 'login', 'auth',
'test', 'demo', 'sample', 'example', 'sandbox', 'staging', 'stage', 'dev', 'devel', 'prod', 'production',
'alpha', 'beta', 'backup', 'bkp', 'tmp', 'temp', 'cache', 'public', 'private',
'qwerty', 'abc', 'abcd', 'abcde', 'abcd1234', 'test123', 'admin123',
];
foreach ( $bad_words as $w ) {
if ( $lc === $w || str_contains( $lc, $w ) ) {
return false;
}
}
// 5) Block SQL keywords (simple set)
$sql_keywords = [
'select', 'insert', 'update', 'delete', 'drop', 'create', 'alter', 'grant', 'revoke',
'union', 'where', 'from', 'join', 'table', 'into', 'values', 'set', 'index', 'view',
];
foreach ( $sql_keywords as $kw ) {
if ( $lc === $kw || str_contains( $lc, $kw ) ) {
return false;
}
}
// 6) Obvious sequences / repeats
// 6a) Same char repeated (aaa…)
if ( preg_match( '/^([a-z0-9])\1{3,}$/i', $clean ) ) {
return false;
}
// 6b) Strict ascending/descending sequences of length >= 5 (abcde, 12345, edcba)
$is_monotonic = function ( $s ) {
$n = strlen( $s );
if ( $n < 5 ) {
return false;
}
$asc = true;
$desc = true;
for ( $i = 1; $i < $n; $i++ ) {
$d = ord( $s[$i] ) - ord( $s[$i - 1] );
if ( $d !== 1 ) {$asc = false;}
if ( $d !== -1 ) {$desc = false;}
if ( !$asc && !$desc ) {
return false;
}
}
return $asc || $desc;
};
if ( $is_monotonic( preg_replace( '/[^a-z0-9]/i', '', $lc ) ) ) {
return false;
}
// 6c) Common keyboard runs
$keyboard_runs = ['qwerty', 'asdf', 'zxcv', '12345', '98765'];
foreach ( $keyboard_runs as $run ) {
if ( str_contains( $lc, $run ) ) {
return false;
}
}
// 7) Entropy-ish check: require variety
$letters = array_unique( str_split( preg_replace( '/[^a-z]/', '', $lc ) ) );
$digits = array_unique( str_split( preg_replace( '/[^0-9]/', '', $lc ) ) );
$unique = array_unique( str_split( $lc ) );
if ( ( count( $letters ) < 2 || count( $digits ) < 2 ) && count( $unique ) < 5 ) {
return false;
}
// 8) Specific legacy defaults
if ( $lc === 'wp' || $lc === 'wp1' || $lc === 'wp2' || $lc === 'wp3' ) {
return false;
}
return true;
}
}
if ( ! function_exists( 'tp_svg_google' ) ) {
function tp_svg_google() {
return '';
}
}
if ( ! function_exists( 'tp_svg_facebook' ) ) {
function tp_svg_facebook() {
return '';
}
}
if ( ! function_exists( 'tp_svg_twitter' ) ) {
function tp_svg_twitter() {
return '';
}
}
if ( ! function_exists( 'tp_svg_linkedin' ) ) {
function tp_svg_linkedin() {
return '';
}
}
if ( !function_exists( 'tpsa_all_features' ) ):
function tpsa_all_features() {
$features = tpsa_settings_option();
$all_features_list = [];
foreach ( $features as $feature_slug => $feature_data ) {
if ( isset( $feature_data['sub'] ) ) {
$features = $feature_data['sub'];
foreach ( $features as $key => $value ) {
$all_features_list[] = $key;
}
} else {
$all_features_list[] = $feature_slug;
}
}
return $all_features_list;
}
endif;
if ( !function_exists( 'tpsa_get_features_summary' ) ):
/**
* Returns a summary of all features in the plugin.
*
* The summary object contains three properties:
* - total: The total number of features.
* - active: The number of features that are currently active (i.e. enabled).
* - inactive: The number of features that are currently inactive (i.e. disabled).
*
* @return object Summary object containing the total, active, and inactive feature counts.
*/
function tpsa_get_features_summary() {
// Cache for the duration of the request: this is called several times per
// admin page load (localize, security score, sidebar).
static $summary = null;
if ( null !== $summary ) {
return $summary;
}
$settings_fields = tpsa_settings_fields();
$all_features_list = tpsa_all_features();
$total = 0;
$active = 0;
$inactive = 0;
$all_active_features = [];
foreach ( $all_features_list as $feature_slug ) {
$fields = isset( $settings_fields[$feature_slug]['fields'] ) ? $settings_fields[$feature_slug]['fields'] : [];
// Skip items with no configurable fields: dashboard pages (analytics,
// monitoring) and Pro features that are not installed. They are not
// user-togglable, so counting them would make a perfect score
// impossible in the free version.
if ( empty( $fields ) ) {
continue;
}
// A feature is "scoreable" only if it exposes an on/off control:
// either a dedicated 'enable' switch or any field of type 'switch'.
$has_enable = isset( $fields['enable'] );
$switch_keys = [];
foreach ( $fields as $field_key => $field_config ) {
if ( isset( $field_config['type'] ) && 'switch' === $field_config['type'] ) {
$switch_keys[] = $field_key;
}
}
if ( !$has_enable && empty( $switch_keys ) ) {
continue;
}
// This feature can be turned on/off, so it counts toward the score.
$total++;
$option_name = get_tpsa_settings_option_name( $feature_slug );
$saved_opts = get_option( $option_name, [] );
$is_active = false;
if ( $has_enable ) {
$is_active = !empty( $saved_opts['enable'] );
} else {
foreach ( $switch_keys as $switch_key ) {
if ( !empty( $saved_opts[$switch_key] ) ) {
$is_active = true;
break;
}
}
}
if ( $is_active ) {
$active++;
$all_active_features[] = $feature_slug;
} else {
$inactive++;
}
}
$summary = (object) [
'total' => $total,
'active' => $active,
'inactive' => $inactive,
'all_active_features' => $all_active_features,
];
return $summary;
}
endif;
if ( !function_exists( 'tpsa_get_security_score' ) ):
/**
* Overall security score for this site.
*
* Delegates to the weighted audit, which grades real site conditions (HTTPS,
* core updates, exposed usernames, PHP version...) alongside plugin coverage.
* The previous version divided active plugin features by available ones, so a
* site could read 100/100 while running outdated core over plain HTTP.
*
* @return int 0-100.
*/
function tpsa_get_security_score() {
$report = \ThemePaste\SecureAdmin\Classes\SecurityAudit::report();
return (int) $report['score'];
}
endif;
if ( !function_exists( 'tpsa_get_security_label' ) ):
/**
* Human label for a score.
*
* @param int $score Score out of 100.
* @return string
*/
function tpsa_get_security_label( $score ) {
if ( $score >= 90 ) { return __( 'Excellent protection', 'admin-safety-guard' ); }
if ( $score >= 75 ) { return __( 'Strong protection', 'admin-safety-guard' ); }
if ( $score >= 60 ) { return __( 'Moderate protection', 'admin-safety-guard' ); }
if ( $score >= 40 ) { return __( 'Weak protection', 'admin-safety-guard' ); }
return __( 'Critical risk', 'admin-safety-guard' );
}
endif;
if ( !function_exists( 'tpsa_get_security_report' ) ):
/**
* The full audit report: score, grade and every check.
*
* @return array
*/
function tpsa_get_security_report() {
return \ThemePaste\SecureAdmin\Classes\SecurityAudit::report();
}
endif;
if ( !function_exists( 'tp_is_pro_active' ) ) {
/**
* Checks if the Admin Safety Guard Pro plugin is active.
*
* @return bool True if the plugin is active, false otherwise.
*/
function tp_is_pro_active() {
$plugin_path = 'admin-safety-guard-pro/admin-safety-guard-pro.php';
if ( !function_exists( 'is_plugin_active' ) ) {
include_once ABSPATH . 'wp-admin/includes/plugin.php';
}
// Normal activation
if ( is_plugin_active( $plugin_path ) ) {
return true;
}
// Network activation (multisite)
if ( is_multisite() && is_plugin_active_for_network( $plugin_path ) ) {
return true;
}
return false;
}
}
if ( !function_exists( 'is_valid_license_available' ) ) {
function is_valid_license_available() {
if ( !class_exists( 'ThemePaste\AdminSafetyGuard\Pro\App' ) ) {
return false;
}
return ThemePaste\AdminSafetyGuard\Pro\App::has_valid_license();
}
}