PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.5.0
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.5.0
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
← All changes | core/Ajax.class.php +243 -161 2.125.5.0 View file →
@@ -1,204 +1,286 @@
1 1 <?php
2 2
3 3 namespace forge12\contactform7\CF7DoubleOptIn {
4 - if (!defined('ABSPATH')) {
5 - exit;
6 - }
7 4
8 - /**
9 - * Class Ajax
10 - * Responsible to handle the admin settings for the double opt-in field
11 - *
12 - * @package forge12\contactform7\CF7OptIn
13 - */
14 - class Ajax
15 - {
16 - /**
17 - * Admin constructor.
18 - */
19 - public function __construct()
20 - {
21 - add_action('wp_ajax_f12_doi_details', array($this, 'getDetails'));
22 - add_action('wp_ajax_f12_doi_templateloader', array($this, 'getTemplate'));
23 - }
5 + use Forge12\Shared\Logger;
6 + use Forge12\Shared\LoggerInterface;
24 7
25 - /**
26 - * Load and get the template we need.
27 - */
28 - public function getTemplate()
29 - {
30 - $content = '';
31 - if (isset($_POST['template']) && wp_verify_nonce($_POST['nonce'], 'f12_doi_templateloader')) {
32 - $template_path = plugin_dir_path(dirname(__FILE__)) . 'mails/' . esc_attr(sanitize_text_field($_POST['template'])) . '.html';
8 + if ( ! defined( 'ABSPATH' ) ) {
9 + exit;
10 + }
33 11
34 - if (file_exists($template_path)) {
35 - $content = file_get_contents($template_path);
36 - }
12 + /**
13 + * Class Ajax
14 + * Responsible to handle the admin settings for the double opt-in field
15 + *
16 + * @package forge12\contactform7\CF7OptIn
17 + */
18 + class Ajax {
19 + private LoggerInterface $logger;
37 20
38 - }
39 - echo wp_json_encode(['status' => 200, 'content' => $content]);
40 - wp_die();
41 - }
21 + /**
22 + * Admin constructor.
23 + */
24 + public function __construct( LoggerInterface $logger ) {
25 + $this->logger = $logger;
42 26
43 - /**
44 - * Return the Popup for the HASH DOI
45 - */
46 - public function getDetails()
47 - {
48 - if (isset($_POST['hash']) && wp_verify_nonce($_POST['nonce'], 'f12_doi_details')) {
49 - global $wpdb;
50 - $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin';
51 - $hash = sanitize_text_field($_POST['hash']);
27 + $this->get_logger()->debug( 'Initializing AJAX handlers', [
28 + 'plugin' => 'double-opt-in',
29 + 'class' => __CLASS__,
30 + 'method' => __METHOD__,
31 + ] );
52 32
53 - $OptIn = OptIn::get_by_hash($hash);
33 + add_action( 'wp_ajax_f12_doi_details', [ $this, 'getDetails' ] );
34 + add_action( 'wp_ajax_f12_doi_templateloader', [ $this, 'getTemplate' ] );
54 35
55 - if (null == $OptIn) {
56 - ob_start();
57 - ?>
58 - <h2><?php _e('Ooops!', 'double-opt-in'); ?></h2>
36 + $this->get_logger()->info( 'AJAX handlers registered', [
37 + 'plugin' => 'double-opt-in',
38 + 'class' => __CLASS__,
39 + 'method' => __METHOD__,
40 + ] );
41 + }
42 +
43 +
44 + public function get_logger() {
45 + return $this->logger;
46 + }
47 +
48 + /**
49 + * Load and get the template we need.
50 + */
51 + public function getTemplate() {
52 + // A nonce proves intent, not authorization — gate on capability too.
53 + if ( ! current_user_can( 'manage_options' ) ) {
54 + wp_die( -1, 403 );
55 + }
56 + $this->get_logger()->debug( 'getTemplate called', [
57 + 'plugin' => 'double-opt-in',
58 + 'class' => __CLASS__,
59 + 'method' => __METHOD__,
60 + 'post' => $_POST,
61 + ] );
62 +
63 + $content = '';
64 + if ( isset( $_POST['template'] ) && wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'f12_doi_templateloader' ) ) {
65 + // Use sanitize_file_name() to prevent path traversal attacks
66 + $template_name = sanitize_file_name( wp_unslash( $_POST['template'] ) );
67 + $mails_dir = plugin_dir_path( dirname( __FILE__ ) ) . 'mails/';
68 + $template_path = $mails_dir . $template_name . '.html';
69 +
70 + // Verify the resolved path is within the allowed directory (prevent path traversal)
71 + $real_template_path = realpath( $template_path );
72 + $real_mails_dir = realpath( $mails_dir );
73 +
74 + if ( $real_template_path && $real_mails_dir && strpos( $real_template_path, $real_mails_dir ) === 0 && file_exists( $real_template_path ) ) {
75 + $this->get_logger()->debug( 'Template file found, loading content', [
76 + 'plugin' => 'double-opt-in',
77 + 'class' => __CLASS__,
78 + 'method' => __METHOD__,
79 + 'template_path' => $template_path,
80 + ] );
81 + $content = file_get_contents( $real_template_path );
82 + } else {
83 + $this->get_logger()->warning( 'Template file not found', [
84 + 'plugin' => 'double-opt-in',
85 + 'class' => __CLASS__,
86 + 'method' => __METHOD__,
87 + 'template_path' => $template_path,
88 + ] );
89 + }
90 + } else {
91 + $this->get_logger()->warning( 'Invalid or missing nonce/template parameter', [
92 + 'plugin' => 'double-opt-in',
93 + 'class' => __CLASS__,
94 + 'method' => __METHOD__,
95 + 'post' => $_POST,
96 + ] );
97 + }
98 +
99 + echo wp_json_encode( [ 'status' => 200, 'content' => $content ] );
100 + wp_die();
101 + }
102 +
103 +
104 + /**
105 + * Return the Popup for the HASH DOI
106 + */
107 + public function getDetails() {
108 + // Exposes opt-in PII (email, IPs, all form fields). A nonce proves
109 + // intent, not authorization — require the capability explicitly.
110 + if ( ! current_user_can( 'manage_options' ) ) {
111 + wp_die( -1, 403 );
112 + }
113 + $this->get_logger()->debug( 'getDetails called', [
114 + 'plugin' => 'double-opt-in',
115 + 'class' => __CLASS__,
116 + 'method' => __METHOD__,
117 + 'post' => $_POST,
118 + ] );
119 +
120 + if ( isset( $_POST['hash'] ) && wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'f12_doi_details' ) ) {
121 + global $wpdb;
122 + $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin';
123 + $hash = sanitize_text_field( $_POST['hash'] );
124 +
125 + $this->get_logger()->debug( 'Looking up OptIn by hash', [
126 + 'plugin' => 'double-opt-in',
127 + 'class' => __CLASS__,
128 + 'method' => __METHOD__,
129 + 'hash' => $hash,
130 + ] );
131 +
132 + $OptIn = OptIn::get_by_hash( $hash );
133 +
134 + if ( null == $OptIn ) {
135 + $this->get_logger()->warning( 'OptIn not found for hash', [
136 + 'plugin' => 'double-opt-in',
137 + 'class' => __CLASS__,
138 + 'method' => __METHOD__,
139 + 'hash' => $hash,
140 + ] );
141 +
142 + ob_start();
143 + ?>
144 + <h2><?php _e( 'Ooops!', 'double-opt-in' ); ?></h2>
59 145 <p>
60 - <?php _e('Something went wrong. The given DOI wasn\'t found. Maybe it got removed?', 'double-opt-in'); ?>
146 + <?php _e( 'Something went wrong. The given DOI wasn\'t found. Maybe it got removed?', 'double-opt-in' ); ?>
61 147 </p>
62 - <?php
63 - $content = ob_get_contents();
64 - ob_end_clean();
65 - echo wp_json_encode(['status' => 200, 'content' => $content]);
66 - wp_die();
67 - }
148 + <?php
149 + $content = ob_get_contents();
150 + ob_end_clean();
151 + echo wp_json_encode( [ 'status' => 200, 'content' => $content ] );
152 + wp_die();
153 + }
68 154
69 - $formfields = maybe_unserialize($OptIn->get_content());
70 - ob_start();
71 - ?>
72 - <h2><?php echo esc_html($OptIn->get_hash()); ?></h2>
73 - <?php if (current_user_can('manage_options')): ?>
155 + $this->get_logger()->info( 'OptIn found', [
156 + 'plugin' => 'double-opt-in',
157 + 'class' => __CLASS__,
158 + 'method' => __METHOD__,
159 + 'optin' => [
160 + 'id' => $OptIn->get_id(),
161 + 'hash' => $OptIn->get_hash(),
162 + 'email' => $OptIn->get_email(),
163 + ],
164 + ] );
165 +
166 + $formfields = maybe_unserialize( $OptIn->get_content() );
167 + // Handle nested content structure (e.g., Avada stores {data: {...}, field_labels: {...}, ...})
168 + if ( is_array( $formfields ) && isset( $formfields['data'] ) && is_array( $formfields['data'] ) && ! isset( $formfields['fields'] ) ) {
169 + $formfields = $formfields['data'];
170 + }
171 + ob_start();
172 + ?>
173 + <h2><?php echo esc_html( $OptIn->get_hash() ); ?></h2>
174 + <?php if ( current_user_can( 'manage_options' ) ): ?>
74 175 <div class="options">
75 - <a href="<?php echo esc_url($OptIn->get_link_delete()); ?>"><?php _e('Delete DOI', 'double-opt-in'); ?></a> |
76 - <a href="<?php echo esc_url($OptIn->get_link_ui()); ?>"><?php _e('Details', 'double-opt-in'); ?></a>
176 + <?php
177 + do_action( 'f12_cf7_doubleoptin_ui_view_optin_options', $OptIn );
178 + ?>
179 + <a class="button"
180 + href="<?php echo esc_url( $OptIn->get_link_delete() ); ?>"><?php _e( 'Delete DOI', 'double-opt-in' ); ?></a>
181 + <a class="button"
182 + href="<?php echo esc_url( $OptIn->get_link_ui() ); ?>"><?php _e( 'Details', 'double-opt-in' ); ?></a>
77 183 </div>
78 - <?php endif; ?>
184 + <?php endif; ?>
79 185 <table>
80 186 <tr>
81 - <td><?php _e('Key', 'double-opt-in'); ?></td>
82 - <td><?php _e('Value', 'double-opt-in'); ?></td>
187 + <td><?php _e( 'Key', 'double-opt-in' ); ?></td>
188 + <td><?php _e( 'Value', 'double-opt-in' ); ?></td>
83 189 </tr>
84 190 <tr>
85 - <td>
86 - <?php _e('ID', 'f12-cf7-doupleoptin'); ?>
87 - </td>
88 - <td>
89 - <?php echo esc_html($OptIn->get_id()); ?>
90 - </td>
191 + <td><?php _e( 'ID', 'double-opt-in' ); ?></td>
192 + <td><?php echo esc_html( $OptIn->get_id() ); ?></td>
91 193 </tr>
92 194 <tr>
93 - <td>
94 - <?php _e('CF7 Form ID', 'double-opt-in'); ?>
95 - </td>
96 - <td>
97 - <?php echo esc_html($OptIn->get_cf_form_id()); ?>
98 - </td>
195 + <td><?php _e( 'CF7 Form ID', 'double-opt-in' ); ?></td>
196 + <td><?php echo esc_html( $OptIn->get_cf_form_id() ); ?></td>
99 197 </tr>
100 198 <tr>
101 - <td>
102 - <?php _e('Registration Date', 'double-opt-in'); ?>
103 - </td>
104 - <td>
105 - <?php
106 - echo esc_html($OptIn->get_createtime('formatted'));
107 - ?>
108 - </td>
199 + <td><?php _e( 'Registration Date', 'double-opt-in' ); ?></td>
200 + <td><?php echo esc_html( $OptIn->get_createtime( 'formatted' ) ); ?></td>
109 201 </tr>
110 202 <tr>
111 - <td>
112 - <?php _e('Registration IP', 'double-opt-in'); ?>
113 - </td>
114 - <td>
115 - <?php echo esc_html($OptIn->get_ipaddr_register()); ?>
116 - </td>
203 + <td><?php _e( 'Registration IP', 'double-opt-in' ); ?></td>
204 + <td><?php echo esc_html( $OptIn->get_ipaddr_register() ); ?></td>
117 205 </tr>
118 206 <tr>
119 - <td>
120 - <?php _e('Confirmation Date', 'double-opt-in'); ?>
121 - </td>
122 - <td>
123 - <?php
124 - if ($OptIn->is_confirmed()) {
125 - echo esc_html($OptIn->get_updatetime('formatted'));
126 - }
127 - ?>
128 - </td>
207 + <td><?php _e( 'Confirmation Date', 'double-opt-in' ); ?></td>
208 + <td><?php if ( $OptIn->is_confirmed() ) {
209 + echo esc_html( $OptIn->get_updatetime( 'formatted' ) );
210 + } ?></td>
129 211 </tr>
130 212 <tr>
131 - <td>
132 - <?php _e('Confirmation IP', 'double-opt-in'); ?>
133 - </td>
134 - <td>
135 - <?php
136 - echo esc_html($OptIn->get_ipaddr_confirmation());
137 - ?>
138 - </td>
213 + <td><?php _e( 'Confirmation IP', 'double-opt-in' ); ?></td>
214 + <td><?php echo esc_html( $OptIn->get_ipaddr_confirmation() ); ?></td>
139 215 </tr>
140 216 </table>
141 217
142 - <h3><?php _e('Form Fields', 'double-opt-in'); ?></h3>
218 + <h3><?php _e( 'Form Fields', 'double-opt-in' ); ?></h3>
143 219 <table>
144 220 <tr>
145 - <td><?php _e('Key', 'double-opt-in'); ?></td>
146 - <td><?php _e('Value', 'double-opt-in'); ?></td>
221 + <td><?php _e( 'Key', 'double-opt-in' ); ?></td>
222 + <td><?php _e( 'Value', 'double-opt-in' ); ?></td>
147 223 </tr>
148 - <?php if (isset($formfields['fields'])): ?>
149 - <?php foreach ($formfields['fields'] as $key => $value): ?>
224 + <?php if ( isset( $formfields['fields'] ) ): ?>
225 + <?php foreach ( $formfields['fields'] as $key => $value ): ?>
150 226 <tr>
151 - <td>
152 - <?php esc_attr_e($key); ?>
153 - </td>
154 - <td>
155 - <?php if (is_array($value)) {
156 - echo esc_html(implode(',', $value));
157 - } else {
158 - echo esc_html($value);
159 - }
160 - ?>
161 - </td>
227 + <td><?php esc_attr_e( $key ); ?></td>
228 + <td><?php echo is_array( $value ) ? esc_html( implode( ',', $value ) ) : esc_html( $value ); ?></td>
162 229 </tr>
163 - <?php endforeach; ?>
164 - <?php else: ?>
165 - <?php foreach ($formfields as $key => $value): ?>
230 + <?php endforeach; ?>
231 + <?php else: ?>
232 + <?php foreach ( $formfields as $key => $value ): ?>
166 233 <tr>
167 - <td>
168 - <?php esc_attr_e($key); ?>
169 - </td>
170 - <td>
171 - <?php if (is_array($value)) {
172 - echo esc_html(implode(',', $value));
173 - } else {
174 - echo esc_html($value);
175 - }
176 - ?>
177 - </td>
234 + <td><?php esc_attr_e( $key ); ?></td>
235 + <td><?php echo is_array( $value ) ? esc_html( implode( ',', $value ) ) : esc_html( $value ); ?></td>
178 236 </tr>
179 - <?php endforeach; ?>
180 - <?php endif; ?>
237 + <?php endforeach; ?>
238 + <?php endif; ?>
181 239 </table>
182 - <?php
183 - $content = ob_get_contents();
184 - ob_end_clean();
185 - echo wp_json_encode(['status' => 200, 'content' => $content]);
186 - exit;
187 - }
188 - wp_die(0);
189 - }
240 + <?php
241 + $content = ob_get_contents();
242 + ob_end_clean();
243 + echo wp_json_encode( [ 'status' => 200, 'content' => $content ] );
244 + exit;
245 + }
190 246
191 - /**
192 - * Add the styles for the form
193 - */
194 - public function addStyles($hook)
195 - {
196 - if ($hook == 'tools_page_f12doubleoptin') {
197 - wp_enqueue_style('f12-cf7-doubleoptin-admin', plugins_url('assets/admin-style.css', __FILE__));
198 - wp_enqueue_script('f12-cf7-doubleoptin-admin', plugins_url('assets/f12-cf7-popup.js', __FILE__), array('jquery'));
199 - }
200 - }
201 - }
247 + $this->get_logger()->warning( 'Invalid request for getDetails (missing or invalid nonce/hash)', [
248 + 'plugin' => 'double-opt-in',
249 + 'class' => __CLASS__,
250 + 'method' => __METHOD__,
251 + 'post' => $_POST,
252 + ] );
202 253
203 - new Ajax();
254 + wp_die( 0 );
255 + }
256 +
257 +
258 + /**
259 + * Add the styles for the form
260 + */
261 + public function addStyles( $hook ) {
262 + $this->get_logger()->debug( 'addStyles called', [
263 + 'plugin' => 'double-opt-in',
264 + 'class' => __CLASS__,
265 + 'method' => __METHOD__,
266 + 'hook' => $hook,
267 + ] );
268 +
269 + if ( $hook == 'tools_page_f12doubleoptin' ) {
270 + $ver = defined( 'FORGE12_OPTIN_VERSION' ) ? FORGE12_OPTIN_VERSION : false;
271 + wp_enqueue_style( 'f12-cf7-doubleoptin-admin', plugins_url( 'assets/admin-style.css', __FILE__ ), array(), $ver );
272 + wp_enqueue_script( 'f12-cf7-doubleoptin-admin', plugins_url( 'assets/f12-cf7-popup.js', __FILE__ ), [ 'jquery' ], $ver, true );
273 +
274 + $this->get_logger()->info( 'Admin styles and scripts enqueued for DOI tools page', [
275 + 'plugin' => 'double-opt-in',
276 + 'class' => __CLASS__,
277 + 'method' => __METHOD__,
278 + 'hook' => $hook,
279 + ] );
280 + }
281 + }
282 +
283 + }
284 +
285 + new Ajax( Logger::getInstance() );
204 286 }