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__( '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'] ) ) {
// Checkbox was present in the form — honour the submitted value, but
// cap it to false when threshold is exceeded.
$input['app_blocking'] = ! $cn->threshold_exceeded();
} else {
// Checkbox absent (unchecked or not rendered) — preserve DB value to avoid
// silently clobbering a React-written true. The preservation loop below
// would normally handle this, but it runs after this block and only fills
// keys missing from $input entirely. Since we must not add app_blocking to
// $input here (the loop checks array_key_exists), we skip setting it and
// let the loop restore it from the DB.
unset( $input['app_blocking'] );
}
// 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/cn-admin-react.js';
$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',
COOKIE_NOTICE_URL . '/assets/react-admin/cn-admin-react.js',
[],
$react_ver,
true
);
wp_enqueue_style(
'cookie-notice-react-admin',
COOKIE_NOTICE_URL . '/assets/react-admin/cn-admin-react.css',
[],
$react_ver
);
// 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', 'cnReactData', [
'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' );
}
}
/**
* Load admin style inline, for menu icon only.
*
* @return void
*/
public function admin_print_styles() {
echo '
';
}
/**
* Register WPML (>= 3.2) strings if needed.
*
* @global object $wpdb
*
* @return void
*/
private function register_wpml_strings() {
// get main instance
$cn = Cookie_Notice();
global $wpdb;
// prepare strings
$strings = [
'Message in the notice' => $cn->options['general']['message_text'],
'Button text' => $cn->options['general']['accept_text'],
'Refuse button text' => $cn->options['general']['refuse_text'],
'Revoke message text' => $cn->options['general']['revoke_message_text'],
'Revoke button text' => $cn->options['general']['revoke_text'],
'Privacy policy text' => $cn->options['general']['see_more_opt']['text'],
'Custom link' => $cn->options['general']['see_more_opt']['link']
];
// get query results
$results = $wpdb->get_col( $wpdb->prepare( "SELECT name FROM " . $wpdb->prefix . "icl_strings WHERE context = %s", 'Cookie Notice' ) );
// check results
foreach( $strings as $string => $value ) {
// string does not exist?
if ( ! in_array( $string, $results, true ) ) {
// register string
do_action( 'wpml_register_single_string', 'Cookie Notice', $string, $value );
}
}
}
/**
* Display errors and notices.
*
* @global string $pagenow
*
* @return void
*/
public function settings_errors() {
global $pagenow;
// force display notices in top menu settings page
if ( $pagenow === 'options-general.php' )
return;
settings_errors( 'cn_cookie_notice_options' );
}
/**
* Save compliance config caching.
*
* @return void
*/
public function ajax_purge_cache() {
// valid nonce?
if ( ! check_ajax_referer( 'cn-purge-cache', 'nonce' ) )
exit;
// check capability
if ( ! current_user_can( apply_filters( 'cn_manage_cookie_notice_cap', 'manage_options' ) ) )
exit;
// request for new config data
Cookie_Notice()->welcome_api->get_app_config( '', true );
// force new config on frontend
if ( Cookie_Notice()->is_network_options() )
set_site_transient( 'cookie_notice_config_update', current_time( 'timestamp', true ), 600 );
else
set_transient( 'cookie_notice_config_update', current_time( 'timestamp', true ), 600 );
exit;
}
/**
* Generate conditions.
*
* @param array $groups
* @return string
*/
public function conditional_display( $groups ) {
$group_template = '