<?php
/**
 * Extends WP_List_Table to display the stock management table
 *
 * @package         Atum\Components
 * @subpackage      AtumListTables
 * @author          BE REBEL - https://berebel.studio
 * @copyright       ©2026 Stock Management Labs™
 *
 * @since           0.0.1
 */

namespace Atum\Components\AtumListTables;

defined( 'ABSPATH' ) || die;

use Atum\Components\AtumCache;
use Atum\Components\AtumCapabilities;
use Atum\Components\AtumHelpGuide;
use Atum\Components\AtumMarketingPopup;
use Atum\Inc\Globals;
use Atum\Inc\Helpers;
use Atum\Models\Interfaces\AtumProductInterface;
use Atum\Modules\ModuleManager;
use Atum\Settings\Settings;
use Atum\Suppliers\Suppliers;
use AtumLevels\Levels\Interfaces\BOMProductInterface;

if ( ! class_exists( '\WP_List_Table' ) ) {
	require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}

abstract class AtumListTable extends \WP_List_Table {

	/**
	 * The list ID
	 *
	 * @var string
	 */
	protected $id = '';

	/**
	 * The post type used to build the table (WooCommerce product)
	 *
	 * @var string
	 */
	protected $post_type = 'product';

	/**
	 * Current item being listed
	 *
	 * @var \WC_Product|\WC_Product_Variation|AtumProductInterface|BOMProductInterface
	 */
	protected $list_item;

	/**
	 * The table columns
	 *
	 * @var array
	 */
	protected static $table_columns;

	/**
	 * The columns that are hidden by default
	 *
	 * @var array
	 */
	protected static $default_hidden_columns = [];

	/**
	 * Which columns are numeric and searchable? and strings? append to this two keys
	 *
	 * @var array
	 */
	protected $searchable_columns = [];

	/**
	 * Set up the ATUM columns and types for correct sorting
	 *
	 * @var array
	 */
	protected $atum_sortable_columns = [];

	/**
	 * The previously selected items
	 *
	 * @var array
	 */
	protected $selected = [];

	/**
	 * Array of product IDs that are excluded from the list
	 *
	 * @var array
	 */
	protected $excluded = [];

	/**
	 * Group title columns
	 *
	 * @var array
	 */
	protected $group_columns = [];

	/**
	 * Group members
	 *
	 * @var array
	 */
	protected $group_members = [];

	/**
	 * The array of container products
	 *
	 * @var array
	 */
	protected $container_products = array(
		'variable'                  => [],
		'all_variable'              => [],
		'grouped'                   => [],
		'all_grouped'               => [],
		'variable_subscription'     => [],
		'all_variable_subscription' => [],
		'bundle'                    => [],
		'all_bundle'                => [],
	);

	/**
	 * Store parent type when in an inheritable sub-loop
	 *
	 * @var string
	 */
	protected $parent_type = '';

	/**
	 * The array of IDs of children products
	 *
	 * @var array
	 */
	protected $children_products = [];

	/**
	 * Elements per page (in order to obviate option default)
	 *
	 * @var int
	 */
	protected $per_page;

	/**
	 * Array with the id's of the products in current page
	 *
	 * @var array
	 */
	protected $current_products = [];

	/**
	 * Used to include product variations in the Supplier filterings
	 *
	 * @var array
	 */
	protected $supplier_variation_products = [];

	/**
	 * Taxonomies to filter by
	 *
	 * @var array
	 */
	protected $taxonomies = [];

	/**
	 * Extra meta args for the list query
	 *
	 * @var array
	 */
	protected $extra_meta = [];

	/**
	 * The ATUM product data used in WP_Query
	 *
	 * @var array
	 */
	protected $atum_query_data = [];

	/**
	 * IDs for views
	 *
	 * @var array
	 */
	protected $id_views = array(
		'in_stock'       => [],
		'out_stock'      => [],
		'back_order'     => [],
		'restock_status' => [],
		'unmanaged'      => [],
	);

	/**
	 * Counters for views
	 *
	 * @var array
	 */
	protected $count_views = array(
		'count_in_stock'       => 0,
		'count_out_stock'      => 0,
		'count_back_order'     => 0,
		'count_restock_status' => 0,
		'count_unmanaged'      => 0,
		'count_all'            => 0,
	);

	/**
	 * Days to re-order from settings
	 *
	 * @var int
	 */
	protected $days_to_reorder;

	/**
	 * Time of query
	 *
	 * @var string
	 */
	protected $day;

	/**
	 * Number of days for Sold Last Days calculations
	 *
	 * @var int
	 */
	protected static $sale_days;

	/**
	 * Whether the currently displayed product is an expandable child product
	 *
	 * @var bool
	 */
	protected $is_child = FALSE;

	/**
	 * Whether the current product should do the calculations for the columns
	 *
	 * @var bool
	 */
	public $allow_calcs = TRUE;

	/**
	 * Default currency symbol
	 *
	 * @var string
	 */
	protected static $default_currency;

	/**
	 * The user meta key used for first edit popup
	 *
	 * @var string
	 */
	protected $first_edit_key;

	/**
	 * Show the checkboxes in table rows
	 *
	 * @var bool
	 */
	protected $show_cb = FALSE;

	/**
	 * Whether to show products controlled by ATUM or not
	 *
	 * @var bool
	 */
	protected $show_controlled = TRUE;

	/**
	 * Columns that allow totalizers with their totals
	 *
	 * @var array
	 */
	protected $totalizers = [];

	/**
	 * Whether to show the totals row
	 *
	 * @var bool
	 */
	protected $show_totals = TRUE;

	/**
	 * Whether the current list query has a filter applied
	 *
	 * @var bool
	 */
	protected $is_filtering = FALSE;

	/**
	 * Filters being applied to the current query
	 *
	 * @var array
	 */
	protected $query_filters = [];

	/**
	 * Counter for the table rows
	 *
	 * @var int
	 */
	protected $row_count = 0;

	/**
	 * Whether to show or not the unmanaged counters
	 *
	 * @var bool
	 */
	protected $show_unmanaged_counters;

	/**
	 * The WC option where is stored whether to notify the customer when the
	 * out of stock thresholsd is reached
	 *
	 * @var string
	 */
	protected $woocommerce_notify_no_stock_amount;

	/**
	 * The columns that will be sticky
	 *
	 * @var array
	 */
	protected $sticky_columns = [];

	/**
	 * Report table flag
	 *
	 * @var bool
	 */
	protected static $is_report = FALSE;

	/**
	 * A list of available actions for the "Actions" column (if any).
	 *
	 * @var array
	 */
	protected static $row_actions = [];

	/**
	 * Whether the current List Table allow editing cells or not.
	 *
	 * @var bool
	 */
	public $allow_edit = TRUE;

	/**
	 * If the current List Table has a help guide, it will hold its name
	 *
	 * @var string
	 */
	protected $help_guide = '';

	/**
	 * Value for empty columns
	 */
	const EMPTY_COL = '&#45;';


	/**
	 * AtumListTable Constructor
	 *
	 * The child class should call this constructor from its own constructor to override the default $args
	 *
	 * @since 0.0.1
	 *
	 * @param array|string $args          {
	 *      Array or string of arguments.
	 *      NOTE: These args are being passed here (instead of ussing the class props) because we need to be able to alter them dynamically.
	 *
	 *      @type bool   $show_cb           Optional. Whether to show the row selector checkbox as first table column.
	 *      @type bool   $show_controlled   Optional. Whether to show items controlled by ATUM or not.
	 *      @type int    $per_page          Optional. The number of posts to show per page (-1 for no pagination).
	 *      @type array  $selected          Optional. The posts selected on the list table.
	 *      @type array  $excluded          Optional. The posts excluded from the list table.
	 * }
	 */
	public function __construct( $args = array() ) {

		$this->is_filtering  = ( isset( $_REQUEST['s'] ) && strlen( $_REQUEST['s'] ) > 0 ) ||
							   ! empty( $_REQUEST['search_column'] ) || ! empty( $_REQUEST['product_cat'] ) ||
							   ! empty( $_REQUEST['product_type'] ) || ! empty( $_REQUEST[ Globals::PRODUCT_LOCATION_TAXONOMY ] ) ||
							   ! empty( $_REQUEST['supplier'] );
		$this->query_filters = $this->get_filters_query_string();
		$this->day           = Helpers::date_format( '', TRUE, TRUE );

		self::set_sales_day();

		// Filter the table data results to show specific product types only.
		$this->set_product_types_query_data();

		$args = wp_parse_args( $args, array(
			'show_cb'         => $this->show_cb,
			'show_controlled' => $this->show_controlled,
			'per_page'        => Settings::DEFAULT_POSTS_PER_PAGE,
		) );

		$this->show_cb         = $args['show_cb'];
		$this->show_controlled = $args['show_controlled'];

		if ( TRUE === $this->show_totals && 'no' === Helpers::get_option( 'show_totals', 'yes' ) ) {
			$this->show_totals = FALSE;
		}

		if ( ! empty( $args['selected'] ) ) {
			$this->selected = is_array( $args['selected'] ) ? $args['selected'] : explode( ',', $args['selected'] );
		}

		if ( ! empty( $args['excluded'] ) ) {
			$this->excluded = is_array( $args['excluded'] ) ? $args['excluded'] : explode( ',', $args['excluded'] );
		}

		if ( isset( $this->group_members['product-details'] ) && TRUE === $this->show_cb ) {
			array_unshift( $this->group_members['product-details']['members'], 'cb' );
		}

		// Remove _out_stock_threshold columns if not set, or add filters to get availability etc.
		$is_out_stock_threshold_managed = ! ( 'no' === Helpers::get_option( 'out_stock_threshold', 'no' ) );

		if ( ! $is_out_stock_threshold_managed ) {

			unset( self::$table_columns[ Globals::OUT_STOCK_THRESHOLD_KEY ] );

			if ( isset( $this->group_members['stock-counters']['members'] ) ) {
				$this->group_members['stock-counters']['members'] = array_diff( $this->group_members['stock-counters']['members'], array( Globals::OUT_STOCK_THRESHOLD_KEY ) );
			}

		}

		// Add the checkbox column to the table if enabled.
		self::$table_columns = TRUE === $this->show_cb ? array_merge( [ 'cb' => 'cb' ], self::$table_columns ) : self::$table_columns;

		// Add the row actions column if needed.
		if ( ! empty( self::$row_actions ) ) {

			self::$table_columns = array_merge( self::$table_columns, [ 'calc_actions' => '<span class="atum-icon atmi-magic-wand-solid tips" data-bs-placement="bottom" data-tip="' . esc_attr__( 'Actions', ATUM_TEXT_DOMAIN ) . '">' . esc_attr__( 'Actions', ATUM_TEXT_DOMAIN ) . '</span>' ] );

			if ( ! empty( $this->group_members ) ) {
				$this->group_members['actions'] = array(
					'title'   => '',
					'members' => array(
						'calc_actions',
					),
				);
			}

		}

		$this->per_page = $args['per_page'] ?? Helpers::get_option( 'posts_per_page', Settings::DEFAULT_POSTS_PER_PAGE );
		$post_type_obj  = get_post_type_object( $this->post_type );

		if ( ! $post_type_obj ) {
			return;
		}

		// Allow filtering the ATUM sortable columns.
		$this->atum_sortable_columns = apply_filters( 'atum/list_table/atum_sortable_columns', $this->atum_sortable_columns );

		// Set \WP_List_Table defaults.
		$args = array_merge( array(
			'singular'      => strtolower( $post_type_obj->labels->singular_name ),
			'plural'        => strtolower( $post_type_obj->labels->name ),
			'ajax'          => TRUE,
			'table_columns' => self::$table_columns,
			'group_members' => $this->group_members,
		), $args );

		parent::__construct( $args );

		add_filter( 'posts_search', array( $this, 'posts_search' ), 10, 2 );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );

		// Hook the default_hidden_columns filter used within get_hidden_columns() function.
		if ( ! empty( static::$default_hidden_columns ) ) {
			add_filter( 'default_hidden_columns', array( $this, 'hidden_columns' ), 10, 2 );
		}

		// Allow adding searchable columns externally.
		if ( ! empty( $this->searchable_columns ) ) {
			$this->searchable_columns = (array) apply_filters( 'atum/list_table/default_serchable_columns', $this->searchable_columns );
		}

		// Custom image placeholder.
		add_filter( 'woocommerce_placeholder_img', array( '\Atum\Inc\Helpers', 'get_product_image_placeholder' ), 10, 3 );

		self::$default_currency = get_woocommerce_currency();

		// Filtering with extra filters.
		if ( ! empty( $_REQUEST['extra_filter'] ) ) {
			add_action( 'pre_get_posts', array( $this, 'do_extra_filter' ) );
		}

	}

	/**
	 * Extra controls to be displayed in table nav sections
	 *
	 * @since  1.3.0
	 *
	 * @param string $which 'top' or 'bottom' table nav.
	 */
	protected function extra_tablenav( $which ) {

		if ( 'top' === $which ) : ?>

			<div class="alignleft actions">
				<div class="actions-wrapper">
					<?php $this->table_nav_filters() ?>
				</div>
			</div>

		<?php endif;

	}

	/**
	 * Add the filters to the table nav
	 *
	 * @since 1.3.0
	 */
	protected function table_nav_filters() {

		// Before get categories.
		add_filter( 'get_terms', array( $this, 'get_filtered_terms' ), 10, 4 );

		// Category filtering.
		wc_product_dropdown_categories( array(
			'show_count'       => 0,
			'selected'         => ! empty( $_REQUEST['product_cat'] ) ? esc_attr( $_REQUEST['product_cat'] ) : '',
			'class'            => 'wc-enhanced-select atum-enhanced-select dropdown_product_cat atum-tooltip auto-filter',
			'show_option_none' => __( 'All categories...', ATUM_TEXT_DOMAIN ),
		) );

		// After get categories.
		remove_filter( 'get_terms', array( $this, 'get_filtered_terms' ) );

		do_action( 'atum/list_table/before_product_types_dropdown', $this->show_controlled );

		// Product type filtering.
		echo Helpers::product_types_dropdown( isset( $_REQUEST['product_type'] ) ? esc_attr( $_REQUEST['product_type'] ) : '', 'wc-enhanced-select atum-enhanced-select dropdown_product_type auto-filter' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

		do_action( 'atum/list_table/after_product_types_dropdown', $this->show_controlled );

		// Before get locations.
		add_filter( 'get_terms', array( $this, 'get_filtered_terms' ), 10, 4 );

		// Locations filtering.
		$args = array(
			'pad_counts'         => 1,
			'hierarchical'       => 1,
			'show_uncategorized' => 1,
			'orderby'            => 'name',
			'selected'           => ! empty( $_REQUEST[ Globals::PRODUCT_LOCATION_TAXONOMY ] ) ? esc_attr( $_REQUEST[ Globals::PRODUCT_LOCATION_TAXONOMY ] ) : '',
			'show_option_none' => __( 'All locations...', ATUM_TEXT_DOMAIN ),
			'option_none_value'  => '',
			'value_field'        => 'slug',
			'taxonomy'           => Globals::PRODUCT_LOCATION_TAXONOMY,
			'name'               => Globals::PRODUCT_LOCATION_TAXONOMY,
			'class'              => 'wc-enhanced-select atum-enhanced-select dropdown_atum_location atum-tooltip auto-filter',
		);

		wp_dropdown_categories( $args );

		// After get locations.
		remove_filter( 'get_terms', array( $this, 'get_filtered_terms' ) );

		// Supplier filtering.
		// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
		if ( $this->show_controlled ) {
			echo Helpers::suppliers_dropdown( [
				'selected' => isset( $_REQUEST['supplier'] ) ? esc_attr( $_REQUEST['supplier'] ) : '',
				'enhanced' => 'yes' === Helpers::get_option( 'enhanced_suppliers_filter', 'no' ),
			] );
		}
		// phpcs:enable

		do_action( 'atum/list_table/after_nav_filters', $this );

	}

	/**
	 * Print the extra filters dropdown
	 *
	 * @since 1.9.54
	 */
	protected function print_extra_filters() {

		// Extra filters.
		$extra_filters = $this->get_extra_filters();

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

		$active_extra_filter = $_REQUEST['extra_filter'] ?? NULL;

		?>
		<select name="extra_filter" class="wc-enhanced-select atum-enhanced-select dropdown_extra_filter auto-filter" autocomplete="off">
			<option value=""><?php esc_html_e( 'Extra filters...', ATUM_TEXT_DOMAIN ) ?></option>

			<?php foreach ( $extra_filters as $extra_filter => $filter_config ) : ?>
				<option value="<?php echo esc_attr( $extra_filter ) ?>"
					<?php
					// NOTE: If the extra filter has values, the filter should not remain selected. We will show the selected values with another component.
					selected( empty( $filter_config['values'] ) && $active_extra_filter === $extra_filter ); ?>
					<?php if ( $filter_config['auto'] === FALSE ) echo ' data-auto-filter="no"' ?>
				><?php echo esc_html( $filter_config['label'] ) ?></option>
			<?php endforeach; ?>
		</select>
		<?php

	}

	/**
	 * Apply an extra filter to the current List Table query
	 *
	 * @since 1.9.6
	 *
	 * @param \WP_Query $query
	 */
	public function do_extra_filter( $query ) {
		// This should be defined in any children classes using it (if needed).
	}

	/**
	 * Loads the current product
	 *
	 * @since 0.0.1
	 *
	 * @param \WP_Post $item The WooCommerce product post.
	 */
	public function single_row( $item ) {

		$this->list_item = Helpers::get_atum_product( $item );

		if ( ! $this->list_item instanceof \WC_Product ) {
			return;
		}

		$type              = $this->list_item->get_type();
		$this->allow_calcs = TRUE;
		$row_classes       = array( ( ++$this->row_count % 2 ? 'even' : 'odd' ) );

		// Inheritable products do not allow calcs.
		if ( Helpers::is_inheritable_type( $type ) ) {

			$this->parent_type = $type;
			$this->allow_calcs = FALSE;

			// WC product bundles compatibility.
			if ( class_exists( '\WC_Product_Bundle' ) && 'bundle' === $type ) {
				$this->allow_calcs = TRUE;
			}

			if ( 'grouped' === $type ) {
				$class_type = 'group';
			}
			elseif ( 'bundle' === $type ) {
				$class_type = 'bundle';
			}
			else {
				$class_type = 'variable';
			}

			$row_classes[] = $class_type;

			if ( 'yes' === Helpers::get_option( 'expandable_rows', 'no' ) ) {
				$row_classes[] = 'expanded';
			}

		}
		else {
			$this->parent_type = '';
		}

		$row_classes = apply_filters( 'atum/list_table/single_row_classes', $row_classes, $item, $this );
		$row_class   = ' class="main-row ' . implode( ' ', $row_classes ) . '"';
		$row_data    = apply_filters( 'atum/list_table/single_row_data', ' data-id="' . $this->get_current_list_item_id() . '"', $item, $this );

		do_action( 'atum/list_table/before_single_row', $item, $this );

		// Output the row.
		echo '<tr' . $row_data . $row_class . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		$this->single_row_columns( $item );
		echo '</tr>';

		// After printing the row, save the update dates for the current product (if outdated).
		$this->maybe_save_update_dates();

		do_action( 'atum/list_table/after_single_row', $item, $this );

		// Add the children products of each inheritable product type.
		if ( ! $this->allow_calcs || 'bundle' === $type ) {

			if ( 'grouped' === $type ) {
				$product_type = 'product';
			}
			elseif ( 'bundle' === $type ) {
				$product_type = 'product_bundle';
			}
			else {
				$product_type = 'product_variation';
			}

			$child_products = $this->get_children( $type, [ $this->list_item->get_id() ], $product_type );

			if ( ! empty( $child_products ) ) {

				// If the post__in filter is applied, bypass the children that are not in the query var.
				$post_in           = get_query_var( 'post__in' );
				$this->allow_calcs = TRUE;

				foreach ( $child_products as $child_id ) {

					if ( ! empty( $post_in ) && ! in_array( $child_id, $post_in ) ) {
						continue;
					}

					// Exclude some children if there is a "Views Filter" active.
					if ( ! empty( $_REQUEST['view'] ) ) {

						$view = esc_attr( $_REQUEST['view'] );
						if ( ! in_array( $child_id, $this->get_id_views()[ $view ] ) ) {
							continue;
						}

					}

					$this->is_child = TRUE;

					// Save the child product to the product prop.
					$this->list_item = Helpers::get_atum_product( $child_id );

					if ( $this->list_item instanceof \WC_Product ) {

						if ( 'grouped' === $type ) {
							$child_type = 'grouped';
						}
						elseif ( 'bundle' === $type ) {
							$child_type = 'bundle-item';
						}
						else {
							$child_type = 'variation';
						}

						$this->single_expandable_row( $this->list_item, $child_type );

						// If the current product has been modified within any of the columns, save it.
						if ( ! empty( $this->list_item->get_changes() ) ) {
							$this->list_item->save_atum_data();
						}

					}

				}
			}

		}

		// Reset the child value.
		$this->is_child = FALSE;

	}

	/**
	 * Generates content for a expandable row on the table
	 *
	 * @since 1.1.0
	 *
	 * @param \WC_Product $item The WooCommerce product.
	 * @param string      $type The type of product.
	 */
	public function single_expandable_row( $item, $type ) {

		$row_style   = 'yes' !== Helpers::get_option( 'expandable_rows', 'no' ) ? ' style="display: none"' : '';
		$row_classes = apply_filters( 'atum/list_table/single_expandable_row_classes', [ 'expandable', 'has-compounded', $type ], $item, $this );
		$row_class   = ' class="' . esc_attr( implode( ' ', $row_classes ) ) . '"';
		$row_data    = apply_filters( 'atum/list_table/expandable_row_data', ' data-id="' . absint( $this->get_current_list_item_id() ) . '"', $item, $this );

		do_action( 'atum/list_table/before_single_expandable_row', $item, $this );

		echo '<tr' . $row_data . $row_class . $row_style . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		$this->single_row_columns( $item );
		echo '</tr>';

		// After printing the row, save the update dates for the child product (if outdated).
		$this->maybe_save_update_dates();

		do_action( 'atum/list_table/after_single_expandable_row', $item, $this );

	}

	/**
	 * The default column (when no specific column method found)
	 *
	 * @since 0.0.1
	 *
	 * @param \WP_Post $item          The WooCommerce product post.
	 * @param string   $column_name   The current column name.
	 *
	 * @return string|bool
	 */
	protected function column_default( $item, $column_name ) {

		$id          = $this->get_current_list_item_id();
		$column_item = '';

		// Check if it's a hidden meta key (will start with underscore).
		if ( '_' === substr( $column_name, 0, 1 ) ) {

			// If the current product has a method to get the prop, use it.
			if ( is_callable( array( $this->list_item, "get{$column_name}" ) ) ) {
				$column_item = call_user_func( array( $this->list_item, "get{$column_name}" ) );
			}
			else {
				$column_item = get_post_meta( $id, $column_name, TRUE );
			}

		}

		if ( '' === $column_item || FALSE === $column_item ) {
			$column_item = self::EMPTY_COL;
		}

		return apply_filters( "atum/list_table/column_default_$column_name", $column_item, $item, $this->list_item, $this, $column_name );

	}

	/**
	 * Generates the columns for a single row of the table
	 *
	 * @since 1.4.15
	 *
	 * @param object $item The current item.
	 */
	public function single_row_columns( $item ) {

		list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();

		$group_members = wp_list_pluck( $this->group_members, 'members' );

		foreach ( $columns as $column_name => $column_display_name ) {

			$classes = "$column_name column-$column_name";
			if ( $primary === $column_name ) {
				$classes .= ' has-row-actions column-primary';
			}

			if ( in_array( $column_name, $hidden ) ) {
				$classes .= ' hidden';
			}

			// Add the group key as class.
			foreach ( $group_members as $group_key => $members ) {
				if ( in_array( $column_name, $members ) ) {
					$classes .= " $group_key";
					break;
				}
			}

			// Check if it's a numeric cell.
			if (
				! empty( $this->searchable_columns['numeric'] ) && is_array( $this->searchable_columns['numeric'] ) &&
				in_array( $column_name, $this->searchable_columns['numeric'], TRUE )
			) {
				$classes .= ' numeric';
			}

			// Comments column uses HTML in the display name with screen reader text.
			// Instead of using esc_attr(), we strip tags to get closer to a user-friendly string.
			$data = 'data-colname="' . wp_strip_all_tags( $column_display_name ) . '"';

			$attributes = "class='$classes' $data";

			if ( 'cb' === $column_name ) {

				echo '<th scope="row" class="check-column column-cb">';
				echo $this->column_cb( $item ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				echo '</th>';

			}
			elseif ( method_exists( apply_filters( "atum/list_table/column_source_object/_column_$column_name", $this, $item ), "_column_$column_name" ) ) {

				// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				echo call_user_func(
					array( apply_filters( "atum/list_table/column_source_object/_column_$column_name", $this, $item ), "_column_$column_name" ),
					$item,
					$classes,
					$data,
					$primary
				);

			}
			elseif ( method_exists( apply_filters( "atum/list_table/column_source_object/column_$column_name", $this, $item ), "column_$column_name" ) ) {

				echo "<td $attributes>"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				/* The first param is the function to be called (filtered), the second param is the current item, the third param is whether it's editable, and the fourth param is the current list table instance. */
				echo call_user_func( array( apply_filters( "atum/list_table/column_source_object/column_$column_name", $this, $item ), "column_$column_name" ), $item, ! static::$is_report, $this ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				echo '</td>';

			}
			else {

				echo "<td $attributes>"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				/* @noinspection PhpParamsInspection */
				echo $this->column_default( $item, $column_name ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				echo '</td>';

			}

		}

	}

	/**
	 * Column selector checkbox
	 *
	 * @since  0.0.1
	 *
	 * @param object $item
	 *
	 * @return string
	 */
	protected function column_cb( $item ) {

		$product_id = $this->get_current_list_item_id();

		return apply_filters( 'atum/list_table/column_cb', sprintf(
			'<input type="checkbox"%s name="%s[]" value="%s">',
			checked( in_array( $product_id, $this->selected ), TRUE, FALSE ),
			$this->_args['singular'],
			$product_id
		), $item, $this->list_item, $this );

	}

	/**
	 * Column for thumbnail
	 *
	 * @since 0.0.1
	 *
	 * @param \WP_Post $item The WooCommerce product post.
	 *
	 * @return string
	 */
	protected function column_thumb( $item ) {

		$product_id = $this->get_current_list_item_id();
		$img_src    = wp_get_attachment_image_src( $this->list_item->get_image_id(), 'full' );
		$url        = $img_src ? $img_src[0] : get_edit_post_link( $product_id );
		$thumb      = '<a href="' . $url . '" target="_blank">' . $this->list_item->get_image( [ 40, 40 ] ) . '</a>';

		return apply_filters( 'atum/list_table/column_thumb', $thumb, $item, $this->list_item, $this );

	}

	/**
	 * Post ID column
	 *
	 * @since  0.0.1
	 *
	 * @param \WP_Post $item The WooCommerce product post.
	 *
	 * @return int
	 */
	protected function column_id( $item ) {
		return apply_filters( 'atum/list_table/column_id', $this->get_current_list_item_id(), $item, $this->list_item, $this );
	}

	/**
	 * Post title column
	 *
	 * @since  0.0.1
	 *
	 * @param \WP_Post $item The WooCommerce product post.
	 *
	 * @return string
	 */
	protected function column_title( $item ) {

		$title       = '';
		$product_id  = $this->get_current_list_item_id();
		$child_arrow = $this->is_child ? '<i class="atum-icon atmi-arrow-child"></i>' : '';

		if ( Helpers::is_child_type( $this->list_item->get_type() ) ) {

			$attributes = $this->list_item->get_attributes();

			if ( ! empty( $attributes ) ) {
				$title = rawurldecode( implode( ' ', array_map( 'ucfirst', $attributes ) ) );
			}

			// Get the variable product ID to get the right link.
			$product_id = $this->list_item->get_parent_id();

		}
		else {
			$title = $this->list_item->get_title();
		}

		$title_length = absint( apply_filters( 'atum/list_table/column_title_length', 20 ) );

		if ( mb_strlen( $title ) > $title_length ) {
			$data_tip = ! static::$is_report ? ' data-tip="' . esc_attr( $title ) . '"' : '';
			$title    = '<span class="tips"' . $data_tip . '>' . trim( mb_substr( $title, 0, $title_length ) ) . '...</span><span class="atum-title-small">' . $title . '</span>';
		}

		$title = '<a href="' . get_edit_post_link( $product_id ) . '" target="_blank">' . $child_arrow . $title . '</a>';

		return apply_filters( 'atum/list_table/column_title', $title, $item, $this->list_item, $this );

	}

	/**
	 * Product SKU column
	 *
	 * @since  1.1.2
	 *
	 * @param \WP_Post $item     The WooCommerce product post.
	 * @param bool     $editable Whether the SKU will be editable.
	 *
	 * @return string
	 */
	protected function column__sku( $item, $editable = TRUE ) {

		$sku = $this->list_item->get_sku();
		$sku = $sku ?: self::EMPTY_COL;

		if ( $editable && $this->allow_edit ) {

			$args = array(
				'meta_key'   => 'sku',
				'value'      => $sku,
				'input_type' => 'text',
				'tooltip'    => esc_attr__( 'Click to edit the SKU', ATUM_TEXT_DOMAIN ),
				'cell_name'  => esc_attr__( 'SKU', ATUM_TEXT_DOMAIN ),
			);

			$sku = self::get_editable_column( $args );

		}

		return apply_filters( 'atum/list_table/column_sku', $sku, $item, $this->list_item, $this );

	}

	/**
	 * Supplier column
	 *
	 * @since  1.3.1
	 *
	 * @param \WP_Post $item     The WooCommerce product post.
	 * @param bool     $editable Optional. Whether the current column is editable.
	 *
	 * @return string
	 */
	protected function column__supplier( $item, $editable = TRUE ) {

		$supplier = self::EMPTY_COL;

		if ( ! $this->allow_calcs || ! AtumCapabilities::current_user_can( 'read_suppliers' ) ) {
			return $supplier;
		}

		$supplier_id = $this->list_item->get_supplier_id();

		if ( $supplier_id ) {

			$supplier_post = get_post( $supplier_id );

			if ( $supplier_post && Suppliers::POST_TYPE === $supplier_post->post_type ) {
				$supplier = $supplier_post->post_title;
			}

		}

		$supplier_length = absint( apply_filters( 'atum/list_table/column_supplier_length', 20 ) );
		$supplier_abb    = mb_strlen( $supplier ) > $supplier_length ? trim( mb_substr( $supplier, 0, $supplier_length ) ) . '...' : $supplier;

		if ( $editable && $this->allow_edit && AtumCapabilities::current_user_can( 'edit_suppliers' ) ) {

			if ( self::EMPTY_COL === $supplier ) {
				$supplier_tooltip = esc_attr__( 'Click to add a supplier', ATUM_TEXT_DOMAIN );
			}
			else {
				/* translators: first one is the supplier name and second is the supplier's ID */
				$supplier_tooltip = sprintf( esc_attr__( 'Click to edit %1$s (ID: %2$d)', ATUM_TEXT_DOMAIN ), $supplier, $supplier_id );

			}

			$args = apply_filters( 'atum/list_table/args_supplier', array(
				'meta_key'   => 'supplier_id',
				'value'      => $supplier_abb,
				'input_type' => 'select',
				'tooltip'    => $supplier_tooltip,
				'cell_name'  => esc_attr__( 'Supplier', ATUM_TEXT_DOMAIN ),
				'extra_data' => [
					'selected-value'       => $supplier_id ?: '',
					'select-options'       => $supplier_id ? [
						(int) $supplier_id => esc_attr( $supplier ), // Make sure the supplier name is escaped to not cause conflicts when adding as an attribute.
					] : [],
					'extra-class'          => 'wc-product-search atum-enhanced-select',
					'allow_clear'          => 'true',
					'action'               => 'atum_json_search_suppliers',
					'placeholder'          => esc_attr__( 'Search Supplier by Name or ID&hellip;', ATUM_TEXT_DOMAIN ),
					'multiple'             => 'false',
					'minimum_input_length' => 1,
					'container-css'        => 'edit-popover-content',
					'footer-content'       => AtumCapabilities::current_user_can( 'create_suppliers' ) ? '<div class="content-footer"><a href="#" class="atum-add-supplier"><i class="atum-icon atmi-plus-circle"></i><span class="footer-text">' . esc_attr__( 'Create new supplier', ATUM_TEXT_DOMAIN ) . '</span></a></div>' : '',
				],
			) );

			$supplier = self::get_editable_column( $args );

		}
		else {

			/* translators: first one is the supplier name and second is the supplier's ID when the user can't edit suppliers */
			$supplier_tooltip = sprintf( esc_attr__( '%1$s (ID: %2$d)', ATUM_TEXT_DOMAIN ), $supplier, $supplier_id );

			$data_tip = ! static::$is_report ? ' data-tip="' . $supplier_tooltip . '"' : '';
			$supplier = '<span class="tips"' . $data_tip . '>' . $supplier_abb . '</span><span class="atum-title-small">' . $supplier_tooltip . '</span>';

		}

		return apply_filters( 'atum/list_table/column_supplier', $supplier, $item, $this->list_item, $this );

	}

	/**
	 * Column for supplier sku
	 *
	 * @since  1.2.0
	 *
	 * @param \WP_Post $item     The WooCommerce product post to use in calculations.
	 * @param bool     $editable Optional. Whether the current column is editable.
	 *
	 * @return float
	 */
	protected function column__supplier_sku( $item, $editable = TRUE ) {

		$supplier_sku = self::EMPTY_COL;

		if ( ! $this->allow_calcs || ! AtumCapabilities::current_user_can( 'read_suppliers' ) ) {
			return $supplier_sku;
		}

		$supplier_id  = $this->list_item->get_supplier_id();
		$supplier_sku = $this->list_item->get_supplier_sku();

		if ( 0 === strlen( $supplier_sku ) ) {
			$supplier_sku = self::EMPTY_COL;
		}

		if ( $editable && $this->allow_edit && AtumCapabilities::current_user_can( 'edit_suppliers' ) ) {

			$args = apply_filters( 'atum/list_table/args_supplier_sku', array(
				'meta_key'   => 'supplier_sku',
				'value'      => $supplier_sku,
				'input_type' => 'text',
				'tooltip'    => esc_attr__( 'Click to edit the supplier SKU', ATUM_TEXT_DOMAIN ),
				'cell_name'  => esc_attr__( 'Supplier SKU', ATUM_TEXT_DOMAIN ),
			) );

			$supplier_sku = self::get_editable_column( $args );

		}

		return apply_filters( 'atum/list_table/column_supplier_sku', $supplier_sku, $item, $this->list_item, $this );

	}

	/**
	 * Column for product type
	 *
	 * @since 1.1.0
	 *
	 * @param \WP_Post $item The WooCommerce product post.
	 *
	 * @return string
	 */
	protected function column_calc_type( $item ) {

		$type = $this->list_item->get_type();

		$product_tip   = '';
		$product_types = wc_get_product_types();

		if ( isset( $product_types[ $type ] ) || $this->is_child ) {

			if ( ! $this->is_child ) {
				$product_tip = $product_types[ $type ];
			}

			switch ( $type ) {
				case 'simple':
					if ( $this->is_child ) {
						$type        = 'grouped-item';
						$product_tip = 'bundle' === $this->parent_type ? esc_attr__( 'Bundled item', ATUM_TEXT_DOMAIN ) : esc_attr__( 'Grouped item', ATUM_TEXT_DOMAIN );
					}
					elseif ( $this->list_item->is_downloadable() ) {
						$type        = 'downloadable';
						$product_tip = esc_attr__( 'Downloadable product', ATUM_TEXT_DOMAIN );
					}
					elseif ( $this->list_item->is_virtual() ) {
						$type        = 'virtual';
						$product_tip = esc_attr__( 'Virtual product', ATUM_TEXT_DOMAIN );
					}

					break;

				case 'variable':
				case 'grouped':
				case 'variable-subscription': // WC Subscriptions compatibility.
					if ( $this->is_child ) {
						$type        = 'grouped-item';
						$product_tip = 'bundle' === $this->parent_type ? esc_attr__( 'Bundled item', ATUM_TEXT_DOMAIN ) : esc_attr__( 'Grouped item', ATUM_TEXT_DOMAIN );
					}
					elseif ( $this->list_item->has_child() ) {

						$product_tip .= '<br>' . sprintf(
							/* translators: product type names */
							esc_attr__( '(click to show/hide %s)', ATUM_TEXT_DOMAIN ), 'grouped' === $type ? esc_attr__( 'grouped items', ATUM_TEXT_DOMAIN ) : esc_attr__( 'variations', ATUM_TEXT_DOMAIN )
						);
						$type .= ' has-child';

					}

					break;

				case 'variation':
					$product_tip = esc_attr__( 'Variation', ATUM_TEXT_DOMAIN );
					break;

				// WC Subscriptions compatibility.
				case 'subscription_variation':
					$type        = 'variation';
					$product_tip = esc_attr__( 'Subscription Variation', ATUM_TEXT_DOMAIN );
					break;

				// WC Bundle Products compatibility.
				case 'bundle':
					if ( $this->is_child ) {
						$type        = 'bundle-item';
						$product_tip = esc_attr__( 'Bundle item', ATUM_TEXT_DOMAIN );
					}

					$children = Helpers::get_bundle_items( array(
						'return'    => 'id=>product_id',
						'bundle_id' => [ $this->list_item->get_id() ],
					) );

					if ( $children ) {
						$product_tip .= '<br>' . sprintf(
							/* translators: product type names */
							esc_attr__( '(click to show/hide %s)', ATUM_TEXT_DOMAIN ), esc_attr__( 'bundle items', ATUM_TEXT_DOMAIN )
						);
						$type .= ' has-child';
					}

					break;
			}

			$data_tip = ! static::$is_report ? ' data-tip="' . $product_tip . '"' : '';

			return apply_filters( 'atum/list_table/column_type', '<span class="product-type tips ' . $type . '"' . $data_tip . '></span>', $item, $this->list_item, $this );

		}

		return '';

	}

	/**
	 * Column for product location
	 *
	 * @since 1.4.2
	 *
	 * @param \WP_Post $item The WooCommerce product post.
	 *
	 * @return string
	 */
	protected function column_calc_location( $item ) {

		$has_location = $this->list_item->get_has_location();

		if ( is_null( $has_location ) ) {
			$location_terms = wp_get_post_terms( $this->get_current_list_item_id(), Globals::PRODUCT_LOCATION_TAXONOMY );
			$has_location   = ! empty( $location_terms );
			$this->list_item->set_has_location( $has_location );
		}

		$location_terms_class = $has_location && 'no' !== $has_location ? ' not-empty' : '';

		$data_tip  = ! static::$is_report ? ' data-tip="' . esc_attr__( 'Show Locations', ATUM_TEXT_DOMAIN ) . '"' : '';
		$locations = '<a href="#" class="show-locations atum-icon atmi-map-marker tips' . $location_terms_class . '"' . $data_tip . ' data-locations=""></a>';

		return apply_filters( 'atum/list_table/column_locations', $locations, $item, $this->list_item, $this );

	}

	/**
	 * Column for regular price
	 *
	 * @since 1.2.0
	 *
	 * @param \WP_Post $item The WooCommerce's product post to use in calculations.
	 *
	 * @return string
	 */
	protected function column__regular_price( $item ) {

		$regular_price = self::EMPTY_COL;

		if ( $this->allow_calcs ) {

			$regular_price_orig = $this->list_item->get_regular_price();

			if ( is_numeric( $regular_price_orig ) ) {

				$regular_price_orig = (float) $regular_price_orig;

				$regular_price_value = Helpers::format_price( $regular_price_orig, [
					'currency' => self::$default_currency,
				] );

				if ( 0.0 < $regular_price_orig && 0.0 === round( $regular_price_orig, wc_get_price_decimals(), PHP_ROUND_HALF_UP ) ) {

					$regular_price_value = "> $regular_price_value";
				}

			}
			else {
				$regular_price_value = $regular_price;
			}

			if ( $this->allow_edit ) {

				$args = apply_filters( 'atum/list_table/args_regular_price', array(
					'meta_key'   => 'regular_price',
					'value'      => $regular_price_value,
					'symbol'     => get_woocommerce_currency_symbol(),
					'currency'   => self::$default_currency,
					'tooltip'    => esc_attr__( 'Click to edit the regular price', ATUM_TEXT_DOMAIN ),
					'cell_name'  => esc_attr__( 'Regular Price', ATUM_TEXT_DOMAIN ),
					'extra_data' => [ 'realValue' => $regular_price_orig ],
				), $this->list_item );

				$regular_price = self::get_editable_column( $args );

			}
			else {
				$regular_price = $regular_price_value;
			}

		}

		return apply_filters( 'atum/list_table/column_regular_price', $regular_price, $item, $this->list_item );

	}

	/**
	 * Column for sale price
	 *
	 * @since 1.2.0
	 *
	 * @param \WP_Post $item The WooCommerce product post to use in calculations.
	 *
	 * @return string
	 */
	protected function column__sale_price( $item ) {

		$sale_price = self::EMPTY_COL;

		if ( $this->allow_calcs ) {

			$sale_price_orig = $this->list_item->get_sale_price();
			if ( is_numeric( $sale_price_orig ) ) {

				$sale_price_value = Helpers::format_price( $sale_price_orig, [
					'currency' => self::$default_currency,
				] );

				if ( 0.0 < $sale_price_orig && 0.0 === round( $sale_price_orig, wc_get_price_decimals(), PHP_ROUND_HALF_UP ) ) {

					$sale_price_value = "> $sale_price_value";
				}
			}
			else {
				$sale_price_value = $sale_price;
			}

			if ( $this->allow_edit ) {

				$date_on_sale_from = $this->list_item->get_date_on_sale_from( 'edit' ) ? date_i18n( 'Y-m-d', $this->list_item->get_date_on_sale_from( 'edit' )->getOffsetTimestamp() ) : '';
				$date_on_sale_to   = $this->list_item->get_date_on_sale_to( 'edit' ) ? date_i18n( 'Y-m-d', $this->list_item->get_date_on_sale_to( 'edit' )->getOffsetTimestamp() ) : '';

				$args = apply_filters( 'atum/list_table/args_sale_price', array(
					'meta_key'   => 'sale_price',
					'value'      => $sale_price_value,
					'symbol'     => get_woocommerce_currency_symbol(),
					'currency'   => self::$default_currency,
					'tooltip'    => esc_attr__( 'Click to edit the sale price', ATUM_TEXT_DOMAIN ),
					'cell_name'  => esc_attr__( 'Sale Price', ATUM_TEXT_DOMAIN ),
					'extra_meta' => array(
						array(
							'name'        => '_sale_price_dates_from',
							'type'        => 'text',
							'placeholder' => esc_attr_x( 'Sale date from...(YYYY-MM-DD)', 'placeholder', ATUM_TEXT_DOMAIN ),
							'value'       => $date_on_sale_from,
							'maxlength'   => 10,
							'pattern'     => '[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])',
							'class'       => 'atum-datepicker from',
						),
						array(
							'name'        => '_sale_price_dates_to',
							'type'        => 'text',
							'placeholder' => esc_attr_x( 'Sale date to...(YYYY-MM-DD)', 'placeholder', ATUM_TEXT_DOMAIN ),
							'value'       => $date_on_sale_to,
							'maxlength'   => 10,
							'pattern'     => '[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])',
							'class'       => 'atum-datepicker to',
						),
					),
					'extra_data' => [ 'realValue' => $sale_price_orig ],
				), $this->list_item );

				$sale_price = self::get_editable_column( $args );

			}
			else {
				$sale_price = $sale_price_value;
			}

		}

		return apply_filters( 'atum/list_table/column_sale_price', $sale_price, $item, $this->list_item );

	}

	/**
	 * Column for purchase price
	 *
	 * @since 1.2.0
	 *
	 * @param \WP_Post $item The WooCommerce product post to use in calculations.
	 *
	 * @return string
	 */
	protected function column__purchase_price( $item ) {

		$purchase_price = self::EMPTY_COL;

		if ( ! AtumCapabilities::current_user_can( 'view_purchase_price' ) ) {
			return $purchase_price;
		}

		if ( $this->allow_calcs ) {

			$purchase_price_orig = $this->list_item->get_purchase_price();
			if ( is_numeric( $purchase_price_orig ) ) {

				$purchase_price_orig  = (float) $purchase_price_orig;
				$purchase_price_value = Helpers::format_price( $purchase_price_orig, [
					'currency' => self::$default_currency,
				] );

				if ( 0.0 < $purchase_price_orig && 0.0 === round( $purchase_price_orig, wc_get_price_decimals(), PHP_ROUND_HALF_UP ) ) {
					$purchase_price_value = "> $purchase_price_value";
				}

			}
			else {
				$purchase_price_value = $purchase_price;
			}

			if ( $this->allow_edit && AtumCapabilities::current_user_can( 'edit_purchase_price' ) ) {

				$args = apply_filters( 'atum/list_table/args_purchase_price', array(
					'meta_key'   => 'purchase_price',
					'value'      => $purchase_price_value,
					'symbol'     => get_woocommerce_currency_symbol(),
					'currency'   => self::$default_currency,
					'tooltip'    => esc_attr__( 'Click to edit the purchase price', ATUM_TEXT_DOMAIN ),
					'cell_name'  => esc_attr__( 'Purchase Price', ATUM_TEXT_DOMAIN ),
					'extra_data' => [ 'realValue' => $purchase_price_orig ],
				) );

				$purchase_price = self::get_editable_column( $args );

			}
			else {
				$purchase_price = $purchase_price_value;
			}

		}

		return apply_filters( 'atum/list_table/column_purchase_price', $purchase_price, $item, $this->list_item, $this );

	}

	/**
	 * Column for gross profit
	 *
	 * @since 1.8.5
	 *
	 * @param \WP_Post $item The WooCommerce product post to use in calculations.
	 *
	 * @return string
	 */
	protected function column_calc_gross_profit( $item ) {

		$gross_profit = self::EMPTY_COL;

		if ( ! AtumCapabilities::current_user_can( 'view_purchase_price' ) ) {
			return $gross_profit;
		}

		if ( $this->allow_calcs ) {

			$purchase_price = (float) $this->list_item->get_purchase_price();
			$price          = (float) $this->list_item->get_price();

			// Exclude rates if prices includes them.
			if ( wc_prices_include_tax() ) {
				$base_tax_rates = \WC_Tax::get_base_tax_rates( $this->list_item->get_tax_class() );
				$base_pur_taxes = \WC_Tax::calc_tax( $purchase_price, $base_tax_rates, true );
				$base_reg_taxes = \WC_Tax::calc_tax( $price, $base_tax_rates, true );
				$purchase_price = round( $purchase_price - array_sum( $base_pur_taxes ), absint( get_option( 'woocommerce_price_num_decimals' ) ), PHP_ROUND_HALF_UP );
				$price          = round( $price - array_sum( $base_reg_taxes ), absint( get_option( 'woocommerce_price_num_decimals' ) ), PHP_ROUND_HALF_UP );
			}

			if ( $purchase_price > 0 && $price > 0 ) {

				$gross_profit_value      = wp_strip_all_tags( wc_price( $price - $purchase_price ) );
				$gross_profit_percentage = wc_round_discount( ( 100 - ( ( $purchase_price * 100 ) / $price ) ), 2 );
				$profit_margin           = (float) Helpers::get_option( 'profit_margin', 50 );
				$profit_margin_class     = $gross_profit_percentage < $profit_margin ? 'cell-red' : 'cell-green';

				if ( 'percentage' === Helpers::get_option( 'gross_profit', 'percentage' ) ) {
					$gross_profit = '<span class="tips ' . $profit_margin_class . '" data-tip="' . $gross_profit_value . '">' . $gross_profit_percentage . '%</span>';
				}
				else {
					$gross_profit = '<span class="tips ' . $profit_margin_class . '" data-tip="' . $gross_profit_percentage . '%">' . $gross_profit_value . '</span>';
				}
			}

		}

		return apply_filters( 'atum/list_table/column_gross_profit', $gross_profit, $item, $this->list_item, $this );

	}

	/**
	 * Column out_stock_threshold column
	 *
	 * @since 1.4.6
	 *
	 * @param \WP_Post $item      The WooCommerce product post.
	 * @param bool     $editable  Optional. Whether the current column is editable.
	 *
	 * @return double
	 */
	protected function column__out_stock_threshold( $item, $editable = TRUE ) {

		$out_stock_threshold = $this->list_item->get_out_stock_threshold();
		$out_stock_threshold = $out_stock_threshold ?: self::EMPTY_COL;

		// Check type and managed stock at product level (override $out_stock_threshold value if set and not allowed).
		$product_type = $this->list_item->get_type();
		if ( ! in_array( $product_type, Globals::get_product_types_with_stock() ) ) {
			$editable            = FALSE;
			$out_stock_threshold = self::EMPTY_COL;
		}

		$manage_stock = $this->list_item->get_manage_stock();

		if ( 'no' === $manage_stock ) {
			$editable            = FALSE;
			$out_stock_threshold = self::EMPTY_COL;
		}

		if ( $editable && $this->allow_edit ) {

			$args = array(
				'meta_key'   => 'out_stock_threshold',
				'value'      => $out_stock_threshold,
				'input_type' => 'number',
				'tooltip'    => esc_attr__( 'Click to edit the out of stock threshold', ATUM_TEXT_DOMAIN ),
				'cell_name'  => esc_attr__( 'Out of Stock Threshold', ATUM_TEXT_DOMAIN ),
			);

			$out_stock_threshold = self::get_editable_column( $args );

		}

		return apply_filters( 'atum/list_table/column_out_stock_threshold', $out_stock_threshold, $item, $this->list_item, $this );

	}

	/**
	 * Column low_stock_threshold column
	 *
	 * @since 1.9.16
	 *
	 * @param \WP_Post $item      The WooCommerce product post.
	 * @param bool     $editable  Optional. Whether the current column is editable.
	 *
	 * @return double
	 */
	protected function column__low_stock_threshold( $item, $editable = TRUE ) {

		$low_stock_threshold = $this->list_item->get_low_stock_amount();
		$low_stock_threshold = $low_stock_threshold ?: self::EMPTY_COL;

		// Check type and managed stock at product level (override $low_stock_threshold value if set and not allowed).
		$product_type = $this->list_item->get_type();
		if ( ! in_array( $product_type, Globals::get_product_types_with_stock() ) ) {
			$editable            = FALSE;
			$low_stock_threshold = self::EMPTY_COL;
		}

		$manage_stock = $this->list_item->get_manage_stock();

		if ( 'no' === $manage_stock ) {
			$editable            = FALSE;
			$low_stock_threshold = self::EMPTY_COL;
		}

		if ( $editable && $this->allow_edit ) {

			$args = array(
				'meta_key'   => 'low_stock_threshold',
				'value'      => $low_stock_threshold,
				'input_type' => 'number',
				'tooltip'    => esc_attr__( 'Click to edit the low stock threshold', ATUM_TEXT_DOMAIN ),
				'cell_name'  => esc_attr__( 'Low Stock Threshold', ATUM_TEXT_DOMAIN ),
			);

			$low_stock_threshold = self::get_editable_column( $args );

		}

		return apply_filters( 'atum/list_table/column_low_stock_threshold', $low_stock_threshold, $item, $this->list_item, $this );

	}

	/**
	 * Column Weight column
	 *
	 * @since 1.4.6
	 *
	 * @param \WP_Post $item      The WooCommerce product post.
	 * @param bool     $editable  Optional. Whether the current column is editable.
	 *
	 * @return double
	 */
	protected function column__weight( $item, $editable = TRUE ) {

		$weight = $this->list_item->get_weight();
		$weight = $weight ?: self::EMPTY_COL;

		if ( $editable && $this->allow_edit ) {

			$args = array(
				'meta_key'   => 'weight',
				'value'      => $weight,
				'input_type' => 'number',
				'tooltip'    => esc_attr__( 'Click to edit the weight', ATUM_TEXT_DOMAIN ),
				'cell_name'  => esc_attr__( 'Weight', ATUM_TEXT_DOMAIN ),
			);

			$weight = self::get_editable_column( $args );

		}

		return apply_filters( 'atum/list_table/column_weight', $weight, $item, $this->list_item, $this );

	}

	/**
	 * Column for stock amount
	 *
	 * @since 0.0.1
	 *
	 * @param \WP_Post $item The WooCommerce product post to use in calculations.
	 * @param bool     $editable Whether the stock will be editable.
	 *
	 * @return string|int
	 */
	protected function column__stock( $item, $editable = TRUE ) {

		$stock = self::EMPTY_COL;

		$classes_title             = '';
		$tooltip_warning           = '';
		$wc_notify_no_stock_amount = wc_stock_amount( get_option( 'woocommerce_notify_no_stock_amount' ) );
		$is_inheritable            = Helpers::is_inheritable_type( $this->list_item->get_type() );
		$editable                  = apply_filters( 'atum/list_table/editable_column_stock', $editable, $this->list_item );

		// Bundle & grouped products.
		if ( $this->is_packed_item() && ! $this->list_item->get_manage_stock() ) {
			$editable = FALSE;
		}

		// Do not show the stock if the product is not managed by WC.
		if ( ! $is_inheritable && ( ! $this->list_item->managing_stock() || 'parent' === $this->list_item->managing_stock() ) ) {
			return apply_filters( 'atum/list_table/column_stock', $stock, $item, $this->list_item, $this );
		}

		$stock = apply_filters( 'atum/list_table/column_stock_value', wc_stock_amount( $this->list_item->get_stock_quantity() ), $this->list_item );

		// Check WC Product Bundle method.
		if ( class_exists( '\WC_Product_Bundle' ) && method_exists( '\WC_Product_Bundle', 'get_bundle_stock_quantity' ) && 'bundle' === $this->list_item->get_type() ) {

			$bundle_product = $this->list_item;

			/**
			 * Variable definition
			 *
			 * @var \WC_Product_Bundle $bundle_product
			 */
			$stock = ! strlen( $bundle_product->get_bundle_stock_quantity() ) ? self::EMPTY_COL : $bundle_product->get_bundle_stock_quantity();

		}

		if ( 0 !== $stock && ( ( isset( $_REQUEST['view'] ) && 'unmanaged' !== $_REQUEST['view'] ) || ! isset( $_REQUEST['view'] ) ) && apply_filters( 'atum/list_table/column_stock_increase_total', TRUE, $this->list_item, $this ) ) {
			$this->increase_total( '_stock', $stock );
		}

		// Check the Out of Stock Threshold.
		if ( $this->list_item->managing_stock() ) {

			// Global setting is enabled?
			$is_out_stock_threshold_managed = ! ( 'no' === Helpers::get_option( 'out_stock_threshold', 'no' ) );

			// NOTE: makes no sense to show the OOT message if the stock is 0 (this only would be a threshold if backorders is enabled).
			if ( $stock > 0 || $stock < 0 || ( (float) $stock === 0.0 && $this->list_item->backorders_allowed() ) ) {

				if ( $is_out_stock_threshold_managed ) {

					$out_stock_threshold = $this->list_item->get_out_stock_threshold();

					if ( $out_stock_threshold !== '' && ! is_null( $out_stock_threshold ) ) {

						if ( wc_stock_amount( $out_stock_threshold ) >= $stock ) {

							if ( ! $editable ) {
								$classes_title = ' class="cell-yellow" title="' . esc_attr__( 'Stock reached the Out of Stock Threshold', ATUM_TEXT_DOMAIN ) . '"';
							}
							else {
								$classes_title   = ' class="cell-yellow"';
								$tooltip_warning = esc_attr__( 'Click to edit the stock quantity (it reached the Out of Stock Threshold)', ATUM_TEXT_DOMAIN );
							}

						}

					}
					elseif ( $wc_notify_no_stock_amount >= $stock ) {

						if ( ! $editable ) {
							$classes_title = ' class="cell-yellow" title="' . esc_attr__( 'Stock reached the Out of Stock Threshold', ATUM_TEXT_DOMAIN ) . '"';
						}
						else {
							$classes_title   = ' class="cell-yellow"';
							$tooltip_warning = esc_attr__( 'Click to edit the stock quantity (it reached the Out of Stock Threshold)', ATUM_TEXT_DOMAIN );
						}

					}

				}
				elseif ( $wc_notify_no_stock_amount >= $stock ) {

					if ( ! $editable ) {
						$classes_title = ' class="cell-yellow" title="' . esc_attr__( 'Stock reached the Out of Stock Threshold', ATUM_TEXT_DOMAIN ) . '"';
					}
					else {
						$classes_title   = ' class="cell-yellow"';
						$tooltip_warning = esc_attr__( 'Click to edit the stock quantity (it reached the Out of Stock Threshold)', ATUM_TEXT_DOMAIN );
					}

				}

			}

		}

		if ( $editable && $this->allow_edit ) {

			$args = array(
				'meta_key'  => 'stock',
				'value'     => $stock,
				'product'   => $this->list_item,
				'tooltip'   => $tooltip_warning ?: esc_attr__( 'Click to edit the stock quantity', ATUM_TEXT_DOMAIN ),
				'cell_name' => esc_attr__( 'Stock Quantity', ATUM_TEXT_DOMAIN ),
			);

			$stock = self::get_editable_column( $args );

		}

		$stock_html = "<span{$classes_title}>{$stock}</span>";

		if ( $is_inheritable && ! $this->is_packed_item() ) {
			$tooltip     = esc_attr__( 'Compounded stock quantity', ATUM_TEXT_DOMAIN );
			$stock_html .= " | <span class='compounded tips' data-tip='$tooltip'>" . self::EMPTY_COL . '</span>';
		}

		return apply_filters( 'atum/list_table/column_stock', $stock_html, $item, $this->list_item, $this );

	}

	/**
	 * Column for backorders amount: show amount if items pending to serve and without existences
	 *
	 * @since 0.0.1
	 *
	 * @param \WP_Post $item The WooCommerce product post to use in calculations.
	 *
	 * @return int|string
	 */
	protected function column__calc_backorders( $item ) {

		$calc_backorders = self::EMPTY_COL;

		if ( $this->allow_calcs && $this->list_item->backorders_allowed() ) {
			$calc_backorders = $this->list_item->get_calc_backorders();
			$this->increase_total( '_calc_backorders', $calc_backorders );

			if ( $calc_backorders < 0 ) {
				$calc_backorders = '<span class="cell-red">' . $calc_backorders . '</span>';
			}
		}

		return apply_filters( 'atum/list_table/column_calc_backorders', $calc_backorders, $item, $this->list_item, $this );

	}

	/**
	 * Column for inbound stock: shows sum of inbound stock within Purchase Orders
	 *
	 * @since 1.3.0
	 *
	 * @param \WP_Post $item The WooCommerce product post to use in calculations.
	 *
	 * @return int
	 */
	protected function column__inbound_stock( $item ) {

		$inbound_stock = self::EMPTY_COL;

		if ( $this->allow_calcs ) {
			$inbound_stock = Helpers::get_product_inbound_stock( $this->list_item );
			$this->increase_total( '_inbound_stock', $inbound_stock );
		}

		return apply_filters( 'atum/list_table/column_inbound_stock', $inbound_stock, $item, $this->list_item, $this );
	}

	/**
	 * Column for stock indicators
	 *
	 * @since 0.0.1
	 *
	 * @param \WP_Post $item The WooCommerce product post to use in calculations.
	 * @param string   $classes
	 * @param string   $data
	 * @param string   $primary
	 */
	protected function _column_calc_stock_indicator( $item, $classes, $data, $primary ) {

		$product_id = $this->get_current_list_item_id();
		$content    = self::EMPTY_COL;

		// Add css class to the <td> elements depending on the quantity in stock compared to the last days sales.
		if ( $this->allow_calcs ) {

			// Stock not managed by WC.
			if ( ! $this->list_item->managing_stock() || 'parent' === $this->list_item->managing_stock() ) {

				$wc_stock_status = $this->list_item->get_stock_status();

				switch ( $wc_stock_status ) {
					case 'instock':
						$classes .= ' cell-green';
						$data_tip = ! static::$is_report ? ' data-tip="' . esc_attr__( 'In Stock (not managed by WC)', ATUM_TEXT_DOMAIN ) . '"' : '';
						$content  = '<span class="atum-icon atmi-question-circle tips"' . $data_tip . '></span>';
						break;

					case 'outofstock':
						$classes .= ' cell-red';
						$data_tip = ! static::$is_report ? ' data-tip="' . esc_attr__( 'Out of Stock (not managed by WC)', ATUM_TEXT_DOMAIN ) . '"' : '';
						$content  = '<span class="atum-icon atmi-question-circle tips"' . $data_tip . '></span>';
						break;

					case 'onbackorder':
						$classes .= ' cell-yellow';
						$data_tip = ! static::$is_report ? ' data-tip="' . esc_attr__( 'On Backorder (not managed by WC)', ATUM_TEXT_DOMAIN ) . '"' : '';
						$content  = '<span class="atum-icon atmi-question-circle tips"' . $data_tip . '></span>';
						break;
				}

			}
			// Out of stock.
			elseif ( in_array( $product_id, $this->get_id_views()['out_stock'] ) ) {
				$classes .= ' cell-red';
				$data_tip = ! static::$is_report ? ' data-tip="' . esc_attr__( 'Out of Stock', ATUM_TEXT_DOMAIN ) . '"' : '';
				$content  = '<span class="atum-icon atmi-cross-circle tips"' . $data_tip . '></span>';
			}
			// Backorders.
			elseif ( in_array( $product_id, $this->get_id_views()['back_order'] ) ) {
				$classes .= ' cell-yellow';
				$data_tip = ! static::$is_report ? ' data-tip="' . esc_attr__( 'Out of Stock (backorders allowed)', ATUM_TEXT_DOMAIN ) . '"' : '';
				$content  = '<span class="atum-icon atmi-circle-minus tips"' . $data_tip . '></span>';
			}
			// Restock Status.
			elseif ( in_array( $product_id, $this->get_id_views()['restock_status'] ) ) {
				$classes .= ' cell-blue';
				$data_tip = ! static::$is_report ? ' data-tip="' . esc_attr__( 'Restock Status', ATUM_TEXT_DOMAIN ) . '"' : '';
				$content  = '<span class="atum-icon atmi-arrow-down-circle tips"' . $data_tip . '></span>';
			}
			// In Stock.
			elseif ( in_array( $product_id, $this->get_id_views()['in_stock'] ) ) {
				$classes .= ' cell-green';
				$data_tip = ! self::$is_report ? ' data-tip="' . esc_attr__( 'In Stock', ATUM_TEXT_DOMAIN ) . '"' : '';
				$content  = '<span class="atum-icon atmi-checkmark-circle tips"' . $data_tip . '></span>';
			}

			$content = apply_filters( 'atum/list_table/column_stock_indicator', $content, $item, $this->list_item, $this );

		}

		$classes = apply_filters( 'atum/list_table/column_stock_indicator_classes', $classes, $this->list_item );
		$classes = $classes ? ' class="' . $classes . '"' : '';

		echo '<td ' . $data . $classes . '>' . $content . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

	}

	/**
	 * Column for row actions
	 *
	 * @since 1.8.4
	 *
	 * @param \WP_Post $item The WooCommerce product post to use in calculations.
	 *
	 * @return string
	 */
	public function column_calc_actions( $item ) {

		if ( apply_filters( 'atum/list_table/allow_row_actions', empty( self::$row_actions ), $item, $this->list_item, $this ) ) {
			return '';
		}

		$actions_button = '<i class="show-actions atum-icon atmi-options" data-bs-placement="left"></i>';

		return apply_filters( 'atum/list_table/column_actions', $actions_button, $item, $this->list_item, $this );

	}

	/**
	 * REQUIRED! This method dictates the table's columns and titles
	 * This should return an array where the key is the column slug (and class) and the value
	 * is the column's title text.
	 *
	 * @see WP_List_Table::single_row_columns()
	 *
	 * @since 0.0.1
	 *
	 * @return array An associative array containing column information: 'slugs'=>'Visible Titles'.
	 */
	public function get_columns() {

		$result = array();

		foreach ( self::$table_columns as $column_name => $column_label ) {
			$group                  = $this->search_group_columns( $column_name );
			$result[ $column_name ] = $group ? "<span class='col-$group'>$column_label</span>" : $column_label;
		}

		return apply_filters( 'atum/list_table/columns', $result );
	}

	/**
	 * Returns primary column name
	 *
	 * @since 0.0.8
	 *
	 * @return string   Name of the default primary column.
	 */
	protected function get_default_primary_column_name() {
		return 'title';
	}

	/**
	 * Create an editable meta cell
	 *
	 * @since 1.2.0
	 *
	 * @param array $args {
	 *      Array of arguments.
	 *
	 *      @type int    $post_id           The current post ID.
	 *      @type string $meta_key          The meta key name (without initial underscore) to be saved.
	 *      @type mixed  $value             The new value for the meta key cell.
	 *      @type string $symbol            Whether to add any symbol to value.
	 *      @type string $tooltip           The informational tooltip text.
	 *      @type string $input_type        The input type field to use to edit the column value.
	 *      @type array  $extra_meta        Any extra fields will be appended to the popover (as JSON array).
	 *      @type string $tooltip_position  Where to place the tooltip.
	 *      @type string $cell_name         The display name for the cell.
	 *      @type array  $extra_data        Any other array of data that should be added to the element.
	 * }
	 *
	 * @return string
	 */
	public static function get_editable_column( $args ) {

		/**
		 * Variable definitions
		 *
		 * @var string $meta_key
		 * @var mixed  $value
		 * @var string $symbol
		 * @var string $tooltip
		 * @var string $input_type
		 * @var array  $extra_meta
		 * @var string $tooltip_position
		 * @var string $cell_name
		 * @var array  $extra_data
		 * @var string $extra_classes
		 */
		extract( wp_parse_args( $args, array(
			'meta_key'         => '',
			'value'            => '',
			'symbol'           => '',
			'tooltip'          => '',
			'input_type'       => 'number',
			'extra_meta'       => array(),
			'tooltip_position' => 'top',
			'cell_name'        => '',
			'extra_data'       => array(),
			'extra_classes'    => '',
		) ) );

		$extra_meta_data = ! empty( $extra_meta ) ? ' data-extra-meta="' . htmlspecialchars( wp_json_encode( $extra_meta ), ENT_QUOTES, 'UTF-8' ) . '"' : '';
		$symbol_data     = ! empty( $symbol ) ? ' data-symbol="' . esc_attr( $symbol ) . '"' : '';
		$extra_data      = ! empty( $extra_data ) ? Helpers::array_to_data( $extra_data ) : '';

		// phpcs:disable Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed
		ob_start(); ?>
		<span class="atum-tooltip<?php echo esc_attr( $extra_classes ) ?>" title="<?php echo esc_attr( $tooltip ) ?>" data-bs-placement="<?php echo esc_attr( $tooltip_position ) ?>">
			<span class="set-meta" data-meta="<?php echo esc_attr( $meta_key ) ?>"
				<?php echo $symbol_data . $extra_meta_data . $extra_data; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				data-input-type="<?php echo esc_attr( $input_type ) ?>" data-cell-name="<?php echo esc_attr( $cell_name ) ?>"
			>
				<?php echo $value; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			</span>
		</span>
		<?php

		return apply_filters( 'atum/list_table/editable_column', ob_get_clean(), $args );

	}

	/**
	 * All columns are sortable by default except cb and thumbnail
	 *
	 * Optional. If you want one or more columns to be sortable (ASC/DESC toggle),
	 * you will need to register it here. This should return an array where the
	 * key is the column that needs to be sortable, and the value is db column to
	 * sort by. Often, the key and value will be the same, but this is not always
	 * the case (as the value is a column name from the database, not the list table).
	 *
	 * This method merely defines which columns should be sortable and makes them
	 * clickable - it does not handle the actual sorting. You still need to detect
	 * the ORDERBY and ORDER querystring variables within prepare_items() and sort
	 * your data accordingly (usually by modifying your query).
	 *
	 * @return array An associative array containing all the columns that should be sortable: 'slugs' => array('data_values', bool).
	 */
	protected function get_sortable_columns() {

		$not_sortable     = array( 'thumb', 'cb' );
		$sortable_columns = array();

		foreach ( self::$table_columns as $key => $column ) {
			if ( ! in_array( $key, $not_sortable ) && 0 !== strpos( $key, 'calc_' ) ) {
				$sortable_columns[ $key ] = array( $key, FALSE );
			}
		}

		return apply_filters( 'atum/list_table/sortable_columns', $sortable_columns );
	}

	/**
	 * Get an associative array ( id => link ) with the list of available views on this table.
	 *
	 * @since 1.3.0
	 *
	 * @return array
	 */
	protected function get_views() {

		$views = array();
		$view  = ! empty( $_REQUEST['view'] ) ? esc_attr( $_REQUEST['view'] ) : 'all_stock';

		$views_name = array(
			'all_stock'      => __( 'All', ATUM_TEXT_DOMAIN ),
			'in_stock'       => __( 'In Stock', ATUM_TEXT_DOMAIN ),
			'out_stock'      => __( 'Out of Stock', ATUM_TEXT_DOMAIN ),
			'back_order'     => __( 'Backorder', ATUM_TEXT_DOMAIN ),
			'restock_status' => __( 'Restock Status', ATUM_TEXT_DOMAIN ),
			'unmanaged'      => __( 'Unmanaged by WC', ATUM_TEXT_DOMAIN ),
		);

		if ( $this->show_unmanaged_counters ) {

			unset( $views_name['unmanaged'] );

			$views = array(
				'all_stock'  => array(
					'all'       => 'all_stock',
					'managed'   => 'managed',
					'unmanaged' => 'unmanaged',
				),
				'in_stock'   => array(
					'all'       => 'all_in_stock',
					'managed'   => 'in_stock',
					'unmanaged' => 'unm_in_stock',
				),
				'out_stock'  => array(
					'all'       => 'all_out_stock',
					'managed'   => 'out_stock',
					'unmanaged' => 'unm_out_stock',
				),
				'back_order' => array(
					'all'       => 'all_back_order',
					'managed'   => 'back_order',
					'unmanaged' => 'unm_back_order',
				),
			);

		}

		global $plugin_page;

		if ( ! $plugin_page && ! empty( $this->_args['screen'] ) ) {
			$plugin_page = str_replace( Globals::ATUM_UI_HOOK . '_page_', '', $this->_args['screen'] );
		}

		$url = esc_url( add_query_arg( 'page', $plugin_page, admin_url() ) );

		foreach ( $views_name as $key => $text ) {

			$class   = $id = $active = $empty = '';
			$classes = array();

			$current_all = ! empty( $views[ $key ]['all'] ) ? $views[ $key ]['all'] : $key;

			if ( 'all_stock' === $current_all ) {
				$count    = $this->count_views['count_all'];
				$view_url = $url;
			}
			else {

				if ( ! empty( $views[ $key ] ) ) {
					$count = $this->count_views[ 'count_' . $views[ $key ]['all'] ];
				}
				else {
					$count = $this->count_views[ 'count_' . $key ];
				}

				$view_url = esc_url( add_query_arg( array( 'view' => $current_all ), $url ) );
				$id       = ' id="' . $current_all . '"';
			}

			$query_filters = $this->query_filters;

			if ( $current_all === $view || ( ! $view && 'all_stock' === $current_all ) ) {
				$classes[] = 'current';
				$active    = ' class="active"';
			}
			else {
				$query_filters['paged'] = 1;
			}

			if ( ! $count ) {
				$classes[] = 'empty';
				$empty     = 'empty';
			}

			if ( $classes ) {
				$class = ' class="' . implode( ' ', $classes ) . '"';
			}

			$hash_params = http_build_query( array_merge( $query_filters, array( 'view' => $current_all ) ) );

			if ( ! empty( $views[ $key ] ) && $this->show_controlled ) {

				$extra_links = '';

				if ( ! empty( $views[ $key ]['managed'] ) ) {

					$man_class = array( 'tips' );
					$man_url   = esc_url( add_query_arg( array( 'view' => $views[ $key ]['managed'] ), $url ) );
					$man_id    = ' id="' . $views[ $key ]['managed'] . '"';
					$man_count = $this->count_views[ 'count_' . $views[ $key ]['managed'] ];

					$query_filters = $this->query_filters;

					if ( ( $views[ $key ]['managed'] === $view ) ) {
						$man_class[] = 'current';
						$active      = ' class="active"';
					}
					else {
						$query_filters['paged'] = 1;
					}

					if ( ! $man_count ) {
						$man_class[] = 'empty';
						$empty       = 'empty';
					}

					$man_class       = ! empty( $man_class ) ? ' class="' . implode( ' ', $man_class ) . '"' : '';
					$man_hash_params = http_build_query( array_merge( $query_filters, array( 'view' => $views[ $key ]['managed'] ) ) );
					$data_tip        = ! static::$is_report ? ' data-tip="' . esc_attr__( 'Managed by WC', ATUM_TEXT_DOMAIN ) . '"' : '';
					$extra_links    .= '<a' . $man_id . $man_class . ' href="' . $man_url . '" rel="address:/?' . $man_hash_params . '"' . $data_tip . '>' . $man_count . '</a>';

				}

				if ( ! empty( $views[ $key ]['unmanaged'] ) ) {

					$unm_class = array( 'tips' );
					$unm_url   = esc_url( add_query_arg( array( 'view' => $views[ $key ]['unmanaged'] ), $url ) );
					$unm_id    = ' id="' . $views[ $key ]['unmanaged'] . '"';
					$unm_count = $this->count_views[ 'count_' . $views[ $key ]['unmanaged'] ];

					$query_filters = $this->query_filters;

					if ( ( $views[ $key ]['unmanaged'] === $view ) ) {
						$unm_class[] = 'current';
						$active      = ' class="active"';
					}
					else {
						$query_filters['paged'] = 1;
					}

					if ( ! $unm_count ) {
						$unm_class[] = 'empty';
					}

					if ( $unm_class ) {
						$unm_class = ' class="' . implode( ' ', $unm_class ) . '"';
					}
					else {
						$unm_class = '';
					}

					$unm_hash_params = http_build_query( array_merge( $query_filters, array( 'view' => $views[ $key ]['unmanaged'] ) ) );
					$data_tip        = ! static::$is_report ? ' data-tip="' . esc_attr__( 'Unmanaged by WC', ATUM_TEXT_DOMAIN ) . '"' : '';
					$extra_links    .= ',<a' . $unm_id . $unm_class . ' href="' . $unm_url . '" rel="address:/?' . $unm_hash_params . '"' . $data_tip . '>' . $unm_count . '</a>';

				}

				$views[ $key ] = '<span' . $active . '><a' . $id . $class . ' href="' . $view_url . '" rel="address:/?' . $hash_params . '">
								  <span' . $active . '>' . $text . ' <span class="count ' . $empty . '">' . $count . '</span></span></a> 
								  <span class="extra-links-container ' . $empty . '">(' . $extra_links . ')</span></span>';

			}
			else {
				$views[ $key ] = '<span' . $active . '><a' . $id . $class . ' href="' . $view_url . '" rel="address:/?' . $hash_params . '">
								  <span' . $active . '>' . $text . ' <span class="count extra-links-container ' . $empty . '">(' . $count . ')</span></span>
								  </a></span>';
			}

		}

		return apply_filters( 'atum/list_table/view_filters', $views );

	}

	/**
	 * Display the list of views available on this table
	 *
	 * @since 1.4.3
	 */
	public function views() {

		$views = $this->get_views();
		$views = apply_filters( "views_{$this->screen->id}", $views ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

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

		$this->screen->render_screen_reader_content( 'heading_views' );

		?>
		<ul class="subsubsub extend-list-table">
			<?php
			foreach ( $views as $class => $view ) :
				$views[ $class ] = "\t<li class='$class'>$view";
			endforeach;

			echo implode( "</li>\n", $views ) . "</li>\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			?>
		</ul>
		<?php

	}

	/**
	 * Bulk actions are an associative array in the format 'slug' => 'Visible Title'
	 *
	 * @since 0.0.1
	 *
	 * @return array An associative array containing all the bulk actions: 'slugs'=>'Visible Titles'.
	 */
	protected function get_bulk_actions() {

		$bulk_actions = array(
			'manage_stock'   => __( "Enable WC's Manage Stock", ATUM_TEXT_DOMAIN ),
			'unmanage_stock' => __( "Disable WC's Manage Stock", ATUM_TEXT_DOMAIN ),
		);

		if (
			( isset( $_GET['uncontrolled'] ) && 1 === absint( $_GET['uncontrolled'] ) ) ||
			( isset( $_REQUEST['show_controlled'] ) && 0 === absint( $_REQUEST['show_controlled'] ) )
		) {
			$bulk_actions['control_stock'] = __( "Enable ATUM's Stock Control", ATUM_TEXT_DOMAIN );
		}
		else {
			$bulk_actions['uncontrol_stock'] = __( "Disable ATUM's Stock Control", ATUM_TEXT_DOMAIN );
		}

		return apply_filters( 'atum/list_table/bulk_actions', $bulk_actions, $this );

	}

	/**
	 * Display the bulk actions dropdown
	 *
	 * @since 1.4.1
	 *
	 * @param string $which The location of the bulk actions: 'top' or 'bottom'.
	 *                      This is designated as optional for backward compatibility.
	 */
	protected function bulk_actions( $which = '' ) {

		if ( empty( $this->_actions ) ) {
			$this->_actions = apply_filters( "atum/list_table/bulk_actions-{$this->screen->id}", $this->get_bulk_actions() );
		}

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

		?>
		<label for="bulk-action-selector-<?php echo esc_attr( $which ) ?>" class="screen-reader-text"><?php esc_html_e( 'Select bulk action', ATUM_TEXT_DOMAIN ) ?></label>
		<select class="wc-enhanced-select atum-enhanced-select atum-tooltip"
            id="bulk-action-selector-<?php echo esc_attr( $which ) ?>" autocomplete="off"
		>
			<option value="-1"><?php esc_html_e( 'Bulk actions...', ATUM_TEXT_DOMAIN ) ?></option>

			<?php foreach ( $this->_actions as $name => $title ) : ?>
				<option value="<?php echo esc_attr( $name ) ?>"<?php if ( 'edit' === $name ) echo ' class="hide-if-no-js"' ?>><?php echo esc_html( $title ) ?></option>
			<?php endforeach; ?>
		</select>
		<?php

		$this->add_apply_bulk_action_button();

	}

	/**
	 * Adds the Bulk Actions' apply button to the List Table view
	 *
	 * @since 1.4.1
	 */
	public function add_apply_bulk_action_button() {
		?>
		<button type="button" class="apply-bulk-action btn btn-warning" style="display: none">
			<?php esc_html_e( 'Apply', ATUM_TEXT_DOMAIN ) ?>
		</button>
		<?php
	}

	/**
	 * Prepare the table data
	 *
	 * @since 0.0.1
	 */
	public function prepare_items() {

		/**
		 * Define our column headers
		 */
		$columns             = $this->get_columns();
		$posts               = array();
		$sortable            = $this->get_sortable_columns();
		$hidden              = get_hidden_columns( $this->screen );
		$this->group_columns = $this->calc_groups( $hidden );

		/**
		 * REQUIRED. Build an array to be used by the class for column headers
		 */
		$this->_column_headers = array( $columns, $hidden, $sortable );

		$args = array(
			'post_type'      => $this->post_type,
			'post_status'    => Globals::get_queryable_product_statuses(),
			'posts_per_page' => $this->per_page,
			'paged'          => $this->get_pagenum(),
		);

		/**
		 * Get Controlled or Uncontrolled items
		 */
		$this->set_controlled_query_data();

		/**
		 * Tax filter
		 */

		// Add the product category to the tax query.
		if ( ! empty( $_REQUEST['product_cat'] ) ) {

			$this->taxonomies[] = array(
				'taxonomy' => 'product_cat',
				'field'    => 'slug',
				'terms'    => esc_attr( $_REQUEST['product_cat'] ),
			);

		}

		/**
		 * Location filter
		 */

		// Add the product location to the tax query.
		if ( ! empty( $_REQUEST[ Globals::PRODUCT_LOCATION_TAXONOMY ] ) ) {

			$this->taxonomies[] = array(
				'taxonomy' => Globals::PRODUCT_LOCATION_TAXONOMY,
				'field'    => 'slug',
				'terms'    => esc_attr( $_REQUEST[ Globals::PRODUCT_LOCATION_TAXONOMY ] ),
			);

		}

		// Change the product type tax query (initialized in constructor) to the current queried type.
		if ( ! empty( $_REQUEST['product_type'] ) ) {

			$type = esc_attr( $_REQUEST['product_type'] );

			foreach ( $this->taxonomies as $index => $taxonomy ) {

				if ( 'product_type' === $taxonomy['taxonomy'] ) {

					if ( in_array( $type, [ 'downloadable', 'virtual' ] ) ) {

						$this->taxonomies[ $index ]['terms'] = 'simple';

						$this->extra_meta = array(
							'key'   => "_$type",
							'value' => 'yes',
						);

					}
					else {
						$this->taxonomies[ $index ]['terms'] = $type;
					}

					break;
				}

			}

		}

		if ( ! empty( $this->taxonomies ) ) {
			$args['tax_query'] = (array) apply_filters( 'atum/list_table/taxonomies', $this->taxonomies );
		}

		/**
		 * Extra meta args
		 */
		if ( ! empty( $this->extra_meta ) ) {
			$args['meta_query'][] = $this->extra_meta;
		}

		/**
		 * Searching
		 */
		if ( ! empty( $_REQUEST['search_column'] ) ) {
			$args['search_column'] = esc_attr( $_REQUEST['search_column'] );
		}

		if ( isset( $_REQUEST['s'] ) && strlen( $_REQUEST['s'] ) > 0 ) {
			$args['s'] = esc_attr( $_REQUEST['s'] );
		}

		/**
		 * Supplier filter
		 * NOTE: it's important to run this filter after processing all the rest because we need to pass the $args through.
		 */
		if ( ! empty( $_REQUEST['supplier'] ) ) {

			$supplier = absint( $_REQUEST['supplier'] );

			if ( AtumCapabilities::current_user_can( 'read_suppliers' ) ) {

				// This query does not get product variations and as each variation may have a distinct supplier,
				// we have to get them separately and to add their variables to the results.
				$this->supplier_variation_products = Suppliers::get_supplier_products( $supplier, [ 'product_variation' ], TRUE, $args );

				/** TODO: Remove this filters if not needed. Add the supplier products to the results is in conflict with pagination, adding results to each page.
				 * if ( ! empty( $this->supplier_variation_products ) ) {
				 * add_filter( 'atum/list_table/views_data_products', array( $this, 'add_supplier_variables_to_query' ), 10, 2 );
				 * add_filter( 'atum/list_table/items', array( $this, 'add_supplier_variables_to_query' ), 10, 2 );
				 * add_filter( 'atum/list_table/views_data_variations', array( $this, 'add_supplier_variations_to_query' ), 10, 2 );
				 * }
				 */

			}

		}

		// Let others play.
		$args = apply_filters( 'atum/list_table/prepare_items/args', $args );

		// Build "Views Filters" and calculate totals.
		$this->set_views_data( $args );

		// Add the orderby args.
		$args = $this->parse_orderby_args( $args );

		$allow_query = TRUE;

		/**
		 * REQUIRED. Register our pagination options & calculations
		 */
		$found_posts = $this->count_views['count_all'] ?? 0;

		if ( ! empty( $_REQUEST['view'] ) ) {

			$view        = esc_attr( $_REQUEST['view'] );
			$allow_query = FALSE;

			foreach ( $this->get_id_views() as $key => $post_ids ) {

				if ( $view === $key ) {

					$this->supplier_variation_products = array_intersect( $this->supplier_variation_products, $post_ids );

					if ( ! empty( $post_ids ) ) {

						$get_parents = FALSE;
						foreach ( Globals::get_inheritable_product_types() as $inheritable_product_type ) {

							if ( ! empty( $this->container_products[ str_replace( '-', '_', $inheritable_product_type ) ] ) ) {
								$get_parents = TRUE;
								break;
							}

						}

						// Add the parent products again to the query.
						if ( $get_parents ) {

							$parents = $this->get_variation_parents( $post_ids );

							// Exclude the parents with no children.
							// For example: the current list may have the "Out of stock" filter applied and a variable product
							// may have all of its variations in stock, but its own stock could be 0. It shouldn't appear empty.
							foreach ( Globals::get_inheritable_product_types() as $inheritable_product_type ) {

								$inheritable_product_type = str_replace( '-', '_', $inheritable_product_type );

								if ( strpos( $inheritable_product_type, 'variable' ) !== 0 ) {
									continue;
								}

								if ( ! isset( $this->container_products[ $inheritable_product_type ] ) ) {
									continue;
								}

								$empty_variables = array_diff( $this->container_products[ $inheritable_product_type ], $parents );

								foreach ( $empty_variables as $empty_variable ) {
									if ( in_array( $empty_variable, $post_ids ) ) {
										unset( $post_ids[ array_search( $empty_variable, $post_ids ) ] );
									}
								}

							}

							// Get the Grouped parents.
							if ( ! empty( $this->container_products['grouped'] ) ) {

								$grouped_parents = $this->get_grouped_parents( $post_ids );

								$empty_grouped_parents = array_diff( $this->container_products['grouped'], $parents );

								foreach ( $empty_grouped_parents as $empty_grouped ) {
									if ( in_array( $empty_grouped, $post_ids ) ) {
										unset( $post_ids[ array_search( $empty_grouped, $post_ids ) ] );
									}
								}

								$parents = array_merge( $parents, $grouped_parents );

							}

							$args['post__in'] = array_merge( $parents, $post_ids );

						}
						else {
							$args['post__in'] = $post_ids;
						}

						$allow_query = TRUE;
						$found_posts = $this->count_views[ "count_$key" ];
						break;

					}

				}

			}
		}

		if ( $allow_query ) {

			if ( ! empty( $this->excluded ) )  {

				if ( isset( $args['post__not_in'] ) ) {
					$args['post__not_in'] = array_merge( $args['post__not_in'], $this->excluded );
				}
				else {
					$args['post__not_in'] = $this->excluded;
				}

			}

			// Set up the WP query.
			global $wp_query;

			// Pass through the ATUM query data filter.
			do_action( 'atum/list_table/before_query_data' );
			add_filter( 'posts_clauses', array( $this, 'atum_product_data_query_clauses' ) );
			$wp_query = new \WP_Query( $args );
			remove_filter( 'posts_clauses', array( $this, 'atum_product_data_query_clauses' ) );
			do_action( 'atum/list_table/after_query_data' );

			$posts = $wp_query->posts;

			if ( $found_posts > 0 && empty( $posts ) ) {

				$args['paged']     = 1;
				$_REQUEST['paged'] = $args['paged'];

				// Pass through the ATUM query data filter.
				do_action( 'atum/list_table/before_query_data' );
				add_filter( 'posts_clauses', array( $this, 'atum_product_data_query_clauses' ) );
				$wp_query = new \WP_Query( $args );
				remove_filter( 'posts_clauses', array( $this, 'atum_product_data_query_clauses' ) );
				do_action( 'atum/list_table/after_query_data' );

				$posts = $wp_query->posts;

			}

			$product_ids = wp_list_pluck( $posts, 'ID' );

			$this->current_products = $product_ids;
			// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
			$total_pages = ( - 1 == $this->per_page || ! $wp_query->have_posts() ) ? 0 : ceil( $wp_query->found_posts / $this->per_page );

		}
		else {
			$found_posts = $total_pages = 0;
		}

		/**
		 * REQUIRED!!!
		 * Save the sorted data to the items' property, where can be used by the rest of the class.
		 */
		$this->items = apply_filters( 'atum/list_table/items', $posts, 'posts' );

		$this->set_pagination_args( array(
			'total_items' => $found_posts,
			'per_page'    => $this->per_page,
			'total_pages' => $total_pages,
			'orderby'     => ! empty( $_REQUEST['orderby'] ) ? $_REQUEST['orderby'] : 'date',
			'order'       => ! empty( $_REQUEST['order'] ) ? $_REQUEST['order'] : 'desc',
		) );

	}

	/**
	 * Set the query data for filtering the Controlled/Uncontrolled products.
	 *
	 * @since 1.5.0
	 */
	protected function set_controlled_query_data() {

		// Do not need to alter the query data if the 'atum_controlled' key is already there.
		if ( ! empty( $this->atum_query_data['where'] ) && ! empty( wp_list_filter( $this->atum_query_data['where'], [ 'key' => 'atum_controlled' ] ) ) ) {
			return;
		}

		if ( $this->show_controlled ) {

			$this->atum_query_data['where'][] = array(
				'key'   => 'atum_controlled',
				'value' => 1,
				'type'  => 'NUMERIC',
			);

		}
		else {

			$this->atum_query_data['where'][] = array(
				'relation' => 'OR',
				array(
					'key'   => 'atum_controlled',
					'value' => 0,
					'type'  => 'NUMERIC',
				),
				array(
					'key'   => 'inheritable',
					'value' => 1,
					'type'  => 'NUMERIC',
				),
			);

		}

	}

	/**
	 * Filter the list table data to show compatible product types only
	 *
	 * @since 1.5.0
	 */
	protected function set_product_types_query_data() {

		$this->taxonomies[] = array(
			'taxonomy' => 'product_type',
			'field'    => 'slug',
			'terms'    => Globals::get_product_types(),
		);

	}

	/**
	 * Customize the WP_Query to handle ATUM product data
	 *
	 * @since 1.5.0
	 *
	 * @param array $pieces
	 *
	 * @return array
	 */
	public function atum_product_data_query_clauses( $pieces ) {

		if ( ! empty( $_REQUEST['supplier'] ) ) {

			if ( AtumCapabilities::current_user_can( 'read_suppliers' ) ) {

				global $wpdb;

				$supplier = absint( $_REQUEST['supplier'] );
				$pd_table = $wpdb->prefix . Globals::ATUM_PRODUCT_DATA_TABLE;
				$statuses = Globals::get_queryable_product_statuses();

				$subquery = "
					SELECT pp.post_parent FROM $wpdb->posts pp
					LEFT JOIN $pd_table papd ON pp.ID = papd.product_id
					WHERE pp.post_status IN('" . implode( "','", $statuses ) . "')
					AND papd.supplier_id = '$supplier'
				";

				$filter_by_supplier = " AND ( $pd_table.supplier_id = '$supplier' OR $wpdb->posts.ID IN ($subquery) ) ";

				$pieces['where']    .= apply_filters( 'atum/list_table/supplier_filter_query_data', $filter_by_supplier );
				$pieces['distinct'] = 'distinct';

			}

		}

		return Helpers::product_data_query_clauses( $this->atum_query_data, $pieces );

	}

	/**
	 * Add the supplier's variable products to the filtered query
	 *
	 * @since 1.4.1.1
	 *
	 * @param array  $products
	 * @param string $return_type Optional. The return type: 'ids' or 'posts'.
	 *
	 * @return array
	 */
	/** TODO: Remove if not needed
	public function add_supplier_variables_to_query( $products, $return_type = 'ids' ) {

		$search_column = isset( $_REQUEST['search_column'] ) ? esc_attr( stripslashes( $_REQUEST['search_column'] ) ) : FALSE;
		$search_term   = isset( $_REQUEST['s'] ) ? sanitize_text_field( urldecode( stripslashes( trim( $_REQUEST['s'] ) ) ) ) : FALSE;

		$searching = $search_column || $search_term ? $this->get_search_terms_ids( $search_column, $search_term, FALSE ) : FALSE;

		foreach ( $this->supplier_variation_products as $index => $variation_id ) {

			$variation_product = Helpers::get_atum_product( $variation_id );

			if ( ! $variation_product instanceof \WC_Product_Variation ) {
				unset( $this->supplier_variation_products[ $index ] );
				continue;
			}

			$is_controlled = Helpers::is_atum_controlling_stock( $variation_product );

			if ( ( $this->show_controlled && ! $is_controlled ) || ( ! $this->show_controlled && $is_controlled ) ) {
				unset( $this->supplier_variation_products[ $index ] );
				continue;
			}

			if ( FALSE === apply_filters( 'atum/list_table/check_supplier_variation', TRUE, $variation_product ) ) {
				unset( $this->supplier_variation_products[ $index ] );
				continue;
			}

			$variable_id = $variation_product->get_parent_id();
			$product_ids = 'ids' === $return_type ? $products : wp_list_pluck( $products, 'ID' );

			// Ignore variation if is not found searching by column.
			if ( FALSE !== $searching && empty( array_intersect( $searching, [ $variation_id, $variable_id ] ) ) ) {
				unset( $this->supplier_variation_products[ $index ] );
				continue;
			}

			if ( ! is_array( $products ) || ! in_array( $variable_id, $product_ids ) ) {
				$this->container_products['all_variable'][] = $this->container_products['variable'][] = $variable_id;

				$products[] = 'posts' === $return_type ? get_post( $variable_id ) : $variable_id;
			}

		}

		return $products;

	}*/

	/**
	 * Add the supplier's variation products to the filtered query
	 *
	 * @since 1.4.1.1
	 *
	 * @param array $variations
	 * @param array $products
	 *
	 * @return array
	 */
	/** TODO: Remove if not needed
	public function add_supplier_variations_to_query( $variations, $products ) {

		return array_merge( $variations, $this->supplier_variation_products );
	}*/

	/**
	 * Set views for table filtering and calculate total value counters for pagination
	 *
	 * @since 0.0.2
	 *
	 * @param array $args WP_Query arguments.
	 */
	protected function set_views_data( $args ) {

		global $wpdb;

		if ( $this->show_unmanaged_counters ) {

			// No need to use the getter in this function.
			$this->id_views = array_merge( $this->id_views, array(
				'managed'        => [],
				'unm_in_stock'   => [],
				'unm_out_stock'  => [],
				'unm_back_order' => [],
				'all_in_stock'   => [],
				'all_out_stock'  => [],
				'all_back_order' => [],
			) );

			$this->count_views = array_merge( $this->count_views, array(
				'count_managed'        => 0,
				'count_unm_in_stock'   => 0,
				'count_unm_out_stock'  => 0,
				'count_unm_back_order' => 0,
				'count_all_in_stock'   => 0,
				'count_all_out_stock'  => 0,
				'count_all_back_order' => 0,
			) );

		}

		// Get all the IDs in the two queries with no pagination.
		$args['fields']         = 'ids';
		$args['posts_per_page'] = - 1;
		unset( $args['paged'] );

		// TODO: PERHAPS THE TRANSIENT CAN BE USED MORE GENERALLY TO AVOID REPETITIVE WORK.
		$all_transient = AtumCache::get_transient_key( 'list_table_all', $this->get_transient_args() );
		$products      = AtumCache::get_transient( $all_transient );

		if ( ! $products ) {

			global $wp_query;

			// Pass through the ATUM query data filter.
			do_action( 'atum/list_table/set_views_data/before_query_data' );
			add_filter( 'posts_clauses', array( $this, 'atum_product_data_query_clauses' ) );
			$wp_query = new \WP_Query( apply_filters( 'atum/list_table/set_views_data/all_args', $args ) );
			remove_filter( 'posts_clauses', array( $this, 'atum_product_data_query_clauses' ) );
			do_action( 'atum/list_table/set_views_data/after_query_data' );

			$products = $wp_query->posts;

			// Save it as a transient to improve the performance.
			AtumCache::set_transient( $all_transient, $products );

		}

		// Let others play here.
		$products = (array) apply_filters( 'atum/list_table/views_data_products', $products );

		$this->count_views['count_all'] = count( $products );

		if ( $this->is_filtering && empty( $products ) ) {
			return;
		}

		// If it's a search or a product filtering, include only the filtered items to search for children.
		$post_in = $this->is_filtering ? $products : array();

		foreach ( $this->taxonomies as $index => $taxonomy ) {

			if ( 'product_type' === $taxonomy['taxonomy'] ) {

				if ( in_array( 'variable', (array) $taxonomy['terms'] ) ) {

					$variations = apply_filters( 'atum/list_table/views_data_variations', $this->get_children( 'variable', $post_in, 'product_variation' ), $post_in );

					// Remove the variable containers from the array and add the variations.
					$products = array_unique( array_merge( array_diff( $products, $this->container_products['all_variable'] ), $variations ) );

				}

				if ( in_array( 'grouped', (array) $taxonomy['terms'] ) ) {

					$group_items = apply_filters( 'atum/list_table/views_data_grouped', $this->get_children( 'grouped', $post_in ), $post_in );

					// Remove the grouped containers from the array and add the group items.
					$products = array_unique( array_merge( array_diff( $products, $this->container_products['all_grouped'] ), $group_items ) );

				}

				// WC Subscriptions compatibility.
				if ( class_exists( '\WC_Subscriptions' ) && in_array( 'variable-subscription', (array) $taxonomy['terms'] ) ) {

					$sc_variations = apply_filters( 'atum/list_table/views_data_sc_variations', $this->get_children( 'variable-subscription', $post_in, 'product_variation' ), $post_in );

					// Remove the variable subscription containers from the array and add the subscription variations.
					$products = array_unique( array_merge( array_diff( $products, $this->container_products['all_variable_subscription'] ), $sc_variations ) );

				}

				// WC Product Bundle compatibility.
				if ( class_exists( '\WC_Product_Bundle' ) && in_array( 'bundle', (array) $taxonomy['terms'] ) ) {

					$sc_bundles = apply_filters( 'atum/list_table/views_data_bundle', $this->get_children( 'bundle', $post_in ), $post_in );

					// Remove the bundle containers from the array and add the subscription variations.
					$products = array_unique( array_merge( array_diff( $products, $this->container_products['all_bundle'] ), $sc_bundles ) );

				}

				// Re-count the resulting products.
				$this->count_views['count_all'] = count( $products );

				// The grouped items must count once per group they belongs to and once individually.
				if ( ! empty( $group_items ) && ( empty( $_REQUEST['product_type'] ) || 'grouped' !== $_REQUEST['product_type'] ) ) {
					$this->count_views['count_all'] += count( $group_items );
				}

				do_action( 'atum/list_table/after_children_count', $taxonomy['terms'], $this );

				break;
			}

		}

		// For the Uncontrolled items, we don't need to calculate stock totals.
		if ( ! $this->show_controlled ) {
			return;
		}

		if ( $products ) {

			$post_types = ( ! empty( $variations ) || ! empty( $sc_variations ) ) ? [ $this->post_type, 'product_variation' ] : [ $this->post_type ];

			/*
			 * Unmanaged products
			 */
			if ( $this->show_unmanaged_counters ) {

				$products_unmanaged        = array();
				$products_unmanaged_status = Helpers::get_unmanaged_products( $post_types, TRUE );

				if ( ! empty( $products_unmanaged_status ) ) {

					// Filter the unmanaged (also removes uncontrolled).
					$products_unmanaged_status = array_filter( $products_unmanaged_status, function ( $row ) use ( $products ) {
						return in_array( $row[0], $products );
					} );

					$this->id_views['unm_in_stock'] = array_column( array_filter( $products_unmanaged_status, function ( $row ) {
						return 'instock' === $row[1];
					} ), 0 );

					$this->count_views['count_unm_in_stock'] = count( $this->get_id_views()['unm_in_stock'] );

					$this->id_views['unm_out_stock'] = array_column( array_filter( $products_unmanaged_status, function ( $row ) {
						return 'outofstock' === $row[1];
					} ), 0 );

					$this->count_views['count_unm_out_stock'] = count( $this->get_id_views()['unm_out_stock'] );

					$this->id_views['unm_back_order'] = array_column( array_filter( $products_unmanaged_status, function ( $row ) {
						return 'onbackorder' === $row[1];
					} ), 0 );

					$this->count_views['count_unm_back_order'] = count( $this->get_id_views()['unm_back_order'] );

					$products_unmanaged = array_column( $products_unmanaged_status, 0 );

					$this->id_views['managed'] = array_diff( $products, $products_unmanaged );
					// Need to substract count unmanaged because group items are not included twice in managed id_views.
					$this->count_views['count_managed'] = $this->count_views['count_all'] - count( $products_unmanaged );

				}

			}
			else {
				$products_unmanaged = array_column( Helpers::get_unmanaged_products( $post_types ), 0 );
			}

			// Remove the unmanaged from the products list.
			if ( ! empty( $products_unmanaged ) ) {

				// Filter the unmanaged (also removes uncontrolled).
				$products_unmanaged = array_intersect( $products, $products_unmanaged );

				$this->id_views['unmanaged']          = $products_unmanaged;
				$this->count_views['count_unmanaged'] = count( $products_unmanaged );

				if ( ! empty( $products_unmanaged ) ) {
					$products = ! empty( $this->count_views['count_managed'] ) ? $this->get_id_views()['managed'] : array_diff( $products, $products_unmanaged );
				}

			}

			/*
			 * Products args.
			 */
			$products_args = array(
				'post_type'      => $post_types,
				'posts_per_page' => - 1,
				'fields'         => 'ids',
				'post__in'       => $products,
			);

			$temp_atum_query_data = $this->atum_query_data;

			/*
			 * Products in stock.
			 */
			if ( ! empty( $this->atum_query_data['where'] ) ) {
				$this->atum_query_data['where']['relation'] = 'AND';
			}

			$this->atum_query_data['where'][] = array(
				'key'   => 'atum_stock_status',
				'value' => 'instock',
				'type'  => 'CHAR',
			);

			$in_stock_transient = AtumCache::get_transient_key( 'list_table_in_stock', $this->get_transient_args() );
			$products_in_stock  = AtumCache::get_transient( $in_stock_transient );

			if ( empty( $products_in_stock ) && ! empty( $products ) ) {
				add_filter( 'posts_clauses', array( $this, 'atum_product_data_query_clauses' ) );
				$products_in_stock = new \WP_Query( apply_filters( 'atum/list_table/set_views_data/in_stock_products_args', $products_args ) );
				remove_filter( 'posts_clauses', array( $this, 'atum_product_data_query_clauses' ) );
				AtumCache::set_transient( $in_stock_transient, $products_in_stock );
			}

			$this->atum_query_data = $temp_atum_query_data;
			$products_in_stock     = $products_in_stock instanceof \WP_Query && $products_in_stock->found_posts ?
				$products_in_stock->posts : [];

			$this->id_views['in_stock']          = (array) $products_in_stock;
			$this->count_views['count_in_stock'] = count( $products_in_stock );

			$products_not_stock = array_diff( (array) $products, (array) $products_in_stock, (array) $products_unmanaged );

			/**
			 * Products on Backorder.
			 */
			$products_args['post__in'] = $products_not_stock;

			if ( ! empty( $this->atum_query_data['where'] ) ) {
				$this->atum_query_data['where']['relation'] = 'AND';
			}

			$this->atum_query_data['where'][] = array(
				'key'   => 'atum_stock_status',
				'value' => 'onbackorder',
				'type'  => 'CHAR',
			);

			$backorders_transient = AtumCache::get_transient_key( 'list_table_backorders', $this->get_transient_args() );
			$products_backorders  = AtumCache::get_transient( $backorders_transient );

			if ( empty( $products_backorders ) && ! empty( $products_not_stock ) ) {
				add_filter( 'posts_clauses', array( $this, 'atum_product_data_query_clauses' ) );
				$products_backorders = new \WP_Query( apply_filters( 'atum/list_table/set_views_data/back_order_products_args', $products_args ) );
				remove_filter( 'posts_clauses', array( $this, 'atum_product_data_query_clauses' ) );
				AtumCache::set_transient( $backorders_transient, $products_backorders );
			}

			$this->atum_query_data = $temp_atum_query_data;
			$products_backorders   = $products_backorders instanceof \WP_Query && $products_backorders->found_posts ?
				$products_backorders->posts : [];

			$this->id_views['back_order']          = (array) $products_backorders;
			$this->count_views['count_back_order'] = count( $products_backorders );

			// As the Group items might be displayed multiple times, we should count them multiple times too.
			if ( ! empty( $group_items ) && ( empty( $_REQUEST['product_type'] ) || 'grouped' !== $_REQUEST['product_type'] ) ) {
				$this->count_views['count_in_stock']   += count( array_intersect( $group_items, (array) $products_in_stock ) );
				$this->count_views['count_back_order'] += count( array_intersect( $group_items, (array) $products_backorders ) );
			}

			/**
			 * Products in restock status
			 */
			if ( ! empty( $products_in_stock ) ) {

				$restock_status_transient = AtumCache::get_transient_key( 'list_table_restock_status', $this->get_transient_args() );
				$products_restock_status  = AtumCache::get_transient( $restock_status_transient );

				if ( empty( $products_restock_status ) ) {

					$atum_product_data_table = $wpdb->prefix . Globals::ATUM_PRODUCT_DATA_TABLE;
					$str_sql                 = apply_filters( 'atum/list_table/set_views_data/restock_status_products', "
						SELECT product_id FROM $atum_product_data_table WHERE restock_status = 1
					" );

					$products_restock_status = $wpdb->get_col( $str_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
					AtumCache::set_transient( $restock_status_transient, $products_restock_status );

				}

				$products_restock_status = ! empty( $products_restock_status ) ? $products_restock_status : [];

				$this->id_views['restock_status']          = (array) $products_restock_status;
				$this->count_views['count_restock_status'] = count( $products_restock_status );

			}

			/**
			 * Products out of stock
			 */
			$products_out_stock = array_diff( $products_not_stock, (array) $products_backorders );

			$this->id_views['out_stock']          = $products_out_stock;
			$this->count_views['count_out_stock'] = $this->count_views['count_all'] - $this->count_views['count_in_stock'] - $this->count_views['count_back_order'] - $this->count_views['count_unmanaged'];

			/**
			 * Calculate totals
			 */
			if ( $this->show_unmanaged_counters ) {

				$this->id_views['all_in_stock']          = array_merge( $this->get_id_views()['in_stock'], $this->get_id_views()['unm_in_stock'] );
				$this->count_views['count_all_in_stock'] = $this->count_views['count_in_stock'] + $this->count_views['count_unm_in_stock'];

				$this->id_views['all_out_stock']          = array_merge( $this->get_id_views()['out_stock'], $this->get_id_views()['unm_out_stock'] );
				$this->count_views['count_all_out_stock'] = $this->count_views['count_out_stock'] + $this->count_views['count_unm_out_stock'];

				$this->id_views['all_back_order']          = array_merge( $this->get_id_views()['back_order'], $this->get_id_views()['unm_back_order'] );
				$this->count_views['count_all_back_order'] = $this->count_views['count_back_order'] + $this->count_views['count_unm_back_order'];

			}

		}

	}

	/**
	 * Print column headers, accounting for hidden and sortable columns
	 *
	 * @since 1.4.5
	 *
	 * @param bool $with_id Whether to set the id attribute or not.
	 */
	public function print_column_headers( $with_id = TRUE ) {

		list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();

		$group_members = wp_list_pluck( $this->group_members, 'members' );

		$current_url     = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
		$current_url     = remove_query_arg( 'paged', $current_url );
		$current_orderby = isset( $_GET['orderby'] ) ? esc_attr( $_GET['orderby'] ) : '';
		$current_order   = ( ! isset( $_GET['order'] ) || 'desc' === $_GET['order'] ) ? 'desc' : 'asc';

		if ( ! empty( $columns['cb'] ) ) {
			static $cb_counter = 1;

			$columns['cb'] = '<label class="screen-reader-text" for="cb-select-all-' . $cb_counter . '">' . esc_html__( 'Select All', ATUM_TEXT_DOMAIN ) . '</label><input id="cb-select-all-' . $cb_counter . '" type="checkbox" />';
			$cb_counter++;
		}

		foreach ( $columns as $column_key => $column_display_name ) {

			$class = array( 'manage-column', "column-$column_key" );

			if ( in_array( $column_key, $hidden ) ) {
				$class[] = 'hidden';
			}

			// Check if it's a numeric column.
			if (
				! empty( $this->searchable_columns['numeric'] ) && is_array( $this->searchable_columns['numeric'] ) &&
				in_array( $column_key, $this->searchable_columns['numeric'], TRUE )
			) {
				$class[] = 'numeric';
			}

			if ( 'cb' === $column_key ) {
				$class[] = 'check-column';
			}
			elseif ( in_array( $column_key, array( 'posts', 'comments', 'links' ) ) ) {
				$class[] = 'num';
			}

			if ( $column_key === $primary ) {
				$class[] = 'column-primary';
			}

			// Add the group key as class.
			foreach ( $group_members as $group_key => $members ) {
				if ( in_array( $column_key, $members ) ) {
					$class[] = $group_key;
					break;
				}
			}

			if ( isset( $sortable[ $column_key ] ) ) {

				list( $orderby, $desc_first ) = $sortable[ $column_key ];

				if ( $current_orderby === $orderby ) {
					$order   = 'asc' === $current_order ? 'desc' : 'asc';
					$class[] = 'sorted';
					$class[] = $current_order;
				}
				else {
					$order   = $desc_first ? 'desc' : 'asc';
					$class[] = 'sortable';
					$class[] = $desc_first ? 'asc' : 'desc';
				}

				$sorting_params = compact( 'orderby', 'order' );
				$sorting_url    = esc_url( add_query_arg( $sorting_params, $current_url ) );
				$hash_params    = http_build_query( array_merge( $this->query_filters, $sorting_params ) );

				$column_display_name = '<a href="' . $sorting_url . '" rel="address:/?' . $hash_params . '"><span>' . $column_display_name . '</span><span class="sorting-indicator"></span></a>';

			}

			$tag   = 'cb' === $column_key ? 'td' : 'th';
			$scope = 'th' === $tag ? 'scope="col"' : '';
			$id    = apply_filters( 'atum/list_table/display_column_id', $with_id ) ? "id='$column_key'" : '';

			if ( ! empty( $class ) ) {
				$class = "class='" . join( ' ', $class ) . "'";
			}

			echo "<$tag $scope $id $class>$column_display_name</$tag>"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

		}

	}

	/**
	 * Prints the columns that groups the distinct header columns
	 *
	 * @since 0.0.1
	 */
	public function print_group_columns() {

		if ( ! empty( $this->group_columns ) ) {

			echo '<tr class="column-groups">';

			foreach ( $this->group_columns as $group_column ) {

				$data = $group_column['collapsed'] ? ' data-collapsed="1"' : '';

				// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				echo '<th class="' . esc_attr( $group_column['name'] ) . '" colspan="' . esc_attr( $group_column['colspan'] ) . '"' . $data . '><span>' . $group_column['title'] . '</span>';

				if ( $group_column['toggler'] ) {
					/* translators: the column group title */
					$data_tip = ! static::$is_report ? ' data-tip="' . esc_attr( sprintf( __( "Show/Hide the '%s' columns", ATUM_TEXT_DOMAIN ), $group_column['title'] ) ) . '"' : '';

					echo '<span class="group-toggler tips"' . $data_tip . '></span>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				}

				echo '</th>';

			}

			echo '</tr>';

		}
	}

	/**
	 * Prints the totals columns on totals row at table footer
	 *
	 * @since 1.4.2
	 */
	public function print_totals_columns() {

		// Does not show the totals row if there are no results.
		if ( empty( $this->items ) ) {
			return;
		}

		/* @noinspection PhpUnusedLocalVariableInspection */
		list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();

		$group_members = wp_list_pluck( $this->group_members, 'members' );
		$column_keys   = array_keys( $columns );
		$first_column  = current( $column_keys );
		$second_column = next( $column_keys );

		// Let to adjust the totals externally if needed.
		$this->totalizers = apply_filters( 'atum/list_table/totalizers', $this->totalizers );

		foreach ( $columns as $column_key => $column_display ) {

			$class   = array( 'manage-column', "column-$column_key" );
			$colspan = '';

			if ( in_array( $column_key, $hidden ) ) {
				$class[] = 'hidden';
			}

			if ( $first_column === $column_key ) {

				$class[] = 'totals-heading';

				// Set a colspan of 2 if the checkbox column is present and the second column isn't hidden.
				if ( 'cb' === $first_column && ! in_array( $second_column, $hidden ) ) {
					$colspan = 'colspan="2"';
				}

				$column_display = '<span>' . __( 'Totals', ATUM_TEXT_DOMAIN ) . '</span>';

			}
			elseif ( 'cb' === $first_column && $second_column === $column_key ) {
				continue; // Get rid of the second column as the first one will have a colspan.
			}
			elseif ( in_array( $column_key, array_keys( $this->totalizers ) ) ) {
				$total          = $this->totalizers[ $column_key ];
				$total_class    = $total < 0 ? ' class="danger"' : '';
				$column_display = "<span{$total_class}>" . round( $total, 2, PHP_ROUND_HALF_UP ) . '</span>';
			}
			else {
				$column_display = '';
			}

			if ( $column_key === $primary ) {
				$class[] = 'column-primary';
			}

			// Add the group key as class.
			foreach ( $group_members as $group_key => $members ) {
				if ( in_array( $column_key, $members ) ) {
					$class[] = $group_key;
					break;
				}
			}

			$tag   = 'cb' === $column_key ? 'td' : 'th';
			$scope = 'th' === $tag ? 'scope="col"' : '';

			if ( ! empty( $class ) ) {
				$class = "class='" . join( ' ', $class ) . "'";
			}

			echo "<$tag $scope $class $colspan>$column_display</th>"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

		}

	}

	/**
	 * Adds the data needed for ajax filtering, sorting and pagination and displays the table
	 *
	 * @since 0.0.1
	 */
	public function display() {

		do_action( 'atum/list_table/before_display', $this );

		$singular = $this->_args['singular'];
		$this->display_tablenav( 'top' );
		$this->screen->render_screen_reader_content( 'heading_list' );

		?>
		<div class="atum-table-wrapper">
			<table class="wp-list-table atum-list-table <?php echo implode( ' ', $this->get_table_classes() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"
				data-currency-pos="<?php echo esc_attr( get_option( 'woocommerce_currency_pos', 'left' ) ) ?>">

				<thead>
					<?php $this->print_group_columns(); ?>

					<tr class="item-heads">
						<?php $this->print_column_headers(); ?>
					</tr>
				</thead>

				<?php // NOTE: to avoid issues with mPDF, the tfoot should be placed above the tbody (as per HTML4 specs). ?>
				<tfoot>

					<?php if ( $this->show_totals ) : ?>
						<tr class="totals">
							<?php $this->print_totals_columns(); ?>
						</tr>
					<?php endif ?>

					<tr class="item-heads">
						<?php $this->print_column_headers( FALSE ); ?>
					</tr>

				</tfoot>

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

			</table>

			<input type="hidden" name="atum-column-edits" value="">
		</div>
		<?php

		$this->display_tablenav( 'bottom' );
		global $plugin_page;

		// Prepare JS vars.
		$vars = array(
			'ajaxFilter'                     => Helpers::get_option( 'enable_ajax_filter', 'yes' ),
			'apply'                          => __( 'Apply', ATUM_TEXT_DOMAIN ),
			'applyAction'                    => __( 'Apply Action', ATUM_TEXT_DOMAIN ),
			'applyBulkAction'                => __( 'Apply Bulk Action', ATUM_TEXT_DOMAIN ),
			'beginning'                      => __( 'Beginning', ATUM_TEXT_DOMAIN ),
			'confirmNewSupplier'             => __( 'create supplier', ATUM_TEXT_DOMAIN ),
			'createSupplierNonce'            => wp_create_nonce( 'create-supplier-nonce' ),
			'currencyFormat'                 => esc_attr( str_replace( [ '%1$s', '%2$s' ], [ '%s', '%v' ], get_woocommerce_price_format() ) ),
			'currencyFormatDecimalSeparator' => wc_get_price_decimal_separator(),
			'currencyFormatNumDecimals'      => wc_get_price_decimals(),
			'dateSelectorFilters'            => [ 'best_seller', 'worst_seller' ],
			'done'                           => __( 'Done!', ATUM_TEXT_DOMAIN ),
			'emptyCol'                       => self::EMPTY_COL,
			'editLocations'                  => __( 'Edit Locations', ATUM_TEXT_DOMAIN ),
			'editLocationsInfo'              => __( 'Click on the location icons to switch the states. Locations marked with blue icons will be set and with gray icons will be unset.', ATUM_TEXT_DOMAIN ),
			'editProductLocations'           => __( 'Edit Product Locations', ATUM_TEXT_DOMAIN ),
			'extraFiltersConfig'			 => $this->get_extra_filters(),
			'from'                           => __( 'From', ATUM_TEXT_DOMAIN ),
			'listUrl'                        => esc_url( add_query_arg( 'page', $plugin_page, admin_url() ) ),
			'locationsSaved'                 => __( 'Locations saved successfully', ATUM_TEXT_DOMAIN ),
			'newSupplier'                    => __( 'New Supplier', ATUM_TEXT_DOMAIN ),
			'noItemsSelected'                => __( 'No Items Selected', ATUM_TEXT_DOMAIN ),
			'noActions'                      => __( 'No actions', ATUM_TEXT_DOMAIN ),
			'nonce'                          => wp_create_nonce( 'atum-list-table-nonce' ),
			'ok'                             => __( 'OK', ATUM_TEXT_DOMAIN ),
			'order'                          => $this->_pagination_args['order'] ?? '',
			'orderby'                        => $this->_pagination_args['orderby'] ?? '',
			'perPage'                        => $this->per_page,
			'productLocations'               => __( 'Product Locations', ATUM_TEXT_DOMAIN ),
			'rowActions'                     => self::$row_actions,
			'saveButton'                     => __( 'Save', ATUM_TEXT_DOMAIN ),
			'searchableColumns'              => $this->searchable_columns,
			'selectDateRange'                => __( 'Select the date range to filter the products.', ATUM_TEXT_DOMAIN ),
			'selectItems'                    => __( 'Please, check the boxes for all the items to which you want to apply this bulk action', ATUM_TEXT_DOMAIN ),
			'setButton'                      => __( 'Set', ATUM_TEXT_DOMAIN ),
			'setTimeWindow'                  => __( 'Set Time Window', ATUM_TEXT_DOMAIN ),
			'setValue'                       => __( 'Set the %% value', ATUM_TEXT_DOMAIN ),
			'selectSetValue'                 => __( 'Set %%', ATUM_TEXT_DOMAIN ),
			'showCb'                         => $this->show_cb,
			'stickyColumns'                  => $this->sticky_columns,
			'stickyColumnsNonce'             => wp_create_nonce( 'atum-sticky-columns-button-nonce' ),
			'supplierNameRequired'           => __( 'The supplier name is required', ATUM_TEXT_DOMAIN ),
			'to'                             => __( 'To', ATUM_TEXT_DOMAIN ),
		);

		$vars = array_merge( $vars, Globals::get_date_time_picker_js_vars() );

		if ( $this->first_edit_key ) {
			$vars['firstEditKey']      = $this->first_edit_key;
			$vars['important']         = __( 'Important!', ATUM_TEXT_DOMAIN );
			$vars['preventLossNotice'] = __( "To prevent any loss of data, please, hit the blue 'Save Data' button at the top left after completing edits.", ATUM_TEXT_DOMAIN );
		}

		// Add the help guide vars if needed.
		if ( $this->help_guide ) {
			$vars = array_merge( $vars, AtumHelpGuide::get_instance()->get_help_guide_js_vars( $this->help_guide, $this->help_guide ) );
		}

		$vars      = apply_filters( 'atum/list_table/js_vars', $vars );
		$list_name = str_replace( '-', '_', $this->id );
		wp_localize_script( 'atum-list', "atumListVars{$list_name}", $vars );

		if ( ModuleManager::is_module_active( 'purchase_orders' ) && AtumCapabilities::current_user_can( 'create_suppliers' ) ) {
			Helpers::load_view( ATUM_PATH . 'views/js-templates/create-supplier' );
		}

		do_action( 'atum/list_table/after_display', $this );

	}

	/**
	 * Generate the table navigation above or below the table
	 * Just the parent function but removing the nonce fields that are not required here
	 *
	 * @since 0.0.1
	 *
	 * @param string $which 'top' or 'bottom' table nav.
	 */
	protected function display_tablenav( $which ) {

		ob_start();
		$this->extra_tablenav( $which );
		$extra_table_nav = ob_get_clean();
		?>
		<div class="tablenav <?php echo esc_attr( $which ); ?> extend-list-table">

			<?php if ( ! empty( $this->get_bulk_actions() ) || $extra_table_nav ) : ?>
				<div class="scroll-filters-container filters-container-box <?php echo 'top' === $which && ( empty( $this->_pagination_args['total_pages'] ) || $this->_pagination_args['total_pages'] <= 1 ) ? 'no-pagination' : ''; ?><?php echo 'no' !== Helpers::get_option( 'enable_ajax_filter', 'yes' ) ? ' no-submit' : ''; ?>">
					<div class="filters-container<?php echo 'top' === $which ? ' nav-with-scroll-effect dragscroll' : '' ?>">

						<?php if ( ! empty( $this->get_bulk_actions() ) ) : ?>
						<div class="alignleft actions bulkactions">
							<?php $this->bulk_actions( $which ); ?>
						</div>
						<?php endif; ?>

						<?php echo $extra_table_nav; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>

						<?php if ( 'top' === $which ) : ?>
							<div class="overflow-opacity-effect-right"></div>
							<div class="overflow-opacity-effect-left"></div>
						<?php endif; ?>

					</div>
				</div>
			<?php endif;

			// Firefox fix to not preserve the pagination input value when reloading the page.
			ob_start(); ?>

			<div class="tablenav-pages-container<?php echo empty( $this->_pagination_args['total_pages'] ) || $this->_pagination_args['total_pages'] <= 1 ? ' one-page' : ''; ?><?php echo 'no' !== Helpers::get_option( 'enable_ajax_filter', 'yes' ) ? ' no-submit' : ''; ?>">

				<?php if ( 'no' === Helpers::get_option( 'enable_ajax_filter', 'yes' ) ) : ?>
					<input type="submit" name="filter_action" class="btn btn-warning search-category hidden-sm" value="<?php esc_attr_e( 'Filter', ATUM_TEXT_DOMAIN ) ?>">
				<?php endif; ?>

				<?php
				$this->pagination( $which );
				echo str_replace( '<input ', '<input autocomplete="off" ', ob_get_clean() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				?>

			</div>
		</div>
		<?php

	}

	/**
	 * Message to be displayed when there are no items
	 *
	 * @since 0.0.1
	 */
	public function no_items() {

		$post_type_obj = get_post_type_object( $this->post_type );
		echo $post_type_obj->labels->not_found; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

		if ( isset( $_REQUEST['s'] ) && strlen( $_REQUEST['s'] ) > 0 ) {
			/* translators: the search query */
			printf( __( " with query '%s'", ATUM_TEXT_DOMAIN ), stripslashes( esc_attr( $_REQUEST['s'] ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		}

	}

	/**
	 * Display the pagination.
	 *
	 * @since 1.4.3
	 *
	 * @param string $which
	 */
	protected function pagination( $which ) {

		if ( empty( $this->_pagination_args ) ) {
			$output = '<span class="displaying-num">' . esc_html__( '0 items', ATUM_TEXT_DOMAIN ) . '</span>';
			echo "<div class='tablenav-pages extend-list-table'>$output</div>"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			return;
		}

		$total_items = $this->_pagination_args['total_items'];
		$total_pages = $this->_pagination_args['total_pages'];

		if ( 'top' === $which && $total_pages > 1 ) {
			$this->screen->render_screen_reader_content( 'heading_pagination' );
		}

		/* translators: the number of items */
		$output = '<span class="displaying-num">' . sprintf( _n( '%s item', '%s items', $total_items, ATUM_TEXT_DOMAIN ), number_format_i18n( $total_items ) ) . '</span>';

		$current              = $this->get_pagenum();
		$removable_query_args = wp_removable_query_args();

		$current_url        = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
		$current_url        = remove_query_arg( $removable_query_args, $current_url );
		$page_links         = array();
		$total_pages_before = '<span class="paging-input">';
		$total_pages_after  = '</span></span>';

		$disable_first = $disable_last = $disable_prev = $disable_next = FALSE;

		$total_pages = (int) $total_pages;

		if ( 1 === $current ) {
			$disable_first = TRUE;
			$disable_prev  = TRUE;
		}

		if ( $current === $total_pages ) {
			$disable_last = TRUE;
			$disable_next = TRUE;
		}

		if ( $disable_first ) {
			$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&laquo;</span>';
		}
		else {

			$page_links[] = sprintf(
				"<a class='first-page' href='%1\$s' rel='address:/?%2\$s'><span class='screen-reader-text'>%3\$s</span><span aria-hidden='true'>%4\$s</span></a>",
				esc_url( remove_query_arg( 'paged', $current_url ) ),
				http_build_query( array_merge( $this->query_filters, [ 'paged' => 1 ] ) ),
				__( 'First page', ATUM_TEXT_DOMAIN ),
				'&laquo;'
			);

		}

		if ( $disable_prev ) {
			$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&lsaquo;</span>';
		}
		else {

			$prev_page    = max( 1, $current - 1 );
			$page_links[] = sprintf(
				"<a class='prev-page' href='%1\$s' rel='address:/?%2\$s'><span class='screen-reader-text'>%3\$s</span><span aria-hidden='true'>%4\$s</span></a>",
				esc_url( add_query_arg( 'paged', $prev_page, $current_url ) ),
				http_build_query( array_merge( $this->query_filters, [ 'paged' => $prev_page ] ) ),
				__( 'Previous page', ATUM_TEXT_DOMAIN ),
				'&lsaquo;'
			);

		}

		if ( 'bottom' === $which ) {
			$current_page_style = 'tablenav-current-page';
			$html_current_page  = $current;
			$total_pages_before = '<span class="screen-reader-text">' . __( 'Current Page', ATUM_TEXT_DOMAIN ) . '</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">';
		}
		else {
			$current_page_style = '';
			/* @noinspection PhpFormatFunctionParametersMismatchInspection */
			$html_current_page = sprintf( "%1\$s<input class='current-page' data-current='%2\$s' id='current-page-selector' type='text' name='paged' value='%2\$s' size='%3\$d' aria-describedby='table-paging' /><span class='tablenav-paging-text'>",
				'<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page', ATUM_TEXT_DOMAIN ) . '</label>',
				$current,
				strlen( $total_pages )
			);
		}

		$html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
		/* translators: first one is the current page number and sesond is the total number of pages */
		$page_links[] = $total_pages_before . sprintf( _x( '%1$s of %2$s', 'paging', ATUM_TEXT_DOMAIN ), '<span class="' . esc_attr( $current_page_style ) . '">' . $html_current_page . '</span>', $html_total_pages ) . $total_pages_after;

		if ( $disable_next ) {
			$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&rsaquo;</span>';
		}
		else {

			$next_page    = min( $total_pages, $current + 1 );
			$page_links[] = sprintf(
				"<a class='next-page' href='%1\$s' rel='address:/?%2\$s'><span class='screen-reader-text'>%3\$s</span><span aria-hidden='true'>%4\$s</span></a>",
				esc_url( add_query_arg( 'paged', $next_page, $current_url ) ),
				http_build_query( array_merge( $this->query_filters, [ 'paged' => $next_page ] ) ),
				__( 'Next page', ATUM_TEXT_DOMAIN ),
				'&rsaquo;'
			);
		}

		if ( $disable_last ) {
			$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&raquo;</span>';
		}
		else {

			$page_links[] = sprintf(
				"<a class='last-page' href='%1\$s' rel='address:/?%2\$s'><span class='screen-reader-text'>%3\$s</span><span aria-hidden='true'>%4\$s</span></a>",
				esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
				http_build_query( array_merge( $this->query_filters, [ 'paged' => $total_pages ] ) ),
				__( 'Last page', ATUM_TEXT_DOMAIN ),
				'&raquo;'
			);

		}

		$pagination_links_class = 'pagination-links';

		$output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>';

		if ( $total_pages ) {
			$page_class = $total_pages < 2 ? ' one-page' : '';
		}
		else {
			$page_class = ' no-pages';
		}

		$this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";

		echo $this->_pagination; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

	}

	/**
	 * Get a list of CSS classes for the WP_List_Table table tag. Deleted 'fixed' from standard function
	 *
	 * @since  0.0.2
	 *
	 * @return array List of CSS classes for the table tag.
	 */
	protected function get_table_classes() {

		return array( 'widefat', $this->_args['plural'] );
	}

	/**
	 * A wrapper to get the right product ID (or variation ID)
	 *
	 * @since 1.2.1
	 *
	 * @return int
	 */
	protected function get_current_list_item_id() {

		if ( 'variation' === $this->list_item->get_type() ) {
			/**
			 * Deprecated notice
			 *
			 * @deprecated
			 * The get_variation_id() method was deprecated in WC 3.0.0
			 * In newer versions the get_id() method always be the variation_id if it's a variation
			 */
			/* @noinspection PhpDeprecationInspection */
			return version_compare( WC()->version, '3.0.0', '<' ) ? $this->list_item->get_variation_id() : $this->list_item->get_id();
		}

		return $this->list_item->get_id();

	}

	/**
	 * Gets the array needed to print html group columns in the table
	 *
	 * @since 0.0.1
	 *
	 * @param array $hidden Hidden columns.
	 *
	 * @return array
	 */
	public function calc_groups( $hidden ) {

		$response = array();

		foreach ( $this->group_members as $name => $group ) {

			$counter = 0;

			foreach ( $group['members'] as $member ) {

				// Check that the current member isn't hidden and the column is enabled.
				if ( ! in_array( $member, $hidden ) && array_key_exists( $member, self::$table_columns ) ) {
					$counter ++;
				}

			}

			// Add the group only if there are columns within.
			if ( $counter ) {

				$response[] = array(
					'name'      => $name,
					'title'     => $group['title'],
					'colspan'   => $counter,
					'toggler'   => ! empty( $group['toggler'] ) && $group['toggler'],
					'collapsed' => ! empty( $group['collapsed'] ) && $group['collapsed'],
				);

			}
		}

		return $response;

	}

	/**
	 * Return the group of columns that a specific column belongs to or false
	 *
	 * @sinece 0.0.5
	 *
	 * @param string $column  The column to search to.
	 *
	 * @return bool|string
	 */
	public function search_group_columns( $column ) {

		foreach ( $this->group_members as $name => $group_member ) {

			if ( in_array( $column, $group_member['members'] ) ) {
				return $name;
			}

		}

		return FALSE;
	}

	/**
	 * Search products by: A (post_title, post_excerpt, post_content ), B (posts.ID), C (posts.title), D (other meta fields wich can be numeric or not)
	 *
	 * @since 1.4.8
	 *
	 * @param string    $search_where The search piece used in the query's SQL.
	 * @param \WP_Query $wp_query     The WP_Query object (passed by reference).
	 *
	 * @return string
	 */
	public function posts_search( $search_where, $wp_query ) {

		global $pagenow, $wpdb;

		// Changed the WooCommerce's "product_search" filter to allow Ajax requests.
		/* @see \WC_Admin_Post_Types::product_search() */

		if (
			! is_admin() || 'product' !== $this->post_type ||
			! in_array( $pagenow, array( 'edit.php', 'admin-ajax.php' ) ) ||
			! isset( $_REQUEST['s'], $_REQUEST['action'] ) || ! str_contains( $_REQUEST['action'], ATUM_PREFIX )
		) {
			return $search_where;
		}

		// Prevent keyUp problems.
		// Scenario: do a search with s and search_column, clean s, change search_column... and you will get nothing (s still set on url).
		if ( empty( trim( $_REQUEST['s'] ) ) && '0' !== trim( $_REQUEST['s'] ) ) {
			return ' AND ( 1 = 1 )';
		}

		$search_column = esc_attr( stripslashes( $_REQUEST['search_column'] ) );
		$search_term   = sanitize_text_field( urldecode( stripslashes( trim( $_REQUEST['s'] ) ) ) );

		$cache_key    = AtumCache::get_cache_key( 'product_search', [ $search_column, $search_term ] );
		$search_where = AtumCache::get_cache( $cache_key, ATUM_TEXT_DOMAIN, FALSE, $has_cache );

		if ( $has_cache ) {
			return $search_where;
		}

		$search_terms = $this->parse_search( $search_term );
		$search_where = $this->get_search_terms_ids( $search_column, $search_term );

		// We've to overwrite the cache generated by ATUM to ensure that the right where clause is set.
		AtumCache::set_cache( $cache_key, $search_where );

		return apply_filters( 'atum/list_table/posts_search/where', $search_where, $search_column, $search_term, $search_terms, $cache_key );

	}

	/**
	 * Get search terms ids.
	 *
	 * @since 1.9.6
	 *
	 * @param string $search_column The column where search in.
	 * @param string $search_term   The searched term.
	 * @param bool   $get_query     Return where query (TRUE) or terms_ids array (FALSE).
	 *
	 * @return array|string
	 */
	public function get_search_terms_ids( $search_column, $search_term, $get_query = TRUE ) {

		global $wpdb;

		$search_where     = '';
		$search_terms_ids = array();

		// If we don't get any result looking for a field, we must force an empty result before
		// WP tries to query {$wpdb->posts}.ID IN ( 'empty value' ), which raises an error.
		$where_without_results = " AND ( {$wpdb->posts}.ID = -1 )";
		$post_type_where       = " post_type IN ('product', 'product_variation')";

		$cache_key    = AtumCache::get_cache_key( 'product_search', [ $search_column, $search_term ] );
		$search_terms = $this->parse_search( $search_term );

		if ( empty( $search_terms ) ) {
			AtumCache::set_cache( $cache_key, $where_without_results );
			return $get_query ? $where_without_results : [];
		}

		//
		// Regular search in post_title, post_excerpt and post_content (with no column selected).
		// --------------------------------------------------------------------------------------!
		if ( empty( $search_column ) ) {

			$search_query = $this->build_search_query( $search_terms );

			$query = "
				SELECT ID, post_type, post_parent FROM $wpdb->posts
		        WHERE $post_type_where 
		        AND $search_query
	         ";

			$search_terms_results = $wpdb->get_results( $query, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

			if ( empty( $search_terms_results ) ) {
				AtumCache::set_cache( $cache_key, $where_without_results );
				return apply_filters( 'atum/list_table/posts_search/where', $where_without_results, $search_column, $search_term, $search_terms, $cache_key );
			}

			foreach ( $search_terms_results as $product_row ) {

				array_push( $search_terms_ids, $product_row['ID'] );

				// For variations: add their parent too.
				if ( 'product_variation' === $product_row['post_type'] ) {
					array_push( $search_terms_ids, $product_row['post_parent'] );
				}

			}

			$search_where = " AND ( {$wpdb->posts}.ID IN (" . implode( ',', array_unique( $search_terms_ids ) ) . ') )';

		}
		//
		// Search by column.
		// ------------------!
		elseif ( Helpers::in_multi_array( $search_column, $this->searchable_columns ) ) {

			$column_name = ltrim( $search_column, '_' );

			//
			// Search by ID.
			// -------------!
			if ( 'ID' === $search_column ) {

				$search_query = $this->build_search_query( $search_terms, $search_column, 'int' );

				// Get all (parent and variations, and build where).
				$query = "
					SELECT ID, post_type, post_parent FROM $wpdb->posts
				    WHERE $search_query
				    AND $post_type_where
			    ";

				$search_term_id = $wpdb->get_row( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

				if ( empty( $search_term_id ) ) {
					AtumCache::set_cache( $cache_key, $where_without_results );
					return apply_filters( 'atum/list_table/posts_search/where', $where_without_results, $search_column, $search_term, $search_terms, $cache_key );
				}

				$search_terms_ids_str = '';

				if ( 'product' === $search_term_id->post_type ) {

					$search_terms_ids_str .= $search_term_id->ID . ',';

					// If has children, add them.
					$product = wc_get_product( $search_term_id->ID );

					// Exclude grouped products children if searching by id.
					if ( 'grouped' !== $product->get_type() ) {

						// Get an array of the children IDs (if any).
						$children = $product->get_children();

						if ( ! empty( $children ) ) {
							foreach ( $children as $child ) {
								$search_terms_ids_str .= $child . ',';
							}
						}
					}

				}
				// Add parent and current.
				else {
					$search_terms_ids_str .= $search_term_id->post_parent . ',';
					$search_terms_ids_str .= $search_term_id->ID . ',';
				}

				$search_terms_ids_str = rtrim( $search_terms_ids_str, ',' );
				$search_where         = " AND ( $wpdb->posts.ID IN ($search_terms_ids_str) )";

			}
			//
			// Search by Supplier name.
			// NOTE: This is commented for now because suppliers have their own filter.
			// ------------------------!
			/*elseif ( Suppliers::SUPPLIER_META_KEY === $search_column ) {

				$search_query = $this->build_search_query( $search_terms, 'post_title' );

				// Get suppliers.
				// phpcs:disable WordPress.DB.PreparedSQL
				$query = $wpdb->prepare( "
					SELECT ID FROM $wpdb->posts
				    WHERE post_type = %s AND $search_query",
					Suppliers::POST_TYPE
				);
				// phpcs:enable

				$search_supplier_ids = $wpdb->get_col( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

				if ( empty( $search_supplier_ids ) ) {
					AtumCache::set_cache( $cache_key, $where_without_results );
					return apply_filters( 'atum/list_table/posts_search/where', $where_without_results, $search_column, $search_term, $search_terms, $cache_key );
				}

				$supplier_products = array();

				// Avoid endless loops.
				remove_filter( 'posts_search', array( $this, 'posts_search' ) );

				foreach ( $search_supplier_ids as $supplier_id ) {
					$supplier_products = array_merge( $supplier_products, Suppliers::get_supplier_products( $supplier_id ) );
				}

				add_filter( 'posts_search', array( $this, 'posts_search' ), 10, 2 );

				$supplier_products = array_unique( $supplier_products );

				if ( empty( $supplier_products ) ) {
					AtumCache::set_cache( $cache_key, $where_without_results );
					return apply_filters( 'atum/list_table/posts_search/where', $where_without_results, $search_column, $search_term, $search_terms, $cache_key );
				}

				$search_where = " AND $wpdb->posts.ID IN (" . implode( ',', $supplier_products ) . ')';

			}*/
			//
			// Search by title and other calc or meta fields.
			// ----------------------------------------------!
			else {

				$atum_data_table = $wpdb->prefix . Globals::ATUM_PRODUCT_DATA_TABLE;

				// Title field is not in meta.
				if ( 'title' === $search_column ) {

					$search_query = $this->build_search_query( $search_terms, 'post_title' );

					$query = "
						SELECT ID, post_type, post_parent FROM $wpdb->posts
				        WHERE $search_query 
				        AND $post_type_where	
			         ";

				}
				/**
				 *  Numeric fields.
				 */
				elseif ( in_array( $search_column, $this->searchable_columns['numeric'] ) ) {

					// Search by ATUM product data columns.
					if ( in_array( $search_column, $this->get_searchable_atum_columns() ) ) {

						$search_query = $this->build_search_query( $search_terms, $column_name, 'float', 'apd' );
						$meta_where   = apply_filters( 'atum/list_table/posts_search/numeric_meta_where', $search_query, $search_column, $search_terms );

						$query = "
							SELECT DISTINCT p.ID, p.post_type, p.post_parent FROM $wpdb->posts p
						    LEFT JOIN $atum_data_table apd ON (p.ID = apd.product_id)
						    WHERE $post_type_where
						    AND $meta_where
					    ";

					}
					// Search using the old way (meta keys).
					else {

						$search_query = $this->build_search_query( $search_terms, $search_column, 'string', 'pm', TRUE );
						$meta_where   = apply_filters( 'atum/list_table/posts_search/numeric_meta_where', $search_query, $search_column, $search_terms );

						$query = "
							SELECT DISTINCT p.ID, p.post_type, p.post_parent FROM $wpdb->posts p
						    LEFT JOIN $wpdb->postmeta pm ON (p.ID = pm.post_id)
						    WHERE $post_type_where
						    AND $meta_where
					    ";

					}

				}
				/**
				 * String fields.
				 */
				else {

					// Search by supplier SKU (or any other possible ATUM string col).
					if ( in_array( $search_column, $this->get_searchable_atum_columns() ) ) {

						$search_query = $this->build_search_query( $search_terms, $column_name, 'string', 'apd' );
						$meta_where   = apply_filters( 'atum/list_table/product_search/string_meta_where', $search_query, $search_column, $search_terms );

						$query = "
							SELECT DISTINCT p.ID, p.post_type, p.post_parent FROM $wpdb->posts p
						    LEFT JOIN $atum_data_table apd ON (p.ID = apd.product_id)
						    WHERE $post_type_where
						    AND $meta_where
					    ";

					}
					// Search using the old way.
					else {

						$search_query = $this->build_search_query( $search_terms, $search_column, 'string', 'pm', TRUE );
						$meta_where   = apply_filters( 'atum/list_table/posts_search/string_meta_where', $search_query, $search_column, $search_terms );

						$query = "SELECT p.ID, p.post_type, p.post_parent FROM $wpdb->posts p
						    LEFT JOIN $wpdb->postmeta pm ON (p.ID = pm.post_id)
						    WHERE $post_type_where
						    AND $meta_where
				         ";

					}

				}

				$search_terms_results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

				if ( empty( $search_terms_results ) ) {
					AtumCache::set_cache( $cache_key, $where_without_results );
					return apply_filters( 'atum/list_table/posts_search/where', $where_without_results, $search_column, $search_term, $search_terms, $cache_key );
				}

				$search_terms_ids = [];

				foreach ( $search_terms_results as $term_result ) {

					$search_terms_ids[] = $term_result->ID;

					if ( 'product' === $term_result->post_type ) {

						$product  = wc_get_product( $term_result->ID );
						$children = $product->get_children();

						if ( ! empty( $children ) ) {
							$search_terms_ids = array_merge( $search_terms_ids, $children );
						}

					}
					// Add parents also.
					else {
						$search_terms_ids[] = $term_result->post_parent;
					}

				}

				$search_where = " AND ( $wpdb->posts.ID IN (" . implode( ',', array_unique( $search_terms_ids ) ) . ') )';

			}

		}

		return $get_query ? $search_where : array_unique( $search_terms_ids );

	}

	/**
	 * Parse the search term submitted and return an array of search terms
	 *
	 * @since 1.5.2
	 *
	 * @param string $search_term
	 *
	 * @return array
	 */
	protected function parse_search( $search_term ) {

		$search_terms = array();

		// There are no line breaks in input fields.
		$search_term = str_replace( array( "\r", "\n" ), '', $search_term );

		if ( preg_match_all( '/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/', $search_term, $matches ) ) {

			//
			// Get stopwords.
			// --------------!
			/* translators: This doesn't need to be translated in ATUM, as it's getting the translation from WordPress core. */
			$words = explode( ',', _x( 'about,an,are,as,at,be,by,com,for,from,how,in,is,it,of,on,or,that,the,this,to,was,what,when,where,who,will,with,www', 'Comma-separated list of search stopwords in your language' ) ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain

			$stopwords = array();
			foreach ( $words as $word ) {

				$word = trim( $word, "\r\n\t " );

				if ( $word ) {
					$stopwords[] = $word;
				}

			}

			/**
			 * Filters stopwords used when parsing search terms.
			 * Note that it uses the same filter name as in WP_Query class for compatibility.
			 *
			 * @param array $stopwords Stopwords.
			 */
			$stopwords = apply_filters( 'wp_search_stopwords', $stopwords ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

			//
			// Parse search terms.
			// -------------------!
			$strtolower = function_exists( 'mb_strtolower' ) ? 'mb_strtolower' : 'strtolower';

			foreach ( $matches[0] as $term ) {

				// Keep before/after spaces when term is for exact match.
				$term = preg_match( '/^".+"$/', $term ) ? trim( $term, "\"'" ) : trim( $term, "\"' " );

				// Avoid single A-Z and single dashes.
				if ( ! $term || ( 1 === strlen( $term ) && preg_match( '/^[a-z\-]$/i', $term ) ) ) {
					continue;
				}

				if ( in_array( call_user_func( $strtolower, $term ), $stopwords, TRUE ) ) {
					continue;
				}

				$search_terms[] = $term;

			}

			// If the search string has only short terms or stopwords, or is 10+ terms long, match it as sentence.
			if ( empty( $search_terms ) || count( $search_terms ) > 9 ) {
				$search_terms = array( $search_term );
			}

		}
		else {
			$search_terms = array( $search_term );
		}

		return $search_terms;

	}

	/**
	 * Build the search SQL query for the given search terms
	 *
	 * @since 1.5.2
	 *
	 * @param array  $search_terms      An array of search terms.
	 * @param string $column            Optional. If passed will search in the specified table column.
	 * @param string $format            Optional. The format that has that column.
	 * @param string $table_prefix      Optional. If passed, this prefix will be added as table alias to the column names.
	 * @param bool   $is_meta_search    Optional. Whether the search is being performed for meta keys.
	 * @param string $search_op         Optional. Logic operator to use to concat multiple terms in SQL query. Possible values: 'AND', 'OR'.
	 *
	 * @return string
	 */
	protected function build_search_query( $search_terms, $column = '', $format = 'string', $table_prefix = '', $is_meta_search = FALSE, $search_op = 'AND' ) {

		global $wpdb;

		$search_query = $search_op_str = $meta_search_query = '';
		$s_column     = $column;

		if ( '_low_stock_threshold' === $column ) {
			$s_column = '_low_stock_amount';
		}

		/**
		 * Filters the prefix that indicates that a search term should be excluded from results.
		 * Note that uses the WP_Query's filter name for compatibility.
		 *
		 * @param string $exclusion_prefix The prefix. Default '-'. Returning an empty value disables exclusions.
		 */
		$exclusion_prefix = apply_filters( 'wp_query_search_exclusion_prefix', '-' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

		foreach ( $search_terms as $term ) {

			// If there is an $exclusion_prefix, terms prefixed with it should be excluded.
			$exclude = $exclusion_prefix && ( substr( $term, 0, 1 ) === $exclusion_prefix );

			if ( $exclude ) {
				$operator   = 'string' === $format ? 'NOT LIKE' : '!=';
				$andor_op   = 'AND';
				$term       = substr( $term, 1 );
				$neutral_op = '1=1';
			}
			else {
				$operator   = 'string' === $format ? 'LIKE' : '=';
				$andor_op   = 'OR';
				$neutral_op = '1=0';
			}

			switch ( $format ) {
				case 'int':
					$term = intval( $term );
					break;

				case 'float':
					$term = floatval( $term );
					break;

				default:
					$term = "'%%" . esc_sql( $wpdb->esc_like( $term ) ) . "%%'"; // Use double %, so it doesn't conflict with wpdb::prepare.
					break;
			}

			// Post meta search.
			if ( $is_meta_search ) {
				$meta_value_column  = $table_prefix ? "$table_prefix.meta_value" : 'meta_value';
				$meta_search_query .= "{$search_op_str}($meta_value_column $operator $term)";
			}
			// Regular search.
			elseif ( empty( $column ) ) {

				$empty_search = '';
				$table_prefix = $table_prefix ?: $wpdb->posts;

				foreach ( (array) apply_filters( 'atum/list_table/post_search_columns', [ 'post_title', 'post_excerpt', 'post_content' ], $term, $this ) as $search_field ) {

					$empty_search .= "($table_prefix.$search_field $operator $term) $andor_op ";
				}


				$search_query .= "{$search_op_str}($empty_search $neutral_op)";
			}
			// Search in column.
			else {
				$s_column      = $table_prefix ? "$table_prefix.$column" : $column;
				$search_query .= "{$search_op_str}(($s_column $operator $term))";
			}

			$search_op_str = " $search_op ";

		}

		if ( $is_meta_search ) {
			$meta_key_column = $table_prefix ? "$table_prefix.meta_key" : 'meta_key';
			$search_query    = " ($meta_search_query) AND $meta_key_column = '$s_column'";
		}

		return $search_query;

	}

	/**
	 * Handle an incoming ajax request
	 * Called by the \Ajax class
	 *
	 * @since 0.0.1
	 *
	 * @param bool $return_data Optional. Whether to return the data or send the JSON to the browser.
	 *
	 * @return array|void
	 */
	public function ajax_response( $return_data = FALSE ) {

		$this->prepare_items();
		extract( $this->_args );
		extract( $this->_pagination_args, EXTR_SKIP );

		ob_start();

		if ( ! empty( $_REQUEST['no_placeholder'] ) ) {
			$this->display_rows();
		}
		else {
			$this->display_rows_or_placeholder();
		}

		$rows = ob_get_clean();

		ob_start();
		$this->print_column_headers();
		$headers = ob_get_clean();

		ob_start();
		$this->display_tablenav( 'top' );
		$extra_tablenav_top = ob_get_clean();

		ob_start();
		$this->display_tablenav( 'bottom' );
		$extra_tablenav_bottom = ob_get_clean();

		ob_start();
		$this->views();
		$views = ob_get_clean();

		$response = array(
			'rows'           => $rows,
			'extra_t_n'      => array(
				'top'    => $extra_tablenav_top,
				'bottom' => $extra_tablenav_bottom,
			),
			'column_headers' => $headers,
			'views'          => $views,
			'row_actions'    => self::$row_actions,
		);

		if ( ! empty( $_REQUEST['paged'] ) ) {
			$response['paged'] = absint( $_REQUEST['paged'] );
		}

		if ( $this->show_totals ) {
			ob_start();
			$this->print_totals_columns();
			$response['totals'] = ob_get_clean();
		}

		if ( isset( $total_items ) ) {
			/* translators: the number of items */
			$response['total_items_i18n'] = sprintf( _n( '%s item', '%s items', $total_items, ATUM_TEXT_DOMAIN ), number_format_i18n( $total_items ) );
		}

		if ( isset( $total_pages ) ) {
			$response['total_pages']      = absint( $total_pages );
			$response['total_pages_i18n'] = number_format_i18n( $total_pages );
		}

		if ( $return_data ) {
			return $response;
		}

		wp_send_json( $response );

	}

	/**
	 * Increase the total of the specified column by the specified amount
	 *
	 * @since 1.4.2
	 *
	 * @param string    $column_name
	 * @param int|float $amount
	 */
	protected function increase_total( $column_name, $amount ) {

		if ( $this->show_totals && isset( $this->totalizers[ $column_name ] ) && is_numeric( $amount ) && ( ! in_array( $this->parent_type, [ 'grouped', 'bundle' ], TRUE ) || ! $this->is_child ) ) {
			$this->totalizers[ $column_name ] += floatval( $amount );
		}
	}

	/**
	 * Enqueue the required scripts
	 *
	 * @since 0.0.1
	 *
	 * @param string $hook
	 */
	public function enqueue_scripts( $hook ) {

		// Sweet Alert 2.
		Helpers::register_swal_scripts();

		// ATUM marketing popup.
		AtumMarketingPopup::get_instance()->maybe_enqueue_scripts();

		// List Table styles.
		wp_register_style( 'atum-list', ATUM_URL . 'assets/css/atum-list.css', [ 'woocommerce_admin_styles', 'sweetalert2' ], ATUM_VERSION );
		wp_enqueue_style( 'atum-list' );

		if ( is_rtl() ) {
			wp_register_style( 'atum-list-rtl', ATUM_URL . 'assets/css/atum-list-rtl.css', [ 'atum-list' ], ATUM_VERSION );
			wp_enqueue_style( 'atum-list-rtl' );
		}

		// Load the ATUM colors.
		Helpers::enqueue_atum_colors( 'atum-list' );

		// If it's the first time the user edits the List Table, load the sweetalert to show the popup.
		// TODO: WHAT IS THIS????
		$first_edit_key = ATUM_PREFIX . "first_edit_$hook";
		if ( ! get_user_meta( get_current_user_id(), $first_edit_key, TRUE ) ) {
			$this->first_edit_key = $first_edit_key;
		}

		$deps = [ 'jquery', 'sweetalert2', 'wc-enhanced-select', 'wp-hooks' ];

		/* @deprecated since WC 10.3.0 */
		if ( version_compare( WC()->version, '10.3.0', '<' ) ) {
			$deps[] = 'jquery-blockui';
		}
		else {
			$deps[] = 'wc-jquery-blockui';
		}

		// List Table script.
		wp_register_script( 'atum-list', ATUM_URL . 'assets/js/build/atum-list-tables.js', $deps, ATUM_VERSION, TRUE );
		wp_enqueue_script( 'atum-list' );

		do_action( 'atum/list_table/after_enqueue_scripts', $this );

	}

	/**
	 * Getter for the list ID.
	 *
	 * @return string
	 */
	public function get_id() {
		return $this->id;
	}

	/**
	 * Getter for the table_columns property
	 *
	 * @since 1.2.5
	 *
	 * @param bool $force_defaults
	 *
	 * @return array
	 */
	public static function get_table_columns( $force_defaults = FALSE ) {
		return self::$table_columns;
	}

	/**
	 * Setter for the table_columns property
	 *
	 * @since 1.2.5
	 *
	 * @param array $table_columns
	 */
	public static function set_table_columns( $table_columns ) {
		self::$table_columns = $table_columns;
	}

	/**
	 * Getter for the group_members property
	 *
	 * @since 1.2.5
	 *
	 * @return array
	 */
	public function get_group_members() {
		return $this->group_members;
	}

	/**
	 * Setter for the group_members property
	 *
	 * @since 1.2.5
	 *
	 * @param array $group_members
	 */
	public function set_group_members( $group_members ) {
		$this->group_members = $group_members;
	}

	/**
	 * Getter for the ATUM's searchable columns (only those stored on the ATUM product data table)
	 *
	 * @since 1.7.2
	 *
	 * @return array
	 */
	public function get_searchable_atum_columns() {

		// Just extract the column names from the atum_sortable_columnms.
		return array_keys( $this->atum_sortable_columns );

	}

	/**
	 * Getter for the current product prop
	 *
	 * @since 1.4.15
	 *
	 * @return \WC_Product
	 */
	public function get_current_product() {
		return $this->list_item;
	}

	/**
	 * Getter for the default_currency prop
	 *
	 * @since 1.4.16
	 *
	 * @return string
	 */
	public static function get_default_currency() {
		return self::$default_currency;
	}

	/**
	 * Get all the available children products in the system
	 *
	 * @since 1.1.1
	 *
	 * @param string $parent_type   The parent product type.
	 * @param array  $post_in       Optional. If is a search query, get only the children from the filtered products.
	 * @param string $post_type     Optional. The children post type.
	 *
	 * @return array
	 */
	protected function get_children( $parent_type, $post_in = array(), $post_type = 'product' ) {

		$post_statuses = Globals::get_queryable_product_statuses();

		// Get the published products of the same type first.
		$parent_args = array(
			'post_type'      => 'product',
			'post_status'    => $post_statuses,
			'posts_per_page' => - 1,
			'fields'         => 'ids',
			'orderby'        => array(
				'menu_order' => 'ASC',
				'date'       => 'DESC',
			),
			'tax_query'      => array(
				array(
					'taxonomy' => 'product_type',
					'field'    => 'slug',
					'terms'    => apply_filters( 'atum/list_table/parent_post_type', $parent_type ),
				),
			),
		);

		if ( ! empty( $post_in ) ) {
			$parent_args['post__in'] = $post_in;
		}

		// As this query does not contain ATUM params, doesn't need the filters.
		$parents = new \WP_Query( apply_filters( 'atum/list_table/get_children/parent_args', $parent_args ) );

		$parents_with_child = $grouped_products = $bundle_children = array();

		if ( $parents->found_posts ) {

			switch ( $parent_type ) {
				case 'variable':
					$this->container_products['all_variable'] = array_unique( array_merge( $this->container_products['all_variable'], $parents->posts ) );
					break;

				case 'grouped':
					$this->container_products['all_grouped'] = array_unique( array_merge( $this->container_products['all_grouped'], $parents->posts ) );

					// Get all the children from their corresponding meta key.
					foreach ( $parents->posts as $parent_id ) {

						$children = get_post_meta( $parent_id, '_children', TRUE );

						// Unset uncontrolled childrens.
						if ( ! empty( $children ) && is_array( $children ) ) {

							foreach ( $children as $key => $grouped_children ) {

								$product_child = Helpers::get_atum_product( $grouped_children );

								if ( $product_child ) {

									$atum_control_status = Helpers::get_atum_control_status( $product_child );

									if (
										( ! $this->show_controlled && 'yes' === $atum_control_status ) ||
										( $this->show_controlled && 'no' === $atum_control_status )
									) {
										unset( $children[ $key ] );
									}

								}

							}

							$grouped_products     = array_merge( $grouped_products, $children );
							$parents_with_child[] = $parent_id;

						}
					}

					break;

				// WC Subscriptions compatibility.
				case 'variable-subscription':
					$this->container_products['all_variable_subscription'] = array_unique( array_merge( $this->container_products['all_variable_subscription'], $parents->posts ) );
					break;

				// WC Bundle Producs compatibility.
				case 'bundle':
					$this->container_products['all_bundle'] = array_unique( array_merge( $this->container_products['all_bundle'], $parents->posts ) );

					$bundle_children = Helpers::get_bundle_items( array(
						'return'    => 'id=>product_id',
						'bundle_id' => $parents->posts,
					) );

					foreach ( $parents->posts as $parent_id ) {

						if ( ! empty( $bundle_children ) && is_array( $bundle_children ) ) {
							$parents_with_child[] = $parent_id;
						}
					}
					break;
			}

			$children_args = array(
				'post_type'      => $post_type,
				'post_status'    => $post_statuses,
				'posts_per_page' => - 1,
				'fields'         => 'id=>parent',
				'orderby'        => 'menu_order',
				'order'          => 'ASC',
			);

			if ( 'grouped' === $parent_type ) {
				$children_args['post__in'] = $grouped_products;
			}
			else {

				if ( ! empty( $this->supplier_variation_products ) ) {
					$children_args['post__in'] = $this->supplier_variation_products;
				}

				$children_args['post_parent__in'] = $parents->posts;

			}

			// Apply the same order and orderby args than their parent.
			$children_args = $this->parse_orderby_args( $children_args );

			// Sometimes with the general cache for this function is not enough to avoid duplicated queries.
			$cache_key    = AtumCache::get_cache_key( 'get_children_query', $children_args );
			$children_ids = AtumCache::get_cache( $cache_key, ATUM_TEXT_DOMAIN, FALSE, $has_cache );

			if ( $has_cache ) {
				return $children_ids;
			}

			/*
			 * NOTE: we should apply here all the query filters related to individual child products
			 * like the ATUM control switch or the supplier
			 */
			$this->set_controlled_query_data();

			// Avoid to duplicate grouped children.
			if ( 'grouped' === $parent_type && $this->is_searching_by_id_column() ) {
				remove_filter( 'posts_search', array( $this, 'posts_search' ) );
			}

			add_filter( 'posts_clauses', array( $this, 'atum_product_data_query_clauses' ) );
			$children = new \WP_Query( apply_filters( 'atum/list_table/get_children/children_args', $children_args, $this ) );
			remove_filter( 'posts_clauses', array( $this, 'atum_product_data_query_clauses' ) );

			if ( 'grouped' === $parent_type && $this->is_searching_by_id_column() ) {
				add_filter( 'posts_search', array( $this, 'posts_search' ), 10, 2 );
			}

			if ( $children->found_posts ) {

				if ( 'grouped' !== $parent_type ) {
					$parents_with_child = wp_list_pluck( $children->posts, 'post_parent' );
				}

				switch ( $parent_type ) {
					case 'variable':
						$this->container_products['variable'] = array_unique( array_merge( $this->container_products['variable'], $parents_with_child ) );

						// Exclude all those variations with no children from the list.
                        if ( apply_filters( 'atum/list_table/get_children/exclude_empty_variable_products', TRUE, $this ) ) {
                            $this->excluded = array_unique( array_merge( $this->excluded, array_diff( $this->container_products['all_variable'], $this->container_products['variable'] ) ) );
                        }
						break;

					case 'group ed':
						$this->container_products['grouped'] = array_unique( array_merge( $this->container_products['grouped'], $parents_with_child ) );

						// Exclude all those grouped with no children from the list.
						$this->excluded = array_unique( array_merge( $this->excluded, array_diff( $this->container_products['all_grouped'], $this->container_products['grouped'] ) ) );
						break;

					case 'variable-subscription':
						$this->container_products['variable_subscription'] = array_unique( array_merge( $this->container_products['variable_subscription'], $parents_with_child ) );

						// Exclude all those subscription variations with no children from the list.
						$this->excluded = array_unique( array_merge( $this->excluded, array_diff( $this->container_products['all_variable_subscription'], $this->container_products['variable_subscription'] ) ) );
						break;
				}

				$children_ids            = wp_list_pluck( $children->posts, 'ID' );
				$this->children_products = array_unique( array_merge( $this->children_products, $children_ids ) );
				AtumCache::set_cache( $cache_key, $children_ids );

				return $children_ids;

			}
			elseif ( class_exists( '\WC_Product_Bundle' ) && 'bundle' === $parent_type ) {

				foreach ( $bundle_children as $key => $bundle_child ) {

					$product_child = Helpers::get_atum_product( $bundle_child );

					if ( $product_child ) {
						if ( 'yes' === Helpers::get_atum_control_status( $product_child ) ) {

							if ( ! $this->show_controlled ) {
								unset( $bundle_children[ $key ] );
							}

						}
						elseif ( $this->show_controlled ) {
							unset( $bundle_children[ $key ] );
						}
					}

				}

				if ( empty( $bundle_children ) ) {
					$parents_with_child = [];
				}
				else {

					$bundle_parents = [];
					foreach ( $bundle_children as $bundle_child ) {
						$bundle_parents = array_merge( $bundle_parents, wc_pb_get_bundled_product_map( $bundle_child ) );
					}

					$parents_with_child = $bundle_parents;

				}

				$this->container_products['bundle'] = array_unique( array_merge( $this->container_products['bundle'], $parents_with_child ) );

				// Exclude all those subscription variations with no children from the list.
				$this->excluded = array_unique( array_merge( $this->excluded, array_diff( $this->container_products['all_bundle'], $this->container_products['bundle'] ) ) );

				$this->children_products = array_unique( array_merge( $this->children_products, array_map( 'intval', $bundle_children ) ) );
				AtumCache::set_cache( $cache_key, $bundle_children );

				return $bundle_children;

			}
			elseif ( 'grouped' !== $parent_type || ! $this->is_searching_by_id_column() ) {
				$this->excluded = array_unique( array_merge( $this->excluded, $parents->posts ) );
			}

		}

		return array();

	}

	/**
	 * Get the parent products from a list of product IDs
	 *
	 * @since 1.1.1
	 *
	 * @param array $product_ids  The array of children product IDs.
	 *
	 * @return array
	 */
	protected function get_variation_parents( $product_ids ) {

		// TODO: WHAT IF WE HAVE TO ADD BUNDLE PRODUCT?
		global $wpdb;

		$parents = "
			SELECT DISTINCT post_parent FROM $wpdb->posts 		
			WHERE ID IN (" . implode( ',', $product_ids ) . ")
			AND post_parent > 0 AND post_type = 'product_variation'	
		";

		return $wpdb->get_col( $parents ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

	}

	/**
	 * Get the parent grouped products from a list of product IDs
	 *
	 * @since 1.6.6
	 *
	 * @param array $product_ids The array of children product IDs.
	 *
	 * @return array
	 */
	protected function get_grouped_parents( $product_ids ) {

		global $wpdb;

		$like_clauses = [];

		foreach ( $product_ids as $product_id ) {
			$like_clauses[] = "meta_value LIKE '%i:" . $product_id . ";%'";
		}

		$grouped_sql = "
			SELECT DISTINCT post_id FROM $wpdb->postmeta 		
			WHERE post_id IN (" . implode( ',', $this->container_products['grouped'] ) . ")
			AND meta_key = '_children' AND ( " . implode( ' OR ', $like_clauses ) . ')	
		';

		return $like_clauses ? $wpdb->get_col( $grouped_sql ) : []; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

	}

	/**
	 * Builds a query string with the active filters
	 *
	 * @since 1.4.3
	 *
	 * @param string $format  Optional. The return format (array or string).
	 *
	 * @return string|array
	 */
	protected function get_filters_query_string( $format = 'array' ) {

		$default_filters = array(
			'paged'                            => 1,
			'order'                            => 'desc',
			'orderby'                          => 'date',
			'view'                             => 'all_stock',
			'product_cat'                      => '',
			'product_type'                     => '',
			Globals::PRODUCT_LOCATION_TAXONOMY => '',
			'supplier'                         => '',
			'extra_filter'                     => '',
			's'                                => '',
			'search_column'                    => '',
			'sold_last_days'                   => '',
		);

		parse_str( $_SERVER['QUERY_STRING'], $query_string );
		$params = array_filter( array_intersect_key( $query_string, $default_filters ) );

		// The filters with default values should be excluded.
		foreach ( $params as $param => $value ) {
			if ( $value === $default_filters[ $param ] ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
				unset( $params[ $param ] );
			}
		}

		return 'string' === $format ? http_build_query( $params ) : $params;

	}

	/**
	 * Get columns hidden by default
	 *
	 * @since 1.2.1
	 *
	 * @return array
	 */
	public static function hidden_columns() {
		return apply_filters( 'atum/list_table/default_hidden_columns', static::$default_hidden_columns );
	}

	/**
	 * Getter fot the is_report prop
	 *
	 * @since 1.4.16
	 *
	 * @return bool
	 */
	public static function is_report() {
		return static::$is_report;
	}

	/**
	 * Getter for the selected prop
	 *
	 * @since 1.9.1
	 *
	 * @return array|false|string[]
	 */
	public function get_selected() {
		return $this->selected;
	}

	/**
	 * Getter for the parent_type prop
	 *
	 * @since 1.9.5
	 *
	 * @return string
	 */
	public function get_parent_type() {
		return $this->parent_type;
	}

	/**
	 * Apply order and orderby args by $_REQUEST options.
	 *
	 * @since 1.8.6
	 *
	 * @param array $args
	 *
	 * @return array
	 */
	protected function parse_orderby_args( $args ) {

		if ( empty( $_REQUEST['orderby'] ) || 'date' === $_REQUEST['orderby'] ) {
			return $args;
		}

		$orderby = esc_attr( $_REQUEST['orderby'] );
		$order   = ( isset( $_REQUEST['order'] ) && 'asc' === $_REQUEST['order'] ) ? 'ASC' : 'DESC';

		// Columns starting by underscore are based in meta keys, so can be sorted.
		if ( '_' === substr( $orderby, 0, 1 ) ) {

			// Order ATUM columns.
			if ( array_key_exists( $orderby, $this->atum_sortable_columns ) ) {

				$this->atum_query_data['order']          = $this->atum_sortable_columns [ $orderby ];
				$this->atum_query_data['order']['order'] = $order;

			}
			// Order regular meta keys.
			else {

				switch ( $orderby ) {
					case '_low_stock_threshold':
						$orderby = '_low_stock_amount';
						break;
				}

				// All the meta key based columns are numeric (in ATUM free) except the SKU.
				// TODO: PERHAPS WE SHOULD MANAGE TYPES FOR ALL SORTABLE META KEYS WITHIN A CLASS PROP.
				$args['orderby']  = apply_filters( 'atum/list_table/meta_key_orderby', '_sku' === $orderby ? 'meta_value' : 'meta_value_num', $orderby, $this );
				$args['orderby'] .= ' ID'; // Second ordering for items that have no meta key.

				$args['meta_query']['relation'] = 'OR';

				// NOTE: The "NOT EXISTS" clause must go before the "EXISTS" one or won't do the sorting correctly.
				// More info: https://wordpress.stackexchange.com/questions/102447/sort-on-meta-value-but-include-posts-that-dont-have-one#comment-471161.
				$args['meta_query'][] = array(
					'key'     => $orderby,
					'compare' => 'NOT EXISTS',
				);

				$args['meta_query'][] = array(
					'key'     => $orderby,
					'compare' => 'EXISTS',
				);

				$args['order'] = $order;

			}

		}
		// Standard Fields.
		else {
			$args['orderby'] = $orderby;
			$args['order']   = $order;
		}

		return $args;

	}

	/**
	 * Set the sales day value
	 *
	 * @since 1.9.3.1
	 *
	 * @return int
	 */
	public static function set_sales_day() {

		$value_set = FALSE;

		if ( isset( $_REQUEST['sold_last_days'] ) ) {

			// Sanitize.
			$value = absint( $_REQUEST['sold_last_days'] );

			if ( $value > 0 && $value < 31 ) {
				self::$sale_days = $value;
				$value_set       = TRUE;
			}

		}

		if ( ! $value_set ) {
			self::$sale_days = Helpers::get_sold_last_days_option();
		}

		return self::$sale_days;

	}

	/**
	 * Check if ListTable is being searched by ID column.
	 *
	 * @since 1.9.4
	 *
	 * @return bool
	 */
	protected function is_searching_by_id_column() {
		return isset( $_REQUEST['search_column'] ) && 'ID' === esc_attr( stripslashes( $_REQUEST['search_column'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
	}

	/**
	 * Save the update dates for any outdated product (if necessary)
	 *
	 * @since 1.9.8
	 */
	protected function maybe_save_update_dates() {

		// If the current product has been modified within any of the columns, save it.
		if ( Helpers::is_product_data_outdated( $this->list_item ) ) {

			// At least, update the calculated dates properties.
			$timestamp = Helpers::get_current_timestamp();
			$this->list_item->set_sales_update_date( $timestamp );
			$this->list_item->set_update_date( $timestamp );

			$this->list_item->save_atum_data();
		}

	}

	/**
	 * Check whether to disable some List Table columns (if the user has specified it in settings)
	 *
	 * @since 1.9.8
	 *
	 * @param array  $table_columns The array of default columns for the current List Table.
	 * @param string $option_key    The option key name from ATUM Settings where the multi-checkbox of available columns is stored.
	 *
	 * @return array
	 */
	protected static function maybe_disable_columns( $table_columns, $option_key ) {

		$available_columns = Helpers::get_option( $option_key, [] );

		if ( ! empty( $available_columns ) && is_array( $available_columns ) ) {

			$disabled_columns = array_filter( $available_columns['options'], function ( $value ) {
				return 'no' === $value;
			} );

			$disabled_column_keys = array_keys( $disabled_columns );

			foreach ( $disabled_column_keys as $disabled_column ) {
				unset( $table_columns[ $disabled_column ] );
			}

		}

		return $table_columns;

	}

	/**
	 * Check whether the current item is group/bundle.
	 *
	 * @since 1.9.10
	 *
	 * @return bool
	 */
	protected function is_packed_item() {

		if (
			( class_exists( '\WC_Product_Bundle' ) && 'bundle' === $this->list_item->get_type() ) ||
			( class_exists( '\WC_Product_Grouped' ) && 'group' === $this->list_item->get_type() )
		) {
			return TRUE;
		}

		return FALSE;
	}

	/**
	 * Prepare the transient args used within List Tables for the table results
	 *
	 * @since 1.9.18
	 *
	 * @return array
	 */
	protected function get_transient_args() {

		$request_params = $_GET;
		unset( $request_params['security'], $request_params['action'] );

		return apply_filters( 'atum/list_table/get_counters_transient_args', $request_params );

	}

	/**
	 * Filters the found terms for the categories and locations dropdown.
	 *
	 * @since 1.9.18.1
	 *
	 * @param array          $terms      Array of found terms.
	 * @param array|null     $taxonomies An array of taxonomies if known.
	 * @param array          $args       An array of get_terms() arguments.
	 * @param \WP_Term_Query $term_query The WP_Term_Query object.
	 *
	 * @return \WP_Term[]
	 */
	public function get_filtered_terms( $terms, $taxonomies, $args, $term_query ) {

		global $wpdb;

		$extra_criteria = apply_filters( 'atum/list_table/get_filtered_terms_extra_criteria', '1', $this );

		$sql = "
			SELECT DISTINCT t.term_id FROM $wpdb->terms AS t  
		    INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id 
          	WHERE tt.taxonomy IN ('" . implode( "','", $taxonomies ) . "') 
			AND ( 
			    tt.term_taxonomy_id IN (
			    	SELECT DISTINCT tr.term_taxonomy_id FROM $wpdb->term_relationships tr 
		            INNER JOIN $wpdb->posts scp ON tr.object_id = scp.ID
			    	WHERE scp.post_type IN( 'product_variation', 'product' ) AND scp.post_status IN ( 'publish', 'private' )
			        AND $extra_criteria
			    )
	          	AND (
					tt.count > 0
					OR t.term_id IN ( SELECT parent FROM $wpdb->term_taxonomy WHERE count > 0 )
                )
			)
			ORDER BY t.name ASC
		";

		$term_ids = $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

		return array_map( 'get_term', $term_ids );

	}

	/**
	 * Getter for the id views
	 *
	 * @since 1.9.39
	 *
	 * @return array
	 */
	protected function get_id_views() {
		return apply_filters( 'atum/list_table/id_views', $this->id_views, $this );
	}

	/**
	 * Get the extra filters used for the list table.
	 *
	 * @since 1.9.54
	 *
	 * @return array
	 */
	protected function get_extra_filters() {
		return [];
	}

}
