Completed
Push — renovate/major-wordpress-monor... ( fa07ab...6cd1fe )
by
unknown
93:31 queued 87:25
created

podcast-player.php ➔ render_player()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 2
dl 0
loc 46
rs 9.1781
c 0
b 0
f 0
1
<?php
2
/**
3
 * Podcast Player Block.
4
 *
5
 * @since 8.4.0
6
 *
7
 * @package Jetpack
8
 */
9
10
namespace Jetpack\Podcast_Player_Block;
11
12
use WP_Error;
13
use Jetpack_Gutenberg;
14
15
const FEATURE_NAME = 'podcast-player';
16
const BLOCK_NAME   = 'jetpack/' . FEATURE_NAME;
17
18
/**
19
 * Registers the block for use in Gutenberg
20
 * This is done via an action so that we can disable
21
 * registration if we need to.
22
 */
23
function register_block() {
24
	jetpack_register_block(
25
		BLOCK_NAME,
26
		array(
27
			'attributes'      => array(
28
				'url'                    => array(
29
					'type' => 'url',
30
				),
31
				'itemsToShow'            => array(
32
					'type'    => 'integer',
33
					'default' => 5,
34
				),
35
				'showCoverArt'           => array(
36
					'type'    => 'boolean',
37
					'default' => true,
38
				),
39
				'showEpisodeDescription' => array(
40
					'type'    => 'boolean',
41
					'default' => true,
42
				),
43
			),
44
			'render_callback' => __NAMESPACE__ . '\render_block',
45
		)
46
	);
47
}
48
add_action( 'init', __NAMESPACE__ . '\register_block' );
49
50
/**
51
 * Podcast Player block registration/dependency declaration.
52
 *
53
 * @param array $attributes Array containing the Podcast Player block attributes.
54
 * @return string
55
 */
56
function render_block( $attributes ) {
57
58
	// Test for empty URLS.
59
	if ( empty( $attributes['url'] ) ) {
60
		return '<p>' . esc_html__( 'No Podcast URL provided. Please enter a valid Podcast RSS feed URL.', 'jetpack' ) . '</p>';
61
	}
62
63
	// Test for invalid URLs.
64
	if ( ! wp_http_validate_url( $attributes['url'] ) ) {
65
		return '<p>' . esc_html__( 'Your podcast URL is invalid and couldn\'t be embedded. Please double check your URL.', 'jetpack' ) . '</p>';
66
	}
67
68
	// Sanitize the URL.
69
	$attributes['url'] = esc_url_raw( $attributes['url'] );
70
71
	$track_list = get_track_list( $attributes['url'], absint( $attributes['itemsToShow'] ) );
72
73
	if ( is_wp_error( $track_list ) ) {
74
		return '<p>' . esc_html( $track_list->get_error_message() ) . '</p>';
75
	}
76
77
	return render_player( $track_list, $attributes );
78
}
79
80
/**
81
 * Renders the HTML for the Podcast player and tracklist.
82
 *
83
 * @param array $track_list The list of podcast tracks.
84
 * @param array $attributes Array containing the Podcast Player block attributes.
85
 * @return string The HTML for the podcast player.
86
 */
87
function render_player( $track_list, $attributes ) {
88
	// If there are no tracks (it is possible) then display appropriate user facing error message.
89
	if ( empty( $track_list ) ) {
90
		return '<p>' . esc_html__( 'No tracks available to play.', 'jetpack' ) . '</p>';
91
	}
92
	$instance_id = wp_unique_id( 'podcast-player-block-' );
93
94
	$player_data = array(
95
		'tracks'     => $track_list,
96
		'attributes' => $attributes,
97
	);
98
99
	$block_classname = Jetpack_Gutenberg::block_classes( FEATURE_NAME, $attributes );
100
101
	ob_start();
102
	?>
103
	<div class="<?php echo esc_attr( $block_classname ); ?>" id="<?php echo esc_attr( $instance_id ); ?>">
104
		<ol class="podcast-player__episodes">
105
			<?php foreach ( $track_list as $attachment ) : ?>
106
			<li class="podcast-player__episode">
107
				<a
108
					class="podcast-player__episode-link"
109
					href="<?php echo esc_url( $attachment['link'] ); ?>"
110
					data-jetpack-podcast-audio="<?php echo esc_url( $attachment['src'] ); ?>"
111
					role="button"
112
					aria-pressed="false"
113
				>
114
					<span class="podcast-player__episode-status-icon"></span>
115
					<span class="podcast-player__episode-title"><?php echo esc_html( $attachment['title'] ); ?></span>
116
					<time class="podcast-player__episode-duration"><?php echo ( ! empty( $attachment['duration'] ) ? esc_html( $attachment['duration'] ) : '' ); ?></time>
117
				</a>
118
			</li>
119
			<?php endforeach; ?>
120
		</ol>
121
		<script type="application/json"><?php echo wp_json_encode( $player_data ); ?></script>
122
	</div>
123
	<script>window.jetpackPodcastPlayers=(window.jetpackPodcastPlayers||[]);window.jetpackPodcastPlayers.push( <?php echo wp_json_encode( $instance_id ); ?> );</script>
124
	<?php
125
	/**
126
	 * Enqueue necessary scripts and styles.
127
	 */
128
	wp_enqueue_style( 'mediaelement' );
129
	Jetpack_Gutenberg::load_assets_as_required( 'podcast-player', array( 'mediaelement' ) );
130
131
	return ob_get_clean();
132
}
133
134
/**
135
 * Gets a list of tracks for the supplied RSS feed.
136
 *
137
 * @param string $feed     The RSS feed to load and list tracks for.
138
 * @param int    $quantity Optional. The number of tracks to return.
139
 * @return array|WP_Error The feed's tracks or a error object.
140
 */
141
function get_track_list( $feed, $quantity = 10 ) {
142
	$rss = fetch_feed( $feed );
143
144
	if ( is_wp_error( $rss ) ) {
145
		return new WP_Error( 'invalid_url', __( 'Your podcast couldn\'t be embedded. Please double check your URL.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_url'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
146
	}
147
148
	if ( ! $rss->get_item_quantity() ) {
149
		return new WP_Error( 'no_tracks', __( 'Podcast audio RSS feed has no tracks.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_tracks'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
150
	}
151
152
	$track_list = array_map( __NAMESPACE__ . '\setup_tracks_callback', $rss->get_items( 0, $quantity ) );
153
154
	// Remove empty tracks.
155
	return \array_filter( $track_list );
156
}
157
158
/**
159
 * Prepares Episode data to be used with MediaElement.js.
160
 *
161
 * @param \SimplePie_Item $episode SimplePie_Item object, representing a podcast episode.
162
 * @return array
163
 */
164
function setup_tracks_callback( \SimplePie_Item $episode ) {
165
	$enclosure = get_audio_enclosure( $episode );
166
167
	// If there is no link return an empty array. We will filter out later.
168
	if ( empty( $enclosure->link ) ) {
169
		return array();
170
	}
171
172
	// Build track data.
173
	$track = array(
174
		'link'        => esc_url( $episode->get_link() ),
175
		'src'         => esc_url( $enclosure->link ),
176
		'type'        => esc_attr( $enclosure->type ),
177
		'description' => wp_kses_post( $episode->get_description() ),
178
	);
179
180
	$track['title'] = esc_html( trim( wp_strip_all_tags( $episode->get_title() ) ) );
181
182
	if ( empty( $track['title'] ) ) {
183
		$track['title'] = esc_html__( '(no title)', 'jetpack' );
184
	}
185
186
	if ( ! empty( $enclosure->duration ) ) {
187
		$track['duration'] = format_track_duration( $enclosure->duration );
188
	}
189
190
	return $track;
191
}
192
193
/**
194
 * Retrieves an audio enclosure.
195
 *
196
 * @param \SimplePie_Item $episode SimplePie_Item object, representing a podcast episode.
197
 * @return \SimplePie_Enclosure|null
198
 */
199
function get_audio_enclosure( \SimplePie_Item $episode ) {
200
	foreach ( (array) $episode->get_enclosures() as $enclosure ) {
201
		if ( 0 === strpos( $enclosure->type, 'audio/' ) ) {
202
			return $enclosure;
203
		}
204
	}
205
206
	// Default to empty SimplePie_Enclosure object.
207
	return $episode->get_enclosure();
208
}
209
210
/**
211
 * Returns the track duration as a formatted string.
212
 *
213
 * @param number $duration of the track in seconds.
214
 * @return string
215
 */
216
function format_track_duration( $duration ) {
217
	$format = $duration > HOUR_IN_SECONDS ? 'H:i:s' : 'i:s';
218
219
	return date_i18n( $format, $duration );
220
}
221