radio_get_state(), 'stations' => radio_get_stations_flat(), 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'radio_save_state' ), 'popoutUrl' => admin_url( 'admin-post.php?action=radio_popout' ), 'strings' => array( 'play' => __( 'Play', 'radio' ), 'pause' => __( 'Pause', 'radio' ), 'loading' => __( 'Loading…', 'radio' ), 'error' => __( 'Stream error — try another station.', 'radio' ), 'saveError' => __( 'Preferences not saved — check your connection.', 'radio' ), 'mute' => __( 'Mute', 'radio' ), 'unmute' => __( 'Unmute', 'radio' ), 'nowPlaying' => __( 'Now Playing', 'radio' ), 'volume' => __( 'Volume', 'radio' ), 'station' => __( 'Station', 'radio' ), 'addFav' => __( 'Add to favourites', 'radio' ), 'removeFav' => __( 'Remove from favourites', 'radio' ), 'clearConfirm' => __( 'Clear all track history? (Favourites are kept.)', 'radio' ), ), ) ); } /** * AJAX endpoint for saving per-user state (station / volume changes). * Avoids a full page reload on every interaction. */ add_action( 'wp_ajax_radio_save_state', 'radio_ajax_save_state' ); function radio_ajax_save_state() { if ( ! is_user_logged_in() ) { wp_send_json_error( 'Not logged in.', 401 ); } check_ajax_referer( 'radio_save_state', 'nonce' ); $patch = array(); if ( isset( $_POST['station_id'] ) ) { $patch['station_id'] = sanitize_key( wp_unslash( $_POST['station_id'] ) ); } if ( isset( $_POST['volume'] ) ) { $patch['volume'] = max( 0.0, min( 1.0, (float) $_POST['volume'] ) ); } if ( empty( $patch ) ) { wp_send_json_error( 'Nothing to save.', 400 ); } radio_update_state( $patch ); wp_send_json_success( radio_get_state() ); } /** * AJAX: log a played track to the user's history (v0.5.0). * Nonce: same `radio_save_state` token used for state saves — both come * from the same `wp_localize_script` config available on player pages. */ add_action( 'wp_ajax_radio_log_track', 'radio_ajax_log_track' ); function radio_ajax_log_track() { if ( ! is_user_logged_in() ) { wp_send_json_error( 'Not logged in.', 401 ); } check_ajax_referer( 'radio_save_state', 'nonce' ); $logged = radio_log_track( array( 'artist' => $_POST['artist'] ?? '', 'title' => $_POST['title'] ?? '', 'station' => $_POST['station'] ?? '', 'station_id' => $_POST['station_id'] ?? '', ) ); wp_send_json_success( array( 'logged' => (bool) $logged ) ); } /** * AJAX: toggle whether a track is favourited. * Nonce: `radio_history` — created fresh per History-page render so the * AJAX is gated to users who actually loaded the page. */ add_action( 'wp_ajax_radio_toggle_favourite', 'radio_ajax_toggle_favourite' ); function radio_ajax_toggle_favourite() { if ( ! is_user_logged_in() ) { wp_send_json_error( 'Not logged in.', 401 ); } check_ajax_referer( 'radio_history', 'nonce' ); $new_state = radio_toggle_favourite( array( 'artist' => $_POST['artist'] ?? '', 'title' => $_POST['title'] ?? '', 'station' => $_POST['station'] ?? '', 'station_id' => $_POST['station_id'] ?? '', ) ); wp_send_json_success( array( 'favourite' => (bool) $new_state ) ); } /** AJAX: clear the current user's history (favourites preserved). */ add_action( 'wp_ajax_radio_clear_history', 'radio_ajax_clear_history' ); function radio_ajax_clear_history() { if ( ! is_user_logged_in() ) { wp_send_json_error( 'Not logged in.', 401 ); } check_ajax_referer( 'radio_history', 'nonce' ); radio_clear_history_all(); wp_send_json_success(); } /** * v0.6.0 — Pop-out mini player. Renders a standalone HTML page (no WP * admin chrome) at `admin-post.php?action=radio_popout`, opened by the * JS via `window.open()`. The popup persists across main-tab navigation * so background music keeps playing when the user moves around the * admin. `&play=1` in the URL tells `radio.js` to auto-resume on load. */ add_action( 'admin_post_radio_popout', 'radio_render_popout_page' ); function radio_render_popout_page() { if ( ! current_user_can( 'read' ) ) { wp_die( esc_html__( 'You do not have permission to view this page.', 'radio' ) ); } $state = radio_get_state(); $station = radio_find_station( $state['station_id'] ); $stations = radio_get_stations_grouped(); $theme = isset( $state['theme'] ) ? $state['theme'] : 'auto'; if ( ! in_array( $theme, array( 'auto', 'light', 'dark' ), true ) ) { $theme = 'auto'; } $cfg = array( 'state' => $state, 'stations' => radio_get_stations_flat(), 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'radio_save_state' ), 'popoutUrl' => '', // already in popout — no further popouts 'autoPlay' => isset( $_GET['play'] ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- query flag only, no state change 'strings' => array( 'play' => __( 'Play', 'radio' ), 'pause' => __( 'Pause', 'radio' ), 'loading' => __( 'Loading…', 'radio' ), 'error' => __( 'Stream error — try another station.', 'radio' ), 'saveError' => __( 'Preferences not saved — check your connection.', 'radio' ), 'mute' => __( 'Mute', 'radio' ), 'unmute' => __( 'Unmute', 'radio' ), 'nowPlaying' => __( 'Now Playing', 'radio' ), 'volume' => __( 'Volume', 'radio' ), 'station' => __( 'Station', 'radio' ), ), ); ?>