Нет описания

podcast-player.php 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. <?php
  2. /**
  3. * Podcast Player Block.
  4. *
  5. * @since 8.4.0
  6. *
  7. * @package automattic/jetpack
  8. */
  9. namespace Automattic\Jetpack\Extensions\Podcast_Player;
  10. use Automattic\Jetpack\Blocks;
  11. use Jetpack_Gutenberg;
  12. use Jetpack_Podcast_Helper;
  13. const FEATURE_NAME = 'podcast-player';
  14. const BLOCK_NAME = 'jetpack/' . FEATURE_NAME;
  15. if ( ! class_exists( 'Jetpack_Podcast_Helper' ) ) {
  16. \jetpack_require_lib( 'class-jetpack-podcast-helper' );
  17. }
  18. /**
  19. * Registers the block for use in Gutenberg. This is done via an action so that
  20. * we can disable registration if we need to.
  21. */
  22. function register_block() {
  23. Blocks::jetpack_register_block(
  24. BLOCK_NAME,
  25. array(
  26. 'attributes' => array(
  27. 'url' => array(
  28. 'type' => 'string',
  29. ),
  30. 'itemsToShow' => array(
  31. 'type' => 'integer',
  32. 'default' => 5,
  33. ),
  34. 'showCoverArt' => array(
  35. 'type' => 'boolean',
  36. 'default' => true,
  37. ),
  38. 'showEpisodeTitle' => array(
  39. 'type' => 'boolean',
  40. 'default' => true,
  41. ),
  42. 'showEpisodeDescription' => array(
  43. 'type' => 'boolean',
  44. 'default' => true,
  45. ),
  46. ),
  47. 'render_callback' => __NAMESPACE__ . '\render_block',
  48. // Since Gutenberg #31873.
  49. 'style' => 'wp-mediaelement',
  50. )
  51. );
  52. }
  53. add_action( 'init', __NAMESPACE__ . '\register_block' );
  54. /**
  55. * Returns the error message wrapped in HTML if current user
  56. * has the capability to edit the post. Public visitors will
  57. * never see errors.
  58. *
  59. * @param string $message The error message to display.
  60. * @return string
  61. */
  62. function render_error( $message ) {
  63. // Suppress errors for users unable to address them.
  64. if ( ! current_user_can( 'edit_posts' ) ) {
  65. return '';
  66. }
  67. return '<p>' . esc_html( $message ) . '</p>';
  68. }
  69. /**
  70. * Podcast Player block registration/dependency declaration.
  71. *
  72. * @param array $attributes Array containing the Podcast Player block attributes.
  73. * @param string $content Fallback content - a direct link to RSS, as rendered by save.js.
  74. * @return string
  75. */
  76. function render_block( $attributes, $content ) {
  77. // Don't render an interactive version of the block outside the frontend context.
  78. if ( ! jetpack_is_frontend() ) {
  79. return $content;
  80. }
  81. // Test for empty URLS.
  82. if ( empty( $attributes['url'] ) ) {
  83. return render_error( __( 'No Podcast URL provided. Please enter a valid Podcast RSS feed URL.', 'jetpack' ) );
  84. }
  85. // Test for invalid URLs.
  86. if ( ! wp_http_validate_url( $attributes['url'] ) ) {
  87. return render_error( __( 'Your podcast URL is invalid and couldn\'t be embedded. Please double check your URL.', 'jetpack' ) );
  88. }
  89. if ( isset( $attributes['selectedEpisodes'] ) && count( $attributes['selectedEpisodes'] ) ) {
  90. $guids = array_map(
  91. function ( $episode ) {
  92. return $episode['guid'];
  93. },
  94. $attributes['selectedEpisodes']
  95. );
  96. $player_args = array( 'guids' => $guids );
  97. } else {
  98. $player_args = array();
  99. }
  100. // Sanitize the URL.
  101. $attributes['url'] = esc_url_raw( $attributes['url'] );
  102. $player_data = ( new Jetpack_Podcast_Helper( $attributes['url'] ) )->get_player_data( $player_args );
  103. if ( is_wp_error( $player_data ) ) {
  104. return render_error( $player_data->get_error_message() );
  105. }
  106. return render_player( $player_data, $attributes );
  107. }
  108. /**
  109. * Renders the HTML for the Podcast player and tracklist.
  110. *
  111. * @param array $player_data The player data details.
  112. * @param array $attributes Array containing the Podcast Player block attributes.
  113. * @return string The HTML for the podcast player.
  114. */
  115. function render_player( $player_data, $attributes ) {
  116. // If there are no tracks (it is possible) then display appropriate user facing error message.
  117. if ( empty( $player_data['tracks'] ) ) {
  118. return render_error( __( 'No tracks available to play.', 'jetpack' ) );
  119. }
  120. // Only use the amount of tracks requested.
  121. $player_data['tracks'] = array_slice(
  122. $player_data['tracks'],
  123. 0,
  124. absint( $attributes['itemsToShow'] )
  125. );
  126. // Generate a unique id for the block instance.
  127. $instance_id = wp_unique_id( 'jetpack-podcast-player-block-' . get_the_ID() . '-' );
  128. $player_data['playerId'] = $instance_id;
  129. // Generate object to be used as props for PodcastPlayer.
  130. $player_props = array_merge(
  131. // Add all attributes.
  132. array( 'attributes' => $attributes ),
  133. // Add all player data.
  134. $player_data
  135. );
  136. $primary_colors = get_colors( 'primary', $attributes, 'color' );
  137. $secondary_colors = get_colors( 'secondary', $attributes, 'color' );
  138. $background_colors = get_colors( 'background', $attributes, 'background-color' );
  139. $player_classes_name = trim( "{$secondary_colors['class']} {$background_colors['class']}" );
  140. $player_inline_style = trim( "{$secondary_colors['style']} ${background_colors['style']}" );
  141. $player_inline_style .= get_css_vars( $attributes );
  142. $block_classname = Blocks::classes( FEATURE_NAME, $attributes, array( 'is-default' ) );
  143. $is_amp = Blocks::is_amp_request();
  144. ob_start();
  145. ?>
  146. <div class="<?php echo esc_attr( $block_classname ); ?>" id="<?php echo esc_attr( $instance_id ); ?>">
  147. <section
  148. class="jetpack-podcast-player <?php echo esc_attr( $player_classes_name ); ?>"
  149. style="<?php echo esc_attr( $player_inline_style ); ?>"
  150. >
  151. <?php
  152. render(
  153. 'podcast-header',
  154. array_merge(
  155. $player_props,
  156. array(
  157. 'primary_colors' => $primary_colors,
  158. 'player_id' => $player_data['playerId'],
  159. )
  160. )
  161. );
  162. ?>
  163. <?php if ( count( $player_data['tracks'] ) > 1 ) : ?>
  164. <ol class="jetpack-podcast-player__tracks">
  165. <?php foreach ( $player_data['tracks'] as $track_index => $attachment ) : ?>
  166. <?php
  167. render(
  168. 'playlist-track',
  169. array(
  170. 'is_active' => 0 === $track_index,
  171. 'attachment' => $attachment,
  172. 'primary_colors' => $primary_colors,
  173. 'secondary_colors' => $secondary_colors,
  174. )
  175. );
  176. ?>
  177. <?php endforeach; ?>
  178. </ol>
  179. <?php endif; ?>
  180. </section>
  181. <?php if ( ! $is_amp ) : ?>
  182. <script type="application/json"><?php echo wp_json_encode( $player_props ); ?></script>
  183. <?php endif; ?>
  184. </div>
  185. <?php
  186. /**
  187. * Enqueue necessary scripts and styles.
  188. */
  189. if ( ! $is_amp ) {
  190. wp_enqueue_style( 'wp-mediaelement' );
  191. }
  192. Jetpack_Gutenberg::load_assets_as_required( FEATURE_NAME, array( 'mediaelement' ) );
  193. return ob_get_clean();
  194. }
  195. /**
  196. * Given the color name, block attributes and the CSS property,
  197. * the function will return an array with the `class` and `style`
  198. * HTML attributes to be used straight in the markup.
  199. *
  200. * @example
  201. * $color = get_colors( 'secondary', $attributes, 'border-color'
  202. * => array( 'class' => 'has-secondary', 'style' => 'border-color: #333' )
  203. *
  204. * @param string $name Color attribute name, for instance `primary`, `secondary`, ...
  205. * @param array $attrs Block attributes.
  206. * @param string $property Color CSS property, fo instance `color`, `background-color`, ...
  207. * @return array Colors array.
  208. */
  209. function get_colors( $name, $attrs, $property ) {
  210. $attr_color = "{$name}Color";
  211. $attr_custom = 'custom' . ucfirst( $attr_color );
  212. $color = isset( $attrs[ $attr_color ] ) ? $attrs[ $attr_color ] : null;
  213. $custom_color = isset( $attrs[ $attr_custom ] ) ? $attrs[ $attr_custom ] : null;
  214. $colors = array(
  215. 'class' => '',
  216. 'style' => '',
  217. );
  218. if ( $color || $custom_color ) {
  219. $colors['class'] .= "has-{$name}";
  220. if ( $color ) {
  221. $colors['class'] .= " has-{$color}-{$property}";
  222. } elseif ( $custom_color ) {
  223. $colors['style'] .= "{$property}: {$custom_color};";
  224. }
  225. }
  226. return $colors;
  227. }
  228. /**
  229. * It generates a string with CSS variables according to the
  230. * block colors, prefixing each one with `--jetpack-podcast-player'.
  231. *
  232. * @param array $attrs Podcast Block attributes object.
  233. * @return string CSS variables depending on block colors.
  234. */
  235. function get_css_vars( $attrs ) {
  236. $colors_name = array( 'primary', 'secondary', 'background' );
  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. * Render the given template in server-side.
  248. * Important note:
  249. * The $template_props array will be extracted.
  250. * This means it will create a var for each array item.
  251. * Keep it mind when using this param to pass
  252. * properties to the template.
  253. *
  254. * @param string $name Template name, available in `./templates` folder.
  255. * @param array $template_props Template properties. Optional.
  256. * @param bool $print Render template. True as default.
  257. * @return false|string HTML markup or false.
  258. */
  259. function render( $name, $template_props = array(), $print = true ) {
  260. if ( ! strpos( $name, '.php' ) ) {
  261. $name = $name . '.php';
  262. }
  263. $template_path = __DIR__ . '/templates/' . $name;
  264. if ( ! file_exists( $template_path ) ) {
  265. return '';
  266. }
  267. if ( $print ) {
  268. include $template_path;
  269. } else {
  270. ob_start();
  271. include $template_path;
  272. $markup = ob_get_contents();
  273. ob_end_clean();
  274. return $markup;
  275. }
  276. }