__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

[email protected]: ~ $
<?php

namespace WPForms\Pro\Admin\Entries;

use WP_List_Table;
use WPForms\Admin\Notice;
use WPForms\Db\Payments\ValueValidator;
use WPForms\Pro\Admin\DashboardWidget;
use WPForms\Pro\Admin\Entries\Table\Facades\Columns;

// IMPORTANT NOTICE:
// This line is needed to prevent fatal errors in the third-party plugins.
// We know about Jetpack (probably others also) can load WP classes during cron jobs or something similar.
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';

/**
 * Generate the table on the entries' overview page.
 *
 * @since 1.8.6
 */
class ListTable extends WP_List_Table {

	// @todo: remove those phpcs lines when deprecated version will be set as a real number, it is a bug in phpcs.
	// phpcs:disable WPForms.Comments.DeprecatedTag.InvalidDeprecatedVersion
	/**
	 * The ID of the table column called "Entry ID".
	 *
	 * @since 1.8.6
	 * @deprecated 1.8.6. Use \WPForms\Pro\Admin\Entries\Table\Facades\Columns::COLUMN_ENTRY_ID instead.
	 *
	 * @var int
	 */
	const COLUMN_ENTRY_ID = -1;

	/**
	 * The ID of the table column called "Entry Notes".
	 *
	 * @since 1.8.6
	 * @deprecated 1.8.6. Use \WPForms\Pro\Admin\Entries\Table\Facades\Columns::COLUMN_NOTES_COUNT instead.
	 *
	 * @var int
	 */
	const COLUMN_NOTES_COUNT = -2;
	// phpcs:enable WPForms.Comments.DeprecatedTag.InvalidDeprecatedVersion

	/**
	 * Number of entries to show per page.
	 *
	 * @since 1.8.6
	 *
	 * @var int
	 */
	public $per_page;

	/**
	 * Form data as an array.
	 *
	 * @since 1.8.6
	 *
	 * @var array
	 */
	public $form_data;

	/**
	 * Form id.
	 *
	 * @since 1.8.6
	 *
	 * @var string|integer
	 */
	public $form_id;

	/**
	 * Number of different entry types.
	 *
	 * @since 1.8.6
	 *
	 * @var int
	 */
	public $counts;

	/**
	 * Date and time format.
	 *
	 * @since 1.8.6
	 *
	 * @var array
	 */
	private $datetime_format;

	/**
	 * Primary class constructor.
	 *
	 * @since 1.8.6
	 */
	public function __construct() {

		// Use the parent constructor to build the main class properties.
		parent::__construct(
			[
				'singular' => 'entry',
				'plural'   => 'entries',
				'ajax'     => false,
				'screen'   => 'entries',
			]
		);

		// Default number of forms to show per page.
		$this->per_page = wpforms()->obj( 'entry' )->get_count_per_page();

		// Date and time formats.
		$this->datetime_format = [
			'date' => get_option( 'date_format' ),
			'time' => get_option( 'time_format' ),
		];

		$this->hooks();
	}

	/**
	 * Register hooks.
	 *
	 * @since 1.8.6
	 */
	private function hooks() {

		// Add trashed views.
		add_filter( 'wpforms_entries_table_views', [ $this, 'add_trashed_views' ] );
	}

	/**
	 * List of CSS classes for the "WP_List_Table" table tag.
	 *
	 * @global string $mode List table view mode.
	 *
	 * @since 1.8.6
	 *
	 * @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',
			$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";

		// Add trash class.
		if ( $this->is_trash_list() ) {
			$classes[] = 'wpforms-entries-table-trash';
		}

		/**
		 * Filters the list of CSS classes for the WP_List_Table table tag.
		 *
		 * @since 1.8.3
		 *
		 * @param string[] $classes   An array of CSS classes for the table tag.
		 * @param array    $form_data Form data.
		 */
		return apply_filters( 'wpforms_entries_table_get_table_classes', $classes, $this->form_data ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
	}

	/**
	 * Get the entry counts for various types of entries.
	 *
	 * @since 1.8.6
	 */
	public function get_counts() {

		$this->counts = [];
		$entry_obj    = wpforms()->obj( 'entry' );

		$this->counts = $entry_obj->get_counts( [ 'form_id' => $this->form_id ] );

		/**
		 * Filters the array of entries counts in different views.
		 *
		 * @since 1.3.3
		 *
		 * @param int[] $counts    An array of entries' counts.
		 * @param array $form_data Form data.
		 */
		$this->counts = (array) apply_filters( 'wpforms_entries_table_counts', $this->counts, $this->form_data ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName

		$defaults = [
			'total'   => 0,
			'unread'  => 0,
			'payment' => 0,
			'starred' => 0,
			'spam'    => 0,
			'trash'   => 0,
		];

		$this->counts = wp_parse_args( $this->counts, $defaults );
	}

	/**
	 * Retrieve the view types.
	 *
	 * @since 1.8.6
	 *
	 * @noinspection HtmlUnknownAttribute
	 * @noinspection HtmlUnknownTarget
	 */
	public function get_views() {

		$base = remove_query_arg( [ 'type', 'status', 'paged', 'message' ] );

		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		$current = isset( $_GET['type'] ) ? sanitize_key( wp_unslash( $_GET['type'] ) ) : '';
		$total   = '&nbsp;<span class="count">(<span class="total-num">' . $this->counts['total'] . '</span>)</span>';
		$unread  = '&nbsp;<span class="count">(<span class="unread-num">' . $this->counts['unread'] . '</span>)</span>';
		$starred = '&nbsp;<span class="count">(<span class="starred-num">' . $this->counts['starred'] . '</span>)</span>';
		$all     = empty( $_GET['status'] ) && ( $current === 'all' || empty( $current ) ) ? ' class="current"' : '';
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		$views = [
			'all'    => sprintf(
				'<a href="%s"%s>%s</a>',
				esc_url( $base ),
				$all,
				esc_html__( 'All', 'wpforms' ) . $total
			),
			'unread' => sprintf(
				'<a href="%s"%s>%s</a>',
				esc_url( add_query_arg( 'type', 'unread', $base ) ),
				$current === 'unread' ? ' class="current"' : '',
				esc_html__( 'Unread', 'wpforms' ) . $unread
			),
		];

		// Only show the payment view if the form has a payment field.
		// Add the "payment" view after the "unread" view.
		if ( isset( $this->counts['payment'] ) ) {
			$payment          = '&nbsp;<span class="count">(<span class="payment-num">' . $this->counts['payment'] . '</span>)</span>';
			$views['payment'] = sprintf(
				'<a href="%s"%s>%s</a>',
				esc_url( add_query_arg( 'type', 'payment', $base ) ),
				$current === 'payment' ? ' class="current"' : '',
				_n( 'Payment', 'Payments', $this->counts['payment'], 'wpforms' ) . $payment
			);
		}

		$views['starred'] = sprintf(
			'<a href="%s"%s>%s</a>',
			esc_url( add_query_arg( 'type', 'starred', $base ) ),
			$current === 'starred' ? ' class="current"' : '',
			esc_html__( 'Starred', 'wpforms' ) . $starred
		);

		/**
		 * Filters the array of entries counts in different views.
		 *
		 * @since 1.3.3
		 *
		 * @param array $views     An array of views.
		 * @param array $form_data Form data.
		 * @param int[] $counts    An array of entries' counts.
		 *
		 * @return array
		 */
		return apply_filters( 'wpforms_entries_table_views', $views, $this->form_data, $this->counts ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
	}

	/**
	 * Add Trashed views to the list of views.
	 * We've separated it to use the filter because we need it to be after spam, which uses the filter.
	 *
	 * @since 1.8.5
	 *
	 * @param array $views Entries table views.
	 *
	 * @return array $views Array of all the list table views.
	 * @noinspection HtmlUnknownTarget
	 * @noinspection HtmlUnknownAttribute
	 */
	public function add_trashed_views( $views ) {

		if (
			! $this->counts['trash'] &&
			( ! isset( $_GET['status'] ) || $_GET['status'] !== 'trash' ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			! wpforms()->obj( 'entry' )->get_trash_count( $this->form_id )
		) {
			return $views;
		}

		$trashed = '&nbsp;<span class="count">(<span class="trashed-num">' . $this->counts['trash'] . '</span>)</span>';
		$base    = remove_query_arg( [ 'type', 'status', 'paged', 'message' ] );

		$views['trashed'] = sprintf(
			'<a href="%1s"%2s>%3s</a>',
			esc_url( add_query_arg( 'status', Page::TRASH_ENTRY_STATUS, $base ) ),
			$this->is_trash_list() ? ' class="current"' : '',
			esc_html__( 'Trash', 'wpforms' ) . $trashed
		);

		return $views;
	}

	/**
	 * Retrieve the table columns.
	 *
	 * @since 1.8.6
	 *
	 * @return array Array of all the list table columns.
	 */
	public function get_columns(): array {

		return Columns::get_list_table_columns( $this );
	}

	/**
	 * Retrieve the table's sortable columns.
	 *
	 * @since 1.8.6
	 *
	 * @return array Array of all the sortable columns
	 */
	public function get_sortable_columns() {

		$sortable = [
			'entry_id'    => [ 'id', false ],
			'notes_count' => [ 'notes_count', false ],
			'id'          => [ 'title', false ],
			'date'        => [ 'date', false ],
		];

		/**
		 * Filters the Entries list table sortable columns.
		 *
		 * @since 1.3.3
		 *
		 * @param array $sortable  Sortable columns.
		 * @param array $form_data Form data.
		 *
		 * @return bool
		 */
		return apply_filters( 'wpforms_entries_table_sortable', $sortable, $this->form_data ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
	}

	/**
	 * Get the list of fields, that are disallowed to be displayed as column in a table.
	 *
	 * @since 1.8.6
	 *
	 * @return array
	 */
	public static function get_columns_form_disallowed_fields() {

		/**
		 * Filter the list of the disallowed fields in the entries' table.
		 *
		 * @since 1.4.4
		 *
		 * @param array $fields Field types list.
		 */
		return (array) apply_filters( 'wpforms_entries_table_fields_disallow', [ 'captcha', 'divider', 'entry-preview', 'html', 'pagebreak', 'layout' ] ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
	}

	/**
	 * Render the checkbox column.
	 *
	 * @since 1.8.6
	 *
	 * @param object $entry Entry data from DB.
	 *
	 * @return string
	 */
	public function column_cb( $entry ) {

		return '<input type="checkbox" name="entry_id[]" value="' . absint( $entry->entry_id ) . '" />';
	}

	/**
	 * Display "Type" column.
	 *
	 * @since 1.8.6
	 *
	 * @param object $entry       Current entry data.
	 * @param string $column_name Current column name.
	 *
	 * @return string
	 * @noinspection PhpMissingParamTypeInspection
	 * @noinspection PhpUnusedParameterInspection
	 */
	public function column_type_field( $entry, $column_name ) {

		// Show the original type if is trash.
		if ( isset( $_GET['status'] ) && $_GET['status'] === Page::TRASH_ENTRY_STATUS ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$meta = wpforms()->obj( 'entry_meta' )->get_meta(
				[
					'entry_id' => $entry->entry_id,
					'type'     => 'status_prev',
				]
			);

			if ( isset( $meta[0] ) && ! empty( $meta[0]->status ) ) {
				return ucwords( sanitize_text_field( $meta[0]->status ) );
			}
		}

		// If the entry has a status, show it.
		if ( ! empty( $entry->status ) && $entry->type !== 'payment' && $entry->status !== Page::TRASH_ENTRY_STATUS ) {
			return ucwords( sanitize_text_field( $entry->status ) );
		}

		// Otherwise, show "Completed" as a placeholder.
		return esc_html__( 'Completed', 'wpforms' );
	}

	/**
	 * Display payment status and total amount.
	 *
	 * @since 1.8.6
	 *
	 * @param object $entry Current entry data.
	 *
	 * @return string
	 * @noinspection HtmlUnknownTarget
	 */
	private function column_payment_field( $entry ) {

		list( $status_label, $status_slug, $payment ) = $this->get_payment_status_by_entry_id( (int) $entry->entry_id );

		// If payment data is not found, return customized N/A.
		if ( ! $payment ) {
			return sprintf(
				'<span class="payment-status-%s">%s</span>',
				$status_slug,
				$status_label
			);
		}

		if ( ! $payment->is_published ) {
			return sprintf(
				'<span class="payment-status-%s" title="%s">%s</a>',
				sanitize_html_class( $status_slug ),
				esc_html__( 'The payment in the Trash.', 'wpforms' ),
				wpforms_format_amount( $payment->total_amount, true, $payment->currency )
			);
		}

		$payment_url = '';

		if ( wpforms_current_user_can() ) {
			// Generate the single payment URL.
			$payment_url = add_query_arg(
				[
					'page'       => 'wpforms-payments',
					'view'       => 'payment',
					'payment_id' => absint( $payment->id ),
				],
				admin_url( 'admin.php' )
			);
		}

		return sprintf(
			'<a href="%s" class="payment-status-%s" title="%s">%s</a>',
			esc_url( $payment_url ),
			sanitize_html_class( $status_slug ),
			esc_html( $status_label ),
			wpforms_format_amount( $payment->total_amount, true, $payment->currency )
		);
	}

	/**
	 * Show specific form fields.
	 *
	 * @since 1.8.6
	 *
	 * @param object $entry       Entry data from DB.
	 * @param string $column_name Column unique name.
	 *
	 * @return string
	 */
	public function column_form_field( $entry, $column_name ) {

		if ( strpos( $column_name, 'wpforms_field_' ) === false ) {
			/**
			 * Filters the entry table column value in case it is not a form field.
			 *
			 * @since 1.8.6
			 *
			 * @param string $value       Value.
			 * @param object $entry       Current entry data.
			 * @param string $column_name Current column name.
			 *
			 * @return string
			 */
			return apply_filters( 'wpforms_pro_admin_entries_list_table_column_form_field_meta_field_value', '', $entry, $column_name );
		}

		$field_id     = (int) str_replace( 'wpforms_field_', '', $column_name );
		$entry_fields = (array) wpforms_decode( $entry->fields );
		$value        = $entry_fields[ $field_id ]['value'] ?? '';

		if ( ! wpforms_is_empty_string( $value ) ) {
			$value = wp_strip_all_tags( trim( $value ) );
		}

		if ( ! wpforms_is_empty_string( $value ) ) {

			$field_type = $entry_fields[ $field_id ]['type'] ?? '';
			$value      = $this->truncate_long_value( $value, $field_type );
			$value      = nl2br( $value );

			/** This filter is documented in src/SmartTags/SmartTag/FieldHtmlId.php.*/
			return apply_filters( 'wpforms_html_field_value', $value, $entry_fields[ $field_id ], $this->form_data, 'entry-table' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
		}

		return '-';
	}

	/**
	 * Render the columns.
	 *
	 * @since 1.8.6
	 * @since 1.5.7 Added an `Entry Notes` column.
	 *
	 * @param object $entry       Current entry data.
	 * @param string $column_name Current column name.
	 *
	 * @return string
	 */
	public function column_default( $entry, $column_name ): string { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$field_type = $this->get_field_type( $entry, $column_name );

		switch ( strtolower( $column_name ) ) {
			case 'entry_id':
			case 'id':
				$value = absint( $entry->entry_id );
				break;

			case 'notes_count':
				$value = absint( $entry->notes_count );
				break;

			case 'date':
				$value = sprintf(
					'<span class="date">%1$s</span> <span class="time">%3$s %2$s</span>',
					wpforms_datetime_format( $entry->date, $this->datetime_format['date'], true ),
					wpforms_datetime_format( $entry->date, $this->datetime_format['time'], true ),
					esc_html__( /* translators: date and time separator. */ 'at', 'wpforms' )
				);
				break;

			case 'type':
				$value = $this->column_type_field( $entry, $column_name );
				break;

			case 'payment':
				$value = $this->column_payment_field( $entry );
				break;

			case 'user_ip':
				$value = esc_html( $entry->ip_address );
				break;

			case 'user_agent':
				$value = esc_html( $entry->user_agent );
				break;

			case 'user_uuid':
				$value = esc_html( $entry->user_uuid );
				break;

			default:
				$value = $this->column_form_field( $entry, $column_name );
		}

		// Adds a wrapper with a field type in data attribute.
		if ( ! empty( $value ) && ! empty( $field_type ) ) {
			$value = sprintf( '<div data-field-type="%s">%s</div>', esc_attr( $field_type ), $value );
		}

		/**
		 * Allow filtering entry table column value.
		 *
		 * @since 1.0.0
		 * @since 1.7.0 Added Field type.
		 *
		 * @param string $value       Value.
		 * @param object $entry       Current entry data.
		 * @param string $column_name Current column name.
		 * @param string $field_type  Field type.
		 */
		return apply_filters( 'wpforms_entry_table_column_value', $value, $entry, $column_name, $field_type ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
	}

	/**
	 * Retrieve a field type.
	 *
	 * @since 1.8.6
	 *
	 * @param object $entry       Current entry data.
	 * @param string $column_name Current column name.
	 *
	 * @return string
	 */
	public function get_field_type( $entry, $column_name ) {

		$field_id     = str_replace( 'wpforms_field_', '', $column_name );
		$entry_fields = wpforms_decode( $entry->fields );
		$field_type   = '';

		if (
			! empty( $entry_fields[ $field_id ] ) &&
			isset( $entry_fields[ $field_id ]['type'] ) &&
			! wpforms_is_empty_string( $entry_fields[ $field_id ]['type'] )
		) {
			$field_type = $entry_fields[ $field_id ]['type'];
		}

		return $field_type;
	}

	/**
	 * Render the indicators' column.
	 *
	 * @since 1.8.6
	 *
	 * @param object $entry Entry data from DB.
	 *
	 * @return string
	 */
	public function column_indicators( $entry ) {

		// Stars.
		$star_action = ! empty( $entry->starred ) ? 'unstar' : 'star';
		$star_title  = ! empty( $entry->starred ) ? esc_html__( 'Unstar entry', 'wpforms' ) : esc_html__( 'Star entry', 'wpforms' );
		$star_icon   = '<a href="#" class="indicator-star ' . $star_action . '" data-id="' . absint( $entry->entry_id ) . '" data-form-id="' . absint( $entry->form_id ) . '" title="' . esc_attr( $star_title ) . '"><span class="dashicons dashicons-star-filled"></span></a>';

		// Viewed.
		$read_action = ! empty( $entry->viewed ) ? 'unread' : 'read';
		$read_title  = ! empty( $entry->viewed ) ? esc_html__( 'Mark entry unread', 'wpforms' ) : esc_html__( 'Mark entry read', 'wpforms' );
		$read_icon   = '<a href="#" class="indicator-read ' . $read_action . '" data-id="' . absint( $entry->entry_id ) . '" data-form-id="' . absint( $entry->form_id ) . '" title="' . esc_attr( $read_title ) . '"></a>';

		return $star_icon . $read_icon;
	}

	/**
	 * Render the actions' column.
	 *
	 * @since 1.8.6
	 *
	 * @param object $entry Entry data from DB.
	 *
	 * @return string
	 * @noinspection HtmlUnknownTarget
	 */
	public function column_actions( $entry ) {

		$actions = [];

		// Show the delete action only on trash and spam page.
		if ( $this->should_delete( $entry->entry_id ) ) {
			if ( wpforms_current_user_can( 'delete_entries_form_single', $this->form_id ) ) {
				// Restore.
				$actions['restore'] = sprintf(
					'<a href="%s" title="%s" class="restore">%s</a>',
					esc_url(
						wp_nonce_url(
							add_query_arg(
								[
									'view'     => 'list',
									'action'   => 'restore',
									'form_id'  => $this->form_id,
									'entry_id' => $entry->entry_id,
								]
							),
							'bulk-entries'
						)
					),
					esc_attr__( 'Restore Form Entry', 'wpforms' ),
					esc_html__( 'Restore', 'wpforms' )
				);
				// Delete.
				$actions['delete'] = sprintf(
					'<a href="%s" title="%s" class="delete">%s</a>',
					esc_url(
						wp_nonce_url(
							add_query_arg(
								[
									'view'     => 'list',
									'action'   => 'delete',
									'form_id'  => $this->form_id,
									'entry_id' => $entry->entry_id,
								]
							),
							'bulk-entries'
						)
					),
					esc_attr__( 'Delete Form Entry', 'wpforms' ),
					esc_html__( 'Delete', 'wpforms' )
				);

			}
		} else {
			// View.
			$actions['view'] = sprintf(
				'<a href="%s" title="%s" class="view">%s</a>',
				esc_url(
					add_query_arg(
						[
							'view'     => 'details',
							'entry_id' => $entry->entry_id,
						],
						admin_url( 'admin.php?page=wpforms-entries' )
					)
				),
				esc_attr__( 'View Form Entry', 'wpforms' ),
				esc_html__( 'View', 'wpforms' )
			);

			if (
				wpforms_current_user_can( 'edit_entries_form_single', $this->form_id ) &&
				wpforms()->obj( 'entry' )->has_editable_fields( $entry )
			) {
				// Edit.
				$actions['edit'] = sprintf(
					'<a href="%s" title="%s" class="edit">%s</a>',
					esc_url(
						add_query_arg(
							[
								'view'     => 'edit',
								'entry_id' => $entry->entry_id,
							],
							admin_url( 'admin.php?page=wpforms-entries' )
						)
					),
					esc_attr__( 'Edit Form Entry', 'wpforms' ),
					esc_html__( 'Edit', 'wpforms' )
				);
			}

			if ( wpforms_current_user_can( 'delete_entries_form_single', $this->form_id ) ) {
				// Trash can share the same capabilities as deleting.
				$actions['trash'] = sprintf(
					'<a href="%s" title="%s" class="trash">%s</a>',
					esc_url(
						wp_nonce_url(
							add_query_arg(
								[
									'view'     => 'list',
									'action'   => 'trash',
									'form_id'  => $this->form_id,
									'entry_id' => $entry->entry_id,
								]
							),
							'bulk-entries'
						)
					),
					esc_attr__( 'Trash Form Entry', 'wpforms' ),
					esc_html__( 'Trash', 'wpforms' )
				);
			}
		}

		/**
		 * Filters the list of actions available for each entry in the table.
		 *
		 * @since 1.0.0
		 *
		 * @param array $actions An array of actions.
		 * @param array $entry   Entry data.
		 */
		$actions = apply_filters( 'wpforms_entry_table_actions', $actions, $entry ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName

		return implode( ' <span class="sep">|</span> ', $actions );
	}

	/**
	 * Extra controls to be displayed between bulk actions and pagination.
	 *
	 * @since 1.8.6
	 *
	 * @param string $which Either top or bottom of the page.
	 */
	protected function extra_tablenav( $which ) {

		if ( $which === 'top' ) {
			$this->display_date_range_filter();
		}

		$this->display_entry_trash_button();

		/**
		 * Fires after the filter controls, before the table.
		 *
		 * @since 1.8.3
		 */
		do_action( 'wpforms_entries_table_extra_tablenav' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
	}

	/**
	 * Display Empty Trash Button.
	 *
	 * @since 1.8.6
	 */
	private function display_entry_trash_button() {

		if ( ! $this->is_trash_list() ) {
			return;
		}

		$base = add_query_arg(
			[
				'page'    => 'wpforms-entries',
				'view'    => 'list',
				'form_id' => absint( $this->form_id ),
			],
			admin_url( 'admin.php' )
		);

		?>

		<a href="<?php echo esc_url( wp_nonce_url( $base, 'bulk-entries' ) ); ?>" class="button delete-all form-details-actions-removeall" data-page="trash">
			<?php esc_html_e( 'Empty Trash', 'wpforms' ); ?>
		</a>

		<?php
	}

	/**
	 * Display date range filter.
	 *
	 * @since 1.8.6
	 */
	private function display_date_range_filter() {

		/**
		 * Filter to disable date range filter.
		 *
		 * @since 1.8.3
		 *
		 * @param bool $disable_date_range_filter Whether to disable date range filter.
		 */
		if ( apply_filters( 'wpforms_entries_table_display_date_range_filter_disable', false ) ) { // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
			return;
		}

		?>

		<div class="alignleft actions wpforms-filter-date">

			<input type="text" name="date" class="regular-text wpforms-filter-date-selector"
				placeholder="<?php esc_attr_e( 'Select a date range', 'wpforms' ); ?>"
				style="cursor: pointer">

			<button type="submit" name="action" value="filter_date" class="button">
				<?php esc_html_e( 'Filter', 'wpforms' ); ?>
			</button>

		</div>

		<?php
	}

	/**
	 * Define bulk actions available for our table listing.
	 *
	 * @since 1.8.6
	 *
	 * @return array
	 */
	public function get_bulk_actions() {

		$bulk_actions = [
			'read'   => esc_html__( 'Mark as Read', 'wpforms' ),
			'unread' => esc_html__( 'Mark as Unread', 'wpforms' ),
			'star'   => esc_html__( 'Star', 'wpforms' ),
			'unstar' => esc_html__( 'Unstar', 'wpforms' ),
			'print'  => esc_html__( 'Print', 'wpforms' ),
		];

		$bulk_actions = $this->get_addtional_bulk_actions( $bulk_actions );

		/**
		 * Filters bulk actions.
		 *
		 * @since 1.8.3
		 *
		 * @param array $bulk_actions Bulk actions.
		 *
		 * @return array
		 */
		return apply_filters( 'wpforms_entries_table_get_bulk_actions', $bulk_actions ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
	}

	/**
	 * Define additional bulk actions available for our table listing.
	 * Additional settings are all related to the delete/restore action.
	 *
	 * @since 1.8.5
	 *
	 * @param array $bulk_actions Bulk actions.
	 *
	 * @return array
	 */
	private function get_addtional_bulk_actions( $bulk_actions ) {

		if ( ! wpforms_current_user_can( 'delete_entries_form_single', $this->form_id ) ) {
			return $bulk_actions;
		}

		$bulk_actions['null'] = esc_html__( '----------', 'wpforms' );

		if ( wpforms()->obj( 'spam_entry' )->is_spam_list() ) {
			$bulk_actions['unspam'] = esc_html__( 'Mark as Not Spam', 'wpforms' );
		} else {
			$bulk_actions['spam'] = esc_html__( 'Mark as Spam', 'wpforms' );
		}

		if ( $this->is_trash_list() ) {
			$bulk_actions['restore'] = esc_html__( 'Restore', 'wpforms' );
		} else {
			$bulk_actions['trash'] = esc_html__( 'Move to Trash', 'wpforms' );
		}

		$bulk_actions['delete'] = esc_html__( 'Delete', 'wpforms' );

		return $bulk_actions;
	}

	/**
	 * Process the bulk actions.
	 *
	 * @since 1.8.6
	 */
	public function process_bulk_actions() {

		$this->display_bulk_action_message();

		if ( empty( $_REQUEST['_wpnonce'] ) ) {
			return;
		}

		if (
			! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'bulk-entries' ) &&
			! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'bulk-entries-nonce' )
		) {
			return;
		}

		$this->process_bulk_action_single();
	}

	/**
	 * Process single bulk action.
	 *
	 * @since 1.8.6
	 */
	protected function process_bulk_action_single() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$doaction = $this->current_action();
		$status   = '';

		if ( empty( $doaction ) || $doaction === 'filter_date' ) {
			return;
		}

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
		$ids = isset( $_GET['entry_id'] ) ? wp_unslash( $_GET['entry_id'] ) : false;

		if ( ! is_array( $ids ) ) {
			$ids = [ $ids ];
		}

		$ids = array_map( 'absint', $ids );

		if ( empty( $ids ) ) {
			return;
		}

		// Check if it is a Trash list.
		if ( $this->is_trash_list() ) {
			$status = Page::TRASH_ENTRY_STATUS;
		}

		$spam_entry = wpforms()->obj( 'spam_entry' );

		// Check if it is a Spam list.
		if ( $spam_entry->is_spam_list() ) {
			$status = $spam_entry::ENTRY_STATUS;
		}

		$args = [
			'entry_id'    => $ids,
			'is_filtered' => true,
			'number'      => $this->get_items_per_page( 'wpforms_entries_per_page', $this->per_page ),
			'status'      => $status,
		];

		// Get entries, that would be affected.
		$entries_list = wpforms()->obj( 'entry' )->get_entries( $args );

		/**
		 * Filter entries list.
		 *
		 * @since 1.8.3
		 *
		 * @param array $entries_list List of entries.
		 * @param array $args         Arguments.
		 */
		$entries_list = apply_filters( 'wpforms_entries_table_process_actions_entries_list', $entries_list, $args ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName

		$sendback = remove_query_arg( [ 'read', 'unread', 'spam', 'unspam', 'starred', 'unstarred', 'print', 'deleted', 'trashed', 'restored', 'paged' ], wp_get_referer() );

		switch ( $doaction ) {
			// Mark as read.
			case 'read':
				$sendback = $this->process_bulk_action_single_read( $entries_list, $ids, $sendback );
				break;

			// Mark as unread.
			case 'unread':
				$sendback = $this->process_bulk_action_single_unread( $entries_list, $ids, $sendback );
				break;

			// Star entry.
			case 'star':
				$sendback = $this->process_bulk_action_single_star( $entries_list, $ids, $sendback );
				break;

			// Unstar entry.
			case 'unstar':
				$sendback = $this->process_bulk_action_single_unstar( $entries_list, $ids, $sendback );
				break;

			// Print entries.
			case 'print':
				$this->process_bulk_action_single_print( $ids );
				break;

			// Trash entries.
			case 'trash':
				$sendback = $this->process_bulk_action_single_trash( $ids, $sendback );
				break;

			// Delete entries.
			case 'delete':
				$sendback = $this->process_bulk_action_single_delete( $ids, $sendback );
				break;

			// Restore entries.
			case 'restore':
				$sendback = $this->process_bulk_action_single_restore( $ids, $sendback );
				break;

			// Mark as Spam.
			case 'spam':
				$sendback = $this->process_bulk_action_single_spam( $entries_list, $ids, $sendback );
				break;

			// Mark as Not Spam.
			case 'unspam':
				$sendback = $this->process_bulk_action_single_unspam( $entries_list, $ids, $sendback );
				break;
		}

		$sendback = remove_query_arg( [ 'action', 'action2', 'entry_id' ], $sendback );

		wp_safe_redirect( $sendback );
		exit();
	}

	/**
	 * Process the bulk action read.
	 *
	 * @since 1.5.7
	 *
	 * @param array  $entries_list Filtered entries list.
	 * @param array  $ids          IDs to process.
	 * @param string $sendback     URL query string.
	 *
	 * @return string
	 */
	protected function process_bulk_action_single_read( $entries_list, $ids, $sendback ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$form_id = ! empty( $_GET['form_id'] ) ? absint( $_GET['form_id'] ) : false;

		if ( empty( $form_id ) ) {
			return $sendback;
		}

		$user_id = get_current_user_id();
		$entries = wp_list_pluck( $entries_list, 'viewed', 'entry_id' );
		$read    = 0;

		foreach ( $ids as $id ) {

			if ( ! array_key_exists( $id, $entries ) ) {
				continue;
			}

			if ( $entries[ $id ] === '1' ) {
				continue;
			}

			$success = wpforms()->obj( 'entry' )->update(
				$id,
				[
					'viewed' => '1',
				]
			);

			if ( $success ) {

				wpforms()->obj( 'entry_meta' )->add(
					[
						'entry_id' => $id,
						'form_id'  => $form_id,
						'user_id'  => $user_id,
						'type'     => 'log',
						'data'     => wpautop( sprintf( '<em>%s</em>', esc_html__( 'Entry read.', 'wpforms' ) ) ),
					],
					'entry_meta'
				);

				++$read;
			}
		}

		return add_query_arg( 'read', $read, $sendback );
	}

	/**
	 * Process the bulk action unread.
	 *
	 * @since 1.8.6
	 *
	 * @param array  $entries_list Filtered entries list.
	 * @param array  $ids          IDs to process.
	 * @param string $sendback     URL query string.
	 *
	 * @return string
	 */
	protected function process_bulk_action_single_unread( $entries_list, $ids, $sendback ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$form_id = ! empty( $_GET['form_id'] ) ? absint( $_GET['form_id'] ) : false;

		if ( empty( $form_id ) ) {
			return $sendback;
		}

		$user_id = get_current_user_id();
		$entries = wp_list_pluck( $entries_list, 'viewed', 'entry_id' );
		$unread  = 0;

		foreach ( $ids as $id ) {

			if ( ! array_key_exists( $id, $entries ) ) {
				continue;
			}

			if ( $entries[ $id ] === '0' ) {
				continue;
			}

			$success = wpforms()->obj( 'entry' )->update(
				$id,
				[
					'viewed' => '0',
				]
			);

			if ( $success ) {
				wpforms()->obj( 'entry_meta' )->add(
					[
						'entry_id' => $id,
						'form_id'  => $form_id,
						'user_id'  => $user_id,
						'type'     => 'log',
						'data'     => wpautop( sprintf( '<em>%s</em>', esc_html__( 'Entry unread.', 'wpforms' ) ) ),
					],
					'entry_meta'
				);

				++$unread;
			}
		}

		return add_query_arg( 'unread', $unread, $sendback );
	}

	/**
	 * Process the bulk action spam.
	 *
	 * @since 1.8.9
	 *
	 * @param array  $entries_list Filtered entries list.
	 * @param array  $ids          IDs to process.
	 * @param string $sendback     URL query string.
	 */
	protected function process_bulk_action_single_spam( $entries_list, $ids, $sendback ) {

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$form_id = ! empty( $_GET['form_id'] ) ? absint( $_GET['form_id'] ) : false;

		if ( empty( $form_id ) ) {
			return $sendback;
		}

		$user       = get_user_by( 'id', get_current_user_id() );
		$entries    = wp_list_pluck( $entries_list, 'status', 'entry_id' );
		$spam_entry = wpforms()->obj( 'spam_entry' );
		$spam       = 0;

		foreach ( $ids as $id ) {

			if ( ! array_key_exists( $id, $entries ) ) {
				continue;
			}

			if ( $entries[ $id ] === $spam_entry::ENTRY_STATUS ) {
				continue;
			}

			$spam_entry->set_as_spam( $id, $form_id, $user->display_name );

			++$spam;
		}

		return add_query_arg( 'spam', $spam, $sendback );
	}

	/**
	 * Process the bulk action unspam.
	 *
	 * @since 1.8.9
	 *
	 * @param array  $entries_list Filtered entries list.
	 * @param array  $ids          IDs to process.
	 * @param string $sendback     URL query string.
	 *
	 * @return string
	 */
	protected function process_bulk_action_single_unspam( $entries_list, $ids, $sendback ) {

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$form_id = ! empty( $_GET['form_id'] ) ? absint( $_GET['form_id'] ) : false;

		if ( empty( $form_id ) ) {
			return $sendback;
		}

		$entries    = wp_list_pluck( $entries_list, 'status', 'entry_id' );
		$spam_entry = wpforms()->obj( 'spam_entry' );
		$unspam     = 0;

		foreach ( $ids as $id ) {

			if ( ! array_key_exists( $id, $entries ) ) {
				continue;
			}

			if ( $entries[ $id ] !== $spam_entry::ENTRY_STATUS ) {
				continue;
			}

			$entry = wpforms()->obj( 'entry' )->get( $id );

			$spam_entry->set_as_not_spam( $entry );

			++$unspam;
		}

		return add_query_arg( 'unspam', $unspam, $sendback );
	}

	/**
	 * Process the bulk action star.
	 *
	 * @since 1.8.6
	 *
	 * @param array  $entries_list Filtered entries list.
	 * @param array  $ids          IDs to process.
	 * @param string $sendback     URL query string.
	 *
	 * @return string
	 */
	protected function process_bulk_action_single_star( $entries_list, $ids, $sendback ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity

		$form_id = ! empty( $_GET['form_id'] ) ? absint( $_GET['form_id'] ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

		if ( empty( $form_id ) ) {
			return $sendback;
		}

		$user_id = get_current_user_id();
		$entries = wp_list_pluck( $entries_list, 'starred', 'entry_id' );
		$starred = 0;

		foreach ( $ids as $id ) {

			if ( ! array_key_exists( $id, $entries ) ) {
				continue;
			}

			if ( $entries[ $id ] === '1' ) {
				continue;
			}

			$success = wpforms()->obj( 'entry' )->update(
				$id,
				[
					'starred' => '1',
				]
			);

			if ( $success ) {
				wpforms()->obj( 'entry_meta' )->add(
					[
						'entry_id' => $id,
						'form_id'  => $form_id,
						'user_id'  => $user_id,
						'type'     => 'log',
						'data'     => wpautop( sprintf( '<em>%s</em>', esc_html__( 'Entry starred.', 'wpforms' ) ) ),
					],
					'entry_meta'
				);

				++$starred;
			}
		}

		return add_query_arg( 'starred', $starred, $sendback );
	}

	/**
	 * Process the bulk action unstar.
	 *
	 * @since 1.8.6
	 *
	 * @param array  $entries_list Filtered entries list.
	 * @param array  $ids          IDs to process.
	 * @param string $sendback     URL query string.
	 *
	 * @return string
	 */
	protected function process_bulk_action_single_unstar( $entries_list, $ids, $sendback ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$form_id = ! empty( $_GET['form_id'] ) ? absint( $_GET['form_id'] ) : false;

		if ( empty( $form_id ) ) {
			return $sendback;
		}

		$user_id   = get_current_user_id();
		$entries   = wp_list_pluck( $entries_list, 'starred', 'entry_id' );
		$unstarred = 0;

		foreach ( $ids as $id ) {

			if ( ! array_key_exists( $id, $entries ) ) {
				continue;
			}

			if ( $entries[ $id ] === '0' ) {
				continue;
			}

			$success = wpforms()->obj( 'entry' )->update(
				$id,
				[
					'starred' => '0',
				]
			);

			if ( $success ) {
				wpforms()->obj( 'entry_meta' )->add(
					[
						'entry_id' => $id,
						'form_id'  => $form_id,
						'user_id'  => $user_id,
						'type'     => 'log',
						'data'     => wpautop( sprintf( '<em>%s</em>', esc_html__( 'Entry unstarred.', 'wpforms' ) ) ),
					],
					'entry_meta'
				);

				++$unstarred;
			}
		}

		return add_query_arg( 'unstarred', $unstarred, $sendback );
	}

	/**
	 * Process the bulk action print.
	 *
	 * @since 1.8.6
	 *
	 * @param array $ids IDs to process.
	 *
	 * @return void
	 */
	private function process_bulk_action_single_print( $ids ) {

		$print_url = add_query_arg(
			[
				'page'     => 'wpforms-entries',
				'view'     => 'print',
				'entry_id' => implode( ',', $ids ),
			],
			admin_url( 'admin.php' )
		);

		wp_safe_redirect( $print_url );
		exit();
	}

	/**
	 * Process the bulk delete action.
	 *
	 * @since 1.8.5
	 *
	 * @param array  $ids      IDs to process.
	 * @param string $sendback URL query string.
	 *
	 * @return string
	 */
	private function process_bulk_action_single_trash( $ids, $sendback ) {

		$trashed = 0;
		$form_id = ! empty( $_GET['form_id'] ) ? absint( $_GET['form_id'] ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$user_id = get_current_user_id();

		foreach ( $ids as $id ) {
			// Get the entry first.
			$entry = wpforms()->obj( 'entry' )->get( $id );

			if ( ! $entry ) {
				continue;
			}

			$status = $entry->status;

			/**
			 * TODO :: After the support for PHP 7 ends,
			 * we can update the following code to use named arguments and skip the optional params.
			 */
			$success = wpforms()->obj( 'entry' )->update(
				$id,
				[ 'status' => Page::TRASH_ENTRY_STATUS ],
				'',
				'',
				[ 'cap' => 'delete_entry_single' ] // Force the cap to trash the entry, since we cant provide edit cap here.
			);

			// If it didn't work, continue.
			if ( ! $success ) {
				continue;
			}

			if ( $status !== '' ) {
				wpforms()->obj( 'entry_meta' )->add(
					[
						'entry_id' => $id,
						'form_id'  => $form_id,
						'user_id'  => $user_id,
						'type'     => 'status_prev',
						'data'     => '',
						'status'   => $status,
					],
					'entry_meta'
				);
			}

			++$trashed;
		}

		// If trashed entries are more than 1, then clear widget cache.
		if ( $trashed >= 1 ) {
			DashboardWidget::clear_widget_cache();
		}

		return add_query_arg(
			[
				'trashed' => $trashed,
				'view'    => 'list', // force the list view to make sure we go to the right page.
				'form_id' => $form_id,
			],
			$sendback
		);
	}

	/**
	 * Process the bulk action restores.
	 *
	 * @since 1.8.5
	 *
	 * @param array  $ids      IDs to process.
	 * @param string $sendback URL query string.
	 *
	 * @return string
	 */
	private function process_bulk_action_single_restore( $ids, $sendback ) {

		$restored = 0;

		foreach ( $ids as $id ) {
			// Reset or set initial status.
			$status = '';

			// Get the entry first.
			$entry = wpforms()->obj( 'entry' )->get( $id );

			if ( ! $entry ) {
				continue;
			}

			$meta = wpforms()->obj( 'entry_meta' )->get_meta(
				[
					'entry_id' => $id,
					'type'     => 'status_prev',
				]
			);

			// check meta for status log to restore the status.
			if ( $meta ) {
				$status = $meta[0]->status;

				// After taking status from meta, delete the meta.
				wpforms()->obj( 'entry_meta' )->delete_by( 'id', $meta[0]->id );
			}

			/**
			 * TODO :: After the support for PHP 7 ends,
			 * we can update the following code to use named arguments and skip the optional params.
			 */
			$success = wpforms()->obj( 'entry' )->update(
				$id,
				[ 'status' => $status ],
				'',
				'',
				[ 'cap' => 'delete_entry_single' ] // Force the cap to trash the entry, since we cant provide edit cap here.
			);

			// If it didn't work, continue.
			if ( ! $success ) {
				continue;
			}

			++$restored;
		}

		$trash_count = wpforms()->obj( 'entry' )->get_entries(
			[
				'form_id' => $this->form_id,
				'status'  => Page::TRASH_ENTRY_STATUS,
			],
			true
		);

		// If trash is emptied.
		if ( $trash_count < 1 ) {
			$sendback = remove_query_arg( 'status', $sendback );
		}

		// If restored entries are more than 1, then clear widget cache.
		if ( $restored >= 1 ) {
			DashboardWidget::clear_widget_cache();
		}

		return add_query_arg( 'restored', $restored, $sendback );
	}

	/**
	 * Process the bulk delete action.
	 *
	 * @since 1.8.6
	 *
	 * @param array  $ids      IDs to process.
	 * @param string $sendback URL query string.
	 *
	 * @return string
	 */
	protected function process_bulk_action_single_delete( $ids, $sendback ) {

		$deleted = 0;
		$form_id = ! empty( $_GET['form_id'] ) ? absint( $_GET['form_id'] ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

		foreach ( $ids as $id ) {
			if ( wpforms()->obj( 'entry' )->delete( $id ) ) {
				++$deleted;
			}
		}

		return add_query_arg(
			[
				'deleted' => $deleted,
				'view'    => 'list', // force the list view to make sure we go to the right page.
				'form_id' => $form_id,
			],
			$sendback
		);
	}

	/**
	 * Display a bulk action result message.
	 *
	 * @since 1.8.6
	 */
	protected function display_bulk_action_message() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity

		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		$bulk_counts = [
			'read'      => isset( $_REQUEST['read'] ) ? absint( $_REQUEST['read'] ) : 0,
			'unread'    => isset( $_REQUEST['unread'] ) ? absint( $_REQUEST['unread'] ) : 0,
			'spam'      => isset( $_REQUEST['spam'] ) ? absint( $_REQUEST['spam'] ) : 0,
			'unspam'    => isset( $_REQUEST['unspam'] ) ? absint( $_REQUEST['unspam'] ) : 0,
			'starred'   => isset( $_REQUEST['starred'] ) ? absint( $_REQUEST['starred'] ) : 0,
			'unstarred' => isset( $_REQUEST['unstarred'] ) ? absint( $_REQUEST['unstarred'] ) : 0,
			'deleted'   => isset( $_REQUEST['deleted'] ) ? (int) $_REQUEST['deleted'] : 0,
			'trashed'   => isset( $_REQUEST['trashed'] ) ? (int) $_REQUEST['trashed'] : 0,
			'restored'  => isset( $_REQUEST['restored'] ) ? (int) $_REQUEST['restored'] : 0,
		];
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		$bulk_messages = [
			/* translators: %d - number of processed entries. */
			'read'      => _n( '%d entry was successfully marked as read.', '%d entries were successfully marked as read.', $bulk_counts['read'], 'wpforms' ),
			/* translators: %d - number of processed entries. */
			'unread'    => _n( '%d entry was successfully marked as unread.', '%d entries were successfully marked as unread.', $bulk_counts['unread'], 'wpforms' ),
			/* translators: %d - number of processed entries. */
			'spam'      => _n( '%d entry was successfully marked as spam.', '%d entries were successfully marked as spam.', $bulk_counts['spam'], 'wpforms' ),
			/* translators: %d - number of processed entries. */
			'unspam'    => _n( '%d entry was successfully marked as not spam.', '%d entries were successfully marked as not spam.', $bulk_counts['unspam'], 'wpforms' ),
			/* translators: %d - number of processed entries. */
			'starred'   => _n( '%d entry was successfully starred.', '%d entries were successfully starred.', $bulk_counts['starred'], 'wpforms' ),
			/* translators: %d - number of processed entries. */
			'unstarred' => _n( '%d entry was successfully unstarred.', '%d entries were successfully unstarred.', $bulk_counts['unstarred'], 'wpforms' ),
			/* translators: %d - number of processed entries. */
			'deleted'   => _n( '%d entry was successfully deleted.', '%d entries were successfully deleted.', $bulk_counts['deleted'], 'wpforms' ),
			/* translators: %d - number of processed entries. */
			'trashed'   => _n( '%d entry was successfully trashed.', '%d entries were successfully trashed.', $bulk_counts['trashed'], 'wpforms' ),
			/* translators: %d - number of processed entries. */
			'restored'  => _n( '%d entry was successfully restored.', '%d entries were successfully restored.', $bulk_counts['restored'], 'wpforms' ),
		];

		if ( $bulk_counts['deleted'] === -1 ) {
			$bulk_messages['deleted'] = esc_html__( 'All entries for the currently selected form were successfully deleted.', 'wpforms' );
		}

		if ( $bulk_counts['trashed'] === -1 ) {
			$bulk_messages['trashed'] = esc_html__( 'All entries for the currently selected form were successfully trashed.', 'wpforms' );
		}

		// Leave only non-zero counts, so only those that were processed are left.
		$bulk_counts = array_filter( $bulk_counts );

		// If we have bulk messages to display.
		$messages = [];

		foreach ( $bulk_counts as $type => $count ) {
			if ( isset( $bulk_messages[ $type ] ) ) {
				$messages[] = sprintf( $bulk_messages[ $type ], $count );
			}
		}

		if ( $messages ) {
			Notice::success( implode( '<br>', array_map( 'esc_html', $messages ) ) );
		}
	}

	/**
	 * Message to be displayed when there are no entries.
	 *
	 * @since 1.8.6
	 */
	public function no_items() {

		printf(
			'<div class="wpforms-no-entries-found">%1$s</div>',
			esc_html__( 'No entries found.', 'wpforms' )
		);
	}

	/**
	 * Entries list form search.
	 *
	 * @since 1.8.6
	 *
	 * @param string $text     The 'submit' button label.
	 * @param string $input_id ID attribute value for the search input field.
	 */
	public function search_box( $text, $input_id ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity

		$input_id .= '-search-input';

		/**
		 * Fires before output the search box on the Entries list page.
		 *
		 * @since 1.4.4
		 *
		 * @param array $forms_data Form data.
		 */
		do_action( 'wpforms_entries_list_form_filters_before', $this->form_data ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName

		$filter_fields = [];

		if ( ! empty( $this->form_data['fields'] ) ) {
			foreach ( $this->form_data['fields'] as $id => $field ) {
				if ( in_array( $field['type'], self::get_columns_form_disallowed_fields(), true ) ) {
					continue;
				}

				$filter_fields[ $id ] = ! empty( $field['label'] ) ? wp_strip_all_tags( $field['label'] ) : esc_html__( 'Field', 'wpforms' );
			}
		}

		/**
		 * Filters fields displayed in the search box.
		 *
		 * @since 1.5.5.1
		 *
		 * @param array     $fields Search box fields.
		 * @param ListTable $fields Instance of the ListTable class.
		 *
		 * @return array
		 */
		$filter_fields = (array) apply_filters( 'wpforms_entries_list_form_filters_search_fields', $filter_fields, $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName

		$cur_field = 'any';

		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		if ( isset( $_GET['search']['field'] ) ) {
			if ( is_numeric( $_GET['search']['field'] ) ) {
				$cur_field = (int) $_GET['search']['field'];
			} else {
				$cur_field = sanitize_key( $_GET['search']['field'] );
			}
		}

		$advanced_options = Helpers::get_search_fields_advanced_options();
		$cur_comparison   = ! empty( $_GET['search']['comparison'] ) ? sanitize_key( $_GET['search']['comparison'] ) : 'contains';
		$cur_term         = '';

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
		if ( isset( $_GET['search']['term'] ) && ! wpforms_is_empty_string( $_GET['search']['term'] ) ) {
			$cur_term = sanitize_text_field( wp_unslash( $_GET['search']['term'] ) );
			$cur_term = empty( $cur_term ) ? htmlspecialchars( wp_unslash( $_GET['search']['term'] ) ) : $cur_term; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		}

		// phpcs:enable WordPress.Security.NonceVerification.Recommended
		$this->search_box_output( $text, $input_id, $filter_fields, $advanced_options, $cur_field, $cur_comparison, $cur_term );

		/**
		 * Allows developers output some HTML after the filter forms on the entries' list page.
		 *
		 * @since 1.4.4
		 *
		 * @param array $form_data Form data.
		 */
		do_action( 'wpforms_entries_list_form_filters_after', $this->form_data ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
	}

	/**
	 * Entries list form search.
	 *
	 * @since 1.8.6
	 *
	 * @param string $text                    The 'submit' button label.
	 * @param string $input_id                ID attribute value for the search input field.
	 * @param array  $filter_fields           Filter fields options.
	 * @param array  $search_advanced_options Advanced options.
	 * @param mixed  $cur_field               Current (selected) field or advanced option.
	 * @param string $cur_comparison          Current comparison.
	 * @param string $cur_term                Current search term.
	 *
	 * @noinspection HtmlUnknownAttribute*/
	private function search_box_output( $text, $input_id, $filter_fields, $search_advanced_options, $cur_field, $cur_comparison, $cur_term ) {

		?>
		<p class="search-box wpforms-form-search-box">

			<select name="search[field]" class="wpforms-form-search-box-field">
				<optgroup label="<?php esc_attr_e( 'Form fields', 'wpforms' ); ?>">
					<option value="any" <?php selected( 'any', $cur_field ); ?>><?php esc_html_e( 'Any form field', 'wpforms' ); ?></option>
					<?php
					if ( ! empty( $filter_fields ) ) {
						foreach ( $filter_fields as $id => $name ) {
							printf(
								'<option value="%1$s" %2$s>%3$s</option>',
								esc_attr( $id ),
								selected( $id, $cur_field, false ),
								esc_html( $name )
							);
						}
					}
					?>
				</optgroup>
				<?php if ( ! empty( $search_advanced_options ) ) : ?>
					<optgroup label="<?php esc_attr_e( 'Advanced Options', 'wpforms' ); ?>">
						<?php
						foreach ( $search_advanced_options as $val => $name ) {
							printf(
								'<option value="%1$s" %2$s>%3$s</option>',
								esc_attr( $val ),
								selected( $val, $cur_field, false ),
								esc_html( $name )
							);
						}
						?>
					</optgroup>
				<?php endif; // Advanced options group. ?>
			</select>

			<select name="search[comparison]" class="wpforms-form-search-box-comparison">
				<option value="contains" <?php selected( 'contains', $cur_comparison ); ?>>
					<?php esc_html_e( 'contains', 'wpforms' ); ?>
				</option>
				<option value="contains_not" <?php selected( 'contains_not', $cur_comparison ); ?>>
					<?php esc_html_e( 'does not contain', 'wpforms' ); ?>
				</option>
				<option value="is" <?php selected( 'is', $cur_comparison ); ?>>
					<?php esc_html_e( 'is', 'wpforms' ); ?>
				</option>
				<option value="is_not" <?php selected( 'is_not', $cur_comparison ); ?>>
					<?php esc_html_e( 'is not', 'wpforms' ); ?>
				</option>
			</select>

			<label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>">
				<?php echo esc_html( $text ); ?>:
			</label>
			<input type="search" name="search[term]" class="wpforms-form-search-box-term" value="<?php echo esc_attr( wp_unslash( $cur_term ) ); ?>" id="<?php echo esc_attr( $input_id ); ?>">

			<button type="submit" class="button"><?php echo esc_html( $text ); ?></button>
		</p>
		<?php
	}

	/**
	 * Fetch and set up the final data for the table.
	 *
	 * @since 1.8.6
	 */
	public function prepare_items() {

		// Retrieve the count.
		$this->get_counts();

		// Set up the columns.
		$columns = $this->get_columns();

		// Hidden columns (none).
		$hidden = [];

		// Define which columns can be sorted.
		$sortable = $this->get_sortable_columns();

		// Get a primary column. It will be a 3rd column.
		$primary = key( array_slice( $columns, 2, 1 ) );

		// Set column headers.
		$this->_column_headers = [ $columns, $hidden, $sortable, $primary ];

		// Get entries.
		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		$total_items = $this->counts['total'];
		$page        = $this->get_pagenum();
		$order       = isset( $_GET['order'] ) ? sanitize_key( $_GET['order'] ) : 'DESC';
		$orderby     = isset( $_GET['orderby'] ) ? sanitize_key( $_GET['orderby'] ) : 'entry_id';
		$per_page    = $this->get_items_per_page( 'wpforms_entries_per_page', $this->per_page );
		$data_args   = [
			'form_id' => $this->form_id,
			'number'  => $per_page,
			'offset'  => $per_page * ( $page - 1 ),
			'order'   => $order,
			'orderby' => $orderby,
		];

		if ( ! empty( $_GET['type'] ) && $_GET['type'] === 'starred' ) {
			$data_args['starred'] = '1';
			$total_items          = $this->counts['starred'];
		}
		if ( ! empty( $_GET['type'] ) && $_GET['type'] === 'unread' ) {
			$data_args['viewed'] = '0';
			$total_items         = $this->counts['unread'];
		}

		if ( ! empty( $_GET['type'] ) && $_GET['type'] === 'payment' ) {
			$data_args['type'] = 'payment';
			$total_items       = $this->counts['payment'];
		}

		if ( ! empty( $_GET['status'] ) ) {
			$data_args['status'] = sanitize_text_field( $_GET['status'] ); // phpcs:ignore WordPress.Security
			$total_items         = ! empty( $this->counts[ $data_args['status'] ] ) ? $this->counts[ $data_args['status'] ] : 0;
		}
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		if ( array_key_exists( 'notes_count', $columns ) ) {
			$data_args['notes_count'] = true;
		}

		// Reset the orderby if the parameter does not exist in columns.
		if ( ! array_key_exists( $orderby, $columns ) ) {
			$data_args['orderby'] = 'entry_id';
		}

		/**
		 * Filters get entries arguments array.
		 *
		 * @since 1.4.0
		 *
		 * @param array $args Arguments.
		 *
		 * @return array
		 */
		$data_args = apply_filters( 'wpforms_entry_table_args', $data_args ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
		$data      = wpforms()->obj( 'entry' )->get_entries( $data_args );

		// Giddy up.
		$this->items = $data;

		// Finalize pagination.
		$this->set_pagination_args(
			[
				'total_items' => $total_items,
				'total_pages' => (int) ceil( $total_items / $per_page ),
				'per_page'    => $per_page,
			]
		);
	}

	/**
	 * Extending the `display_rows()` method to add hooks.
	 *
	 * @since 1.8.6
	 */
	public function display_rows() {

		/**
		 * Fires before displaying the table rows.
		 *
		 * @since 1.5.6.2
		 *
		 * @param ListTable $list_table_obj ListTable instance.
		 */
		do_action( 'wpforms_admin_entries_before_rows', $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName

		parent::display_rows();

		/**
		 * Fires after displaying the table rows.
		 *
		 * @since 1.5.6.2
		 *
		 * @param ListTable $list_table_obj ListTable instance.
		 */
		do_action( 'wpforms_admin_entries_after_rows', $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
	}

	/**
	 * Truncate long text value to X lines and Y characters.
	 *
	 * @since 1.8.6
	 *
	 * @param string $value      The value to truncate, if needed.
	 * @param string $field_type Field type.
	 *
	 * @return string
	 */
	private function truncate_long_value( $value, $field_type ) {

		// Limit multiline text to 4 lines, 5 for Address field, and overall length to 75 characters.
		$lines_limit = $field_type === 'address' ? 5 : 4;
		$chars_limit = 75;

		// Decode HTML entities to avoid truncating on &euro; and similar.
		$value = html_entity_decode( $value, ENT_COMPAT, 'UTF-8' );

		$lines = preg_split( '/\r\n|\r|\n/', $value );
		$value = array_slice( $lines, 0, $lines_limit );
		$value = implode( PHP_EOL, $value );

		// Encode HTML entities back to prevent XSS.
		$value = htmlentities( $value, ENT_COMPAT, 'UTF-8' );

		if ( strlen( $value ) > $chars_limit ) {
			return mb_substr( $value, 0, $chars_limit ) . '&hellip;';
		}

		// Ellipsis should be on a new line if the value is multiline, and extra lines were truncated.
		if ( count( $lines ) > $lines_limit ) {
			return $value . PHP_EOL . '&hellip;';
		}

		return $value;
	}

	/**
	 * Returns payment status label, slug and payment object by given entry ID.
	 * The returned data includes:
	 * - label: payment status label.
	 * - slug: payment status slug.
	 * - payment: payment object.
	 *
	 * @since 1.8.6
	 *
	 * @param int $entry_id Entry ID.
	 *
	 * @return array
	 */
	private function get_payment_status_by_entry_id( $entry_id ) {

		// Get payment data.
		$payment = wpforms()->obj( 'payment' )->get_by( 'entry_id', $entry_id );

		// If payment data is not found, return N/A.
		if ( ! $payment ) {
			return [
				__( 'N/A', 'wpforms' ),
				'n-a',
				null,
			];
		}

		$allowed_statuses = ValueValidator::get_allowed_statuses();
		$payment_status   = ! empty( $payment->subscription_id ) ? $payment->subscription_status : $payment->status;
		$status_slug      = ! empty( $payment_status ) ? $payment_status : 'n-a';
		$status_label     = $allowed_statuses[ $payment_status ] ?? __( 'N/A', 'wpforms' );

		return [ $status_label, $status_slug, $payment ];
	}

	/**
	 * Check the status of entries to check if they are trashable.
	 *
	 * @since 1.8.5
	 *
	 * @param int $entry_id Entry id to check.
	 *
	 * @return boolean
	 */
	private function should_delete( $entry_id ) {

		$entry = wpforms()->obj( 'entry' )->get( $entry_id );

		if ( ! $entry ) {
			return false;
		}

		return $entry->status === Page::TRASH_ENTRY_STATUS;
	}

	/**
	 * Check if the current page is a trash list.
	 *
	 * @since 1.8.5
	 *
	 * @return bool
	 */
	public function is_trash_list(): bool {

		$status = isset( $_GET['status'] ) ? sanitize_key( $_GET['status'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

		return $status === Page::TRASH_ENTRY_STATUS;
	}

	/**
	 * Displays the table.
	 *
	 * @since 1.8.6
	 */
	public function display() {

		$singular = $this->_args['singular'];

		$this->display_tablenav( 'top' );

		$this->screen->render_screen_reader_content( 'heading_list' );
		?>
		<div class="wpforms-table-container">
			<table class="wp-list-table <?php echo esc_attr( implode( ' ', $this->get_table_classes() ) ); ?>">
				<?php $this->print_table_description(); ?>
				<thead>
				<tr>
					<?php $this->print_column_headers(); ?>
				</tr>
				</thead>

				<tbody id="the-list"
					<?php
					if ( $singular ) {
						echo ' data-wp-lists="list:' . esc_attr( $singular ) . '"';
					}
					?>
				>
				<?php $this->display_rows_or_placeholder(); ?>
				</tbody>

				<tfoot>
				<tr>
					<?php $this->print_column_headers( false ); ?>
				</tr>
				</tfoot>
			</table>
		</div>
		<?php
		$this->display_tablenav( 'bottom' );
	}

	/**
	 * Print column headers but without the `actions` column if this is bottom table header.
	 *
	 * @since 1.8.6
	 *
	 * @param bool $with_id Whether to print the `<tr>` element with an `id` attribute.
	 */
	public function print_column_headers( $with_id = true ) {

		if ( $with_id ) {
			parent::print_column_headers();
		} else {
			$column_headers = $this->_column_headers[0];

			if ( isset( $column_headers['actions'] ) ) {
				$column_headers['actions'] = esc_html__( 'Actions', 'wpforms' );

				$this->_column_headers[0] = $column_headers;
			}

			parent::print_column_headers( false );
		}
	}
}

Filemanager

Name Type Size Permission Actions
Ajax Folder 0750
Export Folder 0750
Overview Folder 0750
Table Folder 0750
Edit.php File 50.88 KB 0640
FilterSearch.php File 4.93 KB 0640
Handler.php File 1.54 KB 0640
Helpers.php File 1.23 KB 0640
ListTable.php File 57.47 KB 0640
Page.php File 37.58 KB 0640
PageOptions.php File 1.86 KB 0640
PrintPreview.php File 14.27 KB 0640
Filemanager