PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.0.2
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.0.2
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 All 96 releases
sureforms / inc / payments / admin / admin-handler.php

admin-handler.php in SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz 2.0.2, at inc/payments/admin/admin-handler.php

1,346 lines 42.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin Payment Operations Handler Class.
4 *
5 * @package sureforms.
6 */
7
8 namespace SRFM\Inc\Payments\Admin;
9
10 use SRFM\Inc\Database\Tables\Payments;
11 use SRFM\Inc\Helper;
12 use SRFM\Inc\Payments\Payment_Helper;
13 use SRFM\Inc\Traits\Get_Instance;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit; // Exit if accessed directly.
17 }
18
19 /**
20 * Admin Payment Operations handler class.
21 *
22 * @since 2.0.0
23 */
24 class Admin_Handler {
25 use Get_Instance;
26
27 /**
28 * Class constructor.
29 *
30 * @since 2.0.0
31 * @return void
32 */
33 public function __construct() {
34 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
35 add_action( 'wp_ajax_srfm_fetch_payments_transactions', [ $this, 'fetch_payments' ] );
36 add_action( 'wp_ajax_srfm_fetch_single_payment', [ $this, 'fetch_single_payment' ] );
37 add_action( 'wp_ajax_srfm_fetch_subscription', [ $this, 'fetch_subscription' ] );
38 add_action( 'wp_ajax_srfm_fetch_forms_list', [ $this, 'fetch_forms_list' ] );
39 add_action( 'wp_ajax_srfm_add_payment_note', [ $this, 'ajax_add_note' ] );
40 add_action( 'wp_ajax_srfm_delete_payment_note', [ $this, 'ajax_delete_note' ] );
41 add_action( 'wp_ajax_srfm_delete_payment_log', [ $this, 'ajax_delete_log' ] );
42 add_action( 'wp_ajax_srfm_bulk_delete_payments', [ $this, 'ajax_bulk_delete_payments' ] );
43 }
44
45 /**
46 * Enqueue Admin Scripts for Payment Operations.
47 *
48 * @since 2.0.0
49 * @return void
50 */
51 public function enqueue_scripts() {
52 $current_screen = get_current_screen();
53
54 /**
55 * List of the handles in which we need to add translation compatibility.
56 */
57 $script_translations_handlers = [];
58
59 // Check if we're on payment related pages.
60 if ( isset( $current_screen->id ) &&
61 ( strpos( $current_screen->id, 'sureforms_payments' ) !== false || strpos( $current_screen->id, 'sureforms_payments_react' ) !== false ) ) {
62 // Enqueue payment specific scripts.
63 wp_enqueue_script( SRFM_SLUG . '-payments', SRFM_URL . 'assets/build/payments.js', [], SRFM_VER, true );
64 wp_enqueue_style( SRFM_SLUG . '-payments', SRFM_URL . 'assets/build/payments.css', [], SRFM_VER );
65
66 // Localize script with payment admin data.
67 wp_localize_script(
68 SRFM_SLUG . '-payments',
69 SRFM_SLUG . '_payment_admin',
70 [
71 'ajax_url' => admin_url( 'admin-ajax.php' ),
72 'srfm_payment_admin_nonce' => wp_create_nonce( 'srfm_payment_admin_nonce' ),
73 'zeroDecimalCurrencies' => Payment_Helper::get_zero_decimal_currencies(),
74 'currenciesData' => Payment_Helper::get_all_currencies_data(),
75 ]
76 );
77
78 $script_translations_handlers[] = SRFM_SLUG . '-payments';
79 }
80
81 // Register script translations if needed.
82 if ( ! empty( $script_translations_handlers ) ) {
83 foreach ( $script_translations_handlers as $script_handle ) {
84 Helper::register_script_translations( $script_handle );
85 }
86 }
87 }
88
89 /**
90 * AJAX handler for fetching payments data.
91 *
92 * @since 2.0.0
93 * @return void
94 */
95 public function fetch_payments() {
96 // Verify nonce for security.
97 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'srfm_payment_admin_nonce' ) ) {
98 wp_send_json_error( [ 'message' => __( 'Security verification failed.', 'sureforms' ) ] );
99 }
100
101 // Check user capabilities.
102 if ( ! current_user_can( 'manage_options' ) ) {
103 wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'sureforms' ) ] );
104 }
105
106 try {
107 // Sanitize input parameters.
108 $search = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : '';
109 $status = isset( $_POST['status'] ) ? sanitize_text_field( wp_unslash( $_POST['status'] ) ) : '';
110 $form_id = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0;
111 $payment_mode = isset( $_POST['payment_mode'] ) ? sanitize_text_field( wp_unslash( $_POST['payment_mode'] ) ) : '';
112 $date_from = isset( $_POST['date_from'] ) ? sanitize_text_field( wp_unslash( $_POST['date_from'] ) ) : '';
113 $date_to = isset( $_POST['date_to'] ) ? sanitize_text_field( wp_unslash( $_POST['date_to'] ) ) : '';
114 $page = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
115 $per_page = isset( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 20;
116
117 // Validate pagination parameters.
118 $page = max( 1, $page );
119 $per_page = max( 1, min( 100, $per_page ) ); // Limit to 100 records per page.
120 $offset = ( $page - 1 ) * $per_page;
121
122 // Validate date format if provided.
123 if ( ! empty( $date_from ) && ! $this->validate_date( $date_from ) ) {
124 wp_send_json_error( [ 'message' => __( 'Invalid date format for date_from.', 'sureforms' ) ] );
125 }
126
127 if ( ! empty( $date_to ) && ! $this->validate_date( $date_to ) ) {
128 wp_send_json_error( [ 'message' => __( 'Invalid date format for date_to.', 'sureforms' ) ] );
129 }
130
131 // Get total count for pagination.
132 $total_count = $this->get_payments_count( $search, $status, $date_from, $date_to, $form_id, $payment_mode );
133
134 if ( 0 === $total_count && empty( $search ) && empty( $status ) && empty( $date_from ) && empty( $date_to ) && empty( $form_id ) && empty( $payment_mode ) ) {
135 wp_send_json_success(
136 [
137 'payments' => [],
138 'total' => 0,
139 'transactions_is_empty' => 'with_no_filter',
140 ]
141 );
142 }
143
144 if ( 0 === $total_count && ( ! empty( $search ) || ! empty( $status ) || ! empty( $date_from ) || ! empty( $date_to ) || ! empty( $form_id ) || ! empty( $payment_mode ) ) ) {
145 wp_send_json_success(
146 [
147 'payments' => [],
148 'total' => 0,
149 'transactions_is_empty' => 'with_filter',
150 ]
151 );
152 }
153
154 // Get payments data from database.
155 $payments = $this->get_payments_data( $search, $status, $date_from, $date_to, $per_page, $offset, $form_id, $payment_mode );
156
157 wp_send_json_success(
158 [
159 'payments' => $payments,
160 'total' => $total_count,
161 'page' => $page,
162 'per_page' => $per_page,
163 'total_pages' => ceil( $total_count / $per_page ),
164 'transactions_is_empty' => false,
165 ]
166 );
167
168 } catch ( \Exception $e ) {
169 wp_send_json_error( [ 'message' => __( 'An error occurred while fetching payments.', 'sureforms' ) ] );
170 }
171 }
172
173 /**
174 * AJAX handler for fetching single payment data.
175 *
176 * @since 2.0.0
177 * @return void
178 */
179 public function fetch_single_payment() {
180 // Verify nonce for security.
181 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'srfm_payment_admin_nonce' ) ) {
182 wp_send_json_error( [ 'message' => __( 'Security verification failed.', 'sureforms' ) ] );
183 }
184
185 // Check user capabilities.
186 if ( ! current_user_can( 'manage_options' ) ) {
187 wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'sureforms' ) ] );
188 }
189
190 // Validate payment ID.
191 $payment_id = isset( $_POST['payment_id'] ) ? absint( $_POST['payment_id'] ) : 0;
192 if ( empty( $payment_id ) ) {
193 wp_send_json_error( [ 'message' => __( 'Payment ID is required.', 'sureforms' ) ] );
194 }
195
196 try {
197 // Get single payment from database.
198 $payment = Payments::get( $payment_id );
199
200 if ( ! $payment ) {
201 wp_send_json_error( [ 'message' => __( 'Payment not found.', 'sureforms' ) ] );
202 }
203
204 // Transform payment data for frontend.
205 $payment_data = $this->transform_payment_for_frontend( $payment );
206
207 wp_send_json_success( $payment_data );
208
209 } catch ( \Exception $e ) {
210 wp_send_json_error( [ 'message' => __( 'An error occurred while fetching payment details.', 'sureforms' ) ] );
211 }
212 }
213
214 /**
215 * AJAX handler for fetching subscription data with billing history.
216 *
217 * @since 2.0.0
218 * @return void
219 */
220 public function fetch_subscription() {
221 // Verify nonce for security.
222 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'srfm_payment_admin_nonce' ) ) {
223 wp_send_json_error( [ 'message' => __( 'Security verification failed.', 'sureforms' ) ] );
224 }
225
226 // Check user capabilities.
227 if ( ! current_user_can( 'manage_options' ) ) {
228 wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'sureforms' ) ] );
229 }
230
231 // Validate subscription ID - could be main subscription record ID or subscription_id.
232 $subscription_id = isset( $_POST['subscription_id'] ) ? sanitize_text_field( wp_unslash( $_POST['subscription_id'] ) ) : '';
233 if ( empty( $subscription_id ) ) {
234 wp_send_json_error( [ 'message' => __( 'Subscription ID is required.', 'sureforms' ) ] );
235 }
236
237 try {
238 // Check if the ID is a payment record ID first.
239 if ( is_numeric( $subscription_id ) ) {
240 $payment_record = Payments::get( absint( $subscription_id ) );
241 if ( $payment_record && 'subscription' === ( $payment_record['type'] ?? '' ) ) {
242 // This is a main subscription record ID, get the Stripe subscription ID.
243 $stripe_subscription_id = $payment_record['subscription_id'] ?? '';
244 if ( empty( $stripe_subscription_id ) ) {
245 wp_send_json_error( [ 'message' => __( 'Stripe subscription ID not found in payment record.', 'sureforms' ) ] );
246 }
247 $main_subscription = $payment_record;
248 } else {
249 wp_send_json_error( [ 'message' => __( 'Invalid subscription record.', 'sureforms' ) ] );
250 }
251 } else {
252 // This should be a Stripe subscription ID, get the main subscription record.
253 $main_subscription = Payments::get_main_subscription_record( $subscription_id );
254 if ( ! $main_subscription ) {
255 wp_send_json_error( [ 'message' => __( 'Subscription not found.', 'sureforms' ) ] );
256 }
257 $stripe_subscription_id = $subscription_id;
258 }
259
260 // Get all related billing transactions for this subscription.
261 $billing_payments = Payments::get_subscription_related_payments( $stripe_subscription_id );
262
263 // Transform main subscription data for frontend.
264 $subscription_data = $this->transform_payment_for_frontend( $main_subscription );
265
266 // Transform billing payments for frontend.
267 $billing_data = [];
268 foreach ( $billing_payments as $payment ) {
269 $billing_data[] = $this->transform_payment_for_frontend( $payment );
270 }
271
272 // Add subscription-specific fields.
273 $subscription_data['stripe_subscription_id'] = $stripe_subscription_id;
274 $subscription_data['interval'] = $this->get_subscription_interval( $main_subscription );
275 $subscription_data['next_payment_date'] = $this->get_next_payment_date( $main_subscription );
276 $subscription_data['amount_per_cycle'] = $subscription_data['total_amount']; // Use total_amount as cycle amount.
277
278 // Combine data.
279 $response_data = [
280 'subscription' => $subscription_data,
281 'payments' => $billing_data,
282 ];
283
284 wp_send_json_success( $response_data );
285
286 } catch ( \Exception $e ) {
287 wp_send_json_error( [ 'message' => __( 'An error occurred while fetching subscription details.', 'sureforms' ) ] );
288 }
289 }
290
291 /**
292 * AJAX handler for fetching forms list.
293 *
294 * @since 2.0.0
295 * @return void
296 */
297 public function fetch_forms_list() {
298 // Verify nonce for security.
299 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'srfm_payment_admin_nonce' ) ) {
300 wp_send_json_error( [ 'message' => __( 'Security verification failed.', 'sureforms' ) ] );
301 }
302
303 // Check user capabilities.
304 if ( ! current_user_can( 'manage_options' ) ) {
305 wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'sureforms' ) ] );
306 }
307
308 try {
309 // Get all published forms.
310 $all_forms = \SRFM\Inc\Helper::get_sureforms();
311
312 // Get form IDs that have payments.
313 $forms_with_payments = \SRFM\Inc\Database\Tables\Payments::get_all_forms_with_payments();
314
315 // Filter forms to only include those with payments.
316 $forms_list = [];
317 foreach ( $all_forms as $form_id => $form_title ) {
318 // Only include forms that have payment records.
319 if ( in_array( (int) $form_id, $forms_with_payments, true ) ) {
320 $forms_list[] = [
321 'id' => $form_id,
322 /* translators: %d: Form ID */
323 'title' => ! empty( $form_title ) ? $form_title : sprintf( __( 'Form - #%d', 'sureforms' ), $form_id ),
324 ];
325 }
326 }
327
328 wp_send_json_success( [ 'forms' => $forms_list ] );
329
330 } catch ( \Exception $e ) {
331 wp_send_json_error( [ 'message' => __( 'An error occurred while fetching forms list.', 'sureforms' ) ] );
332 }
333 }
334
335 /**
336 * AJAX handler for adding a note to payment.
337 *
338 * @since 2.0.0
339 * @return void
340 */
341 public function ajax_add_note() {
342 // Verify nonce for security.
343 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'srfm_payment_admin_nonce' ) ) {
344 wp_send_json_error( [ 'message' => __( 'Security verification failed.', 'sureforms' ) ] );
345 }
346
347 // Check user capabilities.
348 if ( ! current_user_can( 'manage_options' ) ) {
349 wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'sureforms' ) ] );
350 }
351
352 // Validate and sanitize inputs.
353 $payment_id = isset( $_POST['payment_id'] ) ? absint( $_POST['payment_id'] ) : 0;
354 $note_text = isset( $_POST['note_text'] ) ? sanitize_textarea_field( wp_unslash( $_POST['note_text'] ) ) : '';
355
356 if ( empty( $payment_id ) ) {
357 wp_send_json_error( [ 'message' => __( 'Payment ID is required.', 'sureforms' ) ] );
358 }
359
360 if ( empty( trim( $note_text ) ) ) {
361 wp_send_json_error( [ 'message' => __( 'Note text cannot be empty.', 'sureforms' ) ] );
362 }
363
364 try {
365 // Add the note.
366 $updated_notes = $this->add_payment_note( $payment_id, $note_text );
367
368 if ( false === $updated_notes ) {
369 wp_send_json_error( [ 'message' => __( 'Failed to add note.', 'sureforms' ) ] );
370 }
371
372 wp_send_json_success( [ 'notes' => $this->get_formatted_notes( $updated_notes ) ] );
373
374 } catch ( \Exception $e ) {
375 wp_send_json_error( [ 'message' => __( 'An error occurred while adding the note.', 'sureforms' ) ] );
376 }
377 }
378
379 /**
380 * AJAX handler for deleting a note from payment.
381 *
382 * @since 2.0.0
383 * @return void
384 */
385 public function ajax_delete_note() {
386 // Verify nonce for security.
387 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'srfm_payment_admin_nonce' ) ) {
388 wp_send_json_error( [ 'message' => __( 'Security verification failed.', 'sureforms' ) ] );
389 }
390
391 // Check user capabilities.
392 if ( ! current_user_can( 'manage_options' ) ) {
393 wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'sureforms' ) ] );
394 }
395
396 // Validate and sanitize inputs.
397 $payment_id = isset( $_POST['payment_id'] ) ? absint( $_POST['payment_id'] ) : 0;
398 $note_index = isset( $_POST['note_index'] ) ? absint( $_POST['note_index'] ) : -1;
399
400 if ( empty( $payment_id ) ) {
401 wp_send_json_error( [ 'message' => __( 'Payment ID is required.', 'sureforms' ) ] );
402 }
403
404 if ( $note_index < 0 ) {
405 wp_send_json_error( [ 'message' => __( 'Invalid note index.', 'sureforms' ) ] );
406 }
407
408 try {
409 // Delete the note.
410 $updated_notes = $this->delete_payment_note( $payment_id, $note_index );
411
412 if ( false === $updated_notes ) {
413 wp_send_json_error( [ 'message' => __( 'Failed to delete note.', 'sureforms' ) ] );
414 }
415
416 wp_send_json_success( [ 'notes' => $this->get_formatted_notes( $updated_notes ) ] );
417
418 } catch ( \Exception $e ) {
419 wp_send_json_error( [ 'message' => __( 'An error occurred while deleting the note.', 'sureforms' ) ] );
420 }
421 }
422
423 /**
424 * AJAX handler for deleting a log entry from payment.
425 *
426 * @since 2.0.0
427 * @return void
428 */
429 public function ajax_delete_log() {
430 // Verify nonce for security.
431 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'srfm_payment_admin_nonce' ) ) {
432 wp_send_json_error( [ 'message' => __( 'Security verification failed.', 'sureforms' ) ] );
433 }
434
435 // Check user capabilities.
436 if ( ! current_user_can( 'manage_options' ) ) {
437 wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'sureforms' ) ] );
438 }
439
440 // Validate and sanitize inputs.
441 $payment_id = isset( $_POST['payment_id'] ) ? absint( $_POST['payment_id'] ) : 0;
442 $log_index = isset( $_POST['log_index'] ) ? absint( $_POST['log_index'] ) : -1;
443
444 if ( empty( $payment_id ) ) {
445 wp_send_json_error( [ 'message' => __( 'Payment ID is required.', 'sureforms' ) ] );
446 }
447
448 if ( $log_index < 0 ) {
449 wp_send_json_error( [ 'message' => __( 'Invalid log index.', 'sureforms' ) ] );
450 }
451
452 try {
453 // Delete the log entry.
454 $updated_logs = $this->delete_payment_log( $payment_id, $log_index );
455
456 if ( false === $updated_logs ) {
457 wp_send_json_error( [ 'message' => __( 'Failed to delete log entry.', 'sureforms' ) ] );
458 }
459
460 wp_send_json_success( [ 'logs' => $updated_logs ] );
461
462 } catch ( \Exception $e ) {
463 wp_send_json_error( [ 'message' => __( 'An error occurred while deleting the log entry.', 'sureforms' ) ] );
464 }
465 }
466
467 /**
468 * AJAX handler for bulk deleting payments.
469 *
470 * @since 2.0.0
471 * @return void
472 */
473 public function ajax_bulk_delete_payments() {
474 // Verify nonce for security.
475 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'srfm_payment_admin_nonce' ) ) {
476 wp_send_json_error( [ 'message' => __( 'Security verification failed.', 'sureforms' ) ] );
477 }
478
479 // Check user capabilities.
480 if ( ! current_user_can( 'manage_options' ) ) {
481 wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'sureforms' ) ] );
482 }
483
484 // Get and validate payment IDs.
485 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized below after JSON decode.
486 $payment_ids_raw = isset( $_POST['payment_ids'] ) ? wp_unslash( $_POST['payment_ids'] ) : [];
487
488 // Handle JSON string or array format.
489 if ( is_string( $payment_ids_raw ) ) {
490 // Decode JSON string.
491 $payment_ids = json_decode( $payment_ids_raw, true );
492
493 // Check if JSON decode was successful.
494 if ( json_last_error() !== JSON_ERROR_NONE ) {
495 wp_send_json_error( [ 'message' => __( 'Invalid JSON format for payment IDs.', 'sureforms' ) ] );
496 }
497 } else {
498 $payment_ids = $payment_ids_raw;
499 }
500
501 // Ensure it's an array.
502 if ( ! is_array( $payment_ids ) ) {
503 wp_send_json_error( [ 'message' => __( 'Invalid payment IDs format.', 'sureforms' ) ] );
504 }
505
506 // Sanitize: Convert to integers and remove invalid values.
507 // This handles both string numbers ("169") and actual integers.
508 $payment_ids = array_map( 'absint', $payment_ids );
509 $payment_ids = array_filter(
510 $payment_ids,
511 static function ( $id ) {
512 return $id > 0;
513 }
514 );
515
516 // Re-index array to ensure sequential keys.
517 $payment_ids = array_values( $payment_ids );
518
519 // Check if array is empty after sanitization.
520 if ( empty( $payment_ids ) ) {
521 wp_send_json_error( [ 'message' => __( 'No valid payment IDs provided.', 'sureforms' ) ] );
522 }
523
524 // Limit bulk operations to prevent timeout (max 100 at once).
525 if ( count( $payment_ids ) > 100 ) {
526 wp_send_json_error(
527 [
528 'message' => __( 'Cannot delete more than 100 payments at once. Please select fewer payments.', 'sureforms' ),
529 ]
530 );
531 }
532
533 try {
534 $deleted_count = 0;
535 $failed_ids = [];
536
537 // Delete each payment with proper error handling.
538 foreach ( $payment_ids as $payment_id ) {
539 // Verify payment exists before attempting delete.
540 $payment = Payments::get( $payment_id );
541
542 if ( ! $payment ) {
543 $failed_ids[] = $payment_id;
544 continue;
545 }
546
547 // Attempt deletion.
548 $result = Payments::delete( $payment_id );
549
550 if ( $result ) {
551 $deleted_count++;
552 } else {
553 $failed_ids[] = $payment_id;
554 }
555 }
556
557 // Prepare response message.
558 if ( count( $payment_ids ) === $deleted_count ) {
559 // All deleted successfully.
560 wp_send_json_success(
561 [
562 'message' => sprintf(
563 /* translators: %d: number of payments deleted */
564 _n(
565 '%d payment deleted successfully.',
566 '%d payments deleted successfully.',
567 $deleted_count,
568 'sureforms'
569 ),
570 $deleted_count
571 ),
572 'deleted_count' => $deleted_count,
573 ]
574 );
575 } elseif ( $deleted_count > 0 ) {
576 // Partial success.
577 wp_send_json_success(
578 [
579 'message' => sprintf(
580 /* translators: 1: number deleted, 2: number failed */
581 __( '%1$d payment(s) deleted successfully. %2$d failed.', 'sureforms' ),
582 $deleted_count,
583 count( $failed_ids )
584 ),
585 'deleted_count' => $deleted_count,
586 'failed_count' => count( $failed_ids ),
587 'partial' => true,
588 ]
589 );
590 } else {
591 // All failed.
592 wp_send_json_error(
593 [
594 'message' => __( 'Failed to delete payments. Please try again.', 'sureforms' ),
595 'failed_count' => count( $failed_ids ),
596 ]
597 );
598 }
599 } catch ( \Exception $e ) {
600 wp_send_json_error(
601 [
602 'message' => __( 'An error occurred while deleting payments.', 'sureforms' ),
603 ]
604 );
605 }
606 }
607
608 /**
609 * Get payments data based on filters.
610 *
611 * @param string $search Search term.
612 * @param string $status Payment status filter.
613 * @param string $date_from Start date filter.
614 * @param string $date_to End date filter.
615 * @param int $limit Number of records to return.
616 * @param int $offset Number of records to skip.
617 * @param int $form_id Form ID filter.
618 * @param string $payment_mode Payment mode filter (test/live).
619 * @since 2.0.0
620 * @return array Filtered payments data.
621 */
622 private function get_payments_data( $search = '', $status = '', $date_from = '', $date_to = '', $limit = 20, $offset = 0, $form_id = 0, $payment_mode = '' ) {
623 // Build WHERE conditions for database query.
624 $where_conditions = [];
625
626 // Add search filter - search in form names, customer data, etc.
627 if ( ! empty( $search ) ) {
628 global $wpdb;
629 $search_term = '%' . $wpdb->esc_like( $search ) . '%';
630 $where_conditions[] = [
631 [
632 'key' => 'id',
633 'compare' => 'IN',
634 'value' => $this->get_payment_ids_by_search( $search_term ),
635 ],
636 ];
637 }
638
639 // Add status filter - map frontend status to database status.
640 if ( ! empty( $status ) ) {
641 $db_status = $this->map_frontend_status_to_db( $status );
642 if ( $db_status ) {
643 $where_conditions[] = [
644 [
645 'key' => 'status',
646 'compare' => '=',
647 'value' => $db_status,
648 ],
649 ];
650 }
651 }
652
653 // Add form_id filter.
654 if ( ! empty( $form_id ) ) {
655 $where_conditions[] = [
656 [
657 'key' => 'form_id',
658 'compare' => '=',
659 'value' => $form_id,
660 ],
661 ];
662 }
663
664 // Add payment mode filter (test/live).
665 if ( ! empty( $payment_mode ) && in_array( $payment_mode, [ 'test', 'live' ], true ) ) {
666 $where_conditions[] = [
667 [
668 'key' => 'mode',
669 'compare' => '=',
670 'value' => $payment_mode,
671 ],
672 ];
673 }
674
675 // Add date range filter.
676 if ( ! empty( $date_from ) || ! empty( $date_to ) ) {
677 if ( ! empty( $date_from ) && ! empty( $date_to ) ) {
678 $where_conditions[] = [
679 [
680 'key' => 'created_at',
681 'compare' => '>=',
682 'value' => $date_from . ' 00:00:00',
683 ],
684 [
685 'key' => 'created_at',
686 'compare' => '<=',
687 'value' => $date_to . ' 23:59:59',
688 ],
689 ];
690 } elseif ( ! empty( $date_from ) ) {
691 $where_conditions[] = [
692 [
693 'key' => 'created_at',
694 'compare' => '>=',
695 'value' => $date_from . ' 00:00:00',
696 ],
697 ];
698 } elseif ( ! empty( $date_to ) ) {
699 $where_conditions[] = [
700 [
701 'key' => 'created_at',
702 'compare' => '<=',
703 'value' => $date_to . ' 23:59:59',
704 ],
705 ];
706 }
707 }
708
709 // Get payments from database using the main payments method.
710 // Sorted by created_at in descending order (newest first).
711 $args = [
712 'where' => $where_conditions,
713 'limit' => $limit,
714 'offset' => $offset,
715 'orderby' => 'created_at',
716 'order' => 'DESC',
717 ];
718
719 $db_payments = Payments::get_all_main_payments( $args, true );
720
721 // Transform database records to frontend format.
722 $formatted_payments = [];
723 foreach ( $db_payments as $payment ) {
724 $formatted_payments[] = $this->transform_payment_for_frontend( $payment );
725 }
726
727 return $formatted_payments;
728 }
729
730 /**
731 * Get payment IDs that match search criteria.
732 *
733 * @param string $search_term Search term with wildcards.
734 * @since 2.0.0
735 * @return array Array of payment IDs.
736 */
737 private function get_payment_ids_by_search( $search_term ) {
738 global $wpdb;
739
740 // Get payments table name.
741 $payments_table = Payments::get_instance()->get_tablename();
742
743 if ( empty( $payments_table ) || ! is_string( $payments_table ) ) {
744 return [ 0 ];
745 }
746
747 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
748 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query for search, table name is validated and cannot be parameterized with prepare().
749 $results = $wpdb->get_col(
750 $wpdb->prepare(
751 "SELECT DISTINCT id FROM {$payments_table}
752 WHERE id LIKE %s
753 OR customer_name LIKE %s
754 OR customer_email LIKE %s
755 OR transaction_id LIKE %s
756 OR srfm_txn_id LIKE %s",
757 $search_term,
758 $search_term,
759 $search_term,
760 $search_term,
761 $search_term
762 )
763 );
764 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
765
766 // If no results found, return array with 0 to prevent empty IN clause.
767 return ! empty( $results ) ? array_map( 'absint', $results ) : [ 0 ];
768 }
769
770 /**
771 * Map frontend status to database status.
772 *
773 * @param string $frontend_status Status from frontend.
774 * @since 2.0.0
775 * @return string|false Database status or false if invalid.
776 */
777 private function map_frontend_status_to_db( $frontend_status ) {
778 $status_mapping = [
779 'succeeded' => 'succeeded',
780 'partially_refunded' => 'partially_refunded',
781 'pending' => 'pending',
782 'failed' => 'failed',
783 'refunded' => 'refunded',
784 'cancelled' => 'canceled',
785 ];
786
787 return $status_mapping[ $frontend_status ] ?? false;
788 }
789
790 /**
791 * Map database status to frontend status.
792 *
793 * @param string $db_status Status from database.
794 * @since 2.0.0
795 * @return string Frontend status.
796 */
797 private function map_db_status_to_frontend( $db_status ) {
798 $status_mapping = [
799 'succeeded' => 'paid',
800 'pending' => 'pending',
801 'failed' => 'failed',
802 'refunded' => 'refunded',
803 'partially_refunded' => 'refunded',
804 'canceled' => 'canceled',
805 'requires_action' => 'pending',
806 'requires_payment_method' => 'pending',
807 'processing' => 'pending',
808 ];
809
810 return $status_mapping[ $db_status ] ?? $db_status;
811 }
812
813 /**
814 * Transform database payment record to frontend format.
815 *
816 * @param array<mixed> $payment Database payment record.
817 * @since 2.0.0
818 * @return array Transformed payment data.
819 */
820 private function transform_payment_for_frontend( $payment ) {
821 static $form_titles = []; // Cache for form titles.
822
823 // Get form title with caching using WordPress built-in function.
824 $form_id = isset( $payment['form_id'] ) && ! empty( $payment['form_id'] ) && is_numeric( $payment['form_id'] ) ? intval( $payment['form_id'] ) : 0;
825 if ( is_numeric( $form_id ) && ! isset( $form_titles[ $form_id ] ) ) {
826 $form_title = get_the_title( intval( $form_id ) );
827 $form_titles[ $form_id ] = ! empty( $form_title ) ? html_entity_decode( $form_title, ENT_QUOTES | ENT_HTML5, 'UTF-8' ) : __( 'Unknown Form', 'sureforms' );
828 }
829 $form_title = isset( $form_titles[ $form_id ] ) && ! empty( $form_titles[ $form_id ] ) ? $form_titles[ $form_id ] : __( 'Unknown Form', 'sureforms' );
830 $form_permalink = isset( $form_titles[ $form_id ] ) && ! empty( $form_titles[ $form_id ] ) ? get_permalink( intval( $form_id ) ) : '';
831 $form_url = ! empty( $form_permalink ) && is_string( $form_permalink ) ? html_entity_decode( $form_permalink ) : '';
832
833 // Get customer name - for now use customer_id, in real implementation.
834 // You would get customer data from entries or payment_data.
835 $customer_name = ! empty( $payment['customer_name'] ) ? $payment['customer_name'] : __( 'N/A', 'sureforms' );
836 $customer_email = ! empty( $payment['customer_email'] ) ? $payment['customer_email'] : __( 'N/A', 'sureforms' );
837 $notes = Payments::get_extra_value( $payment['id'], 'notes', [] );
838 $notes = ! empty( $notes ) && is_array( $notes ) ? $notes : [];
839
840 // Determine payment type display label.
841 if ( 'subscription' === $payment['type'] ) {
842 $payment_type = __( 'Subscription', 'sureforms' );
843 } elseif ( 'renewal' === $payment['type'] ) {
844 $payment_type = __( 'Renewal', 'sureforms' );
845 } else {
846 $payment_type = __( 'One Time', 'sureforms' );
847 }
848
849 $payment_front_end_data = [
850 // All original payment_data fields.
851 'id' => $payment['id'],
852 'form_id' => $payment['form_id'],
853 'block_id' => $payment['block_id'] ?? '',
854 'status' => $payment['status'],
855 'total_amount' => $payment['total_amount'],
856 'refunded_amount' => $payment['refunded_amount'] ?? '0.00000000',
857 'currency' => $payment['currency'],
858 'entry_id' => $payment['entry_id'] ?? '',
859 'gateway' => $payment['gateway'],
860 'type' => $payment['type'],
861 'mode' => $payment['mode'] ?? '',
862 'transaction_id' => $payment['transaction_id'] ?? '',
863 'customer_id' => $payment['customer_id'] ?? '',
864 'subscription_id' => $payment['subscription_id'] ?? '',
865 'subscription_status' => $payment['subscription_status'] ?? '',
866 'parent_subscription_id' => $payment['parent_subscription_id'] ?? '0',
867 'payment_data' => $payment['payment_data'] ?? '{}',
868 'extra' => $payment['extra'] ?? '[]',
869 'log' => $payment['log'] ?? '[]',
870 'created_at' => $payment['created_at'],
871 'updated_at' => $payment['updated_at'],
872 'srfm_txn_id' => $payment['srfm_txn_id'],
873
874 // Additional frontend fields.
875 'form_title' => $form_title,
876 'form_url' => $form_url,
877 'form' => $form_title, // Keep for backward compatibility.
878 'customer_name' => $customer_name,
879 'customer_email' => $customer_email,
880 'amount' => floatval( $payment['total_amount'] ),
881 'frontend_status' => $this->map_db_status_to_frontend( $payment['status'] ),
882 'datetime' => $payment['created_at'], // Keep for backward compatibility.
883 'payment_type' => $payment_type,
884 'notes' => $this->get_formatted_notes( $notes ),
885 'logs' => $this->get_formatted_logs( $payment['log'] ),
886 ];
887
888 return apply_filters( 'srfm_payment_admin_data', $payment_front_end_data, $payment );
889 }
890
891 /**
892 * Get total count of payments with filters.
893 *
894 * @param string $search Search term.
895 * @param string $status Payment status filter.
896 * @param string $date_from Start date filter.
897 * @param string $date_to End date filter.
898 * @param int $form_id Form ID filter.
899 * @param string $payment_mode Payment mode filter (test/live).
900 * @since 2.0.0
901 * @return int Total count.
902 */
903 private function get_payments_count( $search = '', $status = '', $date_from = '', $date_to = '', $form_id = 0, $payment_mode = '' ) {
904 // Build WHERE conditions similar to get_payments_data.
905 $where_conditions = [];
906
907 if ( ! empty( $search ) ) {
908 global $wpdb;
909 $search_term = '%' . $wpdb->esc_like( $search ) . '%';
910 $where_conditions[] = [
911 [
912 'key' => 'id',
913 'compare' => 'IN',
914 'value' => $this->get_payment_ids_by_search( $search_term ),
915 ],
916 ];
917 }
918
919 if ( ! empty( $status ) ) {
920 $db_status = $this->map_frontend_status_to_db( $status );
921 if ( $db_status ) {
922 $where_conditions[] = [
923 [
924 'key' => 'status',
925 'compare' => '=',
926 'value' => $db_status,
927 ],
928 ];
929 }
930 }
931
932 // Add form_id filter.
933 if ( ! empty( $form_id ) ) {
934 $where_conditions[] = [
935 [
936 'key' => 'form_id',
937 'compare' => '=',
938 'value' => $form_id,
939 ],
940 ];
941 }
942
943 // Add payment mode filter (test/live).
944 if ( ! empty( $payment_mode ) && in_array( $payment_mode, [ 'test', 'live' ], true ) ) {
945 $where_conditions[] = [
946 [
947 'key' => 'mode',
948 'compare' => '=',
949 'value' => $payment_mode,
950 ],
951 ];
952 }
953
954 if ( ! empty( $date_from ) || ! empty( $date_to ) ) {
955 if ( ! empty( $date_from ) && ! empty( $date_to ) ) {
956 $where_conditions[] = [
957 [
958 'key' => 'created_at',
959 'compare' => '>=',
960 'value' => $date_from . ' 00:00:00',
961 ],
962 [
963 'key' => 'created_at',
964 'compare' => '<=',
965 'value' => $date_to . ' 23:59:59',
966 ],
967 ];
968 } elseif ( ! empty( $date_from ) ) {
969 $where_conditions[] = [
970 [
971 'key' => 'created_at',
972 'compare' => '>=',
973 'value' => $date_from . ' 00:00:00',
974 ],
975 ];
976 } elseif ( ! empty( $date_to ) ) {
977 $where_conditions[] = [
978 [
979 'key' => 'created_at',
980 'compare' => '<=',
981 'value' => $date_to . ' 23:59:59',
982 ],
983 ];
984 }
985 }
986
987 return Payments::get_total_main_payments_by_status( 'all', 0, $where_conditions );
988 }
989
990 /**
991 * Get subscription billing interval from payment data.
992 *
993 * @param array<mixed> $subscription_record Main subscription payment record.
994 * @since 2.0.0
995 * @return string Billing interval.
996 */
997 private function get_subscription_interval( $subscription_record ) {
998 // Try to get interval from payment_data.
999 if ( ! empty( $subscription_record['payment_data'] ) ) {
1000 $payment_data = \SRFM\Inc\Helper::get_array_value( $subscription_record['payment_data'] );
1001
1002 // Check various possible locations for interval data.
1003 $interval_paths = [
1004 'subscription.items.data.0.price.recurring.interval',
1005 'subscription.plan.interval',
1006 'price.recurring.interval',
1007 'plan.interval',
1008 'interval',
1009 ];
1010
1011 foreach ( $interval_paths as $path ) {
1012 $interval = $this->get_nested_array_value( $payment_data, $path );
1013 if ( ! empty( $interval ) && is_string( $interval ) ) {
1014 return ucfirst( $interval ); // month -> Month, year -> Year.
1015 }
1016 }
1017 }
1018
1019 return __( 'Unknown', 'sureforms' );
1020 }
1021
1022 /**
1023 * Get next payment date from subscription data.
1024 *
1025 * @param array<mixed> $subscription_record Main subscription payment record.
1026 * @since 2.0.0
1027 * @return string|null Next payment date or null.
1028 */
1029 private function get_next_payment_date( $subscription_record ) {
1030 // Try to get next payment date from payment_data.
1031 if ( ! empty( $subscription_record['payment_data'] ) ) {
1032 $payment_data = \SRFM\Inc\Helper::get_array_value( $subscription_record['payment_data'] );
1033
1034 // Check various possible locations for next payment date.
1035 $date_paths = [
1036 'subscription.current_period_end',
1037 'current_period_end',
1038 'next_payment_attempt',
1039 ];
1040
1041 foreach ( $date_paths as $path ) {
1042 $timestamp = $this->get_nested_array_value( $payment_data, $path );
1043 if ( ! empty( $timestamp ) && is_numeric( $timestamp ) ) {
1044 $timestamp_int = intval( $timestamp );
1045 return gmdate( 'Y-m-d H:i:s', $timestamp_int );
1046 }
1047 }
1048 }
1049
1050 return null;
1051 }
1052
1053 /**
1054 * Get nested value from array using dot notation.
1055 *
1056 * @param array<mixed> $array Array to search.
1057 * @param string $path Dot-separated path.
1058 * @since 2.0.0
1059 * @return mixed Value or null if not found.
1060 */
1061 private function get_nested_array_value( $array, $path ) {
1062 $keys = explode( '.', $path );
1063 $current = $array;
1064
1065 foreach ( $keys as $key ) {
1066 if ( ! is_array( $current ) || ! isset( $current[ $key ] ) ) {
1067 return null;
1068 }
1069 $current = $current[ $key ];
1070 }
1071
1072 return $current;
1073 }
1074
1075 /**
1076 * Validate date format (YYYY-MM-DD).
1077 *
1078 * @param string $date Date string to validate.
1079 * @since 2.0.0
1080 * @return bool True if valid, false otherwise.
1081 */
1082 private function validate_date( $date ) {
1083 $parsed_date = \DateTime::createFromFormat( 'Y-m-d', $date );
1084 return $parsed_date && $parsed_date->format( 'Y-m-d' ) === $date;
1085 }
1086
1087 /**
1088 * Add a note to payment.
1089 *
1090 * @param int $payment_id Payment ID.
1091 * @param string $note_text Note text to add.
1092 * @since 2.0.0
1093 * @return array|false Updated notes array or false on failure.
1094 */
1095 private function add_payment_note( $payment_id, $note_text ) {
1096 if ( empty( $payment_id ) || empty( trim( $note_text ) ) ) {
1097 return false;
1098 }
1099
1100 // Verify payment exists.
1101 $payment = Payments::get( $payment_id );
1102 if ( ! $payment ) {
1103 return false;
1104 }
1105
1106 // Get current notes from extra data.
1107 $notes = Payments::get_extra_value( $payment_id, 'notes', [] );
1108
1109 // Ensure notes is an array.
1110 if ( ! is_array( $notes ) ) {
1111 $notes = [];
1112 }
1113
1114 // Create new note with metadata.
1115 $new_note = [
1116 'text' => trim( $note_text ),
1117 'created_at' => current_time( 'mysql' ),
1118 'created_by' => get_current_user_id(),
1119 ];
1120
1121 // Add new note to the beginning of the array (most recent first).
1122 array_unshift( $notes, $new_note );
1123
1124 // Update extra data with new notes array.
1125 $result = Payments::update_extra_key( $payment_id, 'notes', $notes );
1126
1127 if ( false === $result ) {
1128 return false;
1129 }
1130
1131 return $notes;
1132 }
1133
1134 /**
1135 * Delete a note from payment by index.
1136 *
1137 * @param int $payment_id Payment ID.
1138 * @param int $note_index Index of note to delete.
1139 * @since 2.0.0
1140 * @return array|false Updated notes array or false on failure.
1141 */
1142 private function delete_payment_note( $payment_id, $note_index ) {
1143 if ( empty( $payment_id ) || $note_index < 0 ) {
1144 return false;
1145 }
1146
1147 // Verify payment exists.
1148 $payment = Payments::get( $payment_id );
1149 if ( ! $payment ) {
1150 return false;
1151 }
1152
1153 // Get current notes.
1154 $notes = Payments::get_extra_value( $payment_id, 'notes', [] );
1155
1156 // Ensure notes is an array.
1157 if ( ! is_array( $notes ) ) {
1158 return false;
1159 }
1160
1161 // Check if note index exists.
1162 if ( ! isset( $notes[ $note_index ] ) ) {
1163 return false;
1164 }
1165
1166 // Remove note at specified index.
1167 array_splice( $notes, $note_index, 1 );
1168
1169 // Re-index array to prevent gaps.
1170 $notes = array_values( $notes );
1171
1172 // Update extra data with modified notes array.
1173 $result = Payments::update_extra_key( $payment_id, 'notes', $notes );
1174
1175 if ( false === $result ) {
1176 return false;
1177 }
1178
1179 return $notes;
1180 }
1181
1182 /**
1183 * Delete a log entry from payment by index.
1184 *
1185 * @param int $payment_id Payment ID.
1186 * @param int $log_index Index of log entry to delete.
1187 * @since 2.0.0
1188 * @return array|false Updated formatted logs array or false on failure.
1189 */
1190 private function delete_payment_log( $payment_id, $log_index ) {
1191 if ( empty( $payment_id ) || $log_index < 0 ) {
1192 return false;
1193 }
1194
1195 // Verify payment exists.
1196 $payment = Payments::get( $payment_id );
1197 if ( ! $payment ) {
1198 return false;
1199 }
1200
1201 // Get current logs from log column.
1202 $logs_data = $payment['log'] ?? '[]';
1203 $logs = json_decode( $logs_data, true ) ?? [];
1204
1205 // Ensure logs is an array.
1206 if ( ! is_array( $logs ) ) {
1207 return false;
1208 }
1209
1210 // Check if log index exists.
1211 if ( ! isset( $logs[ $log_index ] ) ) {
1212 return false;
1213 }
1214
1215 // Remove log at specified index.
1216 array_splice( $logs, $log_index, 1 );
1217
1218 // Re-index array to prevent gaps.
1219 $logs = array_values( $logs );
1220
1221 // Update log column with modified logs array.
1222 $result = Payments::update(
1223 $payment_id,
1224 [
1225 'log' => wp_json_encode( $logs ),
1226 ]
1227 );
1228
1229 if ( false === $result ) {
1230 return false;
1231 }
1232
1233 $encoded_logs = wp_json_encode( $logs );
1234 $encoded_logs = is_string( $encoded_logs ) ? $encoded_logs : '';
1235
1236 // Return formatted logs for frontend.
1237 return $this->get_formatted_logs( $encoded_logs );
1238 }
1239
1240 /**
1241 * Get formatted logs from log data.
1242 *
1243 * @param string $log_data JSON encoded log data.
1244 * @since 2.0.0
1245 * @return array Formatted logs array.
1246 */
1247 private function get_formatted_logs( $log_data ) {
1248 if ( empty( $log_data ) ) {
1249 return [];
1250 }
1251
1252 // If already an array, use it directly.
1253 if ( is_array( $log_data ) ) {
1254 $logs = $log_data;
1255 } else {
1256 return [];
1257 }
1258
1259 if ( ! is_array( $logs ) ) {
1260 return [];
1261 }
1262
1263 $formatted_logs = [];
1264
1265 foreach ( $logs as $log ) {
1266 // Handle both object and array formats.
1267 if ( is_object( $log ) ) {
1268 $formatted_logs[] = [
1269 'title' => $log->title ?? '',
1270 'created_at' => $log->created_at ?? 0,
1271 'messages' => $log->messages ?? [],
1272 ];
1273 } elseif ( is_array( $log ) ) {
1274 $formatted_logs[] = [
1275 'title' => $log['title'] ?? '',
1276 'created_at' => $log['created_at'] ?? 0,
1277 'messages' => $log['messages'] ?? [],
1278 ];
1279 }
1280 }
1281
1282 return $formatted_logs;
1283 }
1284
1285 /**
1286 * Get formatted notes from notes data.
1287 *
1288 * @param array<mixed> $notes_data Notes array from extra data.
1289 * @since 2.0.0
1290 * @return array Formatted notes array.
1291 */
1292 private function get_formatted_notes( $notes_data ) {
1293 if ( empty( $notes_data ) ) {
1294 return [];
1295 }
1296
1297 // If not an array, return empty.
1298 if ( ! is_array( $notes_data ) ) {
1299 return [];
1300 }
1301
1302 $formatted_notes = [];
1303
1304 foreach ( $notes_data as $note ) {
1305 // Handle both object and array formats.
1306 if ( is_object( $note ) ) {
1307 $formatted_notes[] = [
1308 'text' => $note->text ?? '',
1309 'created_at' => $note->created_at ?? '',
1310 'created_by' => $note->created_by ?? 0,
1311 'created_by_user_name' => isset( $note->created_by ) && is_numeric( $note->created_by ) ? $this->get_user_detail_by_id( $note->created_by ) : __( 'Guest User', 'sureforms' ),
1312 ];
1313 } elseif ( is_array( $note ) ) {
1314 $formatted_notes[] = [
1315 'text' => $note['text'] ?? '',
1316 'created_at' => $note['created_at'] ?? '',
1317 'created_by' => $note['created_by'] ?? 0,
1318 'created_by_user_name' => isset( $note['created_by'] ) && is_numeric( $note['created_by'] ) ? $this->get_user_detail_by_id( $note['created_by'] ) : __( 'Guest User', 'sureforms' ),
1319 ];
1320 }
1321 }
1322
1323 return $formatted_notes;
1324 }
1325
1326 /**
1327 * Get user detail by ID.
1328 *
1329 * @param mixed $user_id User ID.
1330 * @since 2.0.0
1331 * @return string User name or 'Guest User' if user not found.
1332 */
1333 private function get_user_detail_by_id( $user_id ) {
1334 if ( empty( $user_id ) || ! is_numeric( $user_id ) || $user_id <= 0 ) {
1335 return __( 'Guest User', 'sureforms' );
1336 }
1337 $user_id = is_numeric( $user_id ) ? intval( $user_id ) : 0;
1338 if ( $user_id <= 0 ) {
1339 return __( 'Guest User', 'sureforms' );
1340 }
1341 $user = get_userdata( $user_id );
1342 $user_name = $user ? $user->user_login : '';
1343 return is_string( $user_name ) ? $user_name : __( 'Guest User', 'sureforms' );
1344 }
1345 }
1346