cacheDuration = defined('ADBLOCKGUARD_DEBUG') && ADBLOCKGUARD_DEBUG ? HOUR_IN_SECONDS : DAY_IN_SECONDS; if ($licenseKey) { $this->licenseKey = $licenseKey; update_option('wuadblockguard_license_key', $licenseKey); } else { $this->licenseKey = get_option('wuadblockguard_license_key', ''); } add_action('plugins_loaded', [$this, 'registerPluginRowMeta']); $this->initializeUpdater(); $this->setupUpdateTransientsClear(); } public static function getInstance($licenseKey = null) { if (self::$instance === null) { self::$instance = new self($licenseKey); } return self::$instance; } public function getProductId() { return $this->productId; } public function getStoreUrl() { return $this->storeUrl; } public function getLicenseKey() { return $this->licenseKey; } public function setLicenseKey($licenseKey) { $this->licenseKey = $licenseKey; update_option('wuadblockguard_license_key', $licenseKey); } public function activateLicense() { $api_params = array( 'edd_action' => 'activate_license', 'license' => $this->licenseKey, 'item_id' => $this->getProductId(), 'url' => home_url(), ); $response = wp_remote_post( $this->getLicenseApiUrl(), array( 'body' => $api_params, 'timeout' => 20, 'headers' => array( 'User-Agent' => 'AdBlockGuard/' . ADBLOCKGUARD_VERSION . '; ' . home_url(), 'Accept' => 'application/json', 'X-Wutime-License' => '1', ), ) ); if (is_wp_error($response)) { return array('success' => false, 'error' => $response->get_error_message()); } $license_data = json_decode(wp_remote_retrieve_body($response)); if ($license_data && $license_data->license === 'valid') { $this->updateLicenseStatus('valid', $license_data->expires ?? null); PluginLogger::log('info', 'License activated successfully', [ 'license' => substr($this->licenseKey, 0, 6) . '…', 'expires' => $license_data->expires ?? 'unknown', 'bypass' => true, ]); return ['success' => true, 'expires' => $license_data->expires ?? null]; } $this->updateLicenseStatus('invalid'); return array('success' => false, 'error' => $license_data->error ?? 'Activation failed'); } private function getLicenseApiUrl(): string { return rtrim($this->getStoreUrl(), '/') . '/edd-sl/'; } public function deactivateLicense() { $api_params = array( 'edd_action' => 'deactivate_license', 'license' => $this->licenseKey, 'item_id' => $this->getProductId(), 'url' => home_url(), ); $response = wp_remote_post( $this->getLicenseApiUrl(), array( 'body' => $api_params, 'timeout' => 20, 'headers' => array( 'User-Agent' => 'AdBlockGuard/' . ADBLOCKGUARD_VERSION . '; ' . home_url(), 'Accept' => 'application/json', 'X-Wutime-License' => '1', ), ) ); if (is_wp_error($response)) { PluginLogger::log('debug', 'Deactivate License - WP Error: ' . $response->get_error_message()); return array('success' => false, 'error' => $response->get_error_message()); } $license_data = json_decode(wp_remote_retrieve_body($response)); if ($license_data && ($license_data->license ?? '') === 'deactivated') { PluginLogger::log('info', 'License deactivated', [ 'license' => substr($this->licenseKey, 0, 6) . '…', 'bypass' => true, ]); $this->clearLicenseData(); return ['success' => true]; } PluginLogger::log('debug', 'Deactivate License - Full Response', [ 'response' => $license_data, ]); return array( 'success' => false, 'error' => $license_data->error ?? 'Deactivation failed', ); } public function registerPluginRowMeta() { if (has_filter('plugin_row_meta', [$this, 'pluginRowMeta']) === false) { add_filter('plugin_row_meta', [$this, 'pluginRowMeta'], 10, 4); } if (has_filter('plugin_action_links_ad-block-guard/ad-block-guard.php', [$this, 'pluginActionLinks']) === false) { add_filter('plugin_action_links_ad-block-guard/ad-block-guard.php', [$this, 'pluginActionLinks'], 10, 2); } } public function pluginRowMeta($plugin_meta, $plugin_file, $plugin_data = [], $status = '') { if ($plugin_file !== ADBLOCKGUARD_PLUGIN_BASE_NAME) { return $plugin_meta; } $store_base_url = rtrim(ADBLOCKGUARD_STORE_URL, '/'); $plugin_site_url = $plugin_data['PluginURI'] ?? ($store_base_url . '/downloads/wp-adblock-guard/'); $filtered_meta = []; $has_view_details_link = false; foreach ($plugin_meta as $meta_link) { if (strpos($meta_link, 'open-plugin-details-modal') !== false || strpos($meta_link, 'View details') !== false) { $has_view_details_link = true; } // Remove native "Visit plugin site"; we append it in custom order. if (strpos($meta_link, 'Visit plugin site') !== false) { continue; } $filtered_meta[] = $meta_link; } $plugin_meta = $filtered_meta; if (!$has_view_details_link) { $plugin_slug = $plugin_data['slug'] ?? dirname(ADBLOCKGUARD_PLUGIN_BASE_NAME); /* translators: %s: Plugin name. */ $more_info_aria = sprintf(__('More information about %s', 'ad-block-guard'), ADBLOCKGUARD_NAME); $plugin_meta[] = sprintf( '%s', esc_url( network_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . rawurlencode($plugin_slug) . '&TB_iframe=true&width=600&height=550' ) ), esc_attr($more_info_aria), esc_attr(ADBLOCKGUARD_NAME), esc_html__('View details', 'ad-block-guard') ); } /* translators: %s: Plugin name. */ $visit_site_aria = sprintf(__('Visit plugin site for %s', 'ad-block-guard'), ADBLOCKGUARD_NAME); $plugin_meta[] = sprintf( '%s', esc_url($plugin_site_url), esc_attr($visit_site_aria), esc_html__('Visit plugin site', 'ad-block-guard') ); $plugin_meta[] = '' . esc_html__('Support', 'ad-block-guard') . ''; $plugin_meta[] = '' . esc_html__('Review', 'ad-block-guard') . ''; return $plugin_meta; } public function pluginActionLinks($links, $plugin_file) { if ($plugin_file === 'ad-block-guard/ad-block-guard.php') { $settings_link = '' . esc_html__('Settings', 'ad-block-guard') . ''; if ($this->isLicenseValid()) { array_unshift($links, $settings_link); $links[] = '' . esc_html__('Manage License', 'ad-block-guard') . ''; } else { $upgrade_link = '' . esc_html__('Get AdBlock Pro', 'ad-block-guard') . ''; array_unshift($links, $settings_link); array_unshift($links, $upgrade_link); $links[] = '' . esc_html__('Activate License', 'ad-block-guard') . ''; } } return $links; } public function isLicenseValid() { $lastCheck = get_transient(ADBLOCKGUARD_LICENSE_LAST_CHECK); if ($lastCheck) { $licenseStatus = get_option('wuadblockguard_license_status', 'inactive'); return $licenseStatus === 'valid'; } $result = $this->checkLicenseValidity(); return $result['success'] && $result['status'] === 'valid'; } public function isUpdateAvailable() { // Plugin updates are provided by WordPress.org only. return false; } public function initializeUpdater() { // Licensing remains active in this class, but plugin update metadata stays WordPress-native. return; } public function setupUpdateTransientsClear() { add_action('upgrader_process_complete', function ($upgrader_object, $options) { if ($options['action'] == 'update' && $options['type'] == 'plugin') { if (isset($options['plugins']) && in_array(ADBLOCKGUARD_PLUGIN_BASE_NAME, $options['plugins'], true)) { delete_transient('wuadblockguard_latest_version'); delete_transient('wuadblockguard_product_details'); delete_option('wuadblockguard_license_status'); delete_option('wuadblockguard_license_expires'); } } }, 10, 2); } public function updateProductDetails() { // No-op: keep method for backwards compatibility with scheduled hooks. return; } public function checkLicense() { $this->checkLicenseValidity(); } public function getProductDetails() { $transient_name = 'wuadblockguard_product_details'; $product_details = get_transient($transient_name); if ($product_details === false) { $product_details = [ 'permalink' => trailingslashit($this->getStoreUrl()) . 'downloads/wp-adblock-guard/', 'title' => 'AdBlock Guard Pro', 'price' => '', 'version' => ADBLOCKGUARD_VERSION, ]; set_transient($transient_name, $product_details, WEEK_IN_SECONDS); } return $product_details; } public function checkLicenseValidity($forceCheck = false) { $lastCheck = get_transient(ADBLOCKGUARD_LICENSE_LAST_CHECK); if (!$forceCheck && $lastCheck) { $licenseStatus = get_option('wuadblockguard_license_status', 'inactive'); return [ 'success' => $licenseStatus === 'valid', 'status' => $licenseStatus, 'expires' => get_option('wuadblockguard_license_expires', ''), 'cached' => true ]; } $api_params = [ 'edd_action' => 'check_license', 'license' => $this->getLicenseKey(), 'item_id' => $this->getProductId(), 'url' => home_url(), ]; $response = wp_remote_post( $this->getLicenseApiUrl(), [ 'body' => $api_params, 'timeout' => 20, 'headers' => [ 'User-Agent' => 'AdBlockGuard/' . ADBLOCKGUARD_VERSION . '; ' . home_url(), 'Accept' => 'application/json', 'X-Wutime-License' => '1', ], ] ); if (is_wp_error($response)) { PluginLogger::log( 'error', 'Check License Validity - WP Error: ' . $response->get_error_message() ); return [ 'success' => false, 'status' => 'error', 'error' => $response->get_error_message() ]; } $body = wp_remote_retrieve_body($response); // Bail early if response is empty or clearly not JSON if (!is_string($body) || $body === '' || $body[0] !== '{') { $this->logHttpDebug('check_license (non-json)', $response); PluginLogger::log( 'error', 'License Check Error: Non-JSON response from license server.' ); return [ 'success' => false, 'status' => 'error', 'error' => esc_html__('Invalid response from license server.', 'ad-block-guard') ]; } $license_data = json_decode($body); if ($license_data && isset($license_data->license)) { $this->updateLicenseStatus( $license_data->license, $license_data->expires ?? null ); return [ 'success' => $license_data->license === 'valid', 'status' => $license_data->license, 'expires' => $license_data->expires ?? '', 'cached' => false ]; } // JSON decoded but still not what EDD should return $this->logHttpDebug('check_license (invalid-json)', $response); PluginLogger::log( 'error', 'License Check Error: Invalid response from license server.' ); return [ 'success' => false, 'status' => 'error', 'error' => esc_html__('Invalid response from license server.', 'ad-block-guard') ]; } private function logHttpDebug($context, $response) { // Guard: handle unexpected input if (!is_array($response)) { PluginLogger::log('error', $context . ' HTTP debug (invalid response type)', [ 'response_type' => gettype($response), ]); return; } $code = wp_remote_retrieve_response_code($response); $ctype = wp_remote_retrieve_header($response, 'content-type'); $server = wp_remote_retrieve_header($response, 'server'); // Cloudflare correlation headers (gold for searching CF events) $cfRay = wp_remote_retrieve_header($response, 'cf-ray'); $cfCache = wp_remote_retrieve_header($response, 'cf-cache-status'); $cfRequestId = wp_remote_retrieve_header($response, 'cf-request-id'); // sometimes present // Other useful HTTP headers $date = wp_remote_retrieve_header($response, 'date'); $cacheCtrl = wp_remote_retrieve_header($response, 'cache-control'); $expires = wp_remote_retrieve_header($response, 'expires'); $via = wp_remote_retrieve_header($response, 'via'); $xCache = wp_remote_retrieve_header($response, 'x-cache'); $xServedBy = wp_remote_retrieve_header($response, 'x-served-by'); $xFrame = wp_remote_retrieve_header($response, 'x-frame-options'); $body = wp_remote_retrieve_body($response); $bodyStr = is_string($body) ? $body : ''; // Keep logs safe + useful: short preview + detect common block/challenge pages $bodyHead = $bodyStr !== '' ? substr($bodyStr, 0, 600) : ''; $isHtml = $bodyStr !== '' && stripos($bodyStr, 'Just a moment') !== false; // Pull request info that helps you correlate "what triggered this" $requestUri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : 'N/A'; $siteUrl = home_url(); $storeUrl = method_exists($this, 'getStoreUrl') ? $this->getStoreUrl() : (defined('ADBLOCKGUARD_STORE_URL') ? ADBLOCKGUARD_STORE_URL : 'N/A'); // Optional: log the outgoing UA if you set one in wp_remote_post args // (won't always be available, but harmless to include when you provide it) $outgoingUa = 'N/A'; if (isset($response['http_response']) && is_object($response['http_response'])) { // Not guaranteed; WP HTTP API doesn't reliably expose the request UA. $outgoingUa = 'unknown'; } PluginLogger::log('error', $context . ' HTTP debug', [ // Where we were when the error happened (customer site) 'request_uri' => $requestUri, 'site_url' => $siteUrl, // Where we were calling (your license server) 'store_url' => $storeUrl, // Core response signals 'code' => $code, 'content_type' => $ctype, 'server' => $server, // Cloudflare correlation (paste cf_ray into CF Security Events search) 'cf_ray' => $cfRay, 'cf_cache' => $cfCache, 'cf_request_id'=> $cfRequestId, // Useful caching / proxy headers 'date' => $date, 'cache_control'=> $cacheCtrl, 'expires' => $expires, 'via' => $via, 'x_cache' => $xCache, 'x_served_by' => $xServedBy, 'x_frame' => $xFrame, // Body summary (don’t dump full body) 'body_len' => $bodyStr !== '' ? strlen($bodyStr) : 0, 'body_head' => $bodyHead, // Simple classifiers to speed up triage 'flags' => [ 'is_html' => $isHtml, 'looks_like_cf_challenge' => $hasCfMoment, ], ]); } public function validateLicenseCron() { $this->checkLicenseValidity(true); } private function updateLicenseStatus($status, $expires = null) { update_option('wuadblockguard_license_status', $status); set_transient(ADBLOCKGUARD_LICENSE_LAST_CHECK, time() + $this->cacheDuration, $this->cacheDuration); if ($expires) { update_option('wuadblockguard_license_expires', $expires); } } private function clearLicenseData() { delete_transient(ADBLOCKGUARD_LICENSE_LAST_CHECK); delete_option('wuadblockguard_license_key'); delete_option('wuadblockguard_license_status'); delete_option('wuadblockguard_license_expires'); } public function getUpgradeLink() { $productDetails = $this->getProductDetails(); return $productDetails['permalink'] . '?utm_source=WordPress&utm_medium=all-plugins&utm_campaign=liteplugin&utm_locale=' . get_locale() . '&utm_content=' . rawurlencode(esc_html__('Get !AdBlock Guard Pro', 'ad-block-guard')); } }