<?php
use Inpsyde\Restore\ViewLoader;

/**
 * Class For BackWPup Jobs page.
 */
class BackWPup_Page_Jobs extends WP_List_Table {

	/**
	 * Nonce action for list filters and sorting.
	 */
	private const LIST_NONCE_ACTION = 'backwpup_jobs_list';

	/**
	 * Log file path.
	 *
	 * @var string
	 */
	public static $logfile;

	/**
	 * List table object
	 *
	 * @var self
	 */
	private static $listtable;

	/**
	 * Job object.
	 *
	 * @var bool|object
	 */
	private $job_object;

	/**
	 * Job types.
	 *
	 * @var array
	 */
	private $job_types;

	/**
	 * Destinations array.
	 *
	 * @var array
	 */
	private $destinations;

	/**
	 * Constructor.
	 */
	public function __construct() {
		parent::__construct(
			[
				'plural'   => 'jobs',
				'singular' => 'job',
				'ajax'     => true,
			]
			);
	}

	/**
	 * Check if the current user has backwpup capability.
	 *
	 * @return bool
	 */
	public function ajax_user_can() {
		return current_user_can( 'backwpup' );
	}

	/**
	 * Get filtered jobs based on the status UI filter.
	 *
	 * @return array
	 */
	private function get_filtered_jobs() {
		$jobs = array_filter(
			BackWPup_Option::get_job_ids(),
			function ( $job_id ) {
				return BackWPup_Option::get( $job_id, 'legacy', false );
			}
		);

		// Get the "status" filter value from the GET request.
		$status_filter = '';
		$nonce         = $this->get_list_nonce();
		if ( $nonce && wp_verify_nonce( $nonce, self::LIST_NONCE_ACTION ) ) {
			$status_filter = filter_input( INPUT_GET, 'status', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
			if ( null === $status_filter && isset( $_GET['status'] ) ) {
				$status_filter = sanitize_text_field( wp_unslash( $_GET['status'] ) );
			} else {
				$status_filter = $status_filter ? sanitize_text_field( $status_filter ) : '';
			}
		}
		if ( empty( $status_filter ) ) {
			return $jobs;
		}

		return array_filter(
			$jobs,
			function ( $job_id ) use ( $status_filter ) {
				switch ( $status_filter ) {
					case 'disabled':
						return empty( BackWPup_Option::get( $job_id, 'activetype', '' ) );
				}
				return true;
			}
		);
	}

	/**
	 * Prepares the list of items for displaying.
	 *
	 * @return void
	 */
	public function prepare_items() {
		$this->items        = $this->get_filtered_jobs();
		$this->job_object   = BackWPup_Job::get_working_data();
		$this->job_types    = BackWPup::get_job_types();
		$this->destinations = BackWPup::get_registered_destinations();

		$order_param   = '';
		$orderby_param = '';
		$nonce         = $this->get_list_nonce();
		if ( $nonce && wp_verify_nonce( $nonce, self::LIST_NONCE_ACTION ) ) {
			$order_param = filter_input( INPUT_GET, 'order', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
			if ( null === $order_param && isset( $_GET['order'] ) ) {
				$order_param = sanitize_text_field( wp_unslash( $_GET['order'] ) );
			} else {
				$order_param = $order_param ? sanitize_text_field( $order_param ) : '';
			}
			$order_param = $order_param ? sanitize_key( $order_param ) : '';

			$orderby_param = filter_input( INPUT_GET, 'orderby', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
			if ( null === $orderby_param && isset( $_GET['orderby'] ) ) {
				$orderby_param = sanitize_text_field( wp_unslash( $_GET['orderby'] ) );
			} else {
				$orderby_param = $orderby_param ? sanitize_text_field( $orderby_param ) : '';
			}
			$orderby_param = $orderby_param ? sanitize_key( $orderby_param ) : '';
		}

		if ( empty( $order_param ) || empty( $orderby_param ) ) {
			return;
		}

		if ( 'asc' === strtolower( (string) $order_param ) ) {
			$order = SORT_ASC;
		} else {
			$order = SORT_DESC;
		}

		if ( empty( $orderby_param ) || ! in_array( strtolower( (string) $orderby_param ), [ 'jobname', 'type', 'dest', 'next', 'last' ], true ) ) {
			$orderby = 'jobname';
		} else {
			$orderby = strtolower( (string) $orderby_param );
		}

		// Sorting.
		$job_configs = [];
		$i           = 0;

		foreach ( $this->items as $item ) {
			$job_configs[ $i ]['jobid']   = $item;
			$job_configs[ $i ]['jobname'] = BackWPup_Option::get( $item, 'name' );
			$job_configs[ $i ]['type']    = BackWPup_Option::get( $item, 'type' );
			$job_configs[ $i ]['dest']    = BackWPup_Option::get( $item, 'destinations' );
			if ( SORT_ASC === $order ) {
				sort( $job_configs[ $i ]['type'] );
				sort( $job_configs[ $i ]['dest'] );
			} else {
				rsort( $job_configs[ $i ]['type'] );
				rsort( $job_configs[ $i ]['dest'] );
			}
			$job_configs[ $i ]['type'] = array_shift( $job_configs[ $i ]['type'] );
			$job_configs[ $i ]['dest'] = array_shift( $job_configs[ $i ]['dest'] );
			$job_configs[ $i ]['next'] = (int) wp_next_scheduled( 'backwpup_cron', [ 'arg' => $item ] );
			$job_configs[ $i ]['last'] = BackWPup_Option::get( $item, 'lastrun' );
			++$i;
		}

		$tmp = [];

		foreach ( $job_configs as &$ma ) {
			$tmp[] = &$ma[ $orderby ];
		}
		array_multisort( $tmp, $order, $job_configs );

		$this->items = [];

		foreach ( $job_configs as $item ) {
			$this->items[] = $item['jobid'];
		}
	}

	/**
	 * Message to be displayed when there are no items.
	 *
	 * @return void
	 */
	public function no_items() {
		esc_html_e( 'No Legacy Jobs.', 'backwpup' );
	}

	/**
	 * Retrieves the list of bulk actions available for this table.
	 *
	 * @return array
	 */
	public function get_bulk_actions() {
		if ( ! $this->has_items() ) {
			return [];
		}

		$actions           = [];
		$actions['delete'] = __( 'Delete', 'backwpup' );

		return wpm_apply_filters_typed( 'array', 'backwpup_page_jobs_get_bulk_actions', $actions );
	}

	/**
	 * Gets a list of columns.
	 *
	 * @return array
	 */
	public function get_columns() {
		$jobs_columns            = [];
		$jobs_columns['cb']      = '<input type="checkbox" />';
		$jobs_columns['jobname'] = __( 'Job Name', 'backwpup' );
		$jobs_columns['type']    = __( 'Type', 'backwpup' );
		$jobs_columns['dest']    = __( 'Destinations', 'backwpup' );
		$jobs_columns['next']    = __( 'Next Run', 'backwpup' );
		$jobs_columns['last']    = __( 'Last Run', 'backwpup' );

		return $jobs_columns;
	}

	/**
	 * Gets a list of sortable columns.
	 *
	 * @return array
	 */
	public function get_sortable_columns() {
		return [
			'jobname' => 'jobname',
			'type'    => 'type',
			'dest'    => 'dest',
			'next'    => 'next',
			'last'    => 'last',
		];
	}

	/**
	 * Retrieves the table classes for the job page.
	 *
	 * This method constructs an array of CSS classes to be applied to the table
	 * on the job page. The 'fixed' class is intentionally omitted from the default
	 * classes.
	 *
	 * @return array An array of CSS classes for the table.
	 */
	protected function get_table_classes() {
		// Remove 'fixed' from the default classes.
		$classes = [ 'widefat', 'striped', $this->_args['plural'] ];
		return $classes;
	}

	/**
	 * The checkbox column.
	 *
	 * @param int $item Job ID.
	 *
	 * @return string
	 */
	public function column_cb( $item ) {
		return '<input type="checkbox" name="jobs[]" value="' . esc_attr( $item ) . '" />';
	}

	/**
	 * The job name column.
	 *
	 * @param int $item Job ID.
	 *
	 * @return string
	 */
	public function column_jobname( $item ) {
		$job_normal_hide = '';
		if ( is_object( $this->job_object ) ) {
			$job_normal_hide = ' style="display:none;"';
		}

		$r = '<strong title="' . sprintf(
			/* translators: %d: job ID. */
			__( 'Job ID: %d', 'backwpup' ),
			$item
		) . '">' . esc_html( BackWPup_Option::get( $item, 'name' ) ) . '</strong>';
		$actions = [];
		if ( current_user_can( 'backwpup_jobs_edit' ) ) {
			$actions['edit']   = '<a href="' . wp_nonce_url( network_admin_url( 'admin.php' ) . '?page=backwpupeditjob&jobid=' . $item, 'edit-job' ) . '">' . esc_html__( 'Edit', 'backwpup' ) . '</a>';
			$actions['delete'] = '<a class="submitdelete" href="' . wp_nonce_url( network_admin_url( 'admin.php' ) . '?page=backwpupjobs&action=delete&jobs[]=' . $item, 'bulk-jobs' ) . '" onclick="return showNotice.warn();">' . esc_html__( 'Delete', 'backwpup' ) . '</a>';
		}
		if ( current_user_can( 'backwpup_jobs_start' ) ) {
			$url               = BackWPup_Job::get_jobrun_url( 'runnowlink', $item );
			$actions['runnow'] = '<a href="' . esc_attr( $url['url'] ) . '">' . esc_html__( 'Run now', 'backwpup' ) . '</a>';
		}
		if ( current_user_can( 'backwpup_logs' ) && BackWPup_Option::get( $item, 'logfile' ) ) {
			$logfile = basename( (string) BackWPup_Option::get( $item, 'logfile' ) );
			if ( is_object( $this->job_object ) && (int) $this->job_object->job['jobid'] === (int) $item ) {
				$logfile = basename( (string) $this->job_object->logfile );
			}
			$log_name           = str_replace( [ '.html', '.gz' ], '', basename( $logfile ) );
			$actions['lastlog'] = '<a href="' . admin_url( 'admin-ajax.php' ) . '?&action=backwpup_view_log&log=' . $log_name . '&_ajax_nonce=' . wp_create_nonce( 'view-log_' . $log_name ) . '&amp;TB_iframe=true&amp;width=640&amp;height=440\" title="' . esc_attr( $logfile ) . '" class="thickbox">' . __( 'Last log', 'backwpup' ) . '</a>';
		}
		$actions = wpm_apply_filters_typed( 'array', 'backwpup_page_jobs_actions', $actions, $item, false );
		$r      .= '<div class="job-normal"' . $job_normal_hide . '>' . $this->row_actions( $actions ) . '</div>';
		if ( is_object( $this->job_object ) ) {
			$actionsrun = [];
			$actionsrun = wpm_apply_filters_typed( 'array', 'backwpup_page_jobs_actions', $actionsrun, $item, true );
			$r         .= '<div class="job-run">' . $this->row_actions( $actionsrun ) . '</div>';
		}

		return $r;
	}

	/**
	 * The type column.
	 *
	 * @param int $item Job ID.
	 *
	 * @return string
	 */
	public function column_type( $item ) {
		$r     = '';
		$types = BackWPup_Option::get( $item, 'type' );
		if ( $types ) {
			foreach ( $types as $type ) {
				if ( isset( $this->job_types[ $type ] ) ) {
					$r .= $this->job_types[ $type ]->info['name'] . '<br />';
				} else {
					$r .= $type . '<br />';
				}
			}
		}

		return $r;
	}

	/**
	 * The destination column.
	 *
	 * @param int $item Job ID.
	 *
	 * @return string
	 */
	public function column_dest( $item ) {
		$r         = '';
		$backup_to = false;

		foreach ( BackWPup_Option::get( $item, 'type' ) as $typeid ) {
			if ( isset( $this->job_types[ $typeid ] ) && $this->job_types[ $typeid ]->creates_file() ) {
				$backup_to = true;
				break;
			}
		}
		if ( $backup_to ) {
			foreach ( BackWPup_Option::get( $item, 'destinations' ) as $destid ) {
				if ( isset( $this->destinations[ $destid ]['info']['name'] ) ) {
					$r .= $this->destinations[ $destid ]['info']['name'] . '<br />';
				} else {
					$r .= $destid . '<br />';
				}
			}
		} else {
			$r .= '<i>' . __( 'Not needed or set', 'backwpup' ) . '</i><br />';
		}

		return $r;
	}

	/**
	 * The next column.
	 *
	 * @param int $item Job ID.
	 *
	 * @return string
	 */
	public function column_next( $item ) {
		$r = '';

		$job_normal_hide = '';
		if ( is_object( $this->job_object ) ) {
			$job_normal_hide = ' style="display:none;"';
		}
		if ( is_object( $this->job_object ) && $this->job_object->job['jobid'] === $item ) {
			$current_timestamp = time() + (int) ( (float) get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
			$runtime           = $current_timestamp - $this->job_object->start_time;
			$r                .= '<div class="job-run">' . sprintf(
				/* translators: %s: runtime in seconds. */
				esc_html__( 'Running for: %s seconds', 'backwpup' ),
				'<span id="runtime">' . $runtime . '</span>'
			) . '</div>';
		}
		if ( is_object( $this->job_object ) && (int) $this->job_object->job['jobid'] === (int) $item ) {
			$r .= '<div class="job-normal"' . $job_normal_hide . '>';
		}
		if ( 'wpcron' === BackWPup_Option::get( $item, 'activetype' ) ) {
			$nextrun = wp_next_scheduled( 'backwpup_cron', [ 'arg' => $item ] ) + ( get_option( 'gmt_offset' ) * 3600 );
			if ( $nextrun ) {
				// translators: %s: cron expression.
				$title = sprintf( esc_html__( 'Cron: %s', 'backwpup' ), BackWPup_Option::get( $item, 'cron' ) );
				// translators: %1$s: date, %2$s: time.
				$content = sprintf( __( '%1$s at %2$s', 'backwpup' ), wp_date( get_option( 'date_format' ), $nextrun ), wp_date( get_option( 'time_format' ), $nextrun ) );
				$r      .= '<span title="' . $title . '">' . $content . '</span><br />';
			} else {
				$r .= __( 'Not scheduled!', 'backwpup' ) . '<br />';
			}
		} elseif ( 'link' === BackWPup_Option::get( $item, 'activetype' ) ) {
			$r .= __( 'External link', 'backwpup' ) . '<br />';
		} else {
			$r .= __( 'Inactive', 'backwpup' );
		}
		if ( is_object( $this->job_object ) && (int) $this->job_object->job['jobid'] === (int) $item ) {
			$r .= '</div>';
		}

		return $r;
	}

	/**
	 * The last column.
	 *
	 * @param int $item Job ID.
	 *
	 * @return string
	 */
	public function column_last( $item ) {
		$r = '';

		if ( BackWPup_Option::get( $item, 'lastrun' ) ) {
			$lastrun = BackWPup_Option::get( $item, 'lastrun' );
			$r      .= sprintf(
				// translators: %1$s: date, %2$s: time.
				__( '%1$s at %2$s', 'backwpup' ),
				wp_date( get_option( 'date_format' ), $lastrun ),
				wp_date( get_option( 'time_format' ), $lastrun )
			);
			if ( BackWPup_Option::get( $item, 'lastruntime' ) ) {
				$r .= '<br />' . sprintf(
					// translators: %d: seconds.
					__( 'Runtime: %d seconds', 'backwpup' ),
					BackWPup_Option::get( $item, 'lastruntime' )
				);
			}
		} else {
			$r .= __( 'not yet', 'backwpup' );
		}
		$r .= '<br /><span class="last-action-links">';

		$download_url = BackWPup_Option::get( $item, 'lastbackupdownloadurl' );
		if ( current_user_can( 'backwpup_backups_download' ) && ! empty( $download_url ) ) {
			// If it's not a direct link, but will be run through the downloader, then process differently.
			if ( strpos( $download_url, 'backwpupbackups' ) === false ) {
				$r .= '<a href="' . wp_nonce_url( $download_url, 'backwpup_action_nonce' ) . '" title="' . esc_attr( __( 'Download last backup', 'backwpup' ) ) . '">' . esc_html__( 'Download', 'backwpup' ) . '</a> | ';
			} else {
				$r .= self::generate_download_link( $download_url ) . ' | ';
			}
		}

		if ( current_user_can( 'backwpup_logs' ) && BackWPup_Option::get( $item, 'logfile' ) ) {
			$logfile = basename( (string) BackWPup_Option::get( $item, 'logfile' ) );
			if ( is_object( $this->job_object ) && (int) $this->job_object->job['jobid'] === (int) $item ) {
				$logfile = basename( (string) $this->job_object->logfile );
			}
			$log_name = str_replace( [ '.html', '.gz' ], '', basename( $logfile ) );
			$r       .= '<a class="thickbox" href="' . admin_url( 'admin-ajax.php' ) . '?&action=backwpup_view_log&log=' . $log_name . '&_ajax_nonce=' . wp_create_nonce( 'view-log_' . $log_name ) . '&amp;TB_iframe=true&amp;width=640&amp;height=440" title="' . esc_attr( $logfile ) . '">' . esc_html__( 'Log', 'backwpup' ) . '</a>';
		}
		$r .= '</span>';

		return $r;
	}

	/**
	 * Generates download link from url.
	 *
	 * @param string $download_url Download url.
	 * @return string
	 */
	private static function generate_download_link( $download_url ) {
		$params = [];
		parse_str( wp_parse_url( $download_url, PHP_URL_QUERY ), $params );

		$file        = $params['file'];
		$local_file  = untrailingslashit( BackWPup::get_plugin_data( 'TEMP' ) ) . '/' . basename( $params['local_file'] ?? $file );
		$jobid       = $params['jobid'];
		$destination = strtoupper( str_replace( 'download', '', $params['action'] ) );

		// Construct the link.
		return sprintf(
			'<a href="#TB_inline?height=300&width=630&inlineId=tb_download_file" 
            class="backup-download-link thickbox js-backwpup-download-backup" 
            id="backup-download-link"
            data-jobid="%1$s" 
            data-destination="%2$s" 
            data-file="%3$s" 
            data-local-file="%4$s" 
            data-nonce="%5$s" 
            data-url="%6$s">%7$s</a>',
			intval( $jobid ),
			esc_attr( $destination ),
			esc_attr( $file ),
			esc_attr( $local_file ),
			wp_create_nonce( 'backwpup_action_nonce' ),
			wp_nonce_url( $download_url, 'backwpup_action_nonce' ),
			__( 'Download', 'backwpup' )
		);
	}

	/**
	 * Load listing page.
	 *
	 * @return void
	 */
	public static function load() {
		// Create table.
		self::$listtable = new self();

		if ( empty( self::$listtable->current_action() ) ) {
			self::ensure_list_nonce();
		}

		switch ( self::$listtable->current_action() ) {
			case 'delete': // Delete job.
				if ( ! current_user_can( 'backwpup_jobs_edit' ) ) {
					break;
				}
				$jobs = filter_input( INPUT_GET, 'jobs', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
				if ( ! empty( $jobs ) ) {
					check_admin_referer( 'bulk-jobs' );

					foreach ( array_map( 'absint', $jobs ) as $jobid ) {
						wp_clear_scheduled_hook( 'backwpup_cron', [ 'arg' => $jobid ] );
						BackWPup_Option::delete_job( $jobid );
					}
				}
				break;

			case 'copy': // Copy job.
				if ( ! current_user_can( 'backwpup_jobs_edit' ) ) {
					break;
				}
				$old_job_id = absint( filter_input( INPUT_GET, 'jobid', FILTER_SANITIZE_NUMBER_INT ) );
				if ( $old_job_id < 1 ) {
					break;
				}
				check_admin_referer( 'copy-job_' . $old_job_id );
				// Create new.
				$newjobid = BackWPup_Option::get_job_ids();
				sort( $newjobid );
				$newjobid    = end( $newjobid ) + 1;
				$old_options = BackWPup_Option::get_job( $old_job_id );

				foreach ( $old_options as $key => $option ) {
					if ( 'jobid' === $key ) {
						$option = $newjobid;
					}
					if ( 'name' === $key ) {
						$option = __( 'Copy of', 'backwpup' ) . ' ' . $option;
					}
					if ( 'activetype' === $key ) {
						$option = '';
					}
					if ( 'archivename' === $key ) {
						$option = str_replace( $old_job_id, $newjobid, (string) $option );
					}
					if ( 'logfile' === $key || 'lastbackupdownloadurl' === $key || 'lastruntime' === $key || 'lastrun' === $key ) {
						continue;
					}
					BackWPup_Option::update( $newjobid, $key, $option );
				}
				break;

			case 'runnow':
				$jobid = absint( filter_input( INPUT_GET, 'jobid', FILTER_SANITIZE_NUMBER_INT ) );
				if ( $jobid ) {
					if ( ! current_user_can( 'backwpup_jobs_start' ) ) {
						wp_die( esc_html__( 'Sorry, you don\'t have permissions to do that.', 'backwpup' ) );
					}
					check_admin_referer( 'backwpup_job_run-runnowlink' );

					// Check temp folder.
					$temp_folder_message = BackWPup_File::check_folder( BackWPup::get_plugin_data( 'TEMP' ), true );
					BackWPup_Admin::message( $temp_folder_message, true );
					// Check log folder.
					$log_folder         = get_site_option( 'backwpup_cfg_logfolder' );
					$log_folder         = BackWPup_File::get_absolute_path( $log_folder );
					$log_folder_message = BackWPup_File::check_folder( $log_folder );
					BackWPup_Admin::message( $log_folder_message, true );
					// Check backup destinations.
					$job_types      = BackWPup::get_job_types();
					$job_conf_types = BackWPup_Option::get( $jobid, 'type' );
					$creates_file   = false;

					foreach ( $job_types as $id => $job_type_class ) {
						if ( in_array( $id, $job_conf_types, true ) && $job_type_class->creates_file() ) {
							$creates_file = true;
							break;
						}
					}
					if ( $creates_file ) {
						$job_conf_dests = BackWPup_Option::get( $jobid, 'destinations' );
						$destinations   = 0;

						foreach ( BackWPup::get_registered_destinations() as $id => $dest ) {
							if ( ! in_array( $id, $job_conf_dests, true ) || empty( $dest['class'] ) ) {
								continue;
							}

							/* @var BackWPup_Destinations $dest_class */
							$dest_class   = BackWPup::get_destination( $id );
							$job_settings = BackWPup_Option::get_job( $jobid );
							if ( ! $dest_class->can_run( $job_settings ) ) {
								BackWPup_Admin::message(
									sprintf(
										/* translators: 1: job name, 2: destination name. */
										__( 'The job "%1$s" destination "%2$s" is not configured properly', 'backwpup' ),
										esc_attr( BackWPup_Option::get( $jobid, 'name' ) ),
										$id
									),
									true
								);
							}
							++$destinations;
						}
						if ( $destinations < 1 ) {
							BackWPup_Admin::message(
								sprintf(
									/* translators: %s: job name. */
									__( 'The job "%s" needs properly configured destinations to run!', 'backwpup' ),
									esc_attr( BackWPup_Option::get( $jobid, 'name' ) )
								),
								true
							);
						}
					}

					// Only start job if messages empty.
					$log_messages = BackWPup_Admin::get_messages();

					if ( empty( $log_messages ) ) {
						$old_log_file = BackWPup_Option::get( $jobid, 'logfile' );
						BackWPup_Job::get_jobrun_url( 'runnow', $jobid );
						usleep( 250000 ); // Wait a quarter second.
						$new_log_file = BackWPup_Option::get( $jobid, 'logfile', null, false );
						// Sleep as long as job not started.
						$i = 0;

						while ( $old_log_file === $new_log_file ) {
							usleep( 250000 ); // Wait a quarter second for next try.
							$new_log_file = BackWPup_Option::get( $jobid, 'logfile', null, false );
							// Wait maximal 10 sec.
							if ( $i >= 40 ) {
								BackWPup_Admin::message(
									sprintf(
										/* translators: 1: Job name, 2: Information URL */
										__( 'Job "%1$s" has started, but not responded for 10 seconds. Please check <a href="%2$s">information</a>.', 'backwpup' ),
										esc_attr( BackWPup_Option::get( $jobid, 'name' ) ),
										network_admin_url( 'admin.php' ) . '?page=backwpupsettings#backwpup-tab-information'
									),
									true
								);
								break 2;
							}
							++$i;
						}
						BackWPup_Admin::message(
							sprintf(
								/* translators: %s: Job name */
								__( 'Job "%s" started.', 'backwpup' ),
								esc_attr( BackWPup_Option::get( $jobid, 'name' ) )
							)
						);
					}
				}
				break;

			case 'abort': // Abort job.
				if ( ! current_user_can( 'backwpup_jobs_start' ) ) {
					break;
				}
				check_admin_referer( 'abort-job' );
				if ( ! file_exists( BackWPup::get_plugin_data( 'running_file' ) ) ) {
					break;
				}
				// Abort.
				BackWPup_Job::user_abort();
				BackWPup_Admin::message( __( 'Job will be terminated.', 'backwpup' ) );
				break;

			default:
				do_action( 'backwpup_page_jobs_load', self::$listtable->current_action() );
				break;
		}

		self::$listtable->prepare_items();
	}

	/**
	 * Print admin styles
	 *
	 * @return void
	 */
	public static function admin_print_styles() {
		?>
		<style type="text/css" media="screen">

			.column-last, .column-next, .column-type, .column-dest {
				width: 15%;
			}

			#TB_ajaxContent {
				background-color: black;
				color: #c0c0c0;
			}

			#showworking {
				white-space:nowrap;
				display: block;
				width: 100%;
				font-family:monospace;
				font-size:12px;
				line-height:15px;
			}
			#runningjob {
				padding:10px;
				position:relative;
				margin: 15px 0 25px 0;
				padding-bottom:25px;
			}
			h2#runnigtitle {
				margin-bottom: 15px;
				padding: 0;
			}
			#warningsid, #errorid {
				margin-right: 10px;
			}

			.infobuttons {
				position: absolute;
				right: 10px;
				bottom: 0;
			}

			.progressbar {
				margin-top: 20px;
				height: auto;
				background: #f6f6f6 url('<?php echo esc_url( BackWPup::get_plugin_data( 'URL' ) ); ?>/assets/images/progressbarhg.jpg');
			}

			#lastmsg, #onstep, #lasterrormsg {
				text-align: center;
				margin-bottom: 20px;
			}
			#backwpup-page #lastmsg,
			#backwpup-page #onstep,
			#backwpup-page #lasterrormsg {
				font-family: "Open Sans", sans-serif;
			}
			.bwpu-progress {
				background-color: #1d94cf;
				color: #fff;
				padding: 5px 0;
				text-align: center;
			}
			#progresssteps {
				background-color: #007fb6;
			}

			.row-actions .lastlog {
				display: none;
			}

			@media screen and (max-width: 782px) {
				.column-type, .column-dest {
					display: none;
				}
				.row-actions .lastlog {
					display: inline-block;
				}
				.last-action-links {
					display: none;
				}
			}
		</style>
		<?php
	}

	/**
	 * Print admin scripts.
	 *
	 * @return void
	 */
	public static function admin_print_scripts() {
		wp_enqueue_script( 'backwpupgeneral' );

		$suffix              = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
		$plugin_url          = BackWPup::get_plugin_data( 'url' );
		$plugin_dir          = BackWPup::get_plugin_data( 'plugindir' );
		$plugin_scripts_url  = "{$plugin_url}/assets/js";
		$plugin_scripts_dir  = "{$plugin_dir}/assets/js";
		$shared_scripts_path = "{$plugin_url}/vendor/inpsyde/backwpup-shared/resources/js";
		$shared_scripts_dir  = "{$plugin_dir}/vendor/inpsyde/backwpup-shared/resources/js";

		wp_register_script(
			'backwpup_functions',
			"{$shared_scripts_path}/functions{$suffix}.js",
			[ 'underscore', 'jquery' ],
			BackWPup::get_plugin_data( 'Version' ),
			true
		);
		wp_register_script(
			'backwpup_states',
			"{$shared_scripts_path}/states{$suffix}.js",
			[
				'backwpup_functions',
			],
			BackWPup::get_plugin_data( 'Version' ),
			true
		);

		$dependencies = [
			'jquery',
			'underscore',
			'backwpupgeneral',
			'backwpup_functions',
			'backwpup_states',
		];
		if ( \BackWPup::is_pro() ) {
			$dependencies[] = 'decrypter';
		}
		wp_enqueue_script(
			'backwpup-backup-downloader',
			"{$plugin_scripts_url}/backup-downloader{$suffix}.js",
			$dependencies,
			BackWPup::get_plugin_data( 'Version' ),
			true
		);

		if ( \BackWPup::is_pro() ) {
			self::admin_print_pro_scripts( $suffix, $plugin_url, $plugin_dir );
		}
	}

	/**
	 * Print admin pro scripts.
	 *
	 * @param string $suffix Script suffix.
	 * @param string $plugin_url Plugin url.
	 * @param string $plugin_dir Plugin Directory.
	 *
	 * @return void
	 */
	private static function admin_print_pro_scripts( $suffix, $plugin_url, $plugin_dir ) {
		$restore_scripts_path = "{$plugin_url}/vendor/inpsyde/backwpup-restore-shared/resources/js";
		$restore_scripts_dir  = "{$plugin_dir}/vendor/inpsyde/backwpup-restore-shared/resources/js";

		wp_register_script(
			'decrypter',
			"{$restore_scripts_path}/decrypter{$suffix}.js",
			[
				'underscore',
				'jquery',
				'backwpup_states',
				'backwpup_functions',
			],
			BackWPup::get_plugin_data( 'Version' ),
			true
		);
	}

	/**
	 * Admin page assigned to admin menu item.
	 *
	 * @return void
	 */
	public static function page() {
		echo '<div class="wrap" id="backwpup-page">';
		// translators: %s: plugin name.
		echo '<h1>' . esc_html( sprintf( __( '%s &rsaquo; Legacy Jobs', 'backwpup' ), BackWPup::get_plugin_data( 'name' ) ) ) . '</h1>';
		BackWPup_Admin::display_messages();
		$job_object = BackWPup_Job::get_working_data();
		if ( current_user_can( 'backwpup_jobs_start' ) && is_object( $job_object ) ) {
			// Read existing logfile.
			$filesystem  = backwpup_wpfilesystem();
			$logfiledata = $filesystem->get_contents( $job_object->logfile );
			preg_match( '/<body[^>]*>/si', $logfiledata, $match );
			if ( ! empty( $match[0] ) ) {
				$startpos = strpos( $logfiledata, $match[0] ) + strlen( $match[0] );
			} else {
				$startpos = 0;
			}
			$endpos = stripos( $logfiledata, '</body>' );
			if ( empty( $endpos ) ) {
				$endpos = strlen( $logfiledata );
			}
			$length = strlen( $logfiledata ) - ( strlen( $logfiledata ) - $endpos ) - $startpos;
			?>
			<div id="runningjob">
				<div id="runniginfos">
				<?php // translators: %s: job name. ?>
					<h2 id="runningtitle"><?php esc_html( sprintf( __( 'Job currently running: %s', 'backwpup' ), $job_object->job['name'] ) ); ?></h2>
					<span id="warningsid"><?php esc_html_e( 'Warnings:', 'backwpup' ); ?> <span id="warnings"><?php echo absint( $job_object->warnings ); ?></span></span>
					<span id="errorid"><?php esc_html_e( 'Errors:', 'backwpup' ); ?> <span id="errors"><?php echo absint( $job_object->errors ); ?></span></span>
					<div class="infobuttons"><a href="#TB_inline?height=440&width=630&inlineId=tb-showworking" id="showworkingbutton" class="thickbox button button-primary button-primary-bwp" title="<?php esc_attr_e( 'Log of running job', 'backwpup' ); ?>"><?php esc_html_e( 'Display working log', 'backwpup' ); ?></a>
					<a href="<?php echo esc_url( wp_nonce_url( network_admin_url( 'admin.php' ) . '?page=backwpupjobs&action=abort', 'abort-job' ) ); ?>" id="abortbutton" class="backwpup-fancybox button button-bwp"><?php esc_html_e( 'Abort', 'backwpup' ); ?></a>
					<a href="#" id="showworkingclose" title="<?php esc_attr_e( 'Close working screen', 'backwpup' ); ?>" class="button button-bwp" style="display:none" ><?php esc_html_e( 'Close', 'backwpup' ); ?></a></div>
				</div>
				<input type="hidden" name="logpos" id="logpos" value="<?php echo esc_attr( strlen( $logfiledata ) ); ?>">
				<div id="lasterrormsg"></div>
				<div class="progressbar"><div id="progressstep" class="bwpu-progress" style="width:<?php echo esc_attr( (int) $job_object->step_percent ); ?>%;"><?php echo esc_html( $job_object->step_percent ); ?>%</div></div>
				<div id="onstep"><?php echo esc_html( $job_object->steps_data[ $job_object->step_working ]['NAME'] ); ?></div>
				<div class="progressbar"><div id="progresssteps" class="bwpu-progress" style="width:<?php echo esc_attr( (int) $job_object->substep_percent ); ?>%;"><?php echo esc_html( $job_object->substep_percent ); ?>%</div></div>
				<div id="lastmsg"><?php echo esc_html( $job_object->lastmsg ); ?></div>
				<div id="tb-showworking" style="display:none;">
					<div id="showworking"><?php echo wp_kses_post( substr( $logfiledata, $startpos, $length ) ); ?></div>
				</div>
			</div>
			<?php
		}

		// Display jobs table.
		?>
		<form id="posts-filter" action="" method="get">
		<input type="hidden" name="page" value="backwpupjobs" />
		<?php wp_nonce_field( self::LIST_NONCE_ACTION, '_wpnonce', false, false ); ?>
		<?php
		wp_nonce_field( 'backwpup_ajax_nonce', 'backwpupajaxnonce', false );
		self::$listtable->display();
		?>
		<div id="ajax-response"></div>
		</form>
		</div>

		<div id="tb_download_file" style="display: none;">
			<div id="tb_container">
				<p id="download-file-waiting">
					<?php esc_html_e( 'Please wait &hellip;', 'backwpup' ); ?>
				</p>
				<p id="download-file-success" style="display: none;">
					<?php
					esc_html_e(
						'Your download has been generated. It should begin downloading momentarily.',
						'backwpup'
					);
					?>
				</p>
				<div class="progressbar" style="display: none;">
					<div id="progresssteps" class="bwpu-progress" style="width:0%;">0%</div>
				</div>
				<?php
				if ( \BackWPup::is_pro() ) {
					$view = new ViewLoader();
					$view->decrypt_key_input();
				}
				?>
			</div>
		</div>

		<?php

		if ( ! empty( $job_object->logfile ) ) {
			?>
		<script type="text/javascript">
			//<![CDATA[.
			jQuery(document).ready(function ($) {
				backwpup_show_working = function () {
					var save_log_pos = 0;
					$.ajax({
						type: 'GET',
						url: ajaxurl,
						cache: false,
						data:{
							action: 'backwpup_working',
							logpos: $('#logpos').val(),
							logfile: '<?php echo esc_js( basename( (string) $job_object->logfile ) ); ?>',
							_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'backwpupworking_ajax_nonce' ) ); ?>'
						},
						dataType: 'json',
						success:function (rundata) {
							if ( rundata == 0 ) {
								$("#abortbutton").remove();
								$("#backwpup-adminbar-running").remove();
								$(".job-run").hide();
								$("#message").hide();
								$(".job-normal").show();
								$('#showworkingclose').show();
							}
							if (0 < rundata.log_pos) {
								$('#logpos').val(rundata.log_pos);
							}
							if ('' != rundata.log_text) {
								$('#showworking').append(rundata.log_text);
								$('#TB_ajaxContent').scrollTop(rundata.log_pos * 15);
							}
							if (0 < rundata.error_count) {
								$('#errors').replaceWith('<span id="errors">' + rundata.error_count + '</span>');
							}
							if (0 < rundata.warning_count) {
								$('#warnings').replaceWith('<span id="warnings">' + rundata.warning_count + '</span>');
							}
							if (0 < rundata.step_percent) {
								$('#progressstep').replaceWith('<div id="progressstep" class="bwpu-progress">' + rundata.step_percent + '%</div>');
								$('#progressstep').css('width', parseFloat(rundata.step_percent) + '%');
							}
							if (0 < rundata.sub_step_percent) {
								$('#progresssteps').replaceWith('<div id="progresssteps" class="bwpu-progress">' + rundata.sub_step_percent + '%</div>');
								$('#progresssteps').css('width', parseFloat(rundata.sub_step_percent) + '%');
							}
							if (0 < rundata.running_time) {
								$('#runtime').replaceWith('<span id="runtime">' + rundata.running_time + '</span>');
							}
							if ( '' != rundata.onstep ) {
								$('#onstep').replaceWith('<div id="onstep">' + rundata.on_step + '</div>');
							}
							if ( '' != rundata.last_msg ) {
								$('#lastmsg').replaceWith('<div id="lastmsg">' + rundata.last_msg + '</div>');
							}
							if ( '' != rundata.last_error_msg ) {
								$('#lasterrormsg').replaceWith('<div id="lasterrormsg">' + rundata.last_error_msg + '</div>');
							}
							if ( rundata.job_done == 1 ) {
								$("#abortbutton").remove();
								$("#backwpup-adminbar-running").remove();
								$(".job-run").hide();
								$("#message").hide();
								$(".job-normal").show();
								$('#showworkingclose').show();
							} else {
								if ( rundata.restart_url !== '' ) {
									backwpup_trigger_cron( rundata.restart_url );
								}
								setTimeout('backwpup_show_working()', 750);
							}
						},
						error:function( ) {
							setTimeout('backwpup_show_working()', 750);
						}
					});
				};
				backwpup_trigger_cron = function ( cron_url ) {
					$.ajax({
						type: 'POST',
						url: cron_url,
						dataType: 'text',
						cache: false,
						processData: false,
						timeout: 1
					});
				};
				backwpup_show_working();
				$('#showworkingclose').on('click',  function() {
					$("#runningjob").hide( 'slow' );
					return false;
				});
			});
			//]]>.
		</script>
			<?php
		}
	}

	/**
	 * Function to generate json data.
	 *
	 * @throws Exception To check capability and nonce verification.
	 */
	public static function ajax_working() {

		$allowed_caps = [
			'backwpup_jobs',
			'backwpup_jobs_start',
			'backwpup_backups',
			'manage_options', // admins fallback.
		];

		// Check if the user has at least one of the listed capabilities.
		$can_access = array_reduce( $allowed_caps, fn( $carry, $cap ) => $carry || current_user_can( $cap ), false );

		if ( ! $can_access ) {
			http_response_code( 403 );
			echo wp_json_encode(
			[
				'success' => false,
				'data'    => [
					'message'  => __( 'You do not have sufficient permissions to access this function.', 'backwpup' ),
					'job_done' => 1,
				],
			]
			);
			throw new Exception( 'ajax_exit' );
		}

		// Nonce verification.
		if ( ! check_ajax_referer( 'backwpupworking_ajax_nonce', '_ajax_nonce', false ) ) {
			http_response_code( 401 );
			echo wp_json_encode(
			[
				'success' => false,
				'data'    => [
					'message'  => __( 'Security check failed. Please refresh the page and try again.', 'backwpup' ),
					'job_done' => 1,
				],
			]
			);
			throw new Exception( 'ajax_exit' );
		}

		$log_folder = get_site_option( 'backwpup_cfg_logfolder' );
		$log_folder = BackWPup_File::get_absolute_path( $log_folder );

		$job_object = BackWPup_Job::get_working_data();
		$logfile    = basename( (string) $job_object->logfile );
		try {
			$logfile_param = filter_input( INPUT_GET, 'logfile', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
			$logfile_param = $logfile_param ? sanitize_text_field( $logfile_param ) : $logfile;
			$logfile       = self::get_logfile_path( $log_folder, $logfile_param );
		} catch ( \InvalidArgumentException $e ) {
			// Logfile either not passed or is invalid.
			echo 'error';
			exit( 0 );
		}

		$logpos      = absint( filter_input( INPUT_GET, 'logpos', FILTER_SANITIZE_NUMBER_INT ) );
		$restart_url = '';

		// Check if logfile renamed.
		if ( file_exists( $logfile . '.gz' ) ) {
			$logfile .= '.gz';
		}

		if ( ! is_readable( $logfile ) ) {
			exit( '0' );
		}

		$job_object = BackWPup_Job::get_working_data();
		$done       = 0;
		if ( is_object( $job_object ) ) {
			$warnings        = $job_object->warnings;
			$errors          = $job_object->errors;
			$step_percent    = $job_object->step_percent;
			$substep_percent = $job_object->substep_percent;
			$runtime         = current_time( 'timestamp' ) - $job_object->start_time; // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
			$onstep          = $job_object->steps_data[ $job_object->step_working ]['NAME'];
			$lastmsg         = $job_object->lastmsg;
			$lasterrormsg    = $job_object->lasterrormsg;
			$step_done       = count( $job_object->steps_done );
			$step_todo       = count( $job_object->steps_todo );
			$substeps_todo   = $job_object->substeps_todo;
			$substeps_done   = $job_object->substeps_done;
		} else {
			$logheader       = BackWPup_Job::read_logheader( $logfile );
			$warnings        = $logheader['warnings'];
			$runtime         = $logheader['runtime'];
			$errors          = $logheader['errors'];
			$step_percent    = 100;
			$substep_percent = 100;
			$step_done       = 100;
			$step_todo       = 100;
			$substeps_todo   = 100;
			$substeps_done   = 100;
			$onstep          = esc_html__( 'Job completed', 'backwpup' );
			if ( $errors > 0 ) {
				// Translators: %s is duration in seconds.
				$lastmsg = '<div class="bwu-message-error"><p>' . esc_html__( 'ERROR:', 'backwpup' ) . ' ' . sprintf( esc_html__( 'Job has ended with errors in %s seconds. You must resolve the errors for correct execution.', 'backwpup' ), $logheader['runtime'] ) . '</p></div>';
			} elseif ( $warnings > 0 ) {
				// Translators: %s is duration in seconds.
				$lastmsg = '<div class="backwpup-message backwpup-warning"><p>' . esc_html__( 'WARNING:', 'backwpup' ) . ' ' . sprintf( esc_html__( 'backup created with warnings in %s seconds. Please resolve them for correct execution.', 'backwpup' ), $logheader['runtime'] ) . '</p></div>';
			} else {
				// Translators: %s is duration in seconds.
				$lastmsg = sprintf( esc_html__( 'Backup created in %s seconds.', 'backwpup' ), $logheader['runtime'] );
			}
			$lasterrormsg = '';
			$done         = 1;
		}

			$filesystem = backwpup_wpfilesystem();
		if ( '.gz' === substr( $logfile, -3 ) ) {
			$logfiledata = $filesystem->get_contents( 'compress.zlib://' . $logfile );
		} else {
			$logfiledata = $filesystem->get_contents( $logfile );
		}
		if ( $logpos > 0 ) {
			$logfiledata = substr( $logfiledata, $logpos );
		}

		preg_match( '/<body[^>]*>/si', $logfiledata, $match );
		if ( ! empty( $match[0] ) ) {
			$startpos = strpos( $logfiledata, $match[0] ) + strlen( $match[0] );
		} else {
			$startpos = 0;
		}

		$endpos = stripos( $logfiledata, '</body>' );
		if ( false === $endpos ) {
			$endpos = strlen( $logfiledata );
		}

		$length = strlen( $logfiledata ) - ( strlen( $logfiledata ) - $endpos ) - $startpos;

		// Check if restart must done on ALTERNATE_WP_CRON.
		if ( is_object( $job_object ) && defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) {
			$restart = BackWPup_Job::get_jobrun_url( 'restartalt' );
			if ( 0 === $job_object->pid && '' === $job_object->uniqid ) {
				$restart_url = $restart['url'];
			}
			$last_update = microtime( true ) - $job_object->timestamp_last_update;
			if ( empty( $job_object->pid ) && $last_update > 10 ) {
				$restart_url = $restart['url'];
			}
		}

		$data_ro_return = [
			'log_pos'          => strlen( $logfiledata ) + $logpos,
			'log_text'         => substr( $logfiledata, $startpos, $length ),
			'warning_count'    => $warnings,
			'error_count'      => $errors,
			'running_time'     => $runtime,
			'step_percent'     => $step_percent,
			'on_step'          => $onstep,
			'last_msg'         => $lastmsg,
			'last_error_msg'   => $lasterrormsg,
			'sub_step_percent' => $substep_percent,
			'restart_url'      => $restart_url,
			'job_done'         => $done,
			'step_done'        => $step_done,
			'step_todo'        => $step_todo,
			'substeps_todo'    => $substeps_todo,
			'substeps_done'    => $substeps_done,
			'job_id'           => $job_object->job['jobid'],
		];

		wp_send_json( $data_ro_return );
	}

	/**
	 * Get log file path.
	 *
	 * @param string      $folder Folder Path.
	 * @param string|null $filename Filename.
	 * @return string
	 *
	 * @throws \InvalidArgumentException When empty filename.
	 */
	private static function get_logfile_path( string $folder, ?string $filename ): string {
		if ( ! $filename ) {
			throw new \InvalidArgumentException( 'Log file cannot be null.' );
		}

		$filename = basename( trim( $filename ) );

		if (
			preg_match( '/^[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{1,5}$/', $filename ) === 0
			|| strpos( $filename, 'backwpup_log_' ) === false
		) {
			throw new \InvalidArgumentException( 'Invalidly formatted log filename passed.' );
		}

		return $folder . $filename;
	}

	/**
	 * Render extra controls in the table navigation area (above or below the table).
	 *
	 * @param string $which The location of the extra table nav markup: 'top' or 'bottom'.
	 */
	protected function extra_tablenav( $which ) {
		if ( 'top' !== $which ) { // Add the filter only at the top of the table.
			return;
		}
		// Get the current "status" filter from the GET request.
		$current_status_filter = '';
		$nonce                 = $this->get_list_nonce();
		if ( $nonce && wp_verify_nonce( $nonce, self::LIST_NONCE_ACTION ) ) {
			$current_status_filter = filter_input( INPUT_GET, 'status', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
			$current_status_filter = $current_status_filter ? sanitize_text_field( $current_status_filter ) : '';
		}
		?>
		<div class="alignleft actions">
			<label for="filter-by-status" class="screen-reader-text">
				<?php esc_html_e( 'Filter by Status', 'backwpup' ); ?>
			</label>
			<select name="status" id="filter-by-status" onchange="this.form.submit();">
				<option value=""><?php esc_html_e( 'All Statuses', 'backwpup' ); ?></option>
				<option value="disabled" <?php selected( $current_status_filter, 'disabled' ); ?>>
					<?php esc_html_e( 'Disabled', 'backwpup' ); ?>
				</option>
			</select>
			<noscript>
				<button type="submit" class="button"><?php esc_html_e( 'Filter', 'backwpup' ); ?></button>
			</noscript>
		</div>
		<?php
	}

	/**
	 * Ensure the list view has a valid nonce in the URL.
	 *
	 * @return void
	 */
	private static function ensure_list_nonce() {
		$nonce = filter_input( INPUT_GET, '_wpnonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
		if ( null === $nonce && isset( $_GET['_wpnonce'] ) ) {
			$nonce = sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) );
		} else {
			$nonce = $nonce ? sanitize_text_field( $nonce ) : '';
		}

		if ( $nonce && wp_verify_nonce( $nonce, self::LIST_NONCE_ACTION ) ) {
			return;
		}

		$base_url    = is_network_admin() ? network_admin_url( 'admin.php?page=backwpupjobs' ) : admin_url( 'admin.php?page=backwpupjobs' );
		$current_url = add_query_arg( $_GET, $base_url );
		$current_url = remove_query_arg( [ '_wpnonce', '_wp_http_referer' ], $current_url );
		$current_url = add_query_arg( '_wpnonce', wp_create_nonce( self::LIST_NONCE_ACTION ), $current_url );

		wp_safe_redirect( $current_url );
		exit;
	}

	/**
	 * Check if the list nonce is valid.
	 *
	 * @return bool
	 */
	private function get_list_nonce() {
		$nonce = filter_input( INPUT_GET, '_wpnonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
		if ( null === $nonce && isset( $_GET['_wpnonce'] ) ) {
			$nonce = sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) );
		} else {
			$nonce = $nonce ? sanitize_text_field( $nonce ) : '';
		}
		if ( ! $nonce || ! wp_verify_nonce( $nonce, self::LIST_NONCE_ACTION ) ) {
			return '';
		}

		return (string) $nonce;
	}
}
