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.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
← All changes | core/Ajax.class.php +159 -86 3.0.3 → 5.5.0 View file →
@@ -1,7 +1,11 @@
1 1 <?php
2 2
3 3 namespace forge12\contactform7\CF7DoubleOptIn {
4 +
5 + use Forge12\Shared\Logger;
6 + use Forge12\Shared\LoggerInterface;
7 +
4 8 if ( ! defined( 'ABSPATH' ) ) {
5 9 exit;
6 10 }
7 11
@@ -11,45 +15,131 @@
11 15 *
12 16 * @package forge12\contactform7\CF7OptIn
13 17 */
14 18 class Ajax {
19 + private LoggerInterface $logger;
20 +
15 21 /**
16 22 * Admin constructor.
17 23 */
18 - public function __construct() {
19 - add_action( 'wp_ajax_f12_doi_details', array( $this, 'getDetails' ) );
20 - add_action( 'wp_ajax_f12_doi_templateloader', array( $this, 'getTemplate' ) );
24 + public function __construct( LoggerInterface $logger ) {
25 + $this->logger = $logger;
26 +
27 + $this->get_logger()->debug( 'Initializing AJAX handlers', [
28 + 'plugin' => 'double-opt-in',
29 + 'class' => __CLASS__,
30 + 'method' => __METHOD__,
31 + ] );
32 +
33 + add_action( 'wp_ajax_f12_doi_details', [ $this, 'getDetails' ] );
34 + add_action( 'wp_ajax_f12_doi_templateloader', [ $this, 'getTemplate' ] );
35 +
36 + $this->get_logger()->info( 'AJAX handlers registered', [
37 + 'plugin' => 'double-opt-in',
38 + 'class' => __CLASS__,
39 + 'method' => __METHOD__,
40 + ] );
21 41 }
22 42
43 +
44 + public function get_logger() {
45 + return $this->logger;
46 + }
47 +
23 48 /**
24 49 * Load and get the template we need.
25 50 */
26 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 +
27 63 $content = '';
28 - if ( isset( $_POST['template'] ) && wp_verify_nonce( $_POST['nonce'], 'f12_doi_templateloader' ) ) {
29 - $template_path = plugin_dir_path( dirname( __FILE__ ) ) . 'mails/' . esc_attr( sanitize_text_field( $_POST['template'] ) ) . '.html';
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';
30 69
31 - if ( file_exists( $template_path ) ) {
32 - $content = file_get_contents( $template_path );
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 + ] );
33 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 + }
34 98
35 - }
36 99 echo wp_json_encode( [ 'status' => 200, 'content' => $content ] );
37 100 wp_die();
38 101 }
39 102
103 +
40 104 /**
41 105 * Return the Popup for the HASH DOI
42 106 */
43 107 public function getDetails() {
44 - if ( isset( $_POST['hash'] ) && wp_verify_nonce( $_POST['nonce'], 'f12_doi_details' ) ) {
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' ) ) {
45 121 global $wpdb;
46 122 $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin';
47 123 $hash = sanitize_text_field( $_POST['hash'] );
48 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 +
49 132 $OptIn = OptIn::get_by_hash( $hash );
50 133
51 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 +
52 142 ob_start();
53 143 ?>
54 144 <h2><?php _e( 'Ooops!', 'double-opt-in' ); ?></h2>
55 145 <p>
@@ -61,9 +151,24 @@
61 151 echo wp_json_encode( [ 'status' => 200, 'content' => $content ] );
62 152 wp_die();
63 153 }
64 154
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 +
65 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 + }
66 171 ob_start();
67 172 ?>
68 173 <h2><?php echo esc_html( $OptIn->get_hash() ); ?></h2>
69 174 <?php if ( current_user_can( 'manage_options' ) ): ?>
@@ -68,17 +173,8 @@
68 173 <h2><?php echo esc_html( $OptIn->get_hash() ); ?></h2>
69 174 <?php if ( current_user_can( 'manage_options' ) ): ?>
70 175 <div class="options">
71 176 <?php
72 - /**
73 - * Opt-in View Options
74 - *
75 - * This action is used to extend the options for the pro version.
76 - *
77 - * @param \forge12\contactform7\CF7DoubleOptIn\OptIn $OptIn
78 - *
79 - * @since 3.0.0
80 - */
81 177 do_action( 'f12_cf7_doubleoptin_ui_view_optin_options', $OptIn );
82 178 ?>
83 179 <a class="button"
84 180 href="<?php echo esc_url( $OptIn->get_link_delete() ); ?>"><?php _e( 'Delete DOI', 'double-opt-in' ); ?></a>
@@ -91,62 +187,32 @@
91 187 <td><?php _e( 'Key', 'double-opt-in' ); ?></td>
92 188 <td><?php _e( 'Value', 'double-opt-in' ); ?></td>
93 189 </tr>
94 190 <tr>
95 - <td>
96 - <?php _e( 'ID', 'f12-cf7-doupleoptin' ); ?>
97 - </td>
98 - <td>
99 - <?php echo esc_html( $OptIn->get_id() ); ?>
100 - </td>
191 + <td><?php _e( 'ID', 'double-opt-in' ); ?></td>
192 + <td><?php echo esc_html( $OptIn->get_id() ); ?></td>
101 193 </tr>
102 194 <tr>
103 - <td>
104 - <?php _e( 'CF7 Form ID', 'double-opt-in' ); ?>
105 - </td>
106 - <td>
107 - <?php echo esc_html( $OptIn->get_cf_form_id() ); ?>
108 - </td>
195 + <td><?php _e( 'CF7 Form ID', 'double-opt-in' ); ?></td>
196 + <td><?php echo esc_html( $OptIn->get_cf_form_id() ); ?></td>
109 197 </tr>
110 198 <tr>
111 - <td>
112 - <?php _e( 'Registration Date', 'double-opt-in' ); ?>
113 - </td>
114 - <td>
115 - <?php
116 - echo esc_html( $OptIn->get_createtime( 'formatted' ) );
117 - ?>
118 - </td>
199 + <td><?php _e( 'Registration Date', 'double-opt-in' ); ?></td>
200 + <td><?php echo esc_html( $OptIn->get_createtime( 'formatted' ) ); ?></td>
119 201 </tr>
120 202 <tr>
121 - <td>
122 - <?php _e( 'Registration IP', 'double-opt-in' ); ?>
123 - </td>
124 - <td>
125 - <?php echo esc_html( $OptIn->get_ipaddr_register() ); ?>
126 - </td>
203 + <td><?php _e( 'Registration IP', 'double-opt-in' ); ?></td>
204 + <td><?php echo esc_html( $OptIn->get_ipaddr_register() ); ?></td>
127 205 </tr>
128 206 <tr>
129 - <td>
130 - <?php _e( 'Confirmation Date', 'double-opt-in' ); ?>
131 - </td>
132 - <td>
133 - <?php
134 - if ( $OptIn->is_confirmed() ) {
207 + <td><?php _e( 'Confirmation Date', 'double-opt-in' ); ?></td>
208 + <td><?php if ( $OptIn->is_confirmed() ) {
135 209 echo esc_html( $OptIn->get_updatetime( 'formatted' ) );
136 - }
137 - ?>
138 - </td>
210 + } ?></td>
139 211 </tr>
140 212 <tr>
141 - <td>
142 - <?php _e( 'Confirmation IP', 'double-opt-in' ); ?>
143 - </td>
144 - <td>
145 - <?php
146 - echo esc_html( $OptIn->get_ipaddr_confirmation() );
147 - ?>
148 - </td>
213 + <td><?php _e( 'Confirmation IP', 'double-opt-in' ); ?></td>
214 + <td><?php echo esc_html( $OptIn->get_ipaddr_confirmation() ); ?></td>
149 215 </tr>
150 216 </table>
151 217
152 218 <h3><?php _e( 'Form Fields', 'double-opt-in' ); ?></h3>
@@ -157,35 +223,17 @@
157 223 </tr>
158 224 <?php if ( isset( $formfields['fields'] ) ): ?>
159 225 <?php foreach ( $formfields['fields'] as $key => $value ): ?>
160 226 <tr>
161 - <td>
162 - <?php esc_attr_e( $key ); ?>
163 - </td>
164 - <td>
165 - <?php if ( is_array( $value ) ) {
166 - echo esc_html( implode( ',', $value ) );
167 - } else {
168 - echo esc_html( $value );
169 - }
170 - ?>
171 - </td>
227 + <td><?php esc_attr_e( $key ); ?></td>
228 + <td><?php echo is_array( $value ) ? esc_html( implode( ',', $value ) ) : esc_html( $value ); ?></td>
172 229 </tr>
173 230 <?php endforeach; ?>
174 231 <?php else: ?>
175 232 <?php foreach ( $formfields as $key => $value ): ?>
176 233 <tr>
177 - <td>
178 - <?php esc_attr_e( $key ); ?>
179 - </td>
180 - <td>
181 - <?php if ( is_array( $value ) ) {
182 - echo esc_html( implode( ',', $value ) );
183 - } else {
184 - echo esc_html( $value );
185 - }
186 - ?>
187 - </td>
234 + <td><?php esc_attr_e( $key ); ?></td>
235 + <td><?php echo is_array( $value ) ? esc_html( implode( ',', $value ) ) : esc_html( $value ); ?></td>
188 236 </tr>
189 237 <?php endforeach; ?>
190 238 <?php endif; ?>
191 239 </table>
@@ -194,20 +242,45 @@
194 242 ob_end_clean();
195 243 echo wp_json_encode( [ 'status' => 200, 'content' => $content ] );
196 244 exit;
197 245 }
246 +
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 + ] );
253 +
198 254 wp_die( 0 );
199 255 }
200 256
257 +
201 258 /**
202 259 * Add the styles for the form
203 260 */
204 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 +
205 269 if ( $hook == 'tools_page_f12doubleoptin' ) {
206 - wp_enqueue_style( 'f12-cf7-doubleoptin-admin', plugins_url( 'assets/admin-style.css', __FILE__ ) );
207 - wp_enqueue_script( 'f12-cf7-doubleoptin-admin', plugins_url( 'assets/f12-cf7-popup.js', __FILE__ ), array( 'jquery' ) );
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 + ] );
208 280 }
209 281 }
282 +
210 283 }
211 284
212 - new Ajax();
285 + new Ajax( Logger::getInstance() );
213 286 }