PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.0-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.0-beta
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 / render.php
jetpack / extensions / blocks / podcast-player Last commit date
templates 7 months ago podcast-player.php 1 week ago render.php 1 week ago
render.php
346 lines
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