PluginProbe ʕ •ᴥ•ʔ
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.6
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.6
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 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 0.0.8 0.0.9 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.1.0 1.1.1 1.1.2 1.10.0 1.10.1 1.11.0 1.12.0 1.12.1 1.12.2 1.12.3 1.13.0 1.13.1 1.13.2 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5.0 1.5.1 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.8.0 1.9.0 1.9.1 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.2.0 2.2.1 2.2.2 2.3.0 2.4.0 2.5.0 2.5.2 2.6.0
sureforms / inc / admin-ajax.php
sureforms / inc Last commit date
abilities 2 days ago admin 3 months ago ai-form-builder 1 month ago blocks 2 months ago compatibility 4 weeks ago database 2 days ago email 4 weeks ago fields 4 weeks ago global-settings 2 days ago lib 1 month ago migrator 2 months ago page-builders 4 weeks ago payments 2 days ago single-form-settings 2 months ago traits 2 months ago activator.php 1 year ago admin-ajax.php 2 days ago background-process.php 9 months ago client-logger.php 2 days ago create-new-form.php 3 months ago duplicate-form.php 2 days ago entries.php 4 weeks ago events-scheduler.php 2 years ago export.php 2 days ago field-validation.php 4 weeks ago form-restriction.php 2 months ago form-styling.php 1 month ago form-submit.php 2 days ago form-views.php 2 days ago forms-data.php 2 days ago frontend-assets.php 2 days ago generate-form-markup.php 2 days ago gutenberg-hooks.php 2 weeks ago helper.php 2 days ago learn.php 4 months ago onboarding.php 2 months ago post-types.php 2 days ago rest-api.php 2 days ago smart-tags.php 1 week ago submit-token.php 2 days ago translatable.php 1 month ago updater-callbacks.php 4 weeks ago updater.php 4 weeks ago
admin-ajax.php
550 lines
1 <?php
2 /**
3 * Sureforms Admin Ajax Class.
4 *
5 * Class file for public functions.
6 *
7 * @package sureforms
8 */
9
10 namespace SRFM\Inc;
11
12 use BSF_UTM_Analytics;
13 use SRFM\Inc\Traits\Get_Instance;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit; // Exit if accessed directly.
17 }
18
19 if ( ! function_exists( 'get_plugins' ) ) {
20 require_once ABSPATH . 'wp-admin/includes/plugin.php';
21 }
22
23 /**
24 * Public Class
25 *
26 * @since 0.0.1
27 */
28 class Admin_Ajax {
29 use Get_Instance;
30
31 /**
32 * Constructor
33 *
34 * @since 0.0.1
35 */
36 public function __construct() {
37 add_action( 'wp_ajax_sureforms_recommended_plugin_activate', [ $this, 'required_plugin_activate' ] );
38 add_action( 'wp_ajax_sureforms_recommended_plugin_install', 'wp_ajax_install_plugin' );
39 add_action( 'wp_ajax_sureforms_integration', [ $this, 'generate_data_for_suretriggers_integration' ] );
40 add_action( 'wp_ajax_srfm_download_export', [ $this, 'download_export_file' ] );
41 add_action( 'wp_ajax_srfm_download_logs', [ $this, 'download_client_log' ] );
42 add_action( 'wp_ajax_srfm_clear_logs', [ $this, 'clear_client_log' ] );
43
44 add_filter( SRFM_SLUG . '_admin_filter', [ $this, 'localize_script_integration' ] );
45 }
46
47 /**
48 * Required Plugin Activate
49 *
50 * @return void
51 * @since 0.0.1
52 */
53 public function required_plugin_activate() {
54
55 $response_data = [ 'message' => $this->get_error_msg( 'permission' ) ];
56
57 if ( ! Helper::current_user_can() ) {
58 wp_send_json_error( $response_data );
59 }
60
61 if ( empty( $_POST ) ) {
62 $response_data = [ 'message' => $this->get_error_msg( 'invalid' ) ];
63 wp_send_json_error( $response_data );
64 }
65
66 /**
67 * Nonce verification.
68 */
69 if ( ! check_ajax_referer( 'sf_plugin_manager_nonce', 'security', false ) ) {
70 $response_data = [ 'message' => $this->get_error_msg( 'nonce' ) ];
71 wp_send_json_error( $response_data );
72 }
73
74 if ( ! isset( $_POST['init'] ) || ! sanitize_text_field( wp_unslash( $_POST['init'] ) ) ) {
75 wp_send_json_error(
76 [
77 'success' => false,
78 'message' => __( 'No plugin specified', 'sureforms' ),
79 ]
80 );
81 }
82
83 $plugin_init = isset( $_POST['init'] ) ? sanitize_text_field( wp_unslash( $_POST['init'] ) ) : '';
84
85 $plugin_slug = isset( $_POST['slug'] ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : '';
86
87 $activate = activate_plugin( $plugin_init, '', false, true );
88
89 if ( is_wp_error( $activate ) ) {
90 wp_send_json_error(
91 [
92 'success' => false,
93 'message' => $activate->get_error_message(),
94 ]
95 );
96 }
97
98 if ( class_exists( 'BSF_UTM_Analytics' ) && is_callable( 'BSF_UTM_Analytics::update_referer' ) ) {
99 $plugin_slug = pathinfo( $plugin_slug, PATHINFO_FILENAME );
100 BSF_UTM_Analytics::update_referer( 'sureforms', $plugin_slug );
101 }
102
103 wp_send_json_success(
104 [
105 'success' => true,
106 'message' => __( 'Plugin Successfully Activated', 'sureforms' ),
107 ]
108 );
109 }
110
111 /**
112 * Get ajax error message.
113 *
114 * @param string $type Message type.
115 * @return string
116 * @since 0.0.2
117 */
118 public function get_error_msg( $type ) {
119
120 if ( ! isset( $this->errors[ $type ] ) ) {
121 $type = 'default';
122 }
123 if ( ! isset( $this->errors ) ) {
124 return '';
125 }
126 return $this->errors[ $type ];
127 }
128
129 /**
130 * Localize the variables required for integration plugins.
131 *
132 * @param array<mixed> $values localized values.
133 * @return array<mixed>
134 * @since 0.0.1
135 */
136 public function localize_script_integration( $values ) {
137 $is_screen_sureforms_menu = Helper::validate_request_context( 'sureforms_menu', 'page' );
138 return array_merge(
139 $values,
140 [
141 'ajax_url' => admin_url( 'admin-ajax.php' ),
142 'sfPluginManagerNonce' => wp_create_nonce( 'sf_plugin_manager_nonce' ),
143 'plugin_installer_nonce' => wp_create_nonce( 'updates' ),
144 'isRTL' => is_rtl(),
145 'current_screen_id' => $is_screen_sureforms_menu ? 'sureforms_menu' : '',
146 'form_id' => get_post() ? get_post()->ID : '',
147 'suretriggers_nonce' => wp_create_nonce( 'suretriggers_nonce' ),
148 ]
149 );
150 }
151
152 /**
153 * Generates data required for suretriggers integration
154 *
155 * @since 0.0.8
156 * @return void
157 */
158 public function generate_data_for_suretriggers_integration() {
159 if ( ! Helper::current_user_can() ) {
160 wp_send_json_error( [ 'message' => __( 'You do not have permission to access this page.', 'sureforms' ) ] );
161 }
162
163 if ( ! check_ajax_referer( 'suretriggers_nonce', 'security', false ) ) {
164 wp_send_json_error( [ 'message' => __( 'Invalid nonce.', 'sureforms' ) ] );
165 }
166
167 if ( empty( $_POST['formId'] ) ) {
168 wp_send_json_error( [ 'message' => __( 'Form ID is required.', 'sureforms' ) ] );
169 }
170
171 if ( ! Helper::is_suretriggers_ready() ) {
172 wp_send_json_error(
173 [
174 'code' => 'invalid_secret_key',
175 'message' => __( 'OttoKit is not configured properly.', 'sureforms' ),
176 ]
177 );
178 }
179
180 $form_id = Helper::get_integer_value( sanitize_text_field( wp_unslash( $_POST['formId'] ) ) );
181 $form = get_post( $form_id );
182
183 if ( is_null( $form ) || SRFM_FORMS_POST_TYPE !== $form->post_type ) {
184 wp_send_json_error( [ 'message' => __( 'Invalid form ID.', 'sureforms' ) ] );
185 }
186
187 // Translators: %s: Form ID.
188 $form_name = ! empty( $form->post_title ) ? $form->post_title : sprintf( __( 'SureForms id: %s', 'sureforms' ), $form_id );
189 $api_url = apply_filters( 'suretriggers_get_iframe_url', SRFM_SURETRIGGERS_INTEGRATION_BASE_URL ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- SureTriggers' own filter; the name must match SureTriggers exactly to integrate.
190
191 // This is the format of data required by SureTriggers for adding iframe in target id.
192 $body = [
193 'client_id' => 'SureForms',
194 'st_embed_url' => $api_url,
195 'embedded_identifier' => $form_id,
196 'target' => 'suretriggers-iframe-wrapper', // div where we want SureTriggers to add iframe should have this target id.
197 'event' => [
198 'label' => __( 'Form Submitted', 'sureforms' ),
199 'value' => 'sureforms_form_submitted',
200 'description' => __( 'Runs when a form is submitted', 'sureforms' ),
201 ],
202 'summary' => $form_name,
203 'selected_options' => [
204 'form_id' => [
205 'value' => $form_id,
206 'label' => $form_name,
207 ],
208 ],
209 'integration' => 'SureForms',
210 'sample_response' => [
211 'form_id' => $form_id,
212 'to_emails' => [
213 'dev-email@wpengine.local',
214 ],
215 'form_name' => $form_name,
216 'data' => $this->get_form_fields( $form_id ),
217 ],
218 ];
219
220 // Adding entry_id in body sample response if do_not_store_entries is not enabled.
221 $compliance = get_post_meta( $form_id, '_srfm_compliance', true );
222 $do_not_store_entries = is_array( $compliance ) && isset( $compliance[0]['do_not_store_entries'] )
223 ? $compliance[0]['do_not_store_entries']
224 : null;
225
226 if ( ! $do_not_store_entries ) {
227 $body['sample_response']['entry_id'] = 12;
228 }
229
230 wp_send_json_success(
231 [
232 'message' => 'success',
233 'data' => apply_filters( 'srfm_suretriggers_integration_data_filter', $body, $form_id ),
234 ]
235 );
236 }
237
238 /**
239 * This function populates data for particular form.
240 *
241 * @param int $form_id Form ID.
242 * @since 0.0.8
243 * @return array<mixed>
244 */
245 public function get_form_fields( $form_id ) {
246 if ( empty( $form_id ) || ! is_int( $form_id ) ) {
247 return [];
248 }
249
250 if ( SRFM_FORMS_POST_TYPE !== get_post_type( $form_id ) ) {
251 return [];
252 }
253
254 $post = get_post( $form_id );
255
256 if ( is_null( $post ) ) {
257 return [];
258 }
259
260 $blocks = parse_blocks( $post->post_content );
261
262 $blocks = array_filter(
263 $blocks,
264 static function( $block ) {
265 if ( 'srfm/html' === $block['blockName'] ) {
266 return false;
267 }
268 return true;
269 }
270 );
271
272 $blocks = array_values( $blocks );
273
274 if ( empty( $blocks ) ) {
275 return [];
276 }
277
278 $data = [];
279
280 foreach ( $blocks as $block ) {
281 if ( ! empty( $block['blockName'] ) && 0 === strpos( $block['blockName'], 'srfm/' ) ) {
282
283 /**
284 * Determine whether to skip this field from the sample data.
285 *
286 * @param bool $should_skip Default value indicating if field should be skipped.
287 * @param array $block_details Array containing block attributes, including 'block_name'.
288 *
289 * @since 2.0.0
290 *
291 * @hook srfm_should_skip_field_from_sample_data
292 */
293 $should_skip_this_field = apply_filters(
294 'srfm_should_skip_field_from_sample_data',
295 false,
296 [
297 'block_name' => $block['blockName'],
298 ]
299 );
300
301 if ( $should_skip_this_field ) {
302 continue;
303 }
304
305 if ( ! empty( $block['attrs']['slug'] ) ) {
306 $data[ $block['attrs']['slug'] ] = $this->get_sample_data( $block['blockName'] );
307 }
308 }
309 }
310
311 if ( empty( $data ) ) {
312 return [];
313 }
314
315 return $data;
316 }
317
318 /**
319 * Returns sample data for a block.
320 *
321 * @param string $block_name Block name.
322 * @since 0.0.8
323 * @return mixed
324 */
325 public function get_sample_data( $block_name ) {
326 if ( empty( $block_name ) ) {
327 return __( 'Sample data', 'sureforms' );
328 }
329
330 $dummy_data = [
331 'srfm/input' => __( 'Sample input data', 'sureforms' ),
332 'srfm/email' => 'noreply@sureforms.com',
333 'srfm/textarea' => __( 'Sample textarea data', 'sureforms' ),
334 'srfm/number' => 123,
335 'srfm/checkbox' => 'checkbox value',
336 'srfm/gdpr' => 'GDPR value',
337 'srfm/phone' => '1234567890',
338 'srfm/address' => __( 'Address data', 'sureforms' ),
339 'srfm/address-compact' => __( 'Address data', 'sureforms' ),
340 'srfm/dropdown' => __( 'Selected dropdown option', 'sureforms' ),
341 'srfm/multi-choice' => __( 'Selected Multichoice option', 'sureforms' ),
342 'srfm/radio' => __( 'Selected radio option', 'sureforms' ),
343 'srfm/submit' => __( 'Submit', 'sureforms' ),
344 'srfm/url' => 'https://example.com',
345 'srfm/date-time-picker' => '2022-01-01 12:00:00',
346 'srfm/hidden' => __( 'Hidden Value', 'sureforms' ),
347 'srfm/slider' => 50,
348 'srfm/password' => 'DummyPassword123',
349 'srfm/rating' => 4,
350 'srfm/upload' => 'https://example.com/uploads/file.pdf',
351 ];
352
353 /**
354 * Filter the sample data for specific block types.
355 *
356 * Allows plugins and themes to add custom sample data for their block types
357 * or modify existing sample data. This is particularly useful for dynamic
358 * block types that require complex sample data structures.
359 *
360 * @since 0.0.8
361 *
362 * @param array $dummy_data {
363 * Array of sample data keyed by block name.
364 *
365 * @type string|array $block_name Sample data for the block.
366 * }
367 * @param array $filter_args {
368 * Additional filter arguments.
369 *
370 * @type string $block_name The name of the block being processed.
371 * }
372 */
373 $dummy_data = Helper::apply_filters_as_array( 'srfm_sample_data_filter', $dummy_data, [ 'block_name' => $block_name ] );
374
375 if ( ! empty( $dummy_data[ $block_name ] ) ) {
376 return $dummy_data[ $block_name ];
377 }
378 return __( 'Sample data', 'sureforms' );
379 }
380
381 /**
382 * Download exported file.
383 *
384 * @since 2.0.0
385 * @return void
386 */
387 public function download_export_file() {
388 // Check user permissions.
389 if ( ! Helper::current_user_can() ) {
390 wp_die( esc_html__( 'You do not have permission to access this file.', 'sureforms' ) );
391 }
392
393 // Verify nonce for security.
394 if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'srfm_download_export' ) ) {
395 wp_die( esc_html__( 'Security check failed.', 'sureforms' ) );
396 }
397
398 // Get and sanitize the file parameter.
399 $file = isset( $_GET['file'] ) ? sanitize_file_name( wp_unslash( $_GET['file'] ) ) : '';
400
401 if ( empty( $file ) ) {
402 wp_die( esc_html__( 'Invalid file request.', 'sureforms' ) );
403 }
404
405 // Build the full file path.
406 $temp_dir = wp_normalize_path( trailingslashit( get_temp_dir() ) );
407 $filepath = $temp_dir . $file;
408
409 // Security check: ensure the file is in the temp directory.
410 if ( strpos( wp_normalize_path( $filepath ), $temp_dir ) !== 0 ) {
411 wp_die( esc_html__( 'Invalid file path.', 'sureforms' ) );
412 }
413
414 // Check if file exists.
415 if ( ! file_exists( $filepath ) ) {
416 wp_die( esc_html__( 'File not found.', 'sureforms' ) );
417 }
418
419 // Get file info.
420 $file_size = filesize( $filepath );
421 $file_info = pathinfo( $filepath );
422
423 // Determine content type and filename based on file extension.
424 $content_type = 'application/octet-stream';
425 $filename = $file_info['basename'];
426 if ( isset( $file_info['extension'] ) ) {
427 if ( 'csv' === $file_info['extension'] ) {
428 $content_type = 'text/csv';
429 } elseif ( 'zip' === $file_info['extension'] ) {
430 $content_type = 'application/zip';
431 /**
432 * Filter the user-facing filename used when serving an exported ZIP archive.
433 *
434 * @since 2.9.0
435 *
436 * @param string $filename Default ZIP filename.
437 * @param array<string,mixed> $file_info pathinfo() result for the file being served.
438 */
439 $filename = (string) apply_filters( 'srfm_export_zip_filename', 'SureForms Entries.zip', $file_info );
440 }
441 }
442
443 // Set headers for download.
444 header( 'Content-Type: ' . $content_type );
445 header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
446 header( 'Content-Length: ' . $file_size );
447 header( 'Cache-Control: private, max-age=0, must-revalidate' );
448 header( 'Pragma: public' );
449
450 // Clear output buffers.
451 if ( ob_get_level() ) {
452 ob_end_clean();
453 }
454
455 // Output the file.
456 readfile( $filepath ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile, WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Direct file output is required to stream the download.
457
458 // Clean up the temporary file.
459 wp_delete_file( $filepath );
460
461 exit;
462 }
463 /**
464 * Stream the client debug log to an administrator.
465 *
466 * Takes no filename parameter. There is exactly one log file and the server
467 * derives its path, which removes the path-traversal question entirely rather
468 * than guarding against it -- and keeps the unguessable file name, which is
469 * what actually protects the log on nginx, out of the page.
470 *
471 * @since 2.12.6
472 * @return void
473 */
474 public function download_client_log() {
475 $this->verify_log_request();
476
477 $path = Client_Logger::get_log_path( false );
478 $has_log = '' !== $path && file_exists( $path );
479
480 // The buttons are always offered while logging is on, so downloading before
481 // anything has failed is a normal thing to do. Hand back an explanatory file
482 // rather than a wp_die() screen -- an empty log is the good outcome.
483 if ( ! $has_log ) {
484 header( 'Content-Type: text/plain; charset=utf-8' );
485 header( 'X-Content-Type-Options: nosniff' );
486 header( 'Content-Disposition: attachment; filename="sureforms-debug-log.txt"' );
487
488 if ( ob_get_level() ) {
489 ob_end_clean();
490 }
491
492 echo esc_html__( 'No form submission failures have been recorded.', 'sureforms' );
493 exit;
494 }
495
496 $size = filesize( $path );
497
498 header( 'Content-Type: text/plain; charset=utf-8' );
499 header( 'X-Content-Type-Options: nosniff' );
500 header( 'Content-Disposition: attachment; filename="sureforms-debug-log.txt"' );
501
502 if ( is_int( $size ) ) {
503 header( 'Content-Length: ' . $size );
504 }
505
506 header( 'Cache-Control: private, max-age=0, must-revalidate' );
507
508 if ( ob_get_level() ) {
509 ob_end_clean();
510 }
511
512 readfile( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile, WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Direct file output is required to stream the download.
513 exit;
514 }
515
516 /**
517 * Delete the client debug log.
518 *
519 * @since 2.12.6
520 * @return void
521 */
522 public function clear_client_log() {
523 $this->verify_log_request();
524
525 Client_Logger::clear();
526
527 wp_send_json_success();
528 }
529
530 /**
531 * Capability and nonce gate shared by both log actions.
532 *
533 * Capability first, ahead of the nonce, matching the ordering of the sibling
534 * handlers in this class.
535 *
536 * @since 2.12.6
537 * @return void
538 */
539 private function verify_log_request() {
540 if ( ! Helper::current_user_can() ) {
541 wp_die( esc_html__( 'You do not have permission to access this file.', 'sureforms' ) );
542 }
543
544 if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'srfm_client_logs' ) ) {
545 wp_die( esc_html__( 'Security check failed.', 'sureforms' ) );
546 }
547 }
548
549 }
550