PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.6.1
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.6.1
5.6.2 5.6.3 5.6.1 5.6.0 5.5.0 5.4.0 5.3.2 5.3.1 5.1.6 5.1.5 trunk 2.1.5 2.11 2.12 2.13 2.15 3.0.0 3.0.1 3.0.2 3.0.3 3.0.5 3.0.51 3.0.60 3.0.61 3.0.62 All 38 releases
double-opt-in / src / EmailTemplates / PlaceholderMapper.php

PlaceholderMapper.php in Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification 5.6.1, at src/EmailTemplates/PlaceholderMapper.php

353 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Placeholder Mapper
4 *
5 * Maps form fields to standardized placeholders for use across all forms.
6 *
7 * @package Forge12\DoubleOptIn\EmailTemplates
8 * @since 4.0.0
9 */
10
11 namespace Forge12\DoubleOptIn\EmailTemplates;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Class PlaceholderMapper
19 *
20 * Provides automatic detection and manual mapping of form fields to standard placeholders.
21 */
22 class PlaceholderMapper {
23
24 /**
25 * Standard placeholder definitions with auto-detection patterns.
26 *
27 * @var array
28 */
29 private static array $standardPlaceholders = array(
30 'doi_email' => array(
31 'label' => 'E-Mail',
32 'patterns' => array( 'email', 'your-email', 'e-mail', 'mail', 'user-email', 'user_email', 'e_mail' ),
33 ),
34 'doi_name' => array(
35 'label' => 'Name (Full)',
36 'patterns' => array( 'name', 'your-name', 'full-name', 'fullname', 'full_name', 'your_name' ),
37 ),
38 'doi_first_name' => array(
39 'label' => 'First Name',
40 'patterns' => array( 'first-name', 'firstname', 'vorname', 'first_name', 'fname', 'given-name' ),
41 ),
42 'doi_last_name' => array(
43 'label' => 'Last Name',
44 'patterns' => array( 'last-name', 'lastname', 'nachname', 'surname', 'last_name', 'lname', 'family-name' ),
45 ),
46 'doi_phone' => array(
47 'label' => 'Phone',
48 'patterns' => array( 'phone', 'tel', 'telephone', 'your-phone', 'telefon', 'mobile', 'handy', 'phone_number' ),
49 ),
50 'doi_company' => array(
51 'label' => 'Company',
52 'patterns' => array( 'company', 'firma', 'organization', 'organisation', 'business', 'company_name', 'unternehmen' ),
53 ),
54 'doi_message' => array(
55 'label' => 'Message',
56 'patterns' => array( 'message', 'your-message', 'comment', 'nachricht', 'text', 'your_message', 'comments', 'body' ),
57 ),
58 'doi_subject' => array(
59 'label' => 'Subject',
60 'patterns' => array( 'subject', 'your-subject', 'betreff', 'topic', 'your_subject' ),
61 ),
62 'doi_address' => array(
63 'label' => 'Address',
64 'patterns' => array( 'address', 'adresse', 'street', 'strasse', 'your-address' ),
65 ),
66 'doi_city' => array(
67 'label' => 'City',
68 'patterns' => array( 'city', 'stadt', 'ort', 'town' ),
69 ),
70 'doi_zip' => array(
71 'label' => 'ZIP/Postal Code',
72 'patterns' => array( 'zip', 'plz', 'postal', 'postcode', 'postal_code', 'zipcode' ),
73 ),
74 'doi_country' => array(
75 'label' => 'Country',
76 'patterns' => array( 'country', 'land', 'nation' ),
77 ),
78 );
79
80 /**
81 * Meta key for storing custom mappings.
82 *
83 * @var string
84 */
85 const MAPPING_META_KEY = '_doi_placeholder_mapping';
86
87 /**
88 * Get all standard placeholder definitions.
89 *
90 * @return array
91 */
92 public static function getStandardPlaceholders(): array {
93 return self::$standardPlaceholders;
94 }
95
96 /**
97 * Get placeholder labels for UI display.
98 *
99 * @return array Associative array of placeholder => label.
100 */
101 public static function getPlaceholderLabels(): array {
102 $labels = array();
103 foreach ( self::$standardPlaceholders as $key => $config ) {
104 $labels[ $key ] = $config['label'];
105 }
106 return $labels;
107 }
108
109 /**
110 * Auto-detect field mapping based on field names.
111 *
112 * @param array $fieldNames Array of form field names.
113 * @return array Detected mapping [ 'doi_email' => 'your-email', ... ].
114 */
115 public static function autoDetectMapping( array $fieldNames ): array {
116 $mapping = array();
117 $usedFields = array();
118
119 foreach ( self::$standardPlaceholders as $placeholder => $config ) {
120 foreach ( $config['patterns'] as $pattern ) {
121 foreach ( $fieldNames as $fieldName ) {
122 // Skip already mapped fields
123 if ( in_array( $fieldName, $usedFields, true ) ) {
124 continue;
125 }
126
127 // Check for exact match or partial match
128 $normalizedField = strtolower( str_replace( array( '-', '_' ), '', $fieldName ) );
129 $normalizedPattern = strtolower( str_replace( array( '-', '_' ), '', $pattern ) );
130
131 if ( $normalizedField === $normalizedPattern || strpos( $normalizedField, $normalizedPattern ) !== false ) {
132 $mapping[ $placeholder ] = $fieldName;
133 $usedFields[] = $fieldName;
134 break 2; // Found match, move to next placeholder
135 }
136 }
137 }
138 }
139
140 return $mapping;
141 }
142
143 /**
144 * Get custom mapping for a form.
145 *
146 * @param int $formId Form ID.
147 * @param string $formType Form type ('cf7' or 'avada').
148 * @return array Custom mapping array.
149 */
150 public static function getCustomMapping( int $formId, string $formType = 'cf7' ): array {
151 $optionKey = self::getOptionKey( $formId, $formType );
152 $mapping = get_option( $optionKey, array() );
153 return is_array( $mapping ) ? $mapping : array();
154 }
155
156 /**
157 * Save custom mapping for a form.
158 *
159 * @param int $formId Form ID.
160 * @param array $mapping Mapping array.
161 * @param string $formType Form type ('cf7' or 'avada').
162 * @return bool Success status.
163 */
164 public static function saveCustomMapping( int $formId, array $mapping, string $formType = 'cf7' ): bool {
165 $optionKey = self::getOptionKey( $formId, $formType );
166 // Filter out empty mappings
167 $mapping = array_filter( $mapping );
168 return update_option( $optionKey, $mapping );
169 }
170
171 /**
172 * Get the effective mapping for a form (custom + auto-detected).
173 *
174 * @param int $formId Form ID.
175 * @param array $fieldNames Available field names.
176 * @param string $formType Form type ('cf7' or 'avada').
177 * @return array Merged mapping array.
178 */
179 public static function getEffectiveMapping( int $formId, array $fieldNames, string $formType = 'cf7' ): array {
180 $autoMapping = self::autoDetectMapping( $fieldNames );
181 $customMapping = self::getCustomMapping( $formId, $formType );
182
183 // Custom mapping takes precedence over auto-detection
184 return array_merge( $autoMapping, $customMapping );
185 }
186
187 /**
188 * Replace standard placeholders in content with actual values.
189 *
190 * @param string $content Content with placeholders.
191 * @param array $formData Form submission data.
192 * @param int $formId Form ID.
193 * @param array $fieldNames Available field names (optional, extracted from formData if not provided).
194 * @param string $formType Form type ('cf7', 'avada', 'elementor').
195 * @param array $customMapping Optional custom mapping from form settings (takes precedence).
196 * @return string Content with placeholders replaced.
197 */
198 public static function replacePlaceholders(
199 string $content,
200 array $formData,
201 int $formId,
202 array $fieldNames = array(),
203 string $formType = 'cf7',
204 array $customMapping = array()
205 ): string {
206 // Extract field names from form data if not provided
207 if ( empty( $fieldNames ) ) {
208 $fieldNames = array_keys( $formData );
209 }
210
211 // Try to get mapping from central form settings if not provided
212 if ( empty( $customMapping ) ) {
213 $centralSettings = get_post_meta( $formId, 'f12-cf7-doubleoptin', true );
214 if ( is_array( $centralSettings ) && ! empty( $centralSettings['field_mapping'] ) ) {
215 $customMapping = $centralSettings['field_mapping'];
216 }
217 }
218
219 // Get the effective mapping (custom + auto-detected)
220 $autoMapping = self::autoDetectMapping( $fieldNames );
221 // Custom mapping takes precedence over auto-detection
222 $mapping = array_merge( $autoMapping, $customMapping );
223
224 // Replace each standard placeholder
225 foreach ( self::$standardPlaceholders as $placeholder => $config ) {
226 $tag = '[' . $placeholder . ']';
227
228 if ( strpos( $content, $tag ) === false ) {
229 continue;
230 }
231
232 $value = '';
233 if ( isset( $mapping[ $placeholder ] ) ) {
234 $mappedField = $mapping[ $placeholder ];
235 // Remove brackets if present (e.g., "[email]" -> "email")
236 $mappedField = trim( $mappedField, '[]' );
237
238 if ( isset( $formData[ $mappedField ] ) ) {
239 $value = $formData[ $mappedField ];
240 // Handle arrays (multi-select, checkboxes)
241 if ( is_array( $value ) ) {
242 $value = implode( ', ', $value );
243 }
244 }
245 }
246
247 $content = str_replace( $tag, esc_html( $value ), $content );
248 }
249
250 return $content;
251 }
252
253 /**
254 * Get option key for storing mapping.
255 *
256 * @param int $formId Form ID.
257 * @param string $formType Form type.
258 * @return string Option key.
259 */
260 private static function getOptionKey( int $formId, string $formType ): string {
261 return "doi_placeholder_mapping_{$formType}_{$formId}";
262 }
263
264 /**
265 * Get all available placeholders for the email editor.
266 *
267 * @return array Array of placeholder info for UI.
268 */
269 public static function getAvailablePlaceholdersForEditor(): array {
270 $placeholders = array();
271
272 // Add standard placeholders
273 foreach ( self::$standardPlaceholders as $key => $config ) {
274 $placeholders[] = array(
275 'tag' => '[' . $key . ']',
276 'label' => $config['label'],
277 'category' => 'form_fields',
278 'description' => sprintf( __( 'Auto-detected or mapped %s field', 'double-opt-in' ), $config['label'] ),
279 );
280 }
281
282 // Add system placeholders
283 $systemPlaceholders = array(
284 array(
285 'tag' => '[doubleoptinlink]',
286 'label' => __( 'Confirmation Link', 'double-opt-in' ),
287 'category' => 'system',
288 'description' => __( 'Link to confirm the opt-in', 'double-opt-in' ),
289 ),
290 array(
291 'tag' => '[doubleoptoutlink]',
292 'label' => __( 'Opt-out Link', 'double-opt-in' ),
293 'category' => 'system',
294 'description' => __( 'Link to opt-out/unsubscribe', 'double-opt-in' ),
295 ),
296 array(
297 'tag' => '[doubleoptin_form_date]',
298 'label' => __( 'Submission Date', 'double-opt-in' ),
299 'category' => 'system',
300 'description' => __( 'Date of form submission', 'double-opt-in' ),
301 ),
302 array(
303 'tag' => '[doubleoptin_form_time]',
304 'label' => __( 'Submission Time', 'double-opt-in' ),
305 'category' => 'system',
306 'description' => __( 'Time of form submission', 'double-opt-in' ),
307 ),
308 array(
309 'tag' => '[doubleoptin_form_url]',
310 'label' => __( 'Form URL', 'double-opt-in' ),
311 'category' => 'system',
312 'description' => __( 'URL where the form was submitted', 'double-opt-in' ),
313 ),
314 array(
315 'tag' => '[doubleoptin_privacy_url]',
316 'label' => __( 'Privacy Policy URL', 'double-opt-in' ),
317 'category' => 'system',
318 'description' => __( 'URL to the privacy policy page (GDPR)', 'double-opt-in' ),
319 ),
320 );
321
322 return array_merge( $placeholders, $systemPlaceholders );
323 }
324
325 /**
326 * Extract field names from a CF7 form.
327 *
328 * @param int $formId CF7 form ID.
329 * @return array Array of field names.
330 */
331 public static function extractCF7FieldNames( int $formId ): array {
332 if ( ! function_exists( 'wpcf7_contact_form' ) ) {
333 return array();
334 }
335
336 $contactForm = wpcf7_contact_form( $formId );
337 if ( ! $contactForm ) {
338 return array();
339 }
340
341 $tags = $contactForm->scan_form_tags();
342 $fieldNames = array();
343
344 foreach ( $tags as $tag ) {
345 if ( ! empty( $tag->name ) ) {
346 $fieldNames[] = $tag->name;
347 }
348 }
349
350 return $fieldNames;
351 }
352 }
353