PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 3.0.3
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v3.0.3
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 3.0.70 3.0.71 3.0.72 All 35 releases
double-opt-in / compatibility / OptInFrontend.class.php

OptInFrontend.class.php in Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification 3.0.3, at compatibility/OptInFrontend.class.php

532 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace forge12\contactform7\CF7DoubleOptIn;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 abstract class OptInFrontend {
10 /**
11 * The Type of the OptIn Form System
12 *
13 * @var string
14 */
15 protected string $type = '';
16
17 /**
18 * Constructor for the class.
19 *
20 * This constructor registers the necessary actions to be performed,
21 * by hooking methods of the current class, for the following events:
22 * - 'f12_cf7_doubleoptin_before_send_default_mail'
23 * - 'f12_cf7_doubleoptin_after_send_default_mail'
24 * - 'f12_cf7_doubleoptin_trigger_default_mail'
25 * - 'shutdown'
26 *
27 * @return void
28 */
29 public function __construct( string $type ) {
30 $this->type = $type;
31 add_action( 'f12_cf7_doubleoptin_before_send_default_mail', [ $this, 'beforeSendDefaultMail' ], 10, 1 );
32 add_action( 'f12_cf7_doubleoptin_after_send_default_mail', [ $this, 'afterSendDefaultMail' ], 10, 1 );
33 add_action( 'f12_cf7_doubleoptin_trigger_default_mail', [ $this, 'sendDefaultMail' ], 10, 1 );
34 add_action( 'shutdown', [ $this, 'removeFiles' ] );
35 add_action( 'init', [ $this, 'validateOptIn' ] );
36 add_filter( 'f12_cf7_doubleoptin_get_recipient_' . $this->type, [ $this, 'getRecipient' ], 10, 3 );
37 }
38
39 /**
40 * Retrieves the recipient for a given recipient name, form parameters, and post parameters.
41 *
42 * @param string $recipient The name of the recipient to retrieve.
43 * @param array $formParameter The array of form parameters.
44 * @param array $postParameter The array of post parameters.
45 *
46 * @return string The recipient for the given parameters.
47 */
48 abstract public function getRecipient( string $recipient, array $formParameter, array $postParameter ): string;
49
50 /**
51 * Sends a default mail for the given OptIn object.
52 *
53 * This method is declared as abstract, meaning that it must be implemented
54 * by any child class that extends the current class. The method takes
55 * a single parameter, $OptIn, of type OptIn. This parameter represents
56 * the OptIn object for which the default mail needs to be sent.
57 *
58 * This method should be overridden by child classes to define the specific
59 * logic for sending the default mail for the given OptIn object.
60 *
61 * Note that the implementation details of this method vary depending on the
62 * specific subclass. Therefore, the implementation code is not provided here.
63 *
64 * @param OptIn $OptIn The OptIn object for which the default mail needs to be sent.
65 *
66 * @return void
67 */
68 abstract public function sendDefaultMail( OptIn $OptIn ): void;
69
70 /**
71 * This method is used to perform necessary actions before sending the default mail.
72 *
73 * This method removes the filter for the forge12 spam captcha if the class
74 * '\forge12\contactform7\CF7Captcha\TimerValidatorCF7' exists. It removes the filters 'wpcf7_spam' for the methods
75 * '\forge12\contactform7\CF7Captcha::isSpam' and
76 * '\forge12\contactform7\CF7Captcha\CF7IPLog::isSpam', and also removes the action 'wpcf7_mail_sent' for the
77 * method
78 * '\forge12\contactform7\CF7Captcha\CF7IPLog::doLogIP', if these filters and action exist.
79 *
80 * Additionally, this method removes the filter 'wpcf7_spam' for the method 'wpcf7_recaptcha_verify_response' with
81 * a priority of 9.
82 *
83 * @return void
84 */
85 public function beforeSendDefaultMail() {
86 // Remove the filter for the forge12 spam captcha
87 if ( class_exists( '\forge12\contactform7\CF7Captcha\TimerValidatorCF7' ) ) {
88 remove_filter( 'wpcf7_spam', '\forge12\contactform7\CF7Captcha::isSpam' );
89 remove_filter( 'wpcf7_spam', '\forge12\contactform7\CF7Captcha\CF7IPLog::isSpam' );
90 remove_action( 'wpcf7_mail_sent', '\forge12\contactform7\CF7Captcha\CF7IPLog::doLogIP' );
91 }
92
93 // Remove the filter for the google repatcha validation
94 remove_filter( 'wpcf7_spam', 'wpcf7_recaptcha_verify_response', 9 );
95 }
96
97 /**
98 * Performs actions after sending the default mail.
99 *
100 * In this method, two filters and an action are added to the WordPress hooks system.
101 *
102 * The first filter is added with the hook name 'wpcf7_spam' and the callback
103 * function is set to 'wpcf7_recaptcha_verify_response'. The priority is set to 9
104 * and the number of accepted arguments for the callback function is 2.
105 * This filter is added only if the function 'wpcf7_recaptcha_verifiy_response'
106 * exists.
107 *
108 * The second filter is added with the hook name 'wpcf7_spam' and the callback
109 * function is set to '\forge12\contactform7\CF7Captcha\TimerValidatorCF7::isSpam'.
110 * The priority is set to 100 and the number of accepted arguments for the callback
111 * function is 2. This filter is added only if the class '\forge12\contactform7\CF7Captcha\TimerValidatorCF7'
112 * exists.
113 *
114 * The third filter is added with the hook name 'wpcf7_spam' and the callback
115 * function is set to '\forge12\contactform7\CF7Captcha\CF7IPLog::isSpam'.
116 * The priority is set to 100 and the number of accepted arguments for the callback
117 * function is 2. This filter is added only if the class '\forge12\contactform7\CF7Captcha\CF7IPLog'
118 * exists.
119 *
120 * An action is added with the hook name 'wpcf7_mail_sent' and the callback function
121 * is set to '\forge12\contactform7\CF7Captcha\CF7IPLog::doLogIP'. The priority is set
122 * to 100 and the number of accepted arguments for the callback function is 1.
123 * This action is added only if the class '\forge12\contactform7\CF7Captcha\CF7IPLog' exists.
124 *
125 * @return void
126 */
127 public function afterSendDefaultMail() {
128 // re add the filter to ensure for all other forms the recaptcha is used
129 if ( function_exists( 'wpcf7_recaptcha_verifiy_response' ) ) {
130 add_filter( 'wpcf7_spam', 'wpcf7_recaptcha_verify_response', 9, 2 );
131 }
132
133 // re add the filter for the forge12 spam captcha
134 if ( class_exists( '\forge12\contactform7\CF7Captcha\TimerValidatorCF7' ) ) {
135 add_filter( 'wpcf7_spam', '\forge12\contactform7\CF7Captcha\TimerValidatorCF7::isSpam', 100, 2 );
136 add_filter( 'wpcf7_spam', '\forge12\contactform7\CF7Captcha\CF7IPLog::isSpam', 100, 2 );
137 add_action( 'wpcf7_mail_sent', '\forge12\contactform7\CF7Captcha\CF7IPLog::doLogIP', 100, 1 );
138 }
139 }
140
141 /**
142 * Updates the opt-in status by hash.
143 *
144 * @param string $hash The opt-in hash.
145 * @param int $value The opt-in value to set.
146 * @param OptIn|null $OptIn The opt-in object. Optional.
147 *
148 * @return int Returns 0 if the opt-in is already confirmed, 1 if the opt-in is successfully updated.
149 */
150 protected function updateOptInByHash( string $hash, int $value, ?OptIn $OptIn = null ): int {
151 /**
152 * Init the optIn is not defined yet.
153 */
154 $OptIn = $OptIn ?? OptIn::get_by_hash( $hash );
155
156 /**
157 * If the OptIn is already confirmed - we skip the update.
158 */
159 if ( $OptIn->is_confirmed() ) {
160 do_action( 'f12_cf7_doubleoptin_already_confirmed', $hash, $OptIn );
161
162 return 0;
163 }
164
165 /**
166 * Hook
167 */
168 do_action( 'f12_cf7_doubleoptin_before_confirm', $hash, $OptIn );
169
170 $OptIn->set_doubleoptin( $value );
171 $OptIn->set_updatetime( time() );
172 $OptIn->set_ipaddr_confirmation( IPHelper::getIPAdress() );
173
174 $result = $OptIn->save();
175
176 if ( $result ) {
177 do_action( 'f12_cf7_doubleoptin_after_confirm', $hash, $OptIn );
178 }
179
180 return (int) $result;
181 }
182
183 /**
184 * Add additional placeholder like time, date, subject
185 *
186 * @formatter:off
187 *
188 * @param string $body The content containing the placeholder that will be replaced.
189 * @param OptIn $OptIn The OptIn Object.
190 *
191 * @param array $parameter {
192 * @type string $formUrl The URL where the form is displayed.
193 * @type string $subject The Subject of the Form
194 * }
195 * #
196 * @formatter:on
197 */
198 protected function addPlaceholders( string $body, OptIn $OptIn, array $parameter ): string {
199 # set the default timezone
200 $timezone = get_option( 'timezone_string' );
201
202 # set fallback timezone
203 if ( empty( $timezone ) ) {
204 $timezone = 'Europe/Berlin';
205 }
206
207 date_default_timezone_set( $timezone );
208 $placeholder = array(
209 'doubleoptin_form_url' => $parameter['formUrl'],
210 'doubleoptin_form_subject' => $parameter['subject'],
211 'doubleoptin_form_date' => date( get_option( 'date_format' ) ),
212 'doubleoptin_form_time' => date( get_option( 'time_format' ) ),
213 'doubleoptin_form_email' => get_option( 'admin_email' ),
214 'doubleoptinlink' => $OptIn->get_link_optin( $parameter ),
215 'doubleoptoutlink' => $OptIn->get_link_optout()
216 );
217
218 foreach ( $placeholder as $key => $value ) {
219 $body = str_replace( '[' . $key . ']', $value, $body );
220 }
221
222 return $body;
223 }
224
225 /**
226 * Add Stylesheets
227 */
228 public function validateOptIn(): bool {
229 /**
230 * Skip if the hash has not been submitted.
231 */
232 if ( ! isset( $_GET['optin'] ) ) {
233 return false;
234 }
235
236 /**
237 * Get the Hash
238 */
239 $hash = sanitize_text_field( $_GET['optin'] );
240
241 /**
242 * Load the OptIn
243 */
244 $OptIn = OptIn::get_by_hash( $hash );
245
246 /**
247 * Skip if the OptIn does not exist
248 */
249 if ( null == $OptIn ) {
250 return false;
251 }
252
253 /**
254 * Skip if the OptIn is not from Type cf7.
255 */
256 if ( ! $OptIn->isType( $this->type ) ) {
257 return false;
258 }
259
260 /**
261 * Skip if the OptIn has been confirmed already or could not be updated.
262 */
263 if ( $this->updateOptInByHash( $hash, 1 ) <= 0 ) {
264 return false;
265 }
266
267 /**
268 * Enable / Disable default mail.
269 *
270 * Filter to allow developer to enable / disable the default mail.
271 *
272 * @param bool $status Enable (true) or disable (false) the default mail.
273 * @param int $postId The ID of the Post / Form.
274 *
275 * @since 2.3.3
276 */
277 if ( ! apply_filters( 'f12_cf7_doubleoptin_send_default_mail', true, $OptIn->get_cf_form_id() ) ) {
278 return false;
279 }
280
281 /**
282 * Hook triggers before the default mail will be send.
283 *
284 * Action to allow developers to do custom actions before the default mail will be triggered.
285 *
286 * @param OptIn $OptIn
287 *
288 * @since 2.3.3
289 */
290 do_action( 'f12_cf7_doubleoptin_before_send_default_mail', $OptIn );
291
292 /**
293 * Hook triggers the default mail
294 *
295 * Action to allow developers to do custom actions before the default mail will be triggered.
296 *
297 * @param OptIn $OptIn
298 *
299 * @since 2.3.3
300 */
301 do_action( 'f12_cf7_doubleoptin_trigger_default_mail', $OptIn );
302
303 /**
304 * Hook triggers after the default mail has been sent
305 *
306 * Action to allow developers to do custom actions after the default mail has been triggered.
307 *
308 * @param OptIn $OptIn
309 *
310 * @since 2.3.3
311 */
312 do_action( 'f12_cf7_doubleoptin_after_send_default_mail', $OptIn );
313
314 return true;
315 }
316
317
318 /**
319 * Store the files
320 *
321 * @param array $inFiles
322 *
323 * @return array
324 */
325 private function maybeStoreFiles( array $inFiles ): array {
326 $outFiles = array();
327
328 if ( empty( $files ) ) {
329 return $outFiles;
330 }
331
332 foreach ( $files as $key => $subfiles ) {
333 foreach ( $subfiles as $file ) {
334 $newFile = $this->copyAndRenameFile( $file );
335 if ( $newFile ) {
336 $outFiles[] = $newFile;
337 }
338 }
339 }
340
341 return $outFiles;
342 }
343
344 /**
345 * Copy and rename a file
346 *
347 * @param string $file The path to the file to copy and rename
348 *
349 * @return string|null The path to the copied and renamed file, or null if the copy operation failed
350 */
351 private function copyAndRenameFile( string $file ): ?string {
352 $newFile = explode( '/', $file );
353 $name = $newFile[ count( $newFile ) - 1 ];
354 $name = time() . '_' . $name;
355 $newFile[ count( $newFile ) - 1 ] = $name;
356 $newFile = implode( "/", $newFile );
357 if ( copy( $file, $newFile ) ) {
358 return $newFile;
359 }
360
361 return null;
362 }
363
364 /**
365 * Create the OptIn
366 *
367 * @param int $formId The identifier of the form
368 * @param string $formHtml The HTML code of the form
369 * @param array $parameter The Post Parameter of the form.
370 * @param array $files The Files attached to the form.
371 *
372 * @return OptIn|null
373 */
374 protected function maybeCreateOptIn( int $formId, string $formHtml, array $parameter, array $files = array() ): ?OptIn {
375 /**
376 * Maybe copy the files to store them while waiting for the optin confirmation
377 */
378 $files = $this->maybeStoreFiles( $files );
379
380 /**
381 * Filter to manipulate the content parameter before storing them in the database
382 *
383 * @param array $parameter
384 *
385 * @since 2.3.3
386 */
387 $parameter = \apply_filters( 'f12_cf7_doubleoptin_add_request_parameter', $parameter );
388
389 /**
390 * Get the global settings for the formular.
391 */
392 $formParameter = CF7DoubleOptIn::getInstance()->getParameter( $formId );
393
394 /**
395 * Filter to fetch the recipient before creating the optin object.
396 *
397 * @param string $recipient The Default recipient
398 * @param array $formParameter @see CF7DoubleOptIn::getParameter() for details
399 * @param array $parameter The Post Parameter submitted by the visitor.
400 *
401 * @since 2.3.3
402 */
403 $recipient = \apply_filters( 'f12_cf7_doubleoptin_get_recipient_' . $this->type, '', $formParameter, $parameter );
404
405 /**
406 * Skip if no Mail has been found.
407 */
408 if ( empty( $recipient ) ) {
409 return null;
410 }
411
412 /**
413 * Set the Properties of the OptIn Object
414 */
415 $properties = array(
416 'cf_form_id' => $formId,
417 'doubleoptin' => 0,
418 'createtime' => time(),
419 'content' => maybe_serialize( $parameter ),
420 'files' => maybe_serialize( $files ),
421 'ipaddr_register' => IPHelper::getIPAdress(),
422 'category' => (int) $formParameter['category'],
423 'form' => $formHtml,
424 'email' => $recipient
425 );
426
427 $OptIn = new OptIn( $properties );
428
429 if ( $OptIn->save() ) {
430 return $OptIn;
431 }
432
433 return null;
434 }
435
436 /**
437 * Validate if the optin is enabled.
438 */
439 protected function isOptinEnabled( int $formId ): bool {
440 // Disable optin sending if the optin flag is set.
441 if ( isset( $_GET['optin'] ) ) {
442 return false;
443 }
444
445 $parameter = CF7DoubleOptIn::getInstance()->getParameter( $formId );
446
447 if ( (int) $parameter['enable'] != 1 ) {
448 return false;
449 }
450
451 // Check the custom condition
452 if ( isset( $parameter['conditions'] ) ) {
453 $condition = sanitize_text_field( $parameter['conditions'] );
454
455 if ( ( $condition != 'disable' && $condition !== 'disabled' ) && ( ! isset( $_POST[ $condition ] ) || empty( $_POST[ $condition ] ) ) ) {
456 return false;
457 }
458 }
459
460 return true;
461 }
462
463 /**
464 * Removes files associated with the optin parameter.
465 *
466 * This method checks if the optin parameter is set and
467 * loads the OptIn object based on the hash value. If
468 * the OptIn does not exist or no files are found,
469 * the method will return. Otherwise, it will iterate
470 * through the files and delete each one.
471 *
472 * @return void
473 */
474 public function removeFiles(): void {
475 /**
476 * Skip if the optin parameter is not set.
477 */
478 if ( ! isset( $_GET['optin'] ) ) {
479 return;
480 }
481
482 $hash = esc_sql( $_GET['optin'] );
483
484 /**
485 * Load the OptIn
486 */
487 $OptIn = OptIn::get_by_hash( $hash );
488
489 /**
490 * Skip if the OptIn does not exist
491 */
492 if ( null == $OptIn ) {
493 return;
494 }
495
496 /**
497 * Load all files
498 */
499 $files = maybe_unserialize( $OptIn->get_files() );
500
501 /**
502 * Skip if no files found
503 */
504 if ( empty( $files ) ) {
505 return;
506 }
507
508 foreach ( $files as $file ) {
509 /**
510 * Skip if empty
511 */
512 if ( ! empty( $file ) ) {
513 continue;
514 }
515
516 /**
517 * Skip if no file found
518 */
519 if ( ! is_file( $file ) ) {
520 continue;
521 }
522
523 /**
524 * Delete the file
525 */
526 if ( ! unlink( $file ) ) {
527 error_log( "Could not delete file " . $file . "!" );
528 }
529 }
530 }
531
532 }