* @link https://www.fireplugins.com * @copyright Copyright © 2026 FirePlugins All Rights Reserved * @license GNU GPLv3 or later */ namespace FireBox\Core\Form\Tables; if (!defined('ABSPATH')) { exit; // Exit if accessed directly. } // Load WP_List_Table if not loaded if (!class_exists('WP_List_Table')) { require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; } use \FireBox\Core\Helpers\Form\Form; class Submissions extends \WP_List_Table { /** * Total items. * * @var array */ private $total_items; /** * Total items per page. * * @var int */ private $per_page = 20; /** * The selected form id. * * @var string */ private $selected_form_id = null; /** * The selected form. * * @var array */ private $selected_form = null; public function __construct($args = []) { parent::__construct($args); $forms = Form::getForms(); usort($forms, function($a, $b) { return strcmp(strtolower($a['name']), strtolower($b['name'])); }); $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 $this->selected_form = Form::getFormByID($this->selected_form_id, true); } /** * This is a default column renderer. * * @param Object $item * @param string $column_name * * @return string */ public function column_default($item, $column_name) { // Find field related values if (strpos($column_name, 'field_') === 0) { $form_fields = $this->getFormFields(); foreach ($item->meta as $meta_item) { if ('field_' . $meta_item->meta_key !== $column_name) { continue; } $field = current(array_filter($form_fields, function($field) use ($meta_item) { return $field->getOptionValue('id') === $meta_item->meta_key; })); if ($field) { return $field->prepareValueHTML($meta_item->meta_value); } else { /** * The field no longer exists in the form, so there is no field class to * escape the value for us. Escape here instead — stored values predate * any given sanitization rule and must not be trusted at render time. */ return nl2br(esc_html($meta_item->meta_value)); } } return ''; } return $item->$column_name; } /** * Gets a list of CSS classes for the WP_List_Table table tag. * * @since 3.1.0 * * @return string[] Array of CSS classes for the table tag. */ protected function get_table_classes() { $mode = get_user_setting( 'posts_list_mode', 'list' ); $mode_class = esc_attr( 'table-view-' . $mode ); return array( 'widefat', 'striped', $mode_class, $this->_args['plural'] ); } /** * Returns the form fields. * * @return array */ private function getFormFields() { return isset($this->selected_form['fields']) ? $this->selected_form['fields'] : []; } /** * Column "state" output. * * @param object $item * * @return void */ public function column_state($item) { $payload = [ 'input_class' => ['fb-toggle-form-state'], 'name' => 'fb_submission_state_' . $item->id, 'extra_atts' => [ 'data-submission-id' => $item->id ], 'value' => $item->state === '1' ? 1 : 0 ]; echo \FPFramework\Helpers\HTML::renderFPToggle($payload); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Column "created at" output. * * @param object $item * * @return void */ public function column_created_at($item) { return get_date_from_gmt($item->created_at); } /** * Column "id" output. * * @param object $item * * @return void */ public function column_id($item) { $url = admin_url('admin.php?page=firebox-submissions&task=edit&id=' . $item->id . '&_wpnonce=' . wp_create_nonce('edit-firebox-submission')); return '' . $item->id . ''; } /** * Column "user id" output. * * @param object $item * * @return void */ public function column_user_id($item) { if ($item->user_id === '0') { return ''; } return '' . $item->user_id . ''; } /** * Column "user name" output. * * @param object $item * * @return void */ public function column_user_name($item) { if ($item->user_id === '0') { return ''; } $user = get_user_by('id', $item->user_id); return '' . $user->display_name . ''; } /** * Renders a checkbox. * * @param object $item * * @return string */ public function column_cb($item) { return sprintf('', esc_attr($item->id)); } /** * Returns all table columns. * * @return array */ function get_columns() { $columns = [ 'cb' => '', 'state' => fpframework()->_('FPF_STATUS'), 'id' => fpframework()->_('FPF_ID'), 'created_at' => firebox()->_('FB_DATE_SUBMITTED') ]; if ($fields = $this->getFormFields()) { foreach ($fields as $field) { $columns = array_merge($columns, [ 'field_' . $field->getOptionValue('id') => !empty($field->getLabel()) ? $field->getLabel() : $field->getOptionValue('name') ]); } } $columns = array_merge($columns, [ 'user_id' => fpframework()->_('FPF_USER_ID'), 'user_name' => fpframework()->_('FPF_USER_NAME'), 'visitor_id' => fpframework()->_('FPF_VISITOR_ID'), ]); return $columns; } /** * [OPTIONAL] This method return columns that may be used to sort table * all strings in array - is column names * notice that true on name column means that its default sort * * @return array */ public function get_sortable_columns() { return [ 'state' => ['state', false], 'id' => ['id', true], 'created_at' => ['created_at', false], 'user_id' => ['user_id', false], 'visitor_id' => ['visitor_id', false], ]; } /** * Returns the allowed sortable columns keys. * * @return array */ private function getAllowedOrders() { return array_keys($this->get_sortable_columns()); } /** * Set bulk actions. * * @return array */ public function get_bulk_actions() { return [ 'publish' => fpframework()->_('FPF_PUBLISH'), 'unpublish' => fpframework()->_('FPF_UNPUBLISH'), 'delete' => fpframework()->_('FPF_DELETE') ]; } /** * Processes the bulk actions. * * @return void */ public function process_bulk_action() { if (!isset($_GET['form_id'])) { return; } // Ensure we have an action if (!$action = $this->current_action()) { return; } // Ensure its a valid action $allowed_actions = $this->get_bulk_actions(); if (!array_key_exists($action, $allowed_actions)) { return; } // Get nonce $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : ''; if (!$nonce) { return; } // Validate nonce if (!wp_verify_nonce($nonce, 'bulk-firebox_page_firebox-submissions')) { wp_die(esc_html(fpframework()->_('FPF_CANNOT_VALIDATE_REQUEST'))); } unset($_GET['fpframework_fields']); // Ensure we have IDs $ids = isset($_GET['id']) ? array_map('intval', array_map('sanitize_text_field', wp_unslash($_GET['id']))) : []; if (!$ids) { return; } // Publish or Unpublish action if (in_array($action, ['publish', 'unpublish'])) { $new_state = $action === 'publish' ? 1 : 0; foreach ($ids as $id) { if (!\FireBox\Core\Helpers\Form\Submission::updateState($id, $new_state)) { wp_die(esc_html(firebox()->_('FB_CANNOT_UPDATE_SUBMISSION'))); } } \FPFramework\Libs\AdminNotice::displaySuccess(firebox()->_('FB_SUBMISSIONS_UPDATED')); } // Delete action else if ($action === 'delete') { $idsPlaceholders = array_fill(0, count($ids), '%s'); $placeholdersForIds = implode(', ', $idsPlaceholders); global $wpdb; // Find all box log ids for each submission $meta_table_name = firebox()->tables->submissionmeta->getFullTableName(); $box_log_ids = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared "SELECT DISTINCT(meta_value) FROM $meta_table_name WHERE meta_key = %s AND submission_id IN ($placeholdersForIds)", array_merge(['box_log_id'], $ids) ), ARRAY_A ); // Delete the form conversions for these submissions if ($box_log_ids) { $logIdsPlaceholders = array_fill(0, count($box_log_ids), '%s'); $placeholdersForLogIds = implode(', ', $logIdsPlaceholders); $box_log_table_name = firebox()->tables->boxlogdetails->getFullTableName(); // Prepare the box log ids $box_log_ids = array_map(function($box_log_id) { return $box_log_id['meta_value']; }, $box_log_ids); $wpdb->query( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare "DELETE FROM $box_log_table_name WHERE event = 'conversion' AND event_source = 'form' AND log_id IN ($placeholdersForLogIds)", ...$box_log_ids ) ); } // Delete submission $s_table_name = firebox()->tables->submission->getFullTableName(); $wpdb->query( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare "DELETE FROM $s_table_name WHERE id IN ($placeholdersForIds)", ...$ids ) ); // Also delete all submission meta associated with this submission $wpdb->query( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare "DELETE FROM $meta_table_name WHERE submission_id IN ($placeholdersForIds)", ...$ids ) ); \FPFramework\Libs\AdminNotice::displaySuccess(firebox()->_('FB_SUBMISSIONS_UPDATED')); } } /** * Returns the views. * * @return array */ public function get_views() { $current = isset($_GET['status']) ? sanitize_key($_GET['status']) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended $base_url = admin_url('admin.php?page=firebox-submissions'); // Base URL $remove = ['status', 'paged', '_wpnonce']; $url = remove_query_arg($remove, $base_url); $count = ' (%d)'; // All $all_class = in_array($current, ['', 'all'], true) ? ' class="current"' : ''; $all_count = sprintf($count, esc_attr(count($this->total_items))); $all_label = fpframework()->_('FPF_ALL') . $all_count; // Published $p_class = in_array($current, ['published'], true) ? ' class="current"' : ''; $p_count = sprintf($count, esc_attr($this->getPublishedCount())); $p_label = fpframework()->_('FPF_PUBLISHED') . $p_count; // Unpublished $t_class = in_array($current, ['unpublished'], true) ? ' class="current"' : ''; $t_count = sprintf($count, esc_attr($this->getUnpublishedCount())); $t_label = fpframework()->_('FPF_UNPUBLISHED') . $t_count; $views = [ 'all' => sprintf('%s', esc_url($url), $all_class, $all_label), 'published' => sprintf('%s', esc_url(add_query_arg('status', 'published', $base_url)), $p_class, $p_label), 'unpublished' => sprintf('%s', esc_url(add_query_arg('status', 'unpublished', $base_url)), $t_class, $t_label) ]; if ($this->total_items) { $views['export'] = sprintf('%s', 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')); } return $views; } /** * Returns how many published items we have. * * @return int */ private function getPublishedCount() { return count(array_filter($this->total_items, function($submission) { return $submission->state === '1'; })); } /** * Returns how many unpublished items we have. * * @return int */ private function getUnpublishedCount() { return count(array_filter($this->total_items, function($submission) { return $submission->state === '0'; })); } /** * When no items, display this message. * * @return void */ public function no_items() { echo esc_html(firebox()->_('FB_NO_SUBMISSIONS_FOUND')); } /** * Prepares items to be shown in table. * * @return void */ function prepare_items() { $columns = $this->get_columns(); $sortable = $this->get_sortable_columns(); $this->_column_headers = [$columns, [], $sortable]; $offset = isset($_GET['paged']) ? max(0, intval($_GET['paged'] - 1) * $this->per_page) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended $orderby = isset($_GET['orderby']) && in_array($_GET['orderby'], $this->getAllowedOrders()) ? sanitize_key($_GET['orderby']) : 'created_at'; //phpcs:ignore WordPress.Security.NonceVerification.Recommended $order = isset($_GET['order']) && in_array($_GET['order'], ['asc', 'desc']) ? sanitize_key($_GET['order']) : 'desc'; //phpcs:ignore WordPress.Security.NonceVerification.Recommended $orderby = $orderby . ' ' . $order; $payload = [ 'where' => [ 'form_id' => " = '" . sanitize_key($this->selected_form_id) . "'" ], 'offset' => $offset, 'limit' => $this->per_page, 'orderby' => $orderby ]; $status = isset($_GET['status']) ? sanitize_key($_GET['status']) : false; //phpcs:ignore WordPress.Security.NonceVerification.Recommended if ($status) { $payload['where'] = array_merge($payload['where'], [ 'state = ' => "'" . sanitize_key($status === 'published' ? 1 : 0) . "'" ]); } $this->total_items = firebox()->tables->submission->getResults([ 'where' => [ 'form_id' => " = '" . sanitize_key($this->selected_form_id) . "'" ] ], true); $total_items_count = count($this->total_items); $this->items = $total_items_count ? firebox()->tables->submission->getResults($payload) : []; if ($this->items) { // Set submission fields values foreach ($this->items as &$item) { // Find field values $meta = firebox()->tables->submissionmeta->getResults([ 'where' => [ 'submission_id' => " = " . sanitize_key($item->id) ] ]); $item->meta = $meta; } $this->set_pagination_args([ 'total_items' => $total_items_count, 'per_page' => $this->per_page, 'total_pages' => ceil($total_items_count / $this->per_page), ]); } } /** * Generates the table navigation above or below the table * * Overriden. * * @param string $which * * @return void */ protected function display_tablenav($which) { if ('top' === $which) { wp_nonce_field('bulk-' . $this->_args['plural'], '_wpnonce', false, true); } ?>
has_items()): ?>
bulk_actions($which); ?>
extra_tablenav($which); $this->pagination($which); ?>