array( 'local_video' => __( 'WordPress video file', '7sgaleri' ), 'youtube' => __( 'YouTube', '7sgaleri' ), 'vimeo' => __( 'Vimeo', '7sgaleri' ), 'external_video'=> __( 'External video link', '7sgaleri' ), ), 'audio' => array( 'local_audio' => __( 'WordPress audio file', '7sgaleri' ), 'spotify' => __( 'Spotify', '7sgaleri' ), 'apple_music' => __( 'Apple Music', '7sgaleri' ), 'soundcloud' => __( 'SoundCloud', '7sgaleri' ), 'deezer' => __( 'Deezer', '7sgaleri' ), 'youtube_music' => __( 'YouTube Music', '7sgaleri' ), 'external_audio'=> __( 'External audio link', '7sgaleri' ), ), ); } public static function source_label( string $source ): string { foreach ( self::sources() as $group ) { if ( isset( $group[ $source ] ) ) { return $group[ $source ]; } } return $source ?: '—'; } public static function render( WP_Post $post ): void { wp_nonce_field( 'sg7g_save_media', 'sg7g_media_nonce' ); $type = (string) get_post_meta( $post->ID, '_sg7g_media_type', true ); $type = in_array( $type, array( 'video', 'audio' ), true ) ? $type : 'video'; $sources = self::sources(); $source = (string) get_post_meta( $post->ID, '_sg7g_source', true ); if ( ! isset( $sources[ $type ][ $source ] ) ) { $source = 'video' === $type ? 'youtube' : 'spotify'; } $url = get_post_meta( $post->ID, '_sg7g_source_url', true ); $file_id = absint( get_post_meta( $post->ID, '_sg7g_file_id', true ) ); $file_url = $file_id ? wp_get_attachment_url( $file_id ) : ''; ?>

get_error_message() ); } else { delete_post_meta( $post_id, '_sg7g_source_error' ); } $duplicate_id = self::find_duplicate_source( $post_id, $url, $file_id, $source ); if ( $duplicate_id ) { update_post_meta( $post_id, '_sg7g_duplicate_source_id', $duplicate_id ); } else { delete_post_meta( $post_id, '_sg7g_duplicate_source_id' ); } } private static function find_duplicate_source( int $post_id, string $url, int $file_id, string $source ): int { $meta_query = array(); if ( in_array( $source, array( 'local_video', 'local_audio' ), true ) && $file_id > 0 ) { $meta_query[] = array( 'key' => '_sg7g_file_id', 'value' => $file_id, 'type' => 'NUMERIC', ); } elseif ( '' !== $url ) { $meta_query[] = array( 'key' => '_sg7g_source_url', 'value' => $url, ); } if ( empty( $meta_query ) ) { return 0; } $matches = get_posts( array( 'post_type' => 'sg7g_media', 'post_status' => array( 'publish', 'draft', 'pending', 'private', 'future' ), 'posts_per_page' => 1, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Single-ID exclusion for duplicate detection. 'post__not_in' => array( $post_id ), 'fields' => 'ids', 'orderby' => 'ID', 'order' => 'ASC', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Required duplicate lookup, limited to one result. 'meta_query' => $meta_query, 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ) ); return empty( $matches ) ? 0 : absint( $matches[0] ); } /** * Detect source and media type from a public URL. * * @return array{type:string,source:string}|array{} */ public static function detect_source( string $url ): array { $url = html_entity_decode( $url, ENT_QUOTES, 'UTF-8' ); $host = strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) ); $path = strtolower( (string) wp_parse_url( $url, PHP_URL_PATH ) ); $host = preg_replace( '/^www\./', '', $host ); if ( ! $host ) { return array(); } if ( 'music.youtube.com' === $host ) { return array( 'type' => 'audio', 'source' => 'youtube_music' ); } if ( in_array( $host, array( 'youtube.com', 'm.youtube.com', 'youtu.be', 'youtube-nocookie.com' ), true ) || str_ends_with( $host, '.youtube.com' ) ) { return array( 'type' => 'video', 'source' => 'youtube' ); } if ( 'vimeo.com' === $host || str_ends_with( $host, '.vimeo.com' ) ) { return array( 'type' => 'video', 'source' => 'vimeo' ); } if ( 'open.spotify.com' === $host ) { return array( 'type' => 'audio', 'source' => 'spotify' ); } if ( 'music.apple.com' === $host || 'embed.music.apple.com' === $host ) { return array( 'type' => 'audio', 'source' => 'apple_music' ); } if ( 'soundcloud.com' === $host || str_ends_with( $host, '.soundcloud.com' ) ) { return array( 'type' => 'audio', 'source' => 'soundcloud' ); } if ( 'deezer.com' === $host || str_ends_with( $host, '.deezer.com' ) ) { return array( 'type' => 'audio', 'source' => 'deezer' ); } $extension = strtolower( pathinfo( $path, PATHINFO_EXTENSION ) ); if ( in_array( $extension, array( 'mp4', 'webm', 'ogv', 'm3u8' ), true ) ) { return array( 'type' => 'video', 'source' => 'external_video' ); } if ( in_array( $extension, array( 'mp3', 'm4a', 'wav', 'ogg', 'oga', 'aac' ), true ) ) { return array( 'type' => 'audio', 'source' => 'external_audio' ); } return array(); } /** * Validate the selected local file or platform URL without blocking publication. * * @return true|WP_Error */ public static function validate_source( string $type, string $source, string $url, int $file_id ) { if ( in_array( $source, array( 'local_video', 'local_audio' ), true ) ) { if ( ! $file_id || 'attachment' !== get_post_type( $file_id ) ) { return new WP_Error( 'sg7g_missing_file', __( 'No file has been selected from the Media Library.', '7sgaleri' ) ); } $mime = (string) get_post_mime_type( $file_id ); $expected = 'local_audio' === $source ? 'audio/' : 'video/'; if ( ! str_starts_with( $mime, $expected ) ) { return new WP_Error( 'sg7g_wrong_file', __( 'The selected file type does not match the content type.', '7sgaleri' ) ); } return true; } $url = html_entity_decode( $url, ENT_QUOTES, 'UTF-8' ); if ( ! $url || ! wp_http_validate_url( $url ) ) { return new WP_Error( 'sg7g_invalid_url', __( 'No valid source link was entered.', '7sgaleri' ) ); } $detected = self::detect_source( $url ); if ( ! $detected ) { return new WP_Error( 'sg7g_unsupported_url', __( 'The link does not belong to a supported platform or direct media file.', '7sgaleri' ) ); } if ( $detected['type'] !== $type || $detected['source'] !== $source ) { return new WP_Error( 'sg7g_source_mismatch', __( 'The link does not match the selected content type or source.', '7sgaleri' ) ); } $path = trim( (string) wp_parse_url( $url, PHP_URL_PATH ), '/' ); if ( in_array( $source, array( 'youtube', 'youtube_music' ), true ) && ! self::youtube_id( $url ) && ! self::youtube_playlist_id( $url ) ) { return new WP_Error( 'sg7g_youtube_url', __( 'No valid video or playlist ID was found in the YouTube link.', '7sgaleri' ) ); } if ( 'vimeo' === $source && ! self::vimeo_id( $url ) ) { return new WP_Error( 'sg7g_vimeo_url', __( 'Enter a valid Vimeo video link.', '7sgaleri' ) ); } if ( 'spotify' === $source ) { $segments = self::normalized_segments( $path ); if ( isset( $segments[0] ) && 'embed' === strtolower( $segments[0] ) ) { array_shift( $segments ); } if ( count( $segments ) < 2 || ! in_array( $segments[0], array( 'track', 'album', 'playlist', 'episode', 'show', 'artist' ), true ) ) { return new WP_Error( 'sg7g_spotify_url', __( 'Enter a valid Spotify track, album, playlist, artist, or podcast link.', '7sgaleri' ) ); } } if ( 'apple_music' === $source && '' === $path ) { return new WP_Error( 'sg7g_apple_url', __( 'Enter a valid Apple Music content link.', '7sgaleri' ) ); } if ( 'soundcloud' === $source && '' === $path ) { return new WP_Error( 'sg7g_soundcloud_url', __( 'Enter a valid SoundCloud content link.', '7sgaleri' ) ); } if ( 'deezer' === $source ) { $segments = self::normalized_segments( $path ); if ( isset( $segments[0], $segments[1] ) && 'widget' === $segments[0] && 'auto' === $segments[1] ) { $segments = array_slice( $segments, 2 ); } if ( count( $segments ) < 2 || ! in_array( $segments[0], array( 'track', 'album', 'playlist', 'artist', 'show', 'episode' ), true ) ) { return new WP_Error( 'sg7g_deezer_url', __( 'Enter a valid Deezer content link.', '7sgaleri' ) ); } } return true; } private static function normalized_segments( string $path ): array { $segments = array_values( array_filter( explode( '/', trim( $path, '/' ) ) ) ); if ( isset( $segments[0] ) && ( preg_match( '/^intl-[a-z]{2}$/i', $segments[0] ) || preg_match( '/^[a-z]{2}(?:-[a-z]{2})?$/i', $segments[0] ) ) ) { array_shift( $segments ); } return $segments; } public static function youtube_id( string $url ): string { $host = strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) ); $path = trim( (string) wp_parse_url( $url, PHP_URL_PATH ), '/' ); parse_str( (string) wp_parse_url( $url, PHP_URL_QUERY ), $query ); $candidate = ''; if ( str_contains( $host, 'youtu.be' ) ) { $candidate = (string) strtok( $path, '/' ); } elseif ( isset( $query['v'] ) ) { $candidate = (string) $query['v']; } elseif ( preg_match( '~(?:embed|shorts|live)/([^/?]+)~', $path, $matches ) ) { $candidate = $matches[1]; } $candidate = sanitize_text_field( $candidate ); return preg_match( '/^[A-Za-z0-9_-]{6,20}$/', $candidate ) ? $candidate : ''; } public static function youtube_playlist_id( string $url ): string { parse_str( (string) wp_parse_url( $url, PHP_URL_QUERY ), $query ); $candidate = isset( $query['list'] ) ? sanitize_text_field( (string) $query['list'] ) : ''; return preg_match( '/^[A-Za-z0-9_-]{10,80}$/', $candidate ) ? $candidate : ''; } public static function vimeo_id( string $url ): string { $host = strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) ); $path = trim( (string) wp_parse_url( $url, PHP_URL_PATH ), '/' ); if ( ! str_contains( $host, 'vimeo.com' ) ) { return ''; } $segments = array_values( array_filter( explode( '/', $path ) ) ); $video_id = ''; foreach ( $segments as $index => $segment ) { if ( 'video' === strtolower( $segment ) && isset( $segments[ $index + 1 ] ) && ctype_digit( $segments[ $index + 1 ] ) ) { $video_id = $segments[ $index + 1 ]; } elseif ( ctype_digit( $segment ) ) { $video_id = $segment; } } return sanitize_text_field( $video_id ); } /** * Return the privacy hash used by Vimeo unlisted links when available. */ public static function vimeo_hash( string $url ): string { $path = trim( (string) wp_parse_url( $url, PHP_URL_PATH ), '/' ); parse_str( (string) wp_parse_url( $url, PHP_URL_QUERY ), $query ); if ( isset( $query['h'] ) ) { $candidate = sanitize_text_field( (string) $query['h'] ); return preg_match( '/^[A-Za-z0-9]+$/', $candidate ) ? $candidate : ''; } $video_id = self::vimeo_id( $url ); $segments = array_values( array_filter( explode( '/', $path ) ) ); $id_index = array_search( $video_id, $segments, true ); if ( false !== $id_index && isset( $segments[ $id_index + 1 ] ) ) { $candidate = sanitize_text_field( (string) $segments[ $id_index + 1 ] ); if ( preg_match( '/^[A-Za-z0-9]+$/', $candidate ) && ! ctype_digit( $candidate ) && 'video' !== strtolower( $candidate ) ) { return $candidate; } } return ''; } public static function source_notice(): void { $screen = get_current_screen(); if ( ! $screen || 'sg7g_media' !== $screen->post_type || 'post' !== $screen->base ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only post ID on the authorized edit screen. $post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0; if ( ! $post_id ) { return; } if ( 0 === (int) get_post_meta( $post_id, '_sg7g_source_valid', true ) ) { $message = (string) get_post_meta( $post_id, '_sg7g_source_error', true ); if ( '' !== $message ) { printf( '

%1$s %2$s

', esc_html__( 'Media source warning:', '7sgaleri' ), esc_html( $message ) ); } } $duplicate_id = absint( get_post_meta( $post_id, '_sg7g_duplicate_source_id', true ) ); if ( $duplicate_id && 'sg7g_media' === get_post_type( $duplicate_id ) && 'trash' !== get_post_status( $duplicate_id ) ) { $edit_url = get_edit_post_link( $duplicate_id, 'raw' ); printf( '

%1$s %2$s %3$s

', esc_html__( 'Duplicate media source:', '7sgaleri' ), esc_html__( 'Another item using the same media source was found.', '7sgaleri' ), $edit_url ? '' . esc_html__( 'Review content', '7sgaleri' ) . '' : '' ); } } }