post_type ) { return $post_id; } // If this is an autosave, our form has not been submitted, so we don't want to do anything if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return $post_id; } // Check the logged in user has permission to edit this post if ( ! aiovg_current_user_can( 'edit_aiovg_video', $post_id ) ) { return $post_id; } // Check if "aiovg_video_metabox_nonce" nonce is set if ( isset( $_POST['aiovg_video_metabox_nonce'] ) ) { // Verify that the nonce is valid if ( wp_verify_nonce( $_POST['aiovg_video_metabox_nonce'], 'aiovg_save_video_metabox' ) ) { // OK to save meta data $this->save_bunny_stream_data( $post_id ); } } } /** * Update bunny stream data in the post. * * @since 4.2.0 * @param int $post_id Post ID. */ public function save_bunny_stream_data( $post_id ) { $type = isset( $_POST['type'] ) ? sanitize_text_field( $_POST['type'] ) : 'default'; $mp4 = isset( $_POST['mp4'] ) ? trim( $_POST['mp4'] ) : ''; if ( ! empty( $mp4 ) ) { $private_base_url = aiovg_get_private_base_url(); // Check if the URL is a masked uploaded file if ( 0 === strpos( $mp4, $private_base_url ) ) { // Extract the encoded portion $encoded = substr( $mp4, strlen( $private_base_url ) ); // Decode the masked URL $decoded = aiovg_base64_decode( $encoded ); // Sanitize the real file URL $mp4 = aiovg_sanitize_url( $decoded ); } else { // Direct URL entered by the user $mp4 = aiovg_sanitize_url( $mp4 ); } } $bunny_stream_video_id = isset( $_POST['bunny_stream_video_id'] ) ? sanitize_text_field( $_POST['bunny_stream_video_id'] ) : 0; if ( ! empty( $bunny_stream_video_id ) ) { if ( 'default' != $type ) { $this->delete_bunny_stream_video( $bunny_stream_video_id ); $bunny_stream_video_id = 0; } elseif ( ! empty( $mp4 ) ) { if ( strpos( $mp4, '/' . $bunny_stream_video_id . '/' ) === false ) { $this->delete_bunny_stream_video( $bunny_stream_video_id ); $bunny_stream_video_id = 0; } } } update_post_meta( $post_id, 'bunny_stream_video_id', $bunny_stream_video_id ); if ( ! empty( $_POST['deletable_bunny_stream_video_ids'] ) ) { $deletable_bunny_stream_video_ids = explode( ',', $_POST['deletable_bunny_stream_video_ids'] ); $deletable_bunny_stream_video_ids = array_filter( array_map( 'sanitize_text_field', $deletable_bunny_stream_video_ids ) ); if ( ! empty( $deletable_bunny_stream_video_ids ) ) { foreach ( $deletable_bunny_stream_video_ids as $bunny_stream_video_id ) { $this->delete_bunny_stream_video( $bunny_stream_video_id ); } } } // Trailer Bunny Stream if ( isset( $_POST['aiovg_video_trailer_nonce'] ) && wp_verify_nonce( $_POST['aiovg_video_trailer_nonce'], 'aiovg_save_video_trailer' ) ) { $trailer_type = isset( $_POST['trailer_type'] ) ? sanitize_text_field( $_POST['trailer_type'] ) : ''; $trailer_src = isset( $_POST['trailer_src'] ) ? aiovg_sanitize_url( $_POST['trailer_src'] ) : ''; $trailer_bunny_stream_video_id = isset( $_POST['trailer_bunny_stream_video_id'] ) ? sanitize_text_field( $_POST['trailer_bunny_stream_video_id'] ) : ''; if ( ! empty( $trailer_bunny_stream_video_id ) ) { if ( 'embedcode' === $trailer_type ) { $this->delete_bunny_stream_video( $trailer_bunny_stream_video_id ); $trailer_bunny_stream_video_id = ''; } elseif ( ! empty( $trailer_src ) ) { if ( strpos( $trailer_src, '/' . $trailer_bunny_stream_video_id . '/' ) === false ) { $this->delete_bunny_stream_video( $trailer_bunny_stream_video_id ); $trailer_bunny_stream_video_id = ''; } } } update_post_meta( $post_id, 'trailer_bunny_stream_video_id', $trailer_bunny_stream_video_id ); if ( ! empty( $_POST['deletable_trailer_bunny_stream_video_ids'] ) ) { $deletable_trailer_bunny_stream_video_ids = explode( ',', $_POST['deletable_trailer_bunny_stream_video_ids'] ); $deletable_trailer_bunny_stream_video_ids = array_filter( array_map( 'sanitize_text_field', $deletable_trailer_bunny_stream_video_ids ) ); if ( ! empty( $deletable_trailer_bunny_stream_video_ids ) ) { foreach ( $deletable_trailer_bunny_stream_video_ids as $deletable_trailer_id ) { $this->delete_bunny_stream_video( $deletable_trailer_id ); } } } } } /** * Delete Bunny Stream videos associated with a video post before it is deleted. * * @since 4.2.0 * @param int $post_id Video Post ID. */ public function before_delete_post( $post_id ) { if ( 'aiovg_videos' != get_post_type( $post_id ) ) { return false; } // Main video: delete Bunny Stream video if one is stored $video_id = get_post_meta( $post_id, 'bunny_stream_video_id', true ); if ( ! empty( $video_id ) ) { $video_url = get_post_meta( $post_id, 'mp4', true ); if ( strpos( $video_url, '/' . $video_id . '/' ) !== false ) { $this->delete_bunny_stream_video( $video_id ); } } // Trailer: delete Bunny Stream trailer video if one is stored $trailer_video_id = get_post_meta( $post_id, 'trailer_bunny_stream_video_id', true ); if ( ! empty( $trailer_video_id ) ) { $trailer_src = get_post_meta( $post_id, 'trailer_src', true ); if ( strpos( $trailer_src, '/' . $trailer_video_id . '/' ) !== false ) { $this->delete_bunny_stream_video( $trailer_video_id ); } } } /** * Create a Bunny Stream video entry via AJAX. * * @since 4.2.0 */ public function ajax_callback_create_bunny_stream_video() { // Verify nonce check_ajax_referer( 'aiovg_ajax_nonce', 'security' ); // Ensure user is logged in if ( ! is_user_logged_in() ) { wp_send_json_error( __( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ); } // Verify user capability $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0; if ( $post_id > 0 ) { $post = get_post( $post_id ); if ( ! $post || 'aiovg_videos' !== $post->post_type ) { wp_send_json_error( __( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ); } if ( ! aiovg_current_user_can( 'edit_aiovg_video', $post_id ) ) { wp_send_json_error( __( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ); } } else { if ( ! aiovg_current_user_can( 'edit_aiovg_videos' ) ) { wp_send_json_error( __( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ); } } // Vars $settings = aiovg_has_bunny_stream_enabled(); // Fetch Bunny Stream settings $response = array(); if ( empty( $settings ) ) { $response['error'] = __( 'Invalid API Credentials.', 'all-in-one-video-gallery' ); wp_send_json_error( $response ); } // Sanitize and assign the necessary parameters from settings and request $api_key = sanitize_text_field( $settings['api_key'] ); $library_id = intval( $settings['library_id'] ); $cdn_hostname = sanitize_text_field( $settings['cdn_hostname'] ); $collection_id = ! empty( $settings['collection_id'] ) ? sanitize_text_field( $settings['collection_id'] ) : ''; $title = isset( $_POST['title'] ) ? sanitize_text_field( $_POST['title'] ) : ''; $expires = time() + 3600; // Token expiration time (1 hour from now) if ( empty( $title ) ) { $response['error'] = __( 'Invalid Video Title.', 'all-in-one-video-gallery' ); wp_send_json_error( $response ); } // Build the request body for Bunny Stream API $body = array( 'title' => $title, 'thumbnailTime' => 5 // Set thumbnail time in seconds ); if ( ! empty( $collection_id ) ) { $body['collectionId'] = $collection_id; } // Send request to Bunny Stream API to create a video entry $api_response = wp_remote_post( "https://video.bunnycdn.com/library/{$library_id}/videos", array( 'headers' => array( 'AccessKey' => $api_key, 'Content-Type' => 'application/json', 'Accept' => 'application/json' ), 'body' => wp_json_encode( $body ) )); // Handle errors from the remote request if ( is_wp_error( $api_response ) ) { $response['error'] = $api_response->get_error_message(); wp_send_json_error( $response ); } // Decode the response and extract video metadata $body = wp_remote_retrieve_body( $api_response ); $json = json_decode( $body, true ); if ( ! is_array( $json ) ) { $response['error'] = __( 'Sorry, transcoding failed. Please contact the site administrator.', 'all-in-one-video-gallery' ); wp_send_json_error( $response ); } $video_id = $json['guid']; // Build the final response with token and metadata $response = array( 'token' => hash( 'sha256', $library_id . $api_key . $expires . $video_id ), // Upload token for Bunny's direct-to-cloud 'expires' => $expires, 'video_id' => $video_id, 'library_id' => $library_id, 'collection_id' => $collection_id, 'title' => $title, 'video_url' => 'https://' . $cdn_hostname . '/' . $video_id . '/playlist.m3u8', 'thumbnail_url' => 'https://' . $cdn_hostname . '/' . $video_id . '/' . $json['thumbnailFileName'] ); wp_send_json_success( $response ); // Return successful JSON response } /** * Get a Bunny Stream video entry and return its status and metadata. * * @since 4.2.0 */ public function ajax_callback_get_bunny_stream_video() { // Verify nonce check_ajax_referer( 'aiovg_ajax_nonce', 'security' ); // Ensure user is logged in if ( ! is_user_logged_in() ) { wp_send_json_error( __( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ); } // Verify user capability $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0; if ( $post_id > 0 ) { $post = get_post( $post_id ); if ( ! $post || 'aiovg_videos' !== $post->post_type ) { wp_send_json_error( __( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ); } if ( ! aiovg_current_user_can( 'edit_aiovg_video', $post_id ) ) { wp_send_json_error( __( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ); } } else { if ( ! aiovg_current_user_can( 'edit_aiovg_videos' ) ) { wp_send_json_error( __( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ); } } // Vars $settings = aiovg_has_bunny_stream_enabled(); // Fetch Bunny Stream API settings $response = array(); if ( empty( $settings ) ) { $response['error'] = __( 'Invalid API Credentials.', 'all-in-one-video-gallery' ); wp_send_json_error( $response ); } // Sanitize and extract parameters $api_key = sanitize_text_field( $settings['api_key'] ); $library_id = intval( $settings['library_id'] ); $video_id = isset( $_POST['video_id'] ) ? sanitize_text_field( $_POST['video_id'] ) : ''; if ( empty( $video_id ) ) { $response['error'] = __( 'Invalid Video ID.', 'all-in-one-video-gallery' ); wp_send_json_error( $response ); } // Request video data from Bunny Stream API $api_response = wp_remote_get( "https://video.bunnycdn.com/library/{$library_id}/videos/{$video_id}", array( 'headers' => array( 'AccessKey' => $api_key, 'Accept' => 'application/json' ) )); // Handle API errors if ( is_wp_error( $api_response ) ) { $response['error'] = $api_response->get_error_message(); wp_send_json_error( $response ); } // Decode JSON response $body = wp_remote_retrieve_body( $api_response ); $json = json_decode( $body, true ); if ( ! is_array( $json ) ) { $response['error'] = __( 'Sorry, transcoding failed. Please contact the site administrator.', 'all-in-one-video-gallery' ); wp_send_json_error( $response ); } $status_message = __( 'Processing: Your video is being processed. This usually happens quickly, but during busy times, it may take a little longer. You can safely continue and save the form — no need to wait. The video will automatically become playable once processing is complete.', 'all-in-one-video-gallery' ); if ( 3 == $json['status'] ) { $status_message = __( 'Transcoding: Your video is being transcoded to optimize playback across all devices. This usually completes shortly. You can safely save your changes in the meantime — the video will appear on the front-end once transcoding finishes.', 'all-in-one-video-gallery' ); } if ( 4 == $json['status'] ) { $status_message = __( 'Congrats! Your video is ready and available for streaming.', 'all-in-one-video-gallery' ); } $duration = aiovg_convert_seconds_to_human_time( $json['length'] ); // Build the final response $response = array( 'video_id' => $video_id, 'library_id' => $library_id, 'status' => $json['status'], 'message' => $status_message, 'duration' => $duration ); wp_send_json_success( $response ); // Send successful JSON response } /** * Delete a Bunny Stream video entry. * * @since 4.2.2 */ public function ajax_callback_delete_bunny_stream_video() { // Verify nonce check_ajax_referer( 'aiovg_ajax_nonce', 'security' ); // Ensure user is logged in if ( ! is_user_logged_in() ) { wp_send_json_error( __( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ); } // Verify user capability $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0; if ( $post_id > 0 ) { $post = get_post( $post_id ); if ( ! $post || 'aiovg_videos' !== $post->post_type ) { wp_send_json_error( __( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ); } if ( ! aiovg_current_user_can( 'delete_aiovg_video', $post_id ) ) { wp_send_json_error( __( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ); } } else { // If no post exists yet, this is a cancel-upload cleanup if ( ! aiovg_current_user_can( 'edit_aiovg_videos' ) ) { wp_send_json_error( __( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ); } } // Vars $response = array(); $video_id = isset( $_POST['video_id'] ) ? sanitize_text_field( $_POST['video_id'] ) : ''; if ( empty( $video_id ) ) { $response['error'] = __( 'Invalid Video ID.', 'all-in-one-video-gallery' ); wp_send_json_error( $response ); } $this->delete_bunny_stream_video( $video_id ); wp_send_json_success(); // Send successful JSON response } /** * Deletes a video from Bunny Stream CDN. * * @since 4.2.0 * @param string $video_id Bunny Stream Video Library ID. * @return bool True on success, false on failure. */ public function delete_bunny_stream_video( $video_id ) { $settings = aiovg_get_option( 'aiovg_bunny_stream_settings' ); if ( empty( $settings['api_key'] ) || empty( $settings['library_id'] ) ) { return false; } // Sanitize and prepare API credentials $api_key = sanitize_text_field( $settings['api_key'] ); $video_id = sanitize_text_field( $video_id ); $library_id = intval( $settings['library_id'] ); // Build the Bunny Stream API URL for deleting the video $url = "https://video.bunnycdn.com/library/{$library_id}/videos/{$video_id}"; // Prepare the request headers and options $args = array( 'method' => 'DELETE', 'headers' => array( 'Accept' => 'application/json', 'AccessKey' => $api_key, ), 'timeout' => 15, ); // Execute the DELETE request $response = wp_remote_request( $url, $args ); // Handle any request errors if ( is_wp_error( $response ) ) { return false; } // Check for successful HTTP response code return wp_remote_retrieve_response_code( $response ) === 200; } /** * Filters the video sources to use Bunny Stream HLS when available. * * @since 4.2.0 * @param array $sources The original video sources. * @param array $settings Player settings including post ID and post type. * @return array Modified video sources with Bunny Stream HLS if applicable. */ public function filter_player_sources( $sources, $settings = array() ) { // Ensure the MP4 source is present and has a valid URL if ( ! isset( $sources['mp4'] ) || empty( $sources['mp4']['src'] ) ) { return $sources; } // Ensure a valid post ID is provided if ( ! isset( $settings['post_id'] ) || empty( $settings['post_id'] ) ) { return $sources; } $post_id = $settings['post_id']; // Only proceed if the post type is set and matches 'aiovg_videos' if ( ! isset( $settings['post_type'] ) || 'aiovg_videos' != $settings['post_type'] ) { return $sources; } // When playing a trailer, check against the trailer's Bunny Stream GUID instead. $meta_key = ! empty( $settings['is_trailer'] ) ? 'trailer_bunny_stream_video_id' : 'bunny_stream_video_id'; $video_id = get_post_meta( $post_id, $meta_key, true ); if ( empty( $video_id ) ) { return $sources; } // Ensure the video URL belongs to the Bunny video before signing if ( strpos( $sources['mp4']['src'], '/' . $video_id . '/' ) === false ) { return $sources; } // Replace sources with HLS URL from Bunny Stream $hls_url = aiovg_get_bunny_stream_signed_url( $sources['mp4']['src'], $video_id ); $sources = array( 'hls' => array( 'type' => 'application/x-mpegurl', 'src' => $hls_url ) ); return $sources; } /** * Filters the image URL to return a signed Bunny Stream URL if applicable. * * @since 4.2.0 * @param array $image_data The image data array with 'src'. * @param int $post_id The ID of the post the image is related to. * @param string $size The requested image size. * @param string $object_type The type of object, either "post" or "term". * @return array The filtered image data. */ function filter_image_url( $image_data, $post_id, $size, $object_type ) { // If image src is missing or empty, return as-is if ( ! isset( $image_data['src'] ) || empty( $image_data['src'] ) ) { return $image_data; } // Only proceed for 'post' object type if ( 'post' !== $object_type ) { return $image_data; } // Get Bunny Stream video ID from post meta $video_id = get_post_meta( $post_id, 'bunny_stream_video_id', true ); if ( empty( $video_id ) ) { return $image_data; } // Ensure the image URL belongs to the Bunny video before signing if ( strpos( $image_data['src'], '/' . $video_id . '/' ) === false ) { return $image_data; } // Generate a signed Bunny Stream image URL $signed_url = aiovg_get_bunny_stream_signed_url( $image_data['src'], $video_id ); $image_data['src'] = $signed_url; return $image_data; } }