> */ public function get_hooks(): array { return array( array( 'admin_notices', array( $this, 'render' ) ), array( 'wp_ajax_postlinkly_dismiss_notice', array( $this, 'handle_dismiss' ) ), ); } /** * Renders any pending admin notices. * * @since 1.0.0 * @hook admin_notices * * @return void */ public function render(): void { if ( ! current_user_can( CapabilityMap::for( 'view_report' ) ) ) { return; } $user_id = get_current_user_id(); // First-run notice — shown until the user dismisses it. if ( ! get_user_meta( $user_id, self::META_FIRST_RUN_DISMISSED, true ) ) { $this->render_first_run_notice(); } } /** * Handles the dismiss-notice AJAX action. * * Action: `postlinkly_dismiss_notice` * * @since 1.0.0 * @hook wp_ajax_postlinkly_dismiss_notice * * @return void */ public function handle_dismiss(): void { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['nonce'] ) ) : ''; if ( ! wp_verify_nonce( $nonce, 'postlinkly_dismiss_notice' ) ) { wp_send_json_error( array( 'message' => __( 'Invalid nonce.', 'postlinkly' ) ), 403 ); } // Dismissing only writes the current user's own preference, so the // lowest capability is the right one -- but it is still checked // rather than implied by being logged in. if ( ! current_user_can( 'read' ) ) { wp_send_json_error( array( 'message' => __( 'Permission denied.', 'postlinkly' ) ), 403 ); } $notice = isset( $_POST['notice'] ) ? sanitize_key( (string) $_POST['notice'] ) : ''; if ( 'first_run' === $notice ) { update_user_meta( get_current_user_id(), self::META_FIRST_RUN_DISMISSED, '1' ); } wp_send_json_success(); } /** * Renders the first-run welcome notice. * * The shared admin JS bundle handles the dismiss click and fires * the AJAX call. AssetRegistrar ensures the bundle is localised on * every admin page so the handler always has cfg.ajaxUrl available. * * @since 1.0.0 * @since 1.1.0 Removed redundant wp_enqueue_script / wp_localize_script * calls — AssetRegistrar now handles this unconditionally. * * @return void */ private function render_first_run_notice(): void { // Ensure the admin JS is loaded on non-Postlinkly pages (e.g. the // main WP dashboard) so the dismiss button works everywhere. if ( ! wp_script_is( 'postlinkly-admin', 'enqueued' ) ) { wp_enqueue_script( 'postlinkly-admin' ); } $settings_url = AdminUrls::settings(); $nonce = wp_create_nonce( 'postlinkly_dismiss_notice' ); ?>
' . esc_html__( 'Settings', 'postlinkly' ) . '' ); ?>