| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872 | <?phpnamespace PhpOffice\PhpSpreadsheet\Worksheet;use PhpOffice\PhpSpreadsheet\Calculation\Calculation;use PhpOffice\PhpSpreadsheet\Calculation\DateTime;use PhpOffice\PhpSpreadsheet\Calculation\Functions;use PhpOffice\PhpSpreadsheet\Cell\Coordinate;use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;use PhpOffice\PhpSpreadsheet\Shared\Date;class AutoFilter{    /**     * Autofilter Worksheet.     *     * @var Worksheet     */    private $workSheet;    /**     * Autofilter Range.     *     * @var string     */    private $range = '';    /**     * Autofilter Column Ruleset.     *     * @var AutoFilter\Column[]     */    private $columns = [];    /**     * Create a new AutoFilter.     *     * @param string $pRange Cell range (i.e. A1:E10)     * @param Worksheet $pSheet     */    public function __construct($pRange = '', Worksheet $pSheet = null)    {        $this->range = $pRange;        $this->workSheet = $pSheet;    }    /**     * Get AutoFilter Parent Worksheet.     *     * @return Worksheet     */    public function getParent()    {        return $this->workSheet;    }    /**     * Set AutoFilter Parent Worksheet.     *     * @param Worksheet $pSheet     *     * @return AutoFilter     */    public function setParent(Worksheet $pSheet = null)    {        $this->workSheet = $pSheet;        return $this;    }    /**     * Get AutoFilter Range.     *     * @return string     */    public function getRange()    {        return $this->range;    }    /**     * Set AutoFilter Range.     *     * @param string $pRange Cell range (i.e. A1:E10)     *     * @throws PhpSpreadsheetException     *     * @return AutoFilter     */    public function setRange($pRange)    {        // Uppercase coordinate        $cellAddress = explode('!', strtoupper($pRange));        if (count($cellAddress) > 1) {            list($worksheet, $pRange) = $cellAddress;        }        if (strpos($pRange, ':') !== false) {            $this->range = $pRange;        } elseif (empty($pRange)) {            $this->range = '';        } else {            throw new PhpSpreadsheetException('Autofilter must be set on a range of cells.');        }        if (empty($pRange)) {            //    Discard all column rules            $this->columns = [];        } else {            //    Discard any column rules that are no longer valid within this range            list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($this->range);            foreach ($this->columns as $key => $value) {                $colIndex = Coordinate::columnIndexFromString($key);                if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) {                    unset($this->columns[$key]);                }            }        }        return $this;    }    /**     * Get all AutoFilter Columns.     *     * @return AutoFilter\Column[]     */    public function getColumns()    {        return $this->columns;    }    /**     * Validate that the specified column is in the AutoFilter range.     *     * @param string $column Column name (e.g. A)     *     * @throws PhpSpreadsheetException     *     * @return int The column offset within the autofilter range     */    public function testColumnInRange($column)    {        if (empty($this->range)) {            throw new PhpSpreadsheetException('No autofilter range is defined.');        }        $columnIndex = Coordinate::columnIndexFromString($column);        list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($this->range);        if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) {            throw new PhpSpreadsheetException('Column is outside of current autofilter range.');        }        return $columnIndex - $rangeStart[0];    }    /**     * Get a specified AutoFilter Column Offset within the defined AutoFilter range.     *     * @param string $pColumn Column name (e.g. A)     *     * @throws PhpSpreadsheetException     *     * @return int The offset of the specified column within the autofilter range     */    public function getColumnOffset($pColumn)    {        return $this->testColumnInRange($pColumn);    }    /**     * Get a specified AutoFilter Column.     *     * @param string $pColumn Column name (e.g. A)     *     * @throws PhpSpreadsheetException     *     * @return AutoFilter\Column     */    public function getColumn($pColumn)    {        $this->testColumnInRange($pColumn);        if (!isset($this->columns[$pColumn])) {            $this->columns[$pColumn] = new AutoFilter\Column($pColumn, $this);        }        return $this->columns[$pColumn];    }    /**     * Get a specified AutoFilter Column by it's offset.     *     * @param int $pColumnOffset Column offset within range (starting from 0)     *     * @throws PhpSpreadsheetException     *     * @return AutoFilter\Column     */    public function getColumnByOffset($pColumnOffset)    {        list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($this->range);        $pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $pColumnOffset);        return $this->getColumn($pColumn);    }    /**     * Set AutoFilter.     *     * @param AutoFilter\Column|string $pColumn     *            A simple string containing a Column ID like 'A' is permitted     *     * @throws PhpSpreadsheetException     *     * @return AutoFilter     */    public function setColumn($pColumn)    {        if ((is_string($pColumn)) && (!empty($pColumn))) {            $column = $pColumn;        } elseif (is_object($pColumn) && ($pColumn instanceof AutoFilter\Column)) {            $column = $pColumn->getColumnIndex();        } else {            throw new PhpSpreadsheetException('Column is not within the autofilter range.');        }        $this->testColumnInRange($column);        if (is_string($pColumn)) {            $this->columns[$pColumn] = new AutoFilter\Column($pColumn, $this);        } elseif (is_object($pColumn) && ($pColumn instanceof AutoFilter\Column)) {            $pColumn->setParent($this);            $this->columns[$column] = $pColumn;        }        ksort($this->columns);        return $this;    }    /**     * Clear a specified AutoFilter Column.     *     * @param string $pColumn Column name (e.g. A)     *     * @throws PhpSpreadsheetException     *     * @return AutoFilter     */    public function clearColumn($pColumn)    {        $this->testColumnInRange($pColumn);        if (isset($this->columns[$pColumn])) {            unset($this->columns[$pColumn]);        }        return $this;    }    /**     * Shift an AutoFilter Column Rule to a different column.     *     * Note: This method bypasses validation of the destination column to ensure it is within this AutoFilter range.     *        Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value.     *        Use with caution.     *     * @param string $fromColumn Column name (e.g. A)     * @param string $toColumn Column name (e.g. B)     *     * @return AutoFilter     */    public function shiftColumn($fromColumn, $toColumn)    {        $fromColumn = strtoupper($fromColumn);        $toColumn = strtoupper($toColumn);        if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) {            $this->columns[$fromColumn]->setParent();            $this->columns[$fromColumn]->setColumnIndex($toColumn);            $this->columns[$toColumn] = $this->columns[$fromColumn];            $this->columns[$toColumn]->setParent($this);            unset($this->columns[$fromColumn]);            ksort($this->columns);        }        return $this;    }    /**     * Test if cell value is in the defined set of values.     *     * @param mixed $cellValue     * @param mixed[] $dataSet     *     * @return bool     */    private static function filterTestInSimpleDataSet($cellValue, $dataSet)    {        $dataSetValues = $dataSet['filterValues'];        $blanks = $dataSet['blanks'];        if (($cellValue == '') || ($cellValue === null)) {            return $blanks;        }        return in_array($cellValue, $dataSetValues);    }    /**     * Test if cell value is in the defined set of Excel date values.     *     * @param mixed $cellValue     * @param mixed[] $dataSet     *     * @return bool     */    private static function filterTestInDateGroupSet($cellValue, $dataSet)    {        $dateSet = $dataSet['filterValues'];        $blanks = $dataSet['blanks'];        if (($cellValue == '') || ($cellValue === null)) {            return $blanks;        }        if (is_numeric($cellValue)) {            $dateValue = Date::excelToTimestamp($cellValue);            if ($cellValue < 1) {                //    Just the time part                $dtVal = date('His', $dateValue);                $dateSet = $dateSet['time'];            } elseif ($cellValue == floor($cellValue)) {                //    Just the date part                $dtVal = date('Ymd', $dateValue);                $dateSet = $dateSet['date'];            } else {                //    date and time parts                $dtVal = date('YmdHis', $dateValue);                $dateSet = $dateSet['dateTime'];            }            foreach ($dateSet as $dateValue) {                //    Use of substr to extract value at the appropriate group level                if (substr($dtVal, 0, strlen($dateValue)) == $dateValue) {                    return true;                }            }        }        return false;    }    /**     * Test if cell value is within a set of values defined by a ruleset.     *     * @param mixed $cellValue     * @param mixed[] $ruleSet     *     * @return bool     */    private static function filterTestInCustomDataSet($cellValue, $ruleSet)    {        $dataSet = $ruleSet['filterRules'];        $join = $ruleSet['join'];        $customRuleForBlanks = isset($ruleSet['customRuleForBlanks']) ? $ruleSet['customRuleForBlanks'] : false;        if (!$customRuleForBlanks) {            //    Blank cells are always ignored, so return a FALSE            if (($cellValue == '') || ($cellValue === null)) {                return false;            }        }        $returnVal = ($join == AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND);        foreach ($dataSet as $rule) {            if (is_numeric($rule['value'])) {                //    Numeric values are tested using the appropriate operator                switch ($rule['operator']) {                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL:                        $retVal = ($cellValue == $rule['value']);                        break;                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL:                        $retVal = ($cellValue != $rule['value']);                        break;                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN:                        $retVal = ($cellValue > $rule['value']);                        break;                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL:                        $retVal = ($cellValue >= $rule['value']);                        break;                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN:                        $retVal = ($cellValue < $rule['value']);                        break;                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL:                        $retVal = ($cellValue <= $rule['value']);                        break;                }            } elseif ($rule['value'] == '') {                switch ($rule['operator']) {                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL:                        $retVal = (($cellValue == '') || ($cellValue === null));                        break;                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL:                        $retVal = (($cellValue != '') && ($cellValue !== null));                        break;                    default:                        $retVal = true;                        break;                }            } else {                //    String values are always tested for equality, factoring in for wildcards (hence a regexp test)                $retVal = preg_match('/^' . $rule['value'] . '$/i', $cellValue);            }            //    If there are multiple conditions, then we need to test both using the appropriate join operator            switch ($join) {                case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR:                    $returnVal = $returnVal || $retVal;                    //    Break as soon as we have a TRUE match for OR joins,                    //        to avoid unnecessary additional code execution                    if ($returnVal) {                        return $returnVal;                    }                    break;                case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND:                    $returnVal = $returnVal && $retVal;                    break;            }        }        return $returnVal;    }    /**     * Test if cell date value is matches a set of values defined by a set of months.     *     * @param mixed $cellValue     * @param mixed[] $monthSet     *     * @return bool     */    private static function filterTestInPeriodDateSet($cellValue, $monthSet)    {        //    Blank cells are always ignored, so return a FALSE        if (($cellValue == '') || ($cellValue === null)) {            return false;        }        if (is_numeric($cellValue)) {            $dateValue = date('m', Date::excelToTimestamp($cellValue));            if (in_array($dateValue, $monthSet)) {                return true;            }        }        return false;    }    /**     * Search/Replace arrays to convert Excel wildcard syntax to a regexp syntax for preg_matching.     *     * @var array     */    private static $fromReplace = ['\*', '\?', '~~', '~.*', '~.?'];    private static $toReplace = ['.*', '.', '~', '\*', '\?'];    /**     * Convert a dynamic rule daterange to a custom filter range expression for ease of calculation.     *     * @param string $dynamicRuleType     * @param AutoFilter\Column $filterColumn     *     * @return mixed[]     */    private function dynamicFilterDateRange($dynamicRuleType, &$filterColumn)    {        $rDateType = Functions::getReturnDateType();        Functions::setReturnDateType(Functions::RETURNDATE_PHP_NUMERIC);        $val = $maxVal = null;        $ruleValues = [];        $baseDate = DateTime::DATENOW();        //    Calculate start/end dates for the required date range based on current date        switch ($dynamicRuleType) {            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK:                $baseDate = strtotime('-7 days', $baseDate);                break;            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK:                $baseDate = strtotime('-7 days', $baseDate);                break;            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH:                $baseDate = strtotime('-1 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));                break;            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH:                $baseDate = strtotime('+1 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));                break;            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER:                $baseDate = strtotime('-3 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));                break;            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER:                $baseDate = strtotime('+3 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));                break;            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR:                $baseDate = strtotime('-1 year', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));                break;            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR:                $baseDate = strtotime('+1 year', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));                break;        }        switch ($dynamicRuleType) {            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TODAY:            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY:            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW:                $maxVal = (int) Date::PHPtoExcel(strtotime('+1 day', $baseDate));                $val = (int) Date::PHPToExcel($baseDate);                break;            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE:                $maxVal = (int) Date::PHPtoExcel(strtotime('+1 day', $baseDate));                $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1, date('Y', $baseDate)));                break;            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR:            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR:            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR:                $maxVal = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 31, 12, date('Y', $baseDate)));                ++$maxVal;                $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1, date('Y', $baseDate)));                break;            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER:            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER:            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER:                $thisMonth = date('m', $baseDate);                $thisQuarter = floor(--$thisMonth / 3);                $maxVal = (int) Date::PHPtoExcel(gmmktime(0, 0, 0, date('t', $baseDate), (1 + $thisQuarter) * 3, date('Y', $baseDate)));                ++$maxVal;                $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1 + $thisQuarter * 3, date('Y', $baseDate)));                break;            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH:            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH:            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH:                $maxVal = (int) Date::PHPtoExcel(gmmktime(0, 0, 0, date('t', $baseDate), date('m', $baseDate), date('Y', $baseDate)));                ++$maxVal;                $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));                break;            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK:            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK:            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK:                $dayOfWeek = date('w', $baseDate);                $val = (int) Date::PHPToExcel($baseDate) - $dayOfWeek;                $maxVal = $val + 7;                break;        }        switch ($dynamicRuleType) {            //    Adjust Today dates for Yesterday and Tomorrow            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY:                --$maxVal;                --$val;                break;            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW:                ++$maxVal;                ++$val;                break;        }        //    Set the filter column rule attributes ready for writing        $filterColumn->setAttributes(['val' => $val, 'maxVal' => $maxVal]);        //    Set the rules for identifying rows for hide/show        $ruleValues[] = ['operator' => AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, 'value' => $val];        $ruleValues[] = ['operator' => AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN, 'value' => $maxVal];        Functions::setReturnDateType($rDateType);        return ['method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND]];    }    private function calculateTopTenValue($columnID, $startRow, $endRow, $ruleType, $ruleValue)    {        $range = $columnID . $startRow . ':' . $columnID . $endRow;        $dataValues = Functions::flattenArray($this->workSheet->rangeToArray($range, null, true, false));        $dataValues = array_filter($dataValues);        if ($ruleType == AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) {            rsort($dataValues);        } else {            sort($dataValues);        }        return array_pop(array_slice($dataValues, 0, $ruleValue));    }    /**     * Apply the AutoFilter rules to the AutoFilter Range.     *     * @throws PhpSpreadsheetException     *     * @return AutoFilter     */    public function showHideRows()    {        list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($this->range);        //    The heading row should always be visible        $this->workSheet->getRowDimension($rangeStart[1])->setVisible(true);        $columnFilterTests = [];        foreach ($this->columns as $columnID => $filterColumn) {            $rules = $filterColumn->getRules();            switch ($filterColumn->getFilterType()) {                case AutoFilter\Column::AUTOFILTER_FILTERTYPE_FILTER:                    $ruleType = null;                    $ruleValues = [];                    //    Build a list of the filter value selections                    foreach ($rules as $rule) {                        $ruleType = $rule->getRuleType();                        $ruleValues[] = $rule->getValue();                    }                    //    Test if we want to include blanks in our filter criteria                    $blanks = false;                    $ruleDataSet = array_filter($ruleValues);                    if (count($ruleValues) != count($ruleDataSet)) {                        $blanks = true;                    }                    if ($ruleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_FILTER) {                        //    Filter on absolute values                        $columnFilterTests[$columnID] = [                            'method' => 'filterTestInSimpleDataSet',                            'arguments' => ['filterValues' => $ruleDataSet, 'blanks' => $blanks],                        ];                    } else {                        //    Filter on date group values                        $arguments = [                            'date' => [],                            'time' => [],                            'dateTime' => [],                        ];                        foreach ($ruleDataSet as $ruleValue) {                            $date = $time = '';                            if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR])) &&                                ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR] !== '')) {                                $date .= sprintf('%04d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR]);                            }                            if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH])) &&                                ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH] != '')) {                                $date .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH]);                            }                            if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY])) &&                                ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY] !== '')) {                                $date .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY]);                            }                            if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR])) &&                                ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR] !== '')) {                                $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR]);                            }                            if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE])) &&                                ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE] !== '')) {                                $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE]);                            }                            if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND])) &&                                ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND] !== '')) {                                $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND]);                            }                            $dateTime = $date . $time;                            $arguments['date'][] = $date;                            $arguments['time'][] = $time;                            $arguments['dateTime'][] = $dateTime;                        }                        //    Remove empty elements                        $arguments['date'] = array_filter($arguments['date']);                        $arguments['time'] = array_filter($arguments['time']);                        $arguments['dateTime'] = array_filter($arguments['dateTime']);                        $columnFilterTests[$columnID] = [                            'method' => 'filterTestInDateGroupSet',                            'arguments' => ['filterValues' => $arguments, 'blanks' => $blanks],                        ];                    }                    break;                case AutoFilter\Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER:                    $customRuleForBlanks = false;                    $ruleValues = [];                    //    Build a list of the filter value selections                    foreach ($rules as $rule) {                        $ruleValue = $rule->getValue();                        if (!is_numeric($ruleValue)) {                            //    Convert to a regexp allowing for regexp reserved characters, wildcards and escaped wildcards                            $ruleValue = preg_quote($ruleValue);                            $ruleValue = str_replace(self::$fromReplace, self::$toReplace, $ruleValue);                            if (trim($ruleValue) == '') {                                $customRuleForBlanks = true;                                $ruleValue = trim($ruleValue);                            }                        }                        $ruleValues[] = ['operator' => $rule->getOperator(), 'value' => $ruleValue];                    }                    $join = $filterColumn->getJoin();                    $columnFilterTests[$columnID] = [                        'method' => 'filterTestInCustomDataSet',                        'arguments' => ['filterRules' => $ruleValues, 'join' => $join, 'customRuleForBlanks' => $customRuleForBlanks],                    ];                    break;                case AutoFilter\Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER:                    $ruleValues = [];                    foreach ($rules as $rule) {                        //    We should only ever have one Dynamic Filter Rule anyway                        $dynamicRuleType = $rule->getGrouping();                        if (($dynamicRuleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) ||                            ($dynamicRuleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE)) {                            //    Number (Average) based                            //    Calculate the average                            $averageFormula = '=AVERAGE(' . $columnID . ($rangeStart[1] + 1) . ':' . $columnID . $rangeEnd[1] . ')';                            $average = Calculation::getInstance()->calculateFormula($averageFormula, null, $this->workSheet->getCell('A1'));                            //    Set above/below rule based on greaterThan or LessTan                            $operator = ($dynamicRuleType === AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE)                                ? AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN                                : AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN;                            $ruleValues[] = [                                'operator' => $operator,                                'value' => $average,                            ];                            $columnFilterTests[$columnID] = [                                'method' => 'filterTestInCustomDataSet',                                'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR],                            ];                        } else {                            //    Date based                            if ($dynamicRuleType[0] == 'M' || $dynamicRuleType[0] == 'Q') {                                //    Month or Quarter                                sscanf($dynamicRuleType, '%[A-Z]%d', $periodType, $period);                                if ($periodType == 'M') {                                    $ruleValues = [$period];                                } else {                                    --$period;                                    $periodEnd = (1 + $period) * 3;                                    $periodStart = 1 + $period * 3;                                    $ruleValues = range($periodStart, $periodEnd);                                }                                $columnFilterTests[$columnID] = [                                    'method' => 'filterTestInPeriodDateSet',                                    'arguments' => $ruleValues,                                ];                                $filterColumn->setAttributes([]);                            } else {                                //    Date Range                                $columnFilterTests[$columnID] = $this->dynamicFilterDateRange($dynamicRuleType, $filterColumn);                                break;                            }                        }                    }                    break;                case AutoFilter\Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER:                    $ruleValues = [];                    $dataRowCount = $rangeEnd[1] - $rangeStart[1];                    foreach ($rules as $rule) {                        //    We should only ever have one Dynamic Filter Rule anyway                        $toptenRuleType = $rule->getGrouping();                        $ruleValue = $rule->getValue();                        $ruleOperator = $rule->getOperator();                    }                    if ($ruleOperator === AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) {                        $ruleValue = floor($ruleValue * ($dataRowCount / 100));                    }                    if ($ruleValue < 1) {                        $ruleValue = 1;                    }                    if ($ruleValue > 500) {                        $ruleValue = 500;                    }                    $maxVal = $this->calculateTopTenValue($columnID, $rangeStart[1] + 1, $rangeEnd[1], $toptenRuleType, $ruleValue);                    $operator = ($toptenRuleType == AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP)                        ? AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL                        : AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL;                    $ruleValues[] = ['operator' => $operator, 'value' => $maxVal];                    $columnFilterTests[$columnID] = [                        'method' => 'filterTestInCustomDataSet',                        'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR],                    ];                    $filterColumn->setAttributes(['maxVal' => $maxVal]);                    break;            }        }        //    Execute the column tests for each row in the autoFilter range to determine show/hide,        for ($row = $rangeStart[1] + 1; $row <= $rangeEnd[1]; ++$row) {            $result = true;            foreach ($columnFilterTests as $columnID => $columnFilterTest) {                $cellValue = $this->workSheet->getCell($columnID . $row)->getCalculatedValue();                //    Execute the filter test                $result = $result &&                    call_user_func_array(                        [self::class, $columnFilterTest['method']],                        [$cellValue, $columnFilterTest['arguments']]                    );                //    If filter test has resulted in FALSE, exit the loop straightaway rather than running any more tests                if (!$result) {                    break;                }            }            //    Set show/hide for the row based on the result of the autoFilter result            $this->workSheet->getRowDimension($row)->setVisible($result);        }        return $this;    }    /**     * Implement PHP __clone to create a deep clone, not just a shallow copy.     */    public function __clone()    {        $vars = get_object_vars($this);        foreach ($vars as $key => $value) {            if (is_object($value)) {                if ($key == 'workSheet') {                    //    Detach from worksheet                    $this->{$key} = null;                } else {                    $this->{$key} = clone $value;                }            } elseif ((is_array($value)) && ($key == 'columns')) {                //    The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\AutoFilter objects                $this->{$key} = [];                foreach ($value as $k => $v) {                    $this->{$key}[$k] = clone $v;                    // attach the new cloned Column to this new cloned Autofilter object                    $this->{$key}[$k]->setParent($this);                }            } else {                $this->{$key} = $value;            }        }    }    /**     * toString method replicates previous behavior by returning the range if object is     * referenced as a property of its parent.     */    public function __toString()    {        return (string) $this->range;    }}
 |