PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.9.2
Jetpack – WP Security, Backup, Speed, & Growth v15.9.2
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / extensions / blocks / podcast-player / podcast-player.php
jetpack / extensions / blocks / podcast-player Last commit date
templates 9 months ago podcast-player.php 3 months ago
podcast-player.php
361 lines
1 <?php
2 /**
3 * Podcast Player Block.
4 *
5 * @since 8.4.0
6 *
7 * @package automattic/jetpack
8 */
9
10 namespace Automattic\Jetpack\Extensions\Podcast_Player;
11
12 use Automattic\Jetpack\Blocks;
13 use Automattic\Jetpack\Status\Request;
14 use Jetpack_Gutenberg;
15 use Jetpack_Podcast_Helper;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit( 0 );
19 }
20
21 if ( ! class_exists( 'Jetpack_Podcast_Helper' ) ) {
22 require_once JETPACK__PLUGIN_DIR . '/_inc/lib/class-jetpack-podcast-helper.php';
23 }
24
25 /**
26 * Registers the block for use in Gutenberg. This is done via an action so that
27 * we can disable registration if we need to.
28 */
29 function register_block() {
30 Blocks::jetpack_register_block(
31 __DIR__,
32 array(
33 'render_callback' => __NAMESPACE__ . '\render_block',
34 // Since Gutenberg #31873.
35 'style' => 'wp-mediaelement',
36 'render_email_callback' => __NAMESPACE__ . '\render_email',
37 )
38 );
39 }
40 add_action( 'init', __NAMESPACE__ . '\register_block' );
41
42 /**
43 * Returns the error message wrapped in HTML if current user
44 * has the capability to edit the post. Public visitors will
45 * never see errors.
46 *
47 * @param string $message The error message to display.
48 * @return string
49 */
50 function render_error( $message ) {
51 // Suppress errors for users unable to address them.
52 if ( ! current_user_can( 'edit_posts' ) ) {
53 return '';
54 }
55 return '<p>' . esc_html( $message ) . '</p>';
56 }
57
58 /**
59 * Podcast Player block registration/dependency declaration.
60 *
61 * @param array $attributes Array containing the Podcast Player block attributes.
62 * @param string $content Fallback content - a direct link to RSS, as rendered by save.js.
63 * @return string
64 */
65 function render_block( $attributes, $content ) {
66 // Don't render an interactive version of the block outside the frontend context.
67 if ( ! Request::is_frontend() ) {
68 return $content;
69 }
70
71 // Test for empty URLS.
72 if ( empty( $attributes['url'] ) ) {
73 return render_error( __( 'No Podcast URL provided. Please enter a valid Podcast RSS feed URL.', 'jetpack' ) );
74 }
75
76 // Test for invalid URLs.
77 if ( ! wp_http_validate_url( $attributes['url'] ) ) {
78 return render_error( __( 'Your podcast URL is invalid and couldn\'t be embedded. Please double check your URL.', 'jetpack' ) );
79 }
80
81 if ( ! empty( $attributes['selectedEpisodes'] ) ) {
82 $guids = array_map(
83 function ( $episode ) {
84 return $episode['guid'];
85 },
86 $attributes['selectedEpisodes']
87 );
88 $player_args = array( 'guids' => $guids );
89 } else {
90 $player_args = array();
91 }
92
93 // Sanitize the URL.
94 $attributes['url'] = esc_url_raw( $attributes['url'] );
95 $player_data = ( new Jetpack_Podcast_Helper( $attributes['url'] ) )->get_player_data( $player_args );
96
97 if ( is_wp_error( $player_data ) ) {
98 return render_error( $player_data->get_error_message() );
99 }
100
101 return render_player( $player_data, $attributes );
102 }
103
104 /**
105 * Renders the HTML for the Podcast player and tracklist.
106 *
107 * @param array $player_data The player data details.
108 * @param array $attributes Array containing the Podcast Player block attributes.
109 * @return string The HTML for the podcast player.
110 */
111 function render_player( $player_data, $attributes ) {
112 // If there are no tracks (it is possible) then display appropriate user facing error message.
113 if ( empty( $player_data['tracks'] ) ) {
114 return render_error( __( 'No tracks available to play.', 'jetpack' ) );
115 }
116
117 if ( is_wp_error( $player_data['tracks'] ) ) {
118 return render_error( $player_data['tracks']->get_error_message() );
119 }
120
121 // Only use the amount of tracks requested.
122 $player_data['tracks'] = array_slice(
123 $player_data['tracks'],
124 0,
125 absint( $attributes['itemsToShow'] )
126 );
127
128 // Generate a unique id for the block instance.
129 $instance_id = wp_unique_id( 'jetpack-podcast-player-block-' . get_the_ID() . '-' );
130 $player_data['playerId'] = $instance_id;
131
132 // Generate object to be used as props for PodcastPlayer.
133 $player_props = array_merge(
134 // Add all attributes.
135 array( 'attributes' => $attributes ),
136 // Add all player data.
137 $player_data
138 );
139
140 $primary_colors = get_colors( 'primary', $attributes, 'color' );
141 $secondary_colors = get_colors( 'secondary', $attributes, 'color' );
142 $background_colors = get_colors( 'background', $attributes, 'background-color' );
143
144 $player_classes_name = trim( "{$secondary_colors['class']} {$background_colors['class']}" );
145 $player_inline_style = trim( "{$secondary_colors['style']} {$background_colors['style']}" );
146 $player_inline_style .= get_css_vars( $attributes );
147 $wrapper_attributes = \WP_Block_Supports::get_instance()->apply_block_supports();
148 $block_classname = Blocks::classes( Blocks::get_block_feature( __DIR__ ), $attributes, array( 'is-default' ) );
149 $is_amp = Blocks::is_amp_request();
150
151 ob_start();
152 ?>
153 <div class="<?php echo esc_attr( $block_classname ); ?>"<?php echo ! empty( $wrapper_attributes['style'] ) ? ' style="' . esc_attr( $wrapper_attributes['style'] ) . '"' : ''; ?> id="<?php echo esc_attr( $instance_id ); ?>">
154 <section
155 class="jetpack-podcast-player <?php echo esc_attr( $player_classes_name ); ?>"
156 style="<?php echo esc_attr( $player_inline_style ); ?>"
157 >
158 <?php
159 render(
160 'podcast-header',
161 array_merge(
162 $player_props,
163 array(
164 'primary_colors' => $primary_colors,
165 'player_id' => $player_data['playerId'],
166 )
167 )
168 );
169 ?>
170 <?php if ( count( $player_data['tracks'] ) > 1 ) : ?>
171 <ol class="jetpack-podcast-player__tracks">
172 <?php foreach ( $player_data['tracks'] as $track_index => $attachment ) : ?>
173 <?php
174 render(
175 'playlist-track',
176 array(
177 'is_active' => 0 === $track_index,
178 'attachment' => $attachment,
179 'primary_colors' => $primary_colors,
180 'secondary_colors' => $secondary_colors,
181 )
182 );
183 ?>
184 <?php endforeach; ?>
185 </ol>
186 <?php endif; ?>
187 </section>
188 <?php if ( ! $is_amp ) : ?>
189 <script type="application/json"><?php echo wp_json_encode( $player_props, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?></script>
190 <?php endif; ?>
191 </div>
192 <?php
193 /**
194 * Enqueue necessary scripts and styles.
195 */
196 if ( ! $is_amp ) {
197 wp_enqueue_style( 'wp-mediaelement' );
198 }
199 Jetpack_Gutenberg::load_assets_as_required( __DIR__, array( 'mediaelement' ) );
200
201 return ob_get_clean();
202 }
203
204 /**
205 * Given the color name, block attributes and the CSS property,
206 * the function will return an array with the `class` and `style`
207 * HTML attributes to be used straight in the markup.
208 *
209 * @example
210 * $color = get_colors( 'secondary', $attributes, 'border-color'
211 * => array( 'class' => 'has-secondary', 'style' => 'border-color: #333' )
212 *
213 * @param string $name Color attribute name, for instance `primary`, `secondary`, ...
214 * @param array $attrs Block attributes.
215 * @param string $property Color CSS property, fo instance `color`, `background-color`, ...
216 * @return array Colors array.
217 */
218 function get_colors( $name, $attrs, $property ) {
219 $attr_color = "{$name}Color";
220 $attr_custom = 'custom' . ucfirst( $attr_color );
221
222 $color = $attrs[ $attr_color ] ?? null;
223 $custom_color = $attrs[ $attr_custom ] ?? null;
224
225 $colors = array(
226 'class' => '',
227 'style' => '',
228 );
229
230 if ( $color || $custom_color ) {
231 $colors['class'] .= "has-{$name}";
232
233 if ( $color ) {
234 $colors['class'] .= " has-{$color}-{$property}";
235 } elseif ( $custom_color ) {
236 $colors['style'] .= "{$property}: {$custom_color};";
237 }
238 }
239
240 return $colors;
241 }
242
243 /**
244 * It generates a string with CSS variables according to the
245 * block colors, prefixing each one with `--jetpack-podcast-player'.
246 *
247 * @param array $attrs Podcast Block attributes object.
248 * @return string CSS variables depending on block colors.
249 */
250 function get_css_vars( $attrs ) {
251 $colors_name = array( 'primary', 'secondary', 'background' );
252
253 $inline_style = '';
254 foreach ( $colors_name as $color ) {
255 $hex_color = 'hex' . ucfirst( $color ) . 'Color';
256 if ( ! empty( $attrs[ $hex_color ] ) ) {
257 $inline_style .= " --jetpack-podcast-player-{$color}: {$attrs[ $hex_color ]};";
258 }
259 }
260 return $inline_style;
261 }
262
263 /**
264 * Render the given template in server-side.
265 * Important note:
266 * The $template_props array will be extracted.
267 * This means it will create a var for each array item.
268 * Keep it mind when using this param to pass
269 * properties to the template.
270 *
271 * @html-template-var array $template_props
272 *
273 * @param string $name Template name, available in `./templates` folder.
274 * @param array $template_props Template properties. Optional.
275 * @param bool $print Render template. True as default.
276 * @return string|null HTML markup or null.
277 */
278 function render( $name, $template_props = array(), $print = true ) {
279 if ( ! strpos( $name, '.php' ) ) {
280 $name .= '.php';
281 }
282
283 $template_path = __DIR__ . '/templates/' . $name;
284
285 if ( ! file_exists( $template_path ) ) {
286 return '';
287 }
288
289 if ( $print ) {
290 include $template_path;
291 } else {
292 ob_start();
293 include $template_path;
294 $markup = ob_get_contents();
295 ob_end_clean();
296
297 return $markup;
298 }
299 }
300
301 /**
302 * Render podcast player block for email.
303 *
304 * @since 15.0
305 *
306 * @param string $block_content The original block HTML content.
307 * @param array $parsed_block The parsed block data including attributes.
308 * @param object $rendering_context Email rendering context.
309 *
310 * @return string
311 */
312 function render_email( $block_content, array $parsed_block, $rendering_context ) {
313 // Validate input parameters and required dependencies
314 if ( ! isset( $parsed_block['attrs'] ) || ! is_array( $parsed_block['attrs'] ) ||
315 ! class_exists( '\Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks\Audio' ) ) {
316 return '';
317 }
318
319 $attr = $parsed_block['attrs'];
320
321 // Check if we have a valid podcast URL
322 if ( empty( $attr['url'] ) || ! wp_http_validate_url( $attr['url'] ) ) {
323 return '';
324 }
325
326 // Link to the post containing the podcast player for better UX
327 // Users can see the full context and interact with the full player on the site
328 $post_url = get_the_permalink();
329
330 if ( empty( $post_url ) ) {
331 return '';
332 }
333
334 // Build block content HTML with audio tag that the audio renderer expects
335 // Note: The audio renderer extracts the URL from the src attribute and uses it as a link,
336 // not as an actual audio source. While semantically incorrect to use a post URL in an
337 // audio src attribute, this is the expected format for the WooCommerce audio renderer.
338 // The renderer will create a clickable link to the post, not attempt to play audio.
339 $escaped_post_url = esc_url( $post_url );
340 $block_content_html = sprintf( '<audio src="%s"></audio>', $escaped_post_url );
341
342 // Create a mock parsed block that WooCommerce's audio renderer can handle
343 $mock_parsed_block = array(
344 'attrs' => array(
345 'src' => $escaped_post_url,
346 'label' => __( 'Listen to the podcast', 'jetpack' ),
347 ),
348 );
349
350 // Preserve email_attrs if present (used for spacing)
351 if ( ! empty( $parsed_block['email_attrs'] ) ) {
352 $mock_parsed_block['email_attrs'] = $parsed_block['email_attrs'];
353 }
354
355 // Use WooCommerce's core audio renderer
356 $woo_audio_renderer = new \Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks\Audio();
357
358 return $woo_audio_renderer->render( $block_content_html, $mock_parsed_block, $rendering_context );
359 }
360
361