Navigate privacy laws with feline precision — detect every boundary, respect every territory! GDPR compliance and privacy protection for WordPress. - Cookie consent management - Privacy boundary detection - GDPR-compliant analytics gating - Cross-plugin consent coordination (integrates with TigerStyle Heat) - Visitor preference tracking - Configurable cookie categories Includes build.sh and .distignore for WordPress-installable release ZIPs.
610 lines
18 KiB
PHP
610 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* Boundary Detector for TigerStyle Whiskers
|
|
*
|
|
* Like whiskers sensing the environment, this class detects privacy boundaries,
|
|
* compliance requirements, and data protection zones with feline precision.
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class TigerStyleWhiskers_BoundaryDetector {
|
|
|
|
/**
|
|
* Single instance
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Detected boundaries cache
|
|
*/
|
|
private static $boundaries_cache = array();
|
|
|
|
/**
|
|
* GDPR territories (EU/EEA countries)
|
|
*/
|
|
private static $gdpr_territories = array(
|
|
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR',
|
|
'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL',
|
|
'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'IS', 'LI', 'NO'
|
|
);
|
|
|
|
/**
|
|
* Get instance
|
|
*/
|
|
public static function instance() {
|
|
if (is_null(self::$instance)) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor - Initialize boundary sensing
|
|
*/
|
|
private function __construct() {
|
|
$this->init_sensing();
|
|
}
|
|
|
|
/**
|
|
* Initialize sensing capabilities
|
|
*/
|
|
private function init_sensing() {
|
|
// Hook into WordPress to sense boundaries
|
|
add_action('init', array($this, 'sense_visitor_boundaries'));
|
|
add_action('wp_head', array($this, 'inject_boundary_detection_script'), 1);
|
|
|
|
// Sense plugin boundaries (what other plugins are active)
|
|
add_action('plugins_loaded', array($this, 'sense_plugin_boundaries'));
|
|
|
|
// Log our sensing activity
|
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
|
error_log('TigerStyle Whiskers: Boundary detector whiskers are twitching - ready to sense!');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sense visitor boundaries (geographic, regulatory, technical)
|
|
*/
|
|
public function sense_visitor_boundaries() {
|
|
$boundaries = array();
|
|
|
|
// Geographic boundary detection
|
|
$boundaries['geographic'] = $this->detect_geographic_boundary();
|
|
|
|
// Regulatory boundary detection
|
|
$boundaries['regulatory'] = $this->detect_regulatory_boundary();
|
|
|
|
// Technical boundary detection
|
|
$boundaries['technical'] = $this->detect_technical_boundary();
|
|
|
|
// Data processing boundary detection
|
|
$boundaries['data_processing'] = $this->detect_data_processing_boundary();
|
|
|
|
// Cache the detected boundaries
|
|
self::$boundaries_cache = $boundaries;
|
|
|
|
// Store in session for JavaScript access
|
|
if (!headers_sent()) {
|
|
$this->store_boundaries_for_js($boundaries);
|
|
}
|
|
|
|
return $boundaries;
|
|
}
|
|
|
|
/**
|
|
* Detect geographic boundaries (where is the visitor?)
|
|
*/
|
|
private function detect_geographic_boundary() {
|
|
$geographic = array(
|
|
'country_code' => $this->get_visitor_country_code(),
|
|
'is_gdpr_territory' => false,
|
|
'is_ccpa_territory' => false,
|
|
'detection_method' => 'server_based'
|
|
);
|
|
|
|
// Check if visitor is in GDPR territory
|
|
if (in_array($geographic['country_code'], self::$gdpr_territories)) {
|
|
$geographic['is_gdpr_territory'] = true;
|
|
}
|
|
|
|
// Check if visitor is in CCPA territory (California)
|
|
if ($geographic['country_code'] === 'US') {
|
|
$geographic['is_ccpa_territory'] = $this->detect_california_visitor();
|
|
}
|
|
|
|
return $geographic;
|
|
}
|
|
|
|
/**
|
|
* Detect regulatory boundaries (what laws apply?)
|
|
*/
|
|
private function detect_regulatory_boundary() {
|
|
$regulatory = array(
|
|
'gdpr_applies' => false,
|
|
'ccpa_applies' => false,
|
|
'lgpd_applies' => false, // Brazil
|
|
'pipeda_applies' => false, // Canada
|
|
'cookies_law_applies' => false,
|
|
'detection_confidence' => 'high'
|
|
);
|
|
|
|
$geographic = $this->detect_geographic_boundary();
|
|
|
|
// GDPR detection
|
|
if ($geographic['is_gdpr_territory']) {
|
|
$regulatory['gdpr_applies'] = true;
|
|
$regulatory['cookies_law_applies'] = true;
|
|
}
|
|
|
|
// CCPA detection
|
|
if ($geographic['is_ccpa_territory']) {
|
|
$regulatory['ccpa_applies'] = true;
|
|
}
|
|
|
|
// LGPD detection (Brazil)
|
|
if ($geographic['country_code'] === 'BR') {
|
|
$regulatory['lgpd_applies'] = true;
|
|
}
|
|
|
|
// PIPEDA detection (Canada)
|
|
if ($geographic['country_code'] === 'CA') {
|
|
$regulatory['pipeda_applies'] = true;
|
|
}
|
|
|
|
return $regulatory;
|
|
}
|
|
|
|
/**
|
|
* Detect technical boundaries (what capabilities do we have?)
|
|
*/
|
|
private function detect_technical_boundary() {
|
|
$technical = array(
|
|
'cookies_enabled' => $this->detect_cookies_enabled(),
|
|
'javascript_enabled' => $this->detect_javascript_enabled(),
|
|
'local_storage_available' => false, // Will be detected by JS
|
|
'tracking_protection' => $this->detect_tracking_protection(),
|
|
'ad_blocker' => false, // Will be detected by JS
|
|
'do_not_track' => $this->detect_do_not_track(),
|
|
);
|
|
|
|
return $technical;
|
|
}
|
|
|
|
/**
|
|
* Detect data processing boundaries (what data are we handling?)
|
|
*/
|
|
private function detect_data_processing_boundary() {
|
|
$data_processing = array(
|
|
'analytics_active' => $this->detect_analytics_active(),
|
|
'marketing_cookies' => $this->detect_marketing_cookies(),
|
|
'social_media_pixels' => $this->detect_social_pixels(),
|
|
'third_party_integrations' => $this->detect_third_party_integrations(),
|
|
'user_accounts' => $this->detect_user_accounts(),
|
|
'contact_forms' => $this->detect_contact_forms(),
|
|
'ecommerce' => $this->detect_ecommerce(),
|
|
);
|
|
|
|
return $data_processing;
|
|
}
|
|
|
|
/**
|
|
* Get visitor country code
|
|
*/
|
|
private function get_visitor_country_code() {
|
|
// Try multiple methods to detect country
|
|
|
|
// Method 1: CloudFlare header
|
|
if (isset($_SERVER['HTTP_CF_IPCOUNTRY'])) {
|
|
return strtoupper($_SERVER['HTTP_CF_IPCOUNTRY']);
|
|
}
|
|
|
|
// Method 2: MaxMind GeoIP (if available)
|
|
if (function_exists('geoip_country_code_by_name')) {
|
|
$ip = $this->get_visitor_ip();
|
|
$country = geoip_country_code_by_name($ip);
|
|
if ($country) {
|
|
return strtoupper($country);
|
|
}
|
|
}
|
|
|
|
// Method 3: Accept-Language header (rough approximation)
|
|
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
|
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
|
|
$country_map = array(
|
|
'en' => 'US', 'de' => 'DE', 'fr' => 'FR', 'es' => 'ES',
|
|
'it' => 'IT', 'pt' => 'PT', 'nl' => 'NL', 'pl' => 'PL'
|
|
);
|
|
if (isset($country_map[$lang])) {
|
|
return $country_map[$lang];
|
|
}
|
|
}
|
|
|
|
// Default: Unknown
|
|
return 'UNKNOWN';
|
|
}
|
|
|
|
/**
|
|
* Detect if visitor is from California (for CCPA)
|
|
*/
|
|
private function detect_california_visitor() {
|
|
// This would need more sophisticated geo-detection
|
|
// For now, we'll use a conservative approach
|
|
return false; // TODO: Implement proper California detection
|
|
}
|
|
|
|
/**
|
|
* Get visitor IP address
|
|
*/
|
|
private function get_visitor_ip() {
|
|
// Check for IP from various sources
|
|
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
|
|
return $_SERVER['HTTP_CLIENT_IP'];
|
|
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
|
return $_SERVER['HTTP_X_FORWARDED_FOR'];
|
|
} elseif (!empty($_SERVER['REMOTE_ADDR'])) {
|
|
return $_SERVER['REMOTE_ADDR'];
|
|
}
|
|
return '127.0.0.1';
|
|
}
|
|
|
|
/**
|
|
* Detect if cookies are enabled
|
|
*/
|
|
private function detect_cookies_enabled() {
|
|
// This will be verified by JavaScript
|
|
return !isset($_SERVER['HTTP_COOKIE']) ? false : true;
|
|
}
|
|
|
|
/**
|
|
* Detect if JavaScript is enabled
|
|
*/
|
|
private function detect_javascript_enabled() {
|
|
// This will be detected by JavaScript and stored
|
|
return true; // Assume true initially
|
|
}
|
|
|
|
/**
|
|
* Detect Do Not Track header
|
|
*/
|
|
private function detect_do_not_track() {
|
|
return isset($_SERVER['HTTP_DNT']) && $_SERVER['HTTP_DNT'] == '1';
|
|
}
|
|
|
|
/**
|
|
* Detect tracking protection
|
|
*/
|
|
private function detect_tracking_protection() {
|
|
// Check for common tracking protection headers
|
|
return isset($_SERVER['HTTP_SEC_GPC']) && $_SERVER['HTTP_SEC_GPC'] == '1';
|
|
}
|
|
|
|
/**
|
|
* Detect if analytics are active
|
|
*/
|
|
private function detect_analytics_active() {
|
|
// Check if TigerStyle Heat is active
|
|
if (class_exists('TigerStyleSEO_Google_setup')) {
|
|
return !empty(get_option('google_analytics_id', ''));
|
|
}
|
|
|
|
// Check for other analytics plugins
|
|
$analytics_plugins = array(
|
|
'google-analytics-for-wordpress/googleanalytics.php',
|
|
'google-analytics-dashboard-for-wp/gadwp.php',
|
|
'ga-google-analytics/ga-google-analytics.php'
|
|
);
|
|
|
|
foreach ($analytics_plugins as $plugin) {
|
|
if (is_plugin_active($plugin)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Detect marketing cookies
|
|
*/
|
|
private function detect_marketing_cookies() {
|
|
// Check for common marketing cookie patterns
|
|
$marketing_domains = array('facebook.com', 'google.com', 'doubleclick.net', 'googlesyndication.com');
|
|
|
|
foreach ($marketing_domains as $domain) {
|
|
if (isset($_COOKIE) && $this->has_cookies_from_domain($domain)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if cookies exist from a specific domain
|
|
*/
|
|
private function has_cookies_from_domain($domain) {
|
|
// This is a simplified check - in reality, we'd need more sophisticated domain detection
|
|
foreach ($_COOKIE as $name => $value) {
|
|
if (strpos($name, str_replace('.', '_', $domain)) !== false) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Detect social media pixels
|
|
*/
|
|
private function detect_social_pixels() {
|
|
// Check for Facebook Pixel, Twitter Pixel, etc.
|
|
return $this->scan_for_social_tracking_code();
|
|
}
|
|
|
|
/**
|
|
* Scan for social tracking code
|
|
*/
|
|
private function scan_for_social_tracking_code() {
|
|
// This would scan the page content for social tracking scripts
|
|
// For now, we'll check for known plugins
|
|
$social_plugins = array(
|
|
'facebook-for-woocommerce/facebook-for-woocommerce.php',
|
|
'pixel-caffeine/pixel-caffeine.php'
|
|
);
|
|
|
|
foreach ($social_plugins as $plugin) {
|
|
if (is_plugin_active($plugin)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Detect third-party integrations
|
|
*/
|
|
private function detect_third_party_integrations() {
|
|
$integrations = array();
|
|
|
|
// Check for common third-party services
|
|
if ($this->detect_analytics_active()) {
|
|
$integrations[] = 'analytics';
|
|
}
|
|
|
|
if ($this->detect_social_pixels()) {
|
|
$integrations[] = 'social_media';
|
|
}
|
|
|
|
// Check for email marketing
|
|
if (is_plugin_active('mailchimp-for-wp/mailchimp-for-wp.php')) {
|
|
$integrations[] = 'email_marketing';
|
|
}
|
|
|
|
// Check for live chat
|
|
if (is_plugin_active('tidio-live-chat/tidio-live-chat.php')) {
|
|
$integrations[] = 'live_chat';
|
|
}
|
|
|
|
return $integrations;
|
|
}
|
|
|
|
/**
|
|
* Detect user accounts
|
|
*/
|
|
private function detect_user_accounts() {
|
|
return get_option('users_can_register', 0) == 1 || is_plugin_active('woocommerce/woocommerce.php');
|
|
}
|
|
|
|
/**
|
|
* Detect contact forms
|
|
*/
|
|
private function detect_contact_forms() {
|
|
$form_plugins = array(
|
|
'contact-form-7/wp-contact-form-7.php',
|
|
'wpforms-lite/wpforms.php',
|
|
'gravityforms/gravityforms.php'
|
|
);
|
|
|
|
foreach ($form_plugins as $plugin) {
|
|
if (is_plugin_active($plugin)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Detect ecommerce
|
|
*/
|
|
private function detect_ecommerce() {
|
|
return is_plugin_active('woocommerce/woocommerce.php') ||
|
|
is_plugin_active('easy-digital-downloads/easy-digital-downloads.php');
|
|
}
|
|
|
|
/**
|
|
* Sense plugin boundaries (what other plugins might affect compliance)
|
|
*/
|
|
public function sense_plugin_boundaries() {
|
|
$plugins = array(
|
|
'analytics' => $this->detect_analytics_plugins(),
|
|
'cookies' => $this->detect_cookie_plugins(),
|
|
'forms' => $this->detect_form_plugins(),
|
|
'ecommerce' => $this->detect_ecommerce_plugins(),
|
|
'social' => $this->detect_social_plugins(),
|
|
);
|
|
|
|
// Store plugin boundaries
|
|
update_option('tigerstyle_whiskers_plugin_boundaries', $plugins);
|
|
|
|
return $plugins;
|
|
}
|
|
|
|
/**
|
|
* Detect analytics plugins
|
|
*/
|
|
private function detect_analytics_plugins() {
|
|
$plugins = array();
|
|
|
|
if (class_exists('TigerStyleSEO_Google_setup')) {
|
|
$plugins[] = 'tigerstyle-heat';
|
|
}
|
|
|
|
// Add other analytics plugin detection
|
|
|
|
return $plugins;
|
|
}
|
|
|
|
/**
|
|
* Detect cookie plugins
|
|
*/
|
|
private function detect_cookie_plugins() {
|
|
$cookie_plugins = array(
|
|
'cookie-notice/cookie-notice.php' => 'Cookie Notice',
|
|
'cookie-law-info/cookie-law-info.php' => 'GDPR Cookie Consent',
|
|
'gdpr-cookie-compliance/gdpr-cookie-compliance.php' => 'GDPR Cookie Compliance'
|
|
);
|
|
|
|
$active_plugins = array();
|
|
foreach ($cookie_plugins as $plugin => $name) {
|
|
if (is_plugin_active($plugin)) {
|
|
$active_plugins[] = $name;
|
|
}
|
|
}
|
|
|
|
return $active_plugins;
|
|
}
|
|
|
|
/**
|
|
* Detect form plugins
|
|
*/
|
|
private function detect_form_plugins() {
|
|
// Implementation similar to detect_contact_forms but more detailed
|
|
return array();
|
|
}
|
|
|
|
/**
|
|
* Detect ecommerce plugins
|
|
*/
|
|
private function detect_ecommerce_plugins() {
|
|
// Implementation similar to detect_ecommerce but more detailed
|
|
return array();
|
|
}
|
|
|
|
/**
|
|
* Detect social plugins
|
|
*/
|
|
private function detect_social_plugins() {
|
|
// Implementation for social media plugins
|
|
return array();
|
|
}
|
|
|
|
/**
|
|
* Store boundaries for JavaScript access
|
|
*/
|
|
private function store_boundaries_for_js($boundaries) {
|
|
// Store in a way that JavaScript can access
|
|
setcookie(
|
|
'tigerstyle_whiskers_boundaries',
|
|
json_encode($boundaries),
|
|
time() + 3600,
|
|
'/',
|
|
'',
|
|
is_ssl(),
|
|
true
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Inject boundary detection script
|
|
*/
|
|
public function inject_boundary_detection_script() {
|
|
?>
|
|
<!-- TigerStyle Whiskers: Boundary Detection Script -->
|
|
<script type="text/javascript">
|
|
(function() {
|
|
// Enhance boundary detection with client-side capabilities
|
|
var boundaries = {
|
|
technical: {
|
|
cookies_enabled: navigator.cookieEnabled,
|
|
javascript_enabled: true,
|
|
local_storage_available: typeof(Storage) !== "undefined",
|
|
session_storage_available: typeof(sessionStorage) !== "undefined",
|
|
do_not_track: navigator.doNotTrack === "1" || window.doNotTrack === "1",
|
|
screen_width: screen.width,
|
|
screen_height: screen.height,
|
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
},
|
|
user_agent: navigator.userAgent,
|
|
language: navigator.language,
|
|
languages: navigator.languages
|
|
};
|
|
|
|
// Store enhanced boundaries
|
|
if (boundaries.technical.local_storage_available) {
|
|
localStorage.setItem('tigerstyle_whiskers_client_boundaries', JSON.stringify(boundaries));
|
|
}
|
|
|
|
// Send to server via AJAX if needed
|
|
if (typeof tigerstyleWhiskers !== 'undefined') {
|
|
tigerstyleWhiskers.updateClientBoundaries(boundaries);
|
|
}
|
|
})();
|
|
</script>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Static methods for easy access
|
|
*/
|
|
|
|
/**
|
|
* Check if a specific boundary is detected
|
|
*/
|
|
public static function is_detected($boundary_type) {
|
|
if (empty(self::$boundaries_cache)) {
|
|
$instance = self::instance();
|
|
$instance->sense_visitor_boundaries();
|
|
}
|
|
|
|
return isset(self::$boundaries_cache[$boundary_type]);
|
|
}
|
|
|
|
/**
|
|
* Check if visitor is in GDPR territory
|
|
*/
|
|
public static function is_gdpr_territory() {
|
|
if (empty(self::$boundaries_cache)) {
|
|
$instance = self::instance();
|
|
$instance->sense_visitor_boundaries();
|
|
}
|
|
|
|
return isset(self::$boundaries_cache['regulatory']['gdpr_applies']) &&
|
|
self::$boundaries_cache['regulatory']['gdpr_applies'];
|
|
}
|
|
|
|
/**
|
|
* Get all detected boundaries
|
|
*/
|
|
public static function get_all_boundaries() {
|
|
if (empty(self::$boundaries_cache)) {
|
|
$instance = self::instance();
|
|
$instance->sense_visitor_boundaries();
|
|
}
|
|
|
|
return self::$boundaries_cache;
|
|
}
|
|
|
|
/**
|
|
* Force boundary re-detection
|
|
*/
|
|
public static function refresh_boundaries() {
|
|
self::$boundaries_cache = array();
|
|
$instance = self::instance();
|
|
return $instance->sense_visitor_boundaries();
|
|
}
|
|
}
|