Completed
Push — update/connect-podcast-player-... ( eb8dbb...ca5bd7 )
by
unknown
36:16 queued 25:36
created

podcast-player.php ➔ render_block()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 7
nop 2
dl 0
loc 38
rs 8.3786
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 Automattic\Jetpack\Extensions\Podcast_Player;
11
12
use Automattic\Jetpack\Blocks;
13
use Jetpack_Gutenberg;
14
use Jetpack_Podcast_Helper;
15
16
const FEATURE_NAME = 'podcast-player';
17
const BLOCK_NAME   = 'jetpack/' . FEATURE_NAME;
18
19
if ( ! class_exists( 'Jetpack_Podcast_Helper' ) ) {
20
	\jetpack_require_lib( 'class-jetpack-podcast-helper' );
21
}
22
23
/**
24
 * Registers the block for use in Gutenberg. This is done via an action so that
25
 * we can disable registration if we need to.
26
 */
27
function register_block() {
28
	Blocks::jetpack_register_block(
29
		BLOCK_NAME,
30
		array(
31
			'attributes'      => array(
32
				'url'                    => array(
33
					'type' => 'string',
34
				),
35
				'itemsToShow'            => array(
36
					'type'    => 'integer',
37
					'default' => 5,
38
				),
39
				'showCoverArt'           => array(
40
					'type'    => 'boolean',
41
					'default' => true,
42
				),
43
				'showEpisodeTitle'       => array(
44
					'type'    => 'boolean',
45
					'default' => true,
46
				),
47
				'showEpisodeDescription' => array(
48
					'type'    => 'boolean',
49
					'default' => true,
50
				),
51
			),
52
			'render_callback' => __NAMESPACE__ . '\render_block',
53
		)
54
	);
55
}
56
add_action( 'init', __NAMESPACE__ . '\register_block' );
57
58
/**
59
 * Returns the error message wrapped in HTML if current user
60
 * has the capability to edit the post. Public visitors will
61
 * never see errors.
62
 *
63
 * @param string $message The error message to display.
64
 * @return string
65
 */
66
function render_error( $message ) {
67
	// Suppress errors for users unable to address them.
68
	if ( ! current_user_can( 'edit_posts' ) ) {
69
		return '';
70
	}
71
	return '<p>' . esc_html( $message ) . '</p>';
72
}
73
74
/**
75
 * Podcast Player block registration/dependency declaration.
76
 *
77
 * @param array  $attributes Array containing the Podcast Player block attributes.
78
 * @param string $content    Fallback content - a direct link to RSS, as rendered by save.js.
79
 * @return string
80
 */
81
function render_block( $attributes, $content ) {
82
	// Don't render an interactive version of the block outside the frontend context.
83
	if ( ! jetpack_is_frontend() ) {
84
		return $content;
85
	}
86
87
	// Test for empty URLS.
88
	if ( empty( $attributes['url'] ) ) {
89
		return render_error( __( 'No Podcast URL provided. Please enter a valid Podcast RSS feed URL.', 'jetpack' ) );
90
	}
91
92
	// Test for invalid URLs.
93
	if ( ! wp_http_validate_url( $attributes['url'] ) ) {
94
		return render_error( __( 'Your podcast URL is invalid and couldn\'t be embedded. Please double check your URL.', 'jetpack' ) );
95
	}
96
97
	if ( isset( $attributes['selectedEpisodes'] ) && count( $attributes['selectedEpisodes'] ) ) {
98
		$guids       = array_map(
99
			function ( $episode ) {
100
				return $episode['guid'];
101
			},
102
			$attributes['selectedEpisodes']
103
		);
104
		$player_args = array( 'guids' => $guids );
105
	} else {
106
		$player_args = array();
107
	}
108
109
	// Sanitize the URL.
110
	$attributes['url'] = esc_url_raw( $attributes['url'] );
111
	$player_data       = ( new Jetpack_Podcast_Helper( $attributes['url'] ) )->get_player_data( $player_args );
112
113
	if ( is_wp_error( $player_data ) ) {
114
		return render_error( $player_data->get_error_message() );
0 ignored issues
show
Bug introduced by
The method get_error_message() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
115
	}
116
117
	return render_player( $player_data, $attributes );
118
}
119
120
/**
121
 * Renders the HTML for the Podcast player and tracklist.
122
 *
123
 * @param array $player_data The player data details.
124
 * @param array $attributes Array containing the Podcast Player block attributes.
125
 * @return string The HTML for the podcast player.
126
 */
127
function render_player( $player_data, $attributes ) {
128
	// If there are no tracks (it is possible) then display appropriate user facing error message.
129
	if ( empty( $player_data['tracks'] ) ) {
130
		return render_error( __( 'No tracks available to play.', 'jetpack' ) );
131
	}
132
133
	// Only use the amount of tracks requested.
134
	$player_data['tracks'] = array_slice(
135
		$player_data['tracks'],
136
		0,
137
		absint( $attributes['itemsToShow'] )
138
	);
139
140
	// Generate a unique id for the block instance.
141
	$instance_id             = wp_unique_id( 'jetpack-podcast-player-block-' . get_the_ID() . '-' );
142
	$player_data['playerId'] = $instance_id;
143
144
	// Generate object to be used as props for PodcastPlayer.
145
	$player_props = array_merge(
146
		// Add all attributes.
147
		array( 'attributes' => $attributes ),
148
		// Add all player data.
149
		$player_data
150
	);
151
152
	$primary_colors    = get_colors( 'primary', $attributes, 'color' );
153
	$secondary_colors  = get_colors( 'secondary', $attributes, 'color' );
154
	$background_colors = get_colors( 'background', $attributes, 'background-color' );
155
156
	$player_classes_name  = trim( "{$secondary_colors['class']} {$background_colors['class']}" );
157
	$player_inline_style  = trim( "{$secondary_colors['style']} ${background_colors['style']}" );
158
	$player_inline_style .= get_css_vars( $attributes );
159
160
	$block_classname = Blocks::classes( FEATURE_NAME, $attributes, array( 'is-default' ) );
161
	$is_amp          = Blocks::is_amp_request();
162
163
	ob_start();
164
	?>
165
	<div class="<?php echo esc_attr( $block_classname ); ?>" id="<?php echo esc_attr( $instance_id ); ?>">
166
		<section
167
			class="jetpack-podcast-player <?php echo esc_attr( $player_classes_name ); ?>"
168
			style="<?php echo esc_attr( $player_inline_style ); ?>"
169
		>
170
			<?php
171
			render(
172
				'podcast-header',
173
				array_merge(
174
					$player_props,
175
					array(
176
						'primary_colors' => $primary_colors,
177
						'player_id'      => $player_data['playerId'],
178
					)
179
				)
180
			);
181
			?>
182
			<?php if ( count( $player_data['tracks'] ) > 1 ) : ?>
183
			<ol class="jetpack-podcast-player__tracks">
184
				<?php foreach ( $player_data['tracks'] as $track_index => $attachment ) : ?>
185
					<?php
186
					render(
187
						'playlist-track',
188
						array(
189
							'is_active'        => 0 === $track_index,
190
							'attachment'       => $attachment,
191
							'primary_colors'   => $primary_colors,
192
							'secondary_colors' => $secondary_colors,
193
						)
194
					);
195
					?>
196
				<?php endforeach; ?>
197
			</ol>
198
			<?php endif; ?>
199
		</section>
200
		<?php if ( ! $is_amp ) : ?>
201
		<script type="application/json"><?php echo wp_json_encode( $player_props ); ?></script>
202
		<?php endif; ?>
203
	</div>
204
	<?php
205
	/**
206
	 * Enqueue necessary scripts and styles.
207
	 */
208
	if ( ! $is_amp ) {
209
		wp_enqueue_style( 'wp-mediaelement' );
210
	}
211
	Jetpack_Gutenberg::load_assets_as_required( FEATURE_NAME, array( 'mediaelement' ) );
212
213
	return ob_get_clean();
214
}
215
216
/**
217
 * Given the color name, block attributes and the CSS property,
218
 * the function will return an array with the `class` and `style`
219
 * HTML attributes to be used straight in the markup.
220
 *
221
 * @example
222
 * $color = get_colors( 'secondary', $attributes, 'border-color'
223
 *  => array( 'class' => 'has-secondary', 'style' => 'border-color: #333' )
224
 *
225
 * @param string $name     Color attribute name, for instance `primary`, `secondary`, ...
226
 * @param array  $attrs    Block attributes.
227
 * @param string $property Color CSS property, fo instance `color`, `background-color`, ...
228
 * @return array           Colors array.
229
 */
230
function get_colors( $name, $attrs, $property ) {
231
	$attr_color  = "{$name}Color";
232
	$attr_custom = 'custom' . ucfirst( $attr_color );
233
234
	$color        = isset( $attrs[ $attr_color ] ) ? $attrs[ $attr_color ] : null;
235
	$custom_color = isset( $attrs[ $attr_custom ] ) ? $attrs[ $attr_custom ] : null;
236
237
	$colors = array(
238
		'class' => '',
239
		'style' => '',
240
	);
241
242
	if ( $color || $custom_color ) {
243
		$colors['class'] .= "has-{$name}";
244
245
		if ( $color ) {
246
			$colors['class'] .= " has-{$color}-{$property}";
247
		} elseif ( $custom_color ) {
248
			$colors['style'] .= "{$property}: {$custom_color};";
249
		}
250
	}
251
252
	return $colors;
253
}
254
255
/**
256
 * It generates a string with CSS variables according to the
257
 * block colors, prefixing each one with `--jetpack-podcast-player'.
258
 *
259
 * @param array $attrs Podcast Block attributes object.
260
 * @return string      CSS variables depending on block colors.
261
 */
262
function get_css_vars( $attrs ) {
263
	$colors_name = array( 'primary', 'secondary', 'background' );
264
265
	$inline_style = '';
266
	foreach ( $colors_name as $color ) {
267
		$hex_color = 'hex' . ucfirst( $color ) . 'Color';
268
		if ( ! empty( $attrs[ $hex_color ] ) ) {
269
			$inline_style .= " --jetpack-podcast-player-{$color}: {$attrs[ $hex_color ]};";
270
		}
271
	}
272
	return $inline_style;
273
}
274
275
/**
276
 * Render the given template in server-side.
277
 * Important note:
278
 *    The $template_props array will be extracted.
279
 *    This means it will create a var for each array item.
280
 *    Keep it mind when using this param to pass
281
 *    properties to the template.
282
 *
283
 * @param string $name           Template name, available in `./templates` folder.
284
 * @param array  $template_props Template properties. Optional.
285
 * @param bool   $print          Render template. True as default.
286
 * @return false|string          HTML markup or false.
287
 */
288
function render( $name, $template_props = array(), $print = true ) {
289
	if ( ! strpos( $name, '.php' ) ) {
290
		$name = $name . '.php';
291
	}
292
293
	$template_path = __DIR__ . '/templates/' . $name;
294
295
	if ( ! file_exists( $template_path ) ) {
296
		return '';
297
	}
298
299
	/*
300
	 * Optionally provided an assoc array of data to pass to template.
301
	 * IMPORTANT: It will be extracted into variables.
302
	 */
303
	if ( is_array( $template_props ) ) {
304
		/*
305
		 * It ignores the `discouraging` sniffer rule for extract, since it's needed
306
		 * to make the templating system works.
307
		 */
308
		extract( $template_props ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
309
	}
310
311
	if ( $print ) {
312
		include $template_path;
313
	} else {
314
		ob_start();
315
		include $template_path;
316
		$markup = ob_get_contents();
317
		ob_end_clean();
318
319
		return $markup;
320
	}
321
}
322