options_repository = $options_repository; $this->nonce_service = $nonce_service; $this->capabilities_service = $capabilities_service; $this->logger = $logger; } /** * Initialize selection mode functionality. */ public function init(): void { add_action('wp_ajax_adsd_toggle_selection_mode', [$this, 'handle_toggle_selection_mode']); add_action('wp_ajax_adsd_save_element_rule', [$this, 'handle_save_element_rule']); add_action('wp_ajax_adsd_test_xpath', [$this, 'handle_test_xpath']); } /** * Handle AJAX request to toggle selection mode. */ public function handle_toggle_selection_mode(): void { // Verify nonce // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified in verify_nonce call below $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if (!$this->nonce_service->verify_nonce($nonce, 'adsd_selection_mode')) { wp_die(esc_html__('Security check failed', 'ads-destroyer')); } // Check capabilities if (!$this->capabilities_service->can_access_selection_mode()) { wp_die(esc_html__('Insufficient permissions', 'ads-destroyer')); } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified above $enable = isset($_POST['enable']) ? filter_var(wp_unslash($_POST['enable']), FILTER_VALIDATE_BOOLEAN) : false; $user_id = get_current_user_id(); if ($enable) { update_user_meta($user_id, 'adsd_selection_mode', '1'); $this->logger->info('Selection mode enabled by user: ' . wp_get_current_user()->user_login); } else { delete_user_meta($user_id, 'adsd_selection_mode'); $this->logger->info('Selection mode disabled by user: ' . wp_get_current_user()->user_login); } wp_send_json_success([ 'enabled' => $enable, 'message' => $enable ? __('Selection mode enabled', 'ads-destroyer') : __('Selection mode disabled', 'ads-destroyer'), ]); } /** * Handle AJAX request to save element rule. */ public function handle_save_element_rule(): void { // Verify nonce // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified in verify_nonce call below $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if (!$this->nonce_service->verify_nonce($nonce, 'adsd_selection_mode')) { wp_die(esc_html__('Security check failed', 'ads-destroyer')); } // Check capabilities if (!$this->capabilities_service->can_access_selection_mode()) { wp_die(esc_html__('Insufficient permissions', 'ads-destroyer')); } // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verified above, xpath sanitized via SanitizerService $xpath_raw = isset($_POST['xpath']) ? wp_unslash($_POST['xpath']) : ''; $xpath = SanitizerService::sanitize_xpath($xpath_raw); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified above $description = isset($_POST['description']) ? sanitize_text_field(wp_unslash($_POST['description'])) : ''; if (empty($xpath)) { wp_send_json_error(['message' => __('XPath is required', 'ads-destroyer')]); } // Get existing rules $rules = $this->options_repository->get_option('rules', []); // Add new rule $new_rule = [ 'id' => uniqid('rule_'), 'xpath' => $xpath, // translators: %s: XPath expression 'description' => $description ?: sprintf(__('Rule for %s', 'ads-destroyer'), $xpath), 'enabled' => true, 'created_at' => current_time('mysql'), 'created_by' => wp_get_current_user()->user_login, ]; $rules[] = $new_rule; // Save rules $this->options_repository->set_option('rules', $rules); $this->logger->info('New rule created by user: ' . wp_get_current_user()->user_login . ' - XPath: ' . $xpath); wp_send_json_success([ 'rule' => $new_rule, 'message' => __('Rule saved successfully', 'ads-destroyer'), ]); } /** * Handle AJAX request to test XPath. */ public function handle_test_xpath(): void { // Verify nonce // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified in verify_nonce call below $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if (!$this->nonce_service->verify_nonce($nonce, 'adsd_selection_mode')) { wp_die(esc_html__('Security check failed', 'ads-destroyer')); } // Check capabilities if (!$this->capabilities_service->can_access_selection_mode()) { wp_die(esc_html__('Insufficient permissions', 'ads-destroyer')); } // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verified above, xpath sanitized via SanitizerService $xpath_raw = isset($_POST['xpath']) ? wp_unslash($_POST['xpath']) : ''; $xpath = SanitizerService::sanitize_xpath($xpath_raw); if (empty($xpath)) { wp_send_json_error(['message' => __('XPath is required', 'ads-destroyer')]); } // Test XPath by applying it to current page content $test_result = $this->test_xpath_on_current_page($xpath); wp_send_json_success([ 'valid' => $test_result['valid'], 'matches' => $test_result['matches'], 'message' => $test_result['message'], ]); } /** * Test XPath on current page content. * * @param string $xpath * @return array */ private function test_xpath_on_current_page(string $xpath): array { try { // Get current page content with sanitized URL $current_uri = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : ''; $url = home_url($current_uri); $response = wp_remote_get($url); if (is_wp_error($response)) { return [ 'valid' => false, 'matches' => 0, 'message' => __('Could not fetch page content for testing', 'ads-destroyer'), ]; } $html = wp_remote_retrieve_body($response); // Create DOMDocument $dom = new \DOMDocument(); libxml_use_internal_errors(true); $dom->loadHTML('' . $html); libxml_clear_errors(); // Create XPath $xpath_obj = new \DOMXPath($dom); $nodes = $xpath_obj->query($xpath); if ($nodes === false) { return [ 'valid' => false, 'matches' => 0, 'message' => __('Invalid XPath expression', 'ads-destroyer'), ]; } $matches = $nodes->length; return [ 'valid' => true, 'matches' => $matches, 'message' => sprintf( // translators: %d: Number of matched elements _n( 'XPath is valid and matches %d element', 'XPath is valid and matches %d elements', $matches, 'ads-destroyer' ), $matches ), ]; } catch (\Exception $e) { return [ 'valid' => false, 'matches' => 0, // translators: %s: Error message 'message' => sprintf(__('Error testing XPath: %s', 'ads-destroyer'), $e->getMessage()), ]; } } }