store = $store; $this->page_url_resolver = $page_url_resolver; } /** * Hooks up notice rendering and dismissal handling. * * @since 1.0.0 */ public function register(): void { add_action( 'admin_init', array( $this, 'maybe_handle_dismiss' ) ); add_action( 'admin_notices', array( $this, 'render' ) ); } /** * Handles the dismissal redirect when the user clicks the dismiss link. * * @since 1.0.0 */ public function maybe_handle_dismiss(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is verified below. if ( ! isset( $_GET[ self::DISMISS_QUERY_ARG ] ) ) { return; } if ( ! current_user_can( 'manage_options' ) ) { return; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : ''; if ( ! wp_verify_nonce( $nonce, self::DISMISS_NONCE ) ) { return; } update_user_meta( get_current_user_id(), self::DISMISS_META, $this->signature() ); $redirect = remove_query_arg( array( self::DISMISS_QUERY_ARG, '_wpnonce' ) ); wp_safe_redirect( $redirect ); exit; } /** * Renders the notice. * * @since 1.0.0 */ public function render(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } $screen = get_current_screen(); if ( $screen && 'tools_page_ai-connector-approval' === $screen->id ) { return; } $pending = $this->store->get_pending(); if ( empty( $pending ) ) { return; } $signature = $this->signature(); $dismissed = (string) get_user_meta( get_current_user_id(), self::DISMISS_META, true ); if ( $signature === $dismissed ) { return; } $count = count( $pending ); $review_url = (string) call_user_func( $this->page_url_resolver ); $dismiss_url = wp_nonce_url( add_query_arg( self::DISMISS_QUERY_ARG, '1' ), self::DISMISS_NONCE ); $class = 'notice notice-warning ai-connector-approval-notice'; if ( $screen && 'tools_page_ai-request-logs' === $screen->id ) { $class .= ' inline'; } printf( '
', esc_attr( $class ), esc_html( sprintf( /* translators: %d: number of pending approval requests. */ _n( '%d plugin or theme is requesting access to an AI connector.', '%d plugins or themes are requesting access to AI connectors.', $count, 'ai' ), $count ) ), esc_url( $review_url ), esc_html__( 'Review requests', 'ai' ), esc_url( $dismiss_url ), esc_html__( 'Dismiss', 'ai' ) ); } /** * Returns a stable signature of the current pending set for dismissal tracking. * * The signature changes whenever new requests are added, so a dismissal only * silences the notice until the queue meaningfully changes. * * @since 1.0.0 * * @return string */ private function signature(): string { $pending = $this->store->get_pending(); $keys = array_keys( $pending ); sort( $keys ); return md5( implode( '|', $keys ) ); } }