options = get_option('ash_options', array());
if (!is_array($this->options)) {
$this->options = array();
}
}
/**
* Mark plugin admin screens so CSS can hide the WordPress footer and pad the canvas.
*
* @param string $classes Existing admin body classes.
* @return string
*/
public function filter_admin_body_class($classes) {
$screen_id = '';
if (function_exists('get_current_screen')) {
$screen = get_current_screen();
if (is_object($screen) && isset($screen->id)) {
$screen_id = (string) $screen->id;
}
}
$hooks = $this->plugin_admin_hooks();
if (!in_array($screen_id, $hooks, true)) {
return $classes;
}
return $classes . ' ash-admin-screen';
}
/**
* Hook suffixes for every plugin admin screen.
*
* @return array
*/
private function plugin_admin_hooks() {
return array_values(
array_filter(
array(
$this->page_hook,
$this->headers_hook,
$this->csp_hook,
$this->features_hook,
$this->settings_hook,
)
)
);
}
public function enqueue_admin_assets($hook) {
if (!in_array($hook, $this->plugin_admin_hooks(), true)) {
return;
}
$css_path = ASH_PLUGIN_DIR . 'assets/css/admin.css';
$js_path = ASH_PLUGIN_DIR . 'assets/js/admin.js';
$css_version = file_exists($css_path) ? (string) filemtime($css_path) : ASH_VERSION;
$js_version = file_exists($js_path) ? (string) filemtime($js_path) : ASH_VERSION;
wp_enqueue_style(
'ash-admin-styles',
ASH_PLUGIN_URL . 'assets/css/admin.css',
array('dashicons'),
$css_version
);
wp_enqueue_script(
'ash-admin-scripts',
ASH_PLUGIN_URL . 'assets/js/admin.js',
array(),
$js_version,
true
);
wp_localize_script('ash-admin-scripts', 'ashAdmin', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('ash_security_center'),
'settingsNonce' => wp_create_nonce('ash_settings_transfer'),
'headerFields' => array(
'x_xss_protection',
'x_content_type_options',
'strict_transport_security',
'permissions_policy',
'x_frame_options',
'referrer_policy',
),
'featureFields' => array(
'remove_x_powered_by',
'hide_wp_version',
'remove_login_errors',
'disable_xmlrpc',
'remove_x_pingback',
'restrict_rest_api',
),
'strings' => array(
'confirmSave' => __('Are you sure you want to save these settings?', 'abdal-security-headers'),
'confirmReset' => __('Reset unsaved changes and restore the last saved values?', 'abdal-security-headers'),
'saveChanges' => __('Save Changes', 'abdal-security-headers'),
'reset' => __('Reset', 'abdal-security-headers'),
'yes' => __('Yes', 'abdal-security-headers'),
'no' => __('No', 'abdal-security-headers'),
'success' => __('Settings saved successfully', 'abdal-security-headers'),
'ok' => __('OK', 'abdal-security-headers'),
'cancel' => __('Cancel', 'abdal-security-headers'),
'editorOk' => _x('OK', 'csp editor confirm', 'abdal-security-headers'),
/* translators: %s: CSP directive name such as script-src */
'editDirective' => __('Edit %s', 'abdal-security-headers'),
'error' => __('Error', 'abdal-security-headers'),
'errorMessage' => __('An error occurred while saving the settings. Please try again.', 'abdal-security-headers'),
'copied' => __('Copied', 'abdal-security-headers'),
'copyCsp' => __('Copy CSP header', 'abdal-security-headers'),
'copyFailed' => __('Unable to copy the CSP header.', 'abdal-security-headers'),
'cspEmpty' => __('Content-Security-Policy header is enabled but no directives are set.', 'abdal-security-headers'),
'cspDisabled' => __('Content Security Policy is disabled. Enable it to send this header.', 'abdal-security-headers'),
'statusGood' => _x('Good', 'security status', 'abdal-security-headers'),
'statusFair' => _x('Fair', 'security status', 'abdal-security-headers'),
'statusNeedsAttention' => _x('Needs attention', 'security status', 'abdal-security-headers'),
'statusGoodHint' => __('Most security controls are enabled.', 'abdal-security-headers'),
'statusFairHint' => __('Some recommended security controls are still disabled.', 'abdal-security-headers'),
'statusNeedsHint' => __('Enable more security headers and hardening options.', 'abdal-security-headers'),
'cspOn' => _x('Enabled', 'CSP status', 'abdal-security-headers'),
'cspOff' => _x('Disabled', 'CSP status', 'abdal-security-headers'),
'cspOnHint' => __('Content Security Policy is being sent with responses.', 'abdal-security-headers'),
'cspOffHint' => __('Content Security Policy is currently turned off.', 'abdal-security-headers'),
/* translators: 1: number of enabled headers, 2: total headers */
'headersHint' => __('%1$d of %2$d security headers are enabled', 'abdal-security-headers'),
/* translators: 1: number of enabled features, 2: total features */
'featuresHint' => __('%1$d of %2$d additional features are enabled', 'abdal-security-headers'),
'confirmRemoveDirective' => __('Remove this custom Permissions-Policy directive?', 'abdal-security-headers'),
'removeDirective' => __('Remove directive', 'abdal-security-headers'),
'invalidDirectiveName' => __('Enter a valid directive name using lowercase letters, numbers, and hyphens.', 'abdal-security-headers'),
'duplicateDirectiveName' => __('That directive name is already in the list.', 'abdal-security-headers'),
'confirmApplyProfile' => __('Apply this Security Profile? Security Headers and Security Features will be updated. Content Security Policy will not change.', 'abdal-security-headers'),
'confirmApplyHardened' => __('Hardened can affect compatibility with themes, plugins, XML-RPC clients, and some WordPress tools. Apply anyway? Content Security Policy will not change.', 'abdal-security-headers'),
'confirmApplyManual' => __('Switch to Manual? Current Security Headers and Security Features will stay as they are.', 'abdal-security-headers'),
'confirmResetProfile' => __('Reset Security Headers and Security Features to the Recommended profile? Content Security Policy will not change.', 'abdal-security-headers'),
'confirmApplyTitle' => __('Apply Security Profile', 'abdal-security-headers'),
'resetProfileTitle' => __('Reset Profile to Defaults', 'abdal-security-headers'),
'selectProfile' => __('Select a Security Profile before applying.', 'abdal-security-headers'),
'successApply' => __('Security Profile applied. Content Security Policy was not changed.', 'abdal-security-headers'),
'successReset' => __('Security Headers and Security Features were reset to Recommended. Content Security Policy was not changed.', 'abdal-security-headers'),
'successRecalculate' => __('Security status was recalculated from the current configuration.', 'abdal-security-headers'),
'centerError' => __('The security center request could not be completed. Please try again.', 'abdal-security-headers'),
'confirmImportTitle' => __('Import settings', 'abdal-security-headers'),
'confirmImport' => __('Import will replace current plugin settings, including Security Headers, Security Features, Content Security Policy, Security Profile, and Settings. This cannot be undone from this screen.', 'abdal-security-headers'),
'successExport' => __('Settings file downloaded.', 'abdal-security-headers'),
'successImport' => __('Settings were imported. The page will reload.', 'abdal-security-headers'),
'importNoFile' => __('Choose a settings JSON file before importing.', 'abdal-security-headers'),
'chooseFile' => __('Choose file', 'abdal-security-headers'),
'noFileSelected' => __('No file selected', 'abdal-security-headers'),
'transferError' => __('The settings transfer could not be completed. Please try again.', 'abdal-security-headers'),
),
));
if ($hook !== $this->csp_hook) {
return;
}
$assistant_js_path = ASH_PLUGIN_DIR . 'assets/js/csp-assistant.js';
$assistant_js_version = file_exists($assistant_js_path) ? (string) filemtime($assistant_js_path) : ASH_VERSION;
wp_enqueue_script(
'ash-csp-assistant',
ASH_PLUGIN_URL . 'assets/js/csp-assistant.js',
array('ash-admin-scripts'),
$assistant_js_version,
true
);
wp_localize_script('ash-csp-assistant', 'ashCspAssistant', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('ash_csp_assistant'),
'siteHost' => strtolower((string) wp_parse_url(home_url(), PHP_URL_HOST)),
'optionMap' => class_exists('ASH_CSP_Assistant') ? ASH_CSP_Assistant::OPTION_MAP : array(),
'strings' => class_exists('ASH_CSP_Assistant') ? ASH_CSP_Assistant::ui_strings() : array(),
'initial' => class_exists('ASH_CSP_Assistant') ? ASH_CSP_Assistant::instance()->payload() : array(),
));
}
/**
* Apply, reset, or recalculate Security Control Center state.
*
* @return void
*/
public function ajax_security_center() {
if (!current_user_can('manage_options')) {
wp_send_json_error(
array(
'message' => __('You are not allowed to manage these settings.', 'abdal-security-headers'),
),
403
);
}
check_ajax_referer('ash_security_center', 'nonce');
if (!class_exists('ASH_Security_Profile') || !class_exists('ASH_Security_Status')) {
wp_send_json_error(
array(
'message' => __('Security Profile cannot be applied because required settings classes are missing.', 'abdal-security-headers'),
),
500
);
}
$task = isset($_POST['task']) ? sanitize_key(wp_unslash($_POST['task'])) : '';
$allowed = array('apply', 'reset', 'recalculate');
if (!in_array($task, $allowed, true)) {
wp_send_json_error(
array(
'message' => __('The requested security center action is not valid.', 'abdal-security-headers'),
),
400
);
}
if ($task === 'recalculate') {
delete_transient(ASH_Security_Status::PROBE_TRANSIENT);
$options = get_option('ash_options', array());
if (!is_array($options)) {
$options = array();
}
$options = ASH_Security_Status::persist_drift($options);
$payload = ASH_Security_Status::payload($options, true, true);
$payload['message'] = __('Security status was recalculated from the current configuration.', 'abdal-security-headers');
wp_send_json_success($payload);
}
$profile = 'recommended';
if ($task === 'apply') {
$raw_profile = isset($_POST['profile']) ? sanitize_key(wp_unslash($_POST['profile'])) : '';
if (!in_array($raw_profile, ASH_Security_Profile::ids(), true)) {
wp_send_json_error(
array(
'message' => __('Select a Security Profile before applying.', 'abdal-security-headers'),
),
400
);
}
$profile = $raw_profile;
}
$result = ASH_Security_Profile::apply($profile);
if (is_wp_error($result)) {
wp_send_json_error(
array(
'message' => $result->get_error_message(),
),
400
);
}
$payload = ASH_Security_Status::payload($result, true, true);
if ($task === 'reset') {
$payload['message'] = __('Security Headers and Security Features were reset to Recommended. Content Security Policy was not changed.', 'abdal-security-headers');
} else {
$payload['message'] = __('Security Profile applied. Content Security Policy was not changed.', 'abdal-security-headers');
}
wp_send_json_success($payload);
}
/**
* Export or import plugin configuration from the Settings screen.
*
* @return void
*/
public function ajax_settings_transfer() {
if (!class_exists('ASH_Settings_Transfer')) {
wp_send_json_error(
array(
'message' => __('The settings transfer could not be completed. Please try again.', 'abdal-security-headers'),
),
500
);
}
ASH_Settings_Transfer::handle_ajax();
}
public function add_plugin_page() {
$this->page_hook = add_menu_page(
esc_html__('Abdal Security Headers', 'abdal-security-headers'),
esc_html__('Security Headers', 'abdal-security-headers'),
'manage_options',
'abdal-security-headers',
array($this, 'create_admin_page'),
'dashicons-shield',
58
);
add_submenu_page(
'abdal-security-headers',
esc_html__('Dashboard', 'abdal-security-headers'),
esc_html__('Dashboard', 'abdal-security-headers'),
'manage_options',
'abdal-security-headers'
);
$this->headers_hook = add_submenu_page(
'abdal-security-headers',
esc_html__('Security Headers', 'abdal-security-headers'),
esc_html__('Security Headers', 'abdal-security-headers'),
'manage_options',
'abdal-security-headers-headers',
array($this, 'create_headers_page')
);
$this->csp_hook = add_submenu_page(
'abdal-security-headers',
esc_html__('Content Security Policy', 'abdal-security-headers'),
esc_html__('Content Security Policy', 'abdal-security-headers'),
'manage_options',
'abdal-security-headers-csp',
array($this, 'create_csp_page')
);
$this->features_hook = add_submenu_page(
'abdal-security-headers',
esc_html__('Security Features', 'abdal-security-headers'),
esc_html__('Security Features', 'abdal-security-headers'),
'manage_options',
'abdal-security-headers-features',
array($this, 'create_features_page')
);
$this->settings_hook = add_submenu_page(
'abdal-security-headers',
esc_html__('Settings', 'abdal-security-headers'),
esc_html__('Settings', 'abdal-security-headers'),
'manage_options',
'abdal-security-headers-settings',
array($this, 'create_plugin_settings_page')
);
}
public function create_admin_page() {
if (!current_user_can('manage_options')) {
wp_die(esc_html__('You are not allowed to manage these settings.', 'abdal-security-headers'));
}
require_once ASH_PLUGIN_DIR . 'includes/class-ash-admin-ui.php';
$ui = new ASH_Admin_UI($this->options);
$ui->render();
}
/**
* Render the Security Headers screen.
*
* @return void
*/
public function create_headers_page() {
if (!current_user_can('manage_options')) {
wp_die(esc_html__('You are not allowed to manage these settings.', 'abdal-security-headers'));
}
require_once ASH_PLUGIN_DIR . 'includes/class-ash-admin-ui.php';
$ui = new ASH_Admin_UI($this->options);
$ui->render_security_headers();
}
/**
* Render the Content Security Policy screen.
*
* @return void
*/
public function create_csp_page() {
if (!current_user_can('manage_options')) {
wp_die(esc_html__('You are not allowed to manage these settings.', 'abdal-security-headers'));
}
require_once ASH_PLUGIN_DIR . 'includes/class-ash-admin-ui.php';
$ui = new ASH_Admin_UI($this->options);
$ui->render_content_security_policy();
}
/**
* Render the Security Features screen.
*
* @return void
*/
public function create_features_page() {
if (!current_user_can('manage_options')) {
wp_die(esc_html__('You are not allowed to manage these settings.', 'abdal-security-headers'));
}
require_once ASH_PLUGIN_DIR . 'includes/class-ash-admin-ui.php';
$ui = new ASH_Admin_UI($this->options);
$ui->render_security_features();
}
/**
* Render the plugin Settings screen (plugin-owned options, not HTTP headers).
*
* @return void
*/
public function create_plugin_settings_page() {
if (!current_user_can('manage_options')) {
wp_die(esc_html__('You are not allowed to manage these settings.', 'abdal-security-headers'));
}
$plugin_settings = get_option('ash_plugin_settings', array());
if (!is_array($plugin_settings)) {
$plugin_settings = array();
}
require_once ASH_PLUGIN_DIR . 'includes/class-ash-admin-ui.php';
$ui = new ASH_Admin_UI($this->options);
$ui->render_plugin_settings($plugin_settings);
}
public function page_init() {
register_setting(
'ash_options_group',
'ash_options',
array($this, 'sanitize')
);
register_setting(
'ash_plugin_settings_group',
'ash_plugin_settings',
array(
'type' => 'array',
'sanitize_callback' => array($this, 'sanitize_plugin_settings'),
'default' => array(
'remove_data_on_uninstall' => '0',
),
'show_in_rest' => false,
'capability' => 'manage_options',
)
);
// Basic Security Headers Section
add_settings_section(
'ash_basic_headers',
esc_html__('Basic Security Headers', 'abdal-security-headers'),
array($this, 'print_section_info'),
'abdal-security-headers'
);
// Original Headers
$this->add_checkbox_field('x_xss_protection', esc_html__('X-XSS-Protection', 'abdal-security-headers'), 'ash_basic_headers');
$this->add_checkbox_field('x_content_type_options', esc_html__('X-Content-Type-Options', 'abdal-security-headers'), 'ash_basic_headers');
$this->add_checkbox_field('strict_transport_security', esc_html__('Strict-Transport-Security', 'abdal-security-headers'), 'ash_basic_headers');
$this->add_checkbox_field('permissions_policy', esc_html__('Permissions-Policy', 'abdal-security-headers'), 'ash_basic_headers');
$this->add_checkbox_field('x_frame_options', esc_html__('X-Frame-Options', 'abdal-security-headers'), 'ash_basic_headers');
$this->add_checkbox_field('referrer_policy', esc_html__('Referrer-Policy', 'abdal-security-headers'), 'ash_basic_headers');
// Additional Security Section
add_settings_section(
'ash_additional_security',
esc_html__('Additional Security Features', 'abdal-security-headers'),
array($this, 'print_additional_section_info'),
'abdal-security-headers'
);
// New Security Options
$this->add_checkbox_field('remove_x_powered_by', esc_html__('Remove X-Powered-By Header', 'abdal-security-headers'), 'ash_additional_security');
$this->add_checkbox_field('hide_wp_version', esc_html__('Hide WordPress Version', 'abdal-security-headers'), 'ash_additional_security');
$this->add_checkbox_field('remove_login_errors', esc_html__('Remove Login Error Messages', 'abdal-security-headers'), 'ash_additional_security');
$this->add_checkbox_field('disable_xmlrpc', esc_html__('Disable XML-RPC', 'abdal-security-headers'), 'ash_additional_security');
$this->add_checkbox_field('remove_x_pingback', esc_html__('Remove X-Pingback Header', 'abdal-security-headers'), 'ash_additional_security');
$this->add_checkbox_field('restrict_rest_api', esc_html__('Restrict REST API Access', 'abdal-security-headers'), 'ash_additional_security');
// CSP Section
add_settings_section(
'ash_csp_section',
esc_html__('Content Security Policy', 'abdal-security-headers'),
array($this, 'print_csp_section_info'),
'abdal-security-headers'
);
// CSP Fields
$csp_fields = array(
'default-src' => esc_html__('Default Source', 'abdal-security-headers'),
'script-src' => esc_html__('Script Source', 'abdal-security-headers'),
'style-src' => esc_html__('Style Source', 'abdal-security-headers'),
'img-src' => esc_html__('Image Source', 'abdal-security-headers'),
'connect-src' => esc_html__('Connect Source', 'abdal-security-headers'),
'font-src' => esc_html__('Font Source', 'abdal-security-headers'),
'object-src' => esc_html__('Object Source', 'abdal-security-headers'),
'media-src' => esc_html__('Media Source', 'abdal-security-headers'),
'frame-src' => esc_html__('Frame Source', 'abdal-security-headers'),
'worker-src' => esc_html__('Worker Source', 'abdal-security-headers'),
'form-action' => esc_html__('Form Action', 'abdal-security-headers'),
'base-uri' => esc_html__('Base URI', 'abdal-security-headers'),
'sandbox' => esc_html__('Sandbox', 'abdal-security-headers'),
'report-uri' => esc_html__('Report URI', 'abdal-security-headers'),
'report-to' => esc_html__('Report To', 'abdal-security-headers')
);
foreach ($csp_fields as $key => $label) {
$this->add_text_field('csp_' . str_replace('-', '_', $key), $label, 'ash_csp_section');
}
}
private function add_checkbox_field($id, $title, $section) {
add_settings_field(
$id,
$title,
array($this, 'checkbox_callback'),
'abdal-security-headers',
$section,
array(
'id' => $id,
'label' => $title,
'class' => 'ash-settings-row'
)
);
}
private function add_text_field($id, $title, $section) {
add_settings_field(
$id,
$title,
array($this, 'text_callback'),
'abdal-security-headers',
$section,
array(
'id' => $id,
'label' => $title,
'class' => 'ash-settings-row'
)
);
}
public function checkbox_callback($args) {
$id = isset($args['id']) ? $args['id'] : '';
$label = isset($args['label']) ? $args['label'] : '';
$checked = isset($this->options[$id]) ? $this->options[$id] : '0';
?>
options[$id]) ? $this->options[$id] : '';
?>