123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872 |
- <?php
- namespace 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;
- }
- }
|