PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.0
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.0
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
sureforms / inc / email / email-template.php

email-template.php in SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz 2.12.0, at inc/email/email-template.php

440 lines 13.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Email template loader.
4 *
5 * @package SureForms.
6 */
7
8 namespace SRFM\Inc\Email;
9
10 use SRFM\Inc\Helper;
11 use SRFM\Inc\Traits\Get_Instance;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit; // Exit if accessed directly.
15 }
16
17 /**
18 * Email Class
19 *
20 * @since 0.0.1
21 */
22 class Email_Template {
23 use Get_Instance;
24
25 /**
26 * Class Constructor
27 *
28 * @since 0.0.1
29 * @return void
30 */
31 public function __construct() {
32 }
33
34 /**
35 * Get email header.
36 *
37 * @since 0.0.1
38 * @return string|false
39 */
40 public function get_header() {
41 ob_start(); ?>
42 <html>
43
44 <head>
45 <meta charset="utf-8">
46 <title><?php echo esc_html__( 'New form submission', 'sureforms' ); ?></title>
47 </head>
48
49 <body style="margin: 0; padding: 0;">
50 <div id="srfm_wrapper" dir="ltr" style="margin: 0; background-color: #F8F8FC; padding: 40px 0 0 0; width: 100%">
51 <table border="0" cellpadding="0" cellspacing="0" width="100%">
52 <tbody>
53 <tr>
54 <td align="center" valign="top">
55 <table border="0" cellpadding="0" cellspacing="0" width="600" id="srfm_template_container" style="background-color: #ffffff;border: 1px solid #dce0e6;margin-bottom: 25px;
56 ">
57 <tbody>
58 <tr>
59 <td align="center" valign="top">
60 <table border="0" cellpadding="0" cellspacing="0" width="600"
61 id="srfm_template_body">
62 <tbody>
63 <tr>
64 <td valign="top" id="srfm_body_content"
65 style="background-color: #ffffff">
66 <table border="0" cellpadding="20" cellspacing="0" width="100%">
67 <tbody>
68 <tr>
69 <td valign="top" style="padding:32px">
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;">
71 <?php
72 return ob_get_clean();
73 }
74
75 /**
76 * Get email footer.
77 *
78 * @since 0.0.1
79 * @return string|false footer tags.
80 */
81 public function get_footer() {
82 ob_start();
83 ?>
84 </div>
85 </td>
86 </tr>
87 </tbody>
88 </table>
89 </td>
90 </tr>
91 </tbody>
92 </table>
93 </td>
94 </tr>
95 </tbody>
96 </table>
97 </td>
98 </tr>
99 </tbody>
100 </table>
101 </div>
102 </body>
103 </html>
104 <?php
105
106 return ob_get_clean();
107 }
108
109 /**
110 * Render email template.
111 *
112 * @param array<mixed> $fields Submission fields.
113 * @param string $email_body email body.
114 * @since 0.0.1
115 * @return string
116 */
117 public function render( $fields, $email_body ) {
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
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;';
210
211 ob_start();
212
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 }
232
233 $label = explode( '-lbl-', $field_name )[1];
234 $label = explode( '-', $label )[0];
235 $field_label = $label ? Helper::decrypt( $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 decrypted human readable label
254 */
255 do_action(
256 'srfm_before_processing_all_data_field',
257 [
258 'value' => $value,
259 'label' => $field_name,
260 'block_name' => $field_block_name,
261 'processed_label' => $field_label,
262 ]
263 );
264
265 /**
266 * Filters whether to add a field row in the all data section.
267 *
268 * This filter allows skipping rows for fields that cannot be processed with the
269 * core plugin's structure. Fields from other packages may have complex data structures
270 * that could cause fatal errors if processed normally. Those packages can use the
271 * 'srfm_before_processing_all_data_field' action to render their fields and return false here
272 * to prevent the core plugin from attempting to process them.
273 *
274 * @since 1.11.0
275 *
276 * @param bool $should_add_field_row Whether to add the field row. Default true.
277 * @param array $field_data Field data containing:
278 * 'value' => mixed The field value
279 * 'field_name' => string The field name/key
280 * 'block_name' => string The block type identifier
281 *
282 * @return bool Whether to add the field row to the table.
283 */
284 $should_add_field_row = apply_filters(
285 'srfm_all_data_field_row',
286 true,
287 [
288 'value' => $value,
289 'field_name' => $field_name,
290 'block_name' => $field_block_name,
291 ]
292 );
293
294 if ( true !== $should_add_field_row ) {
295 continue;
296 }
297
298 $is_array_value = is_array( $value );
299
300 if ( $is_array_value ) {
301 $values_array = array_filter(
302 $value,
303 static function( $input_value ) {
304 return ! empty( Helper::get_string_value( $input_value ) );
305 }
306 );
307 } else {
308 $value = Helper::get_string_value( $value );
309 }
310
311 // Skip if both $value and $values_array are empty.
312 if ( empty( $value ) || ( $is_array_value && empty( $values_array ) ) ) {
313 continue;
314 }
315
316 ?>
317 <tr class="field-label">
318 <th style="<?php echo esc_attr( $td_style ); ?>color: #1E293B;background-color: #F1F5F9;">
319 <strong><?php echo wp_kses_post( html_entity_decode( $field_label ) ); ?>:</strong>
320 </th>
321 </tr>
322 <tr class="field-value">
323 <td style="<?php echo esc_attr( $td_style ); ?>color: #475569;">
324 <?php
325 if ( ! empty( $values_array ) && is_array( $values_array ) ) {
326 $clean_values = [];
327
328 foreach ( $values_array as $value ) {
329 $value = Helper::get_string_value( $value );
330 if ( ! empty( $value ) && is_string( $value ) ) {
331 $clean_values[] = $value;
332 }
333 }
334
335 if ( count( $clean_values ) === 1 ) {
336 $value = reset( $clean_values );
337 $decoded_value = urldecode( $value );
338 ?>
339 <a target="_blank" rel="noopener noreferrer" href="<?php echo esc_url( $decoded_value ); ?>">
340 <?php echo esc_html( $decoded_value ); ?>
341 </a>
342 <?php
343 } elseif ( count( $clean_values ) > 1 ) {
344 ?>
345 <ol style="list-style: decimal; padding-left: 20px; margin: 0;">
346 <?php foreach ( $clean_values as $value ) { ?>
347 <?php $decoded_value = urldecode( $value ); ?>
348 <li style="margin-bottom: 6px;">
349 <a target="_blank" rel="noopener noreferrer" href="<?php echo esc_url( $decoded_value ); ?>">
350 <?php echo esc_html( $decoded_value ); ?>
351 </a>
352 </li>
353 <?php } ?>
354 </ol>
355 <?php
356 }
357 } elseif ( is_string( $value ) && filter_var( $value, FILTER_VALIDATE_URL ) ) {
358 ob_start();
359 ?>
360 <a target="_blank" href="<?php echo esc_url( $value ); ?>">
361 <?php echo esc_html( esc_url( $value ) ); ?>
362 </a>
363 <?php
364 $template_html = ob_get_clean();
365 // Apply filter.
366 $render_url = apply_filters(
367 'srfm_email_template_render_url',
368 $template_html,
369 [
370 'block_type' => $field_name,
371 'submission_item_value' => $value,
372 ]
373 );
374 // Validate fallback.
375 if ( empty( $render_url ) ) {
376 ob_start();
377 ?>
378 <a target="_blank" href="<?php echo esc_url( $value ); ?>">
379 <?php echo esc_html( esc_url( $value ) ); ?>
380 </a>
381 <?php
382 $render_url = ob_get_clean();
383 }
384
385 echo wp_kses(
386 Helper::get_string_value( $render_url ),
387 [
388 'a' => [
389 'href' => [],
390 'target' => [],
391 ],
392 'img' => [
393 'src' => [],
394 'alt' => [],
395 'width' => [],
396 ],
397 ]
398 );
399
400 } else {
401 if ( strpos( Helper::get_string_value( $field_name ), 'srfm-input-multi-choice' ) !== false && strpos( Helper::get_string_value( $value ), '|' ) !== false ) {
402 $options = array_map( 'trim', explode( '|', Helper::get_string_value( $value ) ) );
403 foreach ( $options as $index => $option ) {
404 $border_style = $index < count( $options ) - 1 ? 'border-bottom:1px solid #E5E7EB;padding-bottom:4px;margin-bottom:4px;' : '';
405 echo '<div style="' . esc_attr( $border_style ) . '">' . esc_html( $option ) . '</div>';
406 }
407 continue;
408 }
409
410 if ( is_string( $value ) ) {
411 if ( false !== strpos( $field_name, 'srfm-textarea' ) ) {
412 echo Helper::esc_textarea( html_entity_decode( $value ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- using a custom escaping function.
413 } else {
414 echo false !== strpos( $value, PHP_EOL ) ? wp_kses_post( wpautop( $value ) ) : wp_kses(
415 $value,
416 [
417 'a' => [
418 'href' => [],
419 'target' => [],
420 ],
421 ]
422 );
423 }
424 }
425 }
426 ?>
427 </td>
428 </tr>
429 <?php } ?>
430 </tbody>
431 </table>
432 <?php
433 $table_data = ob_get_clean();
434 $current_table_data = $table_data ? $table_data : ''; // This is done as str_replace expects array|string but ob_get_clean() returns string|false.
435 $current_table_data = $this->remove_border_from_last_tr_td_table( $current_table_data );
436
437 return str_replace( '{all_data}', $current_table_data, $email_body );
438 }
439 }
440