options['general']['caching_compatibility']; // if it is enabled allow immediately if ( $db_cc ) return true; // check caching compatibility before it is saved, needed when we change caching_compatibility from false to true if ( ! ( isset( $_POST['save_cookie_notice_options'], $_POST['action'], $_POST['_wpnonce'], $_POST['option_page'], $_POST['cookie_notice_options'] ) && $_POST['option_page'] === 'cookie_notice_options' && wp_verify_nonce( $_POST['_wpnonce'], 'cookie_notice_options-options' ) !== false ) ) return false; // check availability of caching compatibility itself if ( ! isset( $_POST['cookie_notice_options']['caching_compatibility'] ) ) return false; // get active caching plugins $active_plugins = cn_get_active_caching_plugins(); // return caching compatibility on the fly return ! empty( $active_plugins ); } /** * Load additional modules. * * @return void */ public function load_modules() { // caching compatibility enabled? if ( $this->is_caching_compatibility() && Cookie_Notice()->get_status() === 'active' ) { // autoptimize if ( cn_is_plugin_active( 'autoptimize' ) ) include_once( COOKIE_NOTICE_PATH . 'includes/modules/autoptimize/autoptimize.php' ); // breeze if ( cn_is_plugin_active( 'breeze' ) ) include_once( COOKIE_NOTICE_PATH . 'includes/modules/breeze/breeze.php' ); // hummingbird if ( cn_is_plugin_active( 'hummingbird' ) ) include_once( COOKIE_NOTICE_PATH . 'includes/modules/hummingbird/hummingbird.php' ); // litespeed cache if ( cn_is_plugin_active( 'litespeed' ) ) include_once( COOKIE_NOTICE_PATH . 'includes/modules/litespeed-cache/litespeed-cache.php' ); // speedycache if ( cn_is_plugin_active( 'speedycache' ) ) include_once( COOKIE_NOTICE_PATH . 'includes/modules/speedycache/speedycache.php' ); // speed optimizer if ( cn_is_plugin_active( 'speedoptimizer' ) ) include_once( COOKIE_NOTICE_PATH . 'includes/modules/speed-optimizer/speed-optimizer.php' ); // wp fastest cache if ( cn_is_plugin_active( 'wpfastestcache' ) ) include_once( COOKIE_NOTICE_PATH . 'includes/modules/wp-fastest-cache/wp-fastest-cache.php' ); // wp-optimize if ( cn_is_plugin_active( 'wpoptimize' ) ) include_once( COOKIE_NOTICE_PATH . 'includes/modules/wp-optimize/wp-optimize.php' ); // wp rocket if ( cn_is_plugin_active( 'wprocket' ) ) include_once( COOKIE_NOTICE_PATH . 'includes/modules/wp-rocket/wp-rocket.php' ); // wp super cache if ( cn_is_plugin_active( 'wpsupercache' ) ) include_once( COOKIE_NOTICE_PATH . 'includes/modules/wp-super-cache/wp-super-cache.php' ); } } /** * Load plugin defaults. * * @return void */ public function load_defaults() { // set tabs $this->tabs = [ 'settings' => __( 'Cookie Consent', 'cookie-notice' ), 'privacy-consent' => __( 'Privacy Consent', 'cookie-notice' ), 'consent-logs' => __( 'Consent Logs', 'cookie-notice' ) ]; // get first default tab $first_tab = array_key_first( $this->tabs ); // sanitize current tab $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : $first_tab; // set current tab $this->current_tab = ! empty( $tab ) && array_key_exists( $tab, $this->tabs ) ? $tab : $first_tab; if ( $this->current_tab === 'consent-logs' ) { $this->sections = [ 'cookie' => __( 'Cookie Consent Logs', 'cookie-notice' ), 'privacy' => __( 'Privacy Consent Logs', 'cookie-notice' ) ]; // check section $section = isset( $_GET['section'] ) ? sanitize_key( $_GET['section'] ) : ''; if ( ! $section || ! array_key_exists( $section, $this->sections ) ) $section = array_key_first( $this->sections ); $this->current_section = $section; } $this->parameters = [ 'page_type' => __( 'Page Type', 'cookie-notice' ), 'page' => __( 'Page', 'cookie-notice' ), 'post_type' => __( 'Post Type', 'cookie-notice' ), 'post_type_archive' => __( 'Post Type Archive', 'cookie-notice' ), 'user_type' => __( 'User Type', 'cookie-notice' ), 'taxonomy_archive' => __( 'Taxonomy Archive', 'cookie-notice' ) ]; $this->operators = [ 'equal' => __( 'is equal to', 'cookie-notice' ), 'not_equal' => __( 'is not equal to', 'cookie-notice' ) ]; $this->conditional_display_types = [ 'hide' => __( 'Hide the banner', 'cookie-notice' ), 'show' => __( 'Show the banner', 'cookie-notice' ) ]; $this->positions = [ 'top' => __( 'Top', 'cookie-notice' ), 'bottom' => __( 'Bottom', 'cookie-notice' ) ]; $this->styles = [ 'none' => __( 'None', 'cookie-notice' ), 'wp-default' => __( 'Light', 'cookie-notice' ), 'bootstrap' => __( 'Dark', 'cookie-notice' ) ]; $this->revoke_opts = [ 'automatic' => __( 'Automatic', 'cookie-notice' ), 'manual' => __( 'Manual', 'cookie-notice' ) ]; $this->links = [ 'page' => __( 'Page link', 'cookie-notice' ), 'custom' => __( 'Custom link', 'cookie-notice' ) ]; $this->link_targets = [ '_blank', '_self' ]; $this->link_positions = [ 'banner' => __( 'Banner', 'cookie-notice' ), 'message' => __( 'Message', 'cookie-notice' ) ]; $this->colors = [ 'text' => __( 'Text color', 'cookie-notice' ), 'button' => __( 'Button color', 'cookie-notice' ), 'bar' => __( 'Bar color', 'cookie-notice' ) ]; $this->times = apply_filters( 'cn_cookie_expiry', [ 'hour' => [ __( 'An hour', 'cookie-notice' ), HOUR_IN_SECONDS ], 'day' => [ __( '1 day', 'cookie-notice' ), DAY_IN_SECONDS ], 'week' => [ __( '1 week', 'cookie-notice' ), WEEK_IN_SECONDS ], 'month' => [ __( '1 month', 'cookie-notice' ), MONTH_IN_SECONDS ], '3months' => [ __( '3 months', 'cookie-notice' ), 7862400 ], '6months' => [ __( '6 months', 'cookie-notice' ), 15811200 ], 'year' => [ __( '1 year', 'cookie-notice' ), YEAR_IN_SECONDS ], 'infinity' => [ __( 'infinity', 'cookie-notice' ), 2147483647 ] ] ); $this->effects = [ 'none' => __( 'None', 'cookie-notice' ), 'fade' => __( 'Fade', 'cookie-notice' ), 'slide' => __( 'Slide', 'cookie-notice' ) ]; $this->script_placements = [ 'header' => __( 'Header', 'cookie-notice' ), 'footer' => __( 'Footer', 'cookie-notice' ) ]; $this->level_names = [ 1 => [ 1 => __( 'Private', 'cookie-notice' ), 2 => __( 'Balanced', 'cookie-notice' ), 3 => __( 'Personalized', 'cookie-notice' ) ], 2 => [ 1 => __( 'Silver', 'cookie-notice' ), 2 => __( 'Gold', 'cookie-notice' ), 3 => __( 'Platinum', 'cookie-notice' ) ], 3 => [ 1 => __( 'Reject All', 'cookie-notice' ), 2 => __( 'Accept Some', 'cookie-notice' ), 3 => __( 'Accept All', 'cookie-notice' ) ] ]; $this->text_strings = [ 'saveBtnText' => __( 'Save my preferences', 'cookie-notice' ), 'privacyBtnText' => __( 'Privacy policy', 'cookie-notice' ), 'dontSellBtnText' => __( 'Do Not Sell', 'cookie-notice' ), 'customizeBtnText' => __( 'Preferences', 'cookie-notice' ), 'headingText' => __( "Your data is your property and we support your right to privacy and transparency.", 'cookie-notice' ), 'bodyText' => __( "To provide you the best experience on our website, we use cookies or similar technologies. Select a data access level to decide for which purposes we may use and share your data.", 'cookie-notice' ), 'levelBodyText_1' => __( 'Highest level of privacy. Data accessed for necessary site operations only. Data shared with 3rd parties to ensure the site is secure and works on your device.', 'cookie-notice' ), 'levelBodyText_2' => __( 'Balanced experience. Data accessed for content personalisation and site optimisation. Data shared with 3rd parties may be used to track and store your preferences for this site.', 'cookie-notice' ), 'levelBodyText_3' => __( 'Highest level of personalisation. Data accessed to make ads and media more relevant. Data shared with 3rd parties may be use to track you on this site and other sites you visit.', 'cookie-notice' ), 'levelNameText_1' => $this->level_names[1][1], 'levelNameText_2' => $this->level_names[1][2], 'levelNameText_3' => $this->level_names[1][3], 'monthText' => __( 'month', 'cookie-notice' ), 'monthsText' => __( 'months', 'cookie-notice' ) ]; // get main instance $cn = Cookie_Notice(); // set default text strings $cn->defaults['general']['message_text'] = __( 'We use cookies to ensure that we give you the best experience on our website. If you continue to use this site we will assume that you are happy with it.', 'cookie-notice' ); $cn->defaults['general']['accept_text'] = __( 'Ok', 'cookie-notice' ); $cn->defaults['general']['refuse_text'] = __( 'No', 'cookie-notice' ); $cn->defaults['general']['revoke_message_text'] = __( 'You can revoke your consent any time using the Revoke consent button.', 'cookie-notice' ); $cn->defaults['general']['revoke_text'] = __( 'Revoke consent', 'cookie-notice' ); $cn->defaults['general']['see_more_opt']['text'] = __( 'Privacy policy', 'cookie-notice' ); // set translation strings on plugin activation if ( ! empty( $cn->options['general']['translate'] ) ) { $cn->options['general']['translate'] = false; $cn->options['general']['message_text'] = $cn->defaults['general']['message_text']; $cn->options['general']['accept_text'] = $cn->defaults['general']['accept_text']; $cn->options['general']['refuse_text'] = $cn->defaults['general']['refuse_text']; $cn->options['general']['revoke_message_text'] = $cn->defaults['general']['revoke_message_text']; $cn->options['general']['revoke_text'] = $cn->defaults['general']['revoke_text']; $cn->options['general']['see_more_opt']['text'] = $cn->defaults['general']['see_more_opt']['text']; if ( $cn->is_network_admin() ) update_site_option( 'cookie_notice_options', $cn->options['general'] ); else update_option( 'cookie_notice_options', $cn->options['general'] ); } // WPML >= 3.2 if ( defined( 'ICL_SITEPRESS_VERSION' ) && version_compare( ICL_SITEPRESS_VERSION, '3.2', '>=' ) ) { $this->register_wpml_strings(); // WPML and Polylang compatibility } elseif ( function_exists( 'icl_register_string' ) ) { icl_register_string( 'Cookie Notice', 'Message in the notice', $cn->options['general']['message_text'] ); icl_register_string( 'Cookie Notice', 'Button text', $cn->options['general']['accept_text'] ); icl_register_string( 'Cookie Notice', 'Refuse button text', $cn->options['general']['refuse_text'] ); icl_register_string( 'Cookie Notice', 'Revoke message text', $cn->options['general']['revoke_message_text'] ); icl_register_string( 'Cookie Notice', 'Revoke button text', $cn->options['general']['revoke_text'] ); icl_register_string( 'Cookie Notice', 'Privacy policy text', $cn->options['general']['see_more_opt']['text'] ); icl_register_string( 'Cookie Notice', 'Custom link', $cn->options['general']['see_more_opt']['link'] ); } } /** * Add submenu. * * @return void */ public function admin_menu_options() { if ( current_action() === 'network_admin_menu' && ! Cookie_Notice()->is_plugin_network_active() ) return; $cap = apply_filters( 'cn_manage_cookie_notice_cap', 'manage_options' ); add_menu_page( __( 'Compliance by Hu-manity.co', 'cookie-notice' ), __( 'Compliance', 'cookie-notice' ), $cap, 'cookie-notice', [ $this, 'options_page' ], 'none', '99.300' ); // React mode: no submenus — React owns in-page tab navigation. // Legacy mode: three submenus matching the PHP tab structure. if ( Cookie_Notice()->options['general']['ui_mode'] === 'legacy' ) { add_submenu_page( 'cookie-notice', __( 'Compliance - Cookie Consent', 'cookie-notice' ), __( 'Cookie Consent', 'cookie-notice' ), $cap, 'cookie-notice', [ $this, 'options_page' ] ); add_submenu_page( 'cookie-notice', __( 'Compliance - Privacy Consent', 'cookie-notice' ), $this->mark_new( __( 'Privacy Consent', 'cookie-notice' ) ), $cap, 'cookie-notice&tab=privacy-consent', [ $this, 'options_page' ] ); add_submenu_page( 'cookie-notice', __( 'Compliance - Consent Logs', 'cookie-notice' ), __( 'Consent Logs', 'cookie-notice' ), $cap, 'cookie-notice&tab=consent-logs', [ $this, 'options_page' ] ); // highlight submenus add_filter( 'submenu_file', [ $this, 'submenu_file' ], 10, 2 ); } } /** * Adds an indicator to mark a new menu item. * * @return string */ private function mark_new( $title ) { return sprintf( '%s', $title, __( 'NEW!', 'cookie-notice' ) ); } /** * Highlight submenu items. * * @param string|null $submenu_file * @param string $parent_file * @return string|null */ public function submenu_file( $submenu_file, $parent_file ) { if ( $parent_file === 'cookie-notice' ) { $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'settings'; if ( $tab !== 'settings' ) return 'cookie-notice&tab=' . $tab; } return $submenu_file; } /** * Options page output. * * @return void */ public function options_page() { // get main instance $cn = Cookie_Notice(); // get cookie compliance status $status = $cn->get_status(); $ui_mode = $cn->options['general']['ui_mode']; echo '
' . esc_html__( 'Loading Compliance dashboard…', 'cookie-notice' ) . '
' . esc_html__( 'This usually takes a second. If the page stays on this message, the admin UI failed to load.', 'cookie-notice' ) . '
' . sprintf( /* translators: %s: link to the Hu-manity support dashboard */ esc_html__( 'If the issue persists, %s.', 'cookie-notice' ), '' . esc_html__( 'open the Hu-manity dashboard to contact support', 'cookie-notice' ) . '' ) . '
' . esc_html__( 'Every site in the network will use the same settings. Site administrators will not be able to change them.', 'cookie-notice' ) . '
' . esc_html__( 'Global network settings override is active. Every site will use the same network settings. Please contact super administrator if you want to have more control over the settings.', 'cookie-notice' ) . '
'; } /** * Consent logs section. * * @return void */ public function cn_consent_logs_section() { if ( ! $this->allow_consent_logs ) return; echo '' . esc_html__( 'Log in to the Compliance by Hu-manity.co dashboard to explore, configure and manage its functionalities.', 'cookie-notice' ) . '
' . esc_html__( 'Log in to the Compliance by Hu-manity.co web application and complete the setup process.', 'cookie-notice' ) . '
' . sprintf( esc_html__( 'Sign up to %s and add GDPR, CCPA and other international data privacy laws compliance features.', 'cookie-notice' ), 'Compliance by Hu-manity.co' ) . '
' . esc_html__( 'Log in to the Compliance by Hu-manity.co dashboard to explore, configure and manage its functionalities.', 'cookie-notice' ) . '
' . esc_html__( 'Log in to the Compliance by Hu-manity.co web application and complete the setup process.', 'cookie-notice' ) . '
' . sprintf( esc_html__( 'Sign up to %s and enable Privacy Consent support.', 'cookie-notice' ), 'Compliance by Hu-manity.co' ) . '
' . esc_html__( 'Enter your Compliance by Hu-manity.co application ID.', 'cookie-notice' ) . '
' . esc_html__( 'Enter your Compliance by Hu-manity.co application secret key.', 'cookie-notice' ) . '
* ' . esc_html__( 'This option has been temporarily disabled because your website has reached the usage limit for the Compliance by Hu-manity.co Free Plan. It will become available again when the current visits cycle resets or you upgrade your website to a Professional plan.', 'cookie-notice' ) . '
' : '' ) . '' . esc_html__( 'Enter WordPress script handles to exclude from autoblocking, one per line. These scripts will be marked as Essential (Category 1) so they are never blocked.', 'cookie-notice' ) . '
' . esc_html__( 'Manually pull the latest configuration including autoblocking. Configuration syncs automatically every 24 hours.', 'cookie-notice' ) . '
' . esc_html__( 'Click the Purge Cache button to refresh the app configuration.', 'cookie-notice' ) . '
' . ( ! $amp_enabled ? esc_html__( 'No compatible Google AMP plugins found.', 'cookie-notice' ) : esc_html__( 'Allows you to activate consent banner support for Google AMP.', 'cookie-notice' ) ) . '
' . esc_html__( 'Currently detected active caching plugins', 'cookie-notice' ) . ': ';
foreach ( $active_plugins as $plugin ) {
$active_plugins_html[] = '' . esc_html( $plugin ) . '';
}
$plugins_html .= implode( ', ', $active_plugins_html ) . '.
' . esc_html__( 'No compatible cache plugins found.', 'cookie-notice' ) . '
'; echo '' . esc_html__( 'Enter the cookie notice message.', 'cookie-notice' ) . '
' . esc_html__( 'The text of the option to accept the notice and make it disappear.', 'cookie-notice' ) . '
' . esc_html__( 'The code to be used in your site header, before the closing head tag.', 'cookie-notice' ) . '
' . esc_html__( 'The code to be used in your site footer, before the closing body tag.', 'cookie-notice' ) . '
' . esc_html__( 'Enter non functional cookies Javascript code here (for e.g. Google Analitycs) to be used after the visitor consent is given.', 'cookie-notice' ) . '
' . esc_html__( 'Group cookies by purpose and let users consent selectively.', 'cookie-notice' ) . '
' . esc_html__( 'Track consent rates, view logs, and generate compliance reports.', 'cookie-notice' ) . '
' . esc_html__( 'Connect your site to unlock →', 'cookie-notice' ) . '
' . esc_html__( 'The amount of time that the cookie should be stored for when user accepts the notice.', 'cookie-notice' ) . '
' . esc_html__( 'The amount of time that the cookie should be stored for when the user doesn\'t accept the notice.', 'cookie-notice' ) . '
' . esc_html__( 'Select where all the plugin scripts should be placed.', 'cookie-notice' ) . '
' . esc_html__( 'Select location for the notice.', 'cookie-notice' ) . '
' . esc_html__( 'Select the animation style.', 'cookie-notice' ) . '
' . esc_html__( 'Enter additional button CSS classes separated by spaces.', 'cookie-notice' ) . '
' . esc_html__( "img-src data:; style-src 'unsafe-inline'; connect-src *.hu-manity.co; script-src 'unsafe-inline' *.hu-manity.co" ) . '', 'error' );
}
}
/**
* Validate options.
*
* @param array $input
*
* @return array
*/
public function validate_options( $input ) {
if ( ! current_user_can( apply_filters( 'cn_manage_cookie_notice_cap', 'manage_options' ) ) )
return $input;
// get main instance
$cn = Cookie_Notice();
$is_network = $cn->is_network_admin();
if ( isset( $_POST['save_cookie_notice_options'] ) ) {
// app id
$input['app_id'] = isset( $input['app_id'] ) ? sanitize_key( $input['app_id'] ) : $cn->defaults['general']['app_id'];
// app key
$input['app_key'] = isset( $input['app_key'] ) ? sanitize_key( $input['app_key'] ) : $cn->defaults['general']['app_key'];
// set app status
if ( ! empty( $input['app_id'] ) && ! empty( $input['app_key'] ) ) {
$app_data = $cn->welcome_api->get_app_config( $input['app_id'], true, false );
if ( $cn->check_status( $app_data['status'] ) === 'active' && $cn->options['general']['app_id'] !== $input['app_id'] ) {
// get_app_analytics requires fresh app data
$this->analytics_app_data = [
'id' => $input['app_id'],
'key' => $input['app_key']
];
// update analytics data
$cn->welcome_api->get_app_analytics( $input['app_id'], true, false );
$this->analytics_app_data = [];
}
} else {
if ( $is_network )
update_site_option( 'cookie_notice_status', $cn->defaults['data'] );
else
update_option( 'cookie_notice_status', $cn->defaults['data'] );
}
// app blocking — checkbox: absent from POST when unchecked.
// If not submitted at all, preserve the existing DB value instead of defaulting
// to false. This prevents a legacy form save from zeroing out a value that was
// set via the React path (field partition: app_blocking is plugin-owned but the
// legacy form does not always render the checkbox, e.g. when connected). See #2272.
if ( isset( $input['app_blocking_rendered'] ) ) {
// Checkbox was rendered and enabled — honour the submitted value (absent = unchecked = false).
$input['app_blocking'] = isset( $input['app_blocking'] ) && ! $cn->threshold_exceeded();
} else {
// Checkbox not rendered (network admin, global_override sub-site) or threshold
// exceeded (disabled) — preserve the DB value via the preservation loop below.
unset( $input['app_blocking'] );
}
unset( $input['app_blocking_rendered'] );
// excluded script handles
$input['excluded_handles'] = isset( $input['excluded_handles'] )
? array_values( array_filter( array_map( 'sanitize_text_field', explode( "\n", $input['excluded_handles'] ) ) ) )
: $cn->defaults['general']['excluded_handles'];
// conditional display
$input['conditional_active'] = isset( $input['conditional_active'] );
$input['conditional_display'] = isset( $input['conditional_display'] ) ? sanitize_key( $input['conditional_display'] ) : $cn->defaults['general']['conditional_display'];
if ( ! in_array( $input['conditional_display'], array_keys( $this->conditional_display_types ), true ) )
$input['conditional_display'] = $cn->defaults['general']['conditional_display'];
if ( ! empty( $input['conditional_rules'] ) && is_array( $input['conditional_rules'] ) ) {
$group_id = $rule_id = 1;
$rules = [];
foreach ( $input['conditional_rules'] as $group_number => $group ) {
// skips template data or empty groups
if ( (int) $group_number <= 0 || empty( $group ) )
continue;
foreach ( $group as $rule ) {
$param = sanitize_key( $rule['param'] );
$operator = sanitize_key( $rule['operator'] );
// do not sanitize value for taxonomy archive
if ( $param === 'taxonomy_archive' )
$value = $rule['value'];
else
$value = sanitize_key( $rule['value'] );
if ( $this->check_rule( $param, $operator, $value ) ) {
$rules[$group_id][$rule_id++] = [
'param' => $param,
'operator' => $operator,
'value' => $value
];
}
}
$rule_id = 1;
$group_id++;
}
$input['conditional_rules'] = $rules;
} else
$input['conditional_rules'] = [];
// bot detection
$input['bot_detection'] = isset( $input['bot_detection'] );
// debug mode
$input['debug_mode'] = isset( $input['debug_mode'] );
// ui_mode is managed exclusively by maybe_switch_ui_mode() via nonce-gated
// query param. It must not be processed here — the #2153 preservation loop
// carries the existing DB value through. See #2155.
// amp support
$input['amp_support'] = isset( $input['amp_support'] ) && cn_is_plugin_active( 'amp' );
// get active caching plugins
$active_plugins = cn_get_active_caching_plugins();
// caching compatibility
$input['caching_compatibility'] = isset( $input['caching_compatibility'] ) && ! empty( $active_plugins );
// display csp notice?
if ( $cn->get_status() === 'active' )
$input['csp_notice'] = $this->check_htaccess();
else
$input['csp_notice'] = false;
// position
if ( isset( $input['position'] ) ) {
$input['position'] = sanitize_key( $input['position'] );
if ( ! array_key_exists( $input['position'], $this->positions ) )
$input['position'] = $cn->defaults['general']['position'];
} else
$input['position'] = $cn->defaults['general']['position'];
// text color
if ( isset( $input['colors']['text'] ) ) {
$input['colors']['text'] = sanitize_hex_color( $input['colors']['text'] );
if ( empty( $input['colors']['text'] ) )
$input['colors']['text'] = $cn->defaults['general']['colors']['text'];
} else
$input['colors']['text'] = $cn->defaults['general']['colors']['text'];
// button color
if ( isset( $input['colors']['button'] ) ) {
$input['colors']['button'] = sanitize_hex_color( $input['colors']['button'] );
if ( empty( $input['colors']['button'] ) )
$input['colors']['button'] = $cn->defaults['general']['colors']['button'];
} else
$input['colors']['button'] = $cn->defaults['general']['colors']['button'];
// bar color
if ( isset( $input['colors']['bar'] ) ) {
$input['colors']['bar'] = sanitize_hex_color( $input['colors']['bar'] );
if ( empty( $input['colors']['bar'] ) )
$input['colors']['bar'] = $cn->defaults['general']['colors']['bar'];
} else
$input['colors']['bar'] = $cn->defaults['general']['colors']['bar'];
// bar opacity
$input['colors']['bar_opacity'] = isset( $input['colors']['bar_opacity'] ) ? (int) $input['colors']['bar_opacity'] : $cn->defaults['general']['colors']['bar_opacity'];
if ( $input['colors']['bar_opacity'] < 50 || $input['colors']['bar_opacity'] > 100 )
$input['colors']['bar_opacity'] = $cn->defaults['general']['colors']['bar_opacity'];
// message text
if ( isset( $input['message_text'] ) ) {
add_filter( 'safe_style_css', [ $this, 'allow_style_attributes' ] );
$input['message_text'] = wp_kses_post( trim( $input['message_text'] ) );
remove_filter( 'safe_style_css', [ $this, 'allow_style_attributes' ] );
if ( $input['message_text'] === '' )
$input['message_text'] = $cn->defaults['general']['message_text'];
} else
$input['message_text'] = $cn->defaults['general']['message_text'];
// accept button text
if ( isset( $input['accept_text'] ) ) {
$input['accept_text'] = sanitize_text_field( $input['accept_text'] );
if ( $input['accept_text'] === '' )
$input['accept_text'] = $cn->defaults['general']['accept_text'];
} else
$input['accept_text'] = $cn->defaults['general']['accept_text'];
// refuse button text
if ( isset( $input['refuse_text'] ) ) {
$input['refuse_text'] = sanitize_text_field( $input['refuse_text'] );
if ( $input['refuse_text'] === '' )
$input['refuse_text'] = $cn->defaults['general']['refuse_text'];
} else
$input['refuse_text'] = $cn->defaults['general']['refuse_text'];
// revoke message text
if ( isset( $input['revoke_message_text'] ) ) {
add_filter( 'safe_style_css', [ $this, 'allow_style_attributes' ] );
$input['revoke_message_text'] = wp_kses_post( trim( $input['revoke_message_text'] ) );
remove_filter( 'safe_style_css', [ $this, 'allow_style_attributes' ] );
if ( $input['revoke_message_text'] === '' )
$input['revoke_message_text'] = $cn->defaults['general']['revoke_message_text'];
} else
$input['revoke_message_text'] = $cn->defaults['general']['revoke_message_text'];
// revoke button text
if ( isset( $input['revoke_text'] ) ) {
$input['revoke_text'] = sanitize_text_field( $input['revoke_text'] );
if ( $input['revoke_text'] === '' )
$input['revoke_text'] = $cn->defaults['general']['revoke_text'];
} else
$input['revoke_text'] = $cn->defaults['general']['revoke_text'];
// refuse consent
$input['refuse_opt'] = isset( $input['refuse_opt'] );
// revoke consent
$input['revoke_cookies'] = isset( $input['revoke_cookies'] );
// revoke consent type
if ( isset( $input['revoke_cookies_opt'] ) ) {
$input['revoke_cookies_opt'] = sanitize_key( $input['revoke_cookies_opt'] );
if ( ! array_key_exists( $input['revoke_cookies_opt'], $this->revoke_opts ) )
$input['revoke_cookies_opt'] = $cn->defaults['general']['revoke_cookies_opt'];
} else
$input['revoke_cookies_opt'] = $cn->defaults['general']['revoke_cookies_opt'];
// body refuse code
if ( isset( $input['refuse_code'] ) )
$input['refuse_code'] = wp_kses( trim( $input['refuse_code'] ), $cn->get_allowed_html( 'body' ) );
else
$input['refuse_code'] = $cn->defaults['general']['refuse_code'];
// head refuse code
if ( isset( $input['refuse_code_head'] ) )
$input['refuse_code_head'] = wp_kses( trim( $input['refuse_code_head'] ), $cn->get_allowed_html( 'head' ) );
else
$input['refuse_code_head'] = $cn->defaults['general']['refuse_code_head'];
// css button class(es)
if ( isset( $input['css_class'] ) ) {
$input['css_class'] = trim( $input['css_class'] );
if ( $input['css_class'] !== '' ) {
// more than 1 class?
if ( strpos( $input['css_class'], ' ' ) !== false ) {
// get unique valid html classes
$input['css_class'] = array_unique( array_filter( array_map( 'sanitize_html_class', explode( ' ', $input['css_class'] ) ) ) );
if ( ! empty( $input['css_class'] ) )
$input['css_class'] = implode( ' ', $input['css_class'] );
else
$input['css_class'] = $cn->defaults['general']['css_class'];
// single class
} else
$input['css_class'] = sanitize_html_class( $input['css_class'] );
}
} else
$input['css_class'] = $cn->defaults['general']['css_class'];
// accepted expiry
if ( isset( $input['time'] ) ) {
$input['time'] = sanitize_key( $input['time'] );
if ( ! array_key_exists( $input['time'], $this->times ) )
$input['time'] = $cn->defaults['general']['time'];
} else
$input['time'] = $cn->defaults['general']['time'];
// rejected expiry
if ( isset( $input['time_rejected'] ) ) {
$input['time_rejected'] = sanitize_key( $input['time_rejected'] );
if ( ! array_key_exists( $input['time_rejected'], $this->times ) )
$input['time_rejected'] = $cn->defaults['general']['time_rejected'];
} else
$input['time_rejected'] = $cn->defaults['general']['time_rejected'];
// script placement
if ( isset( $input['script_placement'] ) ) {
$input['script_placement'] = sanitize_key( $input['script_placement'] );
if ( ! array_key_exists( $input['script_placement'], $this->script_placements ) )
$input['script_placement'] = $cn->defaults['general']['script_placement'];
} else
$input['script_placement'] = $cn->defaults['general']['script_placement'];
// hide effect
if ( isset( $input['hide_effect'] ) ) {
$input['hide_effect'] = sanitize_key( $input['hide_effect'] );
if ( ! array_key_exists( $input['hide_effect'], $this->effects ) )
$input['hide_effect'] = $cn->defaults['general']['hide_effect'];
} else
$input['hide_effect'] = $cn->defaults['general']['hide_effect'];
// reloading
$input['redirection'] = isset( $input['redirection'] );
// on scroll
$input['on_scroll'] = isset( $input['on_scroll'] );
// on scroll offset
$input['on_scroll_offset'] = isset( $input['on_scroll_offset'] ) ? (int) $input['on_scroll_offset'] : $cn->defaults['general']['on_scroll_offset'];
if ( $input['on_scroll_offset'] < 0 )
$input['on_scroll_offset'] = 0;
// on click
$input['on_click'] = isset( $input['on_click'] );
// deactivation
$input['deactivation_delete'] = isset( $input['deactivation_delete'] );
// privacy policy
$input['see_more'] = isset( $input['see_more'] );
// privacy policy link text
if ( isset( $input['see_more_opt']['text'] ) ) {
$input['see_more_opt']['text'] = sanitize_text_field( $input['see_more_opt']['text'] );
if ( $input['see_more_opt']['text'] === '' )
$input['see_more_opt']['text'] = $cn->defaults['general']['see_more_opt']['text'];
} else
$input['see_more_opt']['text'] = $cn->defaults['general']['see_more_opt']['text'];
// privacy policy link type
if ( isset( $input['see_more_opt']['link_type'] ) ) {
$input['see_more_opt']['link_type'] = sanitize_key( $input['see_more_opt']['link_type'] );
if ( ! array_key_exists( $input['see_more_opt']['link_type'], $this->links ) )
$input['see_more_opt']['link_type'] = $cn->defaults['general']['see_more_opt']['link_type'];
} else
$input['see_more_opt']['link_type'] = $cn->defaults['general']['see_more_opt']['link_type'];
if ( $input['see_more_opt']['link_type'] === 'custom' )
$input['see_more_opt']['link'] = $input['see_more'] && isset( $input['see_more_opt']['link'] ) ? esc_url_raw( $input['see_more_opt']['link'] ) : '';
elseif ( $input['see_more_opt']['link_type'] === 'page' ) {
$input['see_more_opt']['id'] = $input['see_more'] && isset( $input['see_more_opt']['id'] ) ? (int) $input['see_more_opt']['id'] : 0;
$input['see_more_opt']['sync'] = isset( $input['see_more_opt']['sync'] );
if ( $input['see_more_opt']['sync'] )
update_option( 'wp_page_for_privacy_policy', $input['see_more_opt']['id'] );
}
// privacy policy link target
if ( isset( $input['link_target'] ) ) {
$input['link_target'] = sanitize_key( $input['link_target'] );
if ( ! in_array( $input['link_target'], $this->link_targets, true ) )
$input['link_target'] = $cn->defaults['general']['link_target'];
} else
$input['link_target'] = $cn->defaults['general']['link_target'];
// policy policy link position
if ( isset( $input['link_position'] ) ) {
$input['link_position'] = sanitize_key( $input['link_position'] );
if ( ! array_key_exists( $input['link_position'], $this->link_positions ) )
$input['link_position'] = $cn->defaults['general']['link_position'];
} else
$input['link_position'] = $cn->defaults['general']['link_position'];
// message link position?
if ( $input['see_more'] && $input['link_position'] === 'message' && strpos( $input['message_text'], '[cookies_policy_link' ) === false )
$input['message_text'] .= ' [cookies_policy_link]';
// notice data
$input['update_version'] = $cn->options['general']['update_version'];
$input['update_notice'] = $cn->options['general']['update_notice'];
$input['review_notice'] = $cn->options['general']['review_notice'];
$input['review_notice_delay'] = $cn->options['general']['review_notice_delay'];
$input['translate'] = false;
// WPML >= 3.2
if ( defined( 'ICL_SITEPRESS_VERSION' ) && version_compare( ICL_SITEPRESS_VERSION, '3.2', '>=' ) ) {
do_action( 'wpml_register_single_string', 'Cookie Notice', 'Message in the notice', $input['message_text'] );
do_action( 'wpml_register_single_string', 'Cookie Notice', 'Button text', $input['accept_text'] );
do_action( 'wpml_register_single_string', 'Cookie Notice', 'Refuse button text', $input['refuse_text'] );
do_action( 'wpml_register_single_string', 'Cookie Notice', 'Revoke message text', $input['revoke_message_text'] );
do_action( 'wpml_register_single_string', 'Cookie Notice', 'Revoke button text', $input['revoke_text'] );
do_action( 'wpml_register_single_string', 'Cookie Notice', 'Privacy policy text', $input['see_more_opt']['text'] );
if ( $input['see_more_opt']['link_type'] === 'custom' )
do_action( 'wpml_register_single_string', 'Cookie Notice', 'Custom link', $input['see_more_opt']['link'] );
}
// Preserve any keys in the current DB options that validate_options() does not
// explicitly process (e.g. displayType, ui_mode, update_delay_date,
// update_threshold_date, update_notice_diss). Without this, WordPress's register_setting()
// replaces the entire cookie_notice_options row with $input, silently dropping these fields
// on every legacy form save. See #2153.
// Re-read from DB here (not from constructor snapshot) to avoid overwriting concurrent
// React changes made in another tab after this page loaded. See #2181.
$current_db_options = $is_network
? (array) get_site_option( 'cookie_notice_options', [] )
: (array) get_option( 'cookie_notice_options', [] );
foreach ( $current_db_options as $key => $value ) {
if ( ! array_key_exists( $key, $input ) ) {
$input[ $key ] = $value;
}
}
add_settings_error( 'cn_cookie_notice_options', 'save_cookie_notice_options', esc_html__( 'Settings saved.', 'cookie-notice' ), 'updated' );
} elseif ( isset( $_POST['reset_cookie_notice_options'] ) ) {
$input = $cn->defaults['general'];
add_settings_error( 'cn_cookie_notice_options', 'reset_cookie_notice_options', esc_html__( 'Settings restored to defaults.', 'cookie-notice' ), 'updated' );
// network area?
if ( $is_network ) {
// set app data
update_site_option( 'cookie_notice_status', $cn->defaults['data'] );
} else {
// set app data
update_option( 'cookie_notice_status', $cn->defaults['data'] );
}
}
do_action( 'cn_configuration_updated', 'settings', $input );
return $input;
}
/**
* Validate network options.
*
* @return void
*/
public function validate_network_options() {
if ( ! current_user_can( apply_filters( 'cn_manage_cookie_notice_cap', 'manage_options' ) ) )
return;
// get main instance
$cn = Cookie_Notice();
// global network page?
if ( $cn->is_network_admin() && isset( $_POST['cn-network-settings'] ) ) {
// network settings
if ( ! empty( $_POST['cookie_notice_options'] ) && check_admin_referer( 'cookie_notice_options-options', '_wpnonce' ) !== false ) {
if ( isset( $_POST['save_cookie_notice_options'] ) ) {
// need to force it early for get_app_config and get_app_analytics
$cn->network_options['general']['global_override'] = true;
// validate options
$data = $this->validate_options( $_POST['cookie_notice_options'] );
// check network settings
$data['global_override'] = isset( $_POST['cookie_notice_options']['global_override'] );
$data['global_cookie'] = isset( $_POST['cookie_notice_options']['global_cookie'] );
$data['update_notice_diss'] = $cn->options['general']['update_notice_diss'];
// set real value
$cn->network_options['general']['global_override'] = $data['global_override'];
if ( $data['global_override'] && ! $cn->options['general']['update_notice_diss'] )
$data['update_notice'] = true;
else
$data['update_notice'] = false;
// update database
update_site_option( 'cookie_notice_options', $data );
// update settings
$cn->options['general'] = $cn->network_options['general'] = $cn->multi_array_merge( $cn->defaults['general'], get_site_option( 'cookie_notice_options', $cn->defaults['general'] ) );
} elseif ( isset( $_POST['reset_cookie_notice_options'] ) ) {
$cn->defaults['general']['update_notice'] = false;
$cn->defaults['general']['update_notice_diss'] = false;
// silent options validation
$this->validate_options( $cn->defaults['general'] );
// update database
update_site_option( 'cookie_notice_options', $cn->defaults['general'] );
// update settings
$cn->options['general'] = $cn->network_options['general'] = $cn->defaults['general'];
}
}
// update status of cookie compliance
$cn->set_status_data();
}
}
/**
* Load scripts and styles - admin.
*
* @return void
*/
public function admin_enqueue_scripts( $page ) {
// get main instance
$cn = Cookie_Notice();
$is_network = $cn->is_network_admin();
if ( $page === 'toplevel_page_cookie-notice' ) {
$ui_mode = $cn->options['general']['ui_mode'];
wp_enqueue_script( 'cookie-notice-admin', COOKIE_NOTICE_URL . '/js/admin' . ( ! ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '.min' : '' ) . '.js', [ 'jquery', 'wp-color-picker' ], $cn->defaults['version'] );
// prepare script data
$script_data = [
'ajaxURL' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'cn-purge-cache' ),
'nonceSyncConfig' => wp_create_nonce( 'cookie-notice-welcome' ),
'nonceConditional' => wp_create_nonce( 'cn-get-group-values' ),
'nonceCookieConsentLogs' => wp_create_nonce( 'cn-get-cookie-consent-logs' ),
'noncePrivacyConsentLogs' => wp_create_nonce( 'cn-get-privacy-consent-logs' ),
'noncePrivacyConsent' => wp_create_nonce( 'cn-privacy-consent-set-form-status' ),
'consentLogsTemplate' => $cn->consent_logs->get_single_row_template(),
'consentLogsError' => $cn->consent_logs->get_error_template(),
'settingsTab' => $this->current_tab,
'settingsSection' => $this->current_section,
'network' => $cn->is_network_admin(),
'resetToDefaults' => esc_html__( 'Are you sure you want to reset these settings to defaults?', 'cookie-notice' ),
'privacyConsentSources' => $cn->privacy_consent->get_sources()
];
wp_add_inline_script( 'cookie-notice-admin', 'var cnArgs = ' . wp_json_encode( $script_data ) . ";\n", 'before' );
wp_enqueue_style( 'wp-color-picker' );
// React admin bundle.
// In CN_DEV_MODE use the file's mtime as the version so every deploy
// busts the browser cache automatically (the plugin version string only
// changes on official releases, causing stale bundles during testing).
$react_js_path = COOKIE_NOTICE_PATH . 'assets/react-admin/' . Cookie_Notice::REACT_ADMIN_BUNDLE_BASENAME;
$react_css_path = COOKIE_NOTICE_PATH . 'assets/react-admin/cn-admin-react.css';
$react_ver = ( defined( 'CN_DEV_MODE' ) && CN_DEV_MODE && file_exists( $react_js_path ) )
? filemtime( $react_js_path )
: $cn->defaults['version'];
if ( $ui_mode === 'react' ) {
wp_enqueue_script(
Cookie_Notice::REACT_ADMIN_HANDLE,
$cn->get_url( 'react-admin' ),
[],
$react_ver,
true
);
wp_enqueue_style(
Cookie_Notice::REACT_ADMIN_HANDLE,
COOKIE_NOTICE_URL . '/assets/react-admin/cn-admin-react.css',
[],
$react_ver
);
// Stamp optimizer/CDN exclusion attributes on the React admin script
// (main bundle + wp_localize_script inline 'before' block) so JS minifiers,
// concatenators, deferers, and CDN script-rewriters skip our IIFE bundle.
// Mirrors the banner-script protection in includes/frontend.php (commit 765e96b).
// Without this, Cloudflare Rocket Loader (which processes admin pages) and
// any "minify admin" toggles in optimizer plugins can break the bundle and
// white-screen the admin page.
add_filter( 'script_loader_tag', [ $this, 'add_react_admin_optimizer_attrs' ], 10, 2 );
// Pull fresh config from Designer API before localizing data.
// Ensures the React UI starts with the latest state, even if
// changes were made in the Admin Portal since the last cron sync.
if ( ! empty( $cn->options['general']['app_id'] ) ) {
$cn->welcome_api->get_app_config( '', true );
}
// Re-apply dev tier override — get_app_config() calls
// set_status_data() which re-reads from DB, overwriting
// the in-memory override set during init.
$cn->maybe_apply_dev_tier_override();
// Read notification rules from shared JSON config.
$cn_rules_json = file_get_contents( COOKIE_NOTICE_PATH . 'includes/notifications.json' );
$cn_rules_data = $cn_rules_json !== false ? json_decode( $cn_rules_json, true ) : null;
$cn_notification_rules = is_array( $cn_rules_data ) ? ( $cn_rules_data['rules'] ?? [] ) : [];
wp_localize_script( Cookie_Notice::REACT_ADMIN_HANDLE, Cookie_Notice::REACT_ADMIN_INLINE_KEYWORD, [
'ajaxURL' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'cn_react_nonce' ),
'welcomeNonce' => wp_create_nonce( 'cookie-notice-welcome' ),
'configureNonce' => wp_create_nonce( 'cn_api_configure' ),
'registerNonce' => wp_create_nonce( 'cn_api_register' ),
'loginNonce' => wp_create_nonce( 'cn_api_login' ),
'paymentNonce' => wp_create_nonce( 'cn_api_payment' ),
'uiMode' => $ui_mode,
'network' => $cn->is_network_admin(),
'status' => $cn->get_status(),
'subscription' => $cn->get_subscription(),
'app_id' => $cn->options['general']['app_id'],
'version' => $cn->defaults['version'],
'options' => $cn->options['general'],
'siteUrl' => home_url(),
'devMode' => defined( 'CN_DEV_MODE' ) && CN_DEV_MODE && current_user_can( 'manage_options' ),
'welcomeDismissedAt' => get_option( 'cookie_notice_welcome_dismissed', '' ),
'setupWizardComplete' => (bool) get_option( 'cookie_notice_setup_wizard_complete', false ),
'selectedLaws' => $cn->is_network_admin()
? get_site_option( 'cookie_notice_app_regulations', [] )
: get_option( 'cookie_notice_app_regulations', [] ),
'wpPages' => array_map( function( $p ) {
return [ 'id' => $p->ID, 'title' => $p->post_title ];
}, get_pages( [ 'sort_column' => 'post_title' ] ) ?: [] ),
'siteLocale' => get_locale(),
'detectedPlugins' => cn_detect_active_plugins(),
'bannerDesign' => $is_network
? get_site_option( 'cookie_notice_app_design', [] )
: get_option( 'cookie_notice_app_design', [] ),
'displayType' => $cn->options['general']['displayType'] ?? 'floating',
'appUrl' => Cookie_Notice()->get_url( 'host' ),
'lastSynced' => ( function() use ( $is_network ) {
$blocking = $is_network
? get_site_option( 'cookie_notice_app_blocking', [] )
: get_option( 'cookie_notice_app_blocking', [] );
return ! empty( $blocking['lastUpdated'] ) ? $blocking['lastUpdated'] : '';
} )(),
'purgeNonce' => wp_create_nonce( 'cn-purge-cache' ),
'notificationRules' => $cn_notification_rules,
'ruleParams' => array_map( function( $label, $key ) {
return [ 'value' => $key, 'label' => $label ];
}, $this->parameters, array_keys( $this->parameters ) ),
'ruleOperators' => array_map( function( $label, $key ) {
return [ 'value' => $key, 'label' => $label ];
}, $this->operators, array_keys( $this->operators ) ),
] );
}
}
wp_enqueue_style( 'cookie-notice-admin', COOKIE_NOTICE_URL . '/css/admin' . ( ! ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '.min' : '' ) . '.css', [], $cn->defaults['version'] );
if ( $this->current_tab === 'consent-logs' ) {
// pagination script
wp_enqueue_script( 'cookie-notice-admin-pagination', COOKIE_NOTICE_URL . '/assets/pagination/pagination.js', [ 'jquery' ], '2.6.0' );
wp_enqueue_style( 'cookie-notice-admin-pagination', COOKIE_NOTICE_URL . '/assets/pagination/pagination.css', [], '2.6.0' );
}
}
/**
* Stamp optimizer/CDN exclusion attributes on the React admin script tags.
*
* WP's script_loader_tag filter receives the combined output for a handle
* (main