capabilities_service = $capabilities_service; $this->options_repository = $options_repository; $this->nonce_service = $nonce_service; } /** * Add toggle button to admin bar. * * @param \WP_Admin_Bar $wp_admin_bar */ public function add_admin_bar_button(\WP_Admin_Bar $wp_admin_bar): void { // Only show button in admin area, not on frontend if (!is_admin()) { return; } if (!$this->capabilities_service->should_show_admin_bar_button()) { return; } $is_active = $this->is_selection_mode_active(); // Generate URL with or without selection mode parameter $current_url = $this->get_current_url(); $href = $is_active ? $this->remove_selection_mode_param($current_url) : $this->add_selection_mode_param($current_url); $button_title = $is_active ? __('Exit selection mode', 'ads-destroyer') : __('Disable ad', 'ads-destroyer'); // Tooltip text with helpful description $tooltip = $is_active ? esc_attr__('Exit selection mode', 'ads-destroyer') : esc_attr__('Click to hide any elements in the admin area: ads, notices, confidential information, menus, and more', 'ads-destroyer'); $wp_admin_bar->add_node([ 'id' => 'adsd-toggle', 'title' => esc_html($button_title), 'href' => $href, 'meta' => [ 'title' => $tooltip, 'class' => $is_active ? 'adsd-selection-active' : '', ], ]); } /** * Enqueue admin bar assets. */ public function enqueue_admin_bar_assets(): void { if (!$this->capabilities_service->can_use_selector()) { return; } // Enqueue CSS file for overlay styles wp_enqueue_style( 'adsd-style', plugin_dir_url(\ADSD_PLUGIN_FILE) . 'build/css/style.min.css', [], ADSD_PLUGIN_VERSION ); wp_enqueue_script( 'adsd-app', plugin_dir_url(\ADSD_PLUGIN_FILE) . 'build/js/app.js', ['jquery', 'wp-i18n'], ADSD_PLUGIN_VERSION, true ); wp_set_script_translations('adsd-app', 'ads-destroyer'); wp_localize_script('adsd-app', 'ADSD_CONFIG', [ 'rest_url' => rest_url('adsd/v1'), 'nonce' => wp_create_nonce('wp_rest'), 'is_active' => $this->is_selection_mode_active(), 'rules' => $this->get_frontend_rules(), ]); } /** * Get active rules for the client-side fallback runtime. * * Passing them with the page saves a REST round trip on every admin screen. * * @since 1.0.29 * @return array> Rules reduced to the fields the runtime needs. */ private function get_frontend_rules(): array { $rules = []; foreach ($this->options_repository->get_active_rules() as $rule) { // Position based rules only make sense on the screen they came from. if (!PageContext::rule_applies_here($rule)) { continue; } $rules[] = [ 'id' => (string) ($rule['id'] ?? ''), 'xpath' => (string) ($rule['xpath'] ?? ''), 'css_selector' => (string) ($rule['css_selector'] ?? ''), 'page_url' => (string) ($rule['page_url'] ?? ''), ]; } return $rules; } /** * Check if selection mode is currently active. * * @return bool */ private function is_selection_mode_active(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- URL parameter check, not form submission return isset($_GET['adsd_selection_mode_active']) && sanitize_text_field(wp_unslash($_GET['adsd_selection_mode_active'])) === '1'; } /** * Get current URL. * * @return string */ private function get_current_url(): string { // Use WordPress function to get current URL $request_uri = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : ''; return home_url($request_uri); } /** * Add selection mode parameter to URL. * * @param string $url * @return string */ private function add_selection_mode_param(string $url): string { return add_query_arg('adsd_selection_mode_active', '1', $url); } /** * Remove selection mode parameter from URL. * * @param string $url * @return string */ private function remove_selection_mode_param(string $url): string { return remove_query_arg('adsd_selection_mode_active', $url); } /** * Enqueue WordPress pointer scripts for tooltip guide. * * @since 1.0.24 */ public function enqueue_pointer_scripts(): void { // Only show to users who see the admin bar button if (!$this->capabilities_service->should_show_admin_bar_button()) { return; } // Allow forcing pointer display via URL parameter (for testing) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- URL parameter check, not form submission $force_pointer = isset($_GET['adsd_show_pointer']) && sanitize_text_field(wp_unslash($_GET['adsd_show_pointer'])) === '1'; // Check if user has already seen the pointer $dismissed_pointers = get_user_meta(get_current_user_id(), 'dismissed_wp_pointers', true); $dismissed_pointers = explode(',', (string) $dismissed_pointers); $pointer_id = 'adsd_admin_bar_pointer_v124'; // If user already dismissed this pointer and not forcing, don't show it if (!$force_pointer && in_array($pointer_id, $dismissed_pointers, true)) { return; } // Enqueue WordPress pointer scripts and styles wp_enqueue_style('wp-pointer'); wp_enqueue_script('wp-pointer'); // Add inline script to initialize the pointer add_action('admin_print_footer_scripts', function() use ($pointer_id) { $this->render_pointer_script($pointer_id); }); } /** * Render pointer JavaScript. * * @since 1.0.24 * @param string $pointer_id Unique pointer identifier */ private function render_pointer_script(string $pointer_id): void { $pointer_content = '

' . esc_html__('Welcome to AdsDestroyer!', 'ads-destroyer') . '

'; $pointer_content .= '

' . esc_html__('Click this button to hide any elements in the admin area: ads, notices, confidential information, menus, and more.', 'ads-destroyer') . '

'; $pointer_content .= '

' . esc_html__('How it works:', 'ads-destroyer') . '

'; $pointer_content .= '
    '; $pointer_content .= '
  1. ' . esc_html__('Click the button to enter selection mode', 'ads-destroyer') . '
  2. '; $pointer_content .= '
  3. ' . esc_html__('Hover and click on any element you want to hide', 'ads-destroyer') . '
  4. '; $pointer_content .= '
  5. ' . esc_html__('Choose how long to hide it (forever, day, week, or month)', 'ads-destroyer') . '
  6. '; $pointer_content .= '
'; ?> nonce_service->verify_nonce($nonce, 'dismiss_pointer')) { wp_send_json_error(['message' => __('Security check failed', 'ads-destroyer')]); } if (!$this->capabilities_service->can_use_selector()) { wp_send_json_error(['message' => __('Insufficient permissions', 'ads-destroyer')]); } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified above $pointer = isset($_POST['pointer']) ? sanitize_text_field(wp_unslash($_POST['pointer'])) : ''; if (empty($pointer)) { wp_send_json_error(['message' => __('Invalid pointer ID', 'ads-destroyer')]); } // Get current dismissed pointers $dismissed_pointers = get_user_meta(get_current_user_id(), 'dismissed_wp_pointers', true); $dismissed_pointers = explode(',', (string) $dismissed_pointers); // Add new pointer to dismissed list if (!in_array($pointer, $dismissed_pointers, true)) { $dismissed_pointers[] = $pointer; $dismissed_pointers = array_filter($dismissed_pointers); update_user_meta(get_current_user_id(), 'dismissed_wp_pointers', implode(',', $dismissed_pointers)); } wp_send_json_success(['message' => 'Pointer dismissed']); } }