<?php

namespace WPForms\Pro\Admin\Entries;

use WPForms\Admin\Notice;
use WPForms\Pro\Admin\Entries\Table\DataObjects\Column;
use WPForms\Pro\Admin\Entries\Table\Facades\Columns;
use WPForms\Pro\Admin\DashboardWidget;
use WPForms\Pro\AntiSpam\SpamEntry;
use WPForms\Pro\Forms\Fields\FileUpload\Field as FileUploadField;
use WPForms_Form_Handler;

/**
 * Display list of all entries for a single form.
 *
 * @since 1.8.6
 */
class Page {

	use FilterSearch;

	/**
	 * Entry trash status.
	 *
	 * @since 1.8.5
	 */
	const TRASH_ENTRY_STATUS = 'trash';

	/**
	 * Store admin alerts.
	 *
	 * @since 1.8.6
	 *
	 * @var array
	 */
	public $alerts = [];

	/**
	 * Abort. Bail on proceeding to process the page.
	 *
	 * @since 1.8.6
	 *
	 * @var bool
	 */
	public $abort = false;

	/**
	 * The human-readable error message.
	 *
	 * @since 1.8.6
	 *
	 * @var string
	 */
	private $abort_message;

	/**
	 * Form ID.
	 *
	 * @since 1.8.6
	 *
	 * @var int
	 */
	public $form_id;

	/**
	 * Form object.
	 *
	 * @since 1.8.6
	 *
	 * @var WPForms_Form_Handler
	 */
	public $form;

	/**
	 * Forms object.
	 *
	 * @since 1.8.6
	 *
	 * @var WPForms_Form_Handler[]
	 */
	public $forms;

	/**
	 * Entries object.
	 *
	 * @since 1.8.6
	 *
	 * @var ListTable
	 */
	public $entries;

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

		$this->hooks();
	}

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

		// Heartbeat doesn't pass $_GET parameters checked by $this->init() condition.
		add_filter( 'heartbeat_received', [ $this, 'heartbeat_new_entries_check' ], 10, 3 );

		// AJAX-callbacks.
		add_action( 'wp_ajax_wpforms_entry_list_process_delete_all', [ $this, 'process_delete' ] );
		add_action( 'wp_ajax_wpforms_entry_list_process_trash_all', [ $this, 'process_trash' ] );
	}

	/**
	 * Determine if the user is viewing the entries list page, if so, party on.
	 *
	 * @since 1.8.6
	 */
	public function init() { // phpcs:disable WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks

		// Only load if we are actually on the overview page.
		if ( ! wpforms_is_admin_page( 'entries', 'list' ) ) {
			return;
		}

		$form_id = $this->get_filtered_form_id();

		if ( empty( $form_id ) ) {
			wp_safe_redirect( admin_url( 'admin.php?page=wpforms-entries' ) );
			exit;
		}

		if ( ! wpforms_current_user_can( 'view_entries_form_single', $form_id ) ) {
			wp_die( esc_html__( 'You do not have permission to view this form\'s entries.', 'wpforms' ), 403 );
		}

		$form = wpforms()->obj( 'form' )->get( $form_id );

		if ( empty( $form ) || $form->post_status === self::TRASH_ENTRY_STATUS ) {
			$this->abort_message = esc_html__( 'It looks like the form you are trying to access is no longer available.', 'wpforms' );
			$this->abort         = true;
		}

		$this->remove_get_parameters();
		set_screen_options();

		// Load the classes that build the entries' table.
		add_action( 'current_screen', [ $this, 'load_entries_list_table' ], 5 );

		// Processing and setup.
		add_action( 'wpforms_entries_init', [ $this, 'process_filter_dates' ], 7, 1 );
		add_action( 'wpforms_entries_init', [ $this, 'process_filter_search' ], 7, 1 );
		add_action( 'wpforms_entries_init', [ $this, 'process_read' ], 8, 1 );
		add_action( 'wpforms_entries_init', [ $this, 'setup' ], 10, 1 );
		add_action( 'wpforms_entries_init', [ $this, 'register_alerts' ], 20, 1 );

		/**
		 * Fires on init entries list page.
		 *
		 * @since 1.0.0
		 *
		 * @param string $context Current context.
		 */
		do_action( 'wpforms_entries_init', 'list' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName

		// Output.
		add_action( 'wpforms_admin_page', [ $this, 'list_all' ] );
		add_action( 'wpforms_admin_page', [ $this, 'display_abort_message' ] );
		add_action( 'wpforms_admin_page', [ $this, 'field_column_setting' ] );
		add_action( 'wpforms_entry_list_title', [ $this, 'list_form_actions' ], 10, 1 );

		// Enqueues.
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueues' ] );
		add_filter( 'wpforms_admin_strings', [ $this, 'js_strings' ] );

		// Apply filter to search by date on Entries page.
		add_filter( 'wpforms_entry_handler_get_entries_args', [ $this, 'get_filtered_entry_table_args' ] );
	} // phpcs:enable WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks

	/**
	 * Remove unnecessary $_GET parameters for shorter URL.
	 *
	 * @since 1.8.6
	 */
	private function remove_get_parameters() {

		$remove_args = [
			'_wp_http_referer',
			'action',
			'action2',
			'_wpnonce',
			'read',
			'unread',
			'spam',
			'unspam',
			'unstarred',
			'starred',
			'deleted',
			'trashed',
			'restored',
		];

		// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
		if ( isset( $_GET['paged'] ) && (int) $_GET['paged'] < 2 ) {
			$remove_args[] = 'paged';
		}

		if ( ! isset( $_GET['search']['term'] ) || wpforms_is_empty_string( $_GET['search']['term'] ) ) {
			$comparison = isset( $_GET['search']['comparison'] ) ? sanitize_text_field( wp_unslash( $_GET['search']['comparison'] ) ) : '';

			if ( ! $this->is_comparison_without_term( $comparison ) ) {
				$remove_args[] = 'search';
			}
		}

		if ( empty( $this->get_filtered_dates() ) ) {
			$remove_args[] = 'date';
		}

		$_SERVER['REQUEST_URI'] = remove_query_arg( $remove_args, $_SERVER['REQUEST_URI'] );
		// phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
	}

	/**
	 * Load the PHP classes and initialize an entries table.
	 *
	 * @since 1.8.6
	 */
	public function load_entries_list_table() {

		// Create an `ListTable` instance and process bulk actions.
		$this->entries = new ListTable();

		$this->entries->process_bulk_actions();
	}

	/**
	 * Enqueue assets for the entries pages.
	 *
	 * @since 1.8.6
	 */
	public function enqueues() {

		// JavaScript.
		wp_enqueue_script(
			'wpforms-htmx',
			WPFORMS_PLUGIN_URL . 'assets/lib/htmx.min.js',
			[],
			WPFORMS_VERSION,
			true
		);

		wp_enqueue_script(
			'wpforms-flatpickr',
			WPFORMS_PLUGIN_URL . 'assets/lib/flatpickr/flatpickr.min.js',
			[ 'jquery' ],
			'4.6.9',
			false
		);

		// CSS.
		wp_enqueue_style(
			'wpforms-flatpickr',
			WPFORMS_PLUGIN_URL . 'assets/lib/flatpickr/flatpickr.min.css',
			[],
			'4.6.9'
		);

		$min = wpforms_get_min_suffix();

		wp_enqueue_style(
			'wpforms-admin-list-table-ext',
			WPFORMS_PLUGIN_URL . "assets/css/admin-list-table-ext{$min}.css",
			[ 'wpforms-multiselect-checkboxes' ],
			WPFORMS_VERSION
		);

		wp_enqueue_script(
			'wpforms-admin-list-table-ext',
			WPFORMS_PLUGIN_URL . "assets/js/admin/share/list-table-ext{$min}.js",
			[ 'jquery', 'jquery-ui-sortable', 'underscore', 'wpforms-multiselect-checkboxes', 'wpforms-htmx' ],
			WPFORMS_VERSION,
			true
		);

		wp_enqueue_script(
			'wpforms-admin-entries',
			WPFORMS_PLUGIN_URL . "assets/pro/js/admin/entries/entries{$min}.js",
			[ 'jquery', 'wpforms-flatpickr' ],
			WPFORMS_VERSION,
			true
		);

		/**
		 * Fires on enqueue entries list page assets.
		 *
		 * @since 1.0.0
		 *
		 * @param string $context Current context.
		 */
		do_action( 'wpforms_entries_enqueue', 'list' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
	}

	/**
	 * Watch for and run complete marking all entries as read.
	 *
	 * @since 1.8.6
	 */
	public function process_read() {

		$form_id = $this->get_filtered_form_id();

		// Check for run switch.
		if ( empty( $_GET['action'] ) || ! $form_id || $_GET['action'] !== 'markread' || empty( $_GET['_wpnonce'] ) ) {
			return;
		}

		// Security check.
		if ( ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'wpforms_entry_list_markread' ) ) {
			return;
		}

		wpforms()->obj( 'entry' )->mark_all_read( $form_id );

		$this->alerts[] = [
			'type'    => 'success',
			'message' => esc_html__( 'All entries marked as read.', 'wpforms' ),
			'dismiss' => true,
		];
	}

	/**
	 * Entry deletion and trigger if needed.
	 *
	 * @since 1.8.6
	 */
	public function process_delete() {

		// Security check.
		if ( ! check_ajax_referer( 'wpforms-admin', 'nonce', false ) ) {
			wp_send_json_error( esc_html__( 'Your session expired. Please reload the builder.', 'wpforms' ) );
		}

		$form_id = $this->get_filtered_form_id();

		// Require data check.
		if ( ! $form_id ) {
			wp_send_json_error( esc_html__( 'Something went wrong while performing this action.', 'wpforms' ) );
		}

		// Permission check.
		if ( ! wpforms_current_user_can( 'delete_entries_form_single', $form_id ) ) {
			wp_send_json_error( esc_html__( 'You do not have permission to perform this action.', 'wpforms' ) );
		}

		// Get the screen first.
		$screen = isset( $_REQUEST['page'] ) ? sanitize_key( $_REQUEST['page'] ) : '';

		// The delete all functionality should only be available on the trash and spam screens.
		// All other places the delete all button will be replaced by trash all button.
		if ( ! in_array( $screen, [ self::TRASH_ENTRY_STATUS, SpamEntry::ENTRY_STATUS ], true ) ) {
			wp_send_json_error( esc_html__( 'Something went wrong while performing this action.', 'wpforms' ) );
		}

		// Default args to find entries.
		$args = [
			'form_id' => $form_id,
			'select'  => 'entry_ids',
			'status'  => $screen,
			'number'  => '-1',
		];

		// Get entries.
		$entries = wpforms()->obj( 'entry' )->get_entries( $args );

		if ( ! $entries ) {
			wp_send_json_error( esc_html__( 'Something went wrong while performing this action.', 'wpforms' ) );
		}

		$entry_ids = wp_list_pluck( $entries, 'entry_id' );
		$deleted   = 0;

		// Delete media if any.
		// It must be done before removing the entry itself.
		array_map( [ FileUploadField::class, 'delete_uploaded_files_from_entry' ], $entry_ids );

		/**
		 * Allow performing additional actions before deleting entries.
		 *
		 * @since 1.8.9
		 *
		 * @param array $entry_ids Entry IDs.
		 * @param int   $form_id   Form ID.
		 */
		do_action( 'wpforms_pro_admin_entries_page_empty_trash_before', $entry_ids, $form_id );

		// Delete meta only if the related entry has been removed successfully.
		if ( wpforms()->obj( 'entry' )->delete_where_in( 'entry_id', $entry_ids ) ) {
			wpforms()->obj( 'entry_meta' )->delete_where_in( 'entry_id', $entry_ids );
			wpforms()->obj( 'entry_fields' )->delete_where_in( 'entry_id', $entry_ids );

			$deleted = count( $entry_ids );
		}

		$redirect_url = ! empty( $_GET['url'] ) ? add_query_arg( 'deleted', $deleted, esc_url_raw( wp_unslash( $_GET['url'] ) ) ) : '';

		DashboardWidget::clear_widget_cache();

		wp_send_json_success( $redirect_url );
	}

	/**
	 * Entry trash and trigger if needed.
	 *
	 * @since 1.8.5
	 */
	public function process_trash() {

		// Security check.
		if ( ! check_ajax_referer( 'wpforms-admin', 'nonce', false ) ) {
			wp_send_json_error( esc_html__( 'Your session expired. Please reload the builder.', 'wpforms' ) );
		}

		$form_id = $this->get_filtered_form_id();

		// Require data check.
		if ( ! $form_id ) {
			wp_send_json_error( esc_html__( 'Something went wrong while performing this action.', 'wpforms' ) );
		}

		// Permission check.
		if ( ! wpforms_current_user_can( 'delete_entries_form_single', $form_id ) ) {
			wp_send_json_error( esc_html__( 'You do not have permission to perform this action.', 'wpforms' ) );
		}

		if ( $this->is_list_filtered() ) {
			$this->process_filter_dates();
			$this->process_filter_search();
		}

		$entries_id = $this->process_trash_data( $form_id );

		if ( empty( $entries_id ) ) {
			wp_send_json_error( esc_html__( 'Something went wrong while performing this action.', 'wpforms' ) );
		}

		$trashed = $this->process_trash_with_ids( $entries_id, $form_id );

		$redirect_url = ! empty( $_GET['url'] ) ? add_query_arg( 'trashed', $trashed, esc_url_raw( wp_unslash( $_GET['url'] ) ) ) : '';

		DashboardWidget::clear_widget_cache();

		wp_send_json_success( $redirect_url );
	}

	/**
	 * Process entries for trashing.
	 *
	 * @since 1.8.5
	 *
	 * @param int $form_id Form ID.
	 *
	 * @return array $entries_id Entry IDs.
	 */
	private function process_trash_data( int $form_id ) {

		// Check if entries filtered.
		// This also checks if there's entry ids provided. See: FilterSearch Trait.
		if ( ! empty( $this->filter['entry_id'] ) ) {
			return $this->filter['entry_id'];
		}

		// Get the screen first.
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$screen = isset( $_REQUEST['page'] ) ? sanitize_key( $_REQUEST['page'] ) : '';

		// We can only trash all on all other screens expect trash screen.
		if ( $screen === self::TRASH_ENTRY_STATUS ) {
			return [];
		}

		// Default args to find entries.
		$args = [
			'form_id' => $form_id,
			'select'  => 'entry_ids',
			'number'  => '-1',
		];

		// Apply the date filter if set.
		if ( ! empty( $this->filter['date'] ) ) {
			$args['date'] = $this->filter['date'];
		}

		// Get entries.
		$all_entries  = wpforms()->obj( 'entry' )->get_entries( $args );
		$spam_entries = wpforms()->obj( 'entry' )->get_entries(
			[
				'form_id' => $form_id,
				'status'  => SpamEntry::ENTRY_STATUS,
				'select'  => 'entry_ids',
			]
		);

		return wp_list_pluck( array_merge( $all_entries, $spam_entries ), 'entry_id' );
	}

	/**
	 * Process trash when entry ids are given.
	 *
	 * @since 1.8.5
	 *
	 * @param array $entry_ids Entry IDs.
	 * @param int   $form_id   Form ID.
	 *
	 * @return int  $trashed Number of trashed entries.
	 */
	private function process_trash_with_ids( $entry_ids, $form_id ): int {

		$trashed = 0;
		$user_id = get_current_user_id();

		foreach ( $entry_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' => self::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;
		}

		return $trashed;
	}

	/**
	 * Return validated information about search or FALSE.
	 *
	 * @since 1.8.6
	 *
	 * @return bool|array
	 */
	protected function get_filter_search_parts() {

		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		if ( empty( $_GET['search'] ) ) {
			return false;
		}

		$expected   = [ 'field', 'comparison', 'term' ];
		$comparison = isset( $_GET['search']['comparison'] ) ? sanitize_text_field( wp_unslash( $_GET['search']['comparison'] ) ) : '';

		foreach ( $expected as $field ) {

			if ( $field === 'term' && $this->is_comparison_without_term( $comparison ) ) {
				continue;
			}

			if ( ! isset( $_GET['search'][ $field ] ) || $_GET['search'][ $field ] === '' ) {
				return false;
			}
		}

		return array_map( 'sanitize_text_field', wp_unslash( $_GET['search'] ) );
		// phpcs:enable WordPress.Security.NonceVerification.Recommended
	}

	/**
	 * Return HTML with information about the search filter.
	 *
	 * @since 1.8.6
	 *
	 * @return string
	 */
	protected function get_filter_search_html(): string { // phpcs:ignore Generic.Metrics.CyclomaticComplexity

		$form_id = $this->get_filtered_form_id();
		$data    = $this->get_filter_search_parts();

		if ( ! $data ) {
			return '';
		}

		$comparisons = [
			'contains'     => __( 'contains', 'wpforms' ),
			'contains_not' => __( 'does not contain', 'wpforms' ),
			'is'           => __( 'is', 'wpforms' ),
			'is_not'       => __( 'is not', 'wpforms' ),
			'empty'        => __( 'is empty', 'wpforms' ),
			'not_empty'    => __( 'is not empty', 'wpforms' ),
		];
		$comparison  = $comparisons[ $data['comparison'] ] ?? $comparisons['contains'];
		$field       = $data['field'] ?? '';
		$term        = $data['term'] ?? '';

		// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		if ( ! empty( $_GET['search']['term'] ) && wpforms_is_empty_string( $term ) ) {
			$term = htmlspecialchars( wp_unslash( $_GET['search']['term'] ) );
		}
		// phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

		if ( is_numeric( $field ) && $form_id ) {
			$meta = wpforms()->obj( 'form' )->get_field( $form_id, $field );

			if ( isset( $meta['label'] ) ) {
				$field = $meta['label'];
			}
		} else {
			$advanced_options = Helpers::get_search_fields_advanced_options();
			$field            = ! empty( $advanced_options[ $field ] ) ? $advanced_options[ $field ] : __( 'any form field', 'wpforms' );
		}

		if ( $this->is_comparison_without_term( $data['comparison'] ) ) {
			return sprintf( /* translators: %1$s - field name, %2$s - operation. */
				__( 'where %1$s %2$s', 'wpforms' ),
				'<em>' . esc_html( $field ) . '</em>',
				esc_html( $comparison )
			);
		}

		return sprintf( /* translators: %1$s - field name, %2$s - operation, %3$s term. */
			__( 'where %1$s %2$s "%3$s"', 'wpforms' ),
			'<em>' . esc_html( $field ) . '</em>',
			esc_html( $comparison ),
			'<em>' . esc_html( $term ) . '</em>'
		);
	}

	/**
	 * Get filtered date range.
	 *
	 * @since 1.8.6
	 *
	 * @return array
	 *
	 * @noinspection PhpMissingReturnTypeInspection
	 */
	private function get_filtered_dates() {

		/**
		 * Filter the date range for the entry list page.
		 *
		 * @since 1.9.8.6
		 *
		 * @param array $dates Date range in the Y-m-d format. The 1st element is the start date, the 2nd element is the end date.
		 */
		$dates = (array) apply_filters( 'wpforms_pro_admin_entries_page_get_filtered_dates', [] );

		if ( ! empty( $dates ) ) {
			return array_map( 'sanitize_text_field', $dates );
		}

		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		if ( empty( $_GET['date'] ) ) {
			return [];
		}

		$dates = (array) explode( ' - ', sanitize_text_field( wp_unslash( $_GET['date'] ) ) );

		return array_map( 'sanitize_text_field', $dates );
		// phpcs:enable WordPress.Security.NonceVerification.Recommended
	}

	/**
	 * Return an array with information (HTML and id) for each filter for this current view.
	 *
	 * @since 1.8.6
	 *
	 * @return array
	 */
	public function get_filters_html(): array {

		$filters = [
			'.search-box' => $this->get_filter_search_html(),
		];

		$dates = $this->get_filtered_dates();

		if ( $dates ) {
			$dates = array_map(
				static function ( $date ) {

					return wpforms_date_format( $date );
				},
				$dates
			);

			$html = '';

			switch ( count( $dates ) ) {
				case 1:
					$html = sprintf( /* translators: %s: date. */
						esc_html__( 'on %s', 'wpforms' ),
						'<em>' . $dates[0] . '</em>'
					);
					break;

				case 2:
					$html = sprintf( /* translators: %1$s - date, %2$s - date. */
						esc_html__( 'between %1$s and %2$s', 'wpforms' ),
						'<em>' . $dates[0] . '</em>',
						'<em>' . $dates[1] . '</em>'
					);
					break;
			}

			$filters['.wpforms-filter-date'] = $html;
		}

		return array_filter( $filters );
	}

	/**
	 * Watch for filtering requests from a dates range selection.
	 *
	 * @since 1.8.6
	 */
	public function process_filter_dates() {

		$form_id = $this->get_filtered_form_id();

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

		$dates = $this->get_filtered_dates();

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

		$this->filter = array_merge(
			$this->filter,
			[
				'date'        => count( $dates ) === 1 ? $dates[0] : $dates,
				'is_filtered' => true,
			]
		);
	}

	/**
	 * Setup entry list page data.
	 *
	 * This function does the error checking and variable setup.
	 *
	 * @since 1.8.6
	 */
	public function setup() {

		if ( wpforms_current_user_can( 'view_forms' ) ) {
			$forms = wpforms()->obj( 'form' )->get(
				'',
				[
					'orderby'   => 'ID',
					'order'     => 'ASC',
					'post_type' => wpforms()->obj( 'entries_overview' )->overview_show_form_templates() ? wpforms()->obj( 'form' )::POST_TYPES : 'wpforms',
				]
			);

			// Fetch all forms.
			$this->forms = wpforms()->obj( 'access' )->filter_forms_by_current_user_capability( $forms, 'view_entries_form_single' );
		}

		// Check that the user has created at least one form.
		if ( empty( $this->forms ) ) {

			$this->alerts[] = [
				'type'    => 'info',
				'message' =>
					sprintf(
						wp_kses(
							/* translators: %s - WPForms Builder page. */
							__( 'Whoops, you haven\'t created a form yet. Want to <a href="%s">give it a go</a>?', 'wpforms' ),
							[
								'a' => [
									'href' => [],
								],
							]
						),
						admin_url( 'admin.php?page=wpforms-builder' )
					),
				'abort'   => true,
			];

		} else {
			$form_id = $this->get_filtered_form_id();

			/**
			 * Filters a default form id on the Entries list page.
			 *
			 * @since 1.1.6
			 *
			 * @param int $form_id Default form id.
			 *
			 * @return int
			 */
			$this->form_id = $form_id ? $form_id : apply_filters( 'wpforms_entry_list_default_form_id', absint( $this->forms[0]->ID ) ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
			$this->form    = wpforms()->obj( 'form' )->get( $this->form_id, [ 'cap' => 'view_entries_form_single' ] );
		}
	}

	/**
	 * Whether the current list of entries is filtered somehow or not.
	 *
	 * @since 1.8.6
	 *
	 * @return bool
	 */
	protected function is_list_filtered(): bool {

		$search_parts = $this->get_filter_search_parts();
		$dates        = $this->get_filtered_dates();
		$is_filtered  = ! empty( $dates ) || ( isset( $search_parts['term'] ) && ! wpforms_is_empty_string( $search_parts['term'] ) );

		if ( ! $is_filtered && ! empty( $search_parts['comparison'] ) && $this->is_comparison_without_term( $search_parts['comparison'] ) ) {
			$is_filtered = true;
		}

		/**
		 * Allows to mark the entries list as filtered or not, based on certain conditions.
		 *
		 * By default, the list is considered filtered if the date range is set or
		 * a non-empty search term is applied.
		 *
		 * @since 1.4.4
		 *
		 * @param bool $is_filtered Is the list filtered?
		 */
		return (bool) apply_filters( 'wpforms_entries_list_is_list_filtered', $is_filtered ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
	}

	/**
	 * List all entries in a specific form.
	 *
	 * @since 1.8.6
	 */
	public function list_all() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity

		if ( $this->abort ) {
			return;
		}

		$post_content = $this->form->post_content ?? '';
		$form_data    = $post_content ? wpforms_decode( $post_content ) : [];

		/**
		 * Filter the list all wrap classes.
		 *
		 * @since 1.8.3
		 *
		 * @param array $classes List all wrap classes.
		 */
		$classes = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
			'wpforms_entries_list_list_all_wrap_classes',
			[
				'wrap',
				'wpforms-admin-wrap',
			]
		);
		?>

		<div id="wpforms-entries-list" class="<?php echo wpforms_sanitize_classes( $classes, true ); ?>">

			<h1 class="page-title"><?php esc_html_e( 'Entries', 'wpforms' ); ?></h1>

			<?php
			$this->entries->form_id   = $this->form_id;
			$this->entries->form_data = $form_data;

			$this->entries->prepare_items();

			$last_entry = wpforms()->obj( 'entry' )->get_last( $this->form_id );
			?>

			<?php $this->entries_disabled_notice(); ?>

			<div class="wpforms-admin-content">

			<?php

			// Show empty entries table after delete bulk action.
			$is_deleted  = isset( $_REQUEST['deleted'] ) && $_REQUEST['deleted'] === '1'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$is_filtered = $this->is_list_filtered();

			if (
				empty( $this->entries->items ) &&
				$this->entries->counts['spam'] === 0 &&
				$this->entries->counts['trash'] === 0 &&
				! $is_deleted &&
				// phpcs:ignore WordPress.Security.NonceVerification.Recommended
				! $is_filtered && ! isset( $_GET['type'] ) && ! isset( $_GET['status'] )
			) {

				// Output no entries screen.
				// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				echo wpforms_render(
					'admin/empty-states/no-entries',
					[
						'message' => ! empty( $this->entries->form_data['settings']['disable_entries'] )
							? __( 'Storing entry information has been disabled for this form.', 'wpforms' )
							: __( 'It looks like you don\'t have any form entries just yet - check back soon!', 'wpforms' ),
					],
					true
				);

			} else {
				/**
				 * Fire before entries table.
				 *
				 * @since 1.1.6
				 *
				 * @param array $form_data    Form content.
				 * @param Page  $entries_list WPForms\Pro\Admin\Entries\Page class instance.
				 */
				do_action( 'wpforms_entry_list_title', $form_data, $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName

				// Are we on the "Spam" or "Trash" tab?
				// phpcs:disable WordPress.Security.NonceVerification.Recommended
				$has_status = isset( $_GET['status'] ) && in_array( $_GET['status'], [ SpamEntry::ENTRY_STATUS, self::TRASH_ENTRY_STATUS ], true );
				$status     = $has_status ? sanitize_key( $_GET['status'] ) : '';
				// phpcs:enable WordPress.Security.NonceVerification.Recommended
			?>

				<form id="wpforms-entries-table" method="GET"
					action="<?php echo esc_url( admin_url( 'admin.php?page=wpforms-entries' ) ); ?>"
					<?php echo ( ! $this->is_list_filtered() && isset( $last_entry->entry_id ) ) ? 'data-last-entry-id="' . absint( $last_entry->entry_id ) . '"' : ''; ?> <?php printf( 'data-filtered-count-trash="%1d"', absint( $this->entries->counts['total'] ) ); ?>>

					<?php if ( $this->get_filters_html() ) : ?>

						<div id="wpforms-reset-filter">
							<?php

							printf(
								wp_kses( /* translators: %s - number of entries found. */
									_n(
										'Found <strong>%s entry</strong>',
										'Found <strong>%s entries</strong>',
										absint( count( (array) $this->entries->items ) ),
										'wpforms'
									),
									[
										'strong' => [],
									]
								),
								$has_status ? absint( $this->entries->counts[ $status ] ) : absint( $this->entries->counts['total'] )
							);
							?>

							<?php foreach ( $this->get_filters_html() as $id => $html ) : ?>
								<?php
								echo wp_kses(
									$html,
									[ 'em' => [] ]
								);
								?>
								<i class="reset fa fa-times-circle" data-scope="<?php echo esc_attr( $id ); ?>"></i>
							<?php endforeach; ?>
						</div>
					<?php endif ?>

					<input type="hidden" name="page" value="wpforms-entries" />
					<input type="hidden" name="view" value="list" />
					<input type="hidden" name="form_id" value="<?php echo absint( $this->form_id ); ?>" />

					<?php if ( $has_status ) : ?>
						<input type="hidden" name="status" value="<?php echo esc_attr( $status ); ?>" />
					<?php endif; ?>

					<?php $this->entries->views(); ?>

					<?php $this->entries->search_box( esc_html__( 'Search', 'wpforms' ), 'wpforms-entries' ); ?>

					<?php $this->entries->display(); ?>

				</form>

			<?php } ?>

			</div>

		</div>

		<?php
	}

	/**
	 * Display abort message using empty state page.
	 *
	 * @since 1.8.6
	 */
	public function display_abort_message() {

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

		?>
		<div id="wpforms-entries-list" class="wrap wpforms-admin-wrap">

			<h1 class="page-title">
				<?php esc_html_e( 'Entries', 'wpforms' ); ?>
			</h1>
			<div class="wpforms-admin-content">
				<?php
				// Output empty state screen.
				// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				echo wpforms_render(
					'admin/empty-states/no-entries',
					[
						'message' => $this->abort_message,
					],
					true
				);
				?>
			</div>
		</div>
		<?php
	}

	/**
	 * Settings for field column personalization!
	 *
	 * @since 1.8.6
	 */
	public function field_column_setting() {

		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		echo $this->get_columns_multiselect();
	}

	/**
	 * Get columns multiselect menu.
	 *
	 * @since 1.8.6
	 *
	 * @return string HTML menu markup.
	 */
	private function get_columns_multiselect(): string {

		$field_columns = Columns::get_field_columns( $this->form_id );
		$meta_columns  = Columns::get_meta_columns();
		$selected_keys = Columns::get_selected_columns_keys();
		$selected_keys = empty( $selected_keys ) ? Columns::get_default_columns_keys() : $selected_keys;

		$html = '
			<div id="wpforms-list-table-ext-edit-columns-select-container" class="wpforms-hidden wpforms-list-table-ext-edit-columns-select-container wpforms-entries-list-page">
			<form method="post" action="' . esc_url( admin_url( 'admin.php?page=wpforms-entries&view=list&form_id=' . (int) $this->form_id ) ) . '">
				<input type="hidden" name="action" value="wpforms_admin_entries_list_save_columns_order"/>
				<input type="hidden" name="form_id" value="' . (int) $this->form_id . '"/>
				<select name="fields[]"
					id="wpforms-entries-table-edit-columns-select"
					class="wpforms-entries-table-edit-columns-select wpforms-list-table-ext-edit-columns-select"
					name="wpforms-entries-table-edit-columns-select"
					multiple="multiple">
					%s
					%s
				</select>
			</form>
			</div>
		';

		$field_options = $this->get_optgroup_html( $field_columns, $selected_keys, esc_html__( 'Form Fields', 'wpforms' ) );
		$meta_options  = $this->get_optgroup_html( $meta_columns, $selected_keys, esc_html__( 'Entry Meta', 'wpforms' ) );

		return sprintf( $html, $field_options, $meta_options );
	}

	/**
	 * Get columns multiselect options.
	 *
	 * @since 1.8.6
	 *
	 * @param Column[] $columns       List of columns.
	 * @param array    $selected_keys List of selected columns keys.
	 * @param string   $label         Label for the optgroup.
	 *
	 * @return string HTML options markup.
	 */
	private function get_optgroup_html( $columns, $selected_keys, $label ): string {

		if ( empty( $columns ) ) {
			return '';
		}

		$html = '<optgroup label="' . esc_html( $label ) . '">';

		foreach ( $columns as $column ) {
			$selected = in_array( $column->get_id(), $selected_keys, true ) ? 'selected' : '';
			$html    .= sprintf( '<option value="%s" %s>%s</option>', esc_attr( $column->get_id() ), $selected, esc_html( $column->get_label() ) );
		}

		$html .= '</optgroup>';

		return $html;
	}

	/**
	 * Entries list form actions.
	 *
	 * @since 1.8.6
	 *
	 * @param array $form_data Form data and settings.
	 *
	 * @noinspection PhpMissingParamTypeInspection, ReturnTypeCanBeDeclaredInspection
	 */
	public function list_form_actions( $form_data ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity

		$actions = [ 'all_entries', 'payments', 'edit_form', 'view_form', 'export_entries', 'read_entries', 'delete_entries' ];

		/**
		 * Filter the list of entries list form actions.
		 *
		 * @since 1.9.8.6
		 *
		 * @param array $actions   List of entries list form actions.
		 * @param array $form_data Form data and settings.
		 */
		$actions = (array) apply_filters( 'wpforms_pro_admin_entries_page_actions', $actions, $form_data );

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

		// Edit Form URL.
		$edit_url = add_query_arg(
			[
				'page'    => 'wpforms-builder',
				'view'    => 'fields',
				'form_id' => absint( $this->form_id ),
			],
			admin_url( 'admin.php' )
		);

		// Payments URL.
		if ( wpforms()->obj( 'payment' )->get_by( 'form_id', $this->form_id ) ) {
			$payments_url = add_query_arg(
				[
					'page'    => 'wpforms-payments',
					'form_id' => absint( $this->form_id ),
				],
				admin_url( 'admin.php' )
			);
		}

		// Preview Entry URL.
		$preview_url = esc_url( wpforms_get_form_preview_url( $this->form_id ) );

		// Export Entry URL.
		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		$export_url = add_query_arg(
			[
				'page'   => 'wpforms-tools',
				'view'   => 'export',
				'form'   => absint( $this->form_id ),
				'search' => ! empty( $_GET['search'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_GET['search'] ) ) : [],
				'date'   => ! empty( $_GET['date'] ) ? sanitize_text_field( wp_unslash( $_GET['date'] ) ) : [],
			],
			admin_url( 'admin.php' )
		);

		$read_url_args = [ 'action' => 'markread' ];

		if ( ! empty( $_GET['status'] ) ) {
			$read_url_args['status'] = sanitize_key( $_GET['status'] );
		}
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		// Mark Read URL.
		$read_url = wp_nonce_url(
			add_query_arg(
				$read_url_args,
				$base
			),
			'wpforms_entry_list_markread'
		);

		// Delete all entries.
		$delete_url = wp_nonce_url( $base, 'bulk-entries' );

		// Form title.
		$form_title = isset( $form_data['settings']['form_title'] ) ? $form_data['settings']['form_title'] : '';

		if ( empty( $form_title ) ) {
			$form = wpforms()->obj( 'form' )->get( $this->form_id );

			$form_title = ! empty( $form )
				? $form->post_title
				: sprintf( /* translators: %d - form ID. */
					esc_html__( 'Form (#%d)', 'wpforms' ),
					$this->form_id
				);
		}

		$is_form_template  = wpforms_is_form_template( $this->form_id );
		$form_title_suffix = '';

		if ( $is_form_template ) {
			$form_title_suffix = sprintf( '<span> — %s</span>', esc_html__( 'Template', 'wpforms' ) );
		}
		?>

		<div class="form-details">

			<span class="form-details-sub"><?php esc_html_e( 'Select Form', 'wpforms' ); ?></span>

			<h3 class="form-details-title">
				<?php
				echo esc_html( wp_strip_all_tags( $form_title ) ) . wp_kses( $form_title_suffix, [ 'span' => [] ] );
				$this->form_selector_html();
				?>
			</h3>

			<div class="form-details-actions">

				<?php if ( in_array( 'all_entries', $actions, true ) && $this->is_list_filtered() ) : ?>
					<a href="<?php echo esc_url( $base ); ?>" class="form-details-actions-entries">
						<span class="dashicons dashicons-list-view"></span>
						<?php esc_html_e( 'All Entries', 'wpforms' ); ?>
					</a>
				<?php endif; ?>
				<?php if ( in_array( 'payments', $actions, true ) && isset( $payments_url ) ) : ?>
					<a href="<?php echo esc_url( $payments_url ); ?>" class="form-details-actions-view-payments">
						<span class="fa fa-credit-card"></span>
						<?php esc_html_e( 'View Payments', 'wpforms' ); ?>
					</a>
				<?php endif; ?>

				<?php if ( in_array( 'edit_form', $actions, true ) && wpforms_current_user_can( 'edit_form_single', $this->form_id ) ) : ?>
					<a href="<?php echo esc_url( $edit_url ); ?>" class="form-details-actions-edit">
						<span class="dashicons dashicons-edit"></span>
						<?php $is_form_template ? esc_html_e( 'Edit This Template', 'wpforms' ) : esc_html_e( 'Edit This Form', 'wpforms' ); ?>
					</a>
				<?php endif; ?>

				<?php if ( in_array( 'view_form', $actions, true ) && wpforms_current_user_can( 'view_form_single', $this->form_id ) ) : ?>
					<a href="<?php echo esc_url( $preview_url ); ?>" class="form-details-actions-preview" target="_blank" rel="noopener noreferrer">
						<span class="dashicons dashicons-visibility"></span>
						<?php $is_form_template ? esc_html_e( 'Preview Template', 'wpforms' ) : esc_html_e( 'Preview Form', 'wpforms' ); ?>
					</a>
				<?php endif; ?>

				<?php if ( in_array( 'export_entries', $actions, true ) && ! $is_form_template ) : ?>
					<a href="<?php echo esc_url( $export_url ); ?>" class="form-details-actions-export">
						<span class="dashicons dashicons-migrate"></span>
						<?php echo $this->is_list_filtered() ? esc_html__( 'Export Filtered', 'wpforms' ) : esc_html__( 'Export All', 'wpforms' ); ?>
					</a>
				<?php endif; ?>

				<?php if ( in_array( 'read_entries', $actions, true ) ) : ?>
					<a href="<?php echo esc_url( $read_url ); ?>" class="form-details-actions-read">
						<span class="dashicons dashicons-marker"></span>
						<?php esc_html_e( 'Mark All Read', 'wpforms' ); ?>
					</a>
				<?php endif; ?>

				<?php if ( in_array( 'delete_entries', $actions, true ) && wpforms_current_user_can( 'delete_entries_form_single', $this->form_id ) ) : ?>
					<?php $status = ! empty( $_GET['status'] ) ? sanitize_key( $_GET['status'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>

					<a href="<?php echo esc_url( $delete_url ); ?>" class="form-details-actions-removeall" data-page="<?php echo esc_attr( $status ); ?>">
						<span class="dashicons dashicons-trash"></span>
						<?php $status === self::TRASH_ENTRY_STATUS ? esc_html_e( 'Empty Trash', 'wpforms' ) : esc_html_e( 'Trash All', 'wpforms' ); ?>
					</a>
				<?php endif; ?>

			</div>

		</div>
		<?php
	}

	/**
	 * Display form selector HTML.
	 *
	 * @since 1.8.6
	 */
	protected function form_selector_html() {

		if ( ! wpforms_current_user_can( 'view_forms' ) ) {
			return;
		}

		if ( empty( $this->forms ) ) {
			return;
		}

		?>
		<div class="form-selector">
			<a href="#" title="<?php esc_attr_e( 'Open form selector', 'wpforms' ); ?>" class="toggle dashicons dashicons-arrow-down-alt2"></a>
			<div class="form-list">
				<ul>
					<?php
					foreach ( $this->forms as $form ) {
						if ( $this->form_id === $form->ID ) {
							continue;
						}

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

						$form_title = $form->post_type === 'wpforms-template'
							? $form->post_title . ' – ' . __( 'Template', 'wpforms' )
							: $form->post_title;

						printf( '<li><a href="%s">%s</a></li>', esc_url( $form_url ), esc_html( $form_title ) );
					}
					?>
				</ul>
			</div>

		</div>
		<?php
	}

	/**
	 * Add notices and errors.
	 *
	 * @since 1.8.6
	 */
	public function register_alerts() {

		if ( empty( $this->alerts ) ) {
			return;
		}

		foreach ( $this->alerts as $alert ) {
			$type = ! empty( $alert['type'] ) ? $alert['type'] : 'info';

			Notice::add( $alert['message'], $type );

			if ( ! empty( $alert['abort'] ) ) {
				$this->abort = true;

				break;
			}
		}
	}

	/**
	 * Check for new entries using Heartbeat API.
	 *
	 * @since 1.8.6
	 *
	 * @param array  $response  The Heartbeat response.
	 * @param array  $data      The $_POST data sent.
	 * @param string $screen_id The screen id.
	 *
	 * @return array
	 */
	public function heartbeat_new_entries_check( $response, $data, $screen_id ) {

		if ( $screen_id !== 'wpforms_page_wpforms-entries' ) {
			return $response;
		}

		$entry_id = ! empty( $data['wpforms_new_entries_entry_id'] ) ? absint( $data['wpforms_new_entries_entry_id'] ) : 0;
		$form_id  = ! empty( $data['wpforms_new_entries_form_id'] ) ? absint( $data['wpforms_new_entries_form_id'] ) : 0;

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

		$entries_count = wpforms()->obj( 'entry' )->get_next_count( $entry_id, $form_id, '' );

		if ( empty( $entries_count ) ) {
			return $response;
		}

		/* translators: %d - number of form entries. */
		$response['wpforms_new_entries_notification'] = esc_html( sprintf( _n( 'See %d new entry', 'See %d new entries', $entries_count, 'wpforms' ), $entries_count ) );

		return $response;
	}

	/**
	 * Localize needed strings.
	 *
	 * @since 1.8.6
	 *
	 * @param array $strings JS strings.
	 *
	 * @return array
	 */
	public function js_strings( $strings ): array {

		$strings = (array) $strings;

		$strings['lang_code']    = sanitize_key( wpforms_get_language_code() );
		$strings['default_date'] = [];
		$dates                   = $this->get_filtered_dates();

		if ( $dates ) {
			if ( count( $dates ) === 1 ) {
				$dates[1] = $dates[0];
			}
			$strings['default_date'] = [ sanitize_text_field( $dates[0] ), sanitize_text_field( $dates[1] ) ];
		}

		return $strings;
	}

	/**
	 * Display info notice for forms that have entries and disable_entries setting.
	 *
	 * The entries list isn't available when register_alerts runs.
	 *
	 * @since 1.8.6
	 *
	 * @return void
	 */
	protected function entries_disabled_notice() {

		if (
			empty( $this->entries->form_data['settings']['disable_entries'] )
			|| empty( $this->entries->items )
		) {
			return;
		}

		?>

		<div class="notice wpforms-notice notice-info" style="display: block;">
			<p>
				<?php esc_html_e( 'Storing entry information has been disabled for this form.', 'wpforms' ); ?>
			</p>
		</div>

		<?php
	}

	/**
	 * Check if the comparison type does not require a search term.
	 *
	 * @since 1.9.9
	 *
	 * @param string $comparison Comparison operator.
	 *
	 * @return bool
	 */
	private function is_comparison_without_term( string $comparison ): bool {

		return in_array( $comparison, [ 'empty', 'not_empty' ], true );
	}
}
