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(); } }