From a0569140aabe24e64e94a5d27713cc9eaa0d4949 Mon Sep 17 00:00:00 2001 From: Gordon Franke Date: Thu, 7 Dec 2023 08:52:02 +0100 Subject: [PATCH 1/7] bug: TemplateProcessor fix multiline values --- docs/changes/2.x/2.0.0.md | 2 ++ src/PhpWord/TemplateProcessor.php | 17 +++++++++++++++++ tests/PhpWordTests/TemplateProcessorTest.php | 18 ++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 2ee54698cc..a8c3cd596e 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -8,6 +8,8 @@ - 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) +- 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) + ### Miscellaneous - Bump dompdf/dompdf from 2.0.3 to 2.0.4 by [@dependabot](https://github.com/dependabot) in [#2530](https://github.com/PHPOffice/PHPWord/pull/2530) diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 1ad901d480..27577499e5 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -357,6 +357,15 @@ public function setValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_ $replace = $xmlEscaper->escape($replace); } + // convert carriage returns + if (is_array($replace)) { + foreach ($replace as &$item) { + $item = $this->replaceCarriageReturns($item); + } + } else { + $replace = $this->replaceCarriageReturns($replace); + } + $this->tempDocumentHeaders = $this->setValueForPart($search, $replace, $this->tempDocumentHeaders, $limit); $this->tempDocumentMainPart = $this->setValueForPart($search, $replace, $this->tempDocumentMainPart, $limit); $this->tempDocumentFooters = $this->setValueForPart($search, $replace, $this->tempDocumentFooters, $limit); @@ -1305,6 +1314,14 @@ protected function indexClonedVariables($count, $xmlBlock) return $results; } + /** + * Replace carriage returns with xml. + */ + public function replaceCarriageReturns(string $string): string + { + return str_replace(["\r\n", "\r", "\n"], '', $string); + } + /** * Replaces variables with values from array, array keys are the variable names. * diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index f2d2cfbf13..65d5cfe9d8 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -591,6 +591,24 @@ public function testSetValues(): void self::assertStringContainsString('Hello John Doe', $templateProcessor->getMainPart()); } + /** + * @covers ::setValues + */ + public function testSetValuesMultiLine(): void + { + $mainPart = ' + + + Address: ${address} + + '; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setValues(['address' => "Peter Pan\nNeverland"]); + + self::assertStringContainsString('Address: Peter PanNeverland', $templateProcessor->getMainPart()); + } + /** * @covers ::setValues */ From 9bf816b084b77b9ff473316d682fc35bf21ceed7 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 7 Jan 2024 12:27:24 -0800 Subject: [PATCH 2/7] Allow `rgb()` When Converting Html (#2512) * Allow `rgb()` When Converting Html Fix #2508. Program currently expects `#xxxxxx` to specify a color when used by the `bgcolor` html attribute or the `color` or `background-color` css attributes. This PR allows support for `rgb(red, green, blue)` as well. * Update Change Log 2.0.0 --- docs/changes/2.x/2.0.0.md | 1 + src/PhpWord/Shared/Html.php | 17 ++++++-- .../Writer/Word2007/Style/FontTest.php | 42 +++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index a8c3cd596e..c679317049 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -18,5 +18,6 @@ - Bump phpmd/phpmd from 2.14.1 to 2.15.0 by [@dependabot](https://github.com/dependabot) in [#2538](https://github.com/PHPOffice/PHPWord/pull/2538) - Bump phpunit/phpunit from 9.6.14 to 9.6.15 by [@dependabot](https://github.com/dependabot) in [#2537](https://github.com/PHPOffice/PHPWord/pull/2537) - 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 [@oleibman](https://github.com/oleibman) fixing [#2508](https://github.com/PHPOffice/PHPWord/issues/2508) in [#2512](https://github.com/PHPOffice/PHPWord/pull/2512) ### BC Breaks diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 0a9b23979c..2022f7da09 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -37,6 +37,8 @@ */ class Html { + private const RGB_REGEXP = '/^\s*rgb\s*[(]\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*[)]\s*$/'; + protected static $listIndex = 0; protected static $xpath; @@ -142,7 +144,7 @@ protected static function parseInlineStyle($node, $styles = []) break; case 'bgcolor': // tables, rows, cells e.g. - $styles['bgColor'] = trim($val, '# '); + $styles['bgColor'] = self::convertRgb($val); break; case 'valign': @@ -720,11 +722,11 @@ protected static function parseStyleDeclarations(array $selectors, array $styles break; case 'color': - $styles['color'] = trim($value, '#'); + $styles['color'] = self::convertRgb($value); break; case 'background-color': - $styles['bgColor'] = trim($value, '#'); + $styles['bgColor'] = self::convertRgb($value); break; case 'line-height': @@ -1170,4 +1172,13 @@ protected static function parseHorizRule($node, $element): void // - line - that is a shape, has different behaviour // - repeated text, e.g. underline "_", because of unpredictable line wrapping } + + private static function convertRgb(string $rgb): string + { + if (preg_match(self::RGB_REGEXP, $rgb, $matches) === 1) { + return sprintf('%02X%02X%02X', $matches[1], $matches[2], $matches[3]); + } + + return trim($rgb, '# '); + } } diff --git a/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php b/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php index 2c10b5ff2f..a8214ec35b 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php @@ -17,6 +17,8 @@ namespace PhpOffice\PhpWordTests\Writer\Word2007\Style; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\Html; use PhpOffice\PhpWordTests\TestHelperDOCX; /** @@ -155,4 +157,44 @@ public function testPosition(): void self::assertTrue($doc->elementExists($path)); self::assertEquals(-20, $doc->getElementAttribute($path, 'w:val')); } + + public static function testRgb(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(['pageNumberingStart' => 1]); + $html = implode( + "\n", + [ + '', + '', + '', + '', + '', + '', + '', + '
This one is in color.This one too.
', + ] + ); + + Html::addHtml($section, $html, false, false); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $element = '/w:document/w:body/w:tbl/w:tr/w:tc/w:p/w:r'; + $txtelem = $element . '/w:t'; + $styelem = $element . '/w:rPr'; + self::assertTrue($doc->elementExists($txtelem)); + self::assertSame('This one is in color.', $doc->getElement($txtelem)->textContent); + self::assertTrue($doc->elementExists($styelem)); + self::assertTrue($doc->elementExists($styelem . '/w:color')); + self::assertSame('A7D9C1', $doc->getElementAttribute($styelem . '/w:color', 'w:val')); + + $element = '/w:document/w:body/w:tbl/w:tr/w:tc[2]/w:p/w:r'; + $txtelem = $element . '/w:t'; + $styelem = $element . '/w:rPr'; + self::assertTrue($doc->elementExists($txtelem)); + self::assertSame('This one too.', $doc->getElement($txtelem)->textContent); + self::assertTrue($doc->elementExists($styelem)); + self::assertTrue($doc->elementExists($styelem . '/w:color')); + self::assertSame('A7D9C1', $doc->getElementAttribute($styelem . '/w:color', 'w:val')); + } } From 98d038e05d2c9c0bf483a0c22118438a69853c49 Mon Sep 17 00:00:00 2001 From: Marin Nikolli <34999323+sibalonat@users.noreply.github.com> Date: Mon, 8 Jan 2024 07:34:35 +0100 Subject: [PATCH 3/7] Added extractVariables method to IOFactory (#2515) * Added extractVariables method to IOFactory * remove var_dumps * remove vardump * fix return and fix php stan errors and instances * fix order and whitespace * extra space * remove new lines * white space * new lines * whiteline * new line * white space * fix * new line * white space * remove some unneecessary if statement * Correct Font Size Calculated by MsDoc Reader Fix #2526. Most of that issue has already been fixed. The one remaining problem was a deprecation message handling font size. The code used `dechex($operand / 2)`, and issued the deprecation message whenever `$operand` was odd because `dechex` is designed only for integer conversion. `$operand` is actually 2 times the point size, so it will be odd only when the point size is some integer plus half a point (no other fractions are allowed). At any rate, it seems that `dechex` should not be used here in the first place; font size is a numeric value, not a hex string. There are many problems with MsDoc Reader at the moment. This PR is narrowly focused on the problem at hand. Its test is, at least, more detailed than the existing MsDoc Reader test, which does nothing more than confirm that read successfully creates a PhpWord object. The new test verifies that the font size is as expected, but does not validate any other aspect of the read. * Suggestions from @Progi1984 * Typo Tolerated By Windows but Not By Unix * Correct Title Line of Change Log * Bump dompdf/dompdf from 2.0.3 to 2.0.4 Bumps [dompdf/dompdf](https://github.com/dompdf/dompdf) from 2.0.3 to 2.0.4. - [Release notes](https://github.com/dompdf/dompdf/releases) - [Commits](https://github.com/dompdf/dompdf/compare/v2.0.3...v2.0.4) --- updated-dependencies: - dependency-name: dompdf/dompdf dependency-type: direct:development ... Signed-off-by: dependabot[bot] * Bump phpunit/phpunit from 9.6.13 to 9.6.14 Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.6.13 to 9.6.14. - [Changelog](https://github.com/sebastianbergmann/phpunit/blob/9.6.14/ChangeLog-9.6.md) - [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.6.13...9.6.14) --- updated-dependencies: - dependency-name: phpunit/phpunit dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump mpdf/mpdf from 8.2.0 to 8.2.2 Bumps [mpdf/mpdf](https://github.com/mpdf/mpdf) from 8.2.0 to 8.2.2. - [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.0...v8.2.2) --- updated-dependencies: - dependency-name: mpdf/mpdf dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump phpmd/phpmd from 2.14.1 to 2.15.0 Bumps [phpmd/phpmd](https://github.com/phpmd/phpmd) from 2.14.1 to 2.15.0. - [Release notes](https://github.com/phpmd/phpmd/releases) - [Changelog](https://github.com/phpmd/phpmd/blob/master/CHANGELOG) - [Commits](https://github.com/phpmd/phpmd/compare/2.14.1...2.15.0) --- updated-dependencies: - dependency-name: phpmd/phpmd dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Updated Changelog * Bump phpunit/phpunit from 9.6.14 to 9.6.15 Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.6.14 to 9.6.15. - [Changelog](https://github.com/sebastianbergmann/phpunit/blob/9.6.15/ChangeLog-9.6.md) - [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.6.14...9.6.15) --- updated-dependencies: - dependency-name: phpunit/phpunit dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Updated Changelog * Bump symfony/process from 5.4.28 to 5.4.34 Bumps [symfony/process](https://github.com/symfony/process) from 5.4.28 to 5.4.34. - [Release notes](https://github.com/symfony/process/releases) - [Changelog](https://github.com/symfony/process/blob/7.0/CHANGELOG.md) - [Commits](https://github.com/symfony/process/compare/v5.4.28...v5.4.34) --- updated-dependencies: - dependency-name: symfony/process dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Updated Changelog * Added extractVariables method to IOFactory * remove var_dumps * fix return and fix php stan errors and instances * fix order and whitespace * remove new lines * white space * new lines * whiteline * new line * white space * fix * white space * remove some unneecessary if statement * variable changes * 2.0.0 md * 2.0.0 * new line at end of the file * add entry to md * additional space, type fixes in enhancement md and iofactory * some fixes on spacing * based on fixer suggestions * new line at the end of the file --------- Signed-off-by: dependabot[bot] Co-authored-by: oleibman <10341515+oleibman@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Progi1984 --- docs/changes/2.x/2.0.0.md | 2 + ..._44_ExtractVariablesFromReaderWord2007.php | 14 +++++++ ...44_ExtractVariablesFromReaderWord2007.docx | Bin 0 -> 12981 bytes src/PhpWord/IOFactory.php | 39 ++++++++++++++++++ tests/PhpWordTests/IOFactoryTest.php | 13 ++++++ .../_files/templates/extract-variable.docx | Bin 0 -> 12575 bytes 6 files changed, 68 insertions(+) create mode 100644 samples/Sample_44_ExtractVariablesFromReaderWord2007.php create mode 100644 samples/resources/Sample_44_ExtractVariablesFromReaderWord2007.docx create mode 100644 tests/PhpWordTests/_files/templates/extract-variable.docx diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index c679317049..74f2184155 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -4,6 +4,8 @@ ## 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) + ### 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) diff --git a/samples/Sample_44_ExtractVariablesFromReaderWord2007.php b/samples/Sample_44_ExtractVariablesFromReaderWord2007.php new file mode 100644 index 0000000000..24574e5fb7 --- /dev/null +++ b/samples/Sample_44_ExtractVariablesFromReaderWord2007.php @@ -0,0 +1,14 @@ +9(1d|z* z_F-_mz}24O2U?%o(ZLl}!Q$X;NGaHER@v10116W!ZQxMr+$avQ1(N*|^enmP7UzF3 z_?qBSZX`0V3J-C9wqb8=!xyghWY72*oV>0xxmSxrcr{6VCsP||CdS|7|4i}!u>Jkzrtc3YctDT|C2Y4U&C*p7|ZcePKcLaJg_{Ob^~>I%rzY zyWu;yLYyZgnzi~H_V}5)!WXZB>A&s6TPpKu{?&4P&;S6`*O%gE?_|PcVsHG>_BC$( z7O;M%O520DQ3g&BF8OdofMI&|6bMxaT=g|P7|fiy20%n{X)$pnVoN@|$&+?b%{L&a zn(7)0XA9)G+fuME$RV=`!eg~q18oL3vy}D zGf3`Cv-H8Z-=_;sMII0^XBh?wC;B#Zl2w+%jmCVZgyckL8G3BY3$3b4=BGUZT6t}G z);2H^B?bMgerX#jr$_D^x0Pg_5OGMFY8`iQr9pz8hOhRpM8{87y{K;jvFLtM$u{$% z!8gFZk$%#wzj-vxJ9Obj4Jct~Zgglh+lC9Ew!v!ukPhLv5)D-V?#|GOk`N8mVH8Nw z(=Qv0hkTTZhiUFh8!U=%xDx&G=S&*J=kSl2+2Uuky0*p<>{4;15GyD{9&$)l%h3cD ziBv%8Kt7r|57B&J{tB0=tu-T#dF5~*uZ+_buQ{PoqG+8lce8wB>>dk546%SG%0}BJ z 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 } + /** + * Loads PhpWord ${variable} from file. + * + * @param string $filename The name of the file + * + * @return array The extracted variables + */ + public static function extractVariables(string $filename, string $readerName = 'Word2007'): array + { + /** @var \PhpOffice\PhpWord\Reader\ReaderInterface $reader */ + $reader = self::createReader($readerName); + $document = $reader->load($filename); + $extractedVariables = []; + foreach ($document->getSections() as $section) { + $concatenatedText = ''; + foreach ($section->getElements() as $element) { + if ($element instanceof TextRun) { + foreach ($element->getElements() as $textElement) { + if ($textElement instanceof Text) { + $text = $textElement->getText(); + $concatenatedText .= $text; + } + } + } + } + preg_match_all('/\$\{([^}]+)\}/', $concatenatedText, $matches); + if (!empty($matches[1])) { + foreach ($matches[1] as $match) { + $trimmedMatch = trim($match); + $extractedVariables[] = $trimmedMatch; + } + } + } + + return $extractedVariables; + } + /** * Check if it's a concrete class (not abstract nor interface). * diff --git a/tests/PhpWordTests/IOFactoryTest.php b/tests/PhpWordTests/IOFactoryTest.php index ee8d7fe180..79f0fd0c76 100644 --- a/tests/PhpWordTests/IOFactoryTest.php +++ b/tests/PhpWordTests/IOFactoryTest.php @@ -116,4 +116,17 @@ public function testLoad(): void IOFactory::load($file) ); } + + /** + * Test for extractVariables method. + */ + public function testExtractVariables(): void + { + $file = __DIR__ . '/_files/templates/extract-variable.docx'; + $extractedVariables = IOFactory::extractVariables($file, 'Word2007'); + + $expectedVariables = ['date', 'A1', 'B1']; + + self::assertEquals($expectedVariables, $extractedVariables, 'Extracted variables do not match expected variables.'); + } } diff --git a/tests/PhpWordTests/_files/templates/extract-variable.docx b/tests/PhpWordTests/_files/templates/extract-variable.docx new file mode 100644 index 0000000000000000000000000000000000000000..f95ec61862f7b5bc97a8eb65f5f3ce24407dd88d GIT binary patch literal 12575 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 Date: Mon, 8 Jan 2024 01:25:37 -0800 Subject: [PATCH 4/7] Template Processor Persist File After Destruct (#2545) * Template Processor Persist File After Destruct Replace PR #2542. Fix #2539. Inadvertent break in TemplateProcessor behavior after #2475. Deleted temp file on destruct. It will now persist after destructor. * Update Change Log --- docs/changes/1.x/1.2.0.md | 3 +- docs/changes/2.x/2.0.0.md | 2 +- src/PhpWord/TemplateProcessor.php | 17 ++--- tests/PhpWordTests/TemplateProcessorTest.php | 73 +++++++++++--------- 4 files changed, 49 insertions(+), 46 deletions(-) diff --git a/docs/changes/1.x/1.2.0.md b/docs/changes/1.x/1.2.0.md index 265d25b033..7a4b09ea2d 100644 --- a/docs/changes/1.x/1.2.0.md +++ b/docs/changes/1.x/1.2.0.md @@ -64,4 +64,5 @@ ### BC Breaks -- Removed dependency `laminas/laminas-escaper` \ No newline at end of file +- Removed dependency `laminas/laminas-escaper` +- *Unintended Break* TemplateProcessor Does Not Persist File After Destruct. [#2539](https://github.com/PHPOffice/PHPWord/issues/2539) To be fixed by [#2545](https://github.com/PHPOffice/PHPWord/pull/2545 diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 74f2184155..575510b222 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -9,7 +9,7 @@ ### 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) - +- 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) - 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) ### Miscellaneous diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 27577499e5..8aee40c546 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -146,18 +146,6 @@ public function __destruct() // Nothing to do here. } } - // Temporary file - if ($this->tempDocumentFilename && file_exists($this->tempDocumentFilename)) { - unlink($this->tempDocumentFilename); - } - } - - public function __wakeup(): void - { - $this->tempDocumentFilename = ''; - $this->zipClass = null; - - throw new Exception('unserialize not permitted for this class'); } /** @@ -1506,4 +1494,9 @@ public function setMacroChars(string $macroOpeningChars, string $macroClosingCha self::$macroOpeningChars = $macroOpeningChars; self::$macroClosingChars = $macroClosingChars; } + + public function getTempDocumentFilename(): string + { + return $this->tempDocumentFilename; + } } diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index 65d5cfe9d8..49e88d1b5b 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -21,7 +21,6 @@ use Exception; use PhpOffice\PhpWord\Element\Text; use PhpOffice\PhpWord\Element\TextRun; -use PhpOffice\PhpWord\Exception\Exception as WordException; use PhpOffice\PhpWord\IOFactory; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; @@ -38,14 +37,36 @@ */ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase { + /** @var ?TemplateProcessor */ + private $templateProcessor; + + private function getTemplateProcessor(string $filename): TemplateProcessor + { + $this->templateProcessor = new TemplateProcessor($filename); + + return $this->templateProcessor; + } + + protected function tearDown(): void + { + if ($this->templateProcessor !== null) { + $filename = $this->templateProcessor->getTempDocumentFilename(); + $this->templateProcessor = null; + if (file_exists($filename)) { + @unlink($filename); + } + } + } + /** * Construct test. * * @covers ::__construct + * @covers ::__destruct */ public function testTheConstruct(): void { - $object = new TemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); + $object = $this->getTemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); self::assertInstanceOf('PhpOffice\\PhpWord\\TemplateProcessor', $object); self::assertEquals([], $object->getVariables()); } @@ -106,7 +127,7 @@ public function xtestTemplateCanBeSavedInTemporaryLocation(string $templateFqfn, public function testXslStyleSheetCanBeApplied(): void { $templateFqfn = __DIR__ . '/_files/templates/with_table_macros.docx'; - $templateProcessor = new TemplateProcessor($templateFqfn); + $templateProcessor = $this->getTemplateProcessor($templateFqfn); $actualDocumentFqfn = $this->xtestTemplateCanBeSavedInTemporaryLocation($templateFqfn, $templateProcessor); $expectedDocumentFqfn = __DIR__ . '/_files/documents/without_table_macros.docx'; @@ -150,7 +171,7 @@ public function testXslStyleSheetCanNotBeAppliedOnFailureOfSettingParameterValue $this->expectExceptionMessage('Could not set values for the given XSL style sheet parameters.'); } - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); $xslDomDocument = new DOMDocument(); $xslDomDocument->load(__DIR__ . '/_files/xsl/passthrough.xsl'); @@ -171,7 +192,7 @@ public function testXslStyleSheetCanNotBeAppliedOnFailureOfLoadingXmlFromTemplat { $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); $this->expectExceptionMessage('Could not load the given XML document.'); - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/corrupted_main_document_part.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/corrupted_main_document_part.docx'); $xslDomDocument = new DOMDocument(); $xslDomDocument->load(__DIR__ . '/_files/xsl/passthrough.xsl'); @@ -190,7 +211,7 @@ public function testXslStyleSheetCanNotBeAppliedOnFailureOfLoadingXmlFromTemplat */ public function testDeleteRow(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/delete-row.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/delete-row.docx'); self::assertEquals( ['deleteMe', 'deleteMeToo'], @@ -216,7 +237,7 @@ public function testDeleteRow(): void */ public function testCloneRow(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/clone-merge.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/clone-merge.docx'); self::assertEquals( ['tableHeader', 'userId', 'userName', 'userLocation'], @@ -240,7 +261,7 @@ public function testCloneRow(): void */ public function testCloneRowWithCustomMacro(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/clone-merge-with-custom-macro.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/clone-merge-with-custom-macro.docx'); $templateProcessor->setMacroOpeningChars('{#'); $templateProcessor->setMacroClosingChars('#}'); @@ -397,7 +418,7 @@ public function testCloneRowAndSetValuesWithCustomMacro(): void */ public function testMacrosCanBeReplacedInHeaderAndFooter(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx'); self::assertEquals(['documentContent', 'headerValue:100:100', 'footerValue'], $templateProcessor->getVariables()); @@ -418,7 +439,7 @@ public function testMacrosCanBeReplacedInHeaderAndFooter(): void */ public function testCustomMacrosCanBeReplacedInHeaderAndFooter(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/header-footer-with-custom-macro.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/header-footer-with-custom-macro.docx'); $templateProcessor->setMacroOpeningChars('{{'); $templateProcessor->setMacroClosingChars('}}'); @@ -440,7 +461,7 @@ public function testCustomMacrosCanBeReplacedInHeaderAndFooter(): void */ public function testSetValue(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/clone-merge.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/clone-merge.docx'); Settings::setOutputEscapingEnabled(true); $helloworld = "hello\nworld"; $templateProcessor->setValue('userName', $helloworld); @@ -455,7 +476,7 @@ public function testSetValue(): void */ public function testSetValueWithCustomMacro(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/clone-merge-with-custom-macro.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/clone-merge-with-custom-macro.docx'); $templateProcessor->setMacroChars('{#', '#}'); Settings::setOutputEscapingEnabled(true); $helloworld = "hello\nworld"; @@ -786,7 +807,7 @@ public function testSetCheckboxWithCustomMacro(): void */ public function testSetImageValue(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx'); $imagePath = __DIR__ . '/_files/images/earth.jpg'; $variablesReplace = [ @@ -866,7 +887,7 @@ public function testSetImageValue(): void */ public function testCloneDeleteBlock(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/clone-delete-block.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/clone-delete-block.docx'); self::assertEquals( ['DELETEME', '/DELETEME', 'CLONEME', 'blockVariable', '/CLONEME'], @@ -906,7 +927,7 @@ public function testGetVariableCountCountsHowManyTimesEachPlaceholderIsPresent() $templatePath = 'test.docx'; $objWriter->save($templatePath); - $templateProcessor = new TemplateProcessor($templatePath); + $templateProcessor = $this->getTemplateProcessor($templatePath); $variableCount = $templateProcessor->getVariableCount(); unlink($templatePath); @@ -943,7 +964,7 @@ public function testGetVariableCountCountsHowManyTimesEachPlaceholderIsPresentWi $templatePath = 'test.docx'; $objWriter->save($templatePath); - $templateProcessor = new TemplateProcessor($templatePath); + $templateProcessor = $this->getTemplateProcessor($templatePath); $templateProcessor->setMacroChars('{{', '}}'); $variableCount = $templateProcessor->getVariableCount(); unlink($templatePath); @@ -981,7 +1002,7 @@ public function testCloneBlockCanCloneABlockTwice(): void $objWriter->save($templatePath); // replace placeholders and save the file - $templateProcessor = new TemplateProcessor($templatePath); + $templateProcessor = $this->getTemplateProcessor($templatePath); $templateProcessor->setValue('title', 'Some title'); $templateProcessor->cloneBlock('subreport', 2); $templateProcessor->setValue('subreport.id', '123', 1); @@ -1034,7 +1055,7 @@ public function testCloneBlockCanCloneABlockTwiceWithCustomMacro(): void $objWriter->save($templatePath); // replace placeholders and save the file - $templateProcessor = new TemplateProcessor($templatePath); + $templateProcessor = $this->getTemplateProcessor($templatePath); $templateProcessor->setMacroChars('{{', '}}'); $templateProcessor->setValue('title', 'Some title'); $templateProcessor->cloneBlock('subreport', 2); @@ -1323,7 +1344,7 @@ public function testFixBrokenMacrosWithCustomMacro(): void */ public function testMainPartNameDetection(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/document22-xml.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/document22-xml.docx'); $variables = ['test']; @@ -1335,7 +1356,7 @@ public function testMainPartNameDetection(): void */ public function testMainPartNameDetectionWithCustomMacro(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/document22-with-custom-macro-xml.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/document22-with-custom-macro-xml.docx'); $templateProcessor->setMacroOpeningChars('{#'); $templateProcessor->setMacroClosingChars('#}'); $variables = ['test']; @@ -1595,18 +1616,6 @@ public function testShouldMakeFieldsUpdateOnOpen(): void self::assertStringContainsString('', $templateProcessor->getSettingsPart()); } - /** - * Should not allow unserialize to avoid malware. - */ - public function testUnserialize(): void - { - $this->expectException(WordException::class); - $this->expectExceptionMessage('unserialize not permitted'); - $object = new TemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); - $serialized = serialize($object); - $object2 = unserialize($serialized); - } - public function testShouldMakeFieldsUpdateOnOpenWithCustomMacro(): void { $settingsPart = ' From 2f4da6e491643c88f3c5d2892f5a63febb66b313 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 15 Jan 2024 11:24:22 -0800 Subject: [PATCH 5/7] Invalid Annotation in Test Member (#2549) * Invalid Annotation in Test Member PhpUnit cannot parse the `@covers` lines in FormulaTest; they result in warnings in Coverage and Deploy tests. This PR fixes them; no change log entry should be needed. * Fluke Failure PhpWordTest ran in such a way that `new PhpWord()` and `new DocInfo()` happened in different seconds. Almost impossible, but easy enough to prevent. --- tests/PhpWordTests/Element/FormulaTest.php | 6 +++--- tests/PhpWordTests/PhpWordTest.php | 11 +++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/PhpWordTests/Element/FormulaTest.php b/tests/PhpWordTests/Element/FormulaTest.php index fef5c2221e..7e368e8995 100644 --- a/tests/PhpWordTests/Element/FormulaTest.php +++ b/tests/PhpWordTests/Element/FormulaTest.php @@ -30,7 +30,7 @@ class FormulaTest extends AbstractWebServerEmbeddedTest { /** - * @covers \Formula::__construct + * @covers \PhpOffice\PhpWord\Element\Formula::__construct */ public function testConstruct(): void { @@ -40,8 +40,8 @@ public function testConstruct(): void } /** - * @covers \Formula::getMath - * @covers \Formula::setMath + * @covers \PhpOffice\PhpWord\Element\Formula::getMath + * @covers \PhpOffice\PhpWord\Element\Formula::setMath */ public function testMath(): void { diff --git a/tests/PhpWordTests/PhpWordTest.php b/tests/PhpWordTests/PhpWordTest.php index 7b756e7082..33118a11e8 100644 --- a/tests/PhpWordTests/PhpWordTest.php +++ b/tests/PhpWordTests/PhpWordTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWordTests; use BadMethodCallException; +use DateTimeImmutable; use PhpOffice\PhpWord\Metadata\DocInfo; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; @@ -35,8 +36,14 @@ class PhpWordTest extends \PHPUnit\Framework\TestCase */ public function testConstruct(): void { - $phpWord = new PhpWord(); - self::assertEquals(new DocInfo(), $phpWord->getDocInfo()); + do { + $dtStart = new DateTimeImmutable(); + $startSecond = $dtStart->format('s'); + $phpWord = new PhpWord(); + $docInfo = new DocInfo(); + $endSecond = (new DateTimeImmutable('now'))->format('s'); + } while ($startSecond !== $endSecond); + self::assertEquals($docInfo, $phpWord->getDocInfo()); self::assertEquals(Settings::DEFAULT_FONT_NAME, $phpWord->getDefaultFontName()); self::assertEquals(Settings::DEFAULT_FONT_SIZE, $phpWord->getDefaultFontSize()); } From 8b891bb6842dd383f679b47898fad0b7c181f325 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 23 Jan 2024 09:15:27 -0800 Subject: [PATCH 6/7] Fix 32-bit Problem in PasswordEncoder (#2551) * Fix 32-bit Problem in PasswordEncoder Fix #2550. * Update change log --- docs/changes/2.x/2.0.0.md | 2 ++ src/PhpWord/Shared/Microsoft/PasswordEncoder.php | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 575510b222..8214ded041 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -12,6 +12,8 @@ - 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) - 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 - Bump dompdf/dompdf from 2.0.3 to 2.0.4 by [@dependabot](https://github.com/dependabot) in [#2530](https://github.com/PHPOffice/PHPWord/pull/2530) diff --git a/src/PhpWord/Shared/Microsoft/PasswordEncoder.php b/src/PhpWord/Shared/Microsoft/PasswordEncoder.php index 5ff42e49b9..d6cf69fc6d 100644 --- a/src/PhpWord/Shared/Microsoft/PasswordEncoder.php +++ b/src/PhpWord/Shared/Microsoft/PasswordEncoder.php @@ -34,6 +34,9 @@ class PasswordEncoder const ALGORITHM_MAC = 'MAC'; const ALGORITHM_HMAC = 'HMAC'; + private const ALL_ONE_BITS = (PHP_INT_SIZE > 4) ? 0xFFFFFFFF : -1; + private const HIGH_ORDER_BIT = (PHP_INT_SIZE > 4) ? 0x80000000 : PHP_INT_MIN; + /** * Mapping between algorithm name and algorithm ID. * @@ -128,7 +131,7 @@ public static function hashPassword($password, $algorithmName = self::ALGORITHM_ // build low-order word and hig-order word and combine them $combinedKey = self::buildCombinedKey($byteChars); // build reversed hexadecimal string - $hex = str_pad(strtoupper(dechex($combinedKey & 0xFFFFFFFF)), 8, '0', \STR_PAD_LEFT); + $hex = str_pad(strtoupper(dechex($combinedKey & self::ALL_ONE_BITS)), 8, '0', \STR_PAD_LEFT); $reversedHex = $hex[6] . $hex[7] . $hex[4] . $hex[5] . $hex[2] . $hex[3] . $hex[0] . $hex[1]; $generatedKey = mb_convert_encoding($reversedHex, 'UCS-2LE', 'UTF-8'); @@ -232,10 +235,10 @@ private static function buildCombinedKey($byteChars) */ private static function int32($value) { - $value = ($value & 0xFFFFFFFF); + $value = $value & self::ALL_ONE_BITS; - if ($value & 0x80000000) { - $value = -((~$value & 0xFFFFFFFF) + 1); + if ($value & self::HIGH_ORDER_BIT) { + $value = -((~$value & self::ALL_ONE_BITS) + 1); } return $value; From 2daa50c6f34c9cb6c532f72350e4bd8d466d6c71 Mon Sep 17 00:00:00 2001 From: Progi1984 Date: Wed, 15 May 2024 13:26:52 +0200 Subject: [PATCH 7/7] Improved Issue Template (#2609) --- .github/ISSUE_TEMPLATE/1_bug_report.yml | 65 ++++++++++++++++++++ .github/ISSUE_TEMPLATE/2_feature_request.yml | 35 +++++++++++ .github/ISSUE_TEMPLATE/bug_report.md | 38 ------------ .github/ISSUE_TEMPLATE/feature_request.md | 22 ------- .github/ISSUE_TEMPLATE/how-to-use.md | 14 ----- .github/PULL_REQUEST_TEMPLATE.md | 7 ++- docs/changes/2.x/2.0.0.md | 3 +- 7 files changed, 106 insertions(+), 78 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/1_bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/2_feature_request.yml delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/ISSUE_TEMPLATE/how-to-use.md diff --git a/.github/ISSUE_TEMPLATE/1_bug_report.yml b/.github/ISSUE_TEMPLATE/1_bug_report.yml new file mode 100644 index 0000000000..ea335468a5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1_bug_report.yml @@ -0,0 +1,65 @@ +name: 🐛 Bug Report +description: Create a report to help improve PHPWord +labels: [ "Bug Report" ] +body: + - type: markdown + attributes: + value: | + ### ❗️ Read this before submitting your bug report: + - **Write in English/French.** Reports in all other languages will be closed. + - **Provide as much detail as possible** + - Attachments : Error logs, Screenshots, Document files (generated and expected). + - If the issue cannot be reproduced, it cannot be fixed. + - type: textarea + id: what-happened + attributes: + label: Describe the bug and add attachments + description: What went wrong? If possible, add screenshots, error logs, document files (generated and expected) or screen recordings to help explain your problem. + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen. + validations: + required: true + - type: textarea + id: steps-reproduce + attributes: + label: Steps to reproduce + description: Please provide a code sample that reproduces the issue. + placeholder: | + ```php + addSection(); + $section->... + ``` + validations: + required: true + - type: input + id: phpword-version + attributes: + label: PHPWord version(s) where the bug happened + placeholder: "e.g., 1.2.0 or master" + validations: + required: true + - type: input + id: php-version + attributes: + label: PHP version(s) where the bug happened + placeholder: "e.g., 7.1 or 8.2" + validations: + required: true + - type: checkboxes + attributes: + label: Priority + description: Funded tickets have a higher priority. + options: + - label: I want to crowdfund the bug fix (with [@algora-io](https://docs.algora.io/bounties/overview)) and fund a community developer. + required: false + - label: I want to pay the bug fix and fund a maintainer for that. (Contact @Progi1984) + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/2_feature_request.yml b/.github/ISSUE_TEMPLATE/2_feature_request.yml new file mode 100644 index 0000000000..bf3539c372 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2_feature_request.yml @@ -0,0 +1,35 @@ +name: 💡 Feature request +description: Suggest an idea for this project +labels: [ "Change Request" ] +body: + - type: markdown + attributes: + value: | + ### ❗️ Read this before submitting your bug report: + - **Write in English/French.** Reports in all other languages will be closed. + - **Provide as much detail as possible** + - Attachments : Error logs, Screenshots, Document files (generated and expected). + - If the issue cannot be reproduced, it cannot be fixed. + - type: textarea + id: problem + attributes: + label: Describe the problem + description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: Describe the expected behavior + description: A clear and concise description of what you expected to happen. If possible, add screenshots, document files (expected). + validations: + required: true + - type: checkboxes + attributes: + label: Priority + description: Funded tickets have a higher priority. + options: + - label: I want to crowdfund the feature (with [@algora-io](https://docs.algora.io/bounties/overview)) and fund a community developer. + required: false + - label: I want to pay the feature and fund a maintainer for that. (Contact @Progi1984) + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index fcb3a65db1..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a report to help improve PHPWord -labels: Bug Report - ---- - -### Describe the Bug - -A clear and concise description of what the bug is. - -### Steps to Reproduce - -Please provide a code sample that reproduces the issue. - -```php -addSection(); -$section->... -``` - -### Expected Behavior - -A clear and concise description of what you expected to happen. - -### Current Behavior - -What is the current behavior? - -### Context - -Please fill in your environment information: - -- PHP Version: -- PHPWord Version: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 171e8378e1..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -labels: Change Request - ---- - -### Is your feature request related to a problem? Please describe. - -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -### Describe the solution you'd like - -A clear and concise description of what you want to happen. - -### Describe alternatives you've considered - -A clear and concise description of any alternative solutions or features you've considered. - -### Additional context - -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/how-to-use.md b/.github/ISSUE_TEMPLATE/how-to-use.md deleted file mode 100644 index 85cc47072e..0000000000 --- a/.github/ISSUE_TEMPLATE/how-to-use.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: How to Use PHPWord -about: Find out how to use PHPWord -labels: WontFix - ---- - -***Please do not use the issue tracker to ask how to use PHPWord.*** - -Documentation is available on [Read the Docs](https://phpword.readthedocs.io/en/latest/). - -Sample code is in the [`/samples/` directory](https://github.com/PHPOffice/PHPWord/tree/master/samples). - -Usage questions belong on [Stack Overflow](https://stackoverflow.com/questions/tagged/phpword). diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5430a996ec..ce201f8bb6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,6 +6,7 @@ Fixes # (issue) ### Checklist: -- [ ] I have run `composer run-script check --timeout=0` and no errors were reported -- [ ] The new code is covered by unit tests (check build/coverage for coverage report) -- [ ] I have updated the documentation to describe the changes +- [ ] My CI is :green_circle: +- [ ] I have covered by unit tests my new code (check build/coverage for coverage report) +- [ ] I have updated the [documentation](https://github.com/PHPOffice/PHPWord/tree/master/docs) to describe the changes +- [ ] I have updated the [changelog](https://github.com/PHPOffice/PHPWord/blob/master/docs/changes/2.x/2.0.0.md) diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md index 8214ded041..c5fbaf4b52 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -22,6 +22,7 @@ - Bump phpmd/phpmd from 2.14.1 to 2.15.0 by [@dependabot](https://github.com/dependabot) in [#2538](https://github.com/PHPOffice/PHPWord/pull/2538) - Bump phpunit/phpunit from 9.6.14 to 9.6.15 by [@dependabot](https://github.com/dependabot) in [#2537](https://github.com/PHPOffice/PHPWord/pull/2537) - 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 [@oleibman](https://github.com/oleibman) fixing [#2508](https://github.com/PHPOffice/PHPWord/issues/2508) in [#2512](https://github.com/PHPOffice/PHPWord/pull/2512) +- 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) ### BC Breaks