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..d6b3ee35d8 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": [ @@ -766,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": { @@ -843,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", @@ -939,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": { @@ -956,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", @@ -986,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": [ { @@ -994,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", @@ -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", @@ -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", @@ -2290,16 +2286,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 +2303,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 +2326,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 +2344,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", @@ -4936,20 +4944,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": { @@ -4996,7 +5004,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": [ { @@ -5004,7 +5012,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 c5fbaf4b52..f1e5583e7c 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -5,14 +5,28 @@ ## 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) +- 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 - 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) +- 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) +- 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 @@ -24,5 +38,10 @@ - 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) +- 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/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 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/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`. diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e07918f6b6..a2f9112ce4 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 @@ -240,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 @@ -385,11 +375,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 @@ -515,16 +500,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 @@ -1105,11 +1080,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 @@ -1130,11 +1100,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 @@ -1225,11 +1190,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 @@ -1810,11 +1770,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/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/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/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 a92e6d5958..ba8a6fed56 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. * @@ -529,6 +669,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/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index f64dc30466..1b8d699f45 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -105,7 +105,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 */ @@ -114,7 +114,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)) { @@ -162,15 +164,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'); @@ -328,10 +330,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); @@ -427,7 +429,7 @@ protected static function parseTable($node, $element, &$styles) $attributes = $node->attributes; if ($attributes->getNamedItem('border') !== null && is_object($newElement->getStyle())) { - $border = (int) $attributes->getNamedItem('border')->value; + $border = (int) $attributes->getNamedItem('border')->nodeValue; $newElement->getStyle()->setBorderSize((int) Converter::pixelToTwip($border)); $newElement->getStyle()->setBorderStyle(($border === 0) ? 'none' : 'single'); } @@ -921,12 +923,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/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/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/src/PhpWord/Style/Border.php b/src/PhpWord/Style/Border.php index 8be7298840..6224b99840 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/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 8aee40c546..f6fe1d8887 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -662,13 +662,13 @@ 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 // 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) { @@ -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); diff --git a/src/PhpWord/Writer/HTML/Element/Table.php b/src/PhpWord/Writer/HTML/Element/Table.php index 742d09ffd5..72b82d9872 100644 --- a/src/PhpWord/Writer/HTML/Element/Table.php +++ b/src/PhpWord/Writer/HTML/Element/Table.php @@ -121,9 +121,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/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/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/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/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/src/PhpWord/Writer/Word2007/Style/MarginBorder.php b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php index 15cf0e4eeb..ed17582f32 100644 --- a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php +++ b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php @@ -118,7 +118,7 @@ public function setSizes($value): void /** * Set colors. * - * @param string[] $value + * @param array $value */ public function setColors($value): void { 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()); + } } 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/Shared/HtmlTest.php b/tests/PhpWordTests/Shared/HtmlTest.php index 29810c15cf..bcd89ffe4a 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. */ 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. */ 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 + } } /** 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); 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')); + } +} 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'); + } +} 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"]')); } } diff --git a/tests/PhpWordTests/_files/documents/reader-styles.docx b/tests/PhpWordTests/_files/documents/reader-styles.docx new file mode 100644 index 0000000000..b02cf73c5f Binary files /dev/null and b/tests/PhpWordTests/_files/documents/reader-styles.docx differ