PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-a.3
Jetpack – WP Security, Backup, Speed, & Growth v16.1-a.3
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 All 500 releases
jetpack / extensions / blocks / podcast-player / render.php
render.php
346 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 }
346