Skip to content

Commit cfc8b8c

Browse files
authored
Merge branch 'master' into issue797
2 parents 89b30eb + e721975 commit cfc8b8c

File tree

12 files changed

+472
-196
lines changed

12 files changed

+472
-196
lines changed

CHANGELOG.md

+14-3
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com)
66
and this project adheres to [Semantic Versioning](https://semver.org).
77

8-
## TBD - 3.9.0
8+
## TBD - 4.0.0
99

1010
### Added
1111

12-
- Methods to get style for row or column. [PR #4317](https://github.com/PHPOffice/PhpSpreadsheet/pull/4317)
13-
- Method for duplicating worksheet in spreadsheet. [PR #4315](https://github.com/PHPOffice/PhpSpreadsheet/pull/4315)
12+
- Pdf Charts and Drawings. [Discussion #4129](https://github.com/PHPOffice/PhpSpreadsheet/discussions/4129) [Discussion #4168](https://github.com/PHPOffice/PhpSpreadsheet/discussions/4168) [PR #4327](https://github.com/PHPOffice/PhpSpreadsheet/pull/4327)
1413

1514
### Changed
1615

@@ -26,6 +25,18 @@ and this project adheres to [Semantic Versioning](https://semver.org).
2625

2726
### Fixed
2827

28+
- Xls writer Parser Mishandling True/False Argument. [Issue #4331](https://github.com/PHPOffice/PhpSpreadsheet/issues/4331) [PR #4333](https://github.com/PHPOffice/PhpSpreadsheet/pull/4333)
29+
30+
## 2025-01-26 - 3.9.0
31+
32+
### Added
33+
34+
- Methods to get style for row or column. [PR #4317](https://github.com/PHPOffice/PhpSpreadsheet/pull/4317)
35+
- Method for duplicating worksheet in spreadsheet. [PR #4315](https://github.com/PHPOffice/PhpSpreadsheet/pull/4315)
36+
37+
### Fixed
38+
39+
- Security patch for control characters in protocol.
2940
- Ods Reader Sheet Names with Period. [Issue #4311](https://github.com/PHPOffice/PhpSpreadsheet/issues/4311) [PR #4313](https://github.com/PHPOffice/PhpSpreadsheet/pull/4313)
3041
- Mpdf and Tcpdf Hidden Columns and Merged Cells. [Issue #4319](https://github.com/PHPOffice/PhpSpreadsheet/issues/4319) [PR #4320](https://github.com/PHPOffice/PhpSpreadsheet/pull/4320)
3142
- Html Writer Allow mailto. [Issue #4316](https://github.com/PHPOffice/PhpSpreadsheet/issues/4316) [PR #4322](https://github.com/PHPOffice/PhpSpreadsheet/pull/4322)

composer.lock

+187-187
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

samples/Chart/32_Chart_read_write_PDF.php

+3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
$reader->setIncludeCharts(true);
3838
$spreadsheet = $reader->load($inputFileName);
3939

40+
$helper->log('Merge chart cells (needed only for Pdf)');
41+
$spreadsheet->mergeChartCellsForPdf();
42+
4043
$helper->log('Iterate worksheets looking at the charts');
4144
foreach ($spreadsheet->getWorksheetIterator() as $worksheet) {
4245
$sheetName = $worksheet->getTitle();

samples/Pdf/21f_Drawing_mpdf.php

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
4+
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
5+
use PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf;
6+
7+
require __DIR__ . '/../Header.php';
8+
require_once __DIR__ . '/Mpdf2.php';
9+
10+
$spreadsheet = new Spreadsheet();
11+
$sheet = $spreadsheet->getActiveSheet();
12+
13+
$sheet->getCell('A1')->setValue('A1');
14+
$sheet->getCell('B1')->setValue('B');
15+
$sheet->getCell('C1')->setValue('C');
16+
$sheet->getCell('D1')->setValue('D');
17+
$sheet->getCell('E1')->setValue('E');
18+
$sheet->getCell('F1')->setValue('F');
19+
$sheet->getCell('G1')->setValue('G');
20+
$sheet->getCell('A2')->setValue('A2');
21+
$sheet->getCell('A3')->setValue('A3');
22+
$sheet->getCell('A4')->setValue('A4');
23+
$sheet->getCell('A5')->setValue('A5');
24+
$sheet->getCell('A6')->setValue('A6');
25+
$sheet->getCell('A7')->setValue('A7');
26+
$sheet->getCell('A8')->setValue('A8');
27+
28+
$helper->log('Add drawing to worksheet');
29+
$drawing = new Drawing();
30+
$drawing->setName('Blue Square');
31+
$path = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'images/blue_square.png';
32+
$drawing->setPath($path);
33+
$drawing->setResizeProportional(false);
34+
$drawing->setWidth(320);
35+
$drawing->setCoordinates('B2');
36+
$drawing->setCoordinates2('G6');
37+
$drawing->setWorksheet($sheet, true);
38+
39+
$helper->log('Merge drawing cells for Pdf');
40+
$spreadsheet->mergeDrawingCellsForPdf();
41+
42+
$helper->log('Write to Mpdf');
43+
$writer = new Mpdf($spreadsheet);
44+
$filename = $helper->getFileName(__FILE__, 'pdf');
45+
$writer->save($filename);
46+
$helper->log("Saved $filename");
47+
if (PHP_SAPI !== 'cli') {
48+
echo '<a href="/download.php?type=pdf&name=' . basename($filename) . '">Download ' . basename($filename) . '</a><br />';
49+
}
50+
$spreadsheet->disconnectWorksheets();

src/PhpSpreadsheet/Spreadsheet.php

+48
Original file line numberDiff line numberDiff line change
@@ -1636,4 +1636,52 @@ public function setValueBinder(?IValueBinder $valueBinder): self
16361636

16371637
return $this;
16381638
}
1639+
1640+
/**
1641+
* All the PDF writers treat charts as if they occupy a single cell.
1642+
* This will be better most of the time.
1643+
* It is not needed for any other output type.
1644+
* It changes the contents of the spreadsheet, so you might
1645+
* be better off cloning the spreadsheet and then using
1646+
* this method on, and then writing, the clone.
1647+
*/
1648+
public function mergeChartCellsForPdf(): void
1649+
{
1650+
foreach ($this->workSheetCollection as $worksheet) {
1651+
foreach ($worksheet->getChartCollection() as $chart) {
1652+
$br = $chart->getBottomRightCell();
1653+
$tl = $chart->getTopLeftCell();
1654+
if ($br !== '' && $br !== $tl) {
1655+
if (!$worksheet->cellExists($br)) {
1656+
$worksheet->getCell($br)->setValue(' ');
1657+
}
1658+
$worksheet->mergeCells("$tl:$br");
1659+
}
1660+
}
1661+
}
1662+
}
1663+
1664+
/**
1665+
* All the PDF writers do better with drawings than charts.
1666+
* This will be better some of the time.
1667+
* It is not needed for any other output type.
1668+
* It changes the contents of the spreadsheet, so you might
1669+
* be better off cloning the spreadsheet and then using
1670+
* this method on, and then writing, the clone.
1671+
*/
1672+
public function mergeDrawingCellsForPdf(): void
1673+
{
1674+
foreach ($this->workSheetCollection as $worksheet) {
1675+
foreach ($worksheet->getDrawingCollection() as $drawing) {
1676+
$br = $drawing->getCoordinates2();
1677+
$tl = $drawing->getCoordinates();
1678+
if ($br !== '' && $br !== $tl) {
1679+
if (!$worksheet->cellExists($br)) {
1680+
$worksheet->getCell($br)->setValue(' ');
1681+
}
1682+
$worksheet->mergeCells("$tl:$br");
1683+
}
1684+
}
1685+
}
1686+
}
16391687
}

src/PhpSpreadsheet/Worksheet/Drawing.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public function setPath(string $path, bool $verifyFile = true, ?ZipArchive $zip
103103

104104
$this->path = '';
105105
// Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979
106-
if (filter_var($path, FILTER_VALIDATE_URL)) {
106+
if (filter_var($path, FILTER_VALIDATE_URL) || (preg_match('/^([\\w\\s\\x00-\\x1f]+):/u', $path) && !preg_match('/^([\\w]+):/u', $path))) {
107107
if (!preg_match('/^(http|https|file|ftp|s3):/', $path)) {
108108
throw new PhpSpreadsheetException('Invalid protocol for linked drawing');
109109
}

src/PhpSpreadsheet/Writer/Html.php

+16-1
Original file line numberDiff line numberDiff line change
@@ -1601,9 +1601,10 @@ private function generateRow(Worksheet $worksheet, array $values, int $row, stri
16011601
$url = $worksheet->getHyperlink($coordinate)->getUrl();
16021602
$urlDecode1 = html_entity_decode($url, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
16031603
$urlTrim = Preg::replace('/^\\s+/u', '', $urlDecode1);
1604-
$parseScheme = Preg::isMatch('/^([\\w\\s]+):/u', strtolower($urlTrim), $matches);
1604+
$parseScheme = Preg::isMatch('/^([\\w\\s\\x00-\\x1f]+):/u', strtolower($urlTrim), $matches);
16051605
if ($parseScheme && !in_array($matches[1], ['http', 'https', 'file', 'ftp', 'mailto', 's3'], true)) {
16061606
$cellData = htmlspecialchars($url, Settings::htmlEntityFlags());
1607+
$cellData = self::replaceControlChars($cellData);
16071608
} else {
16081609
$tooltip = $worksheet->getHyperlink($coordinate)->getTooltip();
16091610
$tooltipOut = empty($tooltip) ? '' : (' title="' . htmlspecialchars($tooltip) . '"');
@@ -1658,6 +1659,20 @@ private function generateRow(Worksheet $worksheet, array $values, int $row, stri
16581659
return $html;
16591660
}
16601661

1662+
private static function replaceNonAscii(array $matches): string
1663+
{
1664+
return '&#' . mb_ord($matches[0], 'UTF-8') . ';';
1665+
}
1666+
1667+
private static function replaceControlChars(string $convert): string
1668+
{
1669+
return (string) preg_replace_callback(
1670+
'/[\\x00-\\x1f]/',
1671+
[self::class, 'replaceNonAscii'],
1672+
$convert
1673+
);
1674+
}
1675+
16611676
/**
16621677
* Takes array where of CSS properties / values and converts to CSS string.
16631678
*/

src/PhpSpreadsheet/Writer/Xls/Parser.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -1624,7 +1624,9 @@ public function toReversePolish(array $tree = []): string
16241624
}
16251625

16261626
// add its left subtree and return.
1627-
return $left_tree . $this->convertFunction($tree['value'], $tree['right']);
1627+
if ($left_tree !== '' || $tree['right'] !== '') {
1628+
return $left_tree . $this->convertFunction($tree['value'], $tree['right'] ?: 0);
1629+
}
16281630
}
16291631
$converted_tree = $this->convert($tree['value']);
16301632

tests/PhpSpreadsheetTests/Reader/Html/HtmlImage2Test.php

+16-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
88
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
9+
use PHPUnit\Framework\Attributes\DataProvider;
910
use PHPUnit\Framework\TestCase;
1011

1112
class HtmlImage2Test extends TestCase
@@ -49,11 +50,11 @@ public function testCantInsertImageNotFound(): void
4950
self::assertCount(0, $drawingCollection);
5051
}
5152

52-
public function testCannotInsertImageBadProtocol(): void
53+
#[DataProvider('providerBadProtocol')]
54+
public function testCannotInsertImageBadProtocol(string $imagePath): void
5355
{
5456
$this->expectException(SpreadsheetException::class);
5557
$this->expectExceptionMessage('Invalid protocol for linked drawing');
56-
$imagePath = 'httpx://phpspreadsheet.readthedocs.io/en/latest/topics/images/01-03-filter-icon-1.png';
5758
$html = '<table>
5859
<tr>
5960
<td><img src="' . $imagePath . '" alt="test image voilà"></td>
@@ -62,4 +63,17 @@ public function testCannotInsertImageBadProtocol(): void
6263
$filename = HtmlHelper::createHtml($html);
6364
HtmlHelper::loadHtmlIntoSpreadsheet($filename, true);
6465
}
66+
67+
public static function providerBadProtocol(): array
68+
{
69+
return [
70+
'unknown protocol' => ['httpx://example.com/image.png'],
71+
'embedded whitespace' => ['ht tp://example.com/image.png'],
72+
'control character' => ["\x14http://example.com/image.png"],
73+
'mailto' => ['mailto:[email protected]'],
74+
'mailto whitespace' => ['mail to:[email protected]'],
75+
'phar' => ['phar://example.com/image.phar'],
76+
'phar control' => ["\x14phar://example.com/image.phar"],
77+
];
78+
}
6579
}

tests/PhpSpreadsheetTests/Writer/Html/BadHyperlinkTest.php

+13-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace PhpOffice\PhpSpreadsheetTests\Writer\Html;
66

77
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
8+
use PhpOffice\PhpSpreadsheet\Reader\Xml as XmlReader;
89
use PhpOffice\PhpSpreadsheet\Writer\Html as HtmlWriter;
910
use PHPUnit\Framework\TestCase;
1011

@@ -17,7 +18,18 @@ public function testBadHyperlink(): void
1718
$spreadsheet = $reader->load($infile);
1819
$writer = new HtmlWriter($spreadsheet);
1920
$html = $writer->generateHtmlAll();
20-
self::assertStringContainsString("<td class=\"column0 style1 f\">jav\tascript:alert()</td>", $html);
21+
self::assertStringContainsString('<td class="column0 style1 f">jav&#9;ascript:alert()</td>', $html);
22+
$spreadsheet->disconnectWorksheets();
23+
}
24+
25+
public function testControlCharacter(): void
26+
{
27+
$reader = new XmlReader();
28+
$infile = 'tests/data/Reader/Xml/sec-w24f.dontuse';
29+
$spreadsheet = $reader->load($infile);
30+
$writer = new HtmlWriter($spreadsheet);
31+
$html = $writer->generateHtmlAll();
32+
self::assertStringContainsString('<td class="column0 style0 f">&#20;j&#13;avascript:alert(1)</td>', $html);
2133
$spreadsheet->disconnectWorksheets();
2234
}
2335
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xls;
6+
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8+
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
9+
10+
class Issue4331Test extends AbstractFunctional
11+
{
12+
public function testIssue4331(): void
13+
{
14+
$spreadsheet = new Spreadsheet();
15+
$sheet = $spreadsheet->getActiveSheet();
16+
$c3 = '=VLOOKUP(B3,$B$10:$C$13,2,FALSE)';
17+
$d3 = '=VLOOKUP("intermediate",$B$10:$C$13,2,TRUE)';
18+
$c4 = '=VLOOKUP(B3,$B$10:$C$13,2,FALSE())';
19+
$d4 = '=VLOOKUP("intermediate",$B$10:$C$13,2,TRUE())';
20+
$sheet->fromArray(
21+
[
22+
['level', 'result'],
23+
['medium', $c3, $d3],
24+
[null, $c4, $d4],
25+
],
26+
null,
27+
'B2',
28+
true
29+
);
30+
$sheet->fromArray(
31+
[
32+
['high', 6],
33+
['low', 2],
34+
['medium', 4],
35+
['none', 0],
36+
],
37+
null,
38+
'B10',
39+
true
40+
);
41+
42+
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xls');
43+
$spreadsheet->disconnectWorksheets();
44+
45+
$worksheet = $reloadedSpreadsheet->getActiveSheet();
46+
self::assertSame($c3, $worksheet->getCell('C3')->getValue());
47+
self::assertSame(4, $worksheet->getCell('C3')->getCalculatedValue());
48+
self::assertSame($d3, $worksheet->getCell('D3')->getValue());
49+
self::assertSame(6, $worksheet->getCell('D3')->getCalculatedValue());
50+
self::assertSame($c4, $worksheet->getCell('C4')->getValue());
51+
self::assertSame(4, $worksheet->getCell('C4')->getCalculatedValue());
52+
self::assertSame($d4, $worksheet->getCell('D4')->getValue());
53+
self::assertSame(6, $worksheet->getCell('D4')->getCalculatedValue());
54+
$reloadedSpreadsheet->disconnectWorksheets();
55+
}
56+
}
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?xml version="1.0"?>
2+
<?mso-application progid="Excel.Sheet"?>
3+
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
4+
xmlns:o="urn:schemas-microsoft-com:office:office"
5+
xmlns:x="urn:schemas-microsoft-com:office:excel"
6+
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
7+
xmlns:html="http://www.w3.org/TR/REC-html40">
8+
<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">
9+
<Author>author</Author>
10+
<LastAuthor>author</LastAuthor>
11+
<Created>2015-06-05T18:19:34Z</Created>
12+
<LastSaved>2024-12-25T10:16:07Z</LastSaved>
13+
<Version>16.00</Version>
14+
</DocumentProperties>
15+
<OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office">
16+
<AllowPNG/>
17+
</OfficeDocumentSettings>
18+
<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
19+
<WindowHeight>11020</WindowHeight>
20+
<WindowWidth>19420</WindowWidth>
21+
<WindowTopX>32767</WindowTopX>
22+
<WindowTopY>32767</WindowTopY>
23+
<ProtectStructure>False</ProtectStructure>
24+
<ProtectWindows>False</ProtectWindows>
25+
</ExcelWorkbook>
26+
<Styles>
27+
<Style ss:ID="Default" ss:Name="Normal">
28+
<Alignment ss:Vertical="Bottom"/>
29+
<Borders/>
30+
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="11" ss:Color="#000000"/>
31+
<Interior/>
32+
<NumberFormat/>
33+
<Protection/>
34+
</Style>
35+
<Style ss:ID="s16">
36+
<NumberFormat ss:Format="General Date"/>
37+
</Style>
38+
</Styles>
39+
<Worksheet ss:Name="Лист1">
40+
<Table ss:ExpandedColumnCount="2" ss:ExpandedRowCount="6" x:FullColumns="1"
41+
x:FullRows="1" ss:DefaultRowHeight="14.5">
42+
<Column ss:AutoFitWidth="0" ss:Width="194"/>
43+
<Row>
44+
<Cell ss:Formula="=HYPERLINK (CHAR(20) &amp; &quot;j&quot; &amp; CHAR(13) &amp; &quot;avascript:alert(1)&quot;)"><Data ss:Type="String"></Data></Cell>
45+
</Row>
46+
</Table>
47+
<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">
48+
<PageSetup>
49+
<Header x:Margin="0.3"/>
50+
<Footer x:Margin="0.3"/>
51+
<PageMargins x:Bottom="0.75" x:Left="0.7" x:Right="0.7" x:Top="0.75"/>
52+
</PageSetup>
53+
<Selected/>
54+
<TopRowVisible>1</TopRowVisible>
55+
<Panes>
56+
<Pane>
57+
<Number>3</Number>
58+
<ActiveRow>6</ActiveRow>
59+
</Pane>
60+
</Panes>
61+
<ProtectObjects>False</ProtectObjects>
62+
<ProtectScenarios>False</ProtectScenarios>
63+
</WorksheetOptions>
64+
</Worksheet>
65+
</Workbook>

0 commit comments

Comments
 (0)