yf_table2_filter class

Table2 plugin.

Fileplugins/table2/classes/table2/yf_table2_filter.class.php
Use_class('table2_filter', 'classes/table2/')

Public properties

A site sets them in its config with $PROJECT_CONF['table2_filter']['NAME'] = ...;

PropertyDefaultDescription
$supported_conds []

Public methods 7

None without a leading underscore.

Methods starting with _ hooks and helpers; not routable as page actions

public function _escape_values()
    public function _escape_values()
    {
        return (bool) _class('table2')->ESCAPE_FILTER_VALUES;
    }

_filter_array

public function _filter_array(&$data, $filter = [], $filter_params = [], $table = null)

Simple filtering of the given array. Need to support table() raw array data with filtering.

    public function _filter_array(&$data, $filter = [], $filter_params = [], $table = null)
    {
        if ( ! $data || ! $filter) {
            return false;
        }
        foreach ((array) $data as $_id => $_data) {
            foreach ((array) $filter as $fk => $fv) {
                if (isset($_data[$fk]) && strlen($fv)) {
                    if (is_array($_data[$fk])) {
                        if (isset($filter_params[$fk])) {
                            $fp = $filter_params[$fk];
                            foreach ((array) $_data[$fk] as $k2 => $v2) {
                                if ($fp == 'like') {
                                    if (false === strpos($_data[$fk][$k2], $fv)) {
                                        unset($data[$_id]);
                                        continue 3;
                                    }
                                } elseif ($fp == 'eq') {
                                    if ($_data[$fk][$k2] != $fv) {
                                        unset($data[$_id]);
                                        continue 3;
                                    }
                                } elseif ($fp == 'ne') {
                                    if ($_data[$fk][$k2] == $fv) {
                                        unset($data[$_id]);
                                        continue 3;
                                    }
                                } elseif ($fp == 'gt') {
                                    if ($_data[$fk][$k2] <= $fv) {
                                        unset($data[$_id]);
                                        continue 3;
                                    }
                                } elseif ($fp == 'gte') {
                                    if ($_data[$fk][$k2] < $fv) {
                                        unset($data[$_id]);
                                        continue 3;
                                    }
                                } elseif ($fp == 'lt') {
                                    if ($_data[$fk][$k2] >= $fv) {
                                        unset($data[$_id]);
                                        continue 3;
                                    }
                                } elseif ($fp == 'lte') {
                                    if ($_data[$fk][$k2] > $fv) {
                                        unset($data[$_id]);
                                        continue 3;
                                    }
                                } elseif ($fp == 'rlike') {
                                    // TODO
                                } elseif ($fp == 'between') {
                                    // TODO
                                }
                            }
                        } elseif ( ! isset($_data[$fk][$fv])) {
                            unset($data[$_id]);
                            continue 2;
                        }
                    } else {
                        if (isset($filter_params[$fk])) {
                            $fp = $filter_params[$fk];
                            if ($fp == 'like') {
                                if (false === strpos($_data[$fk], $fv)) {
                                    unset($data[$_id]);
                                    continue 2;
                                }
                            } elseif ($fp == 'eq') {
                                if ($_data[$fk] != $fv) {
                                    unset($data[$_id]);
                                    continue 2;
                                }
                            } elseif ($fp == 'ne') {
                                if ($_data[$fk] == $fv) {
                                    unset($data[$_id]);
                                    continue 2;
                                }
                            } elseif ($fp == 'gt') {
                                if ($_data[$fk] <= $fv) {
                                    unset($data[$_id]);
                                    continue 2;
                                }
                            } elseif ($fp == 'gte') {
                                if ($_data[$fk] < $fv) {
                                    unset($data[$_id]);
                                    continue 2;
                                }
                            } elseif ($fp == 'lt') {
                                if ($_data[$fk] >= $fv) {
                                    unset($data[$_id]);
                                    continue 2;
                                }
                            } elseif ($fp == 'lte') {
                                if ($_data[$fk] > $fv) {
                                    unset($data[$_id]);
                                    continue 2;
                                }
                            } elseif ($fp == 'rlike') {
                                // TODO
                            } elseif ($fp == 'between') {
                                // TODO
                            }
                        } elseif ($_data[$fk] != $fv) {
                            unset($data[$_id]);
                            continue 2;
                        }
                    }
                }
            }
        }
    }
public function _filter_sql_prepare($filter_data = [], $filter_params = [], $__sql = '', $table = null)
    public function _filter_sql_prepare($filter_data = [], $filter_params = [], $__sql = '', $table = null)
    {
        if ( ! $filter_data) {
            return '';
        }
        if (is_null($table) || ($table?->_params['db'] ?? false) || ! is_object($table->_params['db'])) {
            $db = db();
        } else {
            $db = $table->_params['db'];
        }
        $special_fields = [
            'order_by',
            'order_direction',
        ];
        $array_fields = [
            'in_array',
        ];
        $supported_conds = &$this->supported_conds;
        $sql = null;
        $filter_sql = null;
        $order_sql = null;
        foreach ((array) $filter_data as $k => $v) {
            if ( ! strlen($k)) {
                continue;
            }
            if (in_array($k, $special_fields)) {
                continue;
            }
            // Special field for BETWEEN second value
            if (substr($k, -strlen('__and')) == '__and') {
                $k = substr($k, 0, -strlen('__and'));
                $from = $filter_data[$k];
                if ($from) {
                    continue;
                }
                $v = ' ';
            }
            $field = $k;
            $left_part = '';
            $part_on_the_right = '';
            // Here we support complex filtering conditions, examples:
            // 'price' => array('gt', 'value' => '100')
            // 'price' => array('between', 'value' => '1', 'and' => '10')
            // 'name' => array('like', 'value' => 'john')
            if (is_array($v) && ! (@is_array($filter_params[$k]) && @in_array($filter_params[$k]['cond'], $array_fields))) {
                $cond = isset($v[0]) ? $v[0] : $v['cond'];
                if ( ! $cond) {
                    continue;
                }
                if ( ! isset($supported_conds[$cond])) {
                    continue;
                }
                if ( ! isset($v['and']) && $this->_escape_values()) {
                    $v['and'] = $filter_data[$k . '__and'] ?? null;
                }
                if ($v['field']) {
                    $field = $v['field'];
                }
                $part_on_the_right = $supported_conds[$cond]($v);
            } else {
                if (@is_array($filter_params[$k]) && @in_array($filter_params[$k]['cond'], $array_fields)) {
                    $v = implode(',', array_values($v));
                }
                if ( ! strlen($v)) {
                    continue;
                }
                $cond = 'eq';
                $func = null;
                // Here we can override default 'eq' condition with custom one by passing filter $params like this: table($sql, array('filter_params' => $filter_params)).
                $field_params = $filter_params[$k];
                if ($field_params) {
                    // Fully replacing left and right parts with callback function
                    // Example: table($sql, array('filter_params' => array('my_field' => function($a){ return ' v.value LIKE "%'._es($a['value']).'%" '; } )))
                    if (is_callable($field_params)) {
                        $left_part = ' ';
                        $func = $field_params;
                        // Ways of passing array of params: 1) long and 2) short
                        // Example: table($sql, array('filter_params' => array('value'	=> array('cond' => 'like', 'field' => 'v.value'))))
                        // Example: table($sql, array('filter_params' => array('translation' => array('like', 't.value'))))
                    } elseif (is_array($field_params)) {
                        $cond = isset($field_params['cond']) ? $field_params['cond'] : $field_params[0];
                        if ( ! $cond) {
                            $cond = 'eq';
                        }
                        $func = $supported_conds[$cond];
                        if ($field_params['field']) {
                            $field = $field_params['field'];
                        } elseif (isset($field_params[1])) {
                            $field = $field_params[1];
                        }
                        // Predefined condition found (gt, between, like, etc..)
                        // Example: table($sql, array('filter_params' => array('name' => 'gt', 'price' => 'between')))
                    } elseif (isset($supported_conds[$field_params])) {
                        $cond = $field_params;
                        $func = $supported_conds[$cond];
                    }
                } else {
                    $func = $supported_conds[$cond];
                }
                // Field with __and on the end of its name is special one for 'between' condition
                if ($func) {
                    $part_on_the_right = $func(['value' => $v, 'and' => ($filter_data[$k . '__and'] ?? null)], $filter_data);
                }
            }
            if ( ! strlen($left_part)) {
                if ($this->_escape_values() && ! $this->_is_identifier($field)) {
                    continue;
                }
                $left_part = '`' . str_replace('.', '`.`', $db->es($field)) . '`';
            }
            if ($part_on_the_right) {
                $sql[] = trim($left_part) . ' ' . trim($part_on_the_right);
            }
        }
        if ($sql) {
            $filter_sql = ' AND ' . implode(' AND ', $sql);
        }
        if (isset($filter_data['order_by']) && $filter_data['order_by'] && strpos(strtoupper($__sql), 'ORDER BY') === false) {
            $order_by_field = $filter_data['order_by'];
            if (is_array($filter_params[$order_by_field])) {
                $field_params = $filter_params[$order_by_field];
                if ($field_params['field']) {
                    $order_by_field = $field_params['field'];
                } elseif (isset($field_params[1])) {
                    $order_by_field = $field_params[1];
                }
            }
            if ($this->_escape_values() && ! $this->_is_identifier($order_by_field)) {
                return [$filter_sql, $order_sql];
            }
            $order_sql = ' ORDER BY `' . str_replace('.', '`.`', $db->es($order_by_field)) . '` ';
            $direction = null;
            if ($filter_data['order_direction']) {
                $direction = strtoupper($filter_data['order_direction']);
            }
            if ($direction && in_array($direction, ['ASC', 'DESC'])) {
                $order_sql .= ' ' . $direction;
            }
        }
        return [$filter_sql, $order_sql];
    }

_in_list

public function _in_list($value)

in / in_array list: today _es() of the whole string without quotes, which does not stop "1) OR (1=1"; with yf_table2::$ESCAPE_FILTER_VALUES every comma separated item is escaped and quoted.

    public function _in_list($value)
    {
        if ( ! $this->_escape_values()) {
            return _es($value);
        }
        $items = [];
        foreach ((array) (is_array($value) ? $value : explode(',', (string) $value)) as $item) {
            $items[] = '"' . _es(trim((string) $item)) . '"';
        }
        return implode(',', $items);
    }

_init

public function _init()
    public function _init()
    {
        $this->supported_conds = [
            'in' => function ($a) {
                return ' IN( ' . $this->_in_list($a['value']) . ' )';
            }, // "in"
            'in_array' => function ($a) {
                return ' IN( ' . $this->_in_list($a['value']) . ' )';
            }, // "in array"
            'eq' => function ($a) {
                return ' = "' . _es($a['value']) . '"';
            }, // "equal"
            'ne' => function ($a) {
                return ' != "' . _es($a['value']) . '"';
            }, // "not equal"
            'gt' => function ($a) {
                return ' > "' . _es($a['value']) . '"';
            }, // "greater than",
            'gte' => function ($a) {
                return ' >= "' . _es($a['value']) . '"';
            }, // "greater or equal than",
            'lt' => function ($a) {
                return ' < "' . _es($a['value']) . '"';
            }, // "less than",
            'lte' => function ($a) {
                return ' <= "' . _es($a['value']) . '"';
            }, // "lower or equal than"
            'like' => function ($a) {
                return ' LIKE "%' . _es($a['value']) . '%"';
            }, // LIKE '%'.$value.'%'
            'rlike' => function ($a) {
                return ' RLIKE "' . _es($a['value']) . '"';
            }, // regular expression, RLIKE $value
            'dt_eq' => function ($a) {
                return ' = "' . _es(strtotime($a['value'])) . '"';
            }, // "equal"
            'dt_ne' => function ($a) {
                return ' != "' . _es(strtotime($a['value'])) . '"';
            }, // "not equal"
            'dt_gt' => function ($a) {
                return ' > "' . _es(strtotime($a['value'])) . '"';
            }, // "greater than",
            'dt_gte' => function ($a) {
                return ' >= "' . _es(strtotime($a['value'])) . '"';
            }, // "greater or equal than",
            'dt_lt' => function ($a) {
                return ' < "' . _es(strtotime($a['value'])) . '"';
            }, // "less than",
            'dt_lte' => function ($a) {
                return ' <= "' . _es(strtotime($a['value'])) . '"';
            }, // "lower or equal than"
            'between' => function ($a) {
                $from = trim($a['value']);
                $to = trim($a['and'] ?? '');
                $result = null;
                if ($from && $to) {
                    if ($from > $to) {
                        $tmp = $from;
                        $from = $to;
                        $to = $tmp;
                    }
                    $result = sprintf(' BETWEEN "%s" AND "%s"', $this->_value($from), $this->_value($to));
                } elseif ($from) {
                    $result = sprintf(' >= "%s"', $this->_value($from));
                } elseif ($to) {
                    $result = sprintf(' <= "%s"', $this->_value($to));
                }
                return $result;
            },
            'dt_between' => function ($a) {
                $from = trim($a['value']);
                $to = trim($a['and'] ?? '');
                ! empty($from) && $from = strtotime($from);
                ! empty($to) && $to = strtotime($to);
                $result = null;
                if ($from && $to) {
                    if ($from > $to) {
                        $tmp = $from;
                        $from = $to;
                        $to = $tmp;
                    }
                    $result = sprintf(' BETWEEN "%s" AND "%s"', $from, $to);
                } elseif ($from) {
                    $result = sprintf(' >= "%s"', $from);
                } elseif ($to) {
                    $result = sprintf(' <= "%s"', $to);
                }
                return $result;
            },
            'datetime_between' => function ($a) {
                $from = trim($a['value']);
                $to = trim($a['and'] ?? '');
                $format = 'Y-m-d H-i-s';
                ! empty($from) && $from = date($format, strtotime($from));
                ! empty($to) && $to = date($format, strtotime($to));
                $result = null;
                if ($from && $to) {
                    if ($from > $to) {
                        $tmp = $from;
                        $from = $to;
                        $to = $tmp;
                    }
                    $result = sprintf(' BETWEEN "%s" AND "%s"', $from, $to);
                } elseif ($from) {
                    $result = sprintf(' >= "%s"', $from);
                } elseif ($to) {
                    $result = sprintf(' <= "%s"', $to);
                }
                return $result;
            },
            'daterange_between' => function ($a) {
                $value = $a['value'];
                if ( ! $value || false === strpos($value, '-')) {
                    return false;
                }
                $only_date_regex = '~^[0-9]{2,4}[\.-][0-9]{2,4}[\.-][0-9]{2,4}$~';
                list($from, $to) = array_pad(explode(' - ', $value), 2, '');
                $from = trim($from);
                if (preg_match($only_date_regex, $from)) {
                    $from .= ' 00:00:00';
                }
                $to = trim($to);
                if (preg_match($only_date_regex, $to)) {
                    $to .= ' 23:59:59';
                }
                ! empty($from) && $from = strtotime(str_replace('.', '-', $from));
                ! empty($to) && $to = strtotime(str_replace('.', '-', $to));
                $result = null;
                if ($from && $to) {
                    if ($from > $to) {
                        $tmp = $from;
                        $from = $to;
                        $to = $tmp;
                    }
                    $result = sprintf(' BETWEEN "%s" AND "%s"', $from, $to);
                } elseif ($from) {
                    $result = sprintf(' >= "%s"', $from);
                } elseif ($to) {
                    $result = sprintf(' <= "%s"', $to);
                }
                return $result;
            },
            'daterange_dt_between' => function ($a) {
                $value = $a['value'];
                if ( ! $value || false === strpos($value, '-')) {
                    return false;
                }
                $only_date_regex = '~^[0-9]{2,4}[\.-][0-9]{2,4}[\.-][0-9]{2,4}$~';
                list($from, $to) = array_pad(explode(' - ', $value), 2, '');
                $from = trim($from);
                if (preg_match($only_date_regex, $from)) {
                    $from .= ' 00:00:00';
                }
                $to = trim($to);
                if (preg_match($only_date_regex, $to)) {
                    $to .= ' 23:59:59';
                }
                $format = 'Y-m-d H-i-s';
                ! empty($from) && $from = date($format, strtotime(str_replace('.', '-', $from)));
                ! empty($to) && $to = date($format, strtotime(str_replace('.', '-', $to)));
                $result = null;
                if ($from && $to) {
                    if ($from > $to) {
                        $tmp = $from;
                        $from = $to;
                        $to = $tmp;
                    }
                    $result = sprintf(' BETWEEN "%s" AND "%s"', $from, $to);
                } elseif ($from) {
                    $result = sprintf(' >= "%s"', $from);
                } elseif ($to) {
                    $result = sprintf(' <= "%s"', $to);
                }
                return $result;
            },
        ];
    }
public function _is_identifier($name)

Field and order_by names come from the posted filter and go into backticks, which es() does not escape.

    public function _is_identifier($name)
    {
        return is_string($name) && preg_match('~^[a-z0-9_.]+\z~i', $name);
    }

_value

public function _value($value)

between value inside double quotes: escaped with yf_table2::$ESCAPE_FILTER_VALUES, raw otherwise.

    public function _value($value)
    {
        return $this->_escape_values() ? _es($value) : $value;
    }