Files
att-consent/includes/class-admin.php
Steve Hanlon 2294c8eda7 Security hardening and accessibility improvements (v1.1.0)
Address findings from security audit: wrap inline JSON.parse in
try/catch to prevent consent flow failure on corrupted cookies,
allowlist admin tab parameter, clamp consent_expiry server-side,
escape all server values in admin JS table builder, fix focus trap
listener cleanup, add aria-labelledby and banner focus for screen
readers, skip frontend loading during AJAX.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:32:16 +00:00

315 lines
9.4 KiB
PHP

<?php
/**
* Admin settings handler.
*
* @package ATT_Consent
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class ATT_Consent_Admin {
/**
* Constructor.
*/
public function __construct() {
add_action( 'admin_menu', array( $this, 'add_menu_page' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_action( 'admin_post_att_cc_save_settings', array( $this, 'save_settings' ) );
add_action( 'wp_ajax_att_cc_save_script', array( $this, 'ajax_save_script' ) );
add_action( 'wp_ajax_att_cc_delete_script', array( $this, 'ajax_delete_script' ) );
add_action( 'wp_ajax_att_cc_get_script', array( $this, 'ajax_get_script' ) );
add_filter( 'plugin_action_links_' . ATT_CC_PLUGIN_BASENAME, array( $this, 'add_settings_link' ) );
}
/**
* Add settings link to plugins page.
*
* @param array $links Existing links.
* @return array
*/
public function add_settings_link( $links ) {
$settings_link = sprintf(
'<a href="%s">%s</a>',
esc_url( admin_url( 'admin.php?page=att-consent' ) ),
esc_html__( 'Settings', 'att-consent' )
);
array_unshift( $links, $settings_link );
return $links;
}
/**
* Register the admin menu page.
*/
public function add_menu_page() {
add_menu_page(
__( 'ATT Consent', 'att-consent' ),
__( 'Cookie Consent', 'att-consent' ),
'manage_options',
'att-consent',
array( $this, 'render_settings_page' ),
'dashicons-shield',
81
);
}
/**
* Enqueue admin assets.
*
* @param string $hook_suffix The current admin page hook.
*/
public function enqueue_assets( $hook_suffix ) {
if ( 'toplevel_page_att-consent' !== $hook_suffix ) {
return;
}
wp_enqueue_style( 'wp-color-picker' );
wp_enqueue_style(
'att-consent-admin',
ATT_CC_PLUGIN_URL . 'admin/css/admin.css',
array(),
ATT_CC_VERSION
);
wp_enqueue_script( 'wp-color-picker' );
wp_enqueue_script(
'att-consent-admin',
ATT_CC_PLUGIN_URL . 'admin/js/admin.js',
array( 'jquery', 'wp-color-picker' ),
ATT_CC_VERSION,
true
);
wp_localize_script( 'att-consent-admin', 'attCCAdmin', array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'att_cc_admin' ),
'strings' => array(
'confirmDelete' => __( 'Are you sure you want to delete this script?', 'att-consent' ),
'saved' => __( 'Script saved.', 'att-consent' ),
'deleted' => __( 'Script deleted.', 'att-consent' ),
'error' => __( 'An error occurred. Please try again.', 'att-consent' ),
),
) );
}
/**
* Render the settings page.
*/
public function render_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$tabs = array(
'general' => __( 'General', 'att-consent' ),
'appearance' => __( 'Appearance', 'att-consent' ),
'categories' => __( 'Categories', 'att-consent' ),
'scripts' => __( 'Custom Scripts', 'att-consent' ),
'advanced' => __( 'Advanced', 'att-consent' ),
);
$active_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! array_key_exists( $active_tab, $tabs ) ) {
$active_tab = 'general';
}
$settings = ATT_Consent::get_settings();
?>
<div class="wrap att-cc-admin-wrap">
<h1><?php esc_html_e( 'ATT Consent Settings', 'att-consent' ); ?></h1>
<?php
if ( isset( $_GET['saved'] ) && '1' === $_GET['saved'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Settings saved.', 'att-consent' ) . '</p></div>';
}
?>
<nav class="nav-tab-wrapper">
<?php foreach ( $tabs as $tab_key => $tab_label ) : ?>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=att-consent&tab=' . $tab_key ) ); ?>"
class="nav-tab <?php echo $active_tab === $tab_key ? 'nav-tab-active' : ''; ?>">
<?php echo esc_html( $tab_label ); ?>
</a>
<?php endforeach; ?>
</nav>
<div class="att-cc-tab-content">
<?php
$view_file = ATT_CC_PLUGIN_DIR . 'admin/views/settings-' . $active_tab . '.php';
if ( file_exists( $view_file ) ) {
include $view_file;
}
?>
</div>
</div>
<?php
}
/**
* Save settings from any tab.
*/
public function save_settings() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized.', 'att-consent' ) );
}
check_admin_referer( 'att_cc_save_settings' );
$tab = sanitize_key( $_POST['att_cc_tab'] ?? 'general' );
$settings = ATT_Consent::get_settings();
switch ( $tab ) {
case 'general':
$settings['tracking_mode'] = in_array( $_POST['tracking_mode'] ?? '', array( 'gtag', 'gtm' ), true ) ? $_POST['tracking_mode'] : 'gtag';
$settings['ga4_measurement_id'] = sanitize_text_field( $_POST['ga4_measurement_id'] ?? '' );
$settings['gtm_container_id'] = sanitize_text_field( $_POST['gtm_container_id'] ?? '' );
$settings['consent_mode'] = in_array( $_POST['consent_mode'] ?? '', array( 'advanced', 'basic' ), true ) ? $_POST['consent_mode'] : 'advanced';
$settings['banner_position'] = in_array( $_POST['banner_position'] ?? '', array( 'bottom', 'top', 'center' ), true ) ? $_POST['banner_position'] : 'bottom';
$settings['consent_expiry'] = min( 730, max( 1, absint( $_POST['consent_expiry'] ?? 365 ) ) );
$settings['floating_widget'] = in_array(
$_POST['floating_widget'] ?? '',
array( 'bottom-right', 'right', 'none' ),
true
) ? $_POST['floating_widget'] : 'bottom-right';
$settings['floating_widget_label'] = sanitize_text_field( $_POST['floating_widget_label'] ?? '' );
$settings['expose_gtag_global'] = ! empty( $_POST['expose_gtag_global'] );
break;
case 'appearance':
$color_fields = array(
'banner_bg_color', 'banner_text_color',
'btn_accept_bg', 'btn_accept_text',
'btn_reject_bg', 'btn_reject_text',
'btn_preferences_bg', 'btn_preferences_text',
);
foreach ( $color_fields as $field ) {
$value = $_POST[ $field ] ?? '';
if ( 'transparent' === $value ) {
$settings[ $field ] = 'transparent';
} else {
$settings[ $field ] = sanitize_hex_color( $value ) ?: $settings[ $field ];
}
}
$text_fields = array(
'banner_heading', 'banner_message',
'btn_accept_label', 'btn_reject_label',
'btn_preferences_label', 'btn_save_label',
);
foreach ( $text_fields as $field ) {
if ( 'banner_message' === $field ) {
$settings[ $field ] = wp_kses_post( $_POST[ $field ] ?? '' );
} else {
$settings[ $field ] = sanitize_text_field( $_POST[ $field ] ?? '' );
}
}
break;
case 'categories':
$cat_fields = array(
'cat_necessary_desc', 'cat_functional_desc',
'cat_analytics_desc', 'cat_marketing_desc',
);
foreach ( $cat_fields as $field ) {
$settings[ $field ] = wp_kses_post( $_POST[ $field ] ?? '' );
}
break;
case 'advanced':
$settings['url_passthrough'] = ! empty( $_POST['url_passthrough'] );
$settings['ads_data_redaction'] = ! empty( $_POST['ads_data_redaction'] );
$settings['wait_for_update'] = absint( $_POST['wait_for_update'] ?? 500 );
if ( $settings['wait_for_update'] < 100 ) {
$settings['wait_for_update'] = 100;
}
if ( $settings['wait_for_update'] > 10000 ) {
$settings['wait_for_update'] = 10000;
}
break;
}
$settings = apply_filters( 'att_consent_settings_save', $settings, $tab );
update_option( 'att_consent_settings', $settings );
wp_safe_redirect( admin_url( 'admin.php?page=att-consent&tab=' . $tab . '&saved=1' ) );
exit;
}
/**
* AJAX: Save a custom script.
*/
public function ajax_save_script() {
check_ajax_referer( 'att_cc_admin', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( 'Unauthorized' );
}
$data = array(
'id' => absint( $_POST['script_id'] ?? 0 ) ?: null,
'name' => $_POST['name'] ?? '',
'snippet' => $_POST['snippet'] ?? '',
'category' => $_POST['category'] ?? 'analytics',
'placement' => $_POST['placement'] ?? 'head',
'status' => $_POST['status'] ?? 'active',
'priority' => $_POST['priority'] ?? 10,
);
$result = ATT_Consent_Scripts_Manager::save_script( $data );
if ( false !== $result ) {
wp_send_json_success( array(
'id' => $result,
'scripts' => ATT_Consent_Scripts_Manager::get_all_scripts(),
) );
} else {
wp_send_json_error( 'Failed to save script.' );
}
}
/**
* AJAX: Delete a custom script.
*/
public function ajax_delete_script() {
check_ajax_referer( 'att_cc_admin', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( 'Unauthorized' );
}
$id = absint( $_POST['script_id'] ?? 0 );
if ( ! $id ) {
wp_send_json_error( 'Invalid ID.' );
}
if ( ATT_Consent_Scripts_Manager::delete_script( $id ) ) {
wp_send_json_success( array(
'scripts' => ATT_Consent_Scripts_Manager::get_all_scripts(),
) );
} else {
wp_send_json_error( 'Failed to delete script.' );
}
}
/**
* AJAX: Get a single script for editing.
*/
public function ajax_get_script() {
check_ajax_referer( 'att_cc_admin', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( 'Unauthorized' );
}
$id = absint( $_POST['script_id'] ?? 0 );
$script = ATT_Consent_Scripts_Manager::get_script( $id );
if ( $script ) {
wp_send_json_success( $script );
} else {
wp_send_json_error( 'Script not found.' );
}
}
}