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/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 d534608be6..6f10bb171e 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/2.x/2.0.0.md @@ -4,11 +4,16 @@ ## 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) -- TemplateProcessor Persist File After Destruct [@oleibman](https://github.com/oleibman) fixing [#2539](https://github.com/PHPOffice/PHPWord/issues/2539) in [#2542](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) +- 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 @@ -18,6 +23,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 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 @@ +load($filename); } + /** + * 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/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 1828de3b06..6be54b37a7 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -38,6 +38,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; @@ -143,7 +145,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': @@ -722,11 +724,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': @@ -1172,4 +1174,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/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; diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 1ad901d480..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'); } /** @@ -357,6 +345,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 +1302,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. * @@ -1489,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/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/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/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()); } diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index f2d2cfbf13..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"; @@ -591,6 +612,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 */ @@ -768,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 = [ @@ -848,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'], @@ -888,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); @@ -925,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); @@ -963,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); @@ -1016,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); @@ -1305,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']; @@ -1317,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']; @@ -1577,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 = ' 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')); + } } diff --git a/tests/PhpWordTests/_files/templates/extract-variable.docx b/tests/PhpWordTests/_files/templates/extract-variable.docx new file mode 100644 index 0000000000..f95ec61862 Binary files /dev/null and b/tests/PhpWordTests/_files/templates/extract-variable.docx differ