|
| 1 | +<?php |
| 2 | +/** |
| 3 | + * This file is part of the WooCommerce Email Editor package |
| 4 | + * |
| 5 | + * @package Automattic\WooCommerce\EmailEditor |
| 6 | + */ |
| 7 | + |
| 8 | +declare( strict_types = 1 ); |
| 9 | +namespace Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks; |
| 10 | + |
| 11 | +use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context; |
| 12 | +use Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper; |
| 13 | + |
| 14 | +/** |
| 15 | + * Audio block renderer. |
| 16 | + * This renderer handles core/audio blocks for email. |
| 17 | + */ |
| 18 | +class Audio extends Abstract_Block_Renderer { |
| 19 | + /** |
| 20 | + * Render the block. |
| 21 | + * |
| 22 | + * @param string $block_content The block content. |
| 23 | + * @param array $parsed_block The parsed block. |
| 24 | + * @param Rendering_Context $rendering_context The rendering context. |
| 25 | + * @return string |
| 26 | + */ |
| 27 | + public function render( string $block_content, array $parsed_block, Rendering_Context $rendering_context ): string { |
| 28 | + // Validate input parameters and required dependencies. |
| 29 | + if ( ! isset( $parsed_block['attrs'] ) || ! is_array( $parsed_block['attrs'] ) || |
| 30 | + ! class_exists( '\Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper' ) ) { |
| 31 | + return ''; |
| 32 | + } |
| 33 | + |
| 34 | + $attr = $parsed_block['attrs']; |
| 35 | + |
| 36 | + // Check if we have a valid audio source - return empty string immediately if not. |
| 37 | + // For attachments, check the 'id' attribute. For external URLs, check if src exists in HTML content. |
| 38 | + $has_attachment_id = ! empty( $attr['id'] ); |
| 39 | + $has_src_in_html = preg_match( '#<audio[^>]*\ssrc=["\']([^"\']*)["\'][^>]*/?>#', $block_content ); |
| 40 | + |
| 41 | + // If we have neither an attachment ID nor a src in the HTML content, return empty. |
| 42 | + if ( ! $has_attachment_id && ! $has_src_in_html ) { |
| 43 | + return ''; |
| 44 | + } |
| 45 | + |
| 46 | + // If we have a valid source, proceed with normal rendering. |
| 47 | + $rendered_content = $this->render_content( $block_content, $parsed_block, $rendering_context ); |
| 48 | + |
| 49 | + // If render_content returns empty (e.g., invalid URL), return empty string. |
| 50 | + if ( empty( $rendered_content ) ) { |
| 51 | + return ''; |
| 52 | + } |
| 53 | + |
| 54 | + return $this->add_spacer( $rendered_content, $parsed_block['email_attrs'] ?? array() ); |
| 55 | + } |
| 56 | + /** |
| 57 | + * Renders the audio block content as an audio player for email. |
| 58 | + * |
| 59 | + * @param string $block_content Block content. |
| 60 | + * @param array $parsed_block Parsed block. |
| 61 | + * @param Rendering_Context $rendering_context Rendering context (required by parent contract but unused in this implementation). |
| 62 | + * @return string |
| 63 | + */ |
| 64 | + protected function render_content( string $block_content, array $parsed_block, Rendering_Context $rendering_context ): string { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable |
| 65 | + $attr = $parsed_block['attrs']; |
| 66 | + |
| 67 | + // Get URL and length. |
| 68 | + if ( isset( $attr['id'] ) ) { |
| 69 | + // Audio file from site's media library. |
| 70 | + $url = \wp_get_attachment_url( $attr['id'] ); |
| 71 | + $meta = \get_post_meta( $attr['id'], '_wp_attachment_metadata', true ); |
| 72 | + $length = ( is_array( $meta ) && isset( $meta['length_formatted'] ) && is_string( $meta['length_formatted'] ) ) ? $meta['length_formatted'] : ''; |
| 73 | + } else { |
| 74 | + // Audio file from external URL. |
| 75 | + preg_match( '#<audio[^>]*\ssrc=["\']([^"\']*)["\'][^>]*/?>#', $block_content, $audio ); |
| 76 | + $url = isset( $audio[1] ) ? $audio[1] : $attr['src'] ?? ''; |
| 77 | + $length = null; |
| 78 | + } |
| 79 | + |
| 80 | + // Validate URL with proper ordering and comprehensive checks. |
| 81 | + if ( empty( $url ) ) { |
| 82 | + return ''; |
| 83 | + } |
| 84 | + |
| 85 | + // Validate URL type and format. |
| 86 | + if ( ! str_starts_with( $url, 'data:audio/' ) && |
| 87 | + ! str_starts_with( $url, '/' ) && |
| 88 | + ! str_starts_with( $url, 'https://' ) ) { |
| 89 | + // Reject everything else (http://, ftp://, relative paths, etc.). |
| 90 | + return ''; |
| 91 | + } |
| 92 | + |
| 93 | + // For HTTPS URLs, validate with wp_http_validate_url. |
| 94 | + if ( str_starts_with( $url, 'https://' ) && ! wp_http_validate_url( $url ) ) { |
| 95 | + return ''; |
| 96 | + } |
| 97 | + |
| 98 | + // Get spacing from email_attrs for better consistency with core blocks. |
| 99 | + $email_attrs = $parsed_block['email_attrs'] ?? array(); |
| 100 | + $table_margin_style = ''; |
| 101 | + |
| 102 | + if ( ! empty( $email_attrs ) && class_exists( '\WP_Style_Engine' ) ) { |
| 103 | + // Get margin for table styling. |
| 104 | + $table_margin_style = \WP_Style_Engine::compile_css( array_intersect_key( $email_attrs, array_flip( array( 'margin' ) ) ), '' ); |
| 105 | + } |
| 106 | + |
| 107 | + $icon_image = $this->get_audio_icon_url(); |
| 108 | + $label = ! empty( $attr['label'] ) ? $attr['label'] : __( 'Listen to the audio', 'woocommerce' ); |
| 109 | + |
| 110 | + // Add duration to label if available. |
| 111 | + if ( ! empty( $length ) ) { |
| 112 | + $label .= ' (' . esc_html( (string) $length ) . ')'; |
| 113 | + } |
| 114 | + |
| 115 | + $audio_url = esc_url( $url ); |
| 116 | + |
| 117 | + // Define pill-style colors and styling. |
| 118 | + $background_color = '#f6f7f7'; |
| 119 | + $border_color = '#AAA'; |
| 120 | + $icon_size = '18px'; |
| 121 | + $font_size = '14px'; |
| 122 | + |
| 123 | + // Generate the icon content. |
| 124 | + $icon_content = sprintf( |
| 125 | + '<a href="%1$s" rel="noopener nofollow" target="_blank" style="padding: 0.25em; padding-left: 17px; display: inline-block; vertical-align: middle;"><img height="%2$s" src="%3$s" style="display:block;margin-right:0;vertical-align:middle;" width="%2$s" alt="%4$s"></a>', |
| 126 | + $audio_url, |
| 127 | + esc_attr( $icon_size ), |
| 128 | + esc_url( $icon_image ), |
| 129 | + // translators: %s is the audio player icon. |
| 130 | + sprintf( __( '%s icon', 'woocommerce' ), __( 'Audio', 'woocommerce' ) ) |
| 131 | + ); |
| 132 | + $icon_content = Table_Wrapper_Helper::render_table_cell( $icon_content, array( 'style' => sprintf( 'vertical-align:middle;font-size:%s;', $font_size ) ) ); |
| 133 | + |
| 134 | + // Generate the label content. |
| 135 | + $label_content = sprintf( |
| 136 | + '<a href="%1$s" rel="noopener nofollow" target="_blank" style="text-decoration:none; padding: 0.25em; padding-right: 17px; display: inline-block;"><span style="margin-left:.5em;margin-right:.5em;font-weight:bold"> %2$s </span></a>', |
| 137 | + $audio_url, |
| 138 | + esc_html( $label ) |
| 139 | + ); |
| 140 | + $label_cell_style = sprintf( |
| 141 | + 'vertical-align:middle;font-size:%s;', |
| 142 | + $font_size |
| 143 | + ); |
| 144 | + $label_content = Table_Wrapper_Helper::render_table_cell( $label_content, array( 'style' => $label_cell_style ) ); |
| 145 | + |
| 146 | + // Combine icon and label tables. |
| 147 | + $audio_content = $icon_content . $label_content; |
| 148 | + |
| 149 | + // Create the main pill-style table. |
| 150 | + $main_table_styles = sprintf( |
| 151 | + 'background-color: %s; border-radius: 9999px; float: none; border: 1px solid %s; border-collapse: separate;', |
| 152 | + $background_color, |
| 153 | + $border_color |
| 154 | + ); |
| 155 | + |
| 156 | + $main_table_attrs = array( |
| 157 | + 'align' => 'left', |
| 158 | + 'style' => $main_table_styles, |
| 159 | + ); |
| 160 | + |
| 161 | + $main_table = Table_Wrapper_Helper::render_table_wrapper( $audio_content, $main_table_attrs, array(), array(), false ); |
| 162 | + |
| 163 | + // Create the main wrapper table. |
| 164 | + $table_style = 'width: 100%;'; |
| 165 | + if ( ! empty( $table_margin_style ) ) { |
| 166 | + $table_style = $table_margin_style . '; ' . $table_style; |
| 167 | + } else { |
| 168 | + $table_style = 'margin: 16px 0; ' . $table_style; |
| 169 | + } |
| 170 | + |
| 171 | + $table_attrs = array( |
| 172 | + 'style' => $table_style, |
| 173 | + ); |
| 174 | + |
| 175 | + $cell_attrs = array( |
| 176 | + 'style' => 'min-width: 100%; vertical-align: middle; word-break: break-word; text-align: left;', |
| 177 | + ); |
| 178 | + |
| 179 | + $main_wrapper = Table_Wrapper_Helper::render_table_wrapper( $main_table, $table_attrs, $cell_attrs ); |
| 180 | + |
| 181 | + return Table_Wrapper_Helper::render_outlook_table_wrapper( $main_wrapper, array( 'align' => 'left' ) ); |
| 182 | + } |
| 183 | + |
| 184 | + /** |
| 185 | + * Gets the audio icon URL. |
| 186 | + * |
| 187 | + * @return string The audio icon URL. |
| 188 | + */ |
| 189 | + private function get_audio_icon_url(): string { |
| 190 | + $file_name = '/icons/audio/audio-play.png'; |
| 191 | + return plugins_url( $file_name, __FILE__ ); |
| 192 | + } |
| 193 | +} |
0 commit comments