tigerstyle-whiskers/includes/class-boundary-detector.php
Ryan Malloy adbdae19c8 Initial commit: TigerStyle Whiskers v1.0.0
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.
2026-05-27 14:31:51 -06:00

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();
}
}