PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.8
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.8
2.12.8 2.12.7 2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 All 98 releases
← All changes | inc/email/email-template.php +304 -55 0.0.12 → 2.12.8 View file →
@@ -6,12 +6,11 @@
6 6 */
7 7
8 8 namespace SRFM\Inc\Email;
9 9
10 +use SRFM\Inc\Helper;
10 11 use SRFM\Inc\Traits\Get_Instance;
11 -use SRFM\Inc\Helper;
12 12
13 -
14 13 if ( ! defined( 'ABSPATH' ) ) {
15 14 exit; // Exit if accessed directly.
16 15 }
17 16
@@ -20,9 +19,8 @@
20 19 *
21 20 * @since 0.0.1
22 21 */
23 22 class Email_Template {
24 -
25 23 use Get_Instance;
26 24
27 25 /**
28 26 * Class Constructor
@@ -30,9 +28,8 @@
30 28 * @since 0.0.1
31 29 * @return void
32 30 */
33 31 public function __construct() {
34 -
35 32 }
36 33
37 34 /**
38 35 * Get email header.
@@ -69,9 +66,9 @@
69 66 <table border="0" cellpadding="20" cellspacing="0" width="100%">
70 67 <tbody>
71 68 <tr>
72 69 <td valign="top" style="padding:32px">
73 - <div id="srfm_body_content_inner" style="color: #384860;font-family: Roboto-Medium,Roboto,-apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,Arial,sans-serif;font-size: 14px;line-height: 1;text-align: left;">
70 + <div id="srfm_body_content_inner" style="color: #384860;font-family: Roboto-Medium,Roboto,-apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,Arial,sans-serif;font-size: 14px;line-height: 20px;text-align: left;">
74 71 <?php
75 72 return ob_get_clean();
76 73 }
77 74
@@ -81,10 +78,8 @@
81 78 * @since 0.0.1
82 79 * @return string|false footer tags.
83 80 */
84 81 public function get_footer() {
85 - // Translators: Site URL.
86 - $site_link = sprintf( __( '<a href=%1$s>%2$s</a>', 'sureforms' ), home_url( '/' ), get_bloginfo( 'name' ) );
87 82 ob_start();
88 83 ?>
89 84 </div>
90 85 </td>
@@ -119,53 +114,308 @@
119 114 * @since 0.0.1
120 115 * @return string
121 116 */
122 117 public function render( $fields, $email_body ) {
123 - $message = $this->get_header();
118 + $message = $this->get_header();
119 + $message .= $this->process_all_data_tag( $fields, $email_body );
120 + return $message . $this->get_footer();
121 + }
122 +
123 + /**
124 + * Render email as raw HTML with no template wrapping.
125 + *
126 + * Returns the email body exactly as written with only smart tag
127 + * processing applied. No wrapper, no container, no layout, no
128 + * styles, no footer — true raw HTML output.
129 + *
130 + * @param array<mixed> $fields Submission fields.
131 + * @param string $email_body Email body content.
132 + * @since 2.5.2
133 + * @return string The raw email body with smart tags processed.
134 + */
135 + public function render_raw( $fields, $email_body ) {
136 + return $this->process_all_data_tag( $fields, $email_body );
137 + }
138 +
139 + /**
140 + * Remove border from the last table row in repeater table HTML.
141 + *
142 + * This method finds the last <tr> element in the provided HTML content
143 + * and removes the border-bottom style from all <td> elements within it.
144 + * This is used to clean up the visual appearance of repeater tables.
145 + *
146 + * @param string $content HTML content containing table structure.
147 + *
148 + * @since 1.11.0
149 + * @return string Modified HTML content with border removed from last row.
150 + */
151 + public function remove_border_from_last_tr_td_table( $content ) {
152 + // Check if content contains table and tr elements.
153 + if ( empty( $content ) || ! preg_match( '/<tr[^>]*>/i', $content ) ) {
154 + return $content;
155 + }
156 +
157 + // Find and modify the last tr in one go.
158 + $modified_html = preg_replace_callback(
159 + '/(.*)(<tr[^>]*>.*?<\/tr>)(?!.*<tr)/is',
160 + static function( $matches ) {
161 + $before_last_tr = $matches[1];
162 + $last_tr = $matches[2];
163 +
164 + // Remove border-bottom from all td elements in this tr.
165 + $modified_tr = preg_replace_callback(
166 + '/(<td[^>]*style\s*=\s*["\'])([^"\']*?)(["\'][^>]*>)/i',
167 + static function( $td_match ) {
168 + $start = $td_match[1];
169 + $style = $td_match[2];
170 +
171 + // Remove ONLY border-bottom (not border-bottom-width, etc.).
172 + $style = preg_replace( '/\s*border-bottom\s*:[^;]*;?/i', '', $style );
173 + $style = is_string( $style ) ? $style : '';
174 +
175 + // Clean up multiple semicolons and trim.
176 + $style = preg_replace( '/;+/', ';', $style );
177 + $style = is_string( $style ) ? trim( $style, '; ' ) : '';
178 +
179 + $end = $td_match[3];
180 + return $start . $style . $end;
181 + },
182 + $last_tr
183 + );
184 +
185 + return $before_last_tr . $modified_tr;
186 + },
187 + $content
188 + );
189 +
190 + return is_string( $modified_html ) ? $modified_html : '';
191 + }
192 +
193 + /**
194 + * Process the {all_data} smart tag in the email body.
195 + *
196 + * Replaces {all_data} with a formatted HTML table of all submission fields.
197 + *
198 + * @param array<mixed> $fields Submission fields.
199 + * @param string $email_body Email body content.
200 + * @since 2.5.2
201 + * @return string Email body with {all_data} replaced.
202 + */
203 + private function process_all_data_tag( $fields, $email_body ) {
204 + if ( strpos( $email_body, '{all_data}' ) === false ) {
205 + return $email_body;
206 + }
207 +
124 208 $excluded_fields = [ 'srfm-honeypot-field', 'g-recaptcha-response', 'srfm-sender-email-field' ];
209 + $td_style = 'font-weight: 500;font-size: 14px;line-height: 20px;padding: 12px;text-align:left;word-break: break-word;border-bottom: 1px solid #E5E7EB;';
125 210
126 - $message .= $email_body;
127 - if ( strpos( $email_body, '{all_data}' ) !== false ) {
211 + ob_start();
128 212
129 - ob_start();
213 + ?>
214 + <table class="srfm_all_data" width="536" cellpadding="0" cellspacing="0" style="border: 1px solid #dce0e6;border-radius: 6px;margin-top: 25px;margin-bottom: 25px;overflow:hidden;">
215 + <style>
216 + .srfm_all_data tr:last-child td {
217 + border: none !important;
218 + }
219 + </style>
220 + <tbody>
221 + <?php
222 + foreach ( $fields as $field_name => $value ) {
223 + $values_array = [];
224 + if ( is_array( $value ) ) {
225 + $values_array = $value;
226 + } else {
227 + $value = Helper::get_string_value( $value );
228 + }
229 + if ( in_array( $field_name, $excluded_fields, true ) || false === str_contains( $field_name, '-lbl-' ) ) {
230 + continue;
231 + }
130 232
131 - ?>
132 - <table class="srfm_all_data" width="536" cellpadding="0" cellspacing="0" style="border: 1px solid #dce0e6;border-radius: 6px;margin-top: 25px;margin-bottom: 25px;">
133 - <tbody>
233 + $label = explode( '-lbl-', $field_name )[1];
234 + $label = explode( '-', $label )[0];
235 + $field_label = $label ? Helper::decode( $label ) : '';
236 +
237 + $field_block_name = Helper::get_block_name_from_field( $field_name );
238 +
239 + /**
240 + * Fires before rendering a field in the all data section of emails.
241 + *
242 + * This action allows other packages (like Pro, Business) to process and render fields
243 + * with custom data structures that the core plugin cannot handle. Since the core plugin
244 + * does not know the structure of data from other packages, this action provides a way
245 + * for those packages to properly process and display their field data.
246 + *
247 + * @since 1.11.0
248 + *
249 + * @param array $field_data Field data containing:
250 + * 'value' => mixed The field value
251 + * 'label' => string The field name/key
252 + * 'block_name' => string The block type identifier
253 + * 'processed_label' => string The human readable label, base64-decoded
254 + * out of the submitted field key. Submitter-
255 + * controlled and unauthenticated — escape it
256 + * for the output context (esc_html() for HTML).
257 + */
258 + do_action(
259 + 'srfm_before_processing_all_data_field',
260 + [
261 + 'value' => $value,
262 + 'label' => $field_name,
263 + 'block_name' => $field_block_name,
264 + 'processed_label' => $field_label,
265 + ]
266 + );
267 +
268 + /**
269 + * Filters whether to add a field row in the all data section.
270 + *
271 + * This filter allows skipping rows for fields that cannot be processed with the
272 + * core plugin's structure. Fields from other packages may have complex data structures
273 + * that could cause fatal errors if processed normally. Those packages can use the
274 + * 'srfm_before_processing_all_data_field' action to render their fields and return false here
275 + * to prevent the core plugin from attempting to process them.
276 + *
277 + * @since 1.11.0
278 + *
279 + * @param bool $should_add_field_row Whether to add the field row. Default true.
280 + * @param array $field_data Field data containing:
281 + * 'value' => mixed The field value
282 + * 'field_name' => string The field name/key
283 + * 'block_name' => string The block type identifier
284 + *
285 + * @return bool Whether to add the field row to the table.
286 + */
287 + $should_add_field_row = apply_filters(
288 + 'srfm_all_data_field_row',
289 + true,
290 + [
291 + 'value' => $value,
292 + 'field_name' => $field_name,
293 + 'block_name' => $field_block_name,
294 + ]
295 + );
296 +
297 + if ( true !== $should_add_field_row ) {
298 + continue;
299 + }
300 +
301 + $is_array_value = is_array( $value );
302 +
303 + if ( $is_array_value ) {
304 + $values_array = array_filter(
305 + $value,
306 + static function( $input_value ) {
307 + return ! empty( Helper::get_string_value( $input_value ) );
308 + }
309 + );
310 + } else {
311 + $value = Helper::get_string_value( $value );
312 + }
313 +
314 + // Skip if both $value and $values_array are empty.
315 + if ( empty( $value ) || ( $is_array_value && empty( $values_array ) ) ) {
316 + continue;
317 + }
318 +
319 + ?>
320 + <tr class="field-label">
321 + <th style="<?php echo esc_attr( $td_style ); ?>color: #1E293B;background-color: #F1F5F9;">
322 + <?php // The label is decoded from the submitted field key, so it is attacker-controllable — escape it as text, never as markup. ?>
323 + <strong><?php echo esc_html( html_entity_decode( $field_label ) ); ?>:</strong>
324 + </th>
325 + </tr>
326 + <tr class="field-value">
327 + <td style="<?php echo esc_attr( $td_style ); ?>color: #475569;">
134 328 <?php
135 - foreach ( $fields as $field_name => $value ) {
136 - if ( is_array( $value ) ) {
137 - $values_array = $value;
138 - } else {
329 + if ( ! empty( $values_array ) && is_array( $values_array ) ) {
330 + $clean_values = [];
331 +
332 + foreach ( $values_array as $value ) {
139 333 $value = Helper::get_string_value( $value );
334 + if ( ! empty( $value ) && is_string( $value ) ) {
335 + $clean_values[] = $value;
336 + }
140 337 }
141 - if ( in_array( $field_name, $excluded_fields, true ) || false === str_contains( $field_name, '-lbl-' ) ) {
142 - continue;
338 +
339 + if ( count( $clean_values ) === 1 ) {
340 + $value = reset( $clean_values );
341 + $decoded_value = urldecode( $value );
342 + ?>
343 + <a target="_blank" rel="noopener noreferrer" href="<?php echo esc_url( $decoded_value ); ?>">
344 + <?php echo esc_html( $decoded_value ); ?>
345 + </a>
346 + <?php
347 + } elseif ( count( $clean_values ) > 1 ) {
348 + ?>
349 + <ol style="list-style: decimal; padding-left: 20px; margin: 0;">
350 + <?php foreach ( $clean_values as $value ) { ?>
351 + <?php $decoded_value = urldecode( $value ); ?>
352 + <li style="margin-bottom: 6px;">
353 + <a target="_blank" rel="noopener noreferrer" href="<?php echo esc_url( $decoded_value ); ?>">
354 + <?php echo esc_html( $decoded_value ); ?>
355 + </a>
356 + </li>
357 + <?php } ?>
358 + </ol>
359 + <?php
143 360 }
144 -
145 - $label = explode( '-lbl-', $field_name )[1];
146 - $label = explode( '-', $label )[0];
147 - $field_label = $label ? esc_html( Helper::decrypt( $label ) ) : '';
361 + } elseif ( is_string( $value ) && filter_var( $value, FILTER_VALIDATE_URL ) ) {
362 + ob_start();
148 363 ?>
149 - <tr class="field-label">
150 - <th style="font-weight: 500;font-size: 14px;color: #1E293B;padding: 8px 16px;background-color: #F1F5F9;text-align: left;">
151 - <strong><?php echo esc_html( $field_label ); ?><?php echo esc_html__( ':', 'sureforms' ); ?><strong/>
152 - </th>
153 - </tr>
154 - <tr class="field-value">
155 - <td style="font-size: 14px;color: #475569;padding: 8px 16px 16px 16px;padding-bottom: 10px;">
364 + <a target="_blank" href="<?php echo esc_url( $value ); ?>">
365 + <?php echo esc_html( esc_url( $value ) ); ?>
366 + </a>
156 367 <?php
157 - if ( ! empty( $values_array ) && is_array( $values_array ) ) {
158 - foreach ( $values_array as $value ) {
159 - $value = Helper::get_string_value( $value );
160 - if ( ! empty( $value ) && is_string( $value ) ) {
161 - ?>
162 - <a target="_blank" href="<?php echo esc_attr( urldecode( $value ) ); ?>"><?php echo esc_html__( 'View', 'sureforms' ); ?></a>
163 - <?php
164 - }
368 + $template_html = ob_get_clean();
369 + // Apply filter.
370 + $render_url = apply_filters(
371 + 'srfm_email_template_render_url',
372 + $template_html,
373 + [
374 + 'block_type' => $field_name,
375 + 'submission_item_value' => $value,
376 + ]
377 + );
378 + // Validate fallback.
379 + if ( empty( $render_url ) ) {
380 + ob_start();
381 + ?>
382 + <a target="_blank" href="<?php echo esc_url( $value ); ?>">
383 + <?php echo esc_html( esc_url( $value ) ); ?>
384 + </a>
385 + <?php
386 + $render_url = ob_get_clean();
387 + }
388 +
389 + echo wp_kses(
390 + Helper::get_string_value( $render_url ),
391 + [
392 + 'a' => [
393 + 'href' => [],
394 + 'target' => [],
395 + ],
396 + 'img' => [
397 + 'src' => [],
398 + 'alt' => [],
399 + 'width' => [],
400 + ],
401 + ]
402 + );
403 +
404 + } else {
405 + if ( strpos( Helper::get_string_value( $field_name ), 'srfm-input-multi-choice' ) !== false && strpos( Helper::get_string_value( $value ), '|' ) !== false ) {
406 + $options = array_map( 'trim', explode( '|', Helper::get_string_value( $value ) ) );
407 + foreach ( $options as $index => $option ) {
408 + $border_style = $index < count( $options ) - 1 ? 'border-bottom:1px solid #E5E7EB;padding-bottom:4px;margin-bottom:4px;' : '';
409 + echo '<div style="' . esc_attr( $border_style ) . '">' . esc_html( $option ) . '</div>';
165 410 }
166 - } else {
167 - if ( is_string( $value ) ) {
411 + continue;
412 + }
413 +
414 + if ( is_string( $value ) ) {
415 + if ( false !== strpos( $field_name, 'srfm-textarea' ) ) {
416 + echo Helper::esc_textarea( html_entity_decode( $value ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- using a custom escaping function.
417 + } else {
168 418 echo false !== strpos( $value, PHP_EOL ) ? wp_kses_post( wpautop( $value ) ) : wp_kses(
169 419 $value,
170 420 [
171 421 'a' => [
@@ -175,20 +425,19 @@
175 425 ]
176 426 );
177 427 }
178 428 }
179 - ?>
180 - </td>
181 - </tr>
182 - <?php } ?>
183 - </tbody>
184 - </table>
185 - <?php
186 - $table_data = ob_get_clean();
187 - $current_table_data = $table_data ? $table_data : ''; // This is done as str_replace expects array|string but ob_get_clean() returns string|false.
188 - $message = str_replace( '{all_data}', $current_table_data, $message );
189 - }
190 - $message .= $this->get_footer();
191 - return $message;
429 + }
430 + ?>
431 + </td>
432 + </tr>
433 + <?php } ?>
434 + </tbody>
435 + </table>
436 + <?php
437 + $table_data = ob_get_clean();
438 + $current_table_data = $table_data ? $table_data : ''; // This is done as str_replace expects array|string but ob_get_clean() returns string|false.
439 + $current_table_data = $this->remove_border_from_last_tr_td_table( $current_table_data );
440 +
441 + return str_replace( '{all_data}', $current_table_data, $email_body );
192 442 }
193 -
194 443 }