From f9ce80473fb6116e84fd8ae9b2460a0469b7e5f2 Mon Sep 17 00:00:00 2001 From: Matthew Setter Date: Wed, 7 Aug 2024 17:08:23 +1000 Subject: [PATCH 01/19] Extend the Options section of the PDF Writer documentation (#2642) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Extend the Options section of the PDF Writer documentation When I used PHPWord to write a PDF document, recently, I followed the docs only to find that I also had to specify a PDF renderer — otherwise the operation failed. After a bit of research to figure out what to do to specify one, I thought it only right to document what I learned in the official documentation. * Update the docs changelog detailing the PDF Writer additions This change updates the changelog to include the enhancement made to the PDF Writer docs that show how to specify a PDF renderer when working with the PDF writer. * Fixed docs --------- Co-authored-by: Progi1984 --- docs/changes/2.x/2.0.0.md | 1 + docs/usage/writers.md | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index c5fbaf4b52..18f9267f39 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -5,6 +5,7 @@ ## Enhancements - IOFactory : Added extractVariables method to extract variables from a document [@sibalonat](https://github.com/sibalonat) in [#2515](https://github.com/PHPOffice/PHPWord/pull/2515) +- PDF Writer : Documented how to specify a PDF renderer, when working with the PDF writer, as well as the three available choices by [@settermjd](https://github.com/settermjd) in [#2642](https://github.com/PHPOffice/PHPWord/pull/2642) ### Bug fixes diff --git a/docs/usage/writers.md b/docs/usage/writers.md index 81fb2b99b4..f561345a95 100644 --- a/docs/usage/writers.md +++ b/docs/usage/writers.md @@ -87,6 +87,29 @@ $writer = IOFactory::createWriter($oPhpWord, 'PDF'); $writer->save(__DIR__ . '/sample.pdf'); ``` +#### Specify the PDF Renderer + +Before PHPWord can write a PDF, you **must** specify the renderer to use and the path to it. +Currently, three renderers are supported: + +- [DomPDF](https://github.com/dompdf/dompdf) +- [MPDF](https://mpdf.github.io/) +- [TCPDF](https://tcpdf.org/) + +To specify the renderer you use two static `Settings` functions: + +- `setPdfRendererName`: This sets the name of the renderer library to use. + Provide one of [`Settings`' three `PDF_` constants](https://github.com/PHPOffice/PHPWord/blob/master/src/PhpWord/Settings.php#L39-L41) to the function call. +- `setPdfRendererPath`: This sets the path to the renderer library. + This directory is the renderer's package directory within Composer's _vendor_ directory. + +In the code below, you can see an example of setting MPDF as the desired PDF renderer. + +```php +Settings::setPdfRendererName(Settings::PDF_RENDERER_MPDF); +Settings::setPdfRendererPath(__DIR__ . '/../vendor/mpdf/mpdf'); +``` + ## RTF The name of the writer is `RTF`. From 8e441e8ae00b76b32dd39aa427c0a89b26381cf7 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 7 Aug 2024 04:21:28 -0700 Subject: [PATCH 02/19] Php7 Problem with TemplateProcessor Destructor (#2554) Fix #2548. A particularly perplexing problem accidentally introduced by PR #2475. Problem does not arise for Php8, and does not arise for Php7 unit tests. But, running *not* under Phpunit auspices with Php7 can cause a warning message at destructor time if the `save` function has been used. A very artificial test is introduced to test this situation. --- docs/changes/2.x/2.0.0.md | 1 + src/PhpWord/Shared/ZipArchive.php | 10 +++++++--- tests/PhpWordTests/TemplateProcessorTest.php | 10 ++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 18f9267f39..cdd6b7b49d 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -11,6 +11,7 @@ - MsDoc Reader : Correct Font Size Calculation by [@oleibman](https://github.com/oleibman) fixing [#2526](https://github.com/PHPOffice/PHPWord/issues/2526) in [#2531](https://github.com/PHPOffice/PHPWord/pull/2531) - TemplateProcessor Persist File After Destruct [@oleibman](https://github.com/oleibman) fixing [#2539](https://github.com/PHPOffice/PHPWord/issues/2539) in [#2545](https://github.com/PHPOffice/PHPWord/pull/2545) +- TemplateProcessor Destructor Problem with Php7 [@oleibman](https://github.com/oleibman) fixing [#2548](https://github.com/PHPOffice/PHPWord/issues/2548) in [#2554](https://github.com/PHPOffice/PHPWord/pull/2554) - bug: TemplateProcessor fix multiline values [@gimler](https://github.com/gimler) fixing [#268](https://github.com/PHPOffice/PHPWord/issues/268), [#2323](https://github.com/PHPOffice/PHPWord/issues/2323) and [#2486](https://github.com/PHPOffice/PHPWord/issues/2486) in [#2522](https://github.com/PHPOffice/PHPWord/pull/2522) - 32-bit Problem in PasswordEncoder [@oleibman](https://github.com/oleibman) fixing [#2550](https://github.com/PHPOffice/PHPWord/issues/2550) in [#2551](https://github.com/PHPOffice/PHPWord/pull/2551) diff --git a/src/PhpWord/Shared/ZipArchive.php b/src/PhpWord/Shared/ZipArchive.php index ce4d22533e..f120756d8b 100644 --- a/src/PhpWord/Shared/ZipArchive.php +++ b/src/PhpWord/Shared/ZipArchive.php @@ -20,6 +20,7 @@ use PclZip; use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\Settings; +use Throwable; /** * ZipArchive wrapper. @@ -162,13 +163,16 @@ public function open($filename, $flags = null) * Close the active archive. * * @return bool - * - * @codeCoverageIgnore Can't find any test case. Uncomment when found. */ public function close() { if (!$this->usePclzip) { - if ($this->zip->close() === false) { + try { + $result = @$this->zip->close(); + } catch (Throwable $e) { + $result = false; + } + if ($result === false) { throw new Exception("Could not close zip file {$this->filename}: "); } } diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index 49e88d1b5b..b8ad970ced 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -25,6 +25,7 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\TemplateProcessor; +use Throwable; use TypeError; use ZipArchive; @@ -63,12 +64,21 @@ protected function tearDown(): void * * @covers ::__construct * @covers ::__destruct + * @covers \PhpOffice\PhpWord\Shared\ZipArchive::close */ public function testTheConstruct(): void { $object = $this->getTemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); self::assertInstanceOf('PhpOffice\\PhpWord\\TemplateProcessor', $object); self::assertEquals([], $object->getVariables()); + $object->save(); + + try { + $object->zip()->close(); + self::fail('Expected exception for double close'); + } catch (Throwable $e) { + // nothing to do here + } } /** From 7084019cd47e38d4a9fcde4edc70f6979556f03c Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 7 Aug 2024 04:26:02 -0700 Subject: [PATCH 03/19] Generate Table Cell if Row Doesn't Have Any (#2516) * Generate Table Cell if Row Doesn't Have Any Fix #2505. Word treats file as corrupt if a table row does not contain a cell (documentation for why this is so is included in the issue). Person reporting the issue suggests that dropping such a row from the output file is preferred. However, I think generating an empty cell instead is closer to the user's expectation. For example, as demonstrated in the unit tests added with this PR, if a table has row 1 and 3 which contain cells, but row 2 does not, the table as written to the file will have 3 rows, with the second containing an empty cell. * Remove Commented-Out Code in Tests --- docs/changes/2.x/2.0.0.md | 3 +- src/PhpWord/Writer/Word2007/Element/Table.php | 10 +- .../Writer/Word2007/Element/TableTest.php | 147 ++++++++++++++++++ 3 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 tests/PhpWordTests/Writer/Word2007/Element/TableTest.php diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index cdd6b7b49d..c0423d8caf 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -10,10 +10,11 @@ ### Bug fixes - MsDoc Reader : Correct Font Size Calculation by [@oleibman](https://github.com/oleibman) fixing [#2526](https://github.com/PHPOffice/PHPWord/issues/2526) in [#2531](https://github.com/PHPOffice/PHPWord/pull/2531) +- Html Reader : Process Titles as Headings not Paragraphs [@0b10011](https://github.com/0b10011) and [@oleibman](https://github.com/oleibman) Issue [#1692](https://github.com/PHPOffice/PHPWord/issues/1692) PR [#2533](https://github.com/PHPOffice/PHPWord/pull/2533) +- Generate Table Cell if Row Doesn't Have Any [@oleibman](https://github.com/oleibman) fixing [#2505](https://github.com/PHPOffice/PHPWord/issues/2505) in [#2516](https://github.com/PHPOffice/PHPWord/pull/2516) - TemplateProcessor Persist File After Destruct [@oleibman](https://github.com/oleibman) fixing [#2539](https://github.com/PHPOffice/PHPWord/issues/2539) in [#2545](https://github.com/PHPOffice/PHPWord/pull/2545) - TemplateProcessor Destructor Problem with Php7 [@oleibman](https://github.com/oleibman) fixing [#2548](https://github.com/PHPOffice/PHPWord/issues/2548) in [#2554](https://github.com/PHPOffice/PHPWord/pull/2554) - bug: TemplateProcessor fix multiline values [@gimler](https://github.com/gimler) fixing [#268](https://github.com/PHPOffice/PHPWord/issues/268), [#2323](https://github.com/PHPOffice/PHPWord/issues/2323) and [#2486](https://github.com/PHPOffice/PHPWord/issues/2486) in [#2522](https://github.com/PHPOffice/PHPWord/pull/2522) - - 32-bit Problem in PasswordEncoder [@oleibman](https://github.com/oleibman) fixing [#2550](https://github.com/PHPOffice/PHPWord/issues/2550) in [#2551](https://github.com/PHPOffice/PHPWord/pull/2551) ### Miscellaneous diff --git a/src/PhpWord/Writer/Word2007/Element/Table.php b/src/PhpWord/Writer/Word2007/Element/Table.php index 9364fe45c1..a32cc19639 100644 --- a/src/PhpWord/Writer/Word2007/Element/Table.php +++ b/src/PhpWord/Writer/Word2007/Element/Table.php @@ -103,8 +103,14 @@ private function writeRow(XMLWriter $xmlWriter, RowElement $row): void } // Write cells - foreach ($row->getCells() as $cell) { - $this->writeCell($xmlWriter, $cell); + $cells = $row->getCells(); + if (count($cells) === 0) { + // issue 2505 - Word treats doc as corrupt if row without cell + $this->writeCell($xmlWriter, new CellElement()); + } else { + foreach ($cells as $cell) { + $this->writeCell($xmlWriter, $cell); + } } $xmlWriter->endElement(); // w:tr diff --git a/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php b/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php new file mode 100644 index 0000000000..57010893ae --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php @@ -0,0 +1,147 @@ +addSection(); + $section->addText('Before table (normal).'); + $table = $section->addTable(['width' => 5000, 'unit' => TblWidth::PERCENT]); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R1C1'); + $tc = $table->addCell(); + $tc->addText('R1C2'); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R2C1'); + $tc = $table->addCell(); + $tc->addText('R2C2'); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R3C1'); + $tc = $table->addCell(); + $tc->addText('R3C2'); + $section->addText('After table.'); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl[2]'), 'should be only 1 table'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[3]')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[3]')); + } + + public static function testSomeRowWithNoCells(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Before table (row 2 has no cells).'); + $table = $section->addTable(['width' => 5000, 'unit' => TblWidth::PERCENT]); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R1C1'); + $tc = $table->addCell(); + $tc->addText('R1C2'); + $row = $table->addRow(); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R3C1'); + $tc = $table->addCell(); + $tc->addText('R3C2'); + $section->addText('After table.'); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl[2]'), 'should be only 1 table'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[2]')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[3]')); + } + + public static function testOnly1RowWithNoCells(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Before table (only 1 row and it has no cells).'); + $table = $section->addTable(['width' => 5000, 'unit' => TblWidth::PERCENT]); + $row = $table->addRow(); + $section->addText('After table.'); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl[2]'), 'only 1 table should be written'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]')); + + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]')); + } + + public static function testNoRows(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Before table (no rows therefore omitted).'); + $table = $section->addTable(['width' => 5000, 'unit' => TblWidth::PERCENT]); + $section->addText('After table.'); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl[1]'), 'no table should be written'); + } +} From b99230a3c058b89f22d0247e260bc8e4ec9b1d86 Mon Sep 17 00:00:00 2001 From: Guillaume Lafarge <670645+glafarge@users.noreply.github.com> Date: Wed, 7 Aug 2024 18:45:59 +0200 Subject: [PATCH 04/19] [TYPO] Fix hardcoded macro chars in TemplateProcessor method (#2618) * Fix hardcoded macro chars * Update changelog --------- Co-authored-by: glafarge --- docs/changes/2.x/2.0.0.md | 1 + src/PhpWord/TemplateProcessor.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index c0423d8caf..2d4b3b9cca 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -16,6 +16,7 @@ - TemplateProcessor Destructor Problem with Php7 [@oleibman](https://github.com/oleibman) fixing [#2548](https://github.com/PHPOffice/PHPWord/issues/2548) in [#2554](https://github.com/PHPOffice/PHPWord/pull/2554) - bug: TemplateProcessor fix multiline values [@gimler](https://github.com/gimler) fixing [#268](https://github.com/PHPOffice/PHPWord/issues/268), [#2323](https://github.com/PHPOffice/PHPWord/issues/2323) and [#2486](https://github.com/PHPOffice/PHPWord/issues/2486) in [#2522](https://github.com/PHPOffice/PHPWord/pull/2522) - 32-bit Problem in PasswordEncoder [@oleibman](https://github.com/oleibman) fixing [#2550](https://github.com/PHPOffice/PHPWord/issues/2550) in [#2551](https://github.com/PHPOffice/PHPWord/pull/2551) +- Typo : Fix hardcoded macro chars in TemplateProcessor method [@glafarge](https://github.com/glafarge) in [#2618](https://github.com/PHPOffice/PHPWord/pull/2618) ### Miscellaneous diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 8aee40c546..900169bd68 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -805,8 +805,8 @@ public function cloneRow($search, $numberOfClones): void */ public function deleteRow(string $search): void { - if ('${' !== substr($search, 0, 2) && '}' !== substr($search, -1)) { - $search = '${' . $search . '}'; + if (self::$macroOpeningChars !== substr($search, 0, 2) && self::$macroClosingChars !== substr($search, -1)) { + $search = self::$macroOpeningChars . $search . self::$macroClosingChars; } $tagPos = strpos($this->tempDocumentMainPart, $search); From 761280bb71a3171a31a875e5bf2cb0ae97021139 Mon Sep 17 00:00:00 2001 From: Mark McEver Date: Thu, 8 Aug 2024 09:53:04 -0500 Subject: [PATCH 05/19] Prevented fatal errors when opening corrupt files or "doc" files (#2626) * Prevented fatal errors when opening corrupt files or "doc" files * Ran php-cs-fixer * Fixed phpstan errors * Updated the change log --- docs/changes/2.x/2.0.0.md | 1 + src/PhpWord/Shared/XMLReader.php | 10 +++++++- tests/PhpWordTests/Shared/XMLReaderTest.php | 27 +++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 2d4b3b9cca..bb4eadec99 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -17,6 +17,7 @@ - bug: TemplateProcessor fix multiline values [@gimler](https://github.com/gimler) fixing [#268](https://github.com/PHPOffice/PHPWord/issues/268), [#2323](https://github.com/PHPOffice/PHPWord/issues/2323) and [#2486](https://github.com/PHPOffice/PHPWord/issues/2486) in [#2522](https://github.com/PHPOffice/PHPWord/pull/2522) - 32-bit Problem in PasswordEncoder [@oleibman](https://github.com/oleibman) fixing [#2550](https://github.com/PHPOffice/PHPWord/issues/2550) in [#2551](https://github.com/PHPOffice/PHPWord/pull/2551) - Typo : Fix hardcoded macro chars in TemplateProcessor method [@glafarge](https://github.com/glafarge) in [#2618](https://github.com/PHPOffice/PHPWord/pull/2618) +- XML Reader : Prevent fatal errors when opening corrupt files or "doc" files [@mmcev106](https://github.com/mmcev106) in [#2626](https://github.com/PHPOffice/PHPWord/pull/2626) ### Miscellaneous diff --git a/src/PhpWord/Shared/XMLReader.php b/src/PhpWord/Shared/XMLReader.php index 1c95a64426..e836607d29 100644 --- a/src/PhpWord/Shared/XMLReader.php +++ b/src/PhpWord/Shared/XMLReader.php @@ -61,7 +61,15 @@ public function getDomFromZip($zipFile, $xmlFile) } $zip = new ZipArchive(); - $zip->open($zipFile); + $openStatus = $zip->open($zipFile); + if ($openStatus !== true) { + /** + * Throw an exception since making further calls on the ZipArchive would cause a fatal error. + * This prevents fatal errors on corrupt archives and attempts to open old "doc" files. + */ + throw new Exception("The archive failed to load with the following error code: $openStatus"); + } + $content = $zip->getFromName(ltrim($xmlFile, '/')); $zip->close(); diff --git a/tests/PhpWordTests/Shared/XMLReaderTest.php b/tests/PhpWordTests/Shared/XMLReaderTest.php index 4cbe92a5e2..cc15c85f01 100644 --- a/tests/PhpWordTests/Shared/XMLReaderTest.php +++ b/tests/PhpWordTests/Shared/XMLReaderTest.php @@ -85,6 +85,33 @@ public function testThrowsExceptionOnNonExistingArchive(): void $reader->getDomFromZip($archiveFile, 'test.xml'); } + /** + * Test that read from invalid archive throws exception. + */ + public function testThrowsExceptionOnZipArchiveOpenErrors(): void + { + /** + * @var string + */ + $tempPath = tempnam(sys_get_temp_dir(), 'PhpWord'); + + // Simulate a corrupt archive + file_put_contents($tempPath, mt_rand()); + + $exceptionMessage = null; + + try { + $reader = new XMLReader(); + $reader->getDomFromZip($tempPath, 'test.xml'); + } catch (Exception $e) { + $exceptionMessage = $e->getMessage(); + } + + self::assertNotNull($exceptionMessage); + + unlink($tempPath); + } + /** * Test elements count. */ From 9c9382a75cdb2b71eb0dcec55f5dde9505492baa Mon Sep 17 00:00:00 2001 From: Progi1984 Date: Mon, 12 Aug 2024 10:03:49 +0200 Subject: [PATCH 06/19] Bump phpoffice/math from 0.1.0 to 0.2.0 (#2645) --- composer.json | 2 +- composer.lock | 17 ++++++++--------- docs/changes/2.x/2.0.0.md | 1 + 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 7e0deaa092..bce0f4b8f4 100644 --- a/composer.json +++ b/composer.json @@ -68,7 +68,7 @@ "ext-dom": "*", "ext-json": "*", "ext-xml": "*", - "phpoffice/math": "^0.1" + "phpoffice/math": "^0.2" }, "require-dev": { "ext-zip": "*", diff --git a/composer.lock b/composer.lock index 6eb3324ccb..587491c588 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8d0272c1442e11290de55a7947fc849c", + "content-hash": "15c1d733e1ddd2f424af0383ceb28dfd", "packages": [ { "name": "phpoffice/math", - "version": "0.1.0", + "version": "0.2.0", "source": { "type": "git", "url": "https://github.com/PHPOffice/Math.git", - "reference": "f0f8cad98624459c540cdd61d2a174d834471773" + "reference": "fc2eb6d1a61b058d5dac77197059db30ee3c8329" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPOffice/Math/zipball/f0f8cad98624459c540cdd61d2a174d834471773", - "reference": "f0f8cad98624459c540cdd61d2a174d834471773", + "url": "https://api.github.com/repos/PHPOffice/Math/zipball/fc2eb6d1a61b058d5dac77197059db30ee3c8329", + "reference": "fc2eb6d1a61b058d5dac77197059db30ee3c8329", "shasum": "" }, "require": { @@ -32,8 +32,7 @@ "type": "library", "autoload": { "psr-4": { - "PhpOffice\\Math\\": "src/Math/", - "Tests\\PhpOffice\\Math\\": "tests/Math/" + "PhpOffice\\Math\\": "src/Math/" } }, "notification-url": "https://packagist.org/downloads/", @@ -55,9 +54,9 @@ ], "support": { "issues": "https://github.com/PHPOffice/Math/issues", - "source": "https://github.com/PHPOffice/Math/tree/0.1.0" + "source": "https://github.com/PHPOffice/Math/tree/0.2.0" }, - "time": "2023-09-25T12:08:20+00:00" + "time": "2024-08-12T07:30:45+00:00" } ], "packages-dev": [ diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index bb4eadec99..8a3ef6c82d 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -29,5 +29,6 @@ - Bump symfony/process from 5.4.28 to 5.4.34 by [@dependabot](https://github.com/dependabot) in [#2536](https://github.com/PHPOffice/PHPWord/pull/2536) - Allow rgb() when converting Html by [@oleibman](https://github.com/oleibman) fixing [#2508](https://github.com/PHPOffice/PHPWord/issues/2508) in [#2512](https://github.com/PHPOffice/PHPWord/pull/2512) - Improved Issue Template by [@Progi1984](https://github.com/Progi1984) in [#2609](https://github.com/PHPOffice/PHPWord/pull/2609) +- Bump phpoffice/math from 0.1.0 to 0.2.0 by [@Progi1984](https://github.com/Progi1984) fixing [#2559](https://github.com/PHPOffice/PHPWord/issues/2559) in [#2645](https://github.com/PHPOffice/PHPWord/pull/2645) ### BC Breaks From 997a82996d02c6480939a72d1e4970deb4477b8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:47:44 +0200 Subject: [PATCH 07/19] Bump tecnickcom/tcpdf from 6.6.5 to 6.7.5 (#2646) * Bump tecnickcom/tcpdf from 6.6.5 to 6.7.5 Bumps [tecnickcom/tcpdf](https://github.com/tecnickcom/TCPDF) from 6.6.5 to 6.7.5. - [Changelog](https://github.com/tecnickcom/TCPDF/blob/main/CHANGELOG.TXT) - [Commits](https://github.com/tecnickcom/TCPDF/commits/6.7.5) --- updated-dependencies: - dependency-name: tecnickcom/tcpdf dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Updated changelog --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Progi1984 --- composer.lock | 14 +++++++------- docs/changes/2.x/2.0.0.md | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index 587491c588..6a1df276c9 100644 --- a/composer.lock +++ b/composer.lock @@ -4935,20 +4935,20 @@ }, { "name": "tecnickcom/tcpdf", - "version": "6.6.5", + "version": "6.7.5", "source": { "type": "git", "url": "https://github.com/tecnickcom/TCPDF.git", - "reference": "5fce932fcee4371865314ab7f6c0d85423c5c7ce" + "reference": "951eabf0338ec2522bd0d5d9c79b08a3a3d36b36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/5fce932fcee4371865314ab7f6c0d85423c5c7ce", - "reference": "5fce932fcee4371865314ab7f6c0d85423c5c7ce", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/951eabf0338ec2522bd0d5d9c79b08a3a3d36b36", + "reference": "951eabf0338ec2522bd0d5d9c79b08a3a3d36b36", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=5.5.0" }, "type": "library", "autoload": { @@ -4995,7 +4995,7 @@ ], "support": { "issues": "https://github.com/tecnickcom/TCPDF/issues", - "source": "https://github.com/tecnickcom/TCPDF/tree/6.6.5" + "source": "https://github.com/tecnickcom/TCPDF/tree/6.7.5" }, "funding": [ { @@ -5003,7 +5003,7 @@ "type": "custom" } ], - "time": "2023-09-06T15:09:26+00:00" + "time": "2024-04-20T17:25:10+00:00" }, { "name": "theseer/tokenizer", diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 8a3ef6c82d..09a4feb5d8 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -30,5 +30,6 @@ - Allow rgb() when converting Html by [@oleibman](https://github.com/oleibman) fixing [#2508](https://github.com/PHPOffice/PHPWord/issues/2508) in [#2512](https://github.com/PHPOffice/PHPWord/pull/2512) - Improved Issue Template by [@Progi1984](https://github.com/Progi1984) in [#2609](https://github.com/PHPOffice/PHPWord/pull/2609) - Bump phpoffice/math from 0.1.0 to 0.2.0 by [@Progi1984](https://github.com/Progi1984) fixing [#2559](https://github.com/PHPOffice/PHPWord/issues/2559) in [#2645](https://github.com/PHPOffice/PHPWord/pull/2645) +- Bump tecnickcom/tcpdf from 6.6.5 to 6.7.5 by [@dependabot](https://github.com/dependabot) in [#2646](https://github.com/PHPOffice/PHPWord/pull/2646) ### BC Breaks From fbe52dcd842ccbfc6f3974535ba2ed9bc4ad8128 Mon Sep 17 00:00:00 2001 From: Progi1984 Date: Mon, 12 Aug 2024 11:04:34 +0200 Subject: [PATCH 08/19] Documentation : Updated Comment element (#2650) Fix error on comments code snippet. Co-authored-by: laminga <39699385+laminga@users.noreply.github.com> --- docs/changes/2.x/2.0.0.md | 1 + docs/usage/elements/comment.md | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 09a4feb5d8..3e3e55807d 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -18,6 +18,7 @@ - 32-bit Problem in PasswordEncoder [@oleibman](https://github.com/oleibman) fixing [#2550](https://github.com/PHPOffice/PHPWord/issues/2550) in [#2551](https://github.com/PHPOffice/PHPWord/pull/2551) - Typo : Fix hardcoded macro chars in TemplateProcessor method [@glafarge](https://github.com/glafarge) in [#2618](https://github.com/PHPOffice/PHPWord/pull/2618) - XML Reader : Prevent fatal errors when opening corrupt files or "doc" files [@mmcev106](https://github.com/mmcev106) in [#2626](https://github.com/PHPOffice/PHPWord/pull/2626) +- Documentation : Updated Comment element by [@laminga](https://github.com/laminga) in [#2650](https://github.com/PHPOffice/PHPWord/pull/2650) ### Miscellaneous diff --git a/docs/usage/elements/comment.md b/docs/usage/elements/comment.md index 06a3866a3e..50813fa2a1 100644 --- a/docs/usage/elements/comment.md +++ b/docs/usage/elements/comment.md @@ -1,7 +1,7 @@ # Comment Comments can be added to a document by using ``addComment``. -The comment can contain formatted text. Once the comment has been added, it can be linked to any element with ``setCommentStart``. +The comment can contain formatted text. Once the comment has been added, it can be linked to any element with ``setCommentRangeStart``. ``` php addTextRun(); $textrun->addText('This '); $text = $textrun->addText('is'); // link the comment to the text you just created -$text->setCommentStart($comment); +$text->setCommentRangeStart($comment); +$textrun->addText(' a test'); ``` -If no end is set for a comment using the ``setCommentEnd``, the comment will be ended automatically at the end of the element it is started on. \ No newline at end of file +If no end is set for a comment using the ``setCommentRangeEnd``, the comment will be ended automatically at the end of the element it is started on. \ No newline at end of file From 8730d1bad7a73a7a98200bbbe7fbefc079aec841 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 11:22:21 +0200 Subject: [PATCH 09/19] Bump mpdf/mpdf from 8.2.2 to 8.2.4 (#2647) * Bump mpdf/mpdf from 8.2.2 to 8.2.4 Bumps [mpdf/mpdf](https://github.com/mpdf/mpdf) from 8.2.2 to 8.2.4. - [Release notes](https://github.com/mpdf/mpdf/releases) - [Changelog](https://github.com/mpdf/mpdf/blob/development/CHANGELOG.md) - [Commits](https://github.com/mpdf/mpdf/compare/v8.2.2...v8.2.4) --- updated-dependencies: - dependency-name: mpdf/mpdf dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Updated changelog --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Progi1984 --- composer.lock | 25 +++++++++++++------------ docs/changes/2.x/2.0.0.md | 1 + 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/composer.lock b/composer.lock index 6a1df276c9..cff0751756 100644 --- a/composer.lock +++ b/composer.lock @@ -765,16 +765,16 @@ }, { "name": "mpdf/mpdf", - "version": "v8.2.2", + "version": "v8.2.4", "source": { "type": "git", "url": "https://github.com/mpdf/mpdf.git", - "reference": "596a87b876d7793be7be060a8ac13424de120dd5" + "reference": "9e3ff91606fed11cd58a130eabaaf60e56fdda88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mpdf/mpdf/zipball/596a87b876d7793be7be060a8ac13424de120dd5", - "reference": "596a87b876d7793be7be060a8ac13424de120dd5", + "url": "https://api.github.com/repos/mpdf/mpdf/zipball/9e3ff91606fed11cd58a130eabaaf60e56fdda88", + "reference": "9e3ff91606fed11cd58a130eabaaf60e56fdda88", "shasum": "" }, "require": { @@ -842,7 +842,7 @@ "type": "custom" } ], - "time": "2023-11-07T13:52:14+00:00" + "time": "2024-06-14T16:06:41+00:00" }, { "name": "mpdf/psr-http-message-shim", @@ -938,16 +938,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -955,11 +955,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -985,7 +986,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -993,7 +994,7 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "nikic/php-parser", diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 3e3e55807d..cdb104c413 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -32,5 +32,6 @@ - Improved Issue Template by [@Progi1984](https://github.com/Progi1984) in [#2609](https://github.com/PHPOffice/PHPWord/pull/2609) - Bump phpoffice/math from 0.1.0 to 0.2.0 by [@Progi1984](https://github.com/Progi1984) fixing [#2559](https://github.com/PHPOffice/PHPWord/issues/2559) in [#2645](https://github.com/PHPOffice/PHPWord/pull/2645) - Bump tecnickcom/tcpdf from 6.6.5 to 6.7.5 by [@dependabot](https://github.com/dependabot) in [#2646](https://github.com/PHPOffice/PHPWord/pull/2646) +- Bump mpdf/mpdf from 8.2.2 to 8.2.4 by [@dependabot](https://github.com/dependabot) in [#2647](https://github.com/PHPOffice/PHPWord/pull/2647) ### BC Breaks From 6e27dbf2b67db84afee4794c744206754c52d81e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 12:09:15 +0200 Subject: [PATCH 10/19] Bump phenx/php-svg-lib from 0.5.1 to 0.5.4 (#2649) * Bump phenx/php-svg-lib from 0.5.1 to 0.5.4 Bumps [phenx/php-svg-lib](https://github.com/dompdf/php-svg-lib) from 0.5.1 to 0.5.4. - [Release notes](https://github.com/dompdf/php-svg-lib/releases) - [Commits](https://github.com/dompdf/php-svg-lib/compare/0.5.1...0.5.4) --- updated-dependencies: - dependency-name: phenx/php-svg-lib dependency-type: indirect ... Signed-off-by: dependabot[bot] * Updated changelog --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Progi1984 --- composer.lock | 46 ++++++++++++++++++++++++--------------- docs/changes/2.x/2.0.0.md | 1 + 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/composer.lock b/composer.lock index cff0751756..d309d421c3 100644 --- a/composer.lock +++ b/composer.lock @@ -1322,16 +1322,16 @@ }, { "name": "phenx/php-svg-lib", - "version": "0.5.1", + "version": "0.5.4", "source": { "type": "git", "url": "https://github.com/dompdf/php-svg-lib.git", - "reference": "8a8a1ebcf6aea861ef30197999f096f7bd4b4456" + "reference": "46b25da81613a9cf43c83b2a8c2c1bdab27df691" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8a8a1ebcf6aea861ef30197999f096f7bd4b4456", - "reference": "8a8a1ebcf6aea861ef30197999f096f7bd4b4456", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/46b25da81613a9cf43c83b2a8c2c1bdab27df691", + "reference": "46b25da81613a9cf43c83b2a8c2c1bdab27df691", "shasum": "" }, "require": { @@ -1350,7 +1350,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0" + "LGPL-3.0-or-later" ], "authors": [ { @@ -1362,9 +1362,9 @@ "homepage": "https://github.com/PhenX/php-svg-lib", "support": { "issues": "https://github.com/dompdf/php-svg-lib/issues", - "source": "https://github.com/dompdf/php-svg-lib/tree/0.5.1" + "source": "https://github.com/dompdf/php-svg-lib/tree/0.5.4" }, - "time": "2023-12-11T20:56:08+00:00" + "time": "2024-04-08T12:52:34+00:00" }, { "name": "php-cs-fixer/diff", @@ -2290,16 +2290,16 @@ }, { "name": "sabberworm/php-css-parser", - "version": "8.4.0", + "version": "v8.6.0", "source": { "type": "git", - "url": "https://github.com/sabberworm/PHP-CSS-Parser.git", - "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30" + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/e41d2140031d533348b2192a83f02d8dd8a71d30", - "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d2fb94a9641be84d79c7548c6d39bbebba6e9a70", + "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70", "shasum": "" }, "require": { @@ -2307,13 +2307,17 @@ "php": ">=5.6.20" }, "require-dev": { - "codacy/coverage": "^1.4", - "phpunit/phpunit": "^4.8.36" + "phpunit/phpunit": "^5.7.27" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.0.x-dev" + } + }, "autoload": { "psr-4": { "Sabberworm\\CSS\\": "src/" @@ -2326,6 +2330,14 @@ "authors": [ { "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" } ], "description": "Parser for CSS Files written in PHP", @@ -2336,10 +2348,10 @@ "stylesheet" ], "support": { - "issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues", - "source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.4.0" + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.6.0" }, - "time": "2021-12-11T13:40:54+00:00" + "time": "2024-07-01T07:33:21+00:00" }, { "name": "sebastian/cli-parser", diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index cdb104c413..70f9322750 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -33,5 +33,6 @@ - Bump phpoffice/math from 0.1.0 to 0.2.0 by [@Progi1984](https://github.com/Progi1984) fixing [#2559](https://github.com/PHPOffice/PHPWord/issues/2559) in [#2645](https://github.com/PHPOffice/PHPWord/pull/2645) - Bump tecnickcom/tcpdf from 6.6.5 to 6.7.5 by [@dependabot](https://github.com/dependabot) in [#2646](https://github.com/PHPOffice/PHPWord/pull/2646) - Bump mpdf/mpdf from 8.2.2 to 8.2.4 by [@dependabot](https://github.com/dependabot) in [#2647](https://github.com/PHPOffice/PHPWord/pull/2647) +- Bump phenx/php-svg-lib from 0.5.1 to 0.5.4 by [@dependabot](https://github.com/dependabot) in [#2649](https://github.com/PHPOffice/PHPWord/pull/2649) ### BC Breaks From 72c29a6ff7dbd949e136a1e0f9fda06e7acc5637 Mon Sep 17 00:00:00 2001 From: Progi1984 Date: Mon, 12 Aug 2024 12:17:06 +0200 Subject: [PATCH 11/19] Word2007 Reader: Support for Paragraph Border Style (#2651) Co-authored-by: damienfa --- docs/changes/2.x/2.0.0.md | 1 + src/PhpWord/Element/TextRun.php | 10 ++--- src/PhpWord/Reader/Word2007/AbstractPart.php | 12 ++++++ tests/PhpWordTests/Reader/Word2007Test.php | 36 ++++++++++++++++++ .../_files/documents/reader-styles.docx | Bin 0 -> 63587 bytes 5 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 tests/PhpWordTests/_files/documents/reader-styles.docx diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 70f9322750..3b08c2f0ca 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -6,6 +6,7 @@ - IOFactory : Added extractVariables method to extract variables from a document [@sibalonat](https://github.com/sibalonat) in [#2515](https://github.com/PHPOffice/PHPWord/pull/2515) - PDF Writer : Documented how to specify a PDF renderer, when working with the PDF writer, as well as the three available choices by [@settermjd](https://github.com/settermjd) in [#2642](https://github.com/PHPOffice/PHPWord/pull/2642) +- Word2007 Reader: Support for Paragraph Border Style by [@damienfa](https://github.com/damienfa) in [#2651](https://github.com/PHPOffice/PHPWord/pull/2651) ### Bug fixes diff --git a/src/PhpWord/Element/TextRun.php b/src/PhpWord/Element/TextRun.php index 33c55f6584..8ddc9cd7ba 100644 --- a/src/PhpWord/Element/TextRun.php +++ b/src/PhpWord/Element/TextRun.php @@ -32,14 +32,14 @@ class TextRun extends AbstractContainer /** * Paragraph style. * - * @var \PhpOffice\PhpWord\Style\Paragraph|string + * @var Paragraph|string */ protected $paragraphStyle; /** * Create new instance. * - * @param array|\PhpOffice\PhpWord\Style\Paragraph|string $paragraphStyle + * @param array|Paragraph|string $paragraphStyle */ public function __construct($paragraphStyle = null) { @@ -49,7 +49,7 @@ public function __construct($paragraphStyle = null) /** * Get Paragraph style. * - * @return \PhpOffice\PhpWord\Style\Paragraph|string + * @return Paragraph|string */ public function getParagraphStyle() { @@ -59,9 +59,9 @@ public function getParagraphStyle() /** * Set Paragraph style. * - * @param array|\PhpOffice\PhpWord\Style\Paragraph|string $style + * @param array|Paragraph|string $style * - * @return \PhpOffice\PhpWord\Style\Paragraph|string + * @return Paragraph|string */ public function setParagraphStyle($style = null) { diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 95799387ed..2741841ef3 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -529,6 +529,18 @@ protected function readParagraphStyle(XMLReader $xmlReader, DOMElement $domNode) 'contextualSpacing' => [self::READ_TRUE, 'w:contextualSpacing'], 'bidi' => [self::READ_TRUE, 'w:bidi'], 'suppressAutoHyphens' => [self::READ_TRUE, 'w:suppressAutoHyphens'], + 'borderTopStyle' => [self::READ_VALUE, 'w:pBdr/w:top'], + 'borderTopColor' => [self::READ_VALUE, 'w:pBdr/w:top', 'w:color'], + 'borderTopSize' => [self::READ_VALUE, 'w:pBdr/w:top', 'w:sz'], + 'borderRightStyle' => [self::READ_VALUE, 'w:pBdr/w:right'], + 'borderRightColor' => [self::READ_VALUE, 'w:pBdr/w:right', 'w:color'], + 'borderRightSize' => [self::READ_VALUE, 'w:pBdr/w:right', 'w:sz'], + 'borderBottomStyle' => [self::READ_VALUE, 'w:pBdr/w:bottom'], + 'borderBottomColor' => [self::READ_VALUE, 'w:pBdr/w:bottom', 'w:color'], + 'borderBottomSize' => [self::READ_VALUE, 'w:pBdr/w:bottom', 'w:sz'], + 'borderLeftStyle' => [self::READ_VALUE, 'w:pBdr/w:left'], + 'borderLeftColor' => [self::READ_VALUE, 'w:pBdr/w:left', 'w:color'], + 'borderLeftSize' => [self::READ_VALUE, 'w:pBdr/w:left', 'w:sz'], ]; return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); diff --git a/tests/PhpWordTests/Reader/Word2007Test.php b/tests/PhpWordTests/Reader/Word2007Test.php index fac5b6b59a..ebfd8ad7a2 100644 --- a/tests/PhpWordTests/Reader/Word2007Test.php +++ b/tests/PhpWordTests/Reader/Word2007Test.php @@ -29,6 +29,7 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Reader\Word2007; use PhpOffice\PhpWord\Style\Font; +use PhpOffice\PhpWord\Style\Paragraph; use PhpOffice\PhpWordTests\TestHelperDOCX; /** @@ -82,6 +83,41 @@ public function testLoad(): void self::assertEquals('0', $doc->getElementAttribute('/w:document/w:body/w:p/w:r[w:t/node()="italics"]/w:rPr/w:b', 'w:val')); } + public function testLoadStyles(): void + { + $phpWord = IOFactory::load(dirname(__DIR__, 1) . '/_files/documents/reader-styles.docx', 'Word2007'); + + self::assertInstanceOf(PhpWord::class, $phpWord); + + $section2 = $phpWord->getSection(2); + self::assertInstanceOf(Section::class, $section2); + + $element2_31 = $section2->getElement(31); + self::assertInstanceOf(TextRun::class, $element2_31); + self::assertEquals('This is a paragraph with border differents', $element2_31->getText()); + + /** @var Paragraph $element2_31_pStyle */ + $element2_31_pStyle = $element2_31->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $element2_31_pStyle); + + // Top + self::assertEquals('FFFF00', $element2_31_pStyle->getBorderTopColor()); + self::assertEquals('10', $element2_31_pStyle->getBorderTopSize()); + self::assertEquals('dotted', $element2_31_pStyle->getBorderTopStyle()); + // Right + self::assertEquals('00A933', $element2_31_pStyle->getBorderRightColor()); + self::assertEquals('4', $element2_31_pStyle->getBorderRightSize()); + self::assertEquals('dashed', $element2_31_pStyle->getBorderRightStyle()); + // Bottom + self::assertEquals('F10D0C', $element2_31_pStyle->getBorderBottomColor()); + self::assertEquals('8', $element2_31_pStyle->getBorderBottomSize()); + self::assertEquals('dashSmallGap', $element2_31_pStyle->getBorderBottomStyle()); + // Left + self::assertEquals('3465A4', $element2_31_pStyle->getBorderLeftColor()); + self::assertEquals('8', $element2_31_pStyle->getBorderLeftSize()); + self::assertEquals('dashed', $element2_31_pStyle->getBorderLeftStyle()); + } + /** * Load a Word 2011 file. */ diff --git a/tests/PhpWordTests/_files/documents/reader-styles.docx b/tests/PhpWordTests/_files/documents/reader-styles.docx new file mode 100644 index 0000000000000000000000000000000000000000..b02cf73c5ff84b4dc1c309052f035ac791659b3b GIT binary patch literal 63587 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 Date: Mon, 12 Aug 2024 12:47:47 +0200 Subject: [PATCH 12/19] Bump phpstan/phpstan-phpunit from 1.3.15 to 1.4.0 (#2648) * Bump phpstan/phpstan-phpunit from 1.3.15 to 1.4.0 Bumps [phpstan/phpstan-phpunit](https://github.com/phpstan/phpstan-phpunit) from 1.3.15 to 1.4.0. - [Release notes](https://github.com/phpstan/phpstan-phpunit/releases) - [Commits](https://github.com/phpstan/phpstan-phpunit/compare/1.3.15...1.4.0) --- updated-dependencies: - dependency-name: phpstan/phpstan-phpunit dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Updated changelog * Fixed PHPStan errors --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Progi1984 --- composer.lock | 32 ++++++++++++---------------- docs/changes/2.x/2.0.0.md | 1 + phpstan-baseline.neon | 40 ----------------------------------- phpstan.neon | 4 +++- src/PhpWord/Element/Image.php | 5 +++-- src/PhpWord/Shared/Html.php | 20 ++++++++++-------- 6 files changed, 31 insertions(+), 71 deletions(-) diff --git a/composer.lock b/composer.lock index d309d421c3..a803691ad2 100644 --- a/composer.lock +++ b/composer.lock @@ -1504,16 +1504,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.40", + "version": "1.11.10", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "93c84b5bf7669920d823631e39904d69b9c7dc5d" + "reference": "640410b32995914bde3eed26fa89552f9c2c082f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/93c84b5bf7669920d823631e39904d69b9c7dc5d", - "reference": "93c84b5bf7669920d823631e39904d69b9c7dc5d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/640410b32995914bde3eed26fa89552f9c2c082f", + "reference": "640410b32995914bde3eed26fa89552f9c2c082f", "shasum": "" }, "require": { @@ -1556,31 +1556,27 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2023-10-30T14:48:31+00:00" + "time": "2024-08-08T09:02:50+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "1.3.15", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "70ecacc64fe8090d8d2a33db5a51fe8e88acd93a" + "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/70ecacc64fe8090d8d2a33db5a51fe8e88acd93a", - "reference": "70ecacc64fe8090d8d2a33db5a51fe8e88acd93a", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11", + "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^1.11" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -1612,9 +1608,9 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.15" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0" }, - "time": "2023-10-09T18:58:39+00:00" + "time": "2024-04-20T06:39:00+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5071,9 +5067,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "phpstan/phpstan-phpunit": 0 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 3b08c2f0ca..a299d15b41 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -35,5 +35,6 @@ - Bump tecnickcom/tcpdf from 6.6.5 to 6.7.5 by [@dependabot](https://github.com/dependabot) in [#2646](https://github.com/PHPOffice/PHPWord/pull/2646) - Bump mpdf/mpdf from 8.2.2 to 8.2.4 by [@dependabot](https://github.com/dependabot) in [#2647](https://github.com/PHPOffice/PHPWord/pull/2647) - Bump phenx/php-svg-lib from 0.5.1 to 0.5.4 by [@dependabot](https://github.com/dependabot) in [#2649](https://github.com/PHPOffice/PHPWord/pull/2649) +- Bump phpstan/phpstan-phpunit from 1.3.15 to 1.4.0 by [@dependabot](https://github.com/dependabot) in [#2648](https://github.com/PHPOffice/PHPWord/pull/2648) ### BC Breaks diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 2e44745b3d..8e47429ad9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -60,11 +60,6 @@ parameters: count: 1 path: src/PhpWord/Element/Image.php - - - message: "#^Parameter \\#2 \\$length of function fread expects int\\<0, max\\>, int\\<0, max\\>\\|false given\\.$#" - count: 1 - path: src/PhpWord/Element/Image.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:\\$source \\(string\\) does not accept string\\|false\\.$#" count: 1 @@ -385,11 +380,6 @@ parameters: count: 1 path: src/PhpWord/Shared/Drawing.php - - - message: "#^Access to an undefined property DOMNode\\:\\:\\$value\\.$#" - count: 6 - path: src/PhpWord/Shared/Html.php - - message: "#^Binary operation \"\\*\" between string and 50 results in an error\\.$#" count: 1 @@ -520,16 +510,6 @@ parameters: count: 1 path: src/PhpWord/Shared/XMLWriter.php - - - message: "#^Parameter \\#1 \\$uri of method XMLWriter\\:\\:openUri\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: src/PhpWord/Shared/XMLWriter.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:\\$tempFileName \\(string\\) does not accept string\\|false\\.$#" - count: 1 - path: src/PhpWord/Shared/XMLWriter.php - - message: "#^Call to method add\\(\\) on an unknown class PclZip\\.$#" count: 2 @@ -1110,11 +1090,6 @@ parameters: count: 1 path: src/PhpWord/TemplateProcessor.php - - - message: "#^Parameter \\#1 \\$str of function preg_quote expects string, int\\|string given\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - message: "#^Parameter \\#2 \\$array of function implode expects array\\|null, array\\\\|string given\\.$#" count: 1 @@ -1135,11 +1110,6 @@ parameters: count: 1 path: src/PhpWord/TemplateProcessor.php - - - message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$tempDocumentFilename \\(string\\) does not accept string\\|false\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$tempDocumentFooters \\(array\\\\) does not accept string\\.$#" count: 1 @@ -1230,11 +1200,6 @@ parameters: count: 1 path: src/PhpWord/Writer/PDF.php - - - message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|false given\\.$#" - count: 1 - path: src/PhpWord/Writer/PDF.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:\\$renderer \\(PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\AbstractRenderer\\) does not accept object\\.$#" count: 1 @@ -1815,11 +1780,6 @@ parameters: count: 1 path: tests/PhpWordTests/TestHelperDOCX.php - - - message: "#^Static property PhpOffice\\\\PhpWordTests\\\\TestHelperDOCX\\:\\:\\$file \\(string\\) does not accept string\\|false\\.$#" - count: 1 - path: tests/PhpWordTests/TestHelperDOCX.php - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\TestableTemplateProcesor\\:\\:__construct\\(\\) has parameter \\$mainPart with no type specified\\.$#" count: 1 diff --git a/phpstan.neon b/phpstan.neon index e490e1b179..aac94077bd 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -14,4 +14,6 @@ parameters: - src/PhpWord/Writer/PDF/MPDF.php bootstrapFiles: - tests/bootstrap.php - checkMissingIterableValueType: false + ignoreErrors: + - + identifier: missingType.iterableValue diff --git a/src/PhpWord/Element/Image.php b/src/PhpWord/Element/Image.php index 1f1a62500a..12d637d7d2 100644 --- a/src/PhpWord/Element/Image.php +++ b/src/PhpWord/Element/Image.php @@ -386,8 +386,9 @@ public function getImageString(): ?string $imageBinary = $this->source; } else { $fileHandle = fopen($actualSource, 'rb', false); - if ($fileHandle !== false) { - $imageBinary = fread($fileHandle, filesize($actualSource)); + $fileSize = filesize($actualSource); + if ($fileHandle !== false && $fileSize > 0) { + $imageBinary = fread($fileHandle, $fileSize); fclose($fileHandle); } } diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 2022f7da09..e38f8be910 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -102,7 +102,7 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit * parse Inline style of a node. * * @param DOMNode $node Node to check on attributes and to compile a style array - * @param array $styles is supplied, the inline style attributes are added to the already existing style + * @param array $styles is supplied, the inline style attributes are added to the already existing style * * @return array */ @@ -111,7 +111,9 @@ protected static function parseInlineStyle($node, $styles = []) if (XML_ELEMENT_NODE == $node->nodeType) { $attributes = $node->attributes; // get all the attributes(eg: id, class) - $bidi = ($attributes['dir'] ?? '') === 'rtl'; + $attributeDir = $attributes->getNamedItem('dir'); + $attributeDirValue = $attributeDir ? $attributeDir->nodeValue : ''; + $bidi = $attributeDirValue === 'rtl'; foreach ($attributes as $attribute) { $val = $attribute->value; switch (strtolower($attribute->name)) { @@ -159,15 +161,15 @@ protected static function parseInlineStyle($node, $styles = []) $attributeIdentifier = $attributes->getNamedItem('id'); if ($attributeIdentifier && self::$css) { - $styles = self::parseStyleDeclarations(self::$css->getStyle('#' . $attributeIdentifier->value), $styles); + $styles = self::parseStyleDeclarations(self::$css->getStyle('#' . $attributeIdentifier->nodeValue), $styles); } $attributeClass = $attributes->getNamedItem('class'); if ($attributeClass) { if (self::$css) { - $styles = self::parseStyleDeclarations(self::$css->getStyle('.' . $attributeClass->value), $styles); + $styles = self::parseStyleDeclarations(self::$css->getStyle('.' . $attributeClass->nodeValue), $styles); } - $styles['className'] = $attributeClass->value; + $styles['className'] = $attributeClass->nodeValue; } $attributeStyle = $attributes->getNamedItem('style'); @@ -325,10 +327,10 @@ protected static function parseInput($node, $element, &$styles): void return; } - $inputType = $attributes->getNamedItem('type')->value; + $inputType = $attributes->getNamedItem('type')->nodeValue; switch ($inputType) { case 'checkbox': - $checked = ($checked = $attributes->getNamedItem('checked')) && $checked->value === 'true' ? true : false; + $checked = ($checked = $attributes->getNamedItem('checked')) && $checked->nodeValue === 'true' ? true : false; $textrun = $element->addTextRun($styles['paragraph']); $textrun->addFormField('checkbox')->setValue($checked); @@ -423,8 +425,8 @@ protected static function parseTable($node, $element, &$styles) } $attributes = $node->attributes; - if ($attributes->getNamedItem('border') !== null) { - $border = (int) $attributes->getNamedItem('border')->value; + if ($attributes->getNamedItem('border')) { + $border = (int) $attributes->getNamedItem('border')->nodeValue; $newElement->getStyle()->setBorderSize(Converter::pixelToTwip($border)); } From 00febf54321d77ac4ec99a308ab73d634f6ab422 Mon Sep 17 00:00:00 2001 From: Progi1984 Date: Mon, 12 Aug 2024 14:11:49 +0200 Subject: [PATCH 13/19] Word2007 Writer : Added support for field `REF` (#2652) * added support for REF field * Spelling correction * UnitTest and fixes * updated documentation * Update doc docs/elements.rst. Aded ref support * Rebase & Fixed CI --------- Co-authored-by: Adekunle Co-authored-by: Adekunle Adekoya --- docs/changes/2.x/2.0.0.md | 1 + docs/usage/elements/field.md | 15 ++- src/PhpWord/Element/Field.php | 4 + src/PhpWord/Writer/Word2007/Element/Field.php | 113 +++++++++++++++++- .../Writer/Word2007/Element/FieldTest.php | 73 +++++++++++ 5 files changed, 198 insertions(+), 8 deletions(-) create mode 100644 tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index a299d15b41..06a7ba3675 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -7,6 +7,7 @@ - IOFactory : Added extractVariables method to extract variables from a document [@sibalonat](https://github.com/sibalonat) in [#2515](https://github.com/PHPOffice/PHPWord/pull/2515) - PDF Writer : Documented how to specify a PDF renderer, when working with the PDF writer, as well as the three available choices by [@settermjd](https://github.com/settermjd) in [#2642](https://github.com/PHPOffice/PHPWord/pull/2642) - Word2007 Reader: Support for Paragraph Border Style by [@damienfa](https://github.com/damienfa) in [#2651](https://github.com/PHPOffice/PHPWord/pull/2651) +- Word2007 Writer: Support for field REF by [@crystoline](https://github.com/crystoline) in [#2652](https://github.com/PHPOffice/PHPWord/pull/2652) ### Bug fixes diff --git a/docs/usage/elements/field.md b/docs/usage/elements/field.md index fe8e9756fc..1cafd18ef8 100644 --- a/docs/usage/elements/field.md +++ b/docs/usage/elements/field.md @@ -8,6 +8,7 @@ Currently the following fields are supported: - XE - INDEX - FILENAME +- REF ``` php addText('My '); $fieldText->addText('bold index', ['bold' => true]); $fieldText->addText(' entry'); $section->addField('XE', array(), array(), $fieldText); -//this actually adds the index +// this actually adds the index $section->addField('INDEX', array(), array('\\e " " \\h "A" \\c "3"'), 'right click to update index'); + +// Adding reference to a bookmark +$fieldText->addField('REF', [ + 'name' => 'bookmark' +], [ + 'InsertParagraphNumberRelativeContext', + 'CreateHyperLink', +]); ``` diff --git a/src/PhpWord/Element/Field.php b/src/PhpWord/Element/Field.php index b371bb80d7..a828aaa02e 100644 --- a/src/PhpWord/Element/Field.php +++ b/src/PhpWord/Element/Field.php @@ -91,6 +91,10 @@ class Field extends AbstractElement ], 'options' => ['Path', 'PreserveFormat'], ], + 'REF' => [ + 'properties' => ['name' => ''], + 'options' => ['f', 'h', 'n', 'p', 'r', 't', 'w'], + ], ]; /** diff --git a/src/PhpWord/Writer/Word2007/Element/Field.php b/src/PhpWord/Writer/Word2007/Element/Field.php index 4d7c2a0b46..2977c01626 100644 --- a/src/PhpWord/Writer/Word2007/Element/Field.php +++ b/src/PhpWord/Writer/Word2007/Element/Field.php @@ -17,6 +17,9 @@ namespace PhpOffice\PhpWord\Writer\Word2007\Element; +use PhpOffice\PhpWord\Element\Field as ElementField; +use PhpOffice\PhpWord\Element\TextRun; + /** * Field element writer. * @@ -30,7 +33,7 @@ class Field extends Text public function write(): void { $element = $this->getElement(); - if (!$element instanceof \PhpOffice\PhpWord\Element\Field) { + if (!$element instanceof ElementField) { return; } @@ -42,7 +45,7 @@ public function write(): void } } - private function writeDefault(\PhpOffice\PhpWord\Element\Field $element): void + private function writeDefault(ElementField $element): void { $xmlWriter = $this->getXmlWriter(); $this->startElementP(); @@ -73,7 +76,7 @@ private function writeDefault(\PhpOffice\PhpWord\Element\Field $element): void $xmlWriter->endElement(); // w:r if ($element->getText() != null) { - if ($element->getText() instanceof \PhpOffice\PhpWord\Element\TextRun) { + if ($element->getText() instanceof TextRun) { $containerWriter = new Container($xmlWriter, $element->getText(), true); $containerWriter->write(); @@ -120,7 +123,7 @@ private function writeDefault(\PhpOffice\PhpWord\Element\Field $element): void * * //TODO A lot of code duplication with general method, should maybe be refactored */ - protected function writeMacrobutton(\PhpOffice\PhpWord\Element\Field $element): void + protected function writeMacrobutton(ElementField $element): void { $xmlWriter = $this->getXmlWriter(); $this->startElementP(); @@ -159,7 +162,7 @@ protected function writeMacrobutton(\PhpOffice\PhpWord\Element\Field $element): $this->endElementP(); // w:p } - private function buildPropertiesAndOptions(\PhpOffice\PhpWord\Element\Field $element) + private function buildPropertiesAndOptions(ElementField $element) { $propertiesAndOptions = ''; $properties = $element->getProperties(); @@ -226,4 +229,104 @@ private function buildPropertiesAndOptions(\PhpOffice\PhpWord\Element\Field $ele return $propertiesAndOptions; } + + /** + * Writes a REF field. + */ + protected function writeRef(ElementField $element): void + { + $xmlWriter = $this->getXmlWriter(); + $this->startElementP(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'begin'); + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + + $instruction = ' ' . $element->getType() . ' '; + + foreach ($element->getProperties() as $property) { + $instruction .= $property . ' '; + } + foreach ($element->getOptions() as $optionKey => $optionValue) { + $instruction .= $this->convertRefOption($optionKey, $optionValue) . ' '; + } + + $xmlWriter->startElement('w:r'); + $this->writeFontStyle(); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->text($instruction); + $xmlWriter->endElement(); // w:instrText + $xmlWriter->endElement(); // w:r + + if ($element->getText() != null) { + if ($element->getText() instanceof \PhpOffice\PhpWord\Element\TextRun) { + $containerWriter = new Container($xmlWriter, $element->getText(), true); + $containerWriter->write(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->text('"' . $this->buildPropertiesAndOptions($element)); + $xmlWriter->endElement(); // w:instrText + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->text(' '); + $xmlWriter->endElement(); // w:instrText + $xmlWriter->endElement(); // w:r + } + } + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'separate'); + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:noProof'); + $xmlWriter->endElement(); // w:noProof + $xmlWriter->endElement(); // w:rPr + $xmlWriter->writeElement('w:t', $element->getText() != null && is_string($element->getText()) ? $element->getText() : '1'); + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'end'); + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + + $this->endElementP(); // w:p + } + + private function convertRefOption(string $optionKey, string $optionValue): string + { + if ($optionKey === 'NumberSeperatorSequence') { + return '\\d ' . $optionValue; + } + + switch ($optionValue) { + case 'IncrementAndInsertText': + return '\\f'; + case 'CreateHyperLink': + return '\\h'; + case 'NoTrailingPeriod': + return '\\n'; + case 'IncludeAboveOrBelow': + return '\\p'; + case 'InsertParagraphNumberRelativeContext': + return '\\r'; + case 'SuppressNonDelimiterNonNumericalText': + return '\\t'; + case 'InsertParagraphNumberFullContext': + return '\\w'; + default: + return ''; + } + } } diff --git a/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php b/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php new file mode 100644 index 0000000000..30f875c08c --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php @@ -0,0 +1,73 @@ +addSection(); + $section->addField( + 'REF', + [ + 'name' => 'my-bookmark', + ], + [ + 'InsertParagraphNumberRelativeContext', + 'CreateHyperLink', + ] + ); + + $section->addListItem('line one item'); + $section->addListItem('line two item'); + $section->addBookmark('my-bookmark'); + $section->addListItem('line three item'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $refFieldPath = '/w:document/w:body/w:p[1]/w:r[2]/w:instrText'; + self::assertTrue($doc->elementExists($refFieldPath)); + + $bookMarkElement = $doc->getElement($refFieldPath); + self::assertNotNull($bookMarkElement); + self::assertEquals(' REF my-bookmark \r \h ', $bookMarkElement->textContent); + + $bookmarkPath = '/w:document/w:body/w:bookmarkStart'; + self::assertTrue($doc->elementExists($bookmarkPath)); + self::assertEquals('my-bookmark', $doc->getElementAttribute("$bookmarkPath", 'w:name')); + } +} From 3631936b9e60fe817501bb7922cb220fb9bba74d Mon Sep 17 00:00:00 2001 From: Progi1984 Date: Mon, 12 Aug 2024 14:44:08 +0200 Subject: [PATCH 14/19] Word2007 Reader : Support for FormFields (#2653) * Added code to read FormFields (text input, dropdown and checkbox) from a Word file * Fixed code style issues and added a testcase for reading a FormField of type checkbox * Fixed minor issue found by Scrutinizer * Fixed CI --------- Co-authored-by: Vincent Kool --- docs/changes/2.x/2.0.0.md | 1 + phpstan-baseline.neon | 5 - src/PhpWord/Reader/Word2007/AbstractPart.php | 146 +++++++++++++- .../Reader/Word2007/ElementTest.php | 188 ++++++++++++++++++ 4 files changed, 332 insertions(+), 8 deletions(-) diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 06a7ba3675..e658f1a787 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -8,6 +8,7 @@ - PDF Writer : Documented how to specify a PDF renderer, when working with the PDF writer, as well as the three available choices by [@settermjd](https://github.com/settermjd) in [#2642](https://github.com/PHPOffice/PHPWord/pull/2642) - Word2007 Reader: Support for Paragraph Border Style by [@damienfa](https://github.com/damienfa) in [#2651](https://github.com/PHPOffice/PHPWord/pull/2651) - Word2007 Writer: Support for field REF by [@crystoline](https://github.com/crystoline) in [#2652](https://github.com/PHPOffice/PHPWord/pull/2652) +- Word2007 Reader : Support for FormFields by [@vincentKool](https://github.com/vincentKool) in [#2653](https://github.com/PHPOffice/PHPWord/pull/2653) ### Bug fixes diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 8e47429ad9..026fd8b59a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -235,11 +235,6 @@ parameters: count: 1 path: src/PhpWord/Reader/Word2007/AbstractPart.php - - - message: "#^Parameter \\#1 \\$count of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addTextBreak\\(\\) expects int, null given\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007/AbstractPart.php - - message: "#^Parameter \\#1 \\$depth of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addListItemRun\\(\\) expects int, string\\|null given\\.$#" count: 1 diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 2741841ef3..0f3d8bf711 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -24,6 +24,7 @@ use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType; use PhpOffice\PhpWord\Element\AbstractContainer; use PhpOffice\PhpWord\Element\AbstractElement; +use PhpOffice\PhpWord\Element\FormField; use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\PhpWord; @@ -192,8 +193,44 @@ protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $par // Paragraph style $paragraphStyle = $xmlReader->elementExists('w:pPr', $domNode) ? $this->readParagraphStyle($xmlReader, $domNode) : null; - // PreserveText - if ($xmlReader->elementExists('w:r/w:instrText', $domNode)) { + if ($xmlReader->elementExists('w:r/w:fldChar/w:ffData', $domNode)) { + // FormField + $partOfFormField = false; + $formNodes = []; + $formType = null; + $textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag', $domNode); + if ($textRunContainers > 0) { + $nodes = $xmlReader->getElements('*', $domNode); + $paragraph = $parent->addTextRun($paragraphStyle); + foreach ($nodes as $node) { + if ($xmlReader->elementExists('w:fldChar/w:ffData', $node)) { + $partOfFormField = true; + $formNodes[] = $node; + if ($xmlReader->elementExists('w:fldChar/w:ffData/w:ddList', $node)) { + $formType = 'dropdown'; + } elseif ($xmlReader->elementExists('w:fldChar/w:ffData/w:textInput', $node)) { + $formType = 'textinput'; + } elseif ($xmlReader->elementExists('w:fldChar/w:ffData/w:checkBox', $node)) { + $formType = 'checkbox'; + } + } elseif ($partOfFormField && + $xmlReader->elementExists('w:fldChar', $node) && + 'end' == $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar') + ) { + $formNodes[] = $node; + $partOfFormField = false; + // Process the form fields + $this->readFormField($xmlReader, $formNodes, $paragraph, $paragraphStyle, $formType); + } elseif ($partOfFormField) { + $formNodes[] = $node; + } else { + // normal runs + $this->readRun($xmlReader, $node, $paragraph, $docPart, $paragraphStyle); + } + } + } + } elseif ($xmlReader->elementExists('w:r/w:instrText', $domNode)) { + // PreserveText $ignoreText = false; $textContent = ''; $fontStyle = $this->readFontStyle($xmlReader, $domNode); @@ -272,7 +309,7 @@ protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $par // Text and TextRun $textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag|w:commentReference|w:commentRangeStart|w:commentRangeEnd', $domNode); if (0 === $textRunContainers) { - $parent->addTextBreak(null, $paragraphStyle); + $parent->addTextBreak(1, $paragraphStyle); } else { $nodes = $xmlReader->getElements('*', $domNode); $paragraph = $parent->addTextRun($paragraphStyle); @@ -282,6 +319,109 @@ protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $par } } + /** + * @param DOMElement[] $domNodes + * @param AbstractContainer $parent + * @param mixed $paragraphStyle + * @param string $formType + */ + private function readFormField(XMLReader $xmlReader, array $domNodes, $parent, $paragraphStyle, $formType): void + { + if (!in_array($formType, ['textinput', 'checkbox', 'dropdown'])) { + return; + } + + $formField = $parent->addFormField($formType, null, $paragraphStyle); + $ffData = $xmlReader->getElement('w:fldChar/w:ffData', $domNodes[0]); + + foreach ($xmlReader->getElements('*', $ffData) as $node) { + /** @var DOMElement $node */ + switch ($node->localName) { + case 'name': + $formField->setName($node->getAttribute('w:val')); + + break; + case 'ddList': + $listEntries = []; + foreach ($xmlReader->getElements('*', $node) as $ddListNode) { + switch ($ddListNode->localName) { + case 'result': + $formField->setValue($xmlReader->getAttribute('w:val', $ddListNode)); + + break; + case 'default': + $formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode)); + + break; + case 'listEntry': + $listEntries[] = $xmlReader->getAttribute('w:val', $ddListNode); + + break; + } + } + $formField->setEntries($listEntries); + if (null !== $formField->getValue()) { + $formField->setText($listEntries[$formField->getValue()]); + } + + break; + case 'textInput': + foreach ($xmlReader->getElements('*', $node) as $ddListNode) { + switch ($ddListNode->localName) { + case 'default': + $formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode)); + + break; + case 'format': + case 'maxLength': + break; + } + } + + break; + case 'checkBox': + foreach ($xmlReader->getElements('*', $node) as $ddListNode) { + switch ($ddListNode->localName) { + case 'default': + $formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode)); + + break; + case 'checked': + $formField->setValue($xmlReader->getAttribute('w:val', $ddListNode)); + + break; + case 'size': + case 'sizeAuto': + break; + } + } + + break; + } + } + + if ('textinput' == $formType) { + $ignoreText = true; + $textContent = ''; + foreach ($domNodes as $node) { + if ($xmlReader->elementExists('w:fldChar', $node)) { + $fldCharType = $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar'); + if ('separate' == $fldCharType) { + $ignoreText = false; + } elseif ('end' == $fldCharType) { + $ignoreText = true; + } + } + + if (false === $ignoreText) { + $textContent .= $xmlReader->getValue('w:t', $node); + } + } + $formField->setValue(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8')); + $formField->setText(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8')); + } + } + /** * Returns the depth of the Heading, returns 0 for a Title. * diff --git a/tests/PhpWordTests/Reader/Word2007/ElementTest.php b/tests/PhpWordTests/Reader/Word2007/ElementTest.php index f96d8ac1a2..3685cbea03 100644 --- a/tests/PhpWordTests/Reader/Word2007/ElementTest.php +++ b/tests/PhpWordTests/Reader/Word2007/ElementTest.php @@ -355,4 +355,192 @@ public function testReadDrawing(): void $elements = $phpWord->getSection(0)->getElements(); self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); } + + /** + * Test reading FormField - DROPDOWN. + */ + public function testReadFormFieldDropdown(): void + { + $documentXml = ' + + Reference + + + + + + + + + + + + + + + + + + + + FORMDROPDOWN + + + + + + + + + + + + + + + + + + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + + $subElements = $elements[0]->getElements(); + + self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $subElements[0]); + self::assertEquals('Reference', $subElements[0]->getText()); + + self::assertInstanceOf('PhpOffice\PhpWord\Element\FormField', $subElements[1]); + self::assertEquals('dropdown', $subElements[1]->getType()); + self::assertEquals('DropDownList1', $subElements[1]->getName()); + self::assertEquals('2', $subElements[1]->getValue()); + self::assertEquals('Option Two', $subElements[1]->getText()); + self::assertEquals(['TBD', 'Option One', 'Option Two', 'Option Three', 'Other'], $subElements[1]->getEntries()); + } + + /** + * Test reading FormField - textinput. + */ + public function testReadFormFieldTextinput(): void + { + $documentXml = ' + + Fieldname + + + + + + + + + + + + + + + + FORMTEXT + + + + + + + + + + + + + + + + + + This is some sample text + + + + + + + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + + $subElements = $elements[0]->getElements(); + + self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $subElements[0]); + self::assertEquals('Fieldname', $subElements[0]->getText()); + + self::assertInstanceOf('PhpOffice\PhpWord\Element\FormField', $subElements[1]); + self::assertEquals('textinput', $subElements[1]->getType()); + self::assertEquals('TextInput2', $subElements[1]->getName()); + self::assertEquals('This is some sample text', $subElements[1]->getValue()); + self::assertEquals('This is some sample text', $subElements[1]->getText()); + } + + /** + * Test reading FormField - checkbox. + */ + public function testReadFormFieldCheckbox(): void + { + $documentXml = ' + + + + + + + + + + + + + + + + + + FORMCHECKBOX + + + + + + + + + + + + + + '; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + + $subElements = $elements[0]->getElements(); + +// $this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $subElements[0]); +// $this->assertEquals('Fieldname', $subElements[0]->getText()); + + self::assertInstanceOf('PhpOffice\PhpWord\Element\FormField', $subElements[0]); + self::assertEquals('checkbox', $subElements[0]->getType()); + self::assertEquals('SomeCheckbox', $subElements[0]->getName()); + } } From fdf1343701f76d3eda7a3e312a01859362acfe69 Mon Sep 17 00:00:00 2001 From: Progi1984 Date: Mon, 12 Aug 2024 16:07:06 +0200 Subject: [PATCH 15/19] HTML Reader : Read width & height attributes in points (#2654) --- composer.lock | 4 +- docs/changes/2.x/2.0.0.md | 1 + src/PhpWord/Shared/Html.php | 22 ++++++++++ tests/PhpWordTests/Shared/HtmlTest.php | 60 ++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index a803691ad2..d6b3ee35d8 100644 --- a/composer.lock +++ b/composer.lock @@ -5067,7 +5067,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "phpstan/phpstan-phpunit": 0 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index e658f1a787..19fc9f35e2 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -22,6 +22,7 @@ - Typo : Fix hardcoded macro chars in TemplateProcessor method [@glafarge](https://github.com/glafarge) in [#2618](https://github.com/PHPOffice/PHPWord/pull/2618) - XML Reader : Prevent fatal errors when opening corrupt files or "doc" files [@mmcev106](https://github.com/mmcev106) in [#2626](https://github.com/PHPOffice/PHPWord/pull/2626) - Documentation : Updated Comment element by [@laminga](https://github.com/laminga) in [#2650](https://github.com/PHPOffice/PHPWord/pull/2650) +- HTML Reader : Read width & height attributes in points fixing [#2589](https://github.com/PHPOffice/PHPWord/issues/2589) by [@Progi1984](https://github.com/Progi1984) in [#2654](https://github.com/PHPOffice/PHPWord/pull/2654) ### Miscellaneous diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index e38f8be910..602b74ccbb 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -902,12 +902,34 @@ protected static function parseImage($node, $element) break; case 'width': $width = $attribute->value; + + // pt + if (false !== strpos($width, 'pt')) { + $width = Converter::pointToPixel((float) str_replace('pt', '', $width)); + } + + // px + if (false !== strpos($width, 'px')) { + $width = str_replace('px', '', $width); + } + $style['width'] = $width; $style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX; break; case 'height': $height = $attribute->value; + + // pt + if (false !== strpos($height, 'pt')) { + $height = Converter::pointToPixel((float) str_replace('pt', '', $height)); + } + + // px + if (false !== strpos($height, 'px')) { + $height = str_replace('px', '', $height); + } + $style['height'] = $height; $style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX; diff --git a/tests/PhpWordTests/Shared/HtmlTest.php b/tests/PhpWordTests/Shared/HtmlTest.php index c8640509de..8144eb6f72 100644 --- a/tests/PhpWordTests/Shared/HtmlTest.php +++ b/tests/PhpWordTests/Shared/HtmlTest.php @@ -806,6 +806,66 @@ public function testParseImage(): void self::assertStringMatchesFormat('%Smso-position-horizontal:left%S', $doc->getElementAttribute($baseXpath . '[2]/w:pict/v:shape', 'style')); } + /** + * Test parsing of img. + */ + public function testParseImageSizeInPixels(): void + { + $src = __DIR__ . '/../_files/images/firefox.png'; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

'; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $baseXpath = '/w:document/w:body/w:p/w:r'; + self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape')); + self::assertStringMatchesFormat('%Swidth:150px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style')); + self::assertStringMatchesFormat('%Sheight:200px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style')); + } + + /** + * Test parsing of img. + */ + public function testParseImageSizeInPoints(): void + { + $src = __DIR__ . '/../_files/images/firefox.png'; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

'; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $baseXpath = '/w:document/w:body/w:p/w:r'; + self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape')); + self::assertStringMatchesFormat('%Swidth:200px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style')); + self::assertStringMatchesFormat('%Sheight:266.66666666667%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style')); + } + + /** + * Test parsing of img. + */ + public function testParseImageSizeWithoutUnits(): void + { + $src = __DIR__ . '/../_files/images/firefox.png'; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

'; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $baseXpath = '/w:document/w:body/w:p/w:r'; + self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape')); + self::assertStringMatchesFormat('%Swidth:150px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style')); + self::assertStringMatchesFormat('%Sheight:200px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style')); + } + /** * Test parsing of remote img. */ From c917d039b510df5173337add96ed03926d9672b8 Mon Sep 17 00:00:00 2001 From: Progi1984 Date: Tue, 13 Aug 2024 10:21:36 +0200 Subject: [PATCH 16/19] Template Processor : Fixed bad naming of variables (#2655) --- docs/changes/2.x/2.0.0.md | 1 + src/PhpWord/TemplateProcessor.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 19fc9f35e2..6638178d91 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -23,6 +23,7 @@ - XML Reader : Prevent fatal errors when opening corrupt files or "doc" files [@mmcev106](https://github.com/mmcev106) in [#2626](https://github.com/PHPOffice/PHPWord/pull/2626) - Documentation : Updated Comment element by [@laminga](https://github.com/laminga) in [#2650](https://github.com/PHPOffice/PHPWord/pull/2650) - HTML Reader : Read width & height attributes in points fixing [#2589](https://github.com/PHPOffice/PHPWord/issues/2589) by [@Progi1984](https://github.com/Progi1984) in [#2654](https://github.com/PHPOffice/PHPWord/pull/2654) +- Template Processor : Fixed bad naming of variables fixing [#2586](https://github.com/PHPOffice/PHPWord/issues/2586) by [@Progi1984](https://github.com/Progi1984) in [#2655](https://github.com/PHPOffice/PHPWord/pull/2655) ### Miscellaneous diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 900169bd68..8bef424fd1 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -662,8 +662,8 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM foreach (array_keys($this->tempDocumentHeaders) as $headerIndex) { $searchParts[$this->getHeaderName($headerIndex)] = &$this->tempDocumentHeaders[$headerIndex]; } - foreach (array_keys($this->tempDocumentFooters) as $headerIndex) { - $searchParts[$this->getFooterName($headerIndex)] = &$this->tempDocumentFooters[$headerIndex]; + foreach (array_keys($this->tempDocumentFooters) as $footerIndex) { + $searchParts[$this->getFooterName($footerIndex)] = &$this->tempDocumentFooters[$footerIndex]; } // define templates From 169130124e07254103356d0ccdd7082191a7acfa Mon Sep 17 00:00:00 2001 From: Progi1984 Date: Tue, 13 Aug 2024 15:14:29 +0200 Subject: [PATCH 17/19] RTF Writer : Support for Table Border Style (#2656) --- docs/changes/2.x/2.0.0.md | 1 + src/PhpWord/Style/Border.php | 28 +-- src/PhpWord/Writer/HTML/Element/Table.php | 4 +- .../Writer/RTF/Element/AbstractElement.php | 6 +- src/PhpWord/Writer/RTF/Element/Table.php | 114 ++++++++++++ src/PhpWord/Writer/RTF/Part/Header.php | 9 + .../Writer/Word2007/Style/MarginBorder.php | 2 +- .../Writer/RTF/Element/TableTest.php | 169 ++++++++++++++++++ .../PhpWordTests/Writer/RTF/Element2Test.php | 46 +---- 9 files changed, 313 insertions(+), 66 deletions(-) create mode 100644 tests/PhpWordTests/Writer/RTF/Element/TableTest.php diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 6638178d91..219db0c939 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -9,6 +9,7 @@ - Word2007 Reader: Support for Paragraph Border Style by [@damienfa](https://github.com/damienfa) in [#2651](https://github.com/PHPOffice/PHPWord/pull/2651) - Word2007 Writer: Support for field REF by [@crystoline](https://github.com/crystoline) in [#2652](https://github.com/PHPOffice/PHPWord/pull/2652) - Word2007 Reader : Support for FormFields by [@vincentKool](https://github.com/vincentKool) in [#2653](https://github.com/PHPOffice/PHPWord/pull/2653) +- RTF Writer : Support for Table Border Style fixing [#345](https://github.com/PHPOffice/PHPWord/issues/345) by [@Progi1984](https://github.com/Progi1984) in [#2656](https://github.com/PHPOffice/PHPWord/pull/2656) ### Bug fixes diff --git a/src/PhpWord/Style/Border.php b/src/PhpWord/Style/Border.php index 28e340c040..e2a56c5d21 100644 --- a/src/PhpWord/Style/Border.php +++ b/src/PhpWord/Style/Border.php @@ -34,7 +34,7 @@ class Border extends AbstractStyle /** * Border Top Color. * - * @var string + * @var null|string */ protected $borderTopColor; @@ -55,7 +55,7 @@ class Border extends AbstractStyle /** * Border Left Color. * - * @var string + * @var null|string */ protected $borderLeftColor; @@ -76,7 +76,7 @@ class Border extends AbstractStyle /** * Border Right Color. * - * @var string + * @var null|string */ protected $borderRightColor; @@ -97,7 +97,7 @@ class Border extends AbstractStyle /** * Border Bottom Color. * - * @var string + * @var null|string */ protected $borderBottomColor; @@ -171,7 +171,7 @@ public function setBorderSize($value = null) /** * Get border color. * - * @return string[] + * @return array */ public function getBorderColor() { @@ -186,7 +186,7 @@ public function getBorderColor() /** * Set border color. * - * @param string $value + * @param null|string $value * * @return self */ @@ -259,7 +259,7 @@ public function setBorderTopSize($value = null) /** * Get border top color. * - * @return string + * @return null|string */ public function getBorderTopColor() { @@ -269,7 +269,7 @@ public function getBorderTopColor() /** * Set border top color. * - * @param string $value + * @param null|string $value * * @return self */ @@ -331,7 +331,7 @@ public function setBorderLeftSize($value = null) /** * Get border left color. * - * @return string + * @return null|string */ public function getBorderLeftColor() { @@ -341,7 +341,7 @@ public function getBorderLeftColor() /** * Set border left color. * - * @param string $value + * @param null|string $value * * @return self */ @@ -403,7 +403,7 @@ public function setBorderRightSize($value = null) /** * Get border right color. * - * @return string + * @return null|string */ public function getBorderRightColor() { @@ -413,7 +413,7 @@ public function getBorderRightColor() /** * Set border right color. * - * @param string $value + * @param null|string $value * * @return self */ @@ -475,7 +475,7 @@ public function setBorderBottomSize($value = null) /** * Get border bottom color. * - * @return string + * @return null|string */ public function getBorderBottomColor() { @@ -485,7 +485,7 @@ public function getBorderBottomColor() /** * Set border bottom color. * - * @param string $value + * @param null|string $value * * @return self */ diff --git a/src/PhpWord/Writer/HTML/Element/Table.php b/src/PhpWord/Writer/HTML/Element/Table.php index c7a23d2fe1..f4acc8cdf4 100644 --- a/src/PhpWord/Writer/HTML/Element/Table.php +++ b/src/PhpWord/Writer/HTML/Element/Table.php @@ -120,9 +120,7 @@ private function getTableStyle($tableStyle = null): string return ''; } if (is_string($tableStyle)) { - $style = ' class="' . $tableStyle; - - return $style . '"'; + return ' class="' . $tableStyle . '"'; } $styleWriter = new TableStyleWriter($tableStyle); diff --git a/src/PhpWord/Writer/RTF/Element/AbstractElement.php b/src/PhpWord/Writer/RTF/Element/AbstractElement.php index dedbc8bfa1..5c33868a8b 100644 --- a/src/PhpWord/Writer/RTF/Element/AbstractElement.php +++ b/src/PhpWord/Writer/RTF/Element/AbstractElement.php @@ -24,7 +24,7 @@ use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font as FontStyle; use PhpOffice\PhpWord\Style\Paragraph as ParagraphStyle; -use PhpOffice\PhpWord\Writer\AbstractWriter; +use PhpOffice\PhpWord\Writer\RTF as WriterRTF; use PhpOffice\PhpWord\Writer\RTF\Style\Font as FontStyleWriter; use PhpOffice\PhpWord\Writer\RTF\Style\Paragraph as ParagraphStyleWriter; @@ -38,7 +38,7 @@ abstract class AbstractElement /** * Parent writer. * - * @var \PhpOffice\PhpWord\Writer\AbstractWriter + * @var WriterRTF */ protected $parentWriter; @@ -82,7 +82,7 @@ abstract public function write(); */ protected $escaper; - public function __construct(AbstractWriter $parentWriter, Element $element, bool $withoutP = false) + public function __construct(WriterRTF $parentWriter, Element $element, bool $withoutP = false) { $this->parentWriter = $parentWriter; $this->element = $element; diff --git a/src/PhpWord/Writer/RTF/Element/Table.php b/src/PhpWord/Writer/RTF/Element/Table.php index b7b370bd5a..f2d5f9fb5a 100644 --- a/src/PhpWord/Writer/RTF/Element/Table.php +++ b/src/PhpWord/Writer/RTF/Element/Table.php @@ -21,7 +21,10 @@ use PhpOffice\PhpWord\Element\Row as RowElement; use PhpOffice\PhpWord\Element\Table as TableElement; use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\SimpleType\Border; use PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\Style\Cell as CellStyle; +use PhpOffice\PhpWord\Style\Table as TableStyle; /** * Table element RTF writer. @@ -30,6 +33,11 @@ */ class Table extends AbstractElement { + /** + * @var TableElement + */ + protected $element; + /** * Write element. * @@ -77,9 +85,18 @@ public function write() private function writeRowDef(RowElement $row) { $content = ''; + $tableStyle = $this->element->getStyle(); + if (is_string($tableStyle)) { + $tableStyle = Style::getStyle($tableStyle); + if (!($tableStyle instanceof TableStyle)) { + $tableStyle = null; + } + } $rightMargin = 0; foreach ($row->getCells() as $cell) { + $content .= $this->writeCellStyle($cell->getStyle(), $tableStyle); + $width = $cell->getWidth(); $vMerge = $this->getVMerge($cell->getStyle()->getVMerge()); if ($width === null) { @@ -127,6 +144,103 @@ private function writeCell(CellElement $cell) return $content; } + private function writeCellStyle(CellStyle $cell, ?TableStyle $table): string + { + $content = $this->writeCellBorder( + 't', + $cell->getBorderTopStyle() ?: ($table ? $table->getBorderTopStyle() : null), + (int) round($cell->getBorderTopSize() ?: ($table ? ($table->getBorderTopSize() ?: 0) : 0)), + $cell->getBorderTopColor() ?? ($table ? $table->getBorderTopColor() : null) + ); + $content .= $this->writeCellBorder( + 'l', + $cell->getBorderLeftStyle() ?: ($table ? $table->getBorderLeftStyle() : null), + (int) round($cell->getBorderLeftSize() ?: ($table ? ($table->getBorderLeftSize() ?: 0) : 0)), + $cell->getBorderLeftColor() ?? ($table ? $table->getBorderLeftColor() : null) + ); + $content .= $this->writeCellBorder( + 'b', + $cell->getBorderBottomStyle() ?: ($table ? $table->getBorderBottomStyle() : null), + (int) round($cell->getBorderBottomSize() ?: ($table ? ($table->getBorderBottomSize() ?: 0) : 0)), + $cell->getBorderBottomColor() ?? ($table ? $table->getBorderBottomColor() : null) + ); + $content .= $this->writeCellBorder( + 'r', + $cell->getBorderRightStyle() ?: ($table ? $table->getBorderRightStyle() : null), + (int) round($cell->getBorderRightSize() ?: ($table ? ($table->getBorderRightSize() ?: 0) : 0)), + $cell->getBorderRightColor() ?? ($table ? $table->getBorderRightColor() : null) + ); + + return $content; + } + + private function writeCellBorder(string $prefix, ?string $borderStyle, int $borderSize, ?string $borderColor): string + { + if ($borderSize == 0) { + return ''; + } + + $content = '\clbrdr' . $prefix; + /** + * \brdrs Single-thickness border. + * \brdrth Double-thickness border. + * \brdrsh Shadowed border. + * \brdrdb Double border. + * \brdrdot Dotted border. + * \brdrdash Dashed border. + * \brdrhair Hairline border. + * \brdrinset Inset border. + * \brdrdashsm Dash border (small). + * \brdrdashd Dot dash border. + * \brdrdashdd Dot dot dash border. + * \brdroutset Outset border. + * \brdrtriple Triple border. + * \brdrtnthsg Thick thin border (small). + * \brdrthtnsg Thin thick border (small). + * \brdrtnthtnsg Thin thick thin border (small). + * \brdrtnthmg Thick thin border (medium). + * \brdrthtnmg Thin thick border (medium). + * \brdrtnthtnmg Thin thick thin border (medium). + * \brdrtnthlg Thick thin border (large). + * \brdrthtnlg Thin thick border (large). + * \brdrtnthtnlg Thin thick thin border (large). + * \brdrwavy Wavy border. + * \brdrwavydb Double wavy border. + * \brdrdashdotstr Striped border. + * \brdremboss Emboss border. + * \brdrengrave Engrave border. + */ + switch ($borderStyle) { + case Border::DOTTED: + $content .= '\brdrdot'; + + break; + case Border::SINGLE: + default: + $content .= '\brdrs'; + + break; + } + + // \brdrwN N is the width in twips (1/20 pt) of the pen used to draw the paragraph border line. + // N cannot be greater than 75. + // To obtain a larger border width, the \brdth control word can be used to obtain a width double that of N. + // $borderSize is in eights of a point, i.e. 4 / 8 = .5pt + // 1/20 pt => 1/8 / 2.5 + $content .= '\brdrw' . (int) ($borderSize / 2.5); + + // \brdrcfN N is the color of the paragraph border, specified as an index into the color table in the RTF header. + $colorIndex = 0; + $index = array_search($borderColor, $this->parentWriter->getColorTable()); + if ($index !== false) { + $colorIndex = (int) $index + 1; + } + $content .= '\brdrcf' . $colorIndex; + $content .= PHP_EOL; + + return $content; + } + /** * Get vertical merge style. * diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index e4ad4bee0b..7f8cc84b97 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -21,6 +21,7 @@ use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; +use PhpOffice\PhpWord\Style\Table; /** * RTF header part writer. @@ -236,6 +237,14 @@ private function registerFontItems($style): void $this->registerTableItem($this->fontTable, $style->getName(), $defaultFont); $this->registerTableItem($this->colorTable, $style->getColor(), $defaultColor); $this->registerTableItem($this->colorTable, $style->getFgColor(), $defaultColor); + + return; + } + if ($style instanceof Table) { + $this->registerTableItem($this->colorTable, $style->getBorderTopColor(), $defaultColor); + $this->registerTableItem($this->colorTable, $style->getBorderRightColor(), $defaultColor); + $this->registerTableItem($this->colorTable, $style->getBorderLeftColor(), $defaultColor); + $this->registerTableItem($this->colorTable, $style->getBorderBottomColor(), $defaultColor); } } diff --git a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php index 8d08eec3cc..ce250e54d4 100644 --- a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php +++ b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php @@ -120,7 +120,7 @@ public function setSizes($value): void /** * Set colors. * - * @param string[] $value + * @param array $value */ public function setColors($value): void { diff --git a/tests/PhpWordTests/Writer/RTF/Element/TableTest.php b/tests/PhpWordTests/Writer/RTF/Element/TableTest.php new file mode 100644 index 0000000000..35cdfbec28 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Element/TableTest.php @@ -0,0 +1,169 @@ +write()); + } + + public function testTable(): void + { + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Table(); + $width = 100; + $width2 = 2 * $width; + $element->addRow(); + $tce = $element->addCell($width); + $tce->addText('1'); + $tce = $element->addCell($width); + $tce->addText('2'); + $element->addRow(); + $tce = $element->addCell($width); + $tce->addText('3'); + $tce = $element->addCell($width); + $tce->addText('4'); + $table = new WriterTable($parentWriter, $element); + $expect = implode("\n", [ + '\\pard', + "\\trowd \\cellx$width \\cellx$width2 ", + '\\intbl', + '\\ql{\\cf0\\f0 1}\\par', + '\\cell', + '\\intbl', + '{\\cf0\\f0 2}\\par', + '\\cell', + '\\row', + "\\trowd \\cellx$width \\cellx$width2 ", + '\\intbl', + '\\ql{\\cf0\\f0 3}\\par', + '\\cell', + '\\intbl', + '{\\cf0\\f0 4}\par', + '\\cell', + '\\row', + '\\pard', + '', + ]); + + self::assertEquals($expect, $this->removeCr($table)); + } + + public function testTableStyle(): void + { + $width = 100; + + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + + Style::addTableStyle('TableStyle', ['borderSize' => 6, 'borderColor' => '006699']); + + $element = new Table('TableStyle'); + $element->addRow(); + $elementCell = $element->addCell($width); + $elementCell->addText('1'); + + $expect = implode("\n", [ + '\\pard', + '\\trowd \\clbrdrt\\brdrs\\brdrw2\\brdrcf0', + '\\clbrdrl\\brdrs\\brdrw2\\brdrcf0', + '\\clbrdrb\\brdrs\\brdrw2\\brdrcf0', + '\\clbrdrr\\brdrs\\brdrw2\\brdrcf0', + "\\cellx$width ", + '\\intbl', + '\\ql{\\cf0\\f0 1}\\par', + '\\cell', + '\\row', + '\\pard', + '', + ]); + + self::assertEquals($expect, $this->removeCr(new WriterTable($parentWriter, $element))); + } + + public function testTableStyleNotExisting(): void + { + $width = 100; + + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + + $element = new Table('TableStyleNotExisting'); + $element->addRow(); + $elementCell = $element->addCell($width); + $elementCell->addText('1'); + + $expect = implode("\n", [ + '\\pard', + "\\trowd \\cellx$width ", + '\\intbl', + '\\ql{\\cf0\\f0 1}\\par', + '\\cell', + '\\row', + '\\pard', + '', + ]); + + self::assertEquals($expect, $this->removeCr(new WriterTable($parentWriter, $element))); + } + + public function testTableCellStyle(): void + { + $width = 100; + + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + + $element = new Table(); + $element->addRow(); + $elementCell = $element->addCell($width, ['borderSize' => 6, 'borderColor' => '006699', 'borderStyle' => Border::DOTTED]); + $elementCell->addText('1'); + + $expect = implode("\n", [ + '\\pard', + '\\trowd \\clbrdrt\\brdrdot\\brdrw2\\brdrcf0', + '\\clbrdrl\\brdrdot\\brdrw2\\brdrcf0', + '\\clbrdrb\\brdrdot\\brdrw2\\brdrcf0', + '\\clbrdrr\\brdrdot\\brdrw2\\brdrcf0', + "\\cellx$width ", + '\\intbl', + '\\ql{\\cf0\\f0 1}\\par', + '\\cell', + '\\row', + '\\pard', + '', + ]); + + self::assertEquals($expect, $this->removeCr(new WriterTable($parentWriter, $element))); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Element2Test.php b/tests/PhpWordTests/Writer/RTF/Element2Test.php index d82a3095d7..7d36fac869 100644 --- a/tests/PhpWordTests/Writer/RTF/Element2Test.php +++ b/tests/PhpWordTests/Writer/RTF/Element2Test.php @@ -19,7 +19,6 @@ use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Writer\RTF; -use PhpOffice\PhpWord\Writer\RTF\Element\Table as WriterTable; use PhpOffice\PhpWord\Writer\RTF\Element\TextRun as WriterTextRun; use PhpOffice\PhpWord\Writer\RTF\Element\Title as WriterTitle; @@ -33,55 +32,12 @@ protected function tearDown(): void Settings::setDefaultRtl(null); } - /** @param WriterTable|WriterTextRun|WriterTitle $field */ + /** @param WriterTextRun|WriterTitle $field */ public function removeCr($field): string { return str_replace("\r\n", "\n", $field->write()); } - public function testTable(): void - { - Settings::setDefaultRtl(false); - $parentWriter = new RTF(); - $element = new \PhpOffice\PhpWord\Element\Table(); - $width = 100; - $width2 = 2 * $width; - $element->addRow(); - $tce = $element->addCell($width); - $tce->addText('1'); - $tce = $element->addCell($width); - $tce->addText('2'); - $element->addRow(); - $tce = $element->addCell($width); - $tce->addText('3'); - $tce = $element->addCell($width); - $tce->addText('4'); - $table = new WriterTable($parentWriter, $element); - $expect = implode("\n", [ - '\\pard', - "\\trowd \\cellx$width \\cellx$width2 ", - '\\intbl', - '\\ql{\\cf0\\f0 1}\\par', - '\\cell', - '\\intbl', - '{\\cf0\\f0 2}\\par', - '\\cell', - '\\row', - "\\trowd \\cellx$width \\cellx$width2 ", - '\\intbl', - '\\ql{\\cf0\\f0 3}\\par', - '\\cell', - '\\intbl', - '{\\cf0\\f0 4}\par', - '\\cell', - '\\row', - '\\pard', - '', - ]); - - self::assertEquals($expect, $this->removeCr($table)); - } - public function testTextRun(): void { Settings::setDefaultRtl(false); From dbf0a3e427bceb5744b760c23aeb7ddf558803d3 Mon Sep 17 00:00:00 2001 From: Jack Sleight Date: Wed, 14 Aug 2024 03:50:25 +0100 Subject: [PATCH 18/19] Word2007 Writer : Fixed first footnote appearing as separator (#2635) * Fix first footnote appearing as separator * Add test * Update changelog * Fixed changelog --------- Co-authored-by: Progi1984 --- docs/changes/2.x/2.0.0.md | 1 + src/PhpWord/Writer/Word2007/Element/Footnote.php | 2 +- src/PhpWord/Writer/Word2007/Part/Footnotes.php | 2 +- tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php | 3 +++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 219db0c939..121ce9bb4c 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -25,6 +25,7 @@ - Documentation : Updated Comment element by [@laminga](https://github.com/laminga) in [#2650](https://github.com/PHPOffice/PHPWord/pull/2650) - HTML Reader : Read width & height attributes in points fixing [#2589](https://github.com/PHPOffice/PHPWord/issues/2589) by [@Progi1984](https://github.com/Progi1984) in [#2654](https://github.com/PHPOffice/PHPWord/pull/2654) - Template Processor : Fixed bad naming of variables fixing [#2586](https://github.com/PHPOffice/PHPWord/issues/2586) by [@Progi1984](https://github.com/Progi1984) in [#2655](https://github.com/PHPOffice/PHPWord/pull/2655) +- Word2007 Writer : Fix first footnote appearing as separator [#2634](https://github.com/PHPOffice/PHPWord/issues/2634) by [@jacksleight](https://github.com/jacksleight) in [#2635](https://github.com/PHPOffice/PHPWord/pull/2635) ### Miscellaneous diff --git a/src/PhpWord/Writer/Word2007/Element/Footnote.php b/src/PhpWord/Writer/Word2007/Element/Footnote.php index b59fc5e1cc..77073a239d 100644 --- a/src/PhpWord/Writer/Word2007/Element/Footnote.php +++ b/src/PhpWord/Writer/Word2007/Element/Footnote.php @@ -51,7 +51,7 @@ public function write(): void $xmlWriter->endElement(); // w:rStyle $xmlWriter->endElement(); // w:rPr $xmlWriter->startElement("w:{$this->referenceType}"); - $xmlWriter->writeAttribute('w:id', $element->getRelationId()); + $xmlWriter->writeAttribute('w:id', $element->getRelationId() + 1); $xmlWriter->endElement(); // w:$referenceType $xmlWriter->endElement(); // w:r diff --git a/src/PhpWord/Writer/Word2007/Part/Footnotes.php b/src/PhpWord/Writer/Word2007/Part/Footnotes.php index 9624ab7459..e284c674b5 100644 --- a/src/PhpWord/Writer/Word2007/Part/Footnotes.php +++ b/src/PhpWord/Writer/Word2007/Part/Footnotes.php @@ -141,7 +141,7 @@ public function setElements($elements) protected function writeNote(XMLWriter $xmlWriter, $element): void { $xmlWriter->startElement($this->elementNode); - $xmlWriter->writeAttribute('w:id', $element->getRelationId()); + $xmlWriter->writeAttribute('w:id', $element->getRelationId() + 1); $xmlWriter->startElement('w:p'); // Paragraph style diff --git a/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php b/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php index 14872970a1..526d3591e8 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php @@ -49,5 +49,8 @@ public function testWriteFootnotes(): void self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:footnoteReference')); self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:endnoteReference')); + + self::assertFalse($doc->elementExists('/w:document/w:body/w:p/w:r/w:footnoteReference[@w:id="0"]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:footnoteReference[@w:id="1"]')); } } From a7b90305a3379f0d469dd2ad063f6feaf7adeb44 Mon Sep 17 00:00:00 2001 From: Elwyn Van der Borght <38124619+ElwynVdb@users.noreply.github.com> Date: Thu, 15 Aug 2024 16:26:15 +0200 Subject: [PATCH 19/19] Fix transparent background images to be displayed correctly (#2638) * fix(TemplateProcessor): Images with transparent backgrounds are now correctly displayed with transparency. * docs: Updated changelog * docs: Name --- docs/changes/2.x/2.0.0.md | 1 + src/PhpWord/TemplateProcessor.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 121ce9bb4c..f1e5583e7c 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -26,6 +26,7 @@ - HTML Reader : Read width & height attributes in points fixing [#2589](https://github.com/PHPOffice/PHPWord/issues/2589) by [@Progi1984](https://github.com/Progi1984) in [#2654](https://github.com/PHPOffice/PHPWord/pull/2654) - Template Processor : Fixed bad naming of variables fixing [#2586](https://github.com/PHPOffice/PHPWord/issues/2586) by [@Progi1984](https://github.com/Progi1984) in [#2655](https://github.com/PHPOffice/PHPWord/pull/2655) - Word2007 Writer : Fix first footnote appearing as separator [#2634](https://github.com/PHPOffice/PHPWord/issues/2634) by [@jacksleight](https://github.com/jacksleight) in [#2635](https://github.com/PHPOffice/PHPWord/pull/2635) +- Template Processor : Fixed images with transparent backgrounds displaying a white background by [@ElwynVdb](https://github.com/ElwynVdb) in [#2638](https://github.com/PHPOffice/PHPWord/pull/2638) ### Miscellaneous diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 8bef424fd1..f6fe1d8887 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -668,7 +668,7 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM // define templates // result can be verified via "Open XML SDK 2.5 Productivity Tool" (http://www.microsoft.com/en-us/download/details.aspx?id=30425) - $imgTpl = ''; + $imgTpl = ''; $i = 0; foreach ($searchParts as $partFileName => &$partContent) {