File ManagerCurrent Directory: wp-content/plugins/wpforms/src/Pro/Admin/Entries/OverviewLinux appserver-0d5e4f1e-php-cc8da225320a42ba9b7d66cba40b1f03 6.6.123+ #1 SMP PREEMPT_DYNAMIC Wed Mar 11 09:04:28 UTC 2026 x86_64Edit File: Table.php <?php namespace WPForms\Pro\Admin\Entries\Overview; if ( ! defined( 'ABSPATH' ) ) { exit; } if ( ! class_exists( 'WP_List_Table', false ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; } // phpcs:ignore WPForms.PHP.UseStatement.UnusedUseStatement use wpdb; use WPForms\Admin\Helpers\Datepicker; use WP_List_Table; use WP_Post; use WPForms\Pro\AntiSpam\SpamEntry; use WPForms_Entries_List; use WPForms_Entry_Handler; /** * "Entries" overview table which lists all forms. * This table can be seen within "WPForms" → "Entries" page. * * @since 1.8.2 */ class Table extends WP_List_Table { /** * Array of start and end dates along with the number of days in between. * * Responsible for generating "Last X Days". * * @since 1.8.2 * * @var array */ private $timespan; /** * An array of start and end dates for database queries. * In the database, all datetime are stored in UTC. It is not possible to change this global setting. * * @since 1.8.2 * * @var array */ private $timespan_mysql; /** * Cached object of "entry". * * @since 1.8.2 * * @var WPForms_Entry_Handler */ private $entry_handler; /** * An array of entire SQL result set cached for further data sorting and modifications. * The array contains form ids associated with the number of entries count. * * @since 1.8.2 * * @var array */ private $total_entry_counts; /** * The purpose of this variable is to determine whether * the chart could display the queried form entries * according to the chosen or specified time period. * * The result of the initial database query will also be used in the "Graph" column * to avoid running the database query more than once * when the "timespan" (Last X Days) column is present. * * @since 1.8.2 * * @var bool */ private $form_has_entries_timespan; /** * Placeholder character in place of actual data. * * @since 1.8.2 */ const PLACEHOLDER = '–'; /** * Initialize the "Overview" table list. * * @since 1.8.2 */ public function __construct() { parent::__construct( [ 'singular' => 'entry-overview', // Singular name of the listed records. 'plural' => 'entries-overview', // Plural name of the listed records. 'ajax' => false, ] ); $this->entry_handler = wpforms()->obj( 'entry' ); } /** * Make private timespan properties settable for access within this class. * * @since 1.8.2 * * @param array $timespan Array of start and end dates. */ public function set_timespans( $timespan ) { $this->timespan = $timespan; $this->timespan_mysql = Datepicker::process_timespan_mysql( $timespan ); } /** * Determines whether a current query has forms to loop over. * * @since 1.8.2 * * @return bool */ public function has_items() { if ( $this->items !== null ) { return ! empty( $this->items ); } // Check to see if at least one form with respect to user access control has been published. $one_published_form = wpforms()->obj( 'form' )->get( '', [ 'post_type' => wpforms()->obj( 'form' )::POST_TYPES, 'fields' => 'ids', 'post_status' => 'publish', 'numberposts' => 1, 'nopaging' => false, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ] ); return ! empty( $one_published_form ); } /** * No forms found text. * * @since 1.8.2 */ public function no_items() { esc_html_e( 'No forms found.', 'wpforms' ); } /** * Get list columns. * * @since 1.8.2 * * @return array */ public function get_columns() { return [ 'name' => __( 'Form Name', 'wpforms' ), 'created' => __( 'Created', 'wpforms' ), 'last_entry' => __( 'Last Entry', 'wpforms' ), 'all_time' => __( 'All Time', 'wpforms' ), 'timespan' => isset( $this->timespan[3] ) ? esc_html( $this->timespan[3] ) : '', // The 4th item in the array is always a label. 'graph' => __( 'Graph', 'wpforms' ), ]; } /** * Return "Form Name" column. * * @since 1.8.2 * * @param WP_Post $form Form object. * * @return string */ public function column_name( $form ) { $name = ! empty( $form->post_title ) ? $form->post_title : $form->post_name; $link = $this->get_form_entries_url( $form, $name ); if ( wpforms_is_form_template( $form ) ) { $link .= _post_states( $form, false ); } return $link; } /** * Return "Created" column. * * @since 1.8.2 * * @param WP_Post $form Form object. * * @return string */ public function column_created( $form ) { return get_the_date( get_option( 'date_format' ), $form ); } /** * Return "Last Entry" column. * * @since 1.8.4 * * @param WP_Post $form Form object. * * @return string * @noinspection HtmlUnknownTarget */ public function column_last_entry( $form ) { $last_entry = wpforms()->obj( 'entry' )->get_last( $form->ID, '', 'date' ); if ( ! $last_entry ) { return self::PLACEHOLDER; } $entry_url = add_query_arg( [ 'page' => 'wpforms-entries', 'view' => 'details', 'entry_id' => $last_entry->entry_id, ], admin_url( 'admin.php' ) ); $label = wpforms_date_format( $last_entry->date, get_option( 'date_format' ) ); if ( wpforms_current_user_can( 'edit_entry_single', $last_entry->entry_id ) ) { return sprintf( '<a href="%s">%s</a>', esc_url( $entry_url ), $label ); } return $label; } /** * Return "All Time" column. * * @since 1.8.2 * * @param WP_Post $form Form object. * * @return string */ public function column_all_time( $form ) { $form_id = $form->ID; $total_entries = isset( $this->total_entry_counts[ $form_id ] ) ? absint( $this->total_entry_counts[ $form_id ]->count ) : 0; $form_data = wpforms_decode( $form->post_content ); if ( $total_entries === 0 && ! empty( $form_data['settings']['disable_entries'] ) ) { return '—'; } return $this->get_form_entries_url( $form, $total_entries ); } /** * Return "Last 30 (x) Days" column. * * @since 1.8.2 * * @param WP_Post $form Form object. * * @return string */ public function column_timespan( $form ) { list( $start_date, $end_date ) = $this->timespan; $total_entries = $this->get_entries_count_by_form( $form ); $query_string = [ 'action' => 'filter_date', 'date' => sprintf( '%s - %s', $start_date->format( Datepicker::DATE_FORMAT ), $end_date->format( Datepicker::DATE_FORMAT ) ), ]; $form_data = wpforms_decode( $form->post_content ); if ( $total_entries === 0 && ! empty( $form_data['settings']['disable_entries'] ) ) { return '—'; } return $this->get_form_entries_url( $form, $total_entries, $query_string ); } /** * Return "Graph" column. * * @since 1.8.2 * * @param WP_Post $form Form object. * * @return string */ public function column_graph( $form ) { // Bail early, if the total number of entries is not available. if ( ! $this->form_has_entries_timespan ) { return ''; } $buttons = []; $buttons[] = sprintf( '<button type="button" class="wpforms-reset-chart dashicons dashicons-dismiss wpforms-hide" title="%s"></button>', esc_attr__( 'Reset chart to display all forms', 'wpforms' ) ); $buttons[] = sprintf( '<button type="button" class="wpforms-show-chart dashicons dashicons-chart-bar" title="%s" data-form="%d"></button>', esc_attr__( 'Display only this form data in the graph', 'wpforms' ), absint( $form->ID ) ); return implode( '', $buttons ); } /** * Remove the pagination links from the top navigation. * * @since 1.8.2 * * @param string $which Top or bottom. */ public function display_tablenav( $which ) { // Bail early, if the position is not "bottom". if ( $which !== 'bottom' ) { return; } parent::display_tablenav( $which ); } /** * Set _column_headers property for a table list. * * @since 1.8.2 */ private function prepare_column_headers() { $this->_column_headers = [ $this->get_columns(), get_hidden_columns( $this->screen ), $this->get_sortable_columns(), ]; } /** * List of CSS classes for the "WP_List_Table" table tag. * * @global string $mode List table view mode. * * @since 1.8.2 * * @return array */ protected function get_table_classes() { global $mode; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $mode = get_user_setting( 'posts_list_mode', 'list' ); $mode_class = esc_attr( 'table-view-' . $mode ); $classes = [ 'widefat', 'striped', 'wpforms-table-list', $mode_class, ]; // For styling purposes, we'll add a dedicated class name for determining the number of visible columns. // The ideal threshold for applying responsive styling is set at "5" columns based on the need for "Tablet" view. $columns_class = $this->get_column_count() > 5 ? 'many' : 'few'; $classes[] = "has-{$columns_class}-columns"; return $classes; } /** * Get a list of sortable columns. * * @since 1.8.2 * * @return array */ protected function get_sortable_columns() { return [ 'name' => [ 'title', false ], 'created' => [ 'date', false ], 'last_entry' => [ 'entry', false ], 'all_time' => [ 'entries', false ], 'timespan' => [ 'timespan', false ], ]; } /** * Returns the number of forms to show per page. * * @since 1.8.2 * * @return int */ private function get_per_page() { return $this->get_items_per_page( 'wpforms_entries_per_page', $this->entry_handler->get_count_per_page() ); } /** * Returns the `offset` based on the current pagination query. * * @since 1.8.2 * * @return int */ private function get_offset() { $per_page = $this->get_per_page(); $current_page = $this->get_pagenum(); if ( 1 < $current_page ) { return $per_page * ( $current_page - 1 ); } return 0; } /** * Prepare table list items. * * @since 1.8.2 */ public function prepare_items() { $this->prepare_column_headers(); // phpcs:disable WordPress.Security.NonceVerification.Recommended $order = isset( $_GET['order'] ) && $_GET['order'] === 'asc' ? 'ASC' : 'DESC'; $orderby = isset( $_GET['orderby'] ) ? sanitize_key( $_GET['orderby'] ) : 'ID'; // phpcs:enable WordPress.Security.NonceVerification.Recommended $offset = $this->get_offset(); $per_page = $this->get_per_page(); $form_ids = $this->get_form_ids( $order, $orderby ); $total_items = count( $form_ids ); $in_query_form_ids = array_splice( $form_ids, $offset, $per_page ); $this->total_entry_counts = $this->get_total_entry_counts_by_form_ids( $in_query_form_ids ); $this->items = $this->build_query( $in_query_form_ids ); // Set the pagination. $this->set_pagination_args( [ 'per_page' => $per_page, 'total_items' => $total_items, ] ); } /** * Retrieves an array of the latest forms, or forms matching the given criteria. * * @since 1.8.2 * * @param array $form_ids An array of post IDs to retrieve. * * @return array */ private function build_query( $form_ids ) { // Bail early, if no forms were found to initiate the query. if ( empty( $form_ids ) ) { return []; } return wpforms()->obj( 'form' )->get( '', [ 'orderby' => 'post__in', 'post_type' => wpforms()->obj( 'form' )::POST_TYPES, 'post__in' => $form_ids, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ] ); } /** * Returns an array of form IDs (int[]). * * @since 1.8.2 * * @param string $order Designates ascending or descending order of forms. Default 'DESC'. * @param string $orderby Sort retrieved forms by parameter. Default 'ID'. * * @return array */ private function get_form_ids( $order = 'DESC', $orderby = 'ID' ) { $exclude = []; // Sort the results by the overall number of entries. if ( $orderby === 'entries' ) { $exclude = $this->sort_by_all_time_entries( $order ); } // Sort by the overall number of entries within a specified timeframe. if ( $orderby === 'timespan' ) { $exclude = $this->sort_by_entries_in_timespan( $order ); } // Sort by the last entry. if ( $orderby === 'entry' ) { $exclude = $this->sort_by_last_entry( $order ); } $form_handler = wpforms()->obj( 'form' ); $post_type = wpforms()->obj( 'entries_overview' )->overview_show_form_templates() ? $form_handler::POST_TYPES : [ 'wpforms' ]; $form_ids = (array) $form_handler->get( '', [ 'post_type' => $post_type, 'fields' => 'ids', 'order' => $order, 'orderby' => $orderby, 'exclude' => $exclude, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ] ); // Form ids from the entries' table should be combined with the main query. $form_ids = $order === 'ASC' ? array_merge( $form_ids, $exclude ) : array_merge( $exclude, $form_ids ); return wpforms()->obj( 'access' )->filter_forms_by_current_user_capability( $form_ids, 'view_entries_form_single' ); } /** * Retrieves an array of sorted forms based on the number of entries. * * @global wpdb $wpdb Instantiation of the wpdb class. * * @since 1.8.2 * * @param string $order Designates ascending or descending order of forms. Default 'DESC'. * * @return array */ private function sort_by_all_time_entries( $order = 'DESC' ) { global $wpdb; $spam_status = SpamEntry::ENTRY_STATUS; $trash_status = 'trash'; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $form_ids = $wpdb->get_col( "SELECT DISTINCT form_id, COUNT(entry_id) as count FROM {$this->entry_handler->table_name} WHERE status NOT IN ( '{$spam_status}', '{$trash_status}' ) GROUP BY form_id ORDER BY count {$order}" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared return $this->filter_published_form_ids( $form_ids ); } /** * Retrieves an array of sorted forms based on the last entry. * * @global wpdb $wpdb Instantiation of the wpdb class. * * @since 1.8.4 * * @param string $order Designates ascending or descending order of forms. Default 'DESC'. * * @return array */ private function sort_by_last_entry( $order = 'DESC' ) { global $wpdb; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $form_ids = $wpdb->get_col( $wpdb->prepare( "SELECT form_id FROM {$this->entry_handler->table_name} WHERE status NOT IN ( %s, %s ) GROUP BY form_id ORDER BY MAX(date) {$order}", [ SpamEntry::ENTRY_STATUS, WPForms_Entries_List::TRASH_ENTRY_STATUS, ] ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared return $this->filter_published_form_ids( $form_ids ); } /** * Retrieves an array of sorted forms based on the number of entries. * * @global wpdb $wpdb Instantiation of the wpdb class. * * @since 1.8.2 * * @param string $order Designates ascending or descending order of forms. Default 'DESC'. * * @return array */ private function sort_by_entries_in_timespan( $order = 'DESC' ) { global $wpdb; list( $start_date, $end_date ) = $this->timespan_mysql; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $form_ids = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT form_id, COUNT(entry_id) as count FROM {$this->entry_handler->table_name} WHERE date >= %s AND date <= %s AND status NOT IN ( %s, %s ) GROUP BY form_id ORDER BY count {$order}", [ $start_date->format( Datepicker::DATETIME_FORMAT ), $end_date->format( Datepicker::DATETIME_FORMAT ), SpamEntry::ENTRY_STATUS, 'trash', ] ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared return $this->filter_published_form_ids( $form_ids ); } /** * Counts the number of entries for a specific form id within the specified timespan. * * @global wpdb $wpdb Instantiation of the wpdb class. * * @since 1.8.2 * * @param WP_Post $form Form object. * * @return int */ private function get_entries_count_by_form( $form ) { global $wpdb; list( $start_date, $end_date ) = $this->timespan_mysql; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $total_entries = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(entry_id) as count FROM {$this->entry_handler->table_name} WHERE form_id = %d AND date >= %s AND date <= %s AND status NOT IN ( %s, %s )", [ $form->ID, $start_date->format( Datepicker::DATETIME_FORMAT ), $end_date->format( Datepicker::DATETIME_FORMAT ), SpamEntry::ENTRY_STATUS, 'trash', ] ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $this->form_has_entries_timespan = $total_entries > 0; return $total_entries; } /** * Retrieves an entire SQL result set from the entries table database (i.e., all applicable rows). * Executes a SQL query and returns the entire SQL result. * * @since 1.8.2 * * @param array $form_ids An array of post IDs to retrieve. * * @return array */ private function get_total_entry_counts_by_form_ids( $form_ids ) { // Bail early, if no forms were found to initiate the query. if ( empty( $form_ids ) ) { return []; } $form_ids_in = wpforms_wpdb_prepare_in( $form_ids, '%d' ); $spam_status = SpamEntry::ENTRY_STATUS; $trash_status = 'trash'; // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared return (array) $this->entry_handler->get_results( "SELECT DISTINCT form_id, COUNT(entry_id) as count FROM {$this->entry_handler->table_name} WHERE form_id IN ({$form_ids_in}) AND status NOT IN ( '{$spam_status}', '{$trash_status}' ) GROUP BY form_id", OBJECT_K ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared } /** * Removes form ids that are being deleted or that are no longer published from the given stack. * * @since 1.8.2 * * @param array $form_ids An array of post IDs to retrieve. * * @return array */ private function filter_published_form_ids( $form_ids ) { // Bail early, if no forms were found to initiate the query. if ( empty( $form_ids ) ) { return []; } $form_ids = array_filter( $form_ids, static function ( $form_id ) { // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement return get_post_status( $form_id ) === 'publish'; } ); return array_map( 'absint', $form_ids ); } /** * Get the given form entries page URL. * * @since 1.8.2 * * @param WP_Post $form Form object. * @param string $text If provided, displays the given text inside the link element. * @param array $query_string If provided, merge user defined arguments into defaults query parameters. * * @return string * @noinspection HtmlUnknownTarget */ private function get_form_entries_url( $form, $text = self::PLACEHOLDER, $query_string = [] ) { // When a display text is not provided, leave early. if ( $text === self::PLACEHOLDER ) { return $text; } return sprintf( '<a href="%s">%s</a>', add_query_arg( wp_parse_args( $query_string, [ 'view' => 'list', 'form_id' => $form->ID, 'page' => 'wpforms-entries', ] ), admin_url( 'admin.php' ) ), esc_html( $text ) ); } } Upload File Directory Listing NameTypeSizeActions.. (Parent Directory)DirAjax.phpFile5.73 KB Rename | Delete | EditPage.phpFile9.77 KB Rename | Delete | EditTable.phpFile20.16 KB Rename | Delete | Edit