# sellkit/trunk/includes/admin/components/class-list-table.php

SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster, version trunk. 582 lines.

- Page: https://pluginprobe.com/plugins/sellkit/trunk/code/includes/admin/components/class-list-table.php
- Raw: https://pluginprobe.com/plugins/sellkit/trunk/raw/includes/admin/components/class-list-table.php
- Modified: 2026-04-29T03:49:52+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/sellkit/trunk/code/includes/admin/components/class-list-table.php#L10-L20`.

```php
<?php

defined( 'ABSPATH' ) || die();

use Elementor\Plugin;
use Sellkit\Admin\Funnel\Importer\Ajax_Handler;
use Sellkit\Global_Checkout\Checkout;

/**
 * List Table component class.
 *
 * @since 1.1.0
 * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
 */
class Sellkit_Admin_List_Table {

	/**
	 * Class instance.
	 *
	 * @since 1.1.0
	 * @var Sellkit_Admin_List_Table
	 */
	private static $instance = null;

	/**
	 * Get a class instance.
	 *
	 * @since 1.1.0
	 *
	 * @return Sellkit_Admin_List_Table Class instance.
	 */
	public static function get_instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Class constructor.
	 *
	 * @since 1.1.0
	 */
	public function __construct() {
		add_action( 'wp_ajax_sellkit_list_table_posts', [ $this, 'get_posts' ] );
		add_action( 'wp_ajax_sellkit_list_table_post', [ $this, 'handle_post' ] );
	}

	/**
	 * Get posts.
	 *
	 * @since 1.1.0
	 */
	public function get_posts() {
		check_ajax_referer( 'sellkit', 'nonce' );

		// Sanitize.
		$post_type      = sellkit_htmlspecialchars( INPUT_POST, 'post_type' );
		$paged          = filter_input( INPUT_POST, 'paged', FILTER_SANITIZE_NUMBER_INT );
		$posts_per_page = filter_input( INPUT_POST, 'posts_per_page', FILTER_SANITIZE_NUMBER_INT );
		$search_args    = filter_input( INPUT_POST, 'args', FILTER_DEFAULT, FILTER_FORCE_ARRAY );
		$page           = filter_input( INPUT_POST, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
		$list_status    = isset( $_POST['list_status'] ) ? sanitize_text_field( wp_unslash( $_POST['list_status'] ) ) : 'all';
		if ( ! in_array( $list_status, [ 'all', 'publish', 'draft' ], true ) ) {
			$list_status = 'all';
		}

		/**
		 * Filter List Table query arguments.
		 *
		 * @since 1.1.0
		 *
		 * @param array $args The query arguments.
		 */
		$args = apply_filters( "sellkit_list_table_{$post_type}_args", [
			'post_type' => $post_type,
			'paged' => $paged,
			'posts_per_page' => ! empty( $posts_per_page ) ? $posts_per_page : 20,
			'orderby' => 'ID',
			'order' => 'DESC',
		] );

		if ( is_array( $search_args ) && ! empty( $search_args ) ) {
			foreach ( $search_args as $search_arg ) {
				$args[ $search_arg['param'] ] = $search_arg['value'];
			}
		}

		$not_in = $this->get_list_table_post__not_in( $post_type, $page );
		if ( ! empty( $not_in ) ) {
			// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Exclude global checkout funnel from list.
			$args['post__not_in'] = $not_in;
		}

		if ( 'publish' === $list_status ) {
			$args['post_status'] = 'publish';
		} elseif ( 'draft' === $list_status ) {
			$args['post_status'] = 'draft';
		} else {
			$args['post_status'] = [ 'publish', 'draft', 'pending', 'private', 'future' ];
		}

		// Query.
		$query = new \WP_Query( $args );

		/**
		 * Filter List Table query posts.
		 *
		 * @since 1.1.0
		 *
		 * @param array $args The taxonomy arguments.
		 */
		$posts = apply_filters( "sellkit_list_table_{$post_type}_posts", $query->posts );
		$posts = $this->prepare_posts_for_list_table( $posts );

		/**
		 * Filter List Table columns.
		 *
		 * @since 1.1.0
		 *
		 * @param array $args The columns headings and values.
		 */
		$columns = apply_filters( "sellkit_list_table_{$post_type}_columns", [
			'labels' => [],
			'values' => [],
		], $posts );

		$list_counts = $this->get_list_status_counts( $post_type, $page );

		// Send response.
		wp_send_json_success( [
			'posts' => $posts,
			'max_num_pages' => $query->max_num_pages,
			'columns' => $columns,
			'list_counts' => $list_counts,
		] );
	}

	/**
	 * Post IDs excluded from list queries (e.g. global checkout funnel).
	 *
	 * @since 2.4.0
	 *
	 * @param string $post_type Post type.
	 * @param string $page      Admin page slug context.
	 * @return int[]
	 */
	private function get_list_table_post__not_in( $post_type, $page ) {
		if ( 'sellkit-funnels' === $post_type && 'checkout' !== $page ) {
			$global_checkout_id = (int) get_option( Checkout::SELLKIT_GLOBAL_CHECKOUT_OPTION, 0 );
			if ( $global_checkout_id > 0 ) {
				return [ $global_checkout_id ];
			}
		}
		return [];
	}

	/**
	 * Counts for All / Active / Inactive list filters (respects post__not_in).
	 *
	 * @since 2.4.0
	 *
	 * @param string $post_type Post type.
	 * @param string $page      Admin page slug context.
	 * @return array{all:int,publish:int,draft:int}
	 */
	private function get_list_status_counts( $post_type, $page ) {
		$not_in = $this->get_list_table_post__not_in( $post_type, $page );
		$base   = [
			'post_type'      => $post_type,
			'posts_per_page' => -1,
			'fields'         => 'ids',
			'no_found_rows'  => false,
		];
		if ( ! empty( $not_in ) ) {
			// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in
			$base['post__not_in'] = $not_in;
		}

		$statuses_all = [ 'publish', 'draft', 'pending', 'private', 'future' ];
		$q_all        = new \WP_Query( array_merge( $base, [ 'post_status' => $statuses_all ] ) );
		$q_pub        = new \WP_Query( array_merge( $base, [ 'post_status' => 'publish' ] ) );
		$q_dra        = new \WP_Query( array_merge( $base, [ 'post_status' => 'draft' ] ) );

		return [
			'all'     => (int) $q_all->found_posts,
			'publish' => (int) $q_pub->found_posts,
			'draft'   => (int) $q_dra->found_posts,
		];
	}

	/**
	 * Add formatted published / modified dates for list table rows (JSON).
	 *
	 * @since 2.4.0
	 *
	 * @param array $posts Posts from WP_Query.
	 * @return array
	 */
	private function prepare_posts_for_list_table( $posts ) {
		if ( empty( $posts ) || ! is_array( $posts ) ) {
			return $posts;
		}

		$date_time_format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );

		foreach ( $posts as $key => $post ) {
			if ( ! $post instanceof \WP_Post ) {
				continue;
			}

			$row = array_merge(
				$post->to_array(),
				[
					'custom_date'                    => get_the_date( 'M d, Y', $post ),
					'custom_date_with_time'          => mysql2date( $date_time_format, $post->post_date ),
					'custom_modified_date'           => get_the_modified_date( 'M d, Y', $post ),
					'custom_modified_date_with_time' => mysql2date( $date_time_format, $post->post_modified ),
				]
			);

			$posts[ $key ] = (object) $row;
		}

		return $posts;
	}

	/**
	 * Handle post actions.
	 *
	 * @since 1.1.0
	 */
	public function handle_post() {
		check_ajax_referer( 'sellkit', 'nonce' );

		// Sanitize.
		$post       = filter_input( INPUT_POST, 'post', FILTER_DEFAULT, FILTER_FORCE_ARRAY );
		$sub_action = sellkit_htmlspecialchars( INPUT_POST, 'sub_action' );

		if ( is_array( $post ) && count( $post ) < 3 ) {
			$post = (array) get_post( $post['ID'] );
		}

		call_user_func( [ $this, "handle_post_{$sub_action}" ], $post );
	}

	/**
	 * Handle post toggle status.
	 *
	 * @since 1.1.0
	 * @param array $post Post Array.
	 */
	private function handle_post_toggle_status( $post ) {
		// Format proper post status.
		$post_status = ( 'publish' === $post['post_status'] ) ? 'draft' : 'publish';

		// Update post status.
		$result = wp_update_post( [
			'ID' => $post['ID'],
			'post_status' => $post_status,
		], true );

		do_action( "sellkit_after_toggle_status_{$post['post_type']}", $post['ID'], $post_status );

		// Handle error.
		if ( is_wp_error( $result ) ) {
			wp_send_json_error( $result );
		}

		// Send success response.
		wp_send_json_success( $result );
	}

	/**
	 * Duplicate post.
	 *
	 * @since 1.1.0
	 * @param array $post Post Array.
	 */
	private function handle_post_duplicate_post( $post ) {
		$main_post_id = $post['ID'];

		unset( $post['ID'] );

		$post['post_title']  = sprintf( '%1s - %2s', esc_html( $post['post_title'] ), esc_html__( 'Clone', 'sellkit' ) );
		$post['post_status'] = 'draft';

		$post_id = wp_insert_post( $post );

		$main_post_meta = get_post_custom( $main_post_id );

		foreach ( $main_post_meta as $key => $value ) {
			update_post_meta( $post_id, $key, maybe_unserialize( $value[0] ) );
		}

		// Handle error.
		if ( is_wp_error( $post_id ) ) {
			wp_send_json_error( $post_id );
		}

		// Send success response.
		wp_send_json_success( $post_id );
	}

	/**
	 * Handle post remove.
	 *
	 * @since 1.1.0
	 * @param array $post Post array.
	 */
	private function handle_post_remove( $post ) {
		do_action( 'sellkit_funnels_after_delete_posts_' . $post['post_type'], $post );
		// Delete post.
		$result = wp_delete_post( $post['ID'] );

		// Handle error.
		if ( ! is_object( $result ) ) {
			wp_send_json_error( $result );
		}

		// Send success response.
		wp_send_json_success( $result );
	}

	/**
	 * Duplicate funnel item.
	 *
	 * @since 1.5.0
	 * @param array $post Post Array.
	 */
	private function handle_post_duplicate_single_funnel( $post ) {
		$post_id      = $post['ID'];
		$parent_title = sprintf( '%1s - %2s', esc_html( $post['post_title'] ), esc_html__( 'Clone', 'sellkit' ) );
		$funnel_data  = get_post_meta( $post_id, 'nodes', true );
		$nodes        = true;

		if ( empty( $funnel_data ) ) {
			$funnel_data = get_post_meta( $post_id, 'sellkit_steps', true );
			$nodes       = false;
		}

		$funnel_slug = get_post_meta( $post_id, 'sellkit_post_slug', true );

		// Let's create parent funnel post first.
		$new_funnel_id = wp_insert_post( [
			'post_title'  => $parent_title,
			'post_status' => 'draft',
			'post_type'   => 'sellkit-funnels',
			'meta_input'   => [
				'sellkit_post_slug' => $funnel_slug,
			],
		] );

		// Loop through old parent data and create steps posts of new funnel.
		foreach ( $funnel_data as $key => $funnel_item ) {
			$old_id = $funnel_item['page_id'];

			// Create steps.
			$title = $funnel_item['title'];
			$args  = [
				'post_type'   => 'sellkit_step',
				'post_title'  => esc_html( $title ),
				'post_status' => get_post_status( $old_id ),
			];

			$args = $this->handle_post_content( $args, $old_id );

			$new_id = wp_insert_post( $args );

			// Update new parent funnel data with new steps id.
			$funnel_data[ $key ]['page_id']   = $new_id;
			$funnel_data[ $key ]['funnel_id'] = $new_funnel_id;

			// Add old steps meta data to new steps.
			$old_data = get_post_meta( $old_id );

			foreach ( $old_data as $key => $data ) {
				if ( 'step_data' === $key ) {
					$data[0] = str_replace( $post_id, $new_funnel_id, $data[0] );
				}

				if ( '_elementor_data' === $key && '[' === substr( $data[0], 0, 1 ) ) {
					update_post_meta( $new_id, $key, maybe_unserialize( wp_slash( $data[0] ) ) );

					continue;
				}

				update_post_meta( $new_id, $key, maybe_unserialize( $data[0] ) );
			}

			if ( class_exists( 'Elementor\Plugin' ) ) {
				// Also save post in elementor way. after all it's elementor post.
				$document = Plugin::$instance->documents->get( $new_id );
				$document->save( [] );
			}
		}

		// At the end update parent main meta.
		if ( false === $nodes ) {
			update_post_meta( $new_funnel_id, 'sellkit_steps', $funnel_data );
		} else {
			update_post_meta( $new_funnel_id, 'nodes', $funnel_data );
		}

		wp_send_json_success();
	}

	/**
	 * Handle post content for templates without elementor.
	 *
	 * @param array   $args   funnel nodes arguments.
	 * @param integer $old_id node id.
	 * @since 1.5.0
	 * @return array
	 */
	private function handle_post_content( $args, $old_id ) {
		if ( empty( $old_id ) ) {
			return $args;
		}

		$is_elementor = get_post_meta( $old_id, '_elementor_data', true );

		if ( ! empty( $is_elementor ) ) {
			return $args;
		}

		$post_content = get_post( $old_id );
		$content      = $post_content->post_content;

		return array_merge( $args, [ 'post_content' => $content ] );
	}

	/**
	 * Export single funnel.
	 *
	 * @since 1.5.0
	 * @param array $post Post Array.
	 */
	private function export_single_funnel( $post ) {
		$post_id                         = $post['ID'];
		$data['funnel']['id']            = $post_id;
		$data['funnel']['name']          = $post['post_name'];
		$data['funnel']['title']         = $post['post_title'];
		$data['funnel']['post_type']     = $post['post_type'];
		$data['funnel']['post_status']   = $post['post_status'];
		$data['funnel']['sellkit_steps'] = get_post_meta( $post_id, 'nodes', true );

		// Integration with flowchart.
		if ( empty( $data['funnel']['sellkit_steps'] ) ) {
			$data['funnel']['sellkit_steps'] = get_post_meta( $post_id, 'sellkit_steps', true );
		}

		// Check for steps.
		if ( ! is_array( $data['funnel']['sellkit_steps'] ) ) {
			return $data;
		}

		foreach ( $data['funnel']['sellkit_steps'] as $step ) {
			if ( ! array_key_exists( 'page_id', $step ) ) {
				continue;
			}

			$id          = $step['page_id'];
			$post        = get_post( $id );
			$export_data = get_post_meta( $id );

			$data['step'][ $id ]['ID']           = $post->ID;
			$data['step'][ $id ]['name']         = $post->post_name;
			$data['step'][ $id ]['title']        = $post->post_title;
			$data['step'][ $id ]['post_type']    = $post->post_type;
			$data['step'][ $id ]['post_status']  = $post->post_status;
			$data['step'][ $id ]['post_content'] = $post->post_content;
			$data['step'][ $id ]['page_id']      = $id;

			foreach ( $export_data as $key => $meta ) {
				if ( '_elementor_data' === $key && defined( 'ELEMENTOR_VERSION' ) ) {
					$document      = Plugin::$instance->documents->get( $id );
					$template_data = $document->get_export_data();

					$data['step'][ $id ]['meta'][ $key ] = wp_json_encode( $template_data['content'] );
					continue;
				}

				$data['step'][ $id ]['meta'][ $key ] = $meta[0];
			}
		}

		return $data;
	}

	/**
	 * Handle exporting single funnel by ajax.
	 *
	 * @since 1.5.0
	 * @param array $post Post Array.
	 */
	private function handle_post_export_single_funnel( $post ) {
		$data         = [];
		$data['type'] = 'single';
		$data['data'] = $this->export_single_funnel( $post );

		wp_send_json_success( $data );
	}

	/**
	 * Handle exporting single funnel by ajax.
	 *
	 * @since 1.5.0
	 */
	private function handle_post_export_all_funnel() {
		$data         = [];
		$data['type'] = 'multi';

		$args = [
			'post_type'      => 'sellkit-funnels',
			'posts_per_page' => -1,
		];

		$posts = new \WP_Query( $args );
		$posts = $posts->posts;

		if ( empty( $posts ) ) {
			wp_send_json_error();
		}

		$global_checkout_id = intval( get_option( Checkout::SELLKIT_GLOBAL_CHECKOUT_OPTION, 0 ) );

		foreach ( $posts as $post ) {
			if ( $post->ID === $global_checkout_id ) {
				continue;
			}

			$post = (array) $post;

			$data['data'][] = $this->export_single_funnel( $post );
		}

		wp_send_json_success( $data );
	}

	/**
	 * Handle import funnel.
	 *
	 * @since 1.5.0
	 */
	private function handle_post_import_funnel() {
		$json = filter_input( INPUT_POST, 'funnel', FILTER_DEFAULT );
		$page = filter_input( INPUT_POST, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
		$data = json_decode( $json );

		if ( 'multi' === $data->type && 'checkout' === $page ) {
			wp_send_json_error( esc_html__( 'You can not import multiple funnels to the global checkout.', 'sellkit' ) );
		}

		$ajax_handler = new Ajax_Handler();

		if ( 'single' === $data->type ) {
			$funnel_data = $data->data->funnel;
			$steps_data  = $data->data->step;
			$funnel_id   = $ajax_handler->import_funnel_by_data( $funnel_data, $steps_data );
		}

		if ( 'multi' === $data->type ) {
			foreach ( $data->data as $item ) {
				$funnel_data = $item->funnel;
				$steps_data  = $item->step;
				$funnel_id   = $ajax_handler->import_funnel_by_data( $funnel_data, $steps_data );
			}
		}

		if ( 'checkout' === $page && (int) $funnel_id > 0 ) {
			update_option( Checkout::SELLKIT_GLOBAL_CHECKOUT_OPTION, $funnel_id );
		}

		wp_send_json_success();
	}
}

Sellkit_Admin_List_Table::get_instance();

```
