cache_plugin_compat();
if ( class_exists( 'CookieBanerPro_Updater' ) ) {
CookieBanerPro_Updater::init();
}
}
/** Base address of the CookieBanerPro service (filterable for staging). */
public static function service() {
return untrailingslashit( apply_filters( 'cookiebanerpro_service_url', 'https://cookiebanerpro.pl' ) );
}
public static function is_connected() {
return (bool) get_option( self::OPT_KEY ) && (bool) get_option( self::OPT_TOKEN );
}
/**
* Defaults a theme can ship for its buyers, so the banner matches the theme from the first minute:
* add_theme_support( 'cookiebanerpro', array( 'partner' => 'ABC123', 'colors' => array( 'primary' => '#1E6B52', 'background' => '#FFFFFF', 'text' => '#1E2B2F' ), 'style' => 'box' ) );
* or the filter 'cookiebanerpro_theme_defaults'. Keys: partner (code from the CookieBanerPro Partner Panel),
* colors (primary, background, text), style (box|bar|popup), dark (bool).
*/
public static function theme_defaults() {
$support = get_theme_support( 'cookiebanerpro' );
$defaults = ( is_array( $support ) && isset( $support[0] ) && is_array( $support[0] ) ) ? $support[0] : array();
$defaults = apply_filters( 'cookiebanerpro_theme_defaults', $defaults );
return is_array( $defaults ) ? $defaults : array();
}
/** True when the active theme bundles CookieBanerPro (theme support or the defaults filter). */
public static function theme_bundled() {
return current_theme_supports( 'cookiebanerpro' ) || array() !== self::theme_defaults();
}
/** Language of the banner texts from the site locale (pl, en, de, uk). */
public static function banner_lang() {
$locale = strtolower( substr( (string) get_locale(), 0, 2 ) );
return in_array( $locale, array( 'pl', 'en', 'de', 'uk' ), true ) ? $locale : 'en';
}
/** Plan limit alerts on the dashboard and the Plugins screen (status is cached, so no extra requests). */
public function limit_notice() {
if ( ! self::is_connected() || ! current_user_can( 'manage_options' ) ) {
return;
}
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( ! $screen || ! in_array( $screen->id, array( 'dashboard', 'plugins' ), true ) ) {
return;
}
$status = self::status();
if ( ! is_array( $status ) || isset( $status['_error'] ) || empty( $status['pageviews_limit'] ) ) {
return;
}
$pv = (int) $status['pageviews'];
$limit = (int) $status['pageviews_limit'];
if ( $pv < 0.8 * $limit ) {
return;
}
$over = $pv >= $limit;
$text = $over
? __( 'your site has used up the page views of its plan this month. Choose a plan so the banner keeps working - on the free plan it switches off 7 days after the limit.', 'cookiebanerpro' )
: __( 'your site has used 80% of the page views of its plan this month.', 'cookiebanerpro' );
printf(
'
CookieBanerPro: %2$s %4$s
',
$over ? 'error' : 'warning',
esc_html( $text ),
esc_url( self::page_url() ),
esc_html__( 'See your plan', 'cookiebanerpro' )
);
}
/* --------------------------------------------------------------------
* Front end: banner code in , as early as possible
* ------------------------------------------------------------------ */
public static function site_key() {
$key = (string) get_option( self::OPT_KEY );
return preg_match( '/^[a-z0-9]{12}$/', $key ) ? $key : '';
}
public function print_banner_code() {
if ( is_admin() || is_feed() || is_robots() || is_trackback() ) {
return;
}
if ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() ) {
return;
}
if ( '1' !== (string) get_option( self::OPT_INJECT, '1' ) ) {
return;
}
$key = self::site_key();
if ( '' === $key ) {
return;
}
$src = self::service() . '/c/' . $key . '.js';
// Same code as the manual installation: Google Consent Mode v2 defaults first, then the banner loader.
// data-* attributes tell optimisation plugins (Cloudflare Rocket Loader, LiteSpeed, WP Rocket...) to leave it alone.
$skip = array(
'data-cfasync' => 'false',
'data-no-optimize' => '1',
'data-no-defer' => '1',
'data-no-minify' => '1',
);
wp_print_inline_script_tag(
"window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('consent','default',{ad_storage:'denied',ad_user_data:'denied',ad_personalization:'denied',analytics_storage:'denied',functionality_storage:'denied',personalization_storage:'denied',security_storage:'granted',wait_for_update:500});",
$skip + array( 'id' => 'cookiebanerpro-consent-default', 'data-cookieconsent' => 'ignore' )
);
// Early blocking (default): the banner loads before the rest of the page (no async), so embedded YouTube videos
// and tracking scripts wait for consent from the very first view. With async the page may load them first.
$early = '1' === (string) get_option( self::OPT_EARLY, '1' );
wp_print_script_tag( array( 'src' => esc_url( $src ), 'async' => ! $early, 'id' => 'cookiebanerpro-banner' ) + $skip );
}
/**
* Popular optimisation plugins delay or combine JavaScript, which breaks consent banners
* (the banner shows late or Consent Mode defaults arrive after the tags). Exclude our code.
*/
private function cache_plugin_compat() {
$patterns = array( 'cookiebanerpro', 'gtag(\'consent\'', "gtag('consent'" );
$add = function ( $list ) use ( $patterns ) {
return array_values( array_unique( array_merge( (array) $list, $patterns ) ) );
};
// WP Rocket.
add_filter( 'rocket_delay_js_exclusions', $add );
add_filter( 'rocket_exclude_js', $add );
add_filter( 'rocket_exclude_defer_js', $add );
add_filter( 'rocket_excluded_inline_js_content', $add );
// LiteSpeed Cache.
add_filter( 'litespeed_optm_js_defer_exc', $add );
add_filter( 'litespeed_optimize_js_excludes', $add );
add_filter( 'litespeed_optm_gm_js_exc', $add );
// SiteGround Optimizer.
add_filter( 'sgo_javascript_combine_exclude', $add );
add_filter( 'sgo_js_async_exclude', $add );
add_filter( 'sgo_javascript_combine_excluded_inline_content', $add );
// Autoptimize (comma-separated string).
add_filter(
'autoptimize_filter_js_exclude',
function ( $exclude ) {
return trim( (string) $exclude . ', cookiebanerpro, gtag(\'consent\'', ', ' );
}
);
}
/* --------------------------------------------------------------------
* Admin
* ------------------------------------------------------------------ */
public function admin_menu() {
add_menu_page(
'CookieBanerPro',
'CookieBanerPro',
'manage_options',
self::PAGE,
array( $this, 'render_page' ),
'dashicons-shield-alt',
81
);
}
public function admin_assets( $hook ) {
if ( 'toplevel_page_' . self::PAGE !== $hook && 'index.php' !== $hook ) {
return;
}
wp_enqueue_style( 'cookiebanerpro-admin', COOKIEBANERPRO_URL . 'assets/admin.css', array(), COOKIEBANERPRO_VERSION );
}
public function action_links( $links ) {
$url = admin_url( 'admin.php?page=' . self::PAGE );
array_unshift( $links, '' . esc_html( self::is_connected() ? 'Dashboard' : 'Connect' ) . '' );
return $links;
}
public static function page_url( $args = array() ) {
return add_query_arg( $args, admin_url( 'admin.php?page=' . self::PAGE ) );
}
/** After activation, open the plugin page once (skipped for bulk activation and WP-CLI). */
public function activation_redirect() {
if ( ! get_transient( 'cookiebanerpro_activated' ) ) {
return;
}
delete_transient( 'cookiebanerpro_activated' );
if ( wp_doing_ajax() || is_network_admin() || isset( $_GET['activate-multi'] ) || ! current_user_can( 'manage_options' ) ) { // phpcs:ignore WordPress.Security.NonceVerification
return;
}
wp_safe_redirect( self::page_url() );
exit;
}
public function not_connected_notice() {
if ( self::is_connected() || ! current_user_can( 'manage_options' ) ) {
return;
}
// Only on the Dashboard and the Plugins screen, and only until the user dismisses it.
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( ! $screen || ! in_array( $screen->id, array( 'dashboard', 'plugins' ), true ) || get_user_meta( get_current_user_id(), self::META_DISMISSED, true ) ) {
return;
}
printf(
'CookieBanerPro: %s %s | %s
',
esc_html__( 'the cookie consent banner is not active yet. Connect your site to turn it on.', 'cookiebanerpro' ),
esc_url( self::page_url() ),
esc_html__( 'Set up the banner', 'cookiebanerpro' ),
esc_url( wp_nonce_url( add_query_arg( 'cookiebanerpro_dismiss', '1' ), 'cookiebanerpro_dismiss' ) ),
esc_html__( 'Dismiss', 'cookiebanerpro' )
);
}
/** "Dismiss" link of the not-connected notice: hidden for this user for good. */
public function dismiss_notice() {
if ( empty( $_GET['cookiebanerpro_dismiss'] ) || ! current_user_can( 'manage_options' ) ) { // phpcs:ignore WordPress.Security.NonceVerification
return;
}
check_admin_referer( 'cookiebanerpro_dismiss' );
update_user_meta( get_current_user_id(), self::META_DISMISSED, 1 );
wp_safe_redirect( remove_query_arg( array( 'cookiebanerpro_dismiss', '_wpnonce' ) ) );
exit;
}
/** Address on cookiebanerpro.pl where the user signs up / logs in and approves the connection. */
private function connect_url( $mode ) {
// One state for both buttons and for page reloads while it is fresh - a new state per link would
// invalidate the one the user actually clicked ("Create a free account" is rendered first).
$saved = get_transient( 'cookiebanerpro_state_' . get_current_user_id() );
if ( is_array( $saved ) && isset( $saved['s'], $saved['t'] ) && time() - (int) $saved['t'] < 20 * MINUTE_IN_SECONDS ) {
$state = $saved['s'];
} else {
$state = wp_generate_password( 32, false, false );
set_transient( 'cookiebanerpro_state_' . get_current_user_id(), array( 's' => $state, 't' => time() ), 30 * MINUTE_IN_SECONDS );
}
$args = array(
'domain' => rawurlencode( (string) wp_parse_url( home_url(), PHP_URL_HOST ) ),
'return' => rawurlencode( self::page_url() ),
'state' => rawurlencode( $state ),
'mode' => 'signup' === $mode ? 'signup' : 'login',
);
return add_query_arg( array_merge( $args, self::theme_args() ), self::service() . '/connect' );
}
/**
* Theme defaults passed to cookiebanerpro.pl: the partner code (the account is assigned to the theme
* author) and the colours/style the new banner starts with. Only well-formed values are sent.
*/
public static function theme_args() {
$theme = self::theme_defaults();
$args = array();
if ( isset( $theme['partner'] ) && preg_match( '/^[A-Za-z0-9]{4,16}$/', (string) $theme['partner'] ) ) {
$args['partner'] = (string) $theme['partner'];
}
if ( isset( $theme['colors'] ) && is_array( $theme['colors'] ) ) {
foreach ( array( 'primary', 'background', 'text' ) as $c ) {
if ( isset( $theme['colors'][ $c ] ) && preg_match( '/^#[0-9a-fA-F]{6}$/', (string) $theme['colors'][ $c ] ) ) {
$args[ 'c_' . $c ] = rawurlencode( (string) $theme['colors'][ $c ] );
}
}
}
if ( isset( $theme['style'] ) && in_array( $theme['style'], array( 'box', 'bar', 'popup' ), true ) ) {
$args['style'] = $theme['style'];
}
if ( ! empty( $theme['dark'] ) ) {
$args['dark'] = '1';
}
return $args;
}
/** Handles: return from cookiebanerpro.pl with a one-time code, disconnect, code on/off, refresh. */
public function handle_admin_actions() {
if ( ! isset( $_GET['page'] ) || self::PAGE !== $_GET['page'] || ! current_user_can( 'manage_options' ) ) { // phpcs:ignore WordPress.Security.NonceVerification
return;
}
// 1. Back from cookiebanerpro.pl: exchange the one-time code for the site key and a plugin token.
if ( isset( $_GET['cbp_code'], $_GET['state'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
$code = sanitize_text_field( wp_unslash( $_GET['cbp_code'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
$state = sanitize_text_field( wp_unslash( $_GET['state'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
$saved = get_transient( 'cookiebanerpro_state_' . get_current_user_id() );
$expected = is_array( $saved ) && isset( $saved['s'] ) ? (string) $saved['s'] : '';
delete_transient( 'cookiebanerpro_state_' . get_current_user_id() );
if ( '' === $expected || ! hash_equals( $expected, $state ) ) {
$this->redirect_with( 'error', 'state' );
}
$res = wp_remote_post(
self::service() . '/api/wp/exchange',
array(
'timeout' => 15,
'headers' => array( 'Content-Type' => 'application/json' ),
'body' => wp_json_encode( array( 'code' => $code, 'state' => $state, 'wp_url' => home_url() ) ),
)
);
$data = is_wp_error( $res ) ? null : json_decode( wp_remote_retrieve_body( $res ), true );
if ( empty( $data['token'] ) || empty( $data['site_key'] ) ) {
$this->redirect_with( 'error', is_wp_error( $res ) ? 'network' : 'exchange' );
}
update_option( self::OPT_KEY, sanitize_text_field( $data['site_key'] ) );
update_option( self::OPT_TOKEN, sanitize_text_field( $data['token'] ), false );
update_option( self::OPT_DOMAIN, sanitize_text_field( $data['domain'] ) );
update_option( self::OPT_PANEL, esc_url_raw( $data['panel_url'] ) );
update_option( self::OPT_INJECT, '1' );
update_option( self::OPT_SINCE, time() );
self::forget_status();
$this->redirect_with( 'connected', '1' );
}
if ( empty( $_POST['cookiebanerpro_action'] ) ) {
return;
}
check_admin_referer( 'cookiebanerpro_action' );
$action = sanitize_key( wp_unslash( $_POST['cookiebanerpro_action'] ) );
// Basic banner settings from WordPress (colours, style, privacy policy link) - published at once.
if ( 'settings' === $action && self::is_connected() ) {
$body = array();
foreach ( array( 'primary', 'background', 'text' ) as $c ) {
$v = isset( $_POST[ 'cbp_' . $c ] ) ? sanitize_hex_color( wp_unslash( $_POST[ 'cbp_' . $c ] ) ) : '';
if ( $v ) {
$body['colors'][ $c ] = $v;
}
}
$style = isset( $_POST['cbp_style'] ) ? sanitize_key( wp_unslash( $_POST['cbp_style'] ) ) : '';
if ( in_array( $style, array( 'box', 'bar', 'popup' ), true ) ) {
$body['style'] = $style;
}
if ( isset( $_POST['cbp_policy'] ) ) {
$body['policy_url'] = esc_url_raw( wp_unslash( $_POST['cbp_policy'] ) );
}
$res = wp_remote_post(
self::service() . '/api/wp/ustawienia',
array(
'timeout' => 15,
'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . get_option( self::OPT_TOKEN ) ),
'body' => wp_json_encode( $body ),
)
);
self::forget_status();
$this->redirect_with( is_wp_error( $res ) || 200 !== (int) wp_remote_retrieve_response_code( $res ) ? 'error' : 'published', is_wp_error( $res ) ? 'network' : '1' );
}
if ( 'disconnect' === $action ) {
$token = (string) get_option( self::OPT_TOKEN );
if ( $token ) {
wp_remote_post(
self::service() . '/api/wp/disconnect',
array(
'timeout' => 10,
'headers' => array( 'Authorization' => 'Bearer ' . $token ),
)
);
}
foreach ( array( self::OPT_KEY, self::OPT_TOKEN, self::OPT_DOMAIN, self::OPT_PANEL, self::OPT_SINCE ) as $opt ) {
delete_option( $opt );
}
self::forget_status();
$this->redirect_with( 'disconnected', '1' );
}
if ( 'toggle' === $action ) {
update_option( self::OPT_INJECT, '1' === (string) get_option( self::OPT_INJECT, '1' ) ? '0' : '1' );
$this->redirect_with( 'saved', '1' );
}
if ( 'toggle_early' === $action ) {
update_option( self::OPT_EARLY, '1' === (string) get_option( self::OPT_EARLY, '1' ) ? '0' : '1' );
$this->redirect_with( 'saved', '1' );
}
if ( 'refresh' === $action ) {
self::forget_status();
$this->redirect_with( 'refreshed', '1' );
}
}
private function redirect_with( $key, $value ) {
wp_safe_redirect( self::page_url( array( $key => $value ) ) );
exit;
}
/** Site status from cookiebanerpro.pl (plan, page views, published banner), cached for 10 minutes. */
public static function status() {
$cached = get_transient( 'cookiebanerpro_status' );
if ( is_array( $cached ) ) {
return $cached;
}
$res = wp_remote_get(
self::service() . '/api/wp/status',
array(
'timeout' => 10,
'headers' => array( 'Authorization' => 'Bearer ' . get_option( self::OPT_TOKEN ) ),
)
);
if ( is_wp_error( $res ) ) {
return array( '_error' => 'network' );
}
$code = (int) wp_remote_retrieve_response_code( $res );
$data = json_decode( wp_remote_retrieve_body( $res ), true );
if ( 401 === $code ) {
return array( '_error' => 'revoked' );
}
if ( 200 !== $code || ! is_array( $data ) ) {
return array( '_error' => 'server' );
}
// The site key can change only on the service side (e.g. site re-created) - keep ours in sync.
if ( ! empty( $data['site_key'] ) && preg_match( '/^[a-z0-9]{12}$/', $data['site_key'] ) ) {
update_option( self::OPT_KEY, $data['site_key'] );
}
set_transient( 'cookiebanerpro_status', $data, self::STATUS_TTL );
return $data;
}
/** Clears cached status and statistics (after connecting, disconnecting or "Refresh"). */
public static function forget_status() {
delete_transient( 'cookiebanerpro_status' );
foreach ( self::RANGES as $days ) {
delete_transient( 'cookiebanerpro_stats_' . $days );
}
}
/** Chosen analytics range (days) from the settings screen. */
public static function range() {
$days = isset( $_GET['range'] ) ? (int) $_GET['range'] : 30; // phpcs:ignore WordPress.Security.NonceVerification
return in_array( $days, self::RANGES, true ) ? $days : 30;
}
/** Daily page views and consent decisions of this site for the last N days, cached for 10 minutes. */
public static function stats( $days ) {
$days = in_array( (int) $days, self::RANGES, true ) ? (int) $days : 30;
$cached = get_transient( 'cookiebanerpro_stats_' . $days );
if ( is_array( $cached ) ) {
return $cached;
}
$res = wp_remote_get(
self::service() . '/api/wp/stats?days=' . $days,
array(
'timeout' => 10,
'headers' => array( 'Authorization' => 'Bearer ' . get_option( self::OPT_TOKEN ) ),
)
);
if ( is_wp_error( $res ) || 200 !== (int) wp_remote_retrieve_response_code( $res ) ) {
return null;
}
$data = json_decode( wp_remote_retrieve_body( $res ), true );
if ( ! is_array( $data ) || empty( $data['series'] ) || ! is_array( $data['series'] ) ) {
return null;
}
set_transient( 'cookiebanerpro_stats_' . $days, $data, self::STATUS_TTL );
return $data;
}
/** Page views used / plan limit, always as "used / limit" ("used / ∞" on plans without a limit). */
/**
* Page views of this site this month. The plan limit counts all sites of the account, so when the account
* has more sites, the total is shown below in smaller text. Older service versions send only the total.
*/
public static function site_usage_html( $status ) {
$total = isset( $status['pageviews'] ) ? (int) $status['pageviews'] : 0;
if ( ! isset( $status['pageviews_site'] ) || (int) $status['pageviews_site'] === $total ) {
return self::usage_html( $status );
}
/* translators: %s: page views of all sites in the plan / plan limit */
return esc_html( number_format_i18n( (int) $status['pageviews_site'] ) ) . '' . sprintf( esc_html__( 'All sites in your plan: %s', 'cookiebanerpro' ), self::usage_html( $status ) ) . '';
}
public static function usage_html( $status ) {
$pv = isset( $status['pageviews'] ) ? (int) $status['pageviews'] : 0;
$limit = isset( $status['pageviews_limit'] ) ? (int) $status['pageviews_limit'] : 0;
$max = $limit ? number_format_i18n( $limit ) : '∞';
return esc_html( number_format_i18n( $pv ) ) . ' / ' . ( $limit ? esc_html( $max ) : $max ) . '';
}
/** Widget in the WordPress dashboard: is the banner working, how much of the plan is left. */
public function dashboard_widget() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Logo instead of the plain name; __widget_basename keeps a text name in "Screen Options".
$title = '
CookieBanerPro';
wp_add_dashboard_widget( 'cookiebanerpro_status', $title, array( $this, 'render_dashboard_widget' ), null, array( '__widget_basename' => 'CookieBanerPro' ) );
}
public function render_dashboard_widget() {
require __DIR__ . '/includes/dashboard-widget.php';
}
/** Plan name in English (the service returns Polish names; plan_id is stable). */
public static function plan_label( $status ) {
$names = array( 'free' => __( 'Free', 'cookiebanerpro' ), 'start' => 'Start', 'biznes' => __( 'Business', 'cookiebanerpro' ), 'agencja' => __( 'Agency', 'cookiebanerpro' ), 'enterprise' => 'Enterprise', 'admin' => __( 'Administrator', 'cookiebanerpro' ) );
$id = isset( $status['plan_id'] ) ? $status['plan_id'] : '';
return isset( $names[ $id ] ) ? $names[ $id ] : ( isset( $status['plan'] ) ? $status['plan'] : '-' );
}
public function render_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
require __DIR__ . '/includes/admin-page.php';
}
}
register_activation_hook(
__FILE__,
function () {
set_transient( 'cookiebanerpro_activated', 1, 60 );
}
);
CookieBanerPro_Plugin::instance();