Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 91 additions & 2 deletions src/wp-includes/widgets/class-wp-widget-media-video.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public function __construct() {
'media_library_state_single' => __( 'Video Widget' ),
/* translators: %s: A list of valid video file extensions. */
'unsupported_file_type' => sprintf( __( 'Sorry, the video at the supplied URL cannot be loaded. Please check that the URL is for a supported video file (%s) or stream (e.g. YouTube and Vimeo).' ), '<code>.' . implode( '</code>, <code>.', wp_get_video_extensions() ) . '</code>' ),
'invalid_url' => __( 'Please enter a valid video URL. Supported formats include YouTube, Vimeo, or direct video file links.' ),
'youtube_error' => __( 'This YouTube video cannot be displayed. It may be private, deleted, or restricted in your region.' ),
'vimeo_error' => __( 'This Vimeo video cannot be displayed. It may be private, deleted, or require a password.' ),
'network_error' => __( 'Unable to load the video due to a network error. Please check your internet connection and try again.' ),
'file_not_found' => __( 'The video file could not be found. Please check the URL and make sure the file exists.' ),
)
);
}
Expand Down Expand Up @@ -126,13 +131,18 @@ public function render_media( $instance ) {
return;
}

if ( ! filter_var( $src, FILTER_VALIDATE_URL ) ) {
$this->render_error_message( 'invalid_url' );
return;
}

$youtube_pattern = '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#';
$vimeo_pattern = '#^https?://(.+\.)?vimeo\.com/.*#';

if ( $attachment || preg_match( $youtube_pattern, $src ) || preg_match( $vimeo_pattern, $src ) ) {
add_filter( 'wp_video_shortcode', array( $this, 'inject_video_max_width_style' ) );

echo wp_video_shortcode(
$video_html = wp_video_shortcode(
array_merge(
$instance,
compact( 'src' )
Expand All @@ -141,8 +151,47 @@ public function render_media( $instance ) {
);

remove_filter( 'wp_video_shortcode', array( $this, 'inject_video_max_width_style' ) );

if ( empty( $video_html ) || false !== strpos( $video_html, 'Sorry, this content isn\'t available right now' ) ) {
if ( preg_match( $youtube_pattern, $src ) ) {
$this->render_error_message( 'youtube_error' );
} elseif ( preg_match( $vimeo_pattern, $src ) ) {
$this->render_error_message( 'vimeo_error' );
} else {
$this->render_error_message( 'file_not_found' );
}
} else {
echo $video_html;
}
} else {
echo $this->inject_video_max_width_style( wp_oembed_get( $src ) );
$oembed_html = wp_oembed_get( $src );

if ( empty( $oembed_html ) ) {
$file_extension = pathinfo( parse_url( $src, PHP_URL_PATH ), PATHINFO_EXTENSION );
if ( in_array( strtolower( $file_extension ), wp_get_video_extensions(), true ) ) {
$this->render_error_message( 'file_not_found' );
} else {
$this->render_error_message( 'unsupported_file_type' );
}
} else {
echo $this->inject_video_max_width_style( $oembed_html );
}
}
}

/**
* Render error message using notice classes.
*
* @since 6.9.0
*
* @param string $error_type The error type key from l10n array.
*/
private function render_error_message( $error_type ) {
if ( isset( $this->l10n[ $error_type ] ) ) {
printf(
'<div class="notice notice-error notice-alt"><p>%s</p></div>',
wp_kses_post( $this->l10n[ $error_type ] )
);
}
}

Expand Down Expand Up @@ -247,6 +296,46 @@ public function render_control_template_scripts() {
)
);
?>
<# } else if ( data.error && 'invalid_url' === data.error ) { #>
<?php
wp_admin_notice(
$this->l10n['invalid_url'],
array(
'type' => 'error',
'additional_classes' => array( 'notice-alt' ),
)
);
?>
<# } else if ( data.error && 'youtube_error' === data.error ) { #>
<?php
wp_admin_notice(
$this->l10n['youtube_error'],
array(
'type' => 'error',
'additional_classes' => array( 'notice-alt' ),
)
);
?>
<# } else if ( data.error && 'vimeo_error' === data.error ) { #>
<?php
wp_admin_notice(
$this->l10n['vimeo_error'],
array(
'type' => 'error',
'additional_classes' => array( 'notice-alt' ),
)
);
?>
<# } else if ( data.error && 'file_not_found' === data.error ) { #>
<?php
wp_admin_notice(
$this->l10n['file_not_found'],
array(
'type' => 'error',
'additional_classes' => array( 'notice-alt' ),
)
);
?>
<# } else if ( data.error ) { #>
<?php
wp_admin_notice(
Expand Down
95 changes: 95 additions & 0 deletions tests/phpunit/tests/widgets/wpWidgetMediaVideo.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,43 @@ public function test_constructor() {
'missing_attachment',
'no_media_selected',
'add_media',
'invalid_url',
'youtube_error',
'vimeo_error',
'network_error',
'file_not_found',
),
array_keys( $widget->l10n )
);
}

/**
* Test enhanced error messages are properly defined.
*
* @covers WP_Widget_Media_Video::__construct
*/
public function test_enhanced_error_messages() {
$widget = new WP_Widget_Media_Video();

$required_error_messages = array(
'invalid_url',
'youtube_error',
'vimeo_error',
'file_not_found',
'network_error',
);

foreach ( $required_error_messages as $error_key ) {
$this->assertArrayHasKey( $error_key, $widget->l10n, "Error message '$error_key' should be defined" );
$this->assertNotEmpty( $widget->l10n[ $error_key ], "Error message '$error_key' should not be empty" );
$this->assertIsString( $widget->l10n[ $error_key ], "Error message '$error_key' should be a string" );
}

$this->assertStringNotContainsString( 'filter_var', $widget->l10n['invalid_url'] );
$this->assertStringNotContainsString( 'HTTP', $widget->l10n['youtube_error'] );
$this->assertStringNotContainsString( 'oEmbed', $widget->l10n['vimeo_error'] );
}

/**
* Test get_instance_schema method.
*
Expand Down Expand Up @@ -212,6 +244,69 @@ public function test_update() {
$this->assertSame( $result, $instance );
}

/**
* Test render_error_message method.
*
* @covers WP_Widget_Media_Video::render_error_message
*/
public function test_render_error_message() {
$widget = new WP_Widget_Media_Video();

ob_start();
$reflection = new ReflectionClass( $widget );
$method = $reflection->getMethod( 'render_error_message' );
$method->setAccessible( true );
$method->invoke( $widget, 'invalid_url' );
$output = ob_get_clean();

$this->assertStringContainsString( 'notice notice-error', $output );
$this->assertStringContainsString( $widget->l10n['invalid_url'], $output );

ob_start();
$method->invoke( $widget, 'nonexistent_error' );
$output = ob_get_clean();

$this->assertEmpty( $output );

$error_types = array( 'invalid_url', 'youtube_error', 'vimeo_error', 'file_not_found' );
foreach ( $error_types as $error_type ) {
ob_start();
$method->invoke( $widget, $error_type );
$output = ob_get_clean();

$this->assertStringContainsString( 'notice notice-error', $output, "Error type '$error_type' should render with notice classes" );
$this->assertStringContainsString( $widget->l10n[ $error_type ], $output, "Error type '$error_type' should contain the error message" );
}
}

/**
* Test render_media method with invalid URLs.
*
* @covers WP_Widget_Media_Video::render_media
*/
public function test_render_media_invalid_url() {
$widget = new WP_Widget_Media_Video();

ob_start();
$widget->render_media( array( 'url' => 'not-a-valid-url' ) );
$output = ob_get_clean();

$this->assertStringContainsString( 'notice notice-error', $output );
$this->assertStringContainsString( $widget->l10n['invalid_url'], $output );

ob_start();
$widget->render_media( array( 'url' => '' ) );
$output = ob_get_clean();

$this->assertEmpty( $output );

ob_start();
$widget->render_media( array( 'url' => 'https://www.youtube.com/watch?v=72xdCU__XCk' ) );
$output = ob_get_clean();

$this->assertStringNotContainsString( $widget->l10n['invalid_url'], $output );
}

/**
* Test render_media method.
*
Expand Down
Loading