Skip to content

Commit 98ab11c

Browse files
committed
Table Filter Buttons
Fix PHPOffice#3988. Excel allows a table to have some colums with filter buttons exposed and some with filter buttons hidden. However you cannot do this in "native" Excel - VBA is required for this feature. PhpSpreadsheet is supposed to be able to handle this, but Xlsx/Writer/Table had a bug. The `filterColumn` tags in the xml should be children of the `autoFilter` tag, but were generated as siblings. Moving one statement fixes that problem. Fixing that exposed another problem - autoFilter `showHideRows` was not properly recognizing that a filter could exist without any rules, which is what happens when a table filter button is hidden. Another simple change fixes that problem. The parent issue correctly points out the problem for Column D in samples/Table/01_Table. However, that sample also has a problem with Column A - unlike columns B and C, there is a filter appled to column A, so its dropdown button should appear different than those in B and C. That problem is also corrected with the fixes above. I also added some formal tests based on that sample.
1 parent 35030fa commit 98ab11c

File tree

3 files changed

+105
-2
lines changed

3 files changed

+105
-2
lines changed

src/PhpSpreadsheet/Worksheet/AutoFilter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,7 @@ public function showHideRows(): static
810810
'method' => 'filterTestInSimpleDataSet',
811811
'arguments' => ['filterValues' => $ruleDataSet, 'blanks' => $blanks],
812812
];
813-
} else {
813+
} elseif ($ruleType !== null) {
814814
// Filter on date group values
815815
$arguments = [
816816
'date' => [],

src/PhpSpreadsheet/Writer/Xlsx/Table.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ public function writeTable(WorksheetTable $table, int $tableRef): string
5050
if ($table->getShowHeaderRow() && $table->getAllowFilter() === true) {
5151
$objWriter->startElement('autoFilter');
5252
$objWriter->writeAttribute('ref', $range);
53-
$objWriter->endElement();
5453
foreach (range($rangeStart[0], $rangeEnd[0]) as $offset => $columnIndex) {
5554
$column = $table->getColumnByOffset($offset);
5655

@@ -64,6 +63,7 @@ public function writeTable(WorksheetTable $table, int $tableRef): string
6463
AutoFilter::writeAutoFilterColumn($objWriter, $column, $offset);
6564
}
6665
}
66+
$objWriter->endElement(); // autoFilter
6767
}
6868

6969
// Table Columns
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
6+
7+
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
8+
use PhpOffice\PhpSpreadsheet\Shared\File;
9+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
10+
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
11+
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
12+
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
13+
use PHPUnit\Framework\TestCase;
14+
15+
class Issue3988Test extends TestCase
16+
{
17+
public function testIssue3988(): void
18+
{
19+
// code liberally borrowed from samples/Table/01_Table
20+
$spreadsheet = new Spreadsheet();
21+
$spreadsheet->setActiveSheetIndex(0);
22+
$spreadsheet->getActiveSheet()->setCellValue('A1', 'Year')
23+
->setCellValue('B1', 'Quarter')
24+
->setCellValue('C1', 'Country')
25+
->setCellValue('D1', 'Sales');
26+
27+
$dataArray = [
28+
['2010', 'Q1', 'United States', 790],
29+
['2010', 'Q2', 'United States', 730],
30+
['2010', 'Q3', 'United States', 860],
31+
['2010', 'Q4', 'United States', 850],
32+
['2011', 'Q1', 'United States', 800],
33+
['2011', 'Q2', 'United States', 700],
34+
['2011', 'Q3', 'United States', 900],
35+
['2011', 'Q4', 'United States', 950],
36+
['2010', 'Q1', 'Belgium', 380],
37+
['2010', 'Q2', 'Belgium', 390],
38+
['2010', 'Q3', 'Belgium', 420],
39+
['2010', 'Q4', 'Belgium', 460],
40+
['2011', 'Q1', 'Belgium', 400],
41+
['2011', 'Q2', 'Belgium', 350],
42+
['2011', 'Q3', 'Belgium', 450],
43+
['2011', 'Q4', 'Belgium', 500],
44+
['2010', 'Q1', 'UK', 690],
45+
['2010', 'Q2', 'UK', 610],
46+
['2010', 'Q3', 'UK', 620],
47+
['2010', 'Q4', 'UK', 600],
48+
['2011', 'Q1', 'UK', 720],
49+
['2011', 'Q2', 'UK', 650],
50+
['2011', 'Q3', 'UK', 580],
51+
['2011', 'Q4', 'UK', 510],
52+
['2010', 'Q1', 'France', 510],
53+
['2010', 'Q2', 'France', 490],
54+
['2010', 'Q3', 'France', 460],
55+
['2010', 'Q4', 'France', 590],
56+
['2011', 'Q1', 'France', 620],
57+
['2011', 'Q2', 'France', 650],
58+
['2011', 'Q3', 'France', 415],
59+
['2011', 'Q4', 'France', 570],
60+
];
61+
$spreadsheet->getActiveSheet()->fromArray($dataArray, null, 'A2');
62+
63+
$table = new Table('A1:D33', 'Sales_Data');
64+
65+
// Create Columns
66+
$table->getColumn('D')->setShowFilterButton(false);
67+
$table->getAutoFilter()->getColumn('A')
68+
->setFilterType(AutoFilter\Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER)
69+
->createRule()
70+
->setRule(AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, 2011)
71+
->setRuleType(AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER);
72+
73+
$spreadsheet->getActiveSheet()->addTable($table);
74+
75+
$outfile = File::temporaryFilename();
76+
$writer = new XlsxWriter($spreadsheet);
77+
$writer->save($outfile);
78+
$spreadsheet->disconnectWorksheets();
79+
80+
// Make sure Reader handles row visibility properly.
81+
$reader = new XlsxReader();
82+
$spreadsheet2 = $reader->load($outfile);
83+
self::assertFalse($spreadsheet2->getActiveSheet()->getRowDimension(5)->getVisible());
84+
self::assertTrue($spreadsheet2->getActiveSheet()->getRowDimension(6)->getVisible());
85+
$spreadsheet2->disconnectWorksheets();
86+
87+
// Make sure filterColumn tags are children of autoFilter.
88+
$file = 'zip://';
89+
$file .= $outfile;
90+
$file .= '#xl/tables/table1.xml';
91+
$data = file_get_contents($file);
92+
unlink($outfile);
93+
$expected = '<autoFilter ref="A1:D33">'
94+
. '<filterColumn colId="0">'
95+
. '<customFilters>'
96+
. '<customFilter operator="greaterThanOrEqual" val="2011"/>'
97+
. '</customFilters>'
98+
. '</filterColumn>'
99+
. '<filterColumn colId="3" hiddenButton="1"/>'
100+
. '</autoFilter>';
101+
self::assertStringContainsString($expected, $data);
102+
}
103+
}

0 commit comments

Comments
 (0)