PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.8
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.8
2.12.8 2.12.7 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 All 98 releases
← All changes | inc/admin-ajax.php +275 -113 0.0.13 → 2.12.8 View file →
@@ -8,10 +8,10 @@
8 8 */
9 9
10 10 namespace SRFM\Inc;
11 11
12 +use BSF_UTM_Analytics;
12 13 use SRFM\Inc\Traits\Get_Instance;
13 -use SRFM\Inc\Helper;
14 14
15 15 if ( ! defined( 'ABSPATH' ) ) {
16 16 exit; // Exit if accessed directly.
17 17 }
@@ -25,9 +25,8 @@
25 25 *
26 26 * @since 0.0.1
27 27 */
28 28 class Admin_Ajax {
29 -
30 29 use Get_Instance;
31 30
32 31 /**
33 32 * Constructor
@@ -34,34 +33,19 @@
34 33 *
35 34 * @since 0.0.1
36 35 */
37 36 public function __construct() {
38 - add_action( 'wp_ajax_sureforms_dismiss_plugin_notice', [ $this, 'dismiss_plugin_notice' ] );
39 37 add_action( 'wp_ajax_sureforms_recommended_plugin_activate', [ $this, 'required_plugin_activate' ] );
40 38 add_action( 'wp_ajax_sureforms_recommended_plugin_install', 'wp_ajax_install_plugin' );
41 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' ] );
42 43
43 44 add_filter( SRFM_SLUG . '_admin_filter', [ $this, 'localize_script_integration' ] );
44 45 }
45 46
46 47 /**
47 - * Dismiss plugin notice on dismiss button click using ajax request.
48 - *
49 - * @since 0.0.13
50 - * @return void
51 - */
52 - public function dismiss_plugin_notice() {
53 - if ( empty( $_GET['security'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['security'] ) ), 'srfm_notice_dismiss_nonce' ) ) {
54 - wp_send_json_error();
55 - }
56 -
57 - update_option( 'srfm_dismiss_entries_migration_notice', true );
58 -
59 - wp_send_json_success();
60 - }
61 -
62 -
63 - /**
64 48 * Required Plugin Activate
65 49 *
66 50 * @return void
67 51 * @since 0.0.1
@@ -69,9 +53,9 @@
69 53 public function required_plugin_activate() {
70 54
71 55 $response_data = [ 'message' => $this->get_error_msg( 'permission' ) ];
72 56
73 - if ( ! current_user_can( 'manage_options' ) ) {
57 + if ( ! Helper::current_user_can() ) {
74 58 wp_send_json_error( $response_data );
75 59 }
76 60
77 61 if ( empty( $_POST ) ) {
@@ -86,9 +70,9 @@
86 70 $response_data = [ 'message' => $this->get_error_msg( 'nonce' ) ];
87 71 wp_send_json_error( $response_data );
88 72 }
89 73
90 - if ( ! current_user_can( 'install_plugins' ) || ! isset( $_POST['init'] ) || ! sanitize_text_field( wp_unslash( $_POST['init'] ) ) ) {
74 + if ( ! isset( $_POST['init'] ) || ! sanitize_text_field( wp_unslash( $_POST['init'] ) ) ) {
91 75 wp_send_json_error(
92 76 [
93 77 'success' => false,
94 78 'message' => __( 'No plugin specified', 'sureforms' ),
@@ -95,10 +79,12 @@
95 79 ]
96 80 );
97 81 }
98 82
99 - $plugin_init = ( isset( $_POST['init'] ) ) ? sanitize_text_field( wp_unslash( $_POST['init'] ) ) : '';
83 + $plugin_init = isset( $_POST['init'] ) ? sanitize_text_field( wp_unslash( $_POST['init'] ) ) : '';
100 84
85 + $plugin_slug = isset( $_POST['slug'] ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : '';
86 +
101 87 $activate = activate_plugin( $plugin_init, '', false, true );
102 88
103 89 if ( is_wp_error( $activate ) ) {
104 90 wp_send_json_error(
@@ -108,8 +94,13 @@
108 94 ]
109 95 );
110 96 }
111 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 +
112 103 wp_send_json_success(
113 104 [
114 105 'success' => true,
115 106 'message' => __( 'Plugin Successfully Activated', 'sureforms' ),
@@ -142,8 +133,9 @@
142 133 * @return array<mixed>
143 134 * @since 0.0.1
144 135 */
145 136 public function localize_script_integration( $values ) {
137 + $is_screen_sureforms_menu = Helper::validate_request_context( 'sureforms_menu', 'page' );
146 138 return array_merge(
147 139 $values,
148 140 [
149 141 'ajax_url' => admin_url( 'admin-ajax.php' ),
@@ -148,16 +140,10 @@
148 140 [
149 141 'ajax_url' => admin_url( 'admin-ajax.php' ),
150 142 'sfPluginManagerNonce' => wp_create_nonce( 'sf_plugin_manager_nonce' ),
151 143 'plugin_installer_nonce' => wp_create_nonce( 'updates' ),
152 - 'plugin_activating_text' => __( 'Activating...', 'sureforms' ),
153 - 'plugin_activated_text' => __( 'Activated', 'sureforms' ),
154 - 'plugin_activate_text' => __( 'Activate', 'sureforms' ),
155 - 'integrations' => self::sureforms_get_integration(),
156 - 'plugin_installing_text' => __( 'Installing...', 'sureforms' ),
157 - 'plugin_installed_text' => __( 'Installed', 'sureforms' ),
158 144 'isRTL' => is_rtl(),
159 - 'current_screen_id' => get_current_screen() ? get_current_screen()->id : '',
145 + 'current_screen_id' => $is_screen_sureforms_menu ? 'sureforms_menu' : '',
160 146 'form_id' => get_post() ? get_post()->ID : '',
161 147 'suretriggers_nonce' => wp_create_nonce( 'suretriggers_nonce' ),
162 148 ]
163 149 );
@@ -163,65 +149,8 @@
163 149 );
164 150 }
165 151
166 152 /**
167 - * Get sureforms recommended integrations.
168 - *
169 - * @since 0.0.1
170 - * @return array<mixed>
171 - */
172 - public function sureforms_get_integration() {
173 - $suretrigger_connected = apply_filters( 'suretriggers_is_user_connected', '' );
174 - return apply_filters(
175 - 'srfm_integrated_plugins',
176 - [
177 - [
178 - 'title' => __( 'SureTriggers', 'sureforms' ),
179 - 'subtitle' => __( 'Connect SureForms to hundreds of apps, CRMs and tools such as Slack, Mailchimp, etc.', 'sureforms' ),
180 - 'description' => __( 'SureTriggers is a powerful automation platform that helps you connect your various plugins and apps together. It allows you to automate repetitive tasks, so you can focus on more important work.', 'sureforms' ),
181 - 'status' => self::get_plugin_status( 'suretriggers/suretriggers.php' ),
182 - 'slug' => 'suretriggers',
183 - 'path' => 'suretriggers/suretriggers.php',
184 - 'redirection' => admin_url( 'admin.php?page=suretriggers' ),
185 - 'logo' => self::encode_svg( is_string( file_get_contents( plugin_dir_path( SRFM_FILE ) . 'images/suretriggers.svg' ) ) ? file_get_contents( plugin_dir_path( SRFM_FILE ) . 'images/suretriggers.svg' ) : '' ),
186 - 'logo_full' => self::encode_svg( is_string( file_get_contents( plugin_dir_path( SRFM_FILE ) . 'images/suretriggers_full.svg' ) ) ? file_get_contents( plugin_dir_path( SRFM_FILE ) . 'images/suretriggers_full.svg' ) : '' ),
187 - 'connected' => $suretrigger_connected,
188 - ],
189 - ]
190 - );
191 - }
192 -
193 - /**
194 - * Encodes the given string with base64.
195 - *
196 - * @param string $logo contains svg's.
197 - * @return string
198 - */
199 - public function encode_svg( $logo ) {
200 - return 'data:image/svg+xml;base64,' . base64_encode( $logo ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
201 - }
202 - /**
203 - * Get plugin status
204 - *
205 - * @since 0.0.1
206 - *
207 - * @param string $plugin_init_file Plugin init file.
208 - * @return string
209 - */
210 - public static function get_plugin_status( $plugin_init_file ) {
211 -
212 - $installed_plugins = get_plugins();
213 -
214 - if ( ! isset( $installed_plugins[ $plugin_init_file ] ) ) {
215 - return 'Install';
216 - } elseif ( is_plugin_active( $plugin_init_file ) ) {
217 - return 'Activated';
218 - } else {
219 - return 'Installed';
220 - }
221 - }
222 -
223 - /**
224 153 * Generates data required for suretriggers integration
225 154 *
226 155 * @since 0.0.8
227 156 * @return void
@@ -226,26 +155,25 @@
226 155 * @since 0.0.8
227 156 * @return void
228 157 */
229 158 public function generate_data_for_suretriggers_integration() {
230 - if ( ! current_user_can( 'manage_options' ) ) {
231 - wp_send_json_error( [ 'message' => 'You do not have permission to access this page.' ] );
159 + if ( ! Helper::current_user_can() ) {
160 + wp_send_json_error( [ 'message' => __( 'You do not have permission to access this page.', 'sureforms' ) ] );
232 161 }
233 162
234 163 if ( ! check_ajax_referer( 'suretriggers_nonce', 'security', false ) ) {
235 - wp_send_json_error( [ 'message' => 'Invalid nonce.' ] );
164 + wp_send_json_error( [ 'message' => __( 'Invalid nonce.', 'sureforms' ) ] );
236 165 }
237 166
238 167 if ( empty( $_POST['formId'] ) ) {
239 - wp_send_json_error( [ 'message' => 'Form ID is required.' ] );
168 + wp_send_json_error( [ 'message' => __( 'Form ID is required.', 'sureforms' ) ] );
240 169 }
241 170
242 - $suretriggers_data = get_option( 'suretrigger_options', [] );
243 - if ( ! is_array( $suretriggers_data ) || empty( $suretriggers_data['secret_key'] ) || ! is_string( $suretriggers_data['secret_key'] ) ) {
171 + if ( ! Helper::is_suretriggers_ready() ) {
244 172 wp_send_json_error(
245 173 [
246 174 'code' => 'invalid_secret_key',
247 - 'message' => 'SureTriggers is not configured properly.',
175 + 'message' => __( 'OttoKit is not configured properly.', 'sureforms' ),
248 176 ]
249 177 );
250 178 }
251 179
@@ -255,10 +183,11 @@
255 183 if ( is_null( $form ) || SRFM_FORMS_POST_TYPE !== $form->post_type ) {
256 184 wp_send_json_error( [ 'message' => __( 'Invalid form ID.', 'sureforms' ) ] );
257 185 }
258 186
259 - $form_name = ! empty( $form->post_title ) ? $form->post_title : 'SureForms id: ' . $form_id;
260 - $api_url = apply_filters( 'suretriggers_get_iframe_url', SRFM_SURETRIGGERS_INTERGATION_BASE_URL );
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.
261 190
262 191 // This is the format of data required by SureTriggers for adding iframe in target id.
263 192 $body = [
264 193 'client_id' => 'SureForms',
@@ -265,11 +194,11 @@
265 194 'st_embed_url' => $api_url,
266 195 'embedded_identifier' => $form_id,
267 196 'target' => 'suretriggers-iframe-wrapper', // div where we want SureTriggers to add iframe should have this target id.
268 197 'event' => [
269 - 'label' => 'Form Submitted',
198 + 'label' => __( 'Form Submitted', 'sureforms' ),
270 199 'value' => 'sureforms_form_submitted',
271 - 'description' => 'Runs when a form is submitted',
200 + 'description' => __( 'Runs when a form is submitted', 'sureforms' ),
272 201 ],
273 202 'summary' => $form_name,
274 203 'selected_options' => [
275 204 'form_id' => [
@@ -287,12 +216,22 @@
287 216 'data' => $this->get_form_fields( $form_id ),
288 217 ],
289 218 ];
290 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 +
291 230 wp_send_json_success(
292 231 [
293 232 'message' => 'success',
294 - 'data' => $body,
233 + 'data' => apply_filters( 'srfm_suretriggers_integration_data_filter', $body, $form_id ),
295 234 ]
296 235 );
297 236 }
298 237
@@ -319,8 +258,20 @@
319 258 }
320 259
321 260 $blocks = parse_blocks( $post->post_content );
322 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 +
323 274 if ( empty( $blocks ) ) {
324 275 return [];
325 276 }
326 277
@@ -327,8 +278,31 @@
327 278 $data = [];
328 279
329 280 foreach ( $blocks as $block ) {
330 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 +
331 305 if ( ! empty( $block['attrs']['slug'] ) ) {
332 306 $data[ $block['attrs']['slug'] ] = $this->get_sample_data( $block['blockName'] );
333 307 }
334 308 }
@@ -338,9 +312,8 @@
338 312 return [];
339 313 }
340 314
341 315 return $data;
342 -
343 316 }
344 317
345 318 /**
346 319 * Returns sample data for a block.
@@ -350,28 +323,28 @@
350 323 * @return mixed
351 324 */
352 325 public function get_sample_data( $block_name ) {
353 326 if ( empty( $block_name ) ) {
354 - return 'Sample data';
327 + return __( 'Sample data', 'sureforms' );
355 328 }
356 329
357 330 $dummy_data = [
358 - 'srfm/input' => 'Sample input data',
331 + 'srfm/input' => __( 'Sample input data', 'sureforms' ),
359 332 'srfm/email' => '[email protected]',
360 - 'srfm/textarea' => 'Sample textarea data',
333 + 'srfm/textarea' => __( 'Sample textarea data', 'sureforms' ),
361 334 'srfm/number' => 123,
362 335 'srfm/checkbox' => 'checkbox value',
363 336 'srfm/gdpr' => 'GDPR value',
364 337 'srfm/phone' => '1234567890',
365 - 'srfm/address' => 'Address data',
366 - 'srfm/address-compact' => 'Address data',
367 - 'srfm/dropdown' => 'Selected dropdown option',
368 - 'srfm/multi-choice' => 'Selected Multichoice option',
369 - 'srfm/radio' => 'Selected radio option',
370 - 'srfm/submit' => 'Submit',
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' ),
371 344 'srfm/url' => 'https://example.com',
372 345 'srfm/date-time-picker' => '2022-01-01 12:00:00',
373 - 'srfm/hidden' => 'Hidden Value',
346 + 'srfm/hidden' => __( 'Hidden Value', 'sureforms' ),
374 347 'srfm/slider' => 50,
375 348 'srfm/password' => 'DummyPassword123',
376 349 'srfm/rating' => 4,
377 350 'srfm/upload' => 'https://example.com/uploads/file.pdf',
@@ -376,12 +349,201 @@
376 349 'srfm/rating' => 4,
377 350 'srfm/upload' => 'https://example.com/uploads/file.pdf',
378 351 ];
379 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 +
380 375 if ( ! empty( $dummy_data[ $block_name ] ) ) {
381 376 return $dummy_data[ $block_name ];
382 - } else {
383 - return 'Sample data';
384 377 }
378 + return __( 'Sample data', 'sureforms' );
385 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 +
386 549 }
387 -