args = [ 'sslverify' => apply_filters('https_local_ssl_verify', false), ]; $this->trackerPath = Assets::getSrc('js/tracker.js', Option::get('bypass_ad_blockers'), WP_STATISTICS_URL); $this->initializeData(); } /** * Check if AJAX hit recording is blocked. * * @return bool */ public function checkHitRecording() { $adBlocker = Option::get('bypass_ad_blockers', false); return $adBlocker ? $this->checkAjaxHit() : $this->checkRestHit(); } /** * Check AJAX endpoint for hit recording * * @return bool Returns true if request works, false if blocked or invalid */ private function checkAjaxHit() { $ajax_url = admin_url('admin-ajax.php'); $remoteRequest = new RemoteRequest( $ajax_url, 'POST', [ 'action' => 'wp_statistics_hit_record' ], $this->args ); $remoteRequest->execute(false, false); $response = $remoteRequest->getResponse(); $responseCode = $remoteRequest->getResponseCode(); if ($this->isCloudflareChallenge($response) && 403 === $responseCode) { return true; } return $remoteRequest->isValidJsonResponse(); } /** * Check REST API endpoint for hit recording * * @return bool Returns true if request works, false if blocked or invalid */ private function checkRestHit() { $rest_url = site_url('index.php?rest_route=/wp-statistics/v2/hit'); $remoteRequest = new RemoteRequest( $rest_url, 'POST', [], $this->args ); $remoteRequest->execute(false, false); $response = $remoteRequest->getResponse(); $responseCode = $remoteRequest->getResponseCode(); if ($this->isCloudflareChallenge($response) && 403 === $responseCode) { return true; } return $remoteRequest->isValidJsonResponse(); } /** * Determines if a response indicates a Cloudflare challenge page. * * @param mixed $response The response array containing headers * @return bool True if response indicates a Cloudflare challenge, false otherwise */ private function isCloudflareChallenge($response) { if (!isset($response['headers']) || !is_object($response['headers'])) { return false; } $server = $response['headers']->offsetGet('server') ?? ''; if ($server !== 'cloudflare') { return false; } $cfMitigated = $response['headers']->offsetGet('cf-mitigated') ?? ''; return $cfMitigated === 'challenge'; } /** * Get tracker status information * * @return array Array containing tracker existence, path and cache status */ public function getTrackerStatus() { return $this->trackerStatus; } /** * Initialize tracker status data * Sets up the initial tracker status array with all required information */ private function initializeData() { $fileExists = $this->executeTrackerCheck(); $this->trackerStatus = [ 'exists' => $fileExists, 'path' => $this->trackerPath, 'cacheStatus' => $this->getCacheStatus(), 'hitRecordingStatus' => $this->checkHitRecording() ]; } /** * Execute remote request to check tracker file * Performs a HEAD request to verify file accessibility * * @return bool Whether tracker file exists and is accessible */ public function executeTrackerCheck() { $parsedUrl = parse_url($this->trackerPath); if (empty($parsedUrl['path'])) { return false; } // If the file is dynamic (obfuscated query string), try remote HEAD check if (!empty($parsedUrl['query'])) { parse_str($parsedUrl['query'], $queryParams); $assetNameObfuscator = new AssetNameObfuscator(); $dynamicAssetKey = $assetNameObfuscator->getDynamicAssetKey(); if (isset($queryParams[$dynamicAssetKey])) { $response = wp_safe_remote_head($this->trackerPath, ['sslverify' => false]); return !is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200; } } // Normalize path by removing leading slash $urlPath = ltrim($parsedUrl['path'], '/'); // Try matching with actual wp-content directory $contentDirName = basename(WP_CONTENT_DIR); // usually 'wp-content' $contentPosition = strpos($urlPath, $contentDirName . '/'); if ($contentPosition === false) { return false; } $relativeFilePath = substr($urlPath, $contentPosition + strlen($contentDirName . '/')); $absoluteFilePath = WP_CONTENT_DIR . '/' . $relativeFilePath; return file_exists($absoluteFilePath) && is_readable($absoluteFilePath); } /** * Check if cache plugin is active * * @return bool True if cache plugin is enabled, false otherwise */ public function getCacheStatus() { $cacheInfo = Helper::checkActiveCachePlugin(); return $cacheInfo['status'] ?? false; } /** * Checks if a cache plugin is active and returns its name. * * @return string The active cache plugin name, or an empty string if none is active. */ public function getCachePlugin() { $cacheInfo = Helper::checkActiveCachePlugin(); return $cacheInfo['plugin'] ?? ''; } }