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

Lines changed: 14 additions & 3 deletions
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

Lines changed: 187 additions & 187 deletions
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 50 additions & 0 deletions
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

Lines changed: 48 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 16 additions & 1 deletion
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

Lines changed: 3 additions & 1 deletion
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

Lines changed: 16 additions & 2 deletions
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:xyz@example.com'],
74+
'mailto whitespace' => ['mail to:xyz@example.com'],
75+
'phar' => ['phar://example.com/image.phar'],
76+
'phar control' => ["\x14phar://example.com/image.phar"],
77+
];
78+
}
6579
}

tests/PhpSpreadsheetTests/Writer/Html/BadHyperlinkTest.php

Lines changed: 13 additions & 1 deletion
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
}

0 commit comments

Comments
 (0)