options = is_array($options) ? $options : array(); if (class_exists('ASH_Header_Settings')) { $this->options = ASH_Header_Settings::hydrate($this->options); } if (class_exists('ASH_Feature_Settings')) { $this->options = ASH_Feature_Settings::hydrate($this->options); } if (class_exists('ASH_Security_Profile')) { $this->options = ASH_Security_Profile::hydrate($this->options); } } /** * Render the Security Control Center dashboard. * * @return void */ public function render() { $payload = class_exists('ASH_Security_Status') ? ASH_Security_Status::payload($this->options, false) : array(); $catalog = class_exists('ASH_Security_Profile') ? ASH_Security_Profile::catalog() : array(); $stored_profile = isset($payload['profile']['stored']) ? $payload['profile']['stored'] : 'manual'; ?>

render_help_menu(); ?>
render_dashboard_metrics($payload); ?>
render_dashboard_profiles($catalog, $stored_profile, $payload); ?> render_dashboard_summary($payload); ?> render_dashboard_attention($payload); ?> render_dashboard_quick_actions(); ?>

render_confirm_modal(); ?> render_credit_footer(); ?>

$item) : ?>

render_help_menu(); ?>
render_xss_header_card(); ?> render_xcto_header_card(); ?> render_hsts_header_card(); ?> render_permissions_policy_header_card(); ?> render_xfo_header_card(); ?> render_referrer_policy_header_card(); ?>
render_action_bar(); ?>
render_pp_custom_template(); ?> render_confirm_modal(); ?> render_credit_footer(); ?>
option_string('xss_policy', '0'); $report_url = $this->option_string('xss_report_url', ''); $enabled = $this->is_enabled('x_xss_protection'); $policy_choices = array( '0' => '0', '1' => '1', '1_mode_block' => '1; mode=block', '1_report' => '1; report=', ); ?>

X-XSS-Protection

render_toggle_row( 'x_xss_protection', __('Enable X-XSS-Protection', 'abdal-security-headers'), __('Send the X-XSS-Protection header with the selected policy.', 'abdal-security-headers'), 'headers', 'ash_options', null, array( 'attrs' => array( 'data-ash-header-enable' => '1', ), ) ); ?>
render_field_row( __('Policy', 'abdal-security-headers'), __('Choose the legacy XSS filter policy sent to older browsers.', 'abdal-security-headers'), $this->get_select_markup( 'ash-xss-policy', 'ash_options[xss_policy]', $policy_choices, $policy, array( 'data-ash-xss-policy' => '1', 'dir' => 'ltr', ) ) ); ?>
>
render_info_icon(__('Endpoint that receives XSS filter reports when the report policy is selected.', 'abdal-security-headers')); ?>
is_enabled('x_content_type_options'); ?>

X-Content-Type-Options

render_toggle_row( 'x_content_type_options', __('Enable X-Content-Type-Options', 'abdal-security-headers'), __('When enabled, the plugin always sends nosniff. There are no additional options.', 'abdal-security-headers'), 'headers', 'ash_options', null, array( 'attrs' => array( 'data-ash-header-enable' => '1', ), ) ); ?>
is_enabled('strict_transport_security'); $max_age = $this->option_string('hsts_max_age', '31536000'); $presets = class_exists('ASH_Header_Settings') ? ASH_Header_Settings::max_age_preset_values() : array('31536000'); $is_custom = !in_array($max_age, $presets, true); $preset_value = $is_custom ? 'custom' : $max_age; $preset_choices = array( '86400' => '86400 — ' . __('1 Day', 'abdal-security-headers'), '604800' => '604800 — ' . __('1 Week', 'abdal-security-headers'), '2592000' => '2592000 — ' . __('1 Month', 'abdal-security-headers'), '15768000' => '15768000 — ' . __('6 Months', 'abdal-security-headers'), '31536000' => '31536000 — ' . __('1 Year', 'abdal-security-headers'), '63072000' => '63072000 — ' . __('2 Years', 'abdal-security-headers'), 'custom' => __('Custom value', 'abdal-security-headers'), ); ?>

Strict-Transport-Security

render_toggle_row( 'strict_transport_security', __('Enable Strict-Transport-Security', 'abdal-security-headers'), __('Instruct browsers to use HTTPS for this host for the configured lifetime.', 'abdal-security-headers'), 'headers', 'ash_options', null, array( 'attrs' => array( 'data-ash-header-enable' => '1', ), ) ); ?>
render_field_row( 'max-age', __('How long browsers should remember to use HTTPS only.', 'abdal-security-headers'), $this->get_select_markup( 'ash-hsts-max-age-preset', 'ash_options[hsts_max_age_preset]', $preset_choices, $preset_value, array( 'data-ash-hsts-preset' => '1', 'dir' => 'ltr', ) ) ); ?>
>
render_info_icon(__('Enter max-age in seconds.', 'abdal-security-headers')); ?>
render_toggle_row( 'hsts_include_subdomains', __('Include Subdomains', 'abdal-security-headers'), __('Adds includeSubDomains so the policy also applies to subdomains.', 'abdal-security-headers'), 'headers', 'ash_options', null, array( 'attrs' => array( 'data-ash-hsts-subdomains' => '1', ), ) ); $this->render_toggle_row( 'hsts_preload', __('Preload', 'abdal-security-headers'), __('Adds preload. Requires includeSubDomains and a max-age of at least one year.', 'abdal-security-headers'), 'headers', 'ash_options', null, array( 'attrs' => array( 'data-ash-hsts-preload' => '1', ), ) ); ?>
is_enabled('permissions_policy'); $directives = isset($this->options['pp_directives']) && is_array($this->options['pp_directives']) ? $this->options['pp_directives'] : array(); $custom = isset($this->options['pp_custom']) && is_array($this->options['pp_custom']) ? $this->options['pp_custom'] : array(); $names = class_exists('ASH_Header_Settings') ? ASH_Header_Settings::directive_names() : array(); ?>

Permissions-Policy

render_toggle_row( 'permissions_policy', __('Enable Permissions-Policy', 'abdal-security-headers'), __('Control which origins may use powerful browser features.', 'abdal-security-headers'), 'headers', 'ash_options', null, array( 'attrs' => array( 'data-ash-header-enable' => '1', ), ) ); ?>
render_pp_directive_item($name, $state, false, ''); ?>

$item) : ?> render_pp_directive_item((string) $item['name'], $item, true, (string) $index); ?>
is_enabled('x_frame_options'); $policy = $this->option_string('x_frame_options_policy', 'SAMEORIGIN'); $choices = array( 'DENY' => 'DENY', 'SAMEORIGIN' => 'SAMEORIGIN', ); ?>

X-Frame-Options

render_toggle_row( 'x_frame_options', __('Enable X-Frame-Options', 'abdal-security-headers'), __('Control whether the site may be embedded in frames.', 'abdal-security-headers'), 'headers', 'ash_options', null, array( 'attrs' => array( 'data-ash-header-enable' => '1', ), ) ); ?>
render_field_row( __('Policy', 'abdal-security-headers'), __('DENY blocks all framing. SAMEORIGIN allows framing by this origin only. ALLOW-FROM is obsolete and is not offered.', 'abdal-security-headers'), $this->get_select_markup( 'ash-xfo-policy', 'ash_options[x_frame_options_policy]', $choices, $policy, array( 'dir' => 'ltr', ) ) ); ?>
is_enabled('referrer_policy'); $policy = $this->option_string('referrer_policy_value', 'strict-origin-when-cross-origin'); $sensitive_label = __('privacy/security sensitive', 'abdal-security-headers'); $choices = array( 'no-referrer' => 'no-referrer', 'no-referrer-when-downgrade' => 'no-referrer-when-downgrade', 'origin' => 'origin', 'origin-when-cross-origin' => 'origin-when-cross-origin', 'same-origin' => 'same-origin', 'strict-origin' => 'strict-origin', 'strict-origin-when-cross-origin' => 'strict-origin-when-cross-origin', 'unsafe-url' => 'unsafe-url — ' . $sensitive_label, ); ?>

Referrer-Policy

render_toggle_row( 'referrer_policy', __('Enable Referrer-Policy', 'abdal-security-headers'), __('Control how much referrer information is sent with requests.', 'abdal-security-headers'), 'headers', 'ash_options', null, array( 'attrs' => array( 'data-ash-header-enable' => '1', ), ) ); ?>
render_field_row( __('Policy', 'abdal-security-headers'), __('Choose the Referrer-Policy value sent with responses.', 'abdal-security-headers'), $this->get_select_markup( 'ash-referrer-policy', 'ash_options[referrer_policy_value]', $choices, $policy, array( 'data-ash-referrer-policy' => '1', 'dir' => 'ltr', ) ) ); ?>
>
get_pp_policy_choices(); if ($is_custom) { $base = 'ash_options[pp_custom][' . $index . ']'; $id_base = 'ash-pp-custom-' . $index; } else { $base = 'ash_options[pp_directives][' . $name . ']'; $id_base = 'ash-pp-' . $name; } ?>
>
render_toggle_row( $id_base . '-enabled', __('Enable Directive', 'abdal-security-headers'), __('Include this directive in the generated Permissions-Policy header.', 'abdal-security-headers'), 'headers', 'ash_options', array($id_base . '-enabled' => $enabled ? '1' : '0'), array( 'name' => $base . '[enabled]', 'input_id' => $id_base . '-enabled', 'checked' => $enabled, 'row_class' => 'ash-toggle-row--compact', ) ); ?>
render_field_row( __('Policy', 'abdal-security-headers'), __('Choose how this feature may be used.', 'abdal-security-headers'), $this->get_select_markup( $id_base . '-policy', $base . '[policy]', $policy_choices, $policy, array( 'data-ash-pp-policy' => '1', ) ) ); ?>
>
render_info_icon(__('One origin per line. Use self or full origins such as https://example.com.', 'abdal-security-headers')); ?>
get_pp_policy_choices(); ?> __('Not Set', 'abdal-security-headers'), 'deny' => __('Deny All', 'abdal-security-headers') . ' → ()', 'self' => __('Self', 'abdal-security-headers') . ' → (self)', 'all' => __('Allow All', 'abdal-security-headers') . ' → *', 'custom' => __('Custom Origins', 'abdal-security-headers'), ); } /** * Render a labeled control row. * * @param string $label Field label. * @param string $description Helper text. * @param string $control_html Escaped control markup. * @return void */ private function render_field_row($label, $description, $control_html) { $id = ''; if (preg_match('/\sid=(["\'])([^"\']+)\1/', $control_html, $match)) { $id = $match[2]; } ?>
render_info_icon($description); ?>
label. * @param string $current Selected value. * @param array $attrs Extra attributes. * @return string */ private function get_select_markup($id, $name, $choices, $current, $attrs = array()) { $html = ''; return $html; } /** * Build a styled textarea. * * @param string $id Field id. * @param string $name Input name. * @param string $value Current text. * @param array $attrs Extra attributes. * @return string */ private function get_textarea_markup($id, $name, $value, $attrs = array()) { $html = ''; return $html; } /** * Join a stored list for a textarea. * * @param string $key Option key. * @return string */ private function option_lines($key) { $value = isset($this->options[$key]) && is_array($this->options[$key]) ? $this->options[$key] : array(); return implode("\n", $value); } /** * WordPress roles for REST access checkboxes. * * @return array */ private function get_role_choices() { $choices = array(); if (!function_exists('wp_roles')) { return $choices; } $roles = wp_roles(); if (!is_object($roles) || !isset($roles->role_names) || !is_array($roles->role_names)) { return $choices; } foreach ($roles->role_names as $slug => $label) { $choices[$slug] = translate_user_role($label); } return $choices; } /** * Read a stored option as a string. * * @param string $key Option key. * @param string $default Fallback. * @return string */ private function option_string($key, $default = '') { return isset($this->options[$key]) ? (string) $this->options[$key] : $default; } /** * Render the Security Features screen. * * @return void */ public function render_security_features() { ?>

render_help_menu(); ?>
render_powered_by_feature_card(); ?> render_hide_version_feature_card(); ?> render_login_errors_feature_card(); ?> render_xmlrpc_feature_card(); ?> render_pingback_header_feature_card(); ?> render_rest_api_feature_card(); ?>
render_action_bar(); ?>
render_confirm_modal(); ?> render_credit_footer(); ?>
is_enabled('remove_x_powered_by'); ?>

render_toggle_row( 'remove_x_powered_by', __('Enable Remove X-Powered-By Header', 'abdal-security-headers'), __('When enabled, the plugin removes the X-Powered-By response header. There are no additional options.', 'abdal-security-headers'), 'features', 'ash_options', null, array( 'attrs' => array( 'data-ash-header-enable' => '1', ), ) ); ?>
is_enabled('hide_wp_version'); ?>

render_toggle_row( 'hide_wp_version', __('Enable Hide WordPress Version', 'abdal-security-headers'), __('Turn on WordPress version hiding, then choose which generator outputs to remove.', 'abdal-security-headers'), 'features', 'ash_options', null, array( 'attrs' => array( 'data-ash-header-enable' => '1', ), ) ); ?>
render_toggle_row( 'hide_generator_meta', __('Hide Generator Meta Version', 'abdal-security-headers'), __('Removes the generator meta tag from HTML output.', 'abdal-security-headers'), 'features' ); $this->render_toggle_row( 'hide_version_feeds', __('Hide Version from RSS / Atom / RDF Feeds', 'abdal-security-headers'), __('Removes generator version strings from RSS, Atom, and RDF feeds.', 'abdal-security-headers'), 'features' ); ?>
is_enabled('remove_login_errors'); $mode = $this->option_string('login_error_mode', 'generic'); $custom = $this->option_string('login_error_custom', ''); $choices = array( 'generic' => __('Generic Error', 'abdal-security-headers'), 'hide' => __('Hide Error Completely', 'abdal-security-headers'), 'custom' => __('Custom Error Message', 'abdal-security-headers'), ); ?>

render_toggle_row( 'remove_login_errors', __('Enable Login Error Protection', 'abdal-security-headers'), __('Only authentication failure messages are changed. Informational and successful login messages are left alone.', 'abdal-security-headers'), 'features', 'ash_options', null, array( 'attrs' => array( 'data-ash-header-enable' => '1', ), ) ); ?>
render_field_row( __('Error Mode', 'abdal-security-headers'), __('Choose how authentication failure messages are shown.', 'abdal-security-headers'), $this->get_select_markup( 'ash-login-error-mode', 'ash_options[login_error_mode]', $choices, $mode, array( 'data-ash-login-mode' => '1', ) ) ); ?>
>
render_info_icon(__('This plain-text message replaces authentication failure errors.', 'abdal-security-headers')); ?>
is_enabled('disable_xmlrpc'); $mode = $this->option_string('xmlrpc_mode', 'auth'); $choices = array( 'auth' => __('Disable Authenticated XML-RPC Methods', 'abdal-security-headers'), 'pingback' => __('Disable Pingback Methods Only', 'abdal-security-headers'), 'all' => __('Disable All XML-RPC Methods', 'abdal-security-headers'), 'custom' => __('Custom Method Policy', 'abdal-security-headers'), ); ?>

>

render_toggle_row( 'disable_xmlrpc', __('Enable XML-RPC Protection', 'abdal-security-headers'), __('Apply a selected XML-RPC policy instead of a single on/off switch.', 'abdal-security-headers'), 'features', 'ash_options', null, array( 'attrs' => array( 'data-ash-header-enable' => '1', ), ) ); ?>
render_field_row( __('Protection Mode', 'abdal-security-headers'), __('Choose which XML-RPC methods remain available.', 'abdal-security-headers'), $this->get_select_markup( 'ash-xmlrpc-mode', 'ash_options[xmlrpc_mode]', $choices, $mode, array( 'data-ash-xmlrpc-mode' => '1', ) ) ); ?>
>
> render_field_row( __('Allowed XML-RPC Methods', 'abdal-security-headers'), __('One method name per line. If this list is not empty, only these methods remain.', 'abdal-security-headers'), $this->get_textarea_markup( 'ash-xmlrpc-allow', 'ash_options[xmlrpc_allow_methods]', $this->option_lines('xmlrpc_allow_methods'), array( 'placeholder' => 'wp.getPosts', ) ) ); $this->render_field_row( __('Blocked XML-RPC Methods', 'abdal-security-headers'), __('One method name per line. These methods are removed even if they appear in the allow list.', 'abdal-security-headers'), $this->get_textarea_markup( 'ash-xmlrpc-block', 'ash_options[xmlrpc_block_methods]', $this->option_lines('xmlrpc_block_methods'), array( 'placeholder' => 'pingback.ping', ) ) ); ?>
is_enabled('remove_x_pingback'); $xmlrpc_on = $this->is_enabled('disable_xmlrpc'); $xmlrpc_mode = $this->option_string('xmlrpc_mode', 'auth'); $recommend = $xmlrpc_on && in_array($xmlrpc_mode, array('pingback', 'all'), true) && !$enabled; ?>

render_toggle_row( 'remove_x_pingback', __('Enable Remove X-Pingback Header', 'abdal-security-headers'), __('When enabled, the plugin removes the X-Pingback header. There are no additional options.', 'abdal-security-headers'), 'features', 'ash_options', null, array( 'attrs' => array( 'data-ash-header-enable' => '1', 'data-ash-pingback-enable' => '1', ), ) ); ?>
>
is_enabled('restrict_rest_api'); $policy = $this->option_string('rest_access_policy', 'authenticated'); $capability = $this->option_string('rest_capability', 'edit_posts'); $selected_roles = isset($this->options['rest_roles']) && is_array($this->options['rest_roles']) ? $this->options['rest_roles'] : array(); $users_on = $this->is_enabled('rest_users_restrict'); $users_policy = $this->option_string('rest_users_policy', 'authenticated'); $users_cap = $this->option_string('rest_users_capability', 'list_users'); $policy_choices = array( 'wordpress' => __('WordPress Default', 'abdal-security-headers'), 'authenticated' => __('Authenticated Users Only', 'abdal-security-headers'), 'roles' => __('Selected Roles', 'abdal-security-headers'), 'capability' => __('Required Capability', 'abdal-security-headers'), 'administrators' => __('Administrators Only', 'abdal-security-headers'), 'block_all' => __('Block All REST Access', 'abdal-security-headers'), ); $users_choices = array( 'wordpress' => __('WordPress Default', 'abdal-security-headers'), 'authenticated' => __('Authenticated Users Only', 'abdal-security-headers'), 'capability' => __('Required Capability', 'abdal-security-headers'), 'administrators' => __('Administrators Only', 'abdal-security-headers'), ); ?>

>

render_toggle_row( 'restrict_rest_api', __('Enable REST API Access Control', 'abdal-security-headers'), __('When this is off, WordPress REST behavior is unchanged except for optional user-endpoint protection below.', 'abdal-security-headers'), 'features', 'ash_options', null, array( 'attrs' => array( 'data-ash-header-enable' => '1', ), ) ); ?>
render_field_row( __('Access Policy', 'abdal-security-headers'), __('WordPress Default applies no extra restriction. Administrators Only uses the manage_options capability.', 'abdal-security-headers'), $this->get_select_markup( 'ash-rest-policy', 'ash_options[rest_access_policy]', $policy_choices, $policy, array( 'data-ash-rest-policy' => '1', ) ) ); ?>
>
>

get_role_choices() as $slug => $label) : ?> render_toggle_row( 'rest_role_' . $slug, $label, '', 'features', 'ash_options', null, array( 'name' => 'ash_options[rest_roles][]', 'input_id' => 'ash-rest-role-' . $slug, 'value' => $slug, 'checked' => in_array($slug, $selected_roles, true), ) ); ?>
>
render_info_icon(__('Access is allowed when the current user has this WordPress capability. Examples: read, edit_posts, edit_pages, manage_options.', 'abdal-security-headers')); ?>

render_field_row( __('Allowed Namespaces', 'abdal-security-headers'), __('One namespace per line, such as wp/v2 or wc/v3. Custom plugin namespaces are allowed.', 'abdal-security-headers'), $this->get_textarea_markup( 'ash-rest-allow-ns', 'ash_options[rest_allow_namespaces]', $this->option_lines('rest_allow_namespaces'), array( 'placeholder' => 'wp/v2', ) ) ); $this->render_field_row( __('Allowed Routes', 'abdal-security-headers'), __('One route per line, such as /wp/v2/posts. Matching prefixes are included.', 'abdal-security-headers'), $this->get_textarea_markup( 'ash-rest-allow-routes', 'ash_options[rest_allow_routes]', $this->option_lines('rest_allow_routes'), array( 'placeholder' => '/wp/v2/posts', ) ) ); $this->render_field_row( __('Restricted Namespaces', 'abdal-security-headers'), __('These namespaces are blocked even if the access policy would allow them.', 'abdal-security-headers'), $this->get_textarea_markup( 'ash-rest-deny-ns', 'ash_options[rest_deny_namespaces]', $this->option_lines('rest_deny_namespaces'), array( 'placeholder' => 'wp/v2', ) ) ); $this->render_field_row( __('Restricted Routes', 'abdal-security-headers'), __('These routes are blocked even if the access policy would allow them.', 'abdal-security-headers'), $this->get_textarea_markup( 'ash-rest-deny-routes', 'ash_options[rest_deny_routes]', $this->option_lines('rest_deny_routes'), array( 'placeholder' => '/wp/v2/users', ) ) ); ?>

render_toggle_row( 'rest_users_restrict', __('Restrict REST User Endpoints', 'abdal-security-headers'), __('Applies an extra policy to user-related REST routes such as /wp/v2/users.', 'abdal-security-headers'), 'features', 'ash_options', null, array( 'attrs' => array( 'data-ash-users-enable' => '1', ), ) ); ?>
render_field_row( __('Access Policy', 'abdal-security-headers'), __('Choose who may access WordPress user REST endpoints.', 'abdal-security-headers'), $this->get_select_markup( 'ash-rest-users-policy', 'ash_options[rest_users_policy]', $users_choices, $users_policy, array( 'data-ash-rest-users-policy' => '1', ) ) ); ?>
>
render_info_icon(__('User endpoints are allowed when the current user has this capability.', 'abdal-security-headers')); ?>
get_csp_directive_fields(); $general_fields = $this->get_csp_general_fields(); ?>

render_help_menu(); ?>

render_toggle_row('content_security_policy', esc_html__('Enable Content Security Policy', 'abdal-security-headers'), esc_html__('When enabled, the generated Content-Security-Policy header is sent with HTTP responses.', 'abdal-security-headers'), 'csp'); ?> $field) : ?> render_text_field($id, $field['label'], $field['description'], $field['directive']); ?>

$field) : ?> render_text_field($id, $field['label'], $field['description'], $field['directive'], true); ?>


                        
render_action_bar(); ?>
render_confirm_modal(); ?> render_csp_editor_modal(); ?> render_credit_footer(); ?>

render_help_menu(); ?>
render_settings_transfer(); ?>

render_toggle_row( 'remove_data_on_uninstall', __('Delete all plugin data on uninstall', 'abdal-security-headers'), __('When enabled, deleting this plugin also removes its settings, CSP assistant data, custom tables, and related stored files. This cannot be undone.', 'abdal-security-headers'), 'plugin', 'ash_plugin_settings', $plugin_settings ); ?>
render_action_bar(); ?>
render_confirm_modal(); ?> render_credit_footer(); ?>

options; $checked = isset($source[$id]) && (string) $source[$id] === '1'; if (!is_array($args)) { $args = array(); } if (array_key_exists('checked', $args)) { $checked = (bool) $args['checked']; } $name = isset($args['name']) ? (string) $args['name'] : $option_name . '[' . $id . ']'; $input_id = isset($args['input_id']) ? (string) $args['input_id'] : $id; $value = isset($args['value']) ? (string) $args['value'] : '1'; $row_class = isset($args['row_class']) ? trim('ash-toggle-row ' . (string) $args['row_class']) : 'ash-toggle-row'; $attrs = isset($args['attrs']) && is_array($args['attrs']) ? $args['attrs'] : array(); ?>
render_info_icon($description); ?>
options[$id]) ? $this->options[$id] : ''; ?>
render_info_icon($description); ?>
>
array( 'label' => __('X-XSS-Protection', 'abdal-security-headers'), 'description' => __('Enables the legacy browser XSS filter for older clients.', 'abdal-security-headers'), ), 'x_content_type_options' => array( 'label' => __('X-Content-Type-Options', 'abdal-security-headers'), 'description' => __('Prevents browsers from MIME-sniffing a response away from the declared content type.', 'abdal-security-headers'), ), 'strict_transport_security' => array( 'label' => __('Strict-Transport-Security', 'abdal-security-headers'), 'description' => __('Forces browsers to use HTTPS for future requests to this host.', 'abdal-security-headers'), ), 'permissions_policy' => array( 'label' => __('Permissions-Policy', 'abdal-security-headers'), 'description' => __('Restricts access to powerful browser features such as camera and geolocation.', 'abdal-security-headers'), ), 'x_frame_options' => array( 'label' => __('X-Frame-Options', 'abdal-security-headers'), 'description' => __('Controls whether the site can be embedded in frames to reduce clickjacking risk.', 'abdal-security-headers'), ), 'referrer_policy' => array( 'label' => __('Referrer-Policy', 'abdal-security-headers'), 'description' => __('Limits referrer information sent with navigations and resource requests.', 'abdal-security-headers'), ), ); } /** * Get additional security feature toggle definitions. * * @return array */ private function get_feature_fields() { return array( 'remove_x_powered_by' => array( 'label' => __('Remove X-Powered-By Header', 'abdal-security-headers'), 'description' => __('Hides server technology details exposed by the X-Powered-By header.', 'abdal-security-headers'), ), 'hide_wp_version' => array( 'label' => __('Hide WordPress Version', 'abdal-security-headers'), 'description' => __('Removes the public WordPress version from generator markup and feeds.', 'abdal-security-headers'), ), 'remove_login_errors' => array( 'label' => __('Remove Login Error Messages', 'abdal-security-headers'), 'description' => __('Replaces detailed login errors with a generic message to reduce user enumeration.', 'abdal-security-headers'), ), 'disable_xmlrpc' => array( 'label' => __('Disable XML-RPC', 'abdal-security-headers'), 'description' => __('Applies a selected XML-RPC method policy instead of a single complete shutdown.', 'abdal-security-headers'), ), 'remove_x_pingback' => array( 'label' => __('Remove X-Pingback Header', 'abdal-security-headers'), 'description' => __('Removes the X-Pingback response header from WordPress output.', 'abdal-security-headers'), ), 'restrict_rest_api' => array( 'label' => __('Restrict REST API Access', 'abdal-security-headers'), 'description' => __('Applies a configurable REST API access policy instead of blocking every request.', 'abdal-security-headers'), ), ); } /** * Get CSP source directive fields shown in the main grid. * * @return array */ private function get_csp_directive_fields() { return array( 'csp_default_src' => array( 'label' => __('Default Source', 'abdal-security-headers'), 'directive' => 'default-src', 'description' => __('Default fallback for other resource types', 'abdal-security-headers'), ), 'csp_script_src' => array( 'label' => __('Script Source', 'abdal-security-headers'), 'directive' => 'script-src', 'description' => __('Valid sources for JavaScript files', 'abdal-security-headers'), ), 'csp_style_src' => array( 'label' => __('Style Source', 'abdal-security-headers'), 'directive' => 'style-src', 'description' => __('Valid sources for CSS files', 'abdal-security-headers'), ), 'csp_img_src' => array( 'label' => __('Image Source', 'abdal-security-headers'), 'directive' => 'img-src', 'description' => __('Valid sources for images', 'abdal-security-headers'), ), 'csp_connect_src' => array( 'label' => __('Connect Source', 'abdal-security-headers'), 'directive' => 'connect-src', 'description' => __('Valid sources for AJAX, WebSocket, or EventSource connections', 'abdal-security-headers'), ), 'csp_font_src' => array( 'label' => __('Font Source', 'abdal-security-headers'), 'directive' => 'font-src', 'description' => __('Valid sources for fonts', 'abdal-security-headers'), ), 'csp_object_src' => array( 'label' => __('Object Source', 'abdal-security-headers'), 'directive' => 'object-src', 'description' => __('Valid sources for plugins', 'abdal-security-headers'), ), 'csp_media_src' => array( 'label' => __('Media Source', 'abdal-security-headers'), 'directive' => 'media-src', 'description' => __('Valid sources for audio and video elements', 'abdal-security-headers'), ), 'csp_frame_src' => array( 'label' => __('Frame Source', 'abdal-security-headers'), 'directive' => 'frame-src', 'description' => __('Valid sources for iframes', 'abdal-security-headers'), ), 'csp_worker_src' => array( 'label' => __('Worker Source', 'abdal-security-headers'), 'directive' => 'worker-src', 'description' => __('Valid sources for web workers', 'abdal-security-headers'), ), 'csp_form_action' => array( 'label' => __('Form Action', 'abdal-security-headers'), 'directive' => 'form-action', 'description' => __('Valid targets for form submissions', 'abdal-security-headers'), ), 'csp_base_uri' => array( 'label' => __('Base URI', 'abdal-security-headers'), 'directive' => 'base-uri', 'description' => __('Valid values for the base element', 'abdal-security-headers'), ), 'csp_sandbox' => array( 'label' => __('Sandbox', 'abdal-security-headers'), 'directive' => 'sandbox', 'description' => __('Enables sandbox for the requested resource', 'abdal-security-headers'), ), ); } /** * Get CSP reporting fields shown in the general settings panel. * * @return array */ private function get_csp_general_fields() { return array( 'csp_report_uri' => array( 'label' => __('Report URI', 'abdal-security-headers'), 'directive' => 'report-uri', 'description' => __('URI to send violation reports to', 'abdal-security-headers'), ), 'csp_report_to' => array( 'label' => __('Report To', 'abdal-security-headers'), 'directive' => 'report-to', 'description' => __('Group name for violation reports', 'abdal-security-headers'), ), ); } /** * Check whether a stored option is enabled. * * @param string $key Option key. * @return bool */ private function is_enabled($key) { return isset($this->options[$key]) && (string) $this->options[$key] === '1'; } /** * Count enabled options from a list of keys. * * @param array $keys Option keys. * @return int */ private function count_enabled($keys) { $count = 0; foreach ($keys as $key) { if ($this->is_enabled($key)) { $count++; } } return $count; } /** * Derive the summary security status from enabled settings. * * @param int $header_active Enabled header count. * @param int $header_total Total header count. * @param int $feature_active Enabled feature count. * @param int $feature_total Total feature count. * @return array */ private function get_security_status($header_active, $header_total, $feature_active, $feature_total) { $total = $header_total + $feature_total; $active = $header_active + $feature_active; $ratio = $total > 0 ? ($active / $total) : 0; if ($ratio >= 0.75) { return array( 'tone' => 'green', 'label' => _x('Good', 'security status', 'abdal-security-headers'), 'hint' => __('Most security controls are enabled.', 'abdal-security-headers'), ); } if ($ratio >= 0.4) { return array( 'tone' => 'warning', 'label' => _x('Fair', 'security status', 'abdal-security-headers'), 'hint' => __('Some recommended security controls are still disabled.', 'abdal-security-headers'), ); } return array( 'tone' => 'muted', 'label' => _x('Needs attention', 'security status', 'abdal-security-headers'), 'hint' => __('Enable more security headers and hardening options.', 'abdal-security-headers'), ); } }