PluginProbe
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment / 3.1.13
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment v3.1.13
3.1.13 3.1.12 3.1.11 3.1.10 3.1.9 3.1.8 3.1.7 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 All 122 releases
firebox / Inc / Core / Form / Tables / Submissions.php

Submissions.php in FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment 3.1.13, at Inc/Core/Form/Tables/Submissions.php

617 lines 15.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package FireBox
4 * @version 3.1.13 Free
5 *
6 * @author FirePlugins <info@fireplugins.com>
7 * @link https://www.fireplugins.com
8 * @copyright Copyright © 2026 FirePlugins All Rights Reserved
9 * @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
10 */
11
12 namespace FireBox\Core\Form\Tables;
13
14 if (!defined('ABSPATH'))
15 {
16 exit; // Exit if accessed directly.
17 }
18
19 // Load WP_List_Table if not loaded
20 if (!class_exists('WP_List_Table'))
21 {
22 require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
23 }
24
25 use \FireBox\Core\Helpers\Form\Form;
26
27 class Submissions extends \WP_List_Table
28 {
29 /**
30 * Total items.
31 *
32 * @var array
33 */
34 private $total_items;
35
36 /**
37 * Total items per page.
38 *
39 * @var int
40 */
41 private $per_page = 20;
42
43 /**
44 * The selected form id.
45 *
46 * @var string
47 */
48 private $selected_form_id = null;
49
50 /**
51 * The selected form.
52 *
53 * @var array
54 */
55 private $selected_form = null;
56
57 public function __construct($args = [])
58 {
59 parent::__construct($args);
60
61 $forms = Form::getForms();
62 usort($forms, function($a, $b) {
63 return strcmp(strtolower($a['name']), strtolower($b['name']));
64 });
65 $this->selected_form_id = isset($_GET['form_id']) ? sanitize_key($_GET['form_id']) : (count($forms) ? $forms[0]['id'] : false); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
66 $this->selected_form = Form::getFormByID($this->selected_form_id, true);
67 }
68
69 /**
70 * This is a default column renderer.
71 *
72 * @param Object $item
73 * @param string $column_name
74 *
75 * @return string
76 */
77 public function column_default($item, $column_name)
78 {
79 // Find field related values
80 if (strpos($column_name, 'field_') === 0)
81 {
82 $form_fields = $this->getFormFields();
83
84 foreach ($item->meta as $meta_item)
85 {
86 if ('field_' . $meta_item->meta_key !== $column_name)
87 {
88 continue;
89 }
90
91 $field = current(array_filter($form_fields, function($field) use ($meta_item) {
92 return $field->getOptionValue('id') === $meta_item->meta_key;
93 }));
94
95 if ($field)
96 {
97 return $field->prepareValueHTML($meta_item->meta_value);
98 }
99 else
100 {
101 /**
102 * The field no longer exists in the form, so there is no field class to
103 * escape the value for us. Escape here instead — stored values predate
104 * any given sanitization rule and must not be trusted at render time.
105 */
106 return nl2br(esc_html($meta_item->meta_value));
107 }
108 }
109
110 return '';
111 }
112
113 return $item->$column_name;
114 }
115
116 /**
117 * Gets a list of CSS classes for the WP_List_Table table tag.
118 *
119 * @since 3.1.0
120 *
121 * @return string[] Array of CSS classes for the table tag.
122 */
123 protected function get_table_classes() {
124 $mode = get_user_setting( 'posts_list_mode', 'list' );
125
126 $mode_class = esc_attr( 'table-view-' . $mode );
127
128 return array( 'widefat', 'striped', $mode_class, $this->_args['plural'] );
129 }
130
131 /**
132 * Returns the form fields.
133 *
134 * @return array
135 */
136 private function getFormFields()
137 {
138 return isset($this->selected_form['fields']) ? $this->selected_form['fields'] : [];
139 }
140
141 /**
142 * Column "state" output.
143 *
144 * @param object $item
145 *
146 * @return void
147 */
148 public function column_state($item)
149 {
150 $payload = [
151 'input_class' => ['fb-toggle-form-state'],
152 'name' => 'fb_submission_state_' . $item->id,
153 'extra_atts' => [
154 'data-submission-id' => $item->id
155 ],
156 'value' => $item->state === '1' ? 1 : 0
157 ];
158
159 echo \FPFramework\Helpers\HTML::renderFPToggle($payload); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
160 }
161
162 /**
163 * Column "created at" output.
164 *
165 * @param object $item
166 *
167 * @return void
168 */
169 public function column_created_at($item)
170 {
171 return get_date_from_gmt($item->created_at);
172 }
173
174 /**
175 * Column "id" output.
176 *
177 * @param object $item
178 *
179 * @return void
180 */
181 public function column_id($item)
182 {
183 $url = admin_url('admin.php?page=firebox-submissions&task=edit&id=' . $item->id . '&_wpnonce=' . wp_create_nonce('edit-firebox-submission'));
184
185 return '<a href="' . $url . '">' . $item->id . '</a>';
186 }
187
188 /**
189 * Column "user id" output.
190 *
191 * @param object $item
192 *
193 * @return void
194 */
195 public function column_user_id($item)
196 {
197 if ($item->user_id === '0')
198 {
199 return '';
200 }
201
202 return '<a href="' . get_edit_user_link($item->user_id) . '">' . $item->user_id . '</a>';
203 }
204
205 /**
206 * Column "user name" output.
207 *
208 * @param object $item
209 *
210 * @return void
211 */
212 public function column_user_name($item)
213 {
214 if ($item->user_id === '0')
215 {
216 return '';
217 }
218
219 $user = get_user_by('id', $item->user_id);
220
221 return '<a href="' . get_edit_user_link($item->user_id) . '">' . $user->display_name . '</a>';
222 }
223
224 /**
225 * Renders a checkbox.
226 *
227 * @param object $item
228 *
229 * @return string
230 */
231 public function column_cb($item)
232 {
233 return sprintf('<input type="checkbox" name="id[]" value="%s" />', esc_attr($item->id));
234 }
235
236 /**
237 * Returns all table columns.
238 *
239 * @return array
240 */
241 function get_columns()
242 {
243 $columns = [
244 'cb' => '<input type="checkbox" />',
245 'state' => fpframework()->_('FPF_STATUS'),
246 'id' => fpframework()->_('FPF_ID'),
247 'created_at' => firebox()->_('FB_DATE_SUBMITTED')
248 ];
249
250 if ($fields = $this->getFormFields())
251 {
252 foreach ($fields as $field)
253 {
254 $columns = array_merge($columns, [
255 'field_' . $field->getOptionValue('id') => !empty($field->getLabel()) ? $field->getLabel() : $field->getOptionValue('name')
256 ]);
257 }
258 }
259
260 $columns = array_merge($columns, [
261 'user_id' => fpframework()->_('FPF_USER_ID'),
262 'user_name' => fpframework()->_('FPF_USER_NAME'),
263 'visitor_id' => fpframework()->_('FPF_VISITOR_ID'),
264 ]);
265
266 return $columns;
267 }
268
269 /**
270 * [OPTIONAL] This method return columns that may be used to sort table
271 * all strings in array - is column names
272 * notice that true on name column means that its default sort
273 *
274 * @return array
275 */
276 public function get_sortable_columns()
277 {
278 return [
279 'state' => ['state', false],
280 'id' => ['id', true],
281 'created_at' => ['created_at', false],
282 'user_id' => ['user_id', false],
283 'visitor_id' => ['visitor_id', false],
284 ];
285 }
286
287 /**
288 * Returns the allowed sortable columns keys.
289 *
290 * @return array
291 */
292 private function getAllowedOrders()
293 {
294 return array_keys($this->get_sortable_columns());
295 }
296
297 /**
298 * Set bulk actions.
299 *
300 * @return array
301 */
302 public function get_bulk_actions()
303 {
304 return [
305 'publish' => fpframework()->_('FPF_PUBLISH'),
306 'unpublish' => fpframework()->_('FPF_UNPUBLISH'),
307 'delete' => fpframework()->_('FPF_DELETE')
308 ];
309 }
310
311 /**
312 * Processes the bulk actions.
313 *
314 * @return void
315 */
316 public function process_bulk_action()
317 {
318 if (!isset($_GET['form_id']))
319 {
320 return;
321 }
322
323 // Ensure we have an action
324 if (!$action = $this->current_action())
325 {
326 return;
327 }
328
329 // Ensure its a valid action
330 $allowed_actions = $this->get_bulk_actions();
331 if (!array_key_exists($action, $allowed_actions))
332 {
333 return;
334 }
335
336 // Get nonce
337 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
338 if (!$nonce)
339 {
340 return;
341 }
342
343 // Validate nonce
344 if (!wp_verify_nonce($nonce, 'bulk-firebox_page_firebox-submissions'))
345 {
346 wp_die(esc_html(fpframework()->_('FPF_CANNOT_VALIDATE_REQUEST')));
347 }
348
349 unset($_GET['fpframework_fields']);
350
351 // Ensure we have IDs
352 $ids = isset($_GET['id']) ? array_map('intval', array_map('sanitize_text_field', wp_unslash($_GET['id']))) : [];
353 if (!$ids)
354 {
355 return;
356 }
357
358 // Publish or Unpublish action
359 if (in_array($action, ['publish', 'unpublish']))
360 {
361 $new_state = $action === 'publish' ? 1 : 0;
362
363 foreach ($ids as $id)
364 {
365 if (!\FireBox\Core\Helpers\Form\Submission::updateState($id, $new_state))
366 {
367 wp_die(esc_html(firebox()->_('FB_CANNOT_UPDATE_SUBMISSION')));
368 }
369 }
370
371 \FPFramework\Libs\AdminNotice::displaySuccess(firebox()->_('FB_SUBMISSIONS_UPDATED'));
372 }
373 // Delete action
374 else if ($action === 'delete')
375 {
376 $idsPlaceholders = array_fill(0, count($ids), '%s');
377 $placeholdersForIds = implode(', ', $idsPlaceholders);
378
379 global $wpdb;
380
381 // Find all box log ids for each submission
382 $meta_table_name = firebox()->tables->submissionmeta->getFullTableName();
383
384 $box_log_ids = $wpdb->get_results(
385 $wpdb->prepare(
386 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
387 "SELECT DISTINCT(meta_value) FROM $meta_table_name WHERE meta_key = %s AND submission_id IN ($placeholdersForIds)",
388 array_merge(['box_log_id'], $ids)
389 ),
390 ARRAY_A
391 );
392
393 // Delete the form conversions for these submissions
394 if ($box_log_ids)
395 {
396 $logIdsPlaceholders = array_fill(0, count($box_log_ids), '%s');
397 $placeholdersForLogIds = implode(', ', $logIdsPlaceholders);
398
399 $box_log_table_name = firebox()->tables->boxlogdetails->getFullTableName();
400
401 // Prepare the box log ids
402 $box_log_ids = array_map(function($box_log_id) {
403 return $box_log_id['meta_value'];
404 }, $box_log_ids);
405
406 $wpdb->query(
407 $wpdb->prepare(
408 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
409 "DELETE FROM $box_log_table_name WHERE event = 'conversion' AND event_source = 'form' AND log_id IN ($placeholdersForLogIds)",
410 ...$box_log_ids
411 )
412 );
413 }
414
415 // Delete submission
416 $s_table_name = firebox()->tables->submission->getFullTableName();
417 $wpdb->query(
418 $wpdb->prepare(
419 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
420 "DELETE FROM $s_table_name WHERE id IN ($placeholdersForIds)",
421 ...$ids
422 )
423 );
424
425 // Also delete all submission meta associated with this submission
426 $wpdb->query(
427 $wpdb->prepare(
428 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
429 "DELETE FROM $meta_table_name WHERE submission_id IN ($placeholdersForIds)",
430 ...$ids
431 )
432 );
433
434 \FPFramework\Libs\AdminNotice::displaySuccess(firebox()->_('FB_SUBMISSIONS_UPDATED'));
435 }
436 }
437
438 /**
439 * Returns the views.
440 *
441 * @return array
442 */
443 public function get_views()
444 {
445 $current = isset($_GET['status']) ? sanitize_key($_GET['status']) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
446 $base_url = admin_url('admin.php?page=firebox-submissions');
447
448 // Base URL
449 $remove = ['status', 'paged', '_wpnonce'];
450 $url = remove_query_arg($remove, $base_url);
451
452 $count = '&nbsp;<span class="count">(%d)</span>';
453
454 // All
455 $all_class = in_array($current, ['', 'all'], true) ? ' class="current"' : '';
456 $all_count = sprintf($count, esc_attr(count($this->total_items)));
457 $all_label = fpframework()->_('FPF_ALL') . $all_count;
458
459 // Published
460 $p_class = in_array($current, ['published'], true) ? ' class="current"' : '';
461 $p_count = sprintf($count, esc_attr($this->getPublishedCount()));
462 $p_label = fpframework()->_('FPF_PUBLISHED') . $p_count;
463
464 // Unpublished
465 $t_class = in_array($current, ['unpublished'], true) ? ' class="current"' : '';
466 $t_count = sprintf($count, esc_attr($this->getUnpublishedCount()));
467 $t_label = fpframework()->_('FPF_UNPUBLISHED') . $t_count;
468
469 $views = [
470 'all' => sprintf('<a href="%s"%s>%s</a>', esc_url($url), $all_class, $all_label),
471 'published' => sprintf('<a href="%s"%s>%s</a>', esc_url(add_query_arg('status', 'published', $base_url)), $p_class, $p_label),
472 'unpublished' => sprintf('<a href="%s"%s>%s</a>', esc_url(add_query_arg('status', 'unpublished', $base_url)), $t_class, $t_label)
473 ];
474
475 if ($this->total_items)
476 {
477 $views['export'] = sprintf('<a href="%s">%s</a>', esc_url(add_query_arg(['task' => 'export', 'form_id' => $this->selected_form_id, '_wpnonce' => wp_create_nonce('firebox-export-submissions')], $base_url)), firebox()->_('FB_EXPORT_SUBMISSIONS'));
478 }
479
480 return $views;
481 }
482
483 /**
484 * Returns how many published items we have.
485 *
486 * @return int
487 */
488 private function getPublishedCount()
489 {
490 return count(array_filter($this->total_items, function($submission) {
491 return $submission->state === '1';
492 }));
493 }
494
495 /**
496 * Returns how many unpublished items we have.
497 *
498 * @return int
499 */
500 private function getUnpublishedCount()
501 {
502 return count(array_filter($this->total_items, function($submission) {
503 return $submission->state === '0';
504 }));
505 }
506
507 /**
508 * When no items, display this message.
509 *
510 * @return void
511 */
512 public function no_items()
513 {
514 echo esc_html(firebox()->_('FB_NO_SUBMISSIONS_FOUND'));
515 }
516
517 /**
518 * Prepares items to be shown in table.
519 *
520 * @return void
521 */
522 function prepare_items()
523 {
524 $columns = $this->get_columns();
525
526 $sortable = $this->get_sortable_columns();
527
528 $this->_column_headers = [$columns, [], $sortable];
529
530 $offset = isset($_GET['paged']) ? max(0, intval($_GET['paged'] - 1) * $this->per_page) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
531
532 $orderby = isset($_GET['orderby']) && in_array($_GET['orderby'], $this->getAllowedOrders()) ? sanitize_key($_GET['orderby']) : 'created_at'; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
533 $order = isset($_GET['order']) && in_array($_GET['order'], ['asc', 'desc']) ? sanitize_key($_GET['order']) : 'desc'; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
534 $orderby = $orderby . ' ' . $order;
535
536 $payload = [
537 'where' => [
538 'form_id' => " = '" . sanitize_key($this->selected_form_id) . "'"
539 ],
540 'offset' => $offset,
541 'limit' => $this->per_page,
542 'orderby' => $orderby
543 ];
544
545 $status = isset($_GET['status']) ? sanitize_key($_GET['status']) : false; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
546 if ($status)
547 {
548 $payload['where'] = array_merge($payload['where'], [
549 'state = ' => "'" . sanitize_key($status === 'published' ? 1 : 0) . "'"
550 ]);
551 }
552
553 $this->total_items = firebox()->tables->submission->getResults([
554 'where' => [
555 'form_id' => " = '" . sanitize_key($this->selected_form_id) . "'"
556 ]
557 ], true);
558
559 $total_items_count = count($this->total_items);
560
561 $this->items = $total_items_count ? firebox()->tables->submission->getResults($payload) : [];
562
563 if ($this->items)
564 {
565 // Set submission fields values
566 foreach ($this->items as &$item)
567 {
568 // Find field values
569 $meta = firebox()->tables->submissionmeta->getResults([
570 'where' => [
571 'submission_id' => " = " . sanitize_key($item->id)
572 ]
573 ]);
574 $item->meta = $meta;
575 }
576
577 $this->set_pagination_args([
578 'total_items' => $total_items_count,
579 'per_page' => $this->per_page,
580 'total_pages' => ceil($total_items_count / $this->per_page),
581 ]);
582 }
583 }
584
585 /**
586 * Generates the table navigation above or below the table
587 *
588 * Overriden.
589 *
590 * @param string $which
591 *
592 * @return void
593 */
594 protected function display_tablenav($which)
595 {
596 if ('top' === $which)
597 {
598 wp_nonce_field('bulk-' . $this->_args['plural'], '_wpnonce', false, true);
599 }
600 ?>
601 <div class="tablenav <?php echo esc_attr($which); ?>">
602
603 <?php if ($this->has_items()): ?>
604 <div class="alignleft actions bulkactions">
605 <?php $this->bulk_actions($which); ?>
606 </div>
607 <?php
608 endif;
609 $this->extra_tablenav($which);
610 $this->pagination($which);
611 ?>
612
613 <br class="clear" />
614 </div>
615 <?php
616 }
617 }