<?php

defined('ABSPATH') or die('Access denied.');

class WDTColumn
{

    protected $_id = null;
    protected $_inputType = '';
    protected $_hiddenOnPhones = false;
    protected $_hiddenOnTablets = false;
    protected $_title;
    protected $_orig_header = '';
    protected $_isVisible = true;
    protected $_cssStyle;
    protected $_width;
    protected $_sorting = true;
    protected $_cssClassArray;
    protected $_dataType;
    protected $_jsDataType = 'html';
    protected $_filterType = 'text';
    protected $_possibleValues = array();
    protected $_filterDefaultValue = '';
    protected $_textBefore = '';
    protected $_textAfter = '';
    protected $_notNull = false;
    protected $_showThousandsSeparator = true;
    protected $_conditionalFormattingData = array();
    protected $_searchable = true;
    protected $_decimalPlaces = -1;
    protected $_exactFiltering;
    protected $_globalSearchColumn = 1;
    protected $_searchInSelectBox = 1;
    protected $_searchInSelectBoxEditing = 1;
    protected $_filterLabel;
    protected $_checkboxesInModal = false;
    protected $_andLogic = false;
    protected $_possibleValuesType;
    protected $_possibleValuesAddEmpty = false;
    protected $_possibleValuesAjax = 10;
    protected $_column_align_fields = '';
    protected $_rangeSlider;
    protected $_rangeMaxValueDisplay;
    protected $_customMaxRangeValue;
    protected $_foreignKeyRule;
    protected $_editingDefaultValue = null;
    protected $_parentTable = null;
    protected $_linkButtonLabel;
    protected $_column_align_header = '';
    protected $_transformValueText = array();

    protected $_column_rotate_header_name = '';

    public $masterDetailColumnOption;

    /**
     * WDTColumn constructor.
     *
     * @param array $properties
     */
    public function __construct($properties = array())
    {
        $this->_cssClassArray = WDTTools::defineDefaultValue($properties, 'classes', array());
        $this->_textBefore = WDTTools::defineDefaultValue($properties, 'text_before', '');
        $this->_textAfter = WDTTools::defineDefaultValue($properties, 'text_after', '');
        $this->_transformValueText = WDTTools::defineDefaultValue($properties, 'transformValueText', '');
        $this->setSorting(WDTTools::defineDefaultValue($properties, 'sorting', 1));
        $this->_title = WDTTools::defineDefaultValue($properties, 'title', '');
        $this->_isVisible = WDTTools::defineDefaultValue($properties, 'visible', true);
        $this->_width = WDTTools::defineDefaultValue($properties, 'width', '');
        $this->_orig_header = WDTTools::defineDefaultValue($properties, 'orig_header', '');
        $this->_exactFiltering = WDTTools::defineDefaultValue($properties, 'exactFiltering', '');
        $this->setGlobalSearchColumn(WDTTools::defineDefaultValue($properties, 'globalSearchColumn', 1));
        $this->setSearchInSelectBox(WDTTools::defineDefaultValue($properties, 'searchInSelectBox', 1));
        $this->setSearchInSelectBoxEditing(WDTTools::defineDefaultValue($properties, 'searchInSelectBoxEditing', 1));
        $this->_searchable = WDTTools::defineDefaultValue($properties, 'searchable', true);
        $this->setFilterDefaultValue(WDTTools::defineDefaultValue($properties, 'filterDefaultValue', null));
        $this->setFilterLabel(WDTTools::defineDefaultValue($properties, 'filterLabel', null));
        $this->setCheckboxesInModal(WDTTools::defineDefaultValue($properties, 'checkboxesInModal', false));
        $this->setAndLogic(WDTTools::defineDefaultValue($properties, 'andLogic', false));
        $this->_possibleValuesType = WDTTools::defineDefaultValue($properties, 'possibleValuesType', '');
        $this->setPossibleValuesAddEmpty(WDTTools::defineDefaultValue($properties, 'possibleValuesAddEmpty', false));
        $this->setPossibleValuesAjax(WDTTools::defineDefaultValue($properties, 'possibleValuesAjax', 10));
        $this->setEditingDefaultValue(WDTTools::defineDefaultValue($properties, 'editingDefaultValue', null));
        $this->setParentTable(WDTTools::defineDefaultValue($properties, 'parentTable', null));
        $this->setLinkButtonLabel(WDTTools::defineDefaultValue($properties, 'linkButtonLabel', null));
        $this->_rangeSlider = WDTTools::defineDefaultValue($properties, 'rangeSlider', '');
        $this->setRangeMaxValueDisplay(WDTTools::defineDefaultValue($properties, 'rangeMaxValueDisplay', 'default'));
        $this->setCustomMaxRangeValue(WDTTools::defineDefaultValue($properties, 'customMaxRangeValue', null));

    }

    /**
     * @return string
     */
    public function getInputType()
    {
        return $this->_inputType;
    }

    /**
     * @param string $inputType
     */
    public function setInputType($inputType)
    {
        $this->_inputType = $inputType;
    }

    /**
     * @return bool
     */
    public function isHiddenOnPhones()
    {
        return $this->_hiddenOnPhones;
    }

    /**
     * @param bool $hiddenOnPhones
     */
    public function setHiddenOnPhones($hiddenOnPhones)
    {
        $this->_hiddenOnPhones = $hiddenOnPhones;
    }

    /**
     * @return bool
     */
    public function isHiddenOnTablets()
    {
        return $this->_hiddenOnTablets;
    }

    /**
     * @param bool $hiddenOnTablets
     */
    public function setHiddenOnTablets($hiddenOnTablets)
    {
        $this->_hiddenOnTablets = $hiddenOnTablets;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return apply_filters('wpdatatables_filter_column_title', $this->_title, $this->getOriginalHeader(), $this);
    }

    /**
     * @param string $title
     */
    public function setTitle($title)
    {
        $this->_title = $title;
    }

    /**
     * @return string
     */
    public function getOriginalHeader()
    {
        return $this->_orig_header;
    }

    /**
     * @param string $orig_header
     */
    public function setOriginalHeader($orig_header)
    {
        $this->_orig_header = $orig_header;
    }

    /**
     * @return bool|string
     */
    public function isVisible()
    {
        return $this->_isVisible;
    }

    /**
     * @return bool
     */
    public function isVisibleOnMobiles()
    {
        return ($this->_isVisible && !$this->_hiddenOnPhones && !$this->_hiddenOnTablets);
    }

    /**
     * @param bool|string $isVisible
     */
    public function setIsVisible($isVisible)
    {
        $this->_isVisible = $isVisible;
    }

    /**
     * @return mixed
     */
    public function getCssStyle()
    {
        return $this->_cssStyle;
    }

    /**
     * @param mixed $cssStyle
     */
    public function setCssStyle($cssStyle)
    {
        $this->_cssStyle = $cssStyle;
    }

    /**
     * @return string
     */
    public function getWidth()
    {
        return $this->_width ? $this->_width : 'auto';
    }

    /**
     * @param string $width
     */
    public function setWidth($width)
    {
        $this->_width = $width;
    }

    /**
     * @return bool
     */
    public function getSorting()
    {
        return $this->_sorting;
    }

    /**
     * @param int $sorting
     */
    public function setSorting($sorting)
    {
        $this->_sorting = (bool)$sorting;
    }

    /**
     * @return mixed
     */
    public function getCSSClasses()
    {
        $classesStr = implode(' ', $this->_cssClassArray);
        $classesStr = apply_filters('wpdatatables_filter_column_cssClassArray', $classesStr, $this->_title);
        return $classesStr;
    }

    /**
     * @param $class
     */
    public function addCSSClass($class)
    {
        $this->_cssClassArray[] = $class;
    }

    /**
     * @return mixed
     */
    public function getDataType()
    {
        return $this->_dataType;
    }

    /**
     * @param mixed $dataType
     */
    public function setDataType($dataType)
    {
        $this->_dataType = $dataType;
    }

    /**
     * @return string
     */
    public function getFilterType()
    {
        return $this->_filterType;
    }

    /**
     * @param $filterType
     *
     * @throws WDTException
     */
    public function setFilterType($filterType)
    {
        if (!in_array($filterType,
            array(
                'none',
                '',
                'text',
                'number',
                'select',
                'multiselect',
                'null',
                'number-range',
                'date-range',
                'checkbox',
                'datetime-range',
                'time-range'
            )
        )
        ) {
            throw new WDTException('Unknown column filter type!');
        }
        if (($filterType == 'none') || ($filterType == '')) {
            $filterType = 'null';
        }
        $this->_filterType = $filterType;
    }

    /**
     * @return array
     */
    public function getPossibleValuesList()
    {
        return $this->_possibleValues;
    }

    /**
     * @param array $possibleValues
     */
    public function setPossibleValues($possibleValues)
    {
        if (!empty($possibleValues)) {
            if (!is_array($possibleValues)) {
                $possibleValues = explode('|', $possibleValues);
            }
            $this->_possibleValues = $possibleValues;
        } else {
            $this->_possibleValues = array();
        }
    }

    /**
     * @return string
     * @throws WDTException
     */
    public function getFilterDefaultValue()
    {
        $value = $this->_filterDefaultValue;

        if ($value) {
            if ($this->getForeignKeyRule()) {
                $foreignKeyRule = $this->getForeignKeyRule();
                $joinedTable = WPDataTable::loadWpDataTable($foreignKeyRule->tableId);
                $distinctValues = $joinedTable->getDistinctValuesForColumns($foreignKeyRule);

                $valueCopy = $value;
                $value = [];
                if (is_array($valueCopy)) {
                    foreach ($valueCopy as $copy) {
                        $copy = $this->applyPlaceholders($copy);

                        $value[] = [
                            'value' => $copy,
                            'text' => $distinctValues[$copy]
                        ];
                    }
                } else {
                    $valueCopy = $this->applyPlaceholders($valueCopy);

                    $value['value'] = $valueCopy;

                    if ($this->getFilterType() == "text") {
                        $key = array_search($valueCopy, $distinctValues);
                        $value['text'] = $distinctValues[$key];
                    } else {
                        $value['text'] = $distinctValues[$valueCopy];
                    }
                }
            } else {
                if (is_array($value)) {
                    foreach ($value as &$singleValue) {
                        $singleValue = $this->applyPlaceholders($singleValue);
                    }
                } else {
                    $value = $this->applyPlaceholders($value);
                }
            }
        }

        $value = apply_filters_deprecated(
            'wpdt_filter_filtering_default_value',
            array($value, $this->getOriginalHeader(), $this->getParentTable()->getWpId()),
            WDT_INITIAL_STARTER_VERSION,
            'wpdatatables_filter_filtering_default_value');
        $value = apply_filters('wpdatatables_filter_filtering_default_value', $value, $this->getOriginalHeader(), $this->getParentTable()->getWpId());

        return $value;
    }

    /**
     * @param string $defaultValue
     */
    public function setFilterDefaultValue($defaultValue)
    {
        if ($defaultValue !== null && strpos($defaultValue, '|') !== false) {
            $defaultValue = explode('|', $defaultValue);
        }
        $this->_filterDefaultValue = $defaultValue;
    }

    /**
     * @return string
     */
    public function getTextBefore()
    {
        return $this->_textBefore;
    }

    /**
     * @param string $textBefore
     */
    public function setTextBefore($textBefore)
    {
        $this->_textBefore = $textBefore;
    }

    /**
     * @return string
     */
    public function getTransformValueText()
    {
        return $this->_transformValueText;
    }

    /**
     * @param string $textvalue
     */
    public function setTransformValueText($textvalue)
    {
        $this->_transformValueText = $textvalue;
    }

    /**
     * @return string
     */
    public function getTextAfter()
    {
        return $this->_textAfter;
    }

    /**
     * @param string $textAfter
     */
    public function setTextAfter($textAfter)
    {
        $this->_textAfter = $textAfter;
    }

    /**
     * @return bool
     */
    public function isNotNull()
    {
        return $this->_notNull;
    }

    /**
     * @param bool $notNull
     */
    public function setNotNull($notNull)
    {
        $this->_notNull = (bool)$notNull;
    }

    /**
     * @return bool
     */
    public function isShowThousandsSeparator()
    {
        return $this->_showThousandsSeparator;
    }

    /**
     * @param bool $showThousandsSeparator
     */
    public function setShowThousandsSeparator($showThousandsSeparator)
    {
        $this->_showThousandsSeparator = $showThousandsSeparator;
    }

    /**
     * @return array
     */
    public function getConditionalFormattingData()
    {
        return $this->_conditionalFormattingData;
    }

    /**
     * Set conditional formatting data for column and set
     * conditional formatting cell value to today's date if %TODAY%
     * placeholder is used
     *
     * @param array $conditionalFormattingData
     */
    public function setConditionalFormattingData($conditionalFormattingData)
    {
        $this->_conditionalFormattingData = $conditionalFormattingData;
    }

    /**
     * @return bool
     */
    public function isSearchable()
    {
        return $this->_searchable;
    }

    /**
     * @param bool $searchable
     */
    public function setSearchable($searchable)
    {
        $this->_searchable = $searchable;
    }

    /**
     * @return int
     */
    public function getDecimalPlaces()
    {
        return $this->_decimalPlaces;
    }

    /**
     * @param int $decimalPlaces
     */
    public function setDecimalPlaces($decimalPlaces)
    {
        $this->_decimalPlaces = $decimalPlaces;
    }

    /**
     * @return string
     */
    public function getExactFiltering()
    {
        return $this->_exactFiltering;
    }

    /**
     * @param string $exactFiltering
     */
    public function setExactFiltering($exactFiltering)
    {
        $this->_exactFiltering = $exactFiltering;
    }

    /**
     * @return int
     */
    public function getGlobalSearchColumn()
    {
        return $this->_globalSearchColumn;
    }

    /**
     * @param int $globalSearchColumn
     */
    public function setGlobalSearchColumn($globalSearchColumn)
    {
        $this->_globalSearchColumn = $globalSearchColumn;
    }

    /**
     * @return int
     */
    public function getSearchInSelectBox()
    {
        return $this->_searchInSelectBox;
    }

    /**
     * @param int $searchInSelectBox
     */
    public function setSearchInSelectBox($searchInSelectBox)
    {
        $this->_searchInSelectBox = $searchInSelectBox;
    }

    /**
     * @return int
     */
    public function getSearchInSelectBoxEditing()
    {
        return $this->_searchInSelectBoxEditing;
    }

    /**
     * @param int $searchInSelectBoxEditing
     */
    public function setSearchInSelectBoxEditing($searchInSelectBoxEditing)
    {
        $this->_searchInSelectBoxEditing = $searchInSelectBoxEditing;
    }

    /**
     * @return string
     */
    public function getRangeSlider()
    {
        return $this->_rangeSlider;
    }

    /**
     * @param string $rangeSlider
     */
    public function setRangeSlider($rangeSlider)
    {
        $this->_rangeSlider = $rangeSlider;
    }

    /**
     * @return string
     */
    public function getRangeMaxValueDisplay()
    {
        return $this->_rangeMaxValueDisplay;
    }

    /**
     * @param string $rangeMaxValueDisplay
     */
    public function setRangeMaxValueDisplay($rangeMaxValueDisplay)
    {
        $this->_rangeMaxValueDisplay = $rangeMaxValueDisplay;
    }

    /**
     * @return string
     */
    public function getCustomMaxRangeValue()
    {
        return $this->_customMaxRangeValue;
    }

    /**
     * @param string $customMaxRangeValue
     */
    public function setCustomMaxRangeValue($customMaxRangeValue)
    {
        $this->_customMaxRangeValue = $customMaxRangeValue;
    }

    /**
     * @return string
     */
    public function getFilterLabel()
    {
        return $this->_filterLabel;
    }

    /**
     * @param string $filterLabel
     */
    public function setFilterLabel($filterLabel)
    {
        $this->_filterLabel = $filterLabel;
    }

    /**
     * @return string
     */
    public function getLinkButtonLabel()
    {
        return $this->_linkButtonLabel;
    }

    /**
     * @param string $linkButtonLabel
     */
    public function setLinkButtonLabel($linkButtonLabel)
    {
        $this->_linkButtonLabel = $linkButtonLabel;
    }

    /**
     * @return bool
     */
    public function isCheckboxesInModal()
    {
        return $this->_checkboxesInModal;
    }

    /**
     * @param bool $checkboxesInModal
     */
    public function setCheckboxesInModal($checkboxesInModal)
    {
        $this->_checkboxesInModal = $checkboxesInModal;
    }

    /**
     * @return bool
     */
    public function isAndLogic()
    {
        return $this->_andLogic;
    }

    /**
     * @param bool $andLogic
     */
    public function setAndLogic($andLogic)
    {
        $this->_andLogic = $andLogic;
    }

    /**
     * @return string
     */
    public function getPossibleValuesType()
    {
        return $this->_possibleValuesType;
    }

    /**
     * @param string $possibleValuesType
     */
    public function setPossibleValuesType($possibleValuesType)
    {
        $this->_possibleValuesType = $possibleValuesType;
    }

    /**
     * @return mixed
     */
    public function getPossibleValuesAddEmpty()
    {
        return $this->_possibleValuesAddEmpty;
    }

    /**
     * @param mixed $possibleValuesAddEmpty
     */
    public function setPossibleValuesAddEmpty($possibleValuesAddEmpty)
    {
        $this->_possibleValuesAddEmpty = (bool)$possibleValuesAddEmpty;
    }

    /**
     * @return int
     */
    public function getPossibleValuesAjax()
    {
        return $this->_possibleValuesAjax;
    }

    public function getColumnAlignFields()
    {
        return $this->_column_align_fields;
    }

    /**
     * @param int $possibleValuesAjax
     */
    public function setPossibleValuesAjax($possibleValuesAjax)
    {
        $this->_possibleValuesAjax = $possibleValuesAjax;
    }

    public function getColumnAlignHeader()
    {
        return $this->_column_align_header;
    }

    public function setColumnAlignHeader($column_align)
    {
        $this->_column_align_header = $column_align;
    }

    public function setColumnAlignFields($column_align)
    {
        $this->_column_align_fields = $column_align;
    }

    /**
     * @return mixed
     */
    public function getForeignKeyRule()
    {
        return $this->_foreignKeyRule;
    }

    public function getColumnRotationHeader()
    {
        return $this->_column_rotate_header_name;
    }

    public function setColumnRotationHeader($column_rotate)
    {
        $this->_column_rotate_header_name = $column_rotate;
    }

    /**
     * @param mixed $foreignKeyRule
     */
    public function setForeignKeyRule($foreignKeyRule)
    {
        $this->_foreignKeyRule = $foreignKeyRule;
    }

    /**
     * @return mixed
     */
    public function getEditingDefaultValue()
    {
        $value = $this->_editingDefaultValue;

        if ($value) {
            if ($this->getForeignKeyRule()) {
                $foreignKeyRule = $this->getForeignKeyRule();
                $joinedTable = WPDataTable::loadWpDataTable($foreignKeyRule->tableId);
                $distinctValues = $joinedTable->getDistinctValuesForColumns($foreignKeyRule);

                $valueCopy = $value;
                $value = [];
                if (is_array($valueCopy)) {
                    foreach ($valueCopy as $copy) {
                        $value[] = [
                            'value' => $copy,
                            'text' => $distinctValues[$copy]
                        ];
                    }
                } else {
                    $value['value'] = $valueCopy;
                    $value['text'] = $distinctValues[$valueCopy];
                }
            } else {
                if (is_array($value)) {
                    foreach ($value as &$singleValue) {
                        $singleValue = $this->applyPlaceholders($singleValue);
                    }
                } else {
                    $value = $this->applyPlaceholders($value);
                }
            }
        }
        $value = apply_filters_deprecated(
            'wpdt_filter_editing_default_value',
            array($value, $this->getOriginalHeader(), $this->getParentTable()->getWpId()),
            WDT_INITIAL_STARTER_VERSION,
            'wpdatatables_filter_editing_default_value'
        );
        $value = apply_filters('wpdatatables_filter_editing_default_value', $value, $this->getOriginalHeader(), $this->getParentTable()->getWpId());

        return $value;
    }

    /**
     * @param string $editingDefaultValue
     */
    public function setEditingDefaultValue($editingDefaultValue)
    {
        $this->_editingDefaultValue = $editingDefaultValue;
    }

    /**
     * @return null
     */
    public function getParentTable()
    {
        return $this->_parentTable;
    }

    /**
     * @param null $parentTable
     */
    public function setParentTable($parentTable)
    {
        $this->_parentTable = $parentTable;
    }

    /**
     * @param $cellContent
     *
     * @return mixed
     */
    public function returnCellValue($cellContent)
    {
        $cellValue = $this->prepareCellOutput($cellContent);
        $cellValue = apply_filters('wpdatatables_filter_cell_val', $cellValue, $this->getParentTable()->getWpId());
        return $cellValue;
    }

    /**
     * Get column type for Google Charts
     *
     * @return string
     */
    public function getGoogleChartColumnType()
    {
        return 'string';
    }

    public function hideOnTablets()
    {
        $this->_hiddenOnTablets = true;
    }

    public function showOnTablets()
    {
        $this->_hiddenOnTablets = false;
    }

    public function getHiddenAttr()
    {
        $hidden = array();
        if ($this->_hiddenOnPhones) {
            $hidden[] = 'phone';
        }
        if ($this->_hiddenOnTablets) {
            $hidden[] = 'tablet';
        }
        return implode(',', $hidden);
    }

    /**
     * @param $content
     *
     * @return mixed
     */
    public function prepareCellOutput($content)
    {
        if (is_array($content)) {
            return $content['value'];
        }

        return $content;
    }

    /**
     * Apply placeholders
     *
     * @param $value
     *
     * @return mixed
     */
    private function applyPlaceholders($value)
    {

        if (defined('WDT_PH_INTEGRATION')) {
            return \WDTIntegration\Placeholders::maybeApplyInColumns($value);
        }

        return $value;
    }

    /**
     * Generates column object based on column type
     *
     * @param string $wdtColumnType
     * @param array $properties
     *
     * @return mixed
     */
    public static function generateColumn($wdtColumnType = 'string', $properties = array())
    {
        if (!$wdtColumnType) {
            $wdtColumnType = 'string';
        }
        // Security Fix: Whitelist valid column types to prevent LFI
        $validColumnTypes = array(
            'string', 'int', 'float', 'date', 'datetime', 'time', 'link', 'email',
            'image', 'file', 'formula', 'masterdetail', 'attachment'
        );

        // Normalize and validate column type
        $wdtColumnType = strtolower(sanitize_text_field($wdtColumnType));

        if (!in_array($wdtColumnType, $validColumnTypes, true)) {
            // Invalid column type, default to string
            $wdtColumnType = 'string';
        }

        $columnObj = ucfirst($wdtColumnType) . 'WDTColumn';
        $columnFormatterFileName = 'class.' . $wdtColumnType . '.wpdatacolumn.php';
        $columnFormatterFileName = apply_filters('wpdatatables_column_formatter_file_name', $columnFormatterFileName, $wdtColumnType);
        require_once($columnFormatterFileName);
        return new $columnObj($properties);
    }

    /**
     * Get JSON for a column
     *
     * @return StdClass
     */
    public function getColumnJSON($columnID)
    {
        $colJsDefinition = new StdClass();
        $colJsDefinition = apply_filters('wpdatatables_extend_column_js_definition', $colJsDefinition, $this);
        $colJsDefinition->sType = $this->_jsDataType;
        $colJsDefinition->wdtType = $this->_dataType;
        $colJsDefinition->bVisible = $this->isVisible();
        $colJsDefinition->orderable = $this->getSorting();
        $colJsDefinition->searchable = $this->_searchable && $this->_globalSearchColumn;
        $colJsDefinition->InputType = $this->_inputType;
        $colJsDefinition->name = $this->_orig_header;
        $colJsDefinition->origHeader = $this->_orig_header;
        $colJsDefinition->notNull = $this->_notNull;
        $colJsDefinition->conditionalFormattingRules = $this->getConditionalFormattingData();
        $colJsDefinition->transformValueRules = $this->getTransformValueText();
        if (sanitize_html_class(strtolower(str_replace(' ', '-', $this->_orig_header)))) {
            $colJsDefinition->className = $this->getCSSClasses() . ' column-' . sanitize_html_class(strtolower(str_replace(' ', '-', $this->_orig_header)));
        } else {
            $colJsDefinition->className = $this->getCSSClasses() . ' column-' . $columnID;
        }
        if ($this->_width != '') {
            $colJsDefinition->sWidth = $this->_width;
        }
        $colJsDefinition = apply_filters('wpdatatables_filter_column_js_definition', $colJsDefinition, $this->_title);
        return $colJsDefinition;
    }

    /**
     * Get Filter definition for a column
     *
     * @return stdClass
     * @throws WDTException
     */
    public function getJSFilterDefinition()
    {
        /** @var WPDataTable $parentTable */
        $parentTable = $this->getParentTable();
        $jsFilterDef = new stdClass();

        $jsFilterDef->type = $this->getFilterType();
        $jsFilterDef->columnType = $this->getDataType();
        $jsFilterDef->numberOfDecimalPlaces = $this->getDecimalPlaces() === -1 ? get_option('wdtDecimalPlaces') : $this->getDecimalPlaces();
        $jsFilterDef->possibleValuesType = $this->getPossibleValuesType();
        $jsFilterDef->globalSearchColumn = $this->getGlobalSearchColumn();

        $jsFilterDef->values = null;
        if ((in_array($this->getFilterType(), array('select',
                'multiselect',
                'checkbox')) || in_array($this->getInputType(), array('selectbox', 'multi-selectbox')))) {
            if ($this->_possibleValuesType === 'read' && $parentTable->serverSide()) {
                if (has_filter('wpdatatables_possible_values_' . $parentTable->getTableType())) {
                    $distValues = apply_filters('wpdatatables_possible_values_' . $parentTable->getTableType(), $this, true, false);
                } else {
                    $distValues = self::getPossibleValuesRead($this, true, false);
                }
                foreach ($distValues as $value) {
                    $distinctValue['value'] = $value;
                    $distinctValue['label'] = $this->prepareCellOutput($value);
                    $jsFilterDef->values[] = $distinctValue;
                }
            } elseif ($this->_possibleValuesType === 'list') {
                foreach ($this->getPossibleValuesList() as $value) {
                    $distinctValue['value'] = $value;
                    $distinctValue['label'] = $value;
                    $jsFilterDef->values[] = $distinctValue;
                }
            } elseif ($this->_possibleValuesType === 'foreignkey' && $parentTable->serverSide()) {
                $readValues = [];
                if ($this->getParentTable()->getOnlyOwnRows()) {
                    $readValues = self::getPossibleValuesRead($this, true, false);
                }
                foreach ($this->getPossibleValuesList() as $value => $label) {
                    // If foreign key is used with "User can see only own rows"
                    if ($this->getParentTable()->getOnlyOwnRows()) {
                        if (in_array($value, $readValues, false)) {
                            $distinctValue['value'] = $value;
                            $distinctValue['label'] = $label;
                            $jsFilterDef->values[] = $distinctValue;
                        }
                    } else {
                        $distinctValue['value'] = $value;
                        $distinctValue['label'] = $label;
                        $jsFilterDef->values[] = $distinctValue;
                    }
                }
            }
        }

        if ($this->getRangeSlider() === 1 && $this->getParentTable()->serverSide()) {
            $jsFilterDef->minValue = $this->getColumnMinValue();
            $jsFilterDef->maxValue = $this->getColumnMaxValue();
        }

        if (($this->getFilterType() === 'select') && $parentTable->serverSide() && $this->getPossibleValuesAddEmpty()) {
            array_unshift(
                $jsFilterDef->values,
                [
                    'value' => 'possibleValuesAddEmpty',
                    'label' => ' '
                ]
            );
        }

        $jsFilterDef->values = apply_filters(
            'wpdatatables_filter_js_filtering_definition_values',
            $jsFilterDef->values, $this->getInputType(), $this->getOriginalHeader(), $this->getParentTable()->getWpId(), $this);

        $jsFilterDef->origHeader = $this->getOriginalHeader();
        $jsFilterDef->displayHeader = $this->getTitle();
        $jsFilterDef->possibleValuesAddEmpty = $this->getPossibleValuesAddEmpty();
        $jsFilterDef->possibleValuesAjax = $this->getPossibleValuesAjax();
        $jsFilterDef->column_align_fields = $this->getColumnAlignFields();
        $jsFilterDef->defaultValue = $this->getFilterDefaultValue();
        $jsFilterDef->column_align_header = $this->getColumnAlignHeader();
        $jsFilterDef->column_rotate_header_name = $this->getColumnRotationHeader();
        $jsFilterDef->exactFiltering = $this->getExactFiltering();
        $jsFilterDef->filterLabel = $this->getFilterLabel();
        $jsFilterDef->searchInSelectBox = $this->getSearchInSelectBox();
        $jsFilterDef->searchInSelectBoxEditing = $this->getSearchInSelectBoxEditing();
        $jsFilterDef->checkboxesInModal = $this->isCheckboxesInModal();
        $jsFilterDef->andLogic = $this->isAndLogic();
        $jsFilterDef->linkButtonLabel = $this->getLinkButtonLabel();
        $jsFilterDef->rangeSlider = $this->getRangeSlider();
        $jsFilterDef->rangeMaxValueDisplay = $this->getRangeMaxValueDisplay();
        $jsFilterDef->customMaxRangeValue = $this->getCustomMaxRangeValue();

        return apply_filters(
            'wpdatatables_filter_js_filtering_definition',
            $jsFilterDef, $this->getInputType(), $this->getOriginalHeader(), $this->getParentTable()->getWpId(), $this);
    }

    /**
     * Get Editing definition for a column
     *
     * @return stdClass
     */
    public function getJSEditingDefinition()
    {

        $parentTable = $this->getParentTable();
        $jsEditingDef = new stdClass();

        $jsEditingDef->type = $this->getInputType();
        $jsEditingDef->possibleValuesType = $this->getPossibleValuesType();

        $jsEditingDef->values = null;
        if (in_array($this->getInputType(), array('selectbox', 'multi-selectbox'))) {
            if ($this->_possibleValuesType === 'read' && $parentTable->serverSide()) {
                if (has_filter('wpdatatables_possible_values_' . $parentTable->getTableType())) {
                    $distValues = apply_filters('wpdatatables_possible_values_' . $parentTable->getTableType(), $this, true, false);
                } else {
                    $distValues = self::getPossibleValuesRead($this, true, false);
                }
                foreach ($distValues as $value) {
                    $distinctValue['value'] = $value;
                    $distinctValue['label'] = $this->prepareCellOutput($value);
                    $jsEditingDef->values[] = $distinctValue;
                }
            } elseif ($this->_possibleValuesType === 'list') {
                foreach ($this->getPossibleValuesList() as $value) {
                    $distinctValue['value'] = $value;
                    $distinctValue['label'] = $value;
                    $jsEditingDef->values[] = $distinctValue;
                }
            } elseif ($this->_possibleValuesType === 'foreignkey' && $parentTable->serverSide()) {
                $readValues = [];
                $foreignKeyRule = $this->getForeignKeyRule();
                $allowAllPossibleValuesForeignKey = $foreignKeyRule->allowAllPossibleValuesForeignKey;
                if ($this->getParentTable()->getOnlyOwnRows()) {
                    $readValues = self::getPossibleValuesRead($this, true, false);
                }
                foreach ($this->getPossibleValuesList() as $value => $label) {
                    // If foreign key is used with "User can see only own rows"
                    if ($this->getParentTable()->getOnlyOwnRows() && !$allowAllPossibleValuesForeignKey) {
                        if (in_array($value, $readValues, false)) {
                            $distinctValue['value'] = $value;
                            $distinctValue['label'] = $label;
                            $jsEditingDef->values[] = $distinctValue;
                        }
                    } else {
                        $distinctValue['value'] = $value;
                        $distinctValue['label'] = $label;
                        $jsEditingDef->values[] = $distinctValue;
                    }
                }
            }
        }

        $jsEditingDef->values = apply_filters(
            'wpdatatables_filter_js_editing_definition_values',
            $jsEditingDef->values, $this->getInputType(), $this->getOriginalHeader(), $this->getParentTable()->getWpId(), $this);

        $jsEditingDef->origHeader = $this->getOriginalHeader();
        $jsEditingDef->editorInputType = $this->getInputType();
        $jsEditingDef->defaultValue = $this->getEditingDefaultValue();
        $jsEditingDef->defaultValue = $this->applyPlaceholders($jsEditingDef->defaultValue);
        $jsEditingDef->possibleValuesAjax = $this->getPossibleValuesAjax();
        $jsEditingDef->column_align_fields = $this->getColumnAlignFields();
        $jsEditingDef->mandatory = $this->isNotNull();
        $jsEditingDef->displayHeader = $this->getTitle();
        $jsEditingDef->foreignKeyRule = $this->getForeignKeyRule();
        $jsEditingDef->searchInSelectBoxEditing = $this->getSearchInSelectBoxEditing();
        $jsEditingDef->column_align_header = $this->getColumnAlignHeader();
        $jsEditingDef->column_rotate_header_name = $this->getColumnRotationHeader();

        return apply_filters(
            'wpdatatables_filter_js_editing_definition',
            $jsEditingDef, $this->getInputType(), $this->getOriginalHeader(), $this->getParentTable()->getWpId(), $this);
    }

    /**
     * Get possible values based on "Possible values for column" type
     *
     * Used to populate "Predefined value(s)" selectboxes in column settings and
     * for loading for possible values with AJAX
     *
     * @return array
     */
    public function getPossibleValues()
    {
        /** @var WPDataTable $parentTable */
        $parentTable = $this->getParentTable();
        $values = array();

        if (empty($this->_formula) && $this->getDataType() !== '' && !in_array($this->getDataType(), array('date',
                'datetime',
                'time',
                'formula',
                'select',
                'index'), true)) {
            if ($this->_possibleValuesType === 'read' && $parentTable->serverSide()) {
                if (has_filter('wpdatatables_possible_values_' . $parentTable->getTableType())) {
                    $values = apply_filters('wpdatatables_possible_values_' . $parentTable->getTableType(), $this, true, false);
                } else {
                    $values = self::getPossibleValuesRead($this, true, false);
                }
            } elseif ($this->_possibleValuesType === 'list' || ($this->_possibleValuesType === 'foreignkey' && $parentTable->serverSide() == false)) {
                $values = $this->getPossibleValuesList();
            } elseif ($this->_possibleValuesType === 'foreignkey') {
                foreach ($this->getPossibleValuesList() as $value => $label) {
                    $distinctValue['value'] = $value;
                    $distinctValue['text'] = $label;
                    $values[] = $distinctValue;
                }
            } else {
                foreach ($parentTable->getDataRows() as $row) {
                    $values[] = $row[$this->getOriginalHeader()];
                }
                $values = array_unique($values);
            }
        }

        return $values;
    }

    /**
     * Get distinct values for a column
     *
     * @param WDTColumn $column
     * @param           $tableData
     * @param           $filterByUserId
     *
     * @return array|bool
     */
    public static function getPossibleValuesRead($column, $filterByUserId, $tableData = null)
    {
        global $wdtVar1, $wdtVar2, $wdtVar3, $wdtVar4, $wdtVar5, $wdtVar6, $wdtVar7, $wdtVar8, $wdtVar9;
        $distValues = array();
        /** @var WPDataTable $parentTable */
        $parentTable = $column->getParentTable();
        $columnOrigHeader = $column->getOriginalHeader();

        $vendor = Connection::getVendor($parentTable->connection);

        $leftSysIdentifier = Connection::getLeftColumnQuote($vendor);
        $rightSysIdentifier = Connection::getRightColumnQuote($vendor);

        $isMySql = $vendor === Connection::$MYSQL;
        $isMSSql = $vendor === Connection::$MSSQL;
        $isPostgreSql = $vendor === Connection::$POSTGRESQL;

        $where = 'WHERE 1=1';
        if (isset($_POST['q']) && $_POST['q'] !== '') {
            $q_raw = sanitize_text_field(wp_unslash($_POST['q']));
            if ($isMySql && !Connection::isSeparate($parentTable->connection)) {
                global $wpdb;
                $where .= $wpdb->prepare(
                    " AND ({$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier}) LIKE %s",
                    '%' . $wpdb->esc_like($q_raw) . '%'
                );
            } else {
                $escaped_like = '%' . str_replace(array('\\', '%', '_'), array('\\\\', '\\%', '\\_'), $q_raw) . '%';
                $where .= " AND ({$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier}) LIKE '" . esc_sql($escaped_like) . "'";
            }
        }

        $wdtVar1 = $wdtVar1 === '' && isset($tableData->var1) ? $tableData->var1 : $wdtVar1;
        $wdtVar2 = $wdtVar2 === '' && isset($tableData->var2) ? $tableData->var2 : $wdtVar2;
        $wdtVar3 = $wdtVar3 === '' && isset($tableData->var3) ? $tableData->var3 : $wdtVar3;
        $wdtVar4 = $wdtVar4 === '' && isset($tableData->var4) ? $tableData->var4 : $wdtVar4;
        $wdtVar5 = $wdtVar5 === '' && isset($tableData->var5) ? $tableData->var5 : $wdtVar5;
        $wdtVar6 = $wdtVar6 === '' && isset($tableData->var6) ? $tableData->var6 : $wdtVar6;
        $wdtVar7 = $wdtVar7 === '' && isset($tableData->var7) ? $tableData->var7 : $wdtVar7;
        $wdtVar8 = $wdtVar8 === '' && isset($tableData->var8) ? $tableData->var8 : $wdtVar8;
        $wdtVar9 = $wdtVar9 === '' && isset($tableData->var9) ? $tableData->var9 : $wdtVar9;

        $tableContent = WDTTools::applyPlaceholders($parentTable->getTableContent());

        if ($filterByUserId && $parentTable->getOnlyOwnRows() === true) {
            $where .= " AND {$leftSysIdentifier}" . $parentTable->getUserIdColumn() . "{$rightSysIdentifier} = " . get_current_user_id();
        }

        if ($isMySql) {
            $limit = $column->getPossibleValuesAjax() !== -1 ? 'LIMIT ' . $column->getPossibleValuesAjax() : '';
            $distValuesQuery = "SELECT DISTINCT({$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier}) AS {$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier} FROM ( $tableContent ) tbl $where ORDER BY ({$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier}) $limit";
        }

        if ($isMSSql) {
            $limit = $column->getPossibleValuesAjax() !== -1 ? " OFFSET 0 ROWS FETCH NEXT {$column->getPossibleValuesAjax()} ROWS ONLY" : '';
            $distValuesQuery = "SELECT DISTINCT({$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier}) AS {$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier} FROM ( $tableContent ) tbl $where ORDER BY ({$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier}) $limit";
        }

        if ($isPostgreSql) {
            $limit = $column->getPossibleValuesAjax() !== -1 ? 'LIMIT ' . $column->getPossibleValuesAjax() : '';
            $distValuesQuery = "SELECT DISTINCT({$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier}) AS {$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier} FROM ( $tableContent ) tbl $where ORDER BY ({$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier}) $limit";
        }

        if (!(Connection::isSeparate($parentTable->connection))) {
            global $wpdb;
            $distValues = $wpdb->get_col($distValuesQuery);
            if ($wpdb->last_error) {
                return false;
            }
        } else {
            $sql = Connection::getInstance($parentTable->connection);
            $rows = $sql->getArray($distValuesQuery);

            if (!empty($rows)) {
                foreach ($rows as $row) {
                    $distValues[] = $row[0];
                }
            }
        }

        // Filter array to remove NULL from the $distValues
        return array_values(array_filter($distValues, function ($value) {
            return ($value !== null && $value !== false && $value !== '');
        }));
    }

    private function getColumnMinValue()
    {
        global $wpdb;
        $parentTable = $this->getParentTable();
        $columnOrigHeader = $this->getOriginalHeader();

        // Handle WP Posts Query tables
        if ($parentTable->getTableType() === 'wp_posts_query') {
            $wpQueryParams = json_decode($parentTable->getTableContent(), true);
            $customFieldColumns = isset($wpQueryParams['customFieldColumns']) ? $wpQueryParams['customFieldColumns'] : array();
            $isCustomField = false;

            // Check if this is a custom field
            if (!empty($customFieldColumns)) {
                foreach ($customFieldColumns as $customFieldColumn) {
                    if (isset($customFieldColumn['cf']) && $customFieldColumn['cf'] === $columnOrigHeader) {
                        $isCustomField = true;
                        break;
                    }
                }
            }

            if ($isCustomField) {
                // Get min value from post meta
                $minValue = $wpdb->get_var($wpdb->prepare(
                    "SELECT MIN(CAST(meta_value AS DECIMAL(10,2))) 
                     FROM {$wpdb->postmeta} 
                     WHERE meta_key = %s 
                     AND meta_value REGEXP '^-?[0-9]+\.?[0-9]*$'",
                    $columnOrigHeader
                ));
                return $minValue !== null ? (float)$minValue : 0;
            } else {
                // For standard post fields, get all posts and find min
                $posts = get_posts(array_merge($wpQueryParams, array('posts_per_page' => -1, 'fields' => 'ids')));
                $minValue = null;
                foreach ($posts as $postId) {
                    $value = null;
                    switch ($columnOrigHeader) {
                        case 'ID':
                        case 'comment_count':
                        case 'menu_order':
                        case 'post_parent':
                            $post = get_post($postId);
                            $value = (float)$post->{$columnOrigHeader};
                            break;
                    }
                    if ($value !== null && ($minValue === null || $value < $minValue)) {
                        $minValue = $value;
                    }
                }
                return $minValue !== null ? $minValue : 0;
            }
        }

        // Original SQL-based logic for other table types
        $parentTableContent = WDTTools::applyPlaceholders($parentTable->getTableContent());
        $vendor = Connection::getVendor($parentTable->connection);

        $leftSysIdentifier = Connection::getLeftColumnQuote($vendor);
        $rightSysIdentifier = Connection::getRightColumnQuote($vendor);

        $isMySql = $vendor === Connection::$MYSQL;
        $isMSSql = $vendor === Connection::$MSSQL;
        $isPostgreSql = $vendor === Connection::$POSTGRESQL;

        $minQuery = "SELECT MIN(" . $columnOrigHeader . ") as min FROM (" . $parentTableContent . ") as parentTable";

        if ($isMySql) {
            $minQuery = "SELECT MIN({$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier}) as min FROM (" . $parentTableContent . ") as parentTable";
        } else if ($isMSSql) {
            $minQuery = "SELECT min({$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier}) as min FROM (" . $parentTableContent . ") as parentTable";
        } else if ($isPostgreSql) {
            $minQuery = "SELECT MIN({$leftSysIdentifier}{$columnOrigHeader}$rightSysIdentifier) AS min FROM (" . $parentTableContent . ") AS parentTable";
        }

        if (!(Connection::isSeparate($parentTable->connection))) {
            return (float)$wpdb->get_row($minQuery)->min;
        } else {
            $sql = Connection::getInstance($parentTable->connection);
            $minValue = $sql->getRow($minQuery)['min'];
            return (float)($minValue);
        }
    }

    private function getColumnMaxValue()
    {
        global $wpdb;
        $parentTable = $this->getParentTable();
        $columnOrigHeader = $this->getOriginalHeader();

        // Handle WP Posts Query tables
        if ($parentTable->getTableType() === 'wp_posts_query') {
            $wpQueryParams = json_decode($parentTable->getTableContent(), true);
            $customFieldColumns = isset($wpQueryParams['customFieldColumns']) ? $wpQueryParams['customFieldColumns'] : array();
            $isCustomField = false;

            // Check if this is a custom field
            if (!empty($customFieldColumns)) {
                foreach ($customFieldColumns as $customFieldColumn) {
                    if (isset($customFieldColumn['cf']) && $customFieldColumn['cf'] === $columnOrigHeader) {
                        $isCustomField = true;
                        break;
                    }
                }
            }

            if ($isCustomField) {
                // Get max value from post meta
                $maxValue = $wpdb->get_var($wpdb->prepare(
                    "SELECT MAX(CAST(meta_value AS DECIMAL(10,2))) 
                     FROM {$wpdb->postmeta} 
                     WHERE meta_key = %s 
                     AND meta_value REGEXP '^-?[0-9]+\.?[0-9]*$'",
                    $columnOrigHeader
                ));
                return $maxValue !== null ? (float)$maxValue : 0;
            } else {
                // For standard post fields, get all posts and find max
                $posts = get_posts(array_merge($wpQueryParams, array('posts_per_page' => -1, 'fields' => 'ids')));
                $maxValue = null;
                foreach ($posts as $postId) {
                    $value = null;
                    switch ($columnOrigHeader) {
                        case 'ID':
                        case 'comment_count':
                        case 'menu_order':
                        case 'post_parent':
                            $post = get_post($postId);
                            $value = (float)$post->{$columnOrigHeader};
                            break;
                    }
                    if ($value !== null && ($maxValue === null || $value > $maxValue)) {
                        $maxValue = $value;
                    }
                }
                return $maxValue !== null ? $maxValue : 0;
            }
        }

        // Original SQL-based logic for other table types
        $parentTableContent = WDTTools::applyPlaceholders($parentTable->getTableContent());
        $vendor = Connection::getVendor($parentTable->connection);

        $leftSysIdentifier = Connection::getLeftColumnQuote($vendor);
        $rightSysIdentifier = Connection::getRightColumnQuote($vendor);

        $isMySql = $vendor === Connection::$MYSQL;
        $isMSSql = $vendor === Connection::$MSSQL;
        $isPostgreSql = $vendor === Connection::$POSTGRESQL;

        $maxQuery = "SELECT MAX(" . $columnOrigHeader . ") AS max FROM (" . $parentTableContent . ") AS parentTable";

        if ($isMySql) {
            $maxQuery = "SELECT MAX({$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier}) AS max FROM (" . $parentTableContent . ") AS parentTable";
        } else if ($isMSSql) {
            $maxQuery = "SELECT max({$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier}) as max FROM (" . $parentTableContent . ") as parentTable";
        } else if ($isPostgreSql) {
            $maxQuery = "SELECT MAX({$leftSysIdentifier}{$columnOrigHeader}{$rightSysIdentifier}) AS max FROM (" . $parentTableContent . ") AS parentTable";
        }

        if (!(Connection::isSeparate($parentTable->connection))) {
            return (float)$wpdb->get_row($maxQuery)->max;
        } else {
            $sql = Connection::getInstance($parentTable->connection);
            $maxValue = $sql->getRow($maxQuery)['max'];
            return (float)($maxValue);
        }
    }
}
