Skip to content

Commit 87200bb

Browse files
Merge branch 'trunk' into add/cogs-support
2 parents a36bb2b + ab60ef4 commit 87200bb

File tree

142 files changed

+9060
-1587
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

142 files changed

+9060
-1587
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ jobs:
406406
- name: 'Publish report to dashboard'
407407
if: ${{ !! steps.merge-artifacts.outputs.artifact-id }}
408408
env:
409-
GH_TOKEN: ${{ secrets.REPORTS_TOKEN }}
409+
GH_TOKEN: ${{ secrets.TEST_REPORTS_DISPATCH_WORKFLOW_TOKEN }}
410410
PR_NUMBER: ${{ github.event.pull_request.number }}
411411
REPORT_NAME: ${{ matrix.report }}
412412
HEAD_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}

.syncpackrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,15 @@
160160
],
161161
"pinVersion": "5.7.x"
162162
},
163+
{
164+
"dependencies": [
165+
"jest-fixed-jsdom"
166+
],
167+
"packages": [
168+
"**"
169+
],
170+
"pinVersion": "^0.0.10"
171+
},
163172
{
164173
"dependencies": [
165174
"eslint"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: dev
3+
Comment: Email Editor: Extract html processing helper from table class for reuse in other block rendering classes.
4+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: add
3+
4+
Add email rendering instructions for the core/audio and core/embed blocks.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: add
3+
4+
Add email rendering instructions for the core/gallery block.
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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

Comments
 (0)