PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 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 All 508 releases
← All changes | extensions/blocks/podcast-player/render.php +345 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,345 @@
1 +<?php
2 +/**
3 + * Podcast Player block render implementation.
4 + *
5 + * Loaded lazily from podcast-player.php only when the block is rendered, to keep
6 + * the render body out of the eager front-end PHP/opcache footprint.
7 + *
8 + * @package automattic/jetpack
9 + */
10 +
11 +namespace Automattic\Jetpack\Extensions\Podcast_Player;
12 +
13 +use Automattic\Jetpack\Blocks;
14 +use Automattic\Jetpack\Status\Request;
15 +use Jetpack_Gutenberg;
16 +use Jetpack_Podcast_Helper;
17 +
18 +if ( ! defined( 'ABSPATH' ) ) {
19 + exit( 0 );
20 +}
21 +
22 +if ( ! class_exists( 'Jetpack_Podcast_Helper' ) ) {
23 + require_once JETPACK__PLUGIN_DIR . '/_inc/lib/class-jetpack-podcast-helper.php';
24 +}
25 +
26 +/**
27 + * Returns the error message wrapped in HTML if current user
28 + * has the capability to edit the post. Public visitors will
29 + * never see errors.
30 + *
31 + * @param string $message The error message to display.
32 + * @return string
33 + */
34 +function render_error( $message ) {
35 + // Suppress errors for users unable to address them.
36 + if ( ! current_user_can( 'edit_posts' ) ) {
37 + return '';
38 + }
39 + return '<p>' . esc_html( $message ) . '</p>';
40 +}
41 +
42 +/**
43 + * Podcast Player block registration/dependency declaration.
44 + *
45 + * @param array $attributes Array containing the Podcast Player block attributes.
46 + * @param string $content Fallback content - a direct link to RSS, as rendered by save.js.
47 + * @return string
48 + */
49 +function render_block_implementation( $attributes, $content ) {
50 + // Don't render an interactive version of the block outside the frontend context.
51 + if ( ! Request::is_frontend() ) {
52 + return $content;
53 + }
54 +
55 + // Test for empty URLS.
56 + if ( empty( $attributes['url'] ) ) {
57 + return render_error( __( 'No Podcast URL provided. Please enter a valid Podcast RSS feed URL.', 'jetpack' ) );
58 + }
59 +
60 + // Test for invalid URLs.
61 + if ( ! wp_http_validate_url( $attributes['url'] ) ) {
62 + return render_error( __( 'Your podcast URL is invalid and couldn\'t be embedded. Please double check your URL.', 'jetpack' ) );
63 + }
64 +
65 + if ( ! empty( $attributes['selectedEpisodes'] ) ) {
66 + $guids = array_map(
67 + function ( $episode ) {
68 + return $episode['guid'];
69 + },
70 + $attributes['selectedEpisodes']
71 + );
72 + $player_args = array( 'guids' => $guids );
73 + } else {
74 + $player_args = array();
75 + }
76 +
77 + // Sanitize the URL.
78 + $attributes['url'] = esc_url_raw( $attributes['url'] );
79 + $player_data = ( new Jetpack_Podcast_Helper( $attributes['url'] ) )->get_player_data( $player_args );
80 +
81 + if ( is_wp_error( $player_data ) ) {
82 + return render_error( $player_data->get_error_message() );
83 + }
84 +
85 + return render_player( $player_data, $attributes );
86 +}
87 +
88 +/**
89 + * Renders the HTML for the Podcast player and tracklist.
90 + *
91 + * @param array $player_data The player data details.
92 + * @param array $attributes Array containing the Podcast Player block attributes.
93 + * @return string The HTML for the podcast player.
94 + */
95 +function render_player( $player_data, $attributes ) {
96 + // If there are no tracks (it is possible) then display appropriate user facing error message.
97 + if ( empty( $player_data['tracks'] ) ) {
98 + return render_error( __( 'No tracks available to play.', 'jetpack' ) );
99 + }
100 +
101 + if ( is_wp_error( $player_data['tracks'] ) ) {
102 + return render_error( $player_data['tracks']->get_error_message() );
103 + }
104 +
105 + // Only use the amount of tracks requested.
106 + $player_data['tracks'] = array_slice(
107 + $player_data['tracks'],
108 + 0,
109 + absint( $attributes['itemsToShow'] )
110 + );
111 +
112 + // Generate a unique id for the block instance.
113 + $instance_id = wp_unique_id( 'jetpack-podcast-player-block-' . get_the_ID() . '-' );
114 + $player_data['playerId'] = $instance_id;
115 +
116 + // Generate object to be used as props for PodcastPlayer.
117 + $player_props = array_merge(
118 + // Add all attributes.
119 + array( 'attributes' => $attributes ),
120 + // Add all player data.
121 + $player_data
122 + );
123 +
124 + $primary_colors = get_colors( 'primary', $attributes, 'color' );
125 + $secondary_colors = get_colors( 'secondary', $attributes, 'color' );
126 + $background_colors = get_colors( 'background', $attributes, 'background-color' );
127 +
128 + $player_classes_name = trim( "{$secondary_colors['class']} {$background_colors['class']}" );
129 + $player_inline_style = trim( "{$secondary_colors['style']} {$background_colors['style']}" );
130 + $player_inline_style .= get_css_vars( $attributes );
131 + $wrapper_attributes = \WP_Block_Supports::get_instance()->apply_block_supports();
132 + $block_classname = Blocks::classes( Blocks::get_block_feature( __DIR__ ), $attributes, array( 'is-default' ) );
133 + $is_amp = Blocks::is_amp_request();
134 +
135 + ob_start();
136 + ?>
137 + <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 ); ?>">
138 + <section
139 + class="jetpack-podcast-player <?php echo esc_attr( $player_classes_name ); ?>"
140 + style="<?php echo esc_attr( $player_inline_style ); ?>"
141 + >
142 + <?php
143 + render(
144 + 'podcast-header',
145 + array_merge(
146 + $player_props,
147 + array(
148 + 'primary_colors' => $primary_colors,
149 + 'player_id' => $player_data['playerId'],
150 + )
151 + )
152 + );
153 + ?>
154 + <?php if ( count( $player_data['tracks'] ) > 1 ) : ?>
155 + <ol class="jetpack-podcast-player__tracks">
156 + <?php foreach ( $player_data['tracks'] as $track_index => $attachment ) : ?>
157 + <?php
158 + render(
159 + 'playlist-track',
160 + array(
161 + 'is_active' => 0 === $track_index,
162 + 'attachment' => $attachment,
163 + 'primary_colors' => $primary_colors,
164 + 'secondary_colors' => $secondary_colors,
165 + )
166 + );
167 + ?>
168 + <?php endforeach; ?>
169 + </ol>
170 + <?php endif; ?>
171 + </section>
172 + <?php if ( ! $is_amp ) : ?>
173 + <script type="application/json"><?php echo wp_json_encode( $player_props, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?></script>
174 + <?php endif; ?>
175 + </div>
176 + <?php
177 + /**
178 + * Enqueue necessary scripts and styles.
179 + */
180 + if ( ! $is_amp ) {
181 + wp_enqueue_style( 'wp-mediaelement' );
182 + }
183 + Jetpack_Gutenberg::load_assets_as_required( __DIR__, array( 'mediaelement' ) );
184 +
185 + return ob_get_clean();
186 +}
187 +
188 +/**
189 + * Given the color name, block attributes and the CSS property,
190 + * the function will return an array with the `class` and `style`
191 + * HTML attributes to be used straight in the markup.
192 + *
193 + * @example
194 + * $color = get_colors( 'secondary', $attributes, 'border-color'
195 + * => array( 'class' => 'has-secondary', 'style' => 'border-color: #333' )
196 + *
197 + * @param string $name Color attribute name, for instance `primary`, `secondary`, ...
198 + * @param array $attrs Block attributes.
199 + * @param string $property Color CSS property, fo instance `color`, `background-color`, ...
200 + * @return array Colors array.
201 + */
202 +function get_colors( $name, $attrs, $property ) {
203 + $attr_color = "{$name}Color";
204 + $attr_custom = 'custom' . ucfirst( $attr_color );
205 +
206 + $color = $attrs[ $attr_color ] ?? null;
207 + $custom_color = $attrs[ $attr_custom ] ?? null;
208 +
209 + $colors = array(
210 + 'class' => '',
211 + 'style' => '',
212 + );
213 +
214 + if ( $color || $custom_color ) {
215 + $colors['class'] .= "has-{$name}";
216 +
217 + if ( $color ) {
218 + $colors['class'] .= " has-{$color}-{$property}";
219 + } elseif ( $custom_color ) {
220 + $colors['style'] .= "{$property}: {$custom_color};";
221 + }
222 + }
223 +
224 + return $colors;
225 +}
226 +
227 +/**
228 + * It generates a string with CSS variables according to the
229 + * block colors, prefixing each one with `--jetpack-podcast-player'.
230 + *
231 + * @param array $attrs Podcast Block attributes object.
232 + * @return string CSS variables depending on block colors.
233 + */
234 +function get_css_vars( $attrs ) {
235 + $colors_name = array( 'primary', 'secondary', 'background' );
236 +
237 + $inline_style = '';
238 + foreach ( $colors_name as $color ) {
239 + $hex_color = 'hex' . ucfirst( $color ) . 'Color';
240 + if ( ! empty( $attrs[ $hex_color ] ) ) {
241 + $inline_style .= " --jetpack-podcast-player-{$color}: {$attrs[ $hex_color ]};";
242 + }
243 + }
244 + return $inline_style;
245 +}
246 +
247 +/**
248 + * Render the given template in server-side.
249 + * Important note:
250 + * The $template_props array will be extracted.
251 + * This means it will create a var for each array item.
252 + * Keep it mind when using this param to pass
253 + * properties to the template.
254 + *
255 + * @html-template-var array $template_props
256 + *
257 + * @param string $name Template name, available in `./templates` folder.
258 + * @param array $template_props Template properties. Optional.
259 + * @param bool $print Render template. True as default.
260 + * @return string|null HTML markup or null.
261 + */
262 +function render( $name, $template_props = array(), $print = true ) {
263 + if ( ! strpos( $name, '.php' ) ) {
264 + $name .= '.php';
265 + }
266 +
267 + $template_path = __DIR__ . '/templates/' . $name;
268 +
269 + if ( ! file_exists( $template_path ) ) {
270 + return '';
271 + }
272 +
273 + if ( $print ) {
274 + include $template_path;
275 + } else {
276 + ob_start();
277 + include $template_path;
278 + $markup = ob_get_contents();
279 + ob_end_clean();
280 +
281 + return $markup;
282 + }
283 +}
284 +
285 +/**
286 + * Render podcast player block for email.
287 + *
288 + * @param string $block_content The original block HTML content.
289 + * @param array $parsed_block The parsed block data including attributes.
290 + * @param object $rendering_context Email rendering context.
291 + *
292 + * @return string
293 + */
294 +function render_email_implementation( $block_content, array $parsed_block, $rendering_context ) {
295 + // Validate input parameters and required dependencies
296 + if ( ! isset( $parsed_block['attrs'] ) || ! is_array( $parsed_block['attrs'] ) ||
297 + ! class_exists( '\Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks\Audio' ) ) {
298 + return '';
299 + }
300 +
301 + $attr = $parsed_block['attrs'];
302 +
303 + // Check if we have a valid podcast URL
304 + if ( empty( $attr['url'] ) || ! wp_http_validate_url( $attr['url'] ) ) {
305 + return '';
306 + }
307 +
308 + /*
309 + * Link to the post containing the podcast player for better UX
310 + * Users can see the full context and interact with the full player on the site
311 + */
312 + $post_url = get_the_permalink();
313 +
314 + if ( empty( $post_url ) ) {
315 + return '';
316 + }
317 +
318 + /*
319 + * Build block content HTML with audio tag that the audio renderer expects
320 + * Note: The audio renderer extracts the URL from the src attribute and uses it as a link,
321 + * not as an actual audio source. While semantically incorrect to use a post URL in an
322 + * audio src attribute, this is the expected format for the WooCommerce audio renderer.
323 + * The renderer will create a clickable link to the post, not attempt to play audio.
324 + */
325 + $escaped_post_url = esc_url( $post_url );
326 + $block_content_html = sprintf( '<audio src="%s"></audio>', $escaped_post_url );
327 +
328 + // Create a mock parsed block that WooCommerce's audio renderer can handle
329 + $mock_parsed_block = array(
330 + 'attrs' => array(
331 + 'src' => $escaped_post_url,
332 + 'label' => __( 'Listen to the podcast', 'jetpack' ),
333 + ),
334 + );
335 +
336 + // Preserve email_attrs if present (used for spacing)
337 + if ( ! empty( $parsed_block['email_attrs'] ) ) {
338 + $mock_parsed_block['email_attrs'] = $parsed_block['email_attrs'];
339 + }
340 +
341 + // Use WooCommerce's core audio renderer
342 + $woo_audio_renderer = new \Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks\Audio();
343 +
344 + return $woo_audio_renderer->render( $block_content_html, $mock_parsed_block, $rendering_context );
345 +}