From 5e147c4ffeb87a7188e377ec89ff55501bea5d50 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 6 Feb 2024 15:23:05 -0800 Subject: [PATCH 001/153] WIP Do Not Install All my unimplemented changes. Not intended for install. --- phpstan-baseline.neon | 5 - samples/Sample_45_RTLTitles.php | 35 ++ src/PhpWord/PhpWord.php | 5 +- src/PhpWord/Reader/Word2007/AbstractPart.php | 65 ++- src/PhpWord/Reader/Word2007/Styles.php | 6 +- src/PhpWord/Settings.php | 5 + src/PhpWord/Shared/Html.php | 209 +++++-- src/PhpWord/Shared/HtmlColours.php | 549 ++++++++++++++++++ .../Shared/Microsoft/PasswordEncoder.php | 11 +- src/PhpWord/Shared/ZipArchive.php | 10 +- src/PhpWord/SimpleType/TextDirection.php | 55 ++ src/PhpWord/Style.php | 10 +- src/PhpWord/Style/AbstractStyle.php | 15 + src/PhpWord/Style/Border.php | 8 + src/PhpWord/Style/Paragraph.php | 48 +- src/PhpWord/Style/Table.php | 113 ++++ src/PhpWord/TemplateProcessor.php | 12 +- src/PhpWord/Writer/HTML/Element/Table.php | 5 +- src/PhpWord/Writer/HTML/Element/Title.php | 13 +- src/PhpWord/Writer/HTML/Part/Head.php | 10 +- src/PhpWord/Writer/HTML/Style/Font.php | 7 + src/PhpWord/Writer/HTML/Style/Table.php | 6 +- src/PhpWord/Writer/RTF.php | 2 +- src/PhpWord/Writer/RTF/Element/Title.php | 9 +- src/PhpWord/Writer/Word2007/Element/Table.php | 10 +- src/PhpWord/Writer/Word2007/Part/Styles.php | 17 +- src/PhpWord/Writer/Word2007/Style/Font.php | 5 +- .../Writer/Word2007/Style/MarginBorder.php | 18 +- .../Writer/Word2007/Style/Paragraph.php | 5 + src/PhpWord/Writer/Word2007/Style/Table.php | 3 + .../Reader/Word2007/StyleTableTest.php | 55 ++ tests/PhpWordTests/SettingsRtlTest.php | 81 +++ tests/PhpWordTests/SettingsTest.php | 16 - tests/PhpWordTests/Shared/Html2402Test.php | 208 +++++++ tests/PhpWordTests/Shared/HtmlFullTest.php | 93 +++ .../PhpWordTests/Shared/HtmlHeadingsTest.php | 66 +++ tests/PhpWordTests/Shared/HtmlRtlTest.php | 180 ++++++ tests/PhpWordTests/Shared/HtmlTest.php | 39 +- .../TemplateProcessorSectionTest.php | 92 +++ tests/PhpWordTests/TemplateProcessorTest.php | 10 + tests/PhpWordTests/Writer/HTML/FontTest.php | 56 +- tests/PhpWordTests/Writer/HTML/Helper.php | 11 +- tests/PhpWordTests/Writer/HTML/PartTest.php | 10 +- .../Writer/ODText/Part/ContentTest.php | 10 + .../Writer/ODText/Style/Paragraph2Test.php | 6 +- .../Writer/RTF/RichTextTitleTest.php | 50 ++ .../Writer/Word2007/Element/TableTest.php | 147 +++++ .../_files/documents/word.2474.docx | Bin 0 -> 27593 bytes 48 files changed, 2190 insertions(+), 211 deletions(-) create mode 100644 samples/Sample_45_RTLTitles.php create mode 100644 src/PhpWord/Shared/HtmlColours.php create mode 100644 src/PhpWord/SimpleType/TextDirection.php create mode 100644 tests/PhpWordTests/Reader/Word2007/StyleTableTest.php create mode 100644 tests/PhpWordTests/SettingsRtlTest.php create mode 100644 tests/PhpWordTests/Shared/Html2402Test.php create mode 100644 tests/PhpWordTests/Shared/HtmlFullTest.php create mode 100644 tests/PhpWordTests/Shared/HtmlHeadingsTest.php create mode 100644 tests/PhpWordTests/Shared/HtmlRtlTest.php create mode 100644 tests/PhpWordTests/TemplateProcessorSectionTest.php create mode 100644 tests/PhpWordTests/Writer/RTF/RichTextTitleTest.php create mode 100644 tests/PhpWordTests/Writer/Word2007/Element/TableTest.php create mode 100644 tests/PhpWordTests/_files/documents/word.2474.docx diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 2e44745b3d..e07918f6b6 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -400,11 +400,6 @@ parameters: count: 1 path: src/PhpWord/Shared/Html.php - - - message: "#^Cannot call method setBorderSize\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - message: "#^Cannot call method setStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" count: 1 diff --git a/samples/Sample_45_RTLTitles.php b/samples/Sample_45_RTLTitles.php new file mode 100644 index 0000000000..83dd9b9872 --- /dev/null +++ b/samples/Sample_45_RTLTitles.php @@ -0,0 +1,35 @@ +setDefaultFontName('DejaVu Sans'); // for good rendition of PDF +$rendererName = Settings::PDF_RENDERER_MPDF; +$rendererLibraryPath = $vendorDirPath . '/mpdf/mpdf'; +Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + +// Define styles for headers +$phpWord->addTitleStyle(1, ['bold' => true, 'name' => 'Arial', 'size' => 16], []); +//var_dump($x); +$phpWord->addTitleStyle(2, ['bold' => true, 'name' => 'Arial', 'size' => 14], []); +$phpWord->addTitleStyle(3, ['bold' => true, 'name' => 'Arial', 'size' => 12], []); +$phpWord->addTitleStyle(4, ['bold' => true, 'name' => 'Arial', 'size' => 10], []); + +// New section +$section = $phpWord->addSection(); +$htmlContent = '

مرحبا 1

تجربة 2

تجربة تجربة

هناك hello هنا 4

مرحبا here كلمة انجليزي.

'; +SharedHtml::addHtml($section, $htmlContent, false, false); + +// Save file +echo write($phpWord, basename(__FILE__, '.php'), $writers); +if (!CLI) { + include_once 'Sample_Footer.php'; +} +Settings::setDefaultRtl(false); diff --git a/src/PhpWord/PhpWord.php b/src/PhpWord/PhpWord.php index a7aa95ce45..c85306e67c 100644 --- a/src/PhpWord/PhpWord.php +++ b/src/PhpWord/PhpWord.php @@ -20,6 +20,7 @@ use BadMethodCallException; use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\Exception\Exception; +use PhpOffice\PhpWord\Style\Font; /** * PHPWord main class. @@ -284,9 +285,9 @@ public function setDefaultFontSize($fontSize): void * * @return \PhpOffice\PhpWord\Style\Paragraph */ - public function setDefaultParagraphStyle($styles) + public function setDefaultParagraphStyle($styles, ?Font $fontStyles = null) { - return Style::setDefaultParagraphStyle($styles); + return Style::setDefaultParagraphStyle($styles, $fontStyles); } /** diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 95799387ed..a92e6d5958 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -592,35 +592,46 @@ protected function readTableStyle(XMLReader $xmlReader, DOMElement $domNode) $borders = array_merge($margins, ['insideH', 'insideV']); if ($xmlReader->elementExists('w:tblPr', $domNode)) { + $tblStyleName = ''; if ($xmlReader->elementExists('w:tblPr/w:tblStyle', $domNode)) { - $style = $xmlReader->getAttribute('w:val', $domNode, 'w:tblPr/w:tblStyle'); - } else { - $styleNode = $xmlReader->getElement('w:tblPr', $domNode); - $styleDefs = []; - foreach ($margins as $side) { - $ucfSide = ucfirst($side); - $styleDefs["cellMargin$ucfSide"] = [self::READ_VALUE, "w:tblCellMar/w:$side", 'w:w']; - } - foreach ($borders as $side) { - $ucfSide = ucfirst($side); - $styleDefs["border{$ucfSide}Size"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:sz']; - $styleDefs["border{$ucfSide}Color"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:color']; - $styleDefs["border{$ucfSide}Style"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:val']; - } - $styleDefs['layout'] = [self::READ_VALUE, 'w:tblLayout', 'w:type']; - $styleDefs['bidiVisual'] = [self::READ_TRUE, 'w:bidiVisual']; - $styleDefs['cellSpacing'] = [self::READ_VALUE, 'w:tblCellSpacing', 'w:w']; - $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); - - $tablePositionNode = $xmlReader->getElement('w:tblpPr', $styleNode); - if ($tablePositionNode !== null) { - $style['position'] = $this->readTablePosition($xmlReader, $tablePositionNode); - } + $tblStyleName = $xmlReader->getAttribute('w:val', $domNode, 'w:tblPr/w:tblStyle'); + } + $styleNode = $xmlReader->getElement('w:tblPr', $domNode); + $styleDefs = []; - $indentNode = $xmlReader->getElement('w:tblInd', $styleNode); - if ($indentNode !== null) { - $style['indent'] = $this->readTableIndent($xmlReader, $indentNode); - } + foreach ($margins as $side) { + $ucfSide = ucfirst($side); + $styleDefs["cellMargin$ucfSide"] = [self::READ_VALUE, "w:tblCellMar/w:$side", 'w:w']; + } + foreach ($borders as $side) { + $ucfSide = ucfirst($side); + $styleDefs["border{$ucfSide}Size"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:sz']; + $styleDefs["border{$ucfSide}Color"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:color']; + $styleDefs["border{$ucfSide}Style"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:val']; + } + $styleDefs['layout'] = [self::READ_VALUE, 'w:tblLayout', 'w:type']; + $styleDefs['bidiVisual'] = [self::READ_TRUE, 'w:bidiVisual']; + $styleDefs['cellSpacing'] = [self::READ_VALUE, 'w:tblCellSpacing', 'w:w']; + $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); + + $tablePositionNode = $xmlReader->getElement('w:tblpPr', $styleNode); + if ($tablePositionNode !== null) { + $style['position'] = $this->readTablePosition($xmlReader, $tablePositionNode); + } + + $indentNode = $xmlReader->getElement('w:tblInd', $styleNode); + if ($indentNode !== null) { + $style['indent'] = $this->readTableIndent($xmlReader, $indentNode); + } + if ($xmlReader->elementExists('w:basedOn', $domNode)) { + $style['basedOn'] = $xmlReader->getAttribute('w:val', $domNode, 'w:basedOn'); + } + if ($tblStyleName !== '') { + $style['tblStyle'] = $tblStyleName; + } + // this may be unneeded + if ($xmlReader->elementExists('w:name', $domNode)) { + $style['styleName'] = $xmlReader->getAttribute('w:val', $domNode, 'w:name'); } } diff --git a/src/PhpWord/Reader/Word2007/Styles.php b/src/PhpWord/Reader/Word2007/Styles.php index 760adf9493..f67bc77463 100644 --- a/src/PhpWord/Reader/Word2007/Styles.php +++ b/src/PhpWord/Reader/Word2007/Styles.php @@ -65,8 +65,9 @@ public function read(PhpWord $phpWord): void foreach ($nodes as $node) { $type = $xmlReader->getAttribute('w:type', $node); $name = $xmlReader->getAttribute('w:val', $node, 'w:name'); + $styleId = $xmlReader->getAttribute('w:styleId', $node); if (null === $name) { - $name = $xmlReader->getAttribute('w:styleId', $node); + $name = $styleId; } $headingMatches = []; preg_match('/Heading\s*(\d)/i', $name, $headingMatches); @@ -98,7 +99,8 @@ public function read(PhpWord $phpWord): void case 'table': $tStyle = $this->readTableStyle($xmlReader, $node); if (!empty($tStyle)) { - $phpWord->addTableStyle($name, $tStyle); + $newTable = $phpWord->addTableStyle($styleId, $tStyle); + $newTable->setStyleName($name); } break; diff --git a/src/PhpWord/Settings.php b/src/PhpWord/Settings.php index 984486ccfe..b43bf05228 100644 --- a/src/PhpWord/Settings.php +++ b/src/PhpWord/Settings.php @@ -15,6 +15,8 @@ namespace PhpOffice\PhpWord; +use PhpOffice\PhpWord\SimpleType\TextDirection; + /** * PHPWord settings class. * @@ -397,6 +399,9 @@ public static function setDefaultFontSize($value): bool public static function setDefaultRtl(?bool $defaultRtl): void { self::$defaultRtl = $defaultRtl; + if ($defaultRtl === true && Style::getStyle('Normal') === null) { + Style::setDefaultParagraphStyle(['bidi' => true, 'textDirection' => TextDirection::RLTB], ['rtl' => true]); + } } public static function isDefaultRtl(): ?bool diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 2022f7da09..21d8404ddc 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -25,9 +25,14 @@ use PhpOffice\PhpWord\Element\AbstractContainer; use PhpOffice\PhpWord\Element\Row; use PhpOffice\PhpWord\Element\Table; +use PhpOffice\PhpWord\Element\TextRun; +use PhpOffice\PhpWord\Metadata\DocInfo; +use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\SimpleType\Border; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\NumberFormat; +use PhpOffice\PhpWord\SimpleType\TextDirection; use PhpOffice\PhpWord\Style\Paragraph; /** @@ -37,6 +42,8 @@ */ class Html { + private const SPECIAL_BORDER_WIDTHS = ['thin' => '0.5pt', 'thick' => '3.5pt', 'medium' => '2.0pt']; + 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; @@ -45,6 +52,9 @@ class Html protected static $options; + /** @var ?DocInfo */ + protected static $docInfo; + /** * @var Css */ @@ -69,6 +79,14 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit * which could be applied when such an element occurs in the parseNode function. */ static::$options = $options; + static::$docInfo = null; + if (method_exists($element, 'getPhpWord')) { + /** @var ?PhpWord */ + $phpWord = $element->getPhpWord(); + if ($phpWord !== null) { + static::$docInfo = $phpWord->getDocInfo(); + } + } // Preprocess: remove all line ends, decode HTML entity, // fix ampersand and angle brackets and add body tag for HTML fragments @@ -84,17 +102,20 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit // Load DOM if (\PHP_VERSION_ID < 80000) { - $orignalLibEntityLoader = libxml_disable_entity_loader(true); + $orignalLibEntityLoader = libxml_disable_entity_loader(true); // @codeCoverageIgnore } $dom = new DOMDocument(); $dom->preserveWhiteSpace = $preserveWhiteSpace; $dom->loadXML($html); static::$xpath = new DOMXPath($dom); - $node = $dom->getElementsByTagName('body'); + $node = $dom->getElementsByTagName('html'); + if (count($node) === 0) { + $node = $dom->getElementsByTagName('body'); + } static::parseNode($node->item(0), $element); if (\PHP_VERSION_ID < 80000) { - libxml_disable_entity_loader($orignalLibEntityLoader); + libxml_disable_entity_loader($orignalLibEntityLoader); // @codeCoverageIgnore } } @@ -106,12 +127,20 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit * * @return array */ - protected static function parseInlineStyle($node, $styles = []) + 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'; + $bidi = false; + $direction = isset($attributes['dir']) ? $attributes['dir']->value : ''; + if ($direction === 'rtl') { + $bidi = $styles['bidi'] = $styles['rtl'] = true; + $styles['textDirection'] = TextDirection::RLTB; + } elseif ($direction === 'ltr') { + $bidi = $styles['bidi'] = $styles['rtl'] = false; + $styles['textDirection'] = TextDirection::LRTB; + } foreach ($attributes as $attribute) { $val = $attribute->value; switch (strtolower($attribute->name)) { @@ -144,7 +173,7 @@ protected static function parseInlineStyle($node, $styles = []) break; case 'bgcolor': // tables, rows, cells e.g. - $styles['bgColor'] = self::convertRgb($val); + HtmlColours::setArrayColour($styles, 'bgColor', self::convertRgb($val)); break; case 'valign': @@ -195,6 +224,46 @@ protected static function parseNode($node, $element, $styles = [], $data = []): return; } + if ($node->nodeName === 'title') { + if (self::$docInfo !== null) { + $docTitle = $node->nodeValue; + if ($docTitle !== 'PHPWord' && trim($docTitle) !== '') { // default + self::$docInfo->setTitle($node->nodeValue); + } + } + + return; + } + if ($node->nodeName === 'meta') { + if (self::$docInfo !== null) { + $attributes = $node->attributes; + $name = $attributes->getNamedItem('name'); + $content = $attributes->getNamedItem('content'); + if ($name !== null && $content !== null) { + $mapArray = ['author' => 'creator']; + $others = [ + 'title', + 'description', + 'subject', + 'keywords', + 'category', + 'company', + 'manager', + ]; + $nameValue = $name->nodeValue; + $propertyName = $mapArray[$nameValue] ?? (in_array($nameValue, $others, true) ? $nameValue : ''); + $method = 'set' . ucfirst($propertyName); + if (method_exists(self::$docInfo, $method)) { + self::$docInfo->$method($content->nodeValue); + } + } + } + + return; + } + if ($node->nodeName === 'script') { + return; + } // Populate styles array $styleTypes = ['font', 'paragraph', 'list', 'table', 'row', 'cell']; @@ -208,12 +277,12 @@ protected static function parseNode($node, $element, $styles = [], $data = []): $nodes = [ // $method $node $element $styles $data $argument1 $argument2 'p' => ['Paragraph', $node, $element, $styles, null, null, null], - 'h1' => ['Heading', null, $element, $styles, null, 'Heading1', null], - 'h2' => ['Heading', null, $element, $styles, null, 'Heading2', null], - 'h3' => ['Heading', null, $element, $styles, null, 'Heading3', null], - 'h4' => ['Heading', null, $element, $styles, null, 'Heading4', null], - 'h5' => ['Heading', null, $element, $styles, null, 'Heading5', null], - 'h6' => ['Heading', null, $element, $styles, null, 'Heading6', null], + 'h1' => ['Heading', $node, $element, $styles, null, 'Heading1', null], + 'h2' => ['Heading', $node, $element, $styles, null, 'Heading2', null], + 'h3' => ['Heading', $node, $element, $styles, null, 'Heading3', null], + 'h4' => ['Heading', $node, $element, $styles, null, 'Heading4', null], + 'h5' => ['Heading', $node, $element, $styles, null, 'Heading5', null], + 'h6' => ['Heading', $node, $element, $styles, null, 'Heading6', null], '#text' => ['Text', $node, $element, $styles, null, null, null], 'strong' => ['Property', null, null, $styles, null, 'bold', true], 'b' => ['Property', null, null, $styles, null, 'bold', true], @@ -308,7 +377,12 @@ protected static function parseParagraph($node, $element, &$styles) return $element->addPageBreak(); } - return $element->addTextRun($styles['paragraph']); + $newElement = $element->addTextRun($styles['paragraph']); + if (isset($styles['paragraph']['className']) && $newElement->getParagraphStyle() instanceof Paragraph) { + $newElement->getParagraphStyle()->setStyleName($styles['paragraph']['className']); + } + + return $newElement; } /** @@ -339,21 +413,22 @@ protected static function parseInput($node, $element, &$styles): void /** * Parse heading node. * - * @param \PhpOffice\PhpWord\Element\AbstractContainer $element - * @param array &$styles - * @param string $argument1 Name of heading style - * - * @return \PhpOffice\PhpWord\Element\TextRun - * * @todo Think of a clever way of defining header styles, now it is only based on the assumption, that * Heading1 - Heading6 are already defined somewhere */ - protected static function parseHeading($element, &$styles, $argument1) + protected static function parseHeading(DOMNode $node, AbstractContainer $element, array &$styles, string $headingStyle): TextRun { - $styles['paragraph'] = $argument1; - $newElement = $element->addTextRun($styles['paragraph']); + self::parseInlineStyle($node, $styles['font']); + // Create a TextRun to hold styles and text + $styles['paragraph'] = $headingStyle; + $textRun = new TextRun($styles['paragraph']); - return $newElement; + // Create a title with level corresponding to number in heading style + // (Eg, Heading1 = 1) + $element->addTitle($textRun, (int) ltrim($headingStyle, 'Heading')); + + // Return TextRun so children are parsed + return $textRun; } /** @@ -373,7 +448,11 @@ protected static function parseText($node, $element, &$styles): void } if (is_callable([$element, 'addText'])) { - $element->addText($node->nodeValue, $styles['font'], $styles['paragraph']); + $font = $styles['font']; + if (isset($font['className']) && count($font) === 1) { + $font = $styles['font']['className']; + } + $element->addText($node->nodeValue, $font, $styles['paragraph']); } } @@ -423,9 +502,10 @@ protected static function parseTable($node, $element, &$styles) } $attributes = $node->attributes; - if ($attributes->getNamedItem('border') !== null) { + if ($attributes->getNamedItem('border') !== null && is_object($newElement->getStyle())) { $border = (int) $attributes->getNamedItem('border')->value; - $newElement->getStyle()->setBorderSize(Converter::pixelToTwip($border)); + $newElement->getStyle()->setBorderSize((int) Converter::pixelToTwip($border)); + $newElement->getStyle()->setBorderStyle(($border === 0) ? 'none' : 'single'); } return $newElement; @@ -710,6 +790,7 @@ protected static function parseStyleDeclarations(array $selectors, array $styles case 'direction': $styles['rtl'] = $value === 'rtl'; $styles['bidi'] = $value === 'rtl'; + $styles['textDirection'] = ($value === 'rtl') ? TextDirection::RLTB : TextDirection::LRTB; break; case 'font-size': @@ -722,11 +803,11 @@ protected static function parseStyleDeclarations(array $selectors, array $styles break; case 'color': - $styles['color'] = self::convertRgb($value); + HtmlColours::setArrayColour($styles, 'color', self::convertRgb($value)); break; case 'background-color': - $styles['bgColor'] = self::convertRgb($value); + HtmlColours::setArrayColour($styles, 'bgColor', self::convertRgb($value)); break; case 'line-height': @@ -806,7 +887,7 @@ protected static function parseStyleDeclarations(array $selectors, array $styles break; case 'border-width': - $styles['borderSize'] = Converter::cssToPoint($value); + $styles['borderSize'] = Converter::cssToPoint(self::SPECIAL_BORDER_WIDTHS[$value] ?? $value); break; case 'border-style': @@ -836,29 +917,46 @@ protected static function parseStyleDeclarations(array $selectors, array $styles case 'border-bottom': case 'border-right': case 'border-left': - // must have exact order [width color style], e.g. "1px #0011CC solid" or "2pt green solid" - // Word does not accept shortened hex colors e.g. #CCC, only full e.g. #CCCCCC - if (preg_match('/([0-9]+[^0-9]*)\s+(\#[a-fA-F0-9]+|[a-zA-Z]+)\s+([a-z]+)/', $value, $matches)) { - if (false !== strpos($property, '-')) { - $tmp = explode('-', $property); - $which = $tmp[1]; - $which = ucfirst($which); // e.g. bottom -> Bottom - } else { - $which = ''; - } - // Note - border width normalization: - // Width of border in Word is calculated differently than HTML borders, usually showing up too bold. - // Smallest 1px (or 1pt) appears in Word like 2-3px/pt in HTML once converted to twips. - // Therefore we need to normalize converted twip value to cca 1/2 of value. - // This may be adjusted, if better ratio or formula found. - // BC change: up to ver. 0.17.0 was $size converted to points - Converter::cssToPoint($size) - $size = Converter::cssToTwip($matches[1]); + $stylePattern = '/(^|\\s)(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)(\\s|$)/'; + if (!preg_match($stylePattern, $value, $matches)) { + break; + } + $borderStyle = $matches[2]; + $value = preg_replace($stylePattern, ' ', $value) ?? ''; + $borderSize = $borderColor = null; + $sizePattern = '/(^|\\s)([0-9]+([.][0-9]+)?+(%|[a-z]*)|thick|thin|medium)(\\s|$)/'; + if (preg_match($sizePattern, $value, $matches)) { + $borderSize = $matches[2]; + $borderSize = self::SPECIAL_BORDER_WIDTHS[$borderSize] ?? $borderSize; + $value = preg_replace($sizePattern, ' ', $value) ?? ''; + } + $colorPattern = '/(^|\\s)([#][a-fA-F0-9]{6}|[#][a-fA-F0-9]{3}|[a-z][a-z0-9]+)(\\s|$)/'; + if (preg_match($colorPattern, $value, $matches)) { + $borderColor = HtmlColours::convertColour($matches[2]); + } + if (false !== strpos($property, '-')) { + $tmp = explode('-', $property); + $which = $tmp[1]; + $which = ucfirst($which); // e.g. bottom -> Bottom + } else { + $which = ''; + } + // Note - border width normalization: + // Width of border in Word is calculated differently than HTML borders, usually showing up too bold. + // Smallest 1px (or 1pt) appears in Word like 2-3px/pt in HTML once converted to twips. + // Therefore we need to normalize converted twip value to cca 1/2 of value. + // This may be adjusted, if better ratio or formula found. + // BC change: up to ver. 0.17.0 was $size converted to points - Converter::cssToPoint($size) + if ($borderSize !== null) { + $size = Converter::cssToTwip($borderSize); $size = (int) ($size / 2); // valid variants may be e.g. borderSize, borderTopSize, borderLeftColor, etc .. $styles["border{$which}Size"] = $size; // twips - $styles["border{$which}Color"] = trim($matches[2], '#'); - $styles["border{$which}Style"] = self::mapBorderStyle($matches[3]); } + if (!empty($borderColor)) { + $styles["border{$which}Color"] = $borderColor; + } + $styles["border{$which}Style"] = self::mapBorderStyle($borderStyle); break; case 'vertical-align': @@ -1008,6 +1106,8 @@ protected static function mapBorderStyle($cssBorderStyle) case 'dotted': case 'double': return $cssBorderStyle; + case 'hidden': + return 'none'; default: return 'single'; } @@ -1015,14 +1115,14 @@ protected static function mapBorderStyle($cssBorderStyle) protected static function mapBorderColor(&$styles, $cssBorderColor): void { - $numColors = substr_count($cssBorderColor, '#'); + $colors = explode(' ', $cssBorderColor); + $numColors = count($colors); if ($numColors === 1) { - $styles['borderColor'] = trim($cssBorderColor, '#'); - } elseif ($numColors > 1) { - $colors = explode(' ', $cssBorderColor); + HtmlColours::setArrayColour($styles, 'borderColor', $cssBorderColor); + } else { $borders = ['borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor']; for ($i = 0; $i < min(4, $numColors, count($colors)); ++$i) { - $styles[$borders[$i]] = trim($colors[$i], '#'); + HtmlColours::setArrayColour($styles, $borders[$i], $colors[$i]); } } } @@ -1148,7 +1248,8 @@ protected static function parseLink($node, $element, &$styles) */ protected static function parseHorizRule($node, $element): void { - $styles = self::parseInlineStyle($node); + $unusedStyle = []; + $styles = self::parseInlineStyle($node, $unusedStyle); //
is implemented as an empty paragraph - extending 100% inside the section // Some properties may be controlled, e.g.
diff --git a/src/PhpWord/Shared/HtmlColours.php b/src/PhpWord/Shared/HtmlColours.php new file mode 100644 index 0000000000..40bc0096c6 --- /dev/null +++ b/src/PhpWord/Shared/HtmlColours.php @@ -0,0 +1,549 @@ + 'f0f8ff', + 'antiquewhite' => 'faebd7', + 'antiquewhite1' => 'ffefdb', + 'antiquewhite2' => 'eedfcc', + 'antiquewhite3' => 'cdc0b0', + 'antiquewhite4' => '8b8378', + 'aqua' => '00ffff', + 'aquamarine1' => '7fffd4', + 'aquamarine2' => '76eec6', + 'aquamarine4' => '458b74', + 'azure1' => 'f0ffff', + 'azure2' => 'e0eeee', + 'azure3' => 'c1cdcd', + 'azure4' => '838b8b', + 'beige' => 'f5f5dc', + 'bisque1' => 'ffe4c4', + 'bisque2' => 'eed5b7', + 'bisque3' => 'cdb79e', + 'bisque4' => '8b7d6b', + 'black' => '000000', + 'blanchedalmond' => 'ffebcd', + 'blue' => '0000ff', + 'blue1' => '0000ff', + 'blue2' => '0000ee', + 'blue4' => '00008b', + 'blueviolet' => '8a2be2', + 'brown' => 'a52a2a', + 'brown1' => 'ff4040', + 'brown2' => 'ee3b3b', + 'brown3' => 'cd3333', + 'brown4' => '8b2323', + 'burlywood' => 'deb887', + 'burlywood1' => 'ffd39b', + 'burlywood2' => 'eec591', + 'burlywood3' => 'cdaa7d', + 'burlywood4' => '8b7355', + 'cadetblue' => '5f9ea0', + 'cadetblue1' => '98f5ff', + 'cadetblue2' => '8ee5ee', + 'cadetblue3' => '7ac5cd', + 'cadetblue4' => '53868b', + 'chartreuse1' => '7fff00', + 'chartreuse2' => '76ee00', + 'chartreuse3' => '66cd00', + 'chartreuse4' => '458b00', + 'chocolate' => 'd2691e', + 'chocolate1' => 'ff7f24', + 'chocolate2' => 'ee7621', + 'chocolate3' => 'cd661d', + 'coral' => 'ff7f50', + 'coral1' => 'ff7256', + 'coral2' => 'ee6a50', + 'coral3' => 'cd5b45', + 'coral4' => '8b3e2f', + 'cornflowerblue' => '6495ed', + 'cornsilk1' => 'fff8dc', + 'cornsilk2' => 'eee8cd', + 'cornsilk3' => 'cdc8b1', + 'cornsilk4' => '8b8878', + 'cyan1' => '00ffff', + 'cyan2' => '00eeee', + 'cyan3' => '00cdcd', + 'cyan4' => '008b8b', + 'darkgoldenrod' => 'b8860b', + 'darkgoldenrod1' => 'ffb90f', + 'darkgoldenrod2' => 'eead0e', + 'darkgoldenrod3' => 'cd950c', + 'darkgoldenrod4' => '8b6508', + 'darkgreen' => '006400', + 'darkkhaki' => 'bdb76b', + 'darkolivegreen' => '556b2f', + 'darkolivegreen1' => 'caff70', + 'darkolivegreen2' => 'bcee68', + 'darkolivegreen3' => 'a2cd5a', + 'darkolivegreen4' => '6e8b3d', + 'darkorange' => 'ff8c00', + 'darkorange1' => 'ff7f00', + 'darkorange2' => 'ee7600', + 'darkorange3' => 'cd6600', + 'darkorange4' => '8b4500', + 'darkorchid' => '9932cc', + 'darkorchid1' => 'bf3eff', + 'darkorchid2' => 'b23aee', + 'darkorchid3' => '9a32cd', + 'darkorchid4' => '68228b', + 'darksalmon' => 'e9967a', + 'darkseagreen' => '8fbc8f', + 'darkseagreen1' => 'c1ffc1', + 'darkseagreen2' => 'b4eeb4', + 'darkseagreen3' => '9bcd9b', + 'darkseagreen4' => '698b69', + 'darkslateblue' => '483d8b', + 'darkslategray' => '2f4f4f', + 'darkslategray1' => '97ffff', + 'darkslategray2' => '8deeee', + 'darkslategray3' => '79cdcd', + 'darkslategray4' => '528b8b', + 'darkturquoise' => '00ced1', + 'darkviolet' => '9400d3', + 'deeppink1' => 'ff1493', + 'deeppink2' => 'ee1289', + 'deeppink3' => 'cd1076', + 'deeppink4' => '8b0a50', + 'deepskyblue1' => '00bfff', + 'deepskyblue2' => '00b2ee', + 'deepskyblue3' => '009acd', + 'deepskyblue4' => '00688b', + 'dimgray' => '696969', + 'dodgerblue1' => '1e90ff', + 'dodgerblue2' => '1c86ee', + 'dodgerblue3' => '1874cd', + 'dodgerblue4' => '104e8b', + 'firebrick' => 'b22222', + 'firebrick1' => 'ff3030', + 'firebrick2' => 'ee2c2c', + 'firebrick3' => 'cd2626', + 'firebrick4' => '8b1a1a', + 'floralwhite' => 'fffaf0', + 'forestgreen' => '228b22', + 'fuchsia' => 'ff00ff', + 'gainsboro' => 'dcdcdc', + 'ghostwhite' => 'f8f8ff', + 'gold1' => 'ffd700', + 'gold2' => 'eec900', + 'gold3' => 'cdad00', + 'gold4' => '8b7500', + 'goldenrod' => 'daa520', + 'goldenrod1' => 'ffc125', + 'goldenrod2' => 'eeb422', + 'goldenrod3' => 'cd9b1d', + 'goldenrod4' => '8b6914', + 'gray' => 'bebebe', + 'gray1' => '030303', + 'gray10' => '1a1a1a', + 'gray11' => '1c1c1c', + 'gray12' => '1f1f1f', + 'gray13' => '212121', + 'gray14' => '242424', + 'gray15' => '262626', + 'gray16' => '292929', + 'gray17' => '2b2b2b', + 'gray18' => '2e2e2e', + 'gray19' => '303030', + 'gray2' => '050505', + 'gray20' => '333333', + 'gray21' => '363636', + 'gray22' => '383838', + 'gray23' => '3b3b3b', + 'gray24' => '3d3d3d', + 'gray25' => '404040', + 'gray26' => '424242', + 'gray27' => '454545', + 'gray28' => '474747', + 'gray29' => '4a4a4a', + 'gray3' => '080808', + 'gray30' => '4d4d4d', + 'gray31' => '4f4f4f', + 'gray32' => '525252', + 'gray33' => '545454', + 'gray34' => '575757', + 'gray35' => '595959', + 'gray36' => '5c5c5c', + 'gray37' => '5e5e5e', + 'gray38' => '616161', + 'gray39' => '636363', + 'gray4' => '0a0a0a', + 'gray40' => '666666', + 'gray41' => '696969', + 'gray42' => '6b6b6b', + 'gray43' => '6e6e6e', + 'gray44' => '707070', + 'gray45' => '737373', + 'gray46' => '757575', + 'gray47' => '787878', + 'gray48' => '7a7a7a', + 'gray49' => '7d7d7d', + 'gray5' => '0d0d0d', + 'gray50' => '7f7f7f', + 'gray51' => '828282', + 'gray52' => '858585', + 'gray53' => '878787', + 'gray54' => '8a8a8a', + 'gray55' => '8c8c8c', + 'gray56' => '8f8f8f', + 'gray57' => '919191', + 'gray58' => '949494', + 'gray59' => '969696', + 'gray6' => '0f0f0f', + 'gray60' => '999999', + 'gray61' => '9c9c9c', + 'gray62' => '9e9e9e', + 'gray63' => 'a1a1a1', + 'gray64' => 'a3a3a3', + 'gray65' => 'a6a6a6', + 'gray66' => 'a8a8a8', + 'gray67' => 'ababab', + 'gray68' => 'adadad', + 'gray69' => 'b0b0b0', + 'gray7' => '121212', + 'gray70' => 'b3b3b3', + 'gray71' => 'b5b5b5', + 'gray72' => 'b8b8b8', + 'gray73' => 'bababa', + 'gray74' => 'bdbdbd', + 'gray75' => 'bfbfbf', + 'gray76' => 'c2c2c2', + 'gray77' => 'c4c4c4', + 'gray78' => 'c7c7c7', + 'gray79' => 'c9c9c9', + 'gray8' => '141414', + 'gray80' => 'cccccc', + 'gray81' => 'cfcfcf', + 'gray82' => 'd1d1d1', + 'gray83' => 'd4d4d4', + 'gray84' => 'd6d6d6', + 'gray85' => 'd9d9d9', + 'gray86' => 'dbdbdb', + 'gray87' => 'dedede', + 'gray88' => 'e0e0e0', + 'gray89' => 'e3e3e3', + 'gray9' => '171717', + 'gray90' => 'e5e5e5', + 'gray91' => 'e8e8e8', + 'gray92' => 'ebebeb', + 'gray93' => 'ededed', + 'gray94' => 'f0f0f0', + 'gray95' => 'f2f2f2', + 'gray97' => 'f7f7f7', + 'gray98' => 'fafafa', + 'gray99' => 'fcfcfc', + 'green' => '00ff00', + 'green1' => '00ff00', + 'green2' => '00ee00', + 'green3' => '00cd00', + 'green4' => '008b00', + 'greenyellow' => 'adff2f', + 'honeydew1' => 'f0fff0', + 'honeydew2' => 'e0eee0', + 'honeydew3' => 'c1cdc1', + 'honeydew4' => '838b83', + 'hotpink' => 'ff69b4', + 'hotpink1' => 'ff6eb4', + 'hotpink2' => 'ee6aa7', + 'hotpink3' => 'cd6090', + 'hotpink4' => '8b3a62', + 'indianred' => 'cd5c5c', + 'indianred1' => 'ff6a6a', + 'indianred2' => 'ee6363', + 'indianred3' => 'cd5555', + 'indianred4' => '8b3a3a', + 'ivory1' => 'fffff0', + 'ivory2' => 'eeeee0', + 'ivory3' => 'cdcdc1', + 'ivory4' => '8b8b83', + 'khaki' => 'f0e68c', + 'khaki1' => 'fff68f', + 'khaki2' => 'eee685', + 'khaki3' => 'cdc673', + 'khaki4' => '8b864e', + 'lavender' => 'e6e6fa', + 'lavenderblush1' => 'fff0f5', + 'lavenderblush2' => 'eee0e5', + 'lavenderblush3' => 'cdc1c5', + 'lavenderblush4' => '8b8386', + 'lawngreen' => '7cfc00', + 'lemonchiffon1' => 'fffacd', + 'lemonchiffon2' => 'eee9bf', + 'lemonchiffon3' => 'cdc9a5', + 'lemonchiffon4' => '8b8970', + 'light' => 'eedd82', + 'lightblue' => 'add8e6', + 'lightblue1' => 'bfefff', + 'lightblue2' => 'b2dfee', + 'lightblue3' => '9ac0cd', + 'lightblue4' => '68838b', + 'lightcoral' => 'f08080', + 'lightcyan1' => 'e0ffff', + 'lightcyan2' => 'd1eeee', + 'lightcyan3' => 'b4cdcd', + 'lightcyan4' => '7a8b8b', + 'lightgoldenrod1' => 'ffec8b', + 'lightgoldenrod2' => 'eedc82', + 'lightgoldenrod3' => 'cdbe70', + 'lightgoldenrod4' => '8b814c', + 'lightgoldenrodyellow' => 'fafad2', + 'lightgray' => 'd3d3d3', + 'lightpink' => 'ffb6c1', + 'lightpink1' => 'ffaeb9', + 'lightpink2' => 'eea2ad', + 'lightpink3' => 'cd8c95', + 'lightpink4' => '8b5f65', + 'lightsalmon1' => 'ffa07a', + 'lightsalmon2' => 'ee9572', + 'lightsalmon3' => 'cd8162', + 'lightsalmon4' => '8b5742', + 'lightseagreen' => '20b2aa', + 'lightskyblue' => '87cefa', + 'lightskyblue1' => 'b0e2ff', + 'lightskyblue2' => 'a4d3ee', + 'lightskyblue3' => '8db6cd', + 'lightskyblue4' => '607b8b', + 'lightslateblue' => '8470ff', + 'lightslategray' => '778899', + 'lightsteelblue' => 'b0c4de', + 'lightsteelblue1' => 'cae1ff', + 'lightsteelblue2' => 'bcd2ee', + 'lightsteelblue3' => 'a2b5cd', + 'lightsteelblue4' => '6e7b8b', + 'lightyellow1' => 'ffffe0', + 'lightyellow2' => 'eeeed1', + 'lightyellow3' => 'cdcdb4', + 'lightyellow4' => '8b8b7a', + 'lime' => '00ff00', + 'limegreen' => '32cd32', + 'linen' => 'faf0e6', + 'magenta' => 'ff00ff', + 'magenta2' => 'ee00ee', + 'magenta3' => 'cd00cd', + 'magenta4' => '8b008b', + 'maroon' => 'b03060', + 'maroon1' => 'ff34b3', + 'maroon2' => 'ee30a7', + 'maroon3' => 'cd2990', + 'maroon4' => '8b1c62', + 'medium' => '66cdaa', + 'mediumaquamarine' => '66cdaa', + 'mediumblue' => '0000cd', + 'mediumorchid' => 'ba55d3', + 'mediumorchid1' => 'e066ff', + 'mediumorchid2' => 'd15fee', + 'mediumorchid3' => 'b452cd', + 'mediumorchid4' => '7a378b', + 'mediumpurple' => '9370db', + 'mediumpurple1' => 'ab82ff', + 'mediumpurple2' => '9f79ee', + 'mediumpurple3' => '8968cd', + 'mediumpurple4' => '5d478b', + 'mediumseagreen' => '3cb371', + 'mediumslateblue' => '7b68ee', + 'mediumspringgreen' => '00fa9a', + 'mediumturquoise' => '48d1cc', + 'mediumvioletred' => 'c71585', + 'midnightblue' => '191970', + 'mintcream' => 'f5fffa', + 'mistyrose1' => 'ffe4e1', + 'mistyrose2' => 'eed5d2', + 'mistyrose3' => 'cdb7b5', + 'mistyrose4' => '8b7d7b', + 'moccasin' => 'ffe4b5', + 'navajowhite1' => 'ffdead', + 'navajowhite2' => 'eecfa1', + 'navajowhite3' => 'cdb38b', + 'navajowhite4' => '8b795e', + 'navy' => '000080', + 'navyblue' => '000080', + 'oldlace' => 'fdf5e6', + 'olive' => '808000', + 'olivedrab' => '6b8e23', + 'olivedrab1' => 'c0ff3e', + 'olivedrab2' => 'b3ee3a', + 'olivedrab4' => '698b22', + 'orange' => 'ffa500', + 'orange1' => 'ffa500', + 'orange2' => 'ee9a00', + 'orange3' => 'cd8500', + 'orange4' => '8b5a00', + 'orangered1' => 'ff4500', + 'orangered2' => 'ee4000', + 'orangered3' => 'cd3700', + 'orangered4' => '8b2500', + 'orchid' => 'da70d6', + 'orchid1' => 'ff83fa', + 'orchid2' => 'ee7ae9', + 'orchid3' => 'cd69c9', + 'orchid4' => '8b4789', + 'pale' => 'db7093', + 'palegoldenrod' => 'eee8aa', + 'palegreen' => '98fb98', + 'palegreen1' => '9aff9a', + 'palegreen2' => '90ee90', + 'palegreen3' => '7ccd7c', + 'palegreen4' => '548b54', + 'paleturquoise' => 'afeeee', + 'paleturquoise1' => 'bbffff', + 'paleturquoise2' => 'aeeeee', + 'paleturquoise3' => '96cdcd', + 'paleturquoise4' => '668b8b', + 'palevioletred' => 'db7093', + 'palevioletred1' => 'ff82ab', + 'palevioletred2' => 'ee799f', + 'palevioletred3' => 'cd6889', + 'palevioletred4' => '8b475d', + 'papayawhip' => 'ffefd5', + 'peachpuff1' => 'ffdab9', + 'peachpuff2' => 'eecbad', + 'peachpuff3' => 'cdaf95', + 'peachpuff4' => '8b7765', + 'pink' => 'ffc0cb', + 'pink1' => 'ffb5c5', + 'pink2' => 'eea9b8', + 'pink3' => 'cd919e', + 'pink4' => '8b636c', + 'plum' => 'dda0dd', + 'plum1' => 'ffbbff', + 'plum2' => 'eeaeee', + 'plum3' => 'cd96cd', + 'plum4' => '8b668b', + 'powderblue' => 'b0e0e6', + 'purple' => 'a020f0', + 'rebeccapurple' => '663399', + 'purple1' => '9b30ff', + 'purple2' => '912cee', + 'purple3' => '7d26cd', + 'purple4' => '551a8b', + 'red' => 'ff0000', + 'red1' => 'ff0000', + 'red2' => 'ee0000', + 'red3' => 'cd0000', + 'red4' => '8b0000', + 'rosybrown' => 'bc8f8f', + 'rosybrown1' => 'ffc1c1', + 'rosybrown2' => 'eeb4b4', + 'rosybrown3' => 'cd9b9b', + 'rosybrown4' => '8b6969', + 'royalblue' => '4169e1', + 'royalblue1' => '4876ff', + 'royalblue2' => '436eee', + 'royalblue3' => '3a5fcd', + 'royalblue4' => '27408b', + 'saddlebrown' => '8b4513', + 'salmon' => 'fa8072', + 'salmon1' => 'ff8c69', + 'salmon2' => 'ee8262', + 'salmon3' => 'cd7054', + 'salmon4' => '8b4c39', + 'sandybrown' => 'f4a460', + 'seagreen1' => '54ff9f', + 'seagreen2' => '4eee94', + 'seagreen3' => '43cd80', + 'seagreen4' => '2e8b57', + 'seashell1' => 'fff5ee', + 'seashell2' => 'eee5de', + 'seashell3' => 'cdc5bf', + 'seashell4' => '8b8682', + 'sienna' => 'a0522d', + 'sienna1' => 'ff8247', + 'sienna2' => 'ee7942', + 'sienna3' => 'cd6839', + 'sienna4' => '8b4726', + 'silver' => 'c0c0c0', + 'skyblue' => '87ceeb', + 'skyblue1' => '87ceff', + 'skyblue2' => '7ec0ee', + 'skyblue3' => '6ca6cd', + 'skyblue4' => '4a708b', + 'slateblue' => '6a5acd', + 'slateblue1' => '836fff', + 'slateblue2' => '7a67ee', + 'slateblue3' => '6959cd', + 'slateblue4' => '473c8b', + 'slategray' => '708090', + 'slategray1' => 'c6e2ff', + 'slategray2' => 'b9d3ee', + 'slategray3' => '9fb6cd', + 'slategray4' => '6c7b8b', + 'snow1' => 'fffafa', + 'snow2' => 'eee9e9', + 'snow3' => 'cdc9c9', + 'snow4' => '8b8989', + 'springgreen1' => '00ff7f', + 'springgreen2' => '00ee76', + 'springgreen3' => '00cd66', + 'springgreen4' => '008b45', + 'steelblue' => '4682b4', + 'steelblue1' => '63b8ff', + 'steelblue2' => '5cacee', + 'steelblue3' => '4f94cd', + 'steelblue4' => '36648b', + 'tan' => 'd2b48c', + 'tan1' => 'ffa54f', + 'tan2' => 'ee9a49', + 'tan3' => 'cd853f', + 'tan4' => '8b5a2b', + 'teal' => '008080', + 'thistle' => 'd8bfd8', + 'thistle1' => 'ffe1ff', + 'thistle2' => 'eed2ee', + 'thistle3' => 'cdb5cd', + 'thistle4' => '8b7b8b', + 'tomato1' => 'ff6347', + 'tomato2' => 'ee5c42', + 'tomato3' => 'cd4f39', + 'tomato4' => '8b3626', + 'turquoise' => '40e0d0', + 'turquoise1' => '00f5ff', + 'turquoise2' => '00e5ee', + 'turquoise3' => '00c5cd', + 'turquoise4' => '00868b', + 'violet' => 'ee82ee', + 'violetred' => 'd02090', + 'violetred1' => 'ff3e96', + 'violetred2' => 'ee3a8c', + 'violetred3' => 'cd3278', + 'violetred4' => '8b2252', + 'wheat' => 'f5deb3', + 'wheat1' => 'ffe7ba', + 'wheat2' => 'eed8ae', + 'wheat3' => 'cdba96', + 'wheat4' => '8b7e66', + 'white' => 'ffffff', + 'whitesmoke' => 'f5f5f5', + 'yellow' => 'ffff00', + 'yellow1' => 'ffff00', + 'yellow2' => 'eeee00', + 'yellow3' => 'cdcd00', + 'yellow4' => '8b8b00', + 'yellowgreen' => '9acd32', + ]; + + public static function colourNameLookup(string $colorName): string + { + return self::COLOUR_MAP[$colorName] ?? ''; + } + + public static function convertColour(string $colorName): string + { + $colorName = trim($colorName); + if (preg_match('/^[#][a-fA-F0-9]{6}$/', $colorName) === 1) { + return substr($colorName, 1); + } + if (preg_match('/^[#][a-fA-F0-9]{3}$/', $colorName) === 1) { + return "{$colorName[1]}{$colorName[1]}{$colorName[2]}{$colorName[2]}{$colorName[3]}{$colorName[3]}"; + } + + return self::COLOUR_MAP[$colorName] ?? $colorName; + } + + public static function setArrayColour(array &$array, string $index, string $colorName): void + { + $array[$index] = self::convertColour($colorName); + } +} diff --git a/src/PhpWord/Shared/Microsoft/PasswordEncoder.php b/src/PhpWord/Shared/Microsoft/PasswordEncoder.php index 5ff42e49b9..cb6e26e620 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/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/SimpleType/TextDirection.php b/src/PhpWord/SimpleType/TextDirection.php new file mode 100644 index 0000000000..0797fa9294 --- /dev/null +++ b/src/PhpWord/SimpleType/TextDirection.php @@ -0,0 +1,55 @@ +getParagraph(); } /** diff --git a/src/PhpWord/Style/AbstractStyle.php b/src/PhpWord/Style/AbstractStyle.php index 4e5def618d..1fbcdcd3d4 100644 --- a/src/PhpWord/Style/AbstractStyle.php +++ b/src/PhpWord/Style/AbstractStyle.php @@ -50,6 +50,9 @@ abstract class AbstractStyle */ protected $aliases = []; + /** @var string */ + protected $basedOn = ''; + /** * Is this an automatic style? (Used primarily in OpenDocument driver). * @@ -83,6 +86,18 @@ public function setStyleName($value) return $this; } + public function getBasedOn(): string + { + return $this->basedOn; + } + + public function setBasedOn(string $value): self + { + $this->basedOn = $value; + + return $this; + } + /** * Get index number. * diff --git a/src/PhpWord/Style/Border.php b/src/PhpWord/Style/Border.php index 28e340c040..8be7298840 100644 --- a/src/PhpWord/Style/Border.php +++ b/src/PhpWord/Style/Border.php @@ -528,6 +528,14 @@ public function setBorderBottomStyle($value = null) public function hasBorder() { $borders = $this->getBorderSize(); + if ($borders !== array_filter($borders, 'is_null')) { + return true; + } + $borders = $this->getBorderColor(); + if ($borders !== array_filter($borders, 'is_null')) { + return true; + } + $borders = $this->getBorderStyle(); return $borders !== array_filter($borders, 'is_null'); } diff --git a/src/PhpWord/Style/Paragraph.php b/src/PhpWord/Style/Paragraph.php index c77617403d..a2ab326400 100644 --- a/src/PhpWord/Style/Paragraph.php +++ b/src/PhpWord/Style/Paragraph.php @@ -22,6 +22,7 @@ use PhpOffice\PhpWord\Shared\Text; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\TextAlignment; +use PhpOffice\PhpWord\SimpleType\TextDirection; /** * Paragraph style. @@ -69,7 +70,7 @@ class Paragraph extends Border * * @var string */ - private $basedOn = 'Normal'; + protected $basedOn = 'Normal'; /** * Style for next paragraph. @@ -181,6 +182,13 @@ class Paragraph extends Border */ private $textAlignment; + /** + * Text direction right or left, top or bottom. + * + * @var string + */ + private $textDirection = ''; + /** * Suppress hyphenation for paragraph. * @@ -241,6 +249,7 @@ public function getStyleValues() 'contextualSpacing' => $this->hasContextualSpacing(), 'bidi' => $this->isBidi(), 'textAlignment' => $this->getTextAlignment(), + 'textDirection' => $this->getTextDirection(), 'suppressAutoHyphens' => $this->hasSuppressAutoHyphens(), ]; @@ -273,30 +282,6 @@ public function setAlignment($value) return $this; } - /** - * Get parent style ID. - * - * @return string - */ - public function getBasedOn() - { - return $this->basedOn; - } - - /** - * Set parent style ID. - * - * @param string $value - * - * @return self - */ - public function setBasedOn($value = 'Normal') - { - $this->basedOn = $value; - - return $this; - } - /** * Get style for next paragraph. * @@ -807,6 +792,19 @@ public function setTextAlignment($textAlignment) return $this; } + public function getTextDirection(): string + { + return ($this->textDirection === '' && $this->isBidi()) ? TextDirection::TBRL : $this->textDirection; + } + + public function setTextDirection(string $textDirection): self + { + TextDirection::validate($textDirection); + $this->textDirection = $textDirection; + + return $this; + } + /** * @return bool */ diff --git a/src/PhpWord/Style/Table.php b/src/PhpWord/Style/Table.php index 3adb1a38f5..59c467affd 100644 --- a/src/PhpWord/Style/Table.php +++ b/src/PhpWord/Style/Table.php @@ -110,6 +110,20 @@ class Table extends Border */ private $borderInsideVColor; + /** + * Border style inside horizontal. + * + * @var string + */ + protected $borderInsideHStyle = ''; + + /** + * Border style inside vertical. + * + * @var string + */ + protected $borderInsideVStyle = ''; + /** * Shading. * @@ -168,6 +182,9 @@ class Table extends Border */ private $bidiVisual; + /** @var string */ + private $tblStyle = ''; + /** * Create new table style. * @@ -260,6 +277,42 @@ public function getBorderSize() ]; } + /** + * Get border style. + * + * @return string[] + */ + public function getBorderStyle() + { + return [ + $this->getBorderTopStyle(), + $this->getBorderLeftStyle(), + $this->getBorderRightStyle(), + $this->getBorderBottomStyle(), + $this->getBorderInsideHStyle(), + $this->getBorderInsideVStyle(), + ]; + } + + /** + * Set border style. + * + * @param string $value + * + * @return self + */ + public function setBorderStyle($value = null) + { + $this->setBorderTopStyle($value); + $this->setBorderLeftStyle($value); + $this->setBorderRightStyle($value); + $this->setBorderBottomStyle($value); + $this->setBorderInsideHStyle($value); + $this->setBorderInsideVStyle($value); + + return $this; + } + /** * Set TLRBHV Border Size. * @@ -315,6 +368,26 @@ public function setBorderColor($value = null) return $this; } + /** + * Get border style inside horizontal. + * + * @return string + */ + public function getBorderInsideHStyle() + { + return (string) $this->getTableOnlyProperty('borderInsideHStyle'); + } + + /** + * Get border style inside vertical. + * + * @return string + */ + public function getBorderInsideVStyle() + { + return (string) $this->getTableOnlyProperty('borderInsideVStyle'); + } + /** * Get border size inside horizontal. * @@ -337,6 +410,34 @@ public function setBorderInsideHSize($value = null) return $this->setTableOnlyProperty('borderInsideHSize', $value); } + /** + * Set border style inside horizontal. + * + * @param string $value + * + * @return self + */ + public function setBorderInsideHStyle($value = '') + { + $this->setTableOnlyProperty('borderInsideHStyle', $value, false); + + return $this; + } + + /** + * Set border style inside horizontal. + * + * @param ?string $value + * + * @return self + */ + public function setBorderInsideVStyle($value = null) + { + $this->setTableOnlyProperty('borderInsideVStyle', $value, false); + + return $this; + } + /** * Get border color inside horizontal. * @@ -791,4 +892,16 @@ public function setBidiVisual($bidi) return $this; } + + public function getTblStyle(): string + { + return $this->tblStyle; + } + + public function setTblStyle(string $tblStyle): self + { + $this->tblStyle = $tblStyle; + + return $this; + } } diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 8aee40c546..840520e008 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -275,9 +275,13 @@ protected static function ensureUtf8Encoded($subject) /** * @param string $search */ - public function setComplexValue($search, Element\AbstractElement $complexType): void + public function setComplexValue($search, Element\AbstractElement $complexType, bool $multiple = false): void { + $originalSearch = $search; $elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1); + if ($elementName === 'Section') { + $elementName = 'Container'; + } $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; $xmlWriter = new XMLWriter(); @@ -297,6 +301,9 @@ public function setComplexValue($search, Element\AbstractElement $complexType): $search = static::ensureMacroCompleted($search); $this->replaceXmlBlock($search, $xmlWriter->getData(), 'w:r'); + if ($multiple === true) { + $this->setComplexValue($originalSearch, $complexType, true); + } } /** @@ -305,6 +312,9 @@ public function setComplexValue($search, Element\AbstractElement $complexType): public function setComplexBlock($search, Element\AbstractElement $complexType): void { $elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1); + if ($elementName === 'Section') { + $elementName = 'Container'; + } $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; $xmlWriter = new XMLWriter(); diff --git a/src/PhpWord/Writer/HTML/Element/Table.php b/src/PhpWord/Writer/HTML/Element/Table.php index c7a23d2fe1..742d09ffd5 100644 --- a/src/PhpWord/Writer/HTML/Element/Table.php +++ b/src/PhpWord/Writer/HTML/Element/Table.php @@ -41,7 +41,8 @@ public function write() $rows = $this->element->getRows(); $rowCount = count($rows); if ($rowCount > 0) { - $content .= 'getTableStyle($this->element->getStyle()) . '>' . PHP_EOL; + $tableCss = $this->getTableStyle($this->element->getStyle()); + $content .= '' . PHP_EOL; for ($i = 0; $i < $rowCount; ++$i) { /** @var \PhpOffice\PhpWord\Element\Row $row Type hint */ @@ -53,7 +54,7 @@ public function write() $rowCellCount = count($rowCells); for ($j = 0; $j < $rowCellCount; ++$j) { $cellStyle = $rowCells[$j]->getStyle(); - $cellStyleCss = $this->getTableStyle($cellStyle); + $cellStyleCss = $this->getTableStyle($cellStyle) ?: $tableCss; $cellBgColor = $cellStyle->getBgColor(); $cellFgColor = null; if ($cellBgColor && $cellBgColor !== 'auto') { diff --git a/src/PhpWord/Writer/HTML/Element/Title.php b/src/PhpWord/Writer/HTML/Element/Title.php index 65e6cb090b..6454b45cf9 100644 --- a/src/PhpWord/Writer/HTML/Element/Title.php +++ b/src/PhpWord/Writer/HTML/Element/Title.php @@ -17,7 +17,10 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; +use PhpOffice\PhpWord\Element\Title as PhpWordTitle; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Writer\HTML; +use PhpOffice\PhpWord\Writer\HTML\Style\Font; /** * TextRun element HTML writer. @@ -33,7 +36,7 @@ class Title extends AbstractElement */ public function write() { - if (!$this->element instanceof \PhpOffice\PhpWord\Element\Title) { + if (!$this->element instanceof PhpWordTitle) { return ''; } @@ -46,8 +49,14 @@ public function write() $writer = new Container($this->parentWriter, $text); $text = $writer->write(); } + $css = ''; + $style = Style::getStyle('Heading_' . $this->element->getDepth()); + if ($style !== null) { + $styleWriter = new Font($style); + $css = ' style="' . $styleWriter->write() . '"'; + } - $content = "<{$tag}>{$text}" . PHP_EOL; + $content = "<{$tag}{$css}>{$text}" . PHP_EOL; return $content; } diff --git a/src/PhpWord/Writer/HTML/Part/Head.php b/src/PhpWord/Writer/HTML/Part/Head.php index 0f3f86e3d2..e31432ef1c 100644 --- a/src/PhpWord/Writer/HTML/Part/Head.php +++ b/src/PhpWord/Writer/HTML/Part/Head.php @@ -90,17 +90,16 @@ private function writeStyles(): string 'font-family' => $this->getFontFamily(Settings::getDefaultFontName(), $this->getParentWriter()->getDefaultGenericFont()), 'font-size' => Settings::getDefaultFontSize() . 'pt', ]; - // Mpdf sometimes needs separate tag for body; doesn't harm others. - $bodyarray = $astarray; $defaultWhiteSpace = $this->getParentWriter()->getDefaultWhiteSpace(); if ($defaultWhiteSpace) { $astarray['white-space'] = $defaultWhiteSpace; } + $bodyarray = $astarray; foreach ([ 'body' => $bodyarray, - '*' => $astarray, + //'*' => $astarray, 'a.NoteRef' => [ 'text-decoration' => 'none', ], @@ -119,6 +118,9 @@ private function writeStyles(): string 'td' => [ 'border' => '1px solid black', ], + 'th' => [ + 'border' => '1px solid black', + ], ] as $selector => $style) { $styleWriter = new GenericStyleWriter($style); $css .= $selector . ' {' . $styleWriter->write() . '}' . PHP_EOL; @@ -137,8 +139,8 @@ private function writeStyles(): string $style = $styleParagraph; } else { $name = '.' . $name; + $css .= "{$name} {" . $styleWriter->write() . '}' . PHP_EOL; } - $css .= "{$name} {" . $styleWriter->write() . '}' . PHP_EOL; } if ($style instanceof Paragraph) { $styleWriter = new ParagraphStyleWriter($style); diff --git a/src/PhpWord/Writer/HTML/Style/Font.php b/src/PhpWord/Writer/HTML/Style/Font.php index eb59d02d1e..29b35687a7 100644 --- a/src/PhpWord/Writer/HTML/Style/Font.php +++ b/src/PhpWord/Writer/HTML/Style/Font.php @@ -73,6 +73,13 @@ public function write() } elseif ($style->isRTL() === false) { $css['direction'] = 'ltr'; } + $shading = $style->getShading(); + if ($shading !== null) { + $fill = $shading->getFill(); + if (!empty($fill)) { + $css['background-color'] = preg_match('/^[0-9a-fA-F]{6}$/', $fill) ? "#$fill" : $fill; + } + } return $this->assembleCss($css); } diff --git a/src/PhpWord/Writer/HTML/Style/Table.php b/src/PhpWord/Writer/HTML/Style/Table.php index d2c318a69f..f4b50f56c8 100644 --- a/src/PhpWord/Writer/HTML/Style/Table.php +++ b/src/PhpWord/Writer/HTML/Style/Table.php @@ -54,7 +54,7 @@ public function write() if ($outval === 'single') { $outval = 'solid'; } - if (is_string($outval) && 1 == preg_match('/^[a-z]+$/', $outval)) { + if (is_string($outval) && 1 === preg_match('/^[a-z]+$/', $outval)) { $css['border-' . lcfirst($direction) . '-style'] = $outval; } } @@ -62,7 +62,9 @@ public function write() $method = 'getBorder' . $direction . 'Color'; if (method_exists($style, $method)) { $outval = $style->{$method}(); - if (is_string($outval) && 1 == preg_match('/^[a-z]+$/', $outval)) { + if (is_string($outval) && 1 === preg_match('/^[a-fA-F0-9]{6}$/', $outval)) { + $css['border-' . lcfirst($direction) . '-color'] = "#$outval"; + } elseif (is_string($outval) && 1 === preg_match('/^[a-z][a-z0-9]+$/', $outval)) { $css['border-' . lcfirst($direction) . '-color'] = $outval; } } diff --git a/src/PhpWord/Writer/RTF.php b/src/PhpWord/Writer/RTF.php index 0a04d4f53e..e588b8a1d5 100644 --- a/src/PhpWord/Writer/RTF.php +++ b/src/PhpWord/Writer/RTF.php @@ -67,7 +67,7 @@ public function save(string $filename): void * * @since 0.11.0 */ - private function getContent() + public function getContent() { $content = ''; diff --git a/src/PhpWord/Writer/RTF/Element/Title.php b/src/PhpWord/Writer/RTF/Element/Title.php index fb11da7849..cef9038571 100644 --- a/src/PhpWord/Writer/RTF/Element/Title.php +++ b/src/PhpWord/Writer/RTF/Element/Title.php @@ -57,8 +57,13 @@ public function write() { /** @var \PhpOffice\PhpWord\Element\Title $element Type hint */ $element = $this->element; + $text = method_exists($element, 'getText') ? $element->getText() : null; + // check for text run + if (is_object($text) && method_exists($text, 'getText')) { + $text = $text->getText(); + } $elementClass = str_replace('\\Writer\\RTF', '', static::class); - if (!$element instanceof $elementClass || !is_string($element->getText())) { + if (!$element instanceof $elementClass || !is_string($text)) { return ''; } @@ -82,7 +87,7 @@ public function write() $content .= '{'; $content .= $this->writeFontStyle(); - $content .= $this->writeText($element->getText()); + $content .= $this->writeText($text); $content .= '}'; $content .= $this->writeClosing(); $content .= $endout; 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/Styles.php b/src/PhpWord/Writer/Word2007/Part/Styles.php index 2112fd3ce6..41ff553c3c 100644 --- a/src/PhpWord/Writer/Word2007/Part/Styles.php +++ b/src/PhpWord/Writer/Word2007/Part/Styles.php @@ -59,12 +59,13 @@ public function write() if ($styleName == 'Normal') { continue; } + $name = $style->getStyleName(); // Get style class and execute if the private method exists $styleClass = substr(get_class($style), strrpos(get_class($style), '\\') + 1); $method = "write{$styleClass}Style"; if (method_exists($this, $method)) { - $this->$method($xmlWriter, $styleName, $style); + $this->$method($xmlWriter, $styleName, $style, $name); } } } @@ -163,7 +164,7 @@ private function writeDefaultStyles(XMLWriter $xmlWriter, $styles): void * * @param string $styleName */ - private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $style): void + private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $style, string $name): void { $paragraphStyle = $style->getParagraph(); $styleType = $style->getStyleType(); @@ -204,7 +205,7 @@ private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $sty // Parent style if (null !== $paragraphStyle) { - if ($paragraphStyle->getStyleName() != null) { + if (!empty($paragraphStyle->getStyleName())) { $xmlWriter->writeElementBlock('w:basedOn', 'w:val', $paragraphStyle->getStyleName()); } elseif ($paragraphStyle->getBasedOn() != null) { $xmlWriter->writeElementBlock('w:basedOn', 'w:val', $paragraphStyle->getBasedOn()); @@ -229,7 +230,7 @@ private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $sty * * @param string $styleName */ - private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, ParagraphStyle $style): void + private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, ParagraphStyle $style, string $name): void { $xmlWriter->startElement('w:style'); $xmlWriter->writeAttribute('w:type', 'paragraph'); @@ -241,7 +242,7 @@ private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, Paragraph // Parent style $basedOn = $style->getBasedOn(); - $xmlWriter->writeElementIf(null !== $basedOn, 'w:basedOn', 'w:val', $basedOn); + $xmlWriter->writeElementIf('' !== $basedOn, 'w:basedOn', 'w:val', $basedOn); // Next paragraph style $next = $style->getNext(); @@ -259,15 +260,17 @@ private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, Paragraph * * @param string $styleName */ - private function writeTableStyle(XMLWriter $xmlWriter, $styleName, TableStyle $style): void + private function writeTableStyle(XMLWriter $xmlWriter, $styleName, TableStyle $style, string $name): void { $xmlWriter->startElement('w:style'); $xmlWriter->writeAttribute('w:type', 'table'); $xmlWriter->writeAttribute('w:customStyle', '1'); $xmlWriter->writeAttribute('w:styleId', $styleName); $xmlWriter->startElement('w:name'); - $xmlWriter->writeAttribute('w:val', $styleName); + $xmlWriter->writeAttribute('w:val', $name ?: $styleName); $xmlWriter->endElement(); + $basedOn = $style->getBasedOn(); + $xmlWriter->writeElementIf('' !== $basedOn, 'w:basedOn', 'w:val', $basedOn); $xmlWriter->startElement('w:uiPriority'); $xmlWriter->writeAttribute('w:val', '99'); $xmlWriter->endElement(); diff --git a/src/PhpWord/Writer/Word2007/Style/Font.php b/src/PhpWord/Writer/Word2007/Style/Font.php index 8d9697715c..16b4f9ec95 100644 --- a/src/PhpWord/Writer/Word2007/Style/Font.php +++ b/src/PhpWord/Writer/Word2007/Style/Font.php @@ -153,10 +153,7 @@ private function writeStyle(): void } // RTL - if ($this->isInline === true) { - $styleName = $style->getStyleName(); - $xmlWriter->writeElementIf($styleName === null && $style->isRTL(), 'w:rtl'); - } + $xmlWriter->writeElementIf($style->isRTL(), 'w:rtl'); // Position $xmlWriter->writeElementIf($style->getPosition() !== null, 'w:position', 'w:val', $style->getPosition()); diff --git a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php index 8d08eec3cc..15cf0e4eeb 100644 --- a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php +++ b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php @@ -64,14 +64,12 @@ public function write(): void $sides = ['top', 'left', 'right', 'bottom', 'insideH', 'insideV']; foreach ($this->sizes as $i => $size) { - if ($size !== null) { - $color = null; - if (isset($this->colors[$i])) { - $color = $this->colors[$i]; - } - $style = $this->styles[$i] ?? 'single'; - $this->writeSide($xmlWriter, $sides[$i], $this->sizes[$i], $color, $style); + $color = null; + if (isset($this->colors[$i])) { + $color = $this->colors[$i]; } + $style = $this->styles[$i] ?? 'single'; + $this->writeSide($xmlWriter, $sides[$i], $this->sizes[$i], $color, $style); } } @@ -79,8 +77,8 @@ public function write(): void * Write side. * * @param string $side - * @param int $width - * @param string $color + * @param ?int $width + * @param ?string $color * @param string $borderStyle */ private function writeSide(XMLWriter $xmlWriter, $side, $width, $color = null, $borderStyle = 'solid'): void @@ -93,7 +91,7 @@ private function writeSide(XMLWriter $xmlWriter, $side, $width, $color = null, $ } } $xmlWriter->writeAttribute('w:val', $borderStyle); - $xmlWriter->writeAttribute('w:sz', $width); + $xmlWriter->writeAttributeIf($width != null, 'w:sz', $width); $xmlWriter->writeAttributeIf($color != null, 'w:color', $color); if (!empty($this->attributes)) { if (isset($this->attributes['space'])) { diff --git a/src/PhpWord/Writer/Word2007/Style/Paragraph.php b/src/PhpWord/Writer/Word2007/Style/Paragraph.php index d4ec87a1ab..f66c4df430 100644 --- a/src/PhpWord/Writer/Word2007/Style/Paragraph.php +++ b/src/PhpWord/Writer/Word2007/Style/Paragraph.php @@ -105,6 +105,11 @@ private function writeStyle(): void //Right to left $xmlWriter->writeElementIf($styles['bidi'] === true, 'w:bidi'); + if ($styles['textDirection'] !== '') { + $xmlWriter->startElement('w:textDirection'); + $xmlWriter->writeAttribute('w:val', $styles['textDirection']); + $xmlWriter->endElement(); // w:textDirection + } //Paragraph contextualSpacing $xmlWriter->writeElementIf($styles['contextualSpacing'] === true, 'w:contextualSpacing'); diff --git a/src/PhpWord/Writer/Word2007/Style/Table.php b/src/PhpWord/Writer/Word2007/Style/Table.php index 05cec492ca..ea600a2403 100644 --- a/src/PhpWord/Writer/Word2007/Style/Table.php +++ b/src/PhpWord/Writer/Word2007/Style/Table.php @@ -63,6 +63,8 @@ private function writeStyle(XMLWriter $xmlWriter, TableStyle $style): void { // w:tblPr $xmlWriter->startElement('w:tblPr'); + $tblStyle = $style->getTblStyle(); + $xmlWriter->writeElementIf($tblStyle !== '', 'w:tblStyle', 'w:val', $tblStyle); // Table alignment if ('' !== $style->getAlignment()) { @@ -139,6 +141,7 @@ private function writeBorder(XMLWriter $xmlWriter, TableStyle $style): void $styleWriter = new MarginBorder($xmlWriter); $styleWriter->setSizes($style->getBorderSize()); $styleWriter->setColors($style->getBorderColor()); + $styleWriter->setStyles($style->getBorderStyle()); $styleWriter->write(); $xmlWriter->endElement(); // w:tblBorders diff --git a/tests/PhpWordTests/Reader/Word2007/StyleTableTest.php b/tests/PhpWordTests/Reader/Word2007/StyleTableTest.php new file mode 100644 index 0000000000..fd1a69c0b8 --- /dev/null +++ b/tests/PhpWordTests/Reader/Word2007/StyleTableTest.php @@ -0,0 +1,55 @@ +load($file); + self::assertSame('Times New Roman', $phpWord->getDefaultFontName()); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf(Table::class, $elements[2]); + $style = $elements[2]->getStyle(); + self::assertIsObject($style); + self::assertSame('Tablaconcuadrcula', $style->getTblStyle()); + self::assertSame('none', $style->getBorderTopStyle()); + $baseStyle = Style::getStyle('Tablaconcuadrcula'); + self::assertInstanceOf(TableStyle::class, $baseStyle); + self::assertSame('Table Grid', $baseStyle->getStyleName()); + self::assertSame('Tablanormal', $baseStyle->getBasedOn()); + self::assertSame('single', $baseStyle->getBorderTopStyle()); + } +} diff --git a/tests/PhpWordTests/SettingsRtlTest.php b/tests/PhpWordTests/SettingsRtlTest.php new file mode 100644 index 0000000000..d9e85242e1 --- /dev/null +++ b/tests/PhpWordTests/SettingsRtlTest.php @@ -0,0 +1,81 @@ +defaultRtl = Settings::isDefaultRtl(); + } + + protected function tearDown(): void + { + Settings::setDefaultRtl($this->defaultRtl); + } + + public function testSetGetDefaultRtl(): void + { + self::assertNull(Settings::isDefaultRtl()); + Settings::setDefaultRtl(true); + self::assertTrue(Settings::isDefaultRtl()); + Settings::setDefaultRtl(false); + self::assertFalse(Settings::isDefaultRtl()); + Settings::setDefaultRtl(null); + self::assertNull(Settings::isDefaultRtl()); + } + + public function testNormalStyleAdded(): void + { + $phpWord = new PhpWord(); + self::assertNull(Settings::isDefaultRtl()); + Settings::setDefaultRtl(true); + $style = Style::getStyle('Normal'); + self::assertInstanceOf(Font::class, $style); + self::assertTrue($style->isRtl()); + $paragraph = $style->getParagraph(); + self::assertTrue($paragraph->isBidi()); + self::assertSame(TextDirection::RLTB, $paragraph->getTextDirection()); + } + + public function testNormalStyleNotReplaced(): void + { + $phpWord = new PhpWord(); + $phpWord->setDefaultParagraphStyle([]); + $style = Style::getStyle('Normal'); + self::assertInstanceOf(Paragraph::class, $style); + self::assertNotTrue($style->isBidi()); + self::assertSame(TextDirection::NONE, $style->getTextDirection()); + } +} diff --git a/tests/PhpWordTests/SettingsTest.php b/tests/PhpWordTests/SettingsTest.php index 46c72eab28..15b79189a1 100644 --- a/tests/PhpWordTests/SettingsTest.php +++ b/tests/PhpWordTests/SettingsTest.php @@ -53,9 +53,6 @@ class SettingsTest extends TestCase private $zipClass; - /** @var bool */ - private $defaultRtl; - protected function setUp(): void { $this->compatibility = Settings::hasCompatibility(); @@ -69,7 +66,6 @@ protected function setUp(): void $this->pdfRendererPath = Settings::getPdfRendererPath(); $this->tempDir = Settings::getTempDir(); $this->zipClass = Settings::getZipClass(); - $this->defaultRtl = Settings::isDefaultRtl(); } protected function tearDown(): void @@ -85,7 +81,6 @@ protected function tearDown(): void Settings::setPdfRendererPath($this->pdfRendererPath); Settings::setTempDir($this->tempDir); Settings::setZipClass($this->zipClass); - Settings::setDefaultRtl($this->defaultRtl); } /** @@ -108,17 +103,6 @@ public function testSetGetOutputEscapingEnabled(): void self::assertTrue(Settings::isOutputEscapingEnabled()); } - public function testSetGetDefaultRtl(): void - { - self::assertNull(Settings::isDefaultRtl()); - Settings::setDefaultRtl(true); - self::assertTrue(Settings::isDefaultRtl()); - Settings::setDefaultRtl(false); - self::assertFalse(Settings::isDefaultRtl()); - Settings::setDefaultRtl(null); - self::assertNull(Settings::isDefaultRtl()); - } - /** * Test set/get zip class. */ diff --git a/tests/PhpWordTests/Shared/Html2402Test.php b/tests/PhpWordTests/Shared/Html2402Test.php new file mode 100644 index 0000000000..f3f7f78c0a --- /dev/null +++ b/tests/PhpWordTests/Shared/Html2402Test.php @@ -0,0 +1,208 @@ + + + + header a + header b + header c + + + + 12 + This is bold text6 + + +HTML; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, $html, false, false); + $elements = $section->getElements(); + $table = $elements[0]; + self::assertInstanceOf(Table::class, $table); + $style = $table->getStyle(); + self::assertInstanceOf(TableStyle::class, $style); + self::assertSame('none', $style->getBorderBottomStyle()); + $rows = $table->getRows(); + self::assertCount(3, $rows); + $cells = $rows[1]->getCells(); + self::assertCount(2, $cells); + self::assertSame('dotted', $cells[0]->getStyle()->getBorderRightStyle()); + self::assertSame('FF0000', $cells[0]->getStyle()->getBorderRightColor()); + self::assertEmpty($cells[1]->getStyle()->getBorderRightStyle()); + $writer = new HtmlWriter($phpWord); + $content = $writer->getContent(); + $substring = 'table-layout: auto; border-top-style: none; border-top-width: 0pt; border-left-style: none; border-left-width: 0pt; border-bottom-style: none; border-bottom-width: 0pt; border-right-style: none; border-right-width: 0pt;'; + $count = substr_count($content, $substring); + $expected = substr_count($content, 'header c', $content); + } + + public function testParseTableStyleBorderNone(): void + { + $html = << + + + + + + + + + + + +
header aheader bheader c
12
This is bold text6
+HTML; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, $html, false, false); + $elements = $section->getElements(); + $table = $elements[0]; + self::assertInstanceOf(Table::class, $table); + $style = $table->getStyle(); + self::assertInstanceOf(TableStyle::class, $style); + self::assertSame('none', $style->getBorderBottomStyle()); + $rows = $table->getRows(); + self::assertCount(3, $rows); + $cells = $rows[1]->getCells(); + self::assertCount(2, $cells); + self::assertSame('dotted', $cells[0]->getStyle()->getBorderRightStyle()); + self::assertSame('ff0000', $cells[0]->getStyle()->getBorderRightColor()); + self::assertEmpty($cells[1]->getStyle()->getBorderRightStyle()); + $writer = new HtmlWriter($phpWord); + $content = $writer->getContent(); + $substring = 'table-layout: auto; border-top-style: none; border-left-style: none; border-bottom-style: none; border-right-style: none;'; + $count = substr_count($content, $substring); + $expected = substr_count($content, ' + + + + + + + + + + + +
header aheader bheader c
12
This is bold text6
+HTML; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, $html, false, false); + $elements = $section->getElements(); + $table = $elements[0]; + self::assertInstanceOf(Table::class, $table); + $style = $table->getStyle(); + self::assertInstanceOf(TableStyle::class, $style); + self::assertSame('none', $style->getBorderBottomStyle()); + $rows = $table->getRows(); + self::assertCount(3, $rows); + $cells = $rows[1]->getCells(); + self::assertCount(2, $cells); + self::assertSame('dotted', $cells[0]->getStyle()->getBorderRightStyle()); + self::assertSame('ff0000', $cells[0]->getStyle()->getBorderRightColor()); + self::assertEmpty($cells[1]->getStyle()->getBorderRightStyle()); + } + + public function testParseTableStyleBorder2px(): void + { + $html = << + + + header a + header b + header c + + + + 12 + This is bold text6 + + +HTML; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, $html, false, false); + $elements = $section->getElements(); + $table = $elements[0]; + self::assertInstanceOf(Table::class, $table); + $style = $table->getStyle(); + self::assertInstanceOf(TableStyle::class, $style); + self::assertSame('dashed', $style->getBorderBottomStyle()); + self::assertSame('dashed', $style->getBorderInsideHStyle()); + self::assertSame('dashed', $style->getBorderInsideVStyle()); + self::assertSame(15, $style->getBorderBottomSize()); + self::assertSame('00ff00', $style->getBorderBottomColor()); + $rows = $table->getRows(); + self::assertCount(3, $rows); + $cells = $rows[1]->getCells(); + self::assertCount(2, $cells); + self::assertSame('dotted', $cells[0]->getStyle()->getBorderRightStyle()); + self::assertSame('ff0000', $cells[0]->getStyle()->getBorderRightColor()); + self::assertEmpty($cells[1]->getStyle()->getBorderRightStyle()); + $writer = new HtmlWriter($phpWord); + $content = $writer->getContent(); + + $substring = 'table-layout: auto; border-top-style: dashed; border-top-color: #00ff00; border-top-width: 0.75pt; border-left-style: dashed; border-left-color: #00ff00; border-left-width: 0.75pt; border-bottom-style: dashed; border-bottom-color: #00ff00; border-bottom-width: 0.75pt; border-right-style: dashed; border-right-color: #00ff00; border-right-width: 0.75pt;'; + $count = substr_count($content, $substring); + $expected = substr_count($content, 'addSection(); + $htmlContent = << + + + +Testing Head Section + + + + + + +

This is bold text.

+ + +EOF; + Html::addHtml($section, $htmlContent, true, true); + self::assertSame('Testing Head Section', $phpWord->getDocInfo()->getTitle()); + self::assertSame('PhpWord Test', $phpWord->getDocInfo()->getCreator()); + self::assertSame('testing html read including meta tags', $phpWord->getDocInfo()->getDescription()); + $elements = $section->getElements(); + self::assertCount(1, $elements); + $element = $elements[0]; + self::assertInstanceOf(TextRun::class, $element); + $textElements = $element->getElements(); + self::assertCount(3, $textElements); + + $textElement = $textElements[0]; + self::assertInstanceOf(Text::class, $textElement); + $style = $textElement->getFontStyle(); + self::assertInstanceOf(Font::class, $style); + self::assertNotTrue($style->isBold()); + self::assertSame('This is ', $textElement->getText()); + + $textElement = $textElements[1]; + self::assertInstanceOf(Text::class, $textElement); + $style = $textElement->getFontStyle(); + self::assertInstanceOf(Font::class, $style); + self::assertTrue($style->isBold()); + self::assertSame('bold', $textElement->getText()); + + $textElement = $textElements[2]; + self::assertInstanceOf(Text::class, $textElement); + $style = $textElement->getFontStyle(); + self::assertInstanceOf(Font::class, $style); + self::assertNotTrue($style->isBold()); + self::assertSame(' text.', $textElement->getText()); + } +} diff --git a/tests/PhpWordTests/Shared/HtmlHeadingsTest.php b/tests/PhpWordTests/Shared/HtmlHeadingsTest.php new file mode 100644 index 0000000000..4704e8b3e1 --- /dev/null +++ b/tests/PhpWordTests/Shared/HtmlHeadingsTest.php @@ -0,0 +1,66 @@ +addTitleStyle(1, ['size' => 20]); + $section = $originalDoc->addSection(); + $expectedStrings = []; + $section->addTitle('Title 1', 1); + $expectedStrings[] = '

Title 1

'; + for ($i = 2; $i <= 6; ++$i) { + $textRun = new TextRun(); + $textRun->addText('Title '); + $textRun->addText("$i", ['italic' => true]); + $section->addTitle($textRun, $i); + $expectedStrings[] = "Title $i"; + } + $writer = new HtmlWriter($originalDoc); + $content = $writer->getContent(); + foreach ($expectedStrings as $expectedString) { + self::assertStringContainsString($expectedString, $content); + } + + $newDoc = new PhpWord(); + $newSection = $newDoc->addSection(); + SharedHtml::addHtml($newSection, $content, true); + $newWriter = new HtmlWriter($newDoc); + $newContent = $newWriter->getContent(); + // Reader transforms Text to TextRun, + // but result is functionally the same. + $firstStringAsTextRun = '

Title 1

'; + self::assertSame($content, str_replace($firstStringAsTextRun, $expectedStrings[0], $newContent)); + } +} diff --git a/tests/PhpWordTests/Shared/HtmlRtlTest.php b/tests/PhpWordTests/Shared/HtmlRtlTest.php new file mode 100644 index 0000000000..219437fd9c --- /dev/null +++ b/tests/PhpWordTests/Shared/HtmlRtlTest.php @@ -0,0 +1,180 @@ +addSection(); + $html = '

test1.

'; + $html .= '

test2.

'; + $html .= '

test3.

'; + Html::addHtml($section, $html); + $elements = $section->getElements(); + self::assertCount(3, $elements); + + $index = 0; + $element = $elements[$index]; + self::assertInstanceOf(TextRun::class, $element); + $paragraphStyle = $element->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + self::assertTrue($paragraphStyle->isBidi()); + self::assertSame('tbRl', $paragraphStyle->getTextDirection()); + $textElements = $element->getElements(); + self::assertCount(1, $textElements); + $textElement = $textElements[0]; + self::assertInstanceOf(Text::class, $textElement); + self::assertInstanceOf(Font::class, $textElement->getFontStyle()); + self::assertTrue($textElement->getFontStyle()->isRtl()); + + $index = 1; + $element = $elements[$index]; + self::assertInstanceOf(TextRun::class, $element); + $paragraphStyle = $element->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + self::assertFalse($paragraphStyle->isBidi()); + self::assertSame('lrTb', $paragraphStyle->getTextDirection()); + $textElements = $element->getElements(); + self::assertCount(1, $textElements); + $textElement = $textElements[0]; + self::assertInstanceOf(Text::class, $textElement); + self::assertInstanceOf(Font::class, $textElement->getFontStyle()); + self::assertFalse($textElement->getFontStyle()->isRtl()); + + $index = 2; + $element = $elements[$index]; + self::assertInstanceOf(TextRun::class, $element); + $paragraphStyle = $element->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + self::assertNull($paragraphStyle->isBidi()); + self::assertSame('', $paragraphStyle->getTextDirection()); + $textElements = $element->getElements(); + self::assertCount(1, $textElements); + $textElement = $textElements[0]; + self::assertInstanceOf(Text::class, $textElement); + self::assertInstanceOf(Font::class, $textElement->getFontStyle()); + self::assertNull($textElement->getFontStyle()->isRtl()); + } + + public function testParseHtmlDir(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '

test1.

'; + $html .= '

test2.

'; + $html .= '

test3.

'; + Html::addHtml($section, $html); + $elements = $section->getElements(); + self::assertCount(3, $elements); + + $index = 0; + $element = $elements[$index]; + self::assertInstanceOf(TextRun::class, $element); + $paragraphStyle = $element->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + self::assertTrue($paragraphStyle->isBidi()); + self::assertSame('tbRl', $paragraphStyle->getTextDirection()); + $textElements = $element->getElements(); + self::assertCount(1, $textElements); + $textElement = $textElements[0]; + self::assertInstanceOf(Text::class, $textElement); + self::assertInstanceOf(Font::class, $textElement->getFontStyle()); + self::assertTrue($textElement->getFontStyle()->isRtl()); + + $index = 1; + $element = $elements[$index]; + self::assertInstanceOf(TextRun::class, $element); + $paragraphStyle = $element->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + self::assertFalse($paragraphStyle->isBidi()); + self::assertSame('lrTb', $paragraphStyle->getTextDirection()); + $textElements = $element->getElements(); + self::assertCount(1, $textElements); + $textElement = $textElements[0]; + self::assertInstanceOf(Text::class, $textElement); + self::assertInstanceOf(Font::class, $textElement->getFontStyle()); + self::assertFalse($textElement->getFontStyle()->isRtl()); + + $index = 2; + $element = $elements[$index]; + self::assertInstanceOf(TextRun::class, $element); + $paragraphStyle = $element->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $paragraphStyle); + self::assertNull($paragraphStyle->isBidi()); + self::assertSame('', $paragraphStyle->getTextDirection()); + $textElements = $element->getElements(); + self::assertCount(1, $textElements); + $textElement = $textElements[0]; + self::assertInstanceOf(Text::class, $textElement); + self::assertInstanceOf(Font::class, $textElement->getFontStyle()); + self::assertNull($textElement->getFontStyle()->isRtl()); + } + + public function testCssClassNameOnPElement(): void + { + $phpWord = new PhpWord(); + $phpWord->addFontStyle('customClass', ['bold' => true], ['borderBottomSize' => 3, 'borderBottomColor' => '#00ff00', 'textDirection' => 'tbRl']); + $section = $phpWord->addSection(); + $html = '

test1.

'; + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord); + $path = '/w:document/w:body/w:p'; + $paragraphPath = $path . '/w:pPr'; + $element = $doc->getElement($paragraphPath . '/w:pStyle'); + self::assertSame('customClass', $element->getAttribute('w:val')); + $textPath = $path . '/w:r/w:t'; + self::assertSame('test1.', $doc->getElement($textPath)->nodeValue); + self::assertSame('customClass', $doc->getElement($path . '/w:r/w:rPr/w:rStyle')->getAttribute('w:val')); + + // Styles + $file = 'word/styles.xml'; + $path = '/w:styles/w:style[@w:styleId="customClass"]'; + $paragraphPath = $path . '/w:pPr'; + $element = $doc->getElement($paragraphPath . '/w:pBdr/w:bottom', $file); + self::assertSame('#00ff00', $element->getAttribute('w:color')); + $element = $doc->getElement($paragraphPath . '/w:textDirection', $file); + self::assertSame('tbRl', $element->getAttribute('w:val')); + $fontPath = $path . '/w:rPr'; + $element = $doc->getElement($fontPath . '/w:b', $file); + self::assertSame('1', $element->getAttribute('w:val')); + } +} diff --git a/tests/PhpWordTests/Shared/HtmlTest.php b/tests/PhpWordTests/Shared/HtmlTest.php index c8640509de..765d79f906 100644 --- a/tests/PhpWordTests/Shared/HtmlTest.php +++ b/tests/PhpWordTests/Shared/HtmlTest.php @@ -20,10 +20,14 @@ use Exception; use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\Element\Table; +use PhpOffice\PhpWord\Element\Text; +use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Shared\Html; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\LineSpacingRule; +use PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; use PhpOffice\PhpWordTests\AbstractWebServerEmbeddedTest; use PhpOffice\PhpWordTests\TestHelperDOCX; @@ -154,6 +158,37 @@ public function testParseStyleTableClassName(): void self::assertEquals('pStyle', $section->getElement(0)->getStyle()->getStyleName()); } + public function testSpanClassName(): void + { + $phpWord = new PhpWord(); + $phpWord->addFontStyle('boldtext', ['bold' => true]); + $html = '

This is bold text.

'; + $section = $phpWord->addSection(); + Html::addHtml($section, $html); + self::assertTrue(true); + $element = $section->getElements()[0]; + self::assertInstanceOf(TextRun::class, $element); + $textElements = $element->getElements(); + self::assertCount(3, $textElements); + + $text = $textElements[0]; + self::assertInstanceOf(Text::class, $text); + self::assertInstanceOf(Font::class, $text->getFontStyle()); + self::assertNotTrue($text->getFontStyle()->isBold()); + + $text = $textElements[1]; + self::assertInstanceOf(Text::class, $text); + self::assertSame('boldtext', $text->getFontStyle()); + $style = Style::getStyle('boldtext'); + self::assertInstanceOf(Font::class, $style); + self::assertTrue($style->isBold()); + + $text = $textElements[2]; + self::assertInstanceOf(Text::class, $text); + self::assertInstanceOf(Font::class, $text->getFontStyle()); + self::assertNotTrue($text->getFontStyle()->isBold()); + } + /** * Test underline. */ @@ -635,7 +670,7 @@ public function testParseTableStyleAttributeInlineStyle(): void $xpath = '/w:document/w:body/w:tbl/w:tr[1]/w:tc[1]/w:tcPr/w:shd'; self::assertTrue($doc->elementExists($xpath)); - self::assertEquals('red', $doc->getElement($xpath)->getAttribute('w:fill')); + self::assertEquals('ff0000', $doc->getElement($xpath)->getAttribute('w:fill')); } /** @@ -1016,7 +1051,7 @@ public function testParseHorizontalRule(): void self::assertTrue($doc->elementExists($xpath)); self::assertEquals('single', $doc->getElement($xpath)->getAttribute('w:val')); self::assertEquals((int) (5 * 15 / 2), $doc->getElement($xpath)->getAttribute('w:sz')); - self::assertEquals('lightblue', $doc->getElement($xpath)->getAttribute('w:color')); + self::assertEquals('add8e6', $doc->getElement($xpath)->getAttribute('w:color')); $xpath = '/w:document/w:body/w:p[4]/w:pPr/w:spacing'; self::assertTrue($doc->elementExists($xpath)); diff --git a/tests/PhpWordTests/TemplateProcessorSectionTest.php b/tests/PhpWordTests/TemplateProcessorSectionTest.php new file mode 100644 index 0000000000..0402d4fc66 --- /dev/null +++ b/tests/PhpWordTests/TemplateProcessorSectionTest.php @@ -0,0 +1,92 @@ +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); + } + } + } + + public function testSetComplexSection(): void + { + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/document22-xml.docx'); + $html = ' +

 Bug Report:

+

BugTracker X is ${facing1} an issue.

+

BugTracker X is ${facing2} an issue.

+

BugTracker X is ${facing1} an issue.

+ '; + $section = new Section(0); + Html::addHtml($section, $html, false, false); + $templateProcessor->setComplexBlock('test', $section); + $facing1 = new TextRun(); + $facing1->addText('facing', ['bold' => true]); + $facing2 = new TextRun(); + $facing2->addText('facing', ['italic' => true]); + + $templateProcessor->setComplexBlock('test', $section); + $templateProcessor->setComplexValue('facing1', $facing1, true); + $templateProcessor->setComplexValue('facing2', $facing2); + + $docName = $templateProcessor->save(); + $docFound = file_exists($docName); + self::assertTrue($docFound); + $contents = file_get_contents("zip://$docName#word/document2.xml"); + unlink($docName); + self::assertNotFalse($contents); + $contents = preg_replace('/>\s+<', $contents) ?? ''; + self::assertStringContainsString('Test', $contents); + $count = substr_count($contents, 'facing'); + self::assertSame(2, $count, 'should be 2 bold strings'); + $count = substr_count($contents, 'facing'); + self::assertSame(1, $count, 'should be 1 italic string'); + self::assertStringNotContainsString('$', $contents, 'no leftover macros'); + self::assertStringNotContainsString('facing1', $contents, 'no leftover replaced string1'); + self::assertStringNotContainsString('facing2', $contents, 'no leftover replaced string2'); + } +} 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/HTML/FontTest.php b/tests/PhpWordTests/Writer/HTML/FontTest.php index 442c2639c9..08a8fca6a4 100644 --- a/tests/PhpWordTests/Writer/HTML/FontTest.php +++ b/tests/PhpWordTests/Writer/HTML/FontTest.php @@ -84,23 +84,23 @@ public function testFontNames1(): void self::assertEquals('style5', Helper::getTextContent($xpath, '/html/body/div/p[6]/span', 'class')); $style = Helper::getTextContent($xpath, '/html/head/style'); - $prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); - self::assertEquals('* {font-family: \'Courier New\'; font-size: 12pt;}', $matches[0]); + $prg = preg_match('/^body[^\\r\\n]*/m', $style, $matches); + self::assertSame(1, $prg); + self::assertEquals('body {font-family: \'Courier New\'; font-size: 12pt;}', $matches[0]); $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]); $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style2 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]); $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style3 {font-family: \'hack attempt'}; display:none\'; font-size: 10pt;}', $matches[0]); $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style4 {font-family: \'padmaa 1.1\'; font-size: 10pt; font-weight: bold;}', $matches[0]); $prg = preg_match('/^[.]style5[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style5 {font-family: \'MingLiU-ExtB\'; font-size: 10pt; font-weight: bold;}', $matches[0]); } @@ -134,20 +134,20 @@ public function testFontNames2(): void self::assertEquals('style4', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'class')); $style = Helper::getTextContent($xpath, '/html/head/style'); - $prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); - self::assertEquals('* {font-family: \'Courier New\'; font-size: 12pt;}', $matches[0]); + $prg = preg_match('/^body[^\\r\\n]*/m', $style, $matches); + self::assertSame(1, $prg); + self::assertEquals('body {font-family: \'Courier New\'; font-size: 12pt;}', $matches[0]); $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]); $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style2 {font-family: \'Arial\', sans-serif; font-size: 10pt;}', $matches[0]); $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style3 {font-family: \'DejaVu Sans Monospace\', monospace; font-size: 10pt;}', $matches[0]); $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style4 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]); } @@ -181,20 +181,20 @@ public function testFontNames3(): void self::assertEquals('style4', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'class')); $style = Helper::getTextContent($xpath, '/html/head/style'); - $prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); - self::assertEquals('* {font-family: \'Courier New\', monospace; font-size: 12pt;}', $matches[0]); + $prg = preg_match('/^body[^\\r\\n]*/m', $style, $matches); + self::assertSame(1, $prg); + self::assertEquals('body {font-family: \'Courier New\', monospace; font-size: 12pt;}', $matches[0]); $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]); $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style2 {font-family: \'Arial\', sans-serif; font-size: 10pt;}', $matches[0]); $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style3 {font-family: \'DejaVu Sans Monospace\', monospace; font-size: 10pt;}', $matches[0]); $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style4 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]); } @@ -221,19 +221,19 @@ public function testWhiteSpace(): void $xpath = new DOMXPath($dom); $style = Helper::getTextContent($xpath, '/html/head/style'); - self::assertNotFalse(preg_match('/^[*][^\\r\\n]*/m', $style, $matches)); - self::assertEquals('* {font-family: \'Arial\'; font-size: 12pt; white-space: pre-wrap;}', $matches[0]); + self::assertNotFalse(preg_match('/^body[^\\r\\n]*/m', $style, $matches)); + self::assertEquals('body {font-family: \'Arial\'; font-size: 12pt; white-space: pre-wrap;}', $matches[0]); $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style1 {font-family: \'Courier New\'; font-size: 10pt; white-space: pre-wrap;}', $matches[0]); $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style2 {font-family: \'Courier New\'; font-size: 10pt;}', $matches[0]); $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style3 {font-family: \'Courier New\'; font-size: 10pt; white-space: normal;}', $matches[0]); $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); - self::assertNotFalse($prg); + self::assertSame(1, $prg); self::assertEquals('.style4 {font-family: \'Courier New\'; font-size: 10pt;}', $matches[0]); } diff --git a/tests/PhpWordTests/Writer/HTML/Helper.php b/tests/PhpWordTests/Writer/HTML/Helper.php index b777d4be14..555145d0d6 100644 --- a/tests/PhpWordTests/Writer/HTML/Helper.php +++ b/tests/PhpWordTests/Writer/HTML/Helper.php @@ -64,7 +64,7 @@ public static function getNamedItem(DOMXPath $xpath, string $query, string $name if ($item2 === null) { self::fail('Unexpected null return requesting item'); } else { - $returnValue = $item2->attributes->getNamedItem($namedItem); + $returnVal = $item2->attributes->getNamedItem($namedItem); } } @@ -94,4 +94,13 @@ public static function getAsHTML(PhpWord $phpWord, string $defaultWhiteSpace = ' return $dom; } + + public static function getHtmlString(PhpWord $phpWord, string $defaultWhiteSpace = '', string $defaultGenericFont = ''): string + { + $htmlWriter = new HTML($phpWord); + $htmlWriter->setDefaultWhiteSpace($defaultWhiteSpace); + $htmlWriter->setDefaultGenericFont($defaultGenericFont); + + return $htmlWriter->getContent(); + } } diff --git a/tests/PhpWordTests/Writer/HTML/PartTest.php b/tests/PhpWordTests/Writer/HTML/PartTest.php index 9515932ac8..e919b80b5b 100644 --- a/tests/PhpWordTests/Writer/HTML/PartTest.php +++ b/tests/PhpWordTests/Writer/HTML/PartTest.php @@ -178,11 +178,17 @@ public function testTitleStyles(): void $xpath = new DOMXPath($dom); $style = Helper::getTextContent($xpath, '/html/head/style'); - self::assertNotFalse(strpos($style, 'h1 {font-family: \'Calibri\'; font-weight: bold;}')); + //self::assertNotFalse(strpos($style, 'h1 {font-family: \'Calibri\'; font-weight: bold;}')); self::assertNotFalse(strpos($style, 'h1 {margin-top: 0.5pt; margin-bottom: 0.5pt;}')); - self::assertNotFalse(strpos($style, 'h2 {font-family: \'Times New Roman\'; font-style: italic;}')); + //self::assertNotFalse(strpos($style, 'h2 {font-family: \'Times New Roman\'; font-style: italic;}')); self::assertNotFalse(strpos($style, 'h2 {margin-top: 0.25pt; margin-bottom: 0.25pt;}')); self::assertEquals(1, Helper::getLength($xpath, '/html/body/div/h1')); self::assertEquals(2, Helper::getLength($xpath, '/html/body/div/h2')); + // code for getNamedItem had been erroneous + self::assertSame("font-family: 'Calibri'; font-weight: bold;", Helper::getNamedItem($xpath, '/html/body/div/h1', 'style')->textContent); + $html = Helper::getHtmlString($phpWord); + self::assertStringContainsString('

Header 1 #1

', $html); + self::assertStringContainsString('

Header 2 #1

', $html); + self::assertStringContainsString('

Header 2 #2

', $html); } } diff --git a/tests/PhpWordTests/Writer/ODText/Part/ContentTest.php b/tests/PhpWordTests/Writer/ODText/Part/ContentTest.php index f86507ce06..834b4d4fbb 100644 --- a/tests/PhpWordTests/Writer/ODText/Part/ContentTest.php +++ b/tests/PhpWordTests/Writer/ODText/Part/ContentTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWordTests\Writer\ODText\Part; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWordTests\TestHelperDOCX; @@ -28,11 +29,20 @@ */ class ContentTest extends \PHPUnit\Framework\TestCase { + /** @var string */ + private $defaultFontName; + /** * Executed before each method of the class. */ + protected function setUp(): void + { + $this->defaultFontName = Settings::getDefaultFontName(); + } + protected function tearDown(): void { + Settings::setDefaultFontName($this->defaultFontName); TestHelperDOCX::clear(); } diff --git a/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php b/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php index b638b380b6..8481d01f88 100644 --- a/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php +++ b/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php @@ -49,14 +49,17 @@ public function testTextAlign(): void $element .= '/style:paragraph-properties'; self::assertTrue($doc->elementExists($element)); self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + self::assertEquals('rl-tb', $doc->getElementAttribute($element, 'style:writing-mode')); $element = "$s2a/style:style[6]/style:paragraph-properties"; self::assertTrue($doc->elementExists($element)); self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + self::assertEquals('rl-tb', $doc->getElementAttribute($element, 'style:writing-mode')); $element = "$s2a/style:style[8]/style:paragraph-properties"; self::assertTrue($doc->elementExists($element)); self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + self::assertEquals('rl-tb', $doc->getElementAttribute($element, 'style:writing-mode')); $doc->setDefaultFile('styles.xml'); $element = '/office:document-styles/office:styles/style:style'; @@ -64,7 +67,8 @@ public function testTextAlign(): void self::assertEquals('Normal', $doc->getElementAttribute($element, 'style:name')); $element .= '/style:paragraph-properties'; self::assertTrue($doc->elementExists($element)); - self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + self::assertEquals('rl-tb', $doc->getElementAttribute($element, 'style:writing-mode')); } /** diff --git a/tests/PhpWordTests/Writer/RTF/RichTextTitleTest.php b/tests/PhpWordTests/Writer/RTF/RichTextTitleTest.php new file mode 100644 index 0000000000..0578c17cad --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/RichTextTitleTest.php @@ -0,0 +1,50 @@ +addSection(); + $htmlContent = '

This is heading 1

This is heading 2

'; + Html::addHtml($section, $htmlContent, false, false); + $elements = $section->getElements(); + self::assertInstanceOf(Title::class, $elements[0]); + self::assertInstanceOf(TextRun::class, $elements[0]->getText()); + + $writer = new RTF($phpWord); + $contents = $writer->getContent(); + self::assertStringContainsString('{This is heading 1}\par', $contents); + self::assertStringContainsString('{This is heading 2}\par', $contents); + } +} 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/_files/documents/word.2474.docx b/tests/PhpWordTests/_files/documents/word.2474.docx new file mode 100644 index 0000000000000000000000000000000000000000..8ecbaef2b3bb00517c31c2087cefd094963f2963 GIT binary patch literal 27593 zcmeFYRct0vlD7Go?J_enmzkNFxy;PW%*@Qp%*=_59}| zrIe0R%1}hS@hBqXr9ePY0N?;f002M;P|A|`ECd1o7`|VT0g%9&Lbf(e#x_p6%IZ@spuiM)0O0TY|L6QKw!l!bjNBkSOvttPd+1NqCKEe;$m;#R5yUtQq!nuW z)-W&1Th^y*`;}_Jx@zRYJw?jqj~=-!R=f=_wZthxvP3iOZK8f0v`YnO}l(;`rHxEYi8sOrXDxJ!8lm=}_++&n#@ecTX3PlfdKZ=aAZU_!K(*V6E z&vfk%aQ*u5RIsc#8v>Lj;Eiwa9@>e*^x?*>&IX4n^oRj-iUh-dH_B~kyPJh%lPj~B zmk6^JfcG_VD@JVsoi~x2$t=KD8^c~GsEu!fV)@MG`x z>i|ALi z&Cc)mqS!5b<$VSJwY@1-5Z@Ol0O0Ej1R($4VjC|WtM&TZVP(HV7y3K4bsdbY9O-EP z@%%qA{l7Sn|I4peCG=Z-$29+S;78zGx58RKMxiX7;p{rbDg>magf#N{AB&aG5AHvI zfVEHc#U|z#5@$UevqYVDlC*EIQdD3gx}lc;>I`bWb-4kOgS(0u+?MV3U^0)MOg_em zB`C)OBh^tOX0X8HU&7L+`j8KQ3O^l|LYos&&MX>|G#2LAtE@by`7q_BGb}Azh--R6 zRPc!Ogy42eWBU9s;j2$&qKS(g*lN%oQa(#;V~n<-e5A&*qNgLxiZ`hGfpyb0cji&p zTRI#bObwL}6;Ai&Wuk?o@p&9^gvrNxO{m_ayK9S`r7dv(t#JPuAH~MXm^Y#TOC3St9E9O=Wf%p5m@G80orG%iJd?sIfu zooW!;q3s44KZx?p3Vu1fo#xJT5*QdI&P$v5jYjCDCSrTj2;*Uh3M?}Z4?ANaPyc8^ zSqUlz*Q7NRjK1aLIcZU8(c+0_Xs9_vuO(3$Ay&2GmK+73b=}eS;XGM zGBjKSR|nzO%MV_o=wusbD*8ne#{>zg(JX#NKaU@E9}-{1I)*RWuwFDruSk_W?-J0M zWm4B>#61jb^LJdF!FTmQ6);^NhHj4(2|_PZ`+JAo6EhD{oF!;6#sk@l*cegfqO7D2 z+k3DvFQM1sp!iIHQWrKFRHg$LdL$aQ16_j9LVau4wfLpWf_mH%ycFss-guaEI`b+H zuDE}%Bg9JAv5!^e%)1lEnYy{Rxy2nbgt#v9Rpny7Xz|^dh>=TJi3ls4_GebEx@CeO zI6geyK?d@1LCjPsxEhV0nAu3E$P|VOOdw9Bro�v5Mn(nYgT%6ZP325V~XzAmL0M z$cdWAxIh#>ebo4{Y8}FrEDZN-T+@4;Uxx4MLt<*B%c|1@A+vM11qxbu;*p zr;bEAP98NC`yAG&J?nAqYI`_sHq!pVpN2SeNVAZVGJzIz%Af~)B-uENq??uQn3gh6 zR3B(tvuM#d^gGIuKwI|Z;LGXf5>fu1(8OroQ0mL(q5cN7;H~<4Ne-Y=J+@1Eg&oT(r|_4HAbV7Tr2> zNy!ser8n(s8z0oy${#75%<2!xBk^5Y{g5y+q|gr870Y}^YiE4g7TQ!)84g@j2Z_8p zQ%Cn>*_2d&lX-8dB9`vFN1!JRNDqAv@f1;G8AU43$=52+5`L;+&v?c$WoPJw4cs@< zY~!_$CT5@3@|+ZHX4N*W$*khf;d2%!o_|=SOJA&VXXUILco?o@mD9f2w6NpbL~^vu zZjSbhLQy%%%n0s3Hw}N;X?mGn1=l$0N1l5)u6urPtZi<^biLw`Sv_;}P{+&05 zv3MVw(HqL}6pFn+ncaRWLl;%=Mb*`KwY7qAJs2&Jf0~)^tng{ZiuVEkkF?jNx^A;a zkMNQFlh5xuwL)3)!fh!uS-x|gH*^SvM6zJVX8q$e8dp*%jGRI8Lw)-Mo_`XXWZTIUOByEKParQil9Kx-GEqgrCm&X~~^(`(umY-R#3>h(n*u@8*{m*ZEWuAJ=fIIWQ$u z+oq;rsl=lONg{5UPCZ8*EX)L8{&kksB*BtYW^zz4H&=y(Hcp8$dK9i*o~%hwa_B&i zZaNq-fl>Cu8TVDh0#Gku=zy5|3~d}?$i4?`m*W^Nzvvfq91lhC84aT<2+R%lE5-7+ z%3`Wr(lXl881UDR*+m9BP3Xy{w z(24@_m_VO=?zxf~5gSnk38f)YqXoPPFMEH%2#VUqPfySsr^Q3*$xhVZ z4;Y2opX{-sC(xUvsC(&2P)m4YVeWYk5R~eBw-p*D(uaqkR>6$a04RF!beMybCje1W zM-ylJapDxJjB;h%g#4e#m`V0h2TT)@O5A}Cz!Z4FFa|}Q6(Sp5Un1_iojONH=fnig z)65KRC@b0wZ4y=^kQ-N74Y$L+)>pun^iowZ}6KXc^OEP&g=oj zG(Xq3)`P7tOPV~d|LD3TYBjG^#>jfAhHssjuBpU&)(JdeXN-hya~%^43l?a;MiO+~ z%IhXf9mpo6tDkPo&U3t1^R~5@7w>oPgAngH=H2^{R7TMUH6#z-OD8CAj?%D zkyOc7xrMF7p1?T$ z(cNt9E08ZfX$lC6)?4N5?o#XKCy#?$ExEMjryVD&61c$UXcYlb{3HLIcOq3BjUrxH z?oJm0uK*HTXf`&r5m+pi(_bQ;wwjcb8_n}Rtf{J^*_TKQ4)tS3_y$}UDg-CE9B9mu zi%yWy;N9!9dB!W!Pz+4=Z|5qi?=cgeayj!f&{G67;Zt}XczR&|B`3Xf0<^&{asdXl z#a~%-WMK?ERKCcnguyH)}rG!Mv7wrG^?&Ro6((lP}8 z`59*|c=y!!S7+>J9@~TnOv@KvuH%K{-Y}_F>ATQ#N9z3>h5-%7CqoMa>7f?)e9JwJA`eg^rCKSg9 zV#NK>O=!V^*&MQ50Ys4o#`p_XywbghYP$M3{+;mD1<#y=#QG>6d?XwWo*g|Xy+2JV zqRBm3Og!FeVtBw0Z2r_TqxjVz?W&_cFR6Q~ZnR7TSX(p^sx|H|%xI!4{|0xI6x#Bm zU`cafP*RtzI>2Z@g=NB;kmFqXRFrCDs3rlQeVS6gBMK{tgvtIunzY=k5+qxSDNq80 z!yCm9c`Cy__(rs3tygkAe=P)#{r9`awb?KAPlh>1v5OLsrU(YI_ihtIC>HWIO}AfW z9{rD^Xu04!@Bm9`Q5`f`LnS7HtA=%l`FQgsy9(|4tS0Cxwg&JXwi1Z2I|)eN)j#^! zRM+*&MTJIKHLJvC&Ekci<*eq^@zRcVOMA3U=(hInT8iGO+~~M8A2^roRJFk$^XYvR ziH*ASi^oy!5^?k@se3nnN85yJ)9XizG7KArDP8+MGlfhu@8Yv2d^%TRs#W#2--I0_ zOGonNqLUu*=UF8Qth|^U6{^D9;k7C!6tS)O%yfN@merFi;dVaZTJ9p| z*yUlzECl~*$csT?g*yg!ixA!^idyKJi8MtLXAZh+T_Z{`=&6kVFqY z`0ewT-#-8UK#{SH(SLFIb%%8Vgpg~l1=m@-Yl1OTnG0zp^6U-ggeM@#0W@TL4E~gl zCotU2(JAq98E<`(ov%OIe?(ec$x}SU3Yby7KmxGw-GU4em3;pAZTI4lQ|*`sxe}3g-K0SaHJKu6iM0=5$6*i?L$b(13cZFXR0W}VItGFT8uXT?^=Xx1 zg!t^B%PB%hH?^F5i@k?2m)X56#-dRrY1V^G6UfFf9xO5j^v7{%4XU7{!~oRc2lFB` z;6!Qi8Y@6DqJh;bIg9?X&!Zlr?ThQDHsOe6xJXpm7fst6Y=Y2|?7G;|p$Pr%sQ@t& z?>2TY=tshQl~l5rPC2EsfGK#l4ifpbgz$$MPg+fo%pgH_KZA&sK#9BJAyM>NI#$wq ziTOvuljbsQYsi6-qP0c!8FHs$4>G%9P$rRueEi~X1-oqdRb>d)6cdO+So&5ZSpj2#&Mk>iWf)ME=d(fqc>GQJRui`>I2)PYG3WH%8(M|{V+QsIN{)oOf@*vC!W=(@KaK7Q(B3BQ6BaOi}kjh$0J8Zd>QEpj6X^VmqkMQ z5_aH=F=TYT+81%yJ22#A90_khM{kq?J;f3P!Xqg{+!;vtC)O!fqG69H5nqCbF0nIT z!mBQ^w;lOcVPI%Hrk^C0v=drzjT08m7u{li$zwQ5pAx@;=c~){u$KeHHw80fET%3XKH|?Q zm!1{zPe&qu-+>H-+qHcrqAvV<+HiR87wa9m@*{!vA@aZ(Tz($_eaGLislDlwZH%|TdnMxVXdNbg*;)9$$OC&D}OW2}7*3U4JSuDXR z)uK?buGYA&$@R^D9*7-)m3hn>ae#^P^AD~I z0K3CqKS0l*jo>1~8m2{(SOo$ZWQrTTBGu0z?}02dQR$xAN`nlRwR zf?d3}c?T7eaxL^R8m!Ktg^F1c7H@t>p#Vw=?S}41?Vh9r-o1AO=wuAy%HPy`*VcL!{SrpPsU3H%pQg=9b`bs|(M zV3EBFG6-%hStXw% zvOk*F6cUZ`n6ETxZZ_n7^ILNhEREBEqb_09RIP4Y$wBt3EoY_FXr zI)GdO$a@)QCEy@X!;N8)h%Y*+IF#wci^QP^Pq`%FIXNr%Yd(J_<{3uzVa0$4J_-W0 z%@|7z6M6{!`zz2~9$%gf(3^EAI2<7LjH0;>U+yu@p|5EYZ}g#+l;ePJB!V+-{}U*f5c8}`s6XW0 z5&-USQi>lnUP<{Ucu*jHhS5)I3!t;dE-rF#jbs`J3iHC2z?5W=96R1AkvRJ@6`J7g zTu(L}JV(3SeN9Tw7Bg|Mq83wLhv$^ z<_~@?vnx{wo|}wwSCsyn;!2OuYcJZkei1-gd?(_!E#!|ugp?65+oJuC3`s45dU|Rv zxj#H6eHDSpF<+d^LrVQTFYwE-Zko86z5uP}>##Z83*+D~JgLEmqe=2SAQ{Cy^-f72 zyLsub;_ujC4lCqtkmcZVb~DBOytMA6jbDi-@S*NHR1Ff0+j9muZ zK5>o3I@(3UX68)xv?GotgMm<=%OfvpH?pS*b z?`%%8H0_0ru`C`rTy`kHVlp9=MMs?w0Y>`)*>>k5k;YCwpD4U?|A^Yokh1D7t{N~T zI_RZJ#Vuz^TR5hy5xP(_g=gFa-tXY}zT z2WawZ$l!)8wbFQvUsvwOF4He%VZY1s+CBw&_9SIApxX16--*PVx1y;em%D6rNaWTmhJoOPBo{-bos|{PxYjVRBx2Fa_lCCWaOQLv zW7Vg;+m%O>v8f=%6CygLlsxI zB(AJ)^jcBiH<^$dQYdv36q6ZY{s_G<$A@NdIYYFPRP|_p zPj;Z45sfIyx{;N=L{B6sK65We9(2?VJDb;h{0loXyp#<2OvI$Y_RA%bidC6Kc6=o9 zwA`|a*g{hp-gwKjU9#OAE6n4LDKvq`kG#xHreprPniCx^h*b=}|{!3Ad6jN4uU#FMPqh z<)Z$u#zp)i%ul%OXQa{)2eBO7zS3UDw2%8!l@e5ow_(}C)3GWV zOJKBV&ZiQho-LA&lFJG8j*Ei;R>Q$~V{@qr{=Qf4Rq2xZg~*y{6s}s2h^mW>DyggCXP>}3ct_78(JX2!09ovn`H*2P1>)v`2XfxD^v(TkY_MSdf0`rd(5@S<{@mFLRv3{LS3K4rXe`+iV*K;4$h=zG` z+aRv$T;bKl$DkX&7*UyoT%)KdS91!P3JXK2lDD0kg1FsONTP0cy5tb$e+ntErA|-@ z#nhmrWE%}{MzsLj>xgEjk23tp3Re=izBvy_?>slM_1wSpiluu(p_vkm@5G`iVK2FZ zjr7A6a&Xx<$FdF>*BlBl#cX`!#j90OLb3|fSI)eGo$}+VlQQ(^GF#piPq3er;apA{ zU!?u^>t1e?|hGMPi39BylSojpaRgQ^5ZaAK(%Ov&jE7MH%mH1fC&fleEceu4;V@`o=eaVQIrwN z=Z#k98@{K7(>+KFylC!gu6TUhb^&9f}7t-OhV#_#p!f!M@ z(m>NOPL+56%?OFPy~+A=X$x(S)t%WXZ7zZK;1s<({`$9lRLbADd`SNupcwxHpj2b` zJ5c?$K@RvZ;Ns8?e+~aku{{D$Fm6Y0x6}c49{Y9e5ch?b&`_#%O>1Hbt7h=w<~F@c zSpGlq)5^c)rzhNh^3xjKfAG`d$p1%v;{PA{$zJUmQP>9v)I=O9w(N?o5CcZ?5m3+r z3arQzcTyYQ!^Cl9NpcrIhXeX*w%yN(09(IiMe!U!^Ob@((@>Ur!#F z`X0I1XfPxCCduyRF4vh}qOLNo42zE2FpNbk)C@d%BlP09Y#z~YpQ`40^X)Z|;(g$;??AL*?!IR;H zT#>-gxLyO=ywx!w^Up{RL>NUhDzn=O;=iws#yqw$0OkTR%s<^2pb;1KW|_^VH^;Qe zxa_N1ry%VrIpX&TINNKzIHJky@Ht37rx&r#tgfUVDY4|@_a>2{hmn2qGhzAn_|*4r z@oDdcc5q=+GuXUsv?k~esYnZ#pJU`}twZxqW=Ysw`Ar4BA4=#~wfm~}#U!9_mPKnO z3z$u6>c1>ZeE0afum3?$FMj`xo&Zt{POZKT3?#Jy`!odg^yxr5%~a z>4T_%$8i7&v(N*XA+z;rN)`#pKGg14{-q(8Xebq7S6eDF3PU9&Hg|w*$z|@Ja|OHmnf+f!mRO+X+XX z7@#agg}yWD0S$+stR4=(&7afD-tdFx(9|ZN`!Obg89P0HJ0Sen1(-Ve#DYPtvE;NH zDuMSwOUQu--WW*}+f4#mgiHd-gTw*;f^Mn{z#E8glzCPH3JLYQEiw}2OE-pqQj4IS zJp8o&pZJ6m`JB^-sj-8RwMU`wijRU&Wi!G8!;}_S=k6LXlheDk`G4aR$G^oVPwS$m zeEm>=|M~PY4;idC3f=-B+>LE4tgy7ZPo=FeSU<>463FpC$HK(2-=N+US%e^#e;h+-?Z z!92M96MFc-3x5n$lHH-|Xz0NgRcDvD+*oL7B#Vr^jlOZ``djP(zR*g@j5X(ZQgck~ zBtM)Z8gCZuwFA?jR+DUvhqMmcyuwESySEZVelGMJ0+({@i6rG*5hMk-MZwM+Us98h zo}T($4vgoBkJ3L8=DkCCP^pjS=~)HVO(Q4W*KfV)5?n6F+}Qg)XJP=-NU|)4cS>QK zopb!tZg%pO%r10$w^CWxK>X%P)o^x1cjca?$9I{vCOZe|~}4 z63kqF(4&G09D!x#-t4f&Llk*Cx2C}|R^|h8xYT@xQiIW~xNlnF3~RkOXrkSBjMkhw zE@LSE~7)U!D z2_5!MyBZR>x;=_a$sAXpUS(>g!lvuflxb&4<51TJi#`kziWNDjoDA&GLuItxDsq1yy|lE$?f8sD&wr2G*bjqO{FeIb z#>nFw?JR5^d73}zIjO9honU}SmohQ$Iu>bEm3GfS9;-iT#W^^qbv%95R!A`|JG}n^ z1+<^vgP77DxT-D<=_pVGKHOzG2dbRDdU7hMLZ>Z>aE#yn+99_D6epxAKtx5id7 z{C#sT$l5fg=DcUKJj=fZEMu{)pZ!VIfg6SS>9Xd36RxjG-3RmFijL%yM>>eX`&pju zaku^6*V5ZJ<4SCNbm{~3ba69q5*>nedA zCz|${xnTPvu;@HBF(H$fXj6tB2*~xc00+)>*hM>X^|NXOiDLsg+;a!s^Eoa!sf)mq zZb9GVBqpAebcB4`EX>^m4%(>>)yu7M7{qbttU^({B3b|@MBjhD8pO&=__O!L6~;nH|Epu=rWwy6O3@25@T438BsUP(S52X(3J`i z?4&7G&l$1lruPF6df@qON*GNc{a88{C@hAyWPcTPcLV1T12Z)mpwq&fJv+(ox=bUE$e!MOUr_XXJZ z7a%UWKo5~rdvPkyyGrKXM*8x((x)QRQr&qI(euJQK-ctK3T3KSvRZH2^F?WOiiP>b zKLMx}4|?cy^u(j`l5NO7@76nEunz^!(OS>&B-|;?ojIUFQ2|)MU~qp~lBCmsvnT?)%>p8YjjdW?5PN;sO?bQfkB z@N(|A;hyQAUmjt6G%P#vN)1VTKfgOr|Js0hgSZet_?~r%#sUCP|FZ$*WM*t_O!v>{ zKf6#D>YFy}tSCMBb*= zGJ!hRhi!gX%`EDl;~9_`uNKMFp6me+&tioAz0V$uJ&wbtJb%hQ&+?NRVlNTdw%oYm z@ry_!YZHIlO1zh-J_n-Ji{}@FUVKgo^K^~j`?_G|M#3CNjC%s(3-G2xHtQAbc}PI+P#%DRmANs5);ednU7OyW$uI4Ua;fYJ)#*!Y;a_)&3Aeb!Si3?QY#`L&UX17;IKoSDLra? zL=v}gdiH*EGW0DRw4@1>-cz~vl=oU*f7;Wi0b)?t^9ITzE}01U9fm>2x#ylF9UhQ7 zE1)aI_+2-avzlQZwW5;Yb={t~w^G|a-QJJ)r=@2bZnj%u5y0IKR@9585&2P|0wfI0toJgMHozqYv3@ zKN8YOA8b7CSgEvRFtnTN?FNxoN_}u2+|CDet6ynS0-^CV$EK=bx`G-?xu;zVy?rcU zms2#Z^`U!L+5}6iHfyC!FkXu4S44uR;*_CV&qtM{ipw?)JY-$t{8cpT~dmg*UjHbP9? z1`XmN(asm?>Z{xCdVMA|^*mbe+|tWgeZ}Uko9!8s>_D0tigQY#dCl7KdNR77pv=rkjH>LBw2B&BYBl|%y$DZAnpm*im*ZlK^;Z%*av zxnWkfvTF1LBJWk57)Damp<5sTDYR<4j4VU6b!;>iJ<1vL>2sO8il`lS z>i;Y>l#ln!hc`Zl=&9sGOT1D`)$}D%|1qQhL3P3U=?8UWfI9`sWN2?&&_=3SS#9=V z(z03*H_6n-sUXcjx7*QGVmK+pw3(Bvtt(@aQ9-i#vgq9!e?~9tI0IB5E7R)Z)h4v1 z*F5JPsY1EAuCY?tWV20ae-F~1*Md1&P=di#X02gu0nvqkF<6~?)WftlTiX9rp#mZ7 zT)Wtuor>)As#WtI-&I2!wv+L*BV`6s;jz)79JHfZG1U?X4d%HU-G?2rzyxFI-G;pe zth%q(c~GE|6>{*GAr9;NT-{$qa1kSSV(O8c(PPX8t!~e`ORj6(yR!^$suK6?Gfz8h z?DJ>3=%dePI>5weml+#*_b=N&HIEvB5;PdFoy|Inc*~-fWlR}S1$FD0T@QW5;1Geo zP?|OdA<7n*UWNu^n&q1{&^>v&5QABU}IA4T^8pJo*pXcf~9s9%VonbhjqneRc>Om5$ zb46aO1YbEslo~CiU3KJX=#fC_1>uHgPg{LWezMtp1J{{XmCD;0DtpH@1*7Ezv!~E;@DUZ!V2&MC@ zL~9w4ovjXy=bJN|%jmEv1>ni9}4#dhQMrN5=Y!iH+6it2Y5- z!{;|fw$fbn!EVzn3DWw%^{g^{(`_y#!1z`RxZ^FE)l1u!uziD82Ujy__NuD)ZJvAG zhV9SGsB&1>_O|k7*;6tr0s&_hE_GJZ$oIA|JX0S~N4zxl%wT6j*tt2Lw7e#MK8E~9 zN4tis?}^bYX1uLRX!wo9xfe;teMo=3r@5JCQN8V7;!rt9oOw>pbnu;?hYI=&{-!DI zjcpiu$0(?Fhq53)G;eTRmSk`%luWawvPC<4sQPp9XM-8}GI+4P2NS^Y?H9UZ914BY zQNGx_zIAuocGK@ed5T%RwpJ~8!0f_RYxhH3CN;EEcZBz*6H5;4k$}E-1Sea5< zH}9{H<}GW_^?^JtSZJjOqr00wc+yP29(watr`Ssk2JQo0IIpAETR1~k4b~r~f8DnY zd~QB5Jdi>dGnrphUt+IkZg1YR$zwpa6460q4;3<npu` zT)ZMt$E3gTalbG@5X{iu+w$+7k1QLBK8uO3!)I6-iT+ByR5{IZPL%=D`qWQMe(ScU z7l3NE2Xms!?#ADLA3gzQJ;P_du4noR=${q(!;&;+ZS=OatUJx}_;Ek2#k+vc%NAkd z^8%qfz91jVAo7t|3gRKk9m{B6a7YzYCcX=Ks6ed{WG}!GVV7Ri2B#`WNLaIFb*lN4d6D zoTuh*B*?!eF>@-~`zh@BQwWKX*)igWh{85XlR`v|STLT1{I>EaB%T7QLr9}4X`kgr zps=wU*c%=^2WRYz-vb4_*GX6#gwH54=DsksCZ-8D@JcHer^{g3!Os{$ZW*k>nzaLQpuVV0m62 zS~xvT5^F!3NF@u=sn}c?DXY+&q!F^ZiW!YTq6}zQOMf^t%^Q1Y>9E zKTa|}M;#<6aGx05$F`v{WRMlEPQAd!EpA3Jt7clRjG2*39cb&&G-8?#(_PwZcDc!B>E+datTj5eY8(y;eXu}h|*sxY+W$Iz_JfCzl^4yr(v0wqxuCDwU zXt)g%CA*SYaIrRYF>-t+*_qk6xd0g|3ElYhIJRteZ@57>jkgZf?b?wIH+PRPP&z2u zO_}*=sXlXe)wVvZ5$zH?lV_;(?sEK_J$ulx_jFgDQ%BTmX@A!R$1-Dyj@;llc^wWy z)v0W=s`N7wXz(3|%L-$G}P092B;$6K@raXfEC~Bj-d;v|(dZ zwy{^rZKL!gqkYPlIvK?3ZaquQ&rD7Bh?&?xFWbuTt<1d%%r8-9F#WFCHmkccH4HtW z?*WqrOxpbpCLBCZuI-QyoumDa@h}&N%?zWC2oRe!3A>KWb2L}%`&psU$Oc{7juvW* z4**oi!Xo{%xl=LzWxX$hOJ7L;r4;mF_Q~my!H1xGRjg_cjB1cOl-3sSa_9ZjR;l@t z%Ic%$f<>w4xygksu_!w=XKxT=P^kiKa4nJhnh%6q#zDg}8=3jUeP4N3xEmt@*Ps&mXdF7GU@vMyP$I6A^KcymcX!pnD(B%E$M;io9R{T%7rT@6(RFj00M~<(Kfg z3x*7!02n$uI@wyQSzFPWI~iO5lhOzZNB|Uh1mBnb=Tj9Y02e@yAW|#n0f?YgL+Fb; z`6ZLl=AbkVkXgf=j{&!}T{?l%l0EuJ{i}5S>$-s}$P5a*ehfNZWSQMe3^JM@sk7yZ zN~Befz$E;)+GBKDdaC5OnaK_3Q4^qKQ@r0&)-a7Ex{#%g-0|Lzx>ZDBeCug?9_4|N zZ%FJ3KN%PzL|^{h?)1Xa^(sl*ofjrWsc}KmPS6zbj->;|&0p*iIZG{GyN8ZKL~}xS z_vzp67sIM{NAG_Br^>fIF#g3J@(#9kj{m=mk`?#QWz>-C!0y1g&F1x@d>Z?Ra`Y2t ztGdc__He?Mz{{xUde_`R*-d-$TM6;)*t7f4?AcDZIkyqL&V0Wnr5+|5y_?thJ&?S# zqVt1;Y?B@Ctjv+nj>uA3GR#Ey*Ni5Sc72ELAKMy7c&Y3Ac#aw=FMT4O z`R_zIlAo{?mD;?>m{F8d`3@}5_-3l6R&1#oOs|p&3%|_kc zjmXTFIop?}n=+>kH8OL83o`k=U=y*2%##Zt%DAxr4>bC< zsS=YI;?1Y065q$oVvJ>l7RAP&a2L%vNdAWAo=_jR-7*nDQhs0Ala&hCyC>}3>vNYE zVN<|Sk;6fbgCh%@pJC$OYxD>b-j)%iZzfrQY>oGrSP4xkf zr1PZq%_^=R`AJ)8V`C5HI^{$M4G$$%nZZ)6$8gLbHQ}A~yhNBD`P=r2EAEXB(Jpsr zvn>=yC(U9enY9*b5b*}-dHoNm{r=xnM8YgChIP6vj$fsH)aTi!*YC_#fco!Nt(T=7Ht}T@>mU>JZ4JCX|`g4T!E6p$c1u zBBSbXt}()z0=`C1G+&Q#bvBbPCne#|-Z0Plww0*r1nw(Qw;DO^3E-*h7oCl;mtuPh zr9*~obLVaH(SYxs*i(I`4aG>1jd7nEljnTMX!Eo$L zGejymZ29BZRj79JP}G z7L)*be4MsWPj6#AYu}%gIuxX!38~f%+wK=OA08)LhhEax3H9g+b&Z{YUgTeBXVw&* zj~DwBW!sK$myN=_wpX*(TD9KHUx{v5EN)R5r$C_2wb~qkkefJrY0X=?peL!mpQaW; z6wHr~h#tHcrs7&buq5^V)CMopRCW6DN;0fx1T0qaCx+YW1;eX#fU?vg533zwY&1st zh7VOekio|0i_6urO^ZJ_T6#hkeXTU)rsWe2-J-+J|ffc(b z(I3=O5DX~Qg~}C$A%_NixSCmTkkZCK%30;>fL|i-e!cK$Z>`OOV`N6}J04bVf1aOS z1yYP@`T=d1_^H0p$F)|gb+gy?e!hJvfmjx-CGu8rmN&ns24Bp0HJ!kwK3<46!&#T^ z_NuYBcriQp7pusNJWEHpG4QP(7{}gndu&|D3sOJE*spq)mgGff%E7p{Qc!k7D#=f$WC_k}}29 zU}q7@rXtB?Nji{8>iKQjZj#^%c@NB^$$>6m4~Ytu@&_U}6MG+b51GY!gRk5wLqr$c zO+YnpcBLUr6od1WMl}53&r`J#;2LY2nI`j@kb3Kd&uG+<;E#r1G@^N1$FK5&6M|DG zhP5q1FBV>@W=kRJS*Y3McVdBz@B=3=yJeT5&)M4ZE-v>~QA2EbCcCMFzBjEnT*0ja5Ql6YTw_B~yyp1dmlP$?FgUt|p$ zy&t{P)}6?!m?u0n&9;fR&Ofw-0PVyzA+{(vUsN|%m7OK_bufZmpYLks1G~K)oi~c>9tu~ zCr`RgJ12VK9&WsjFkE*AG{~T#>jgL4RXzk(;!RLdD-6h>6-8wKJARsw(cb6bAgdp9GbV?&#(nyDdfHcw|Ih6D_-m6#T-rs-l&GW2h zIA`;&v(A})_E~4`wcg#_#-QFZ|QjDX-<3Qq82K++o& z^P`@%R=UzU4qlWCFN^rXaS}YyQlIQ;7?@of=_&kGXspHE`{Jy?Q)qTWM6^>-m+bo<8tAKN1)3egY;nUhFk!1Vcu>UnL5EFnMYzwVOF-(PlfjXpr zmmfMgyIKETd$^-B>o~`Y?YUTVjW{X1E6P7+o*TUr5;N8nqHQGBNh9eOBt8f+ zls-?n_PGwZ`h5Q(Kdd|s4asYhJWFQPhyY^>7|%wMc^LM^+i&$De(c;sgbMOfEBmKX z_v8TN6{~yB$LFWp6MYQ5jxiE6>qsJGBBR#FD;VClvu7A~l~;^!H6Fsm>6TZ0^O<8pGq&azLUPZ#E>wF#Rxwoqp&B>UuH~&_WEM%)7|41o z>QC9-)Jx1`n9rNzy6;ERPsfy2A@62k%)lz-yyzECHZdG2uK%Egm0+#k9SCFI)e&a~ zj`pTU$nnU{eEsdE-gQk#9gGye4=fA^+7>nXiiFV)k~B^rkUw<`ArOcVp=E~Z!_uEa zi@cUO_BB0@RzM@h+JZ_+J3oTgUV*tVNA=tKyiSMmqe%D|eG$wL@W|>n%-!s_`8TwZ<2fD2|LuHUuKy(y)rYOk6Dv=9NzhI*5KAr$C6+dH(@r)SJZxSqax)MQ zarAt+3Z%M8Csn{RaKLZ&MBUQtMs?t&RPvj75Owc*2!4&k6XMn?JZg(O$h2XSA;9*vm#2UIAqOfE#s zib&OeaZy0Bt)J)eA+dFBbM4%X*oSU?nM11+a)1BD1<^{__KhX{{_7OlbA5I#`*eEY z{3r4hfjQ9<`h}Kb80o3U=G!JwToav`nIc}hZ(nXt{ zs0Qi!c64V-N3-X}a$ra{QyKIJx`yM}9exoR2>2@F3%3CzRaQ`^xK2vyAl4;eNcxiZ z%9lu&2>OTrtM)hScoy6W$z$f}xNk>=w@RxO^xVhI(~rMz$mO6-&9V*{;Vlo97|nU_ z?oq8fUVGb%neMPrQr#mIo!{gcUkb=wWuyIS@{k}Z#bAijXjWijRN3AtZM-q-vgqY_ z)VpdujS>In$g&|{F3PgbAsM3+nk8G>oT|0=dsR(#KVXFd}5kcF^O60`Y z*UIL;zkOjLr>s~rk0IH(>@Y}SzKY?Hkl$3zGrop_OWUk{vuoK*L!_Lif|l(~_k3O* z6a4UNJ8WYxlI+#N>FGpBIsZYTzV@QmONu#2O$i{+@CqjVNEn(8p5&f&K+!sSE_UsF z@a$7>Uu^zpx|^nml0Ly;Cyg@IVJ}WhFF>QCX)p_UU+>srSep3!$$M!FH+BVi5z66S zOC35ga9ZTgm<%>{Zij7H)#Ih_LnobYxXB~k z8W{||kxO8jEz0j7zXgyJ6w@3D82WEGv>|^ydnu9})^SvBn1A3Uqp=!}3cntT1Qi6R zz{0P``+VU$Ak^>NU*>EJSnOB!b(6V=%Ax17{^H`S8p+t_ypjukeP9?jiCsupzh%eT zk!Yj~K+}HnmJoIVznDis`eGFcV)JZ2C=h-OdzWqveq2HrPpYU#3cs}V_FHg`kP=UI>RYv)R?Q#i)2<@<`j?}A_Oeg4pPoC(~44Og!y z5=72uN|L`oVZJOw0%7!4Kd?IrNnTY{kp8@0t>u`)HANHYA=AOD;4Z`Tu2j0kHoA#s z%QJ_Pv~K$2+vO_4MTZnTo7W^MHe9*k#<57`bsCC8s=iemaUO_KX#!_3mAeSIs#7l`*4uOwGzHcqccB)`%d`Kw4sj*k(7&PlHma|$zD9p)(?Cvz=Y}Qs1oGsqVnL?-;(70g%q!U1uF0P znIs$>{%HL9Pd%R&nD$l+FyVI#Fz^2@mhdhHDH#}dBrT@Va2Sb&>R5nceJ^bKjvLwkb+;9|3hn$3pye{Q(TV#Q=BW* z&gJDl4Ekvp=KZyT$8&v(l*luRlyFdyNb%ghmYEefGrB6uKRLa=K`C!k&KT@zkt>k) zE+_)Il-yv@r8;?0aEs>hE1pP-Ve&>2ftWufh`8W@<|$syz+azdUpr9XcrKwNANyfh z4&eGwoW{dhF$LPt*~$1)?UxUY$w^1OAg=}LDPuqL3$%FxIyZR{7?Kp>W3QFO`ML-H zu-4orNF1-O%Um4OvYz!3iSP4vo5RnT!pO$<3-O}ch51|Srpi7erV$7o6=YdMER-Ns z{(y+lL~bE~-s#Q*Pm5~m%x~2lOTjhbSwyws4T@;__Fzkx6?vZ5j&F$p&_fj&b5CI2`+-a?K==t$A(^nm%ah~@ zF%TzBd`;wAk2DSSH_XIGvNRv%k`>d!wpVESYOAKM6w${1&l3qRPDn0raa9q5TC81T zoJR~|txcWZ#E>pg!sA6H?|otpA7OqI^2bp=0*8-`2vorcm5j&?Gv8MOR2!|feP}; z#w#UtHPL3qGRn}@w~*Zz+wBzRwGR(h)JJK(!Az@({ZFHvMDWqdFS{x@gy8jXmC=YP z9*l=giDIg0Jh_H7$S`ZH**?;f!6GY~W%$a=ZYYY-KjS4N%OgPVo0CCX_c(q`t!Rbz zGraVGvKCGm+lag+=yu}k*6|5Y?izz-83W>Q8rPG8GQgZ$v{qGf3@JSs>`CL&;@yCl zm;g9ea7@IKwPR;Ghav@QFznQ+gviGIt6Tb_Wg3)-OivVEjP4su)H+)g)pQJ;J!`&I zq6nYY{)D(KFV#B6O@$QAYSW5fMyf)QTTB-1qINx3@y@7u!e$2KW*tRIrj8~(%hnx* z-iTI6E}8=`sh-KWH+)l2 zuFvE zJFl8o>W`e-B-vyU%&4HjpXRrCUf-DER8Z!jE}nK9MQ`vVo14Y(b77@vR0?bOt1<3} zvaR%K(w~hT!S>lUo_vq2SK7i#P5qgKE>^2lSGne!i)v;d(~WuQN8H(kMJO_|DgcNq zmS(hfkj(RI%9Xu!t*Y#%N7VVX%JkFWXhPB;{5_gMiSqEx6OKc@`46&Yvjwe@`#cJ_ zd`#q0)zfP#8cm+N;<5fIg+4rhvnm^r^EX>l4kmF~VDCvI-6-|erhFA&>@5KteI(C>~GVUs!i6U9wU7mK+rXYq9!G(*hFhffaT$P9sl@5 z_~k}Xd@GbnGW1(H*2yqf_HHj=DPUst2^BQRM1ZD=JN1T42YI}?9xUxLn=<3olB~k z7K6d8#F(iXT4t7-;>hToO5#+IkUHzgREAI+P|`)J<-{=WZpM>xovyS0{`#mDqRl~` z-W5lpQd^kWColv*XZQXMWFr6Tu4HidD_AkqO@ zYp~&h`SOc5H1P26ltnD8frkUoX&*Xx7#Q52p$S(LL*?I6PEz9(?Vv<4J&W|aLh4O? z{0qq@YOyk-p9;sgRkhCUJrM0NZ=-zZFs9JT<$&!vE#6Gv=^5n{%%SMTXJRP$XebJmM~w>ZrE6Gq3;X0I#F29HPP+_t{RJlTwQZM?2N`tbovWTz^w3X`2 z=GvV_9#w+p)Q=5IgC+9QN83%1<~$2;Y@gEYC^s>`V%-9$SQ1=tS`CzSb>%lDc{4yh zTBLhj**%v^hEDzJ3#@n^2hMQcJ`l@!k6y+h$?e@`(WAO1#H`Dmz{WyN-{kyNuoDTX znN^W`o~t7KrNrhF)0McdAfoW`3xhSI+$*KyQQ`0{xDlYqClujKJvV3xP<6@(@ofIS zUhK*za_5)#Z_Q1dWDGQOBOsu6vyksmr+@$W6ADT~u+WGVJhbNTH(D%PXB7iOYm@I2 zS95BA(PFh^RJ%T&t0M&TMkcG-Og)V)(q-9o+4Vu1EDyKNmrgfG^oVS3UoiEEBqMoR z0GQ+`P2-Mw0In4Si?%q)53^0OxRM8}s2l`;&7hj16vjtd3k|-s7ZV|Bj2KIz=O;*g zHixFT*L6P2XIAXMRIi3!vSB&69GqgI6DFe2MP#6!7UFA9_{`r}y)#GqCF;P(fn^%} zlD6bw3;C076wHvh5J~b*sY-uY9-piw~t(3a4KP$*0IQlarPFXU6zE*fv;6VPJioQayM}LsxUT^>TL&9}L zT%^a*N|B;-XM&7Wtab!I+h87VIIt$fU|Z}sv=PE`boLT|Af(Ih9ZaUuJuFaf@FdzHJjolXY*yRJMY1N=CjI zt2v^xu9Cn+*UeKJWJqBhhzWM`L{x8H$yjW;`gT);Y@$F-q3!#atW{Kthgj14o$uqJ z1!-K=z;VGV`dwOgNviS0D_iV&wk&!hvw#Lia(;jBQfRhxtp(x-uA%AbhvQ)-L%N^z zd4X&x2wactaCb>|&ey}r1c^&zcs3H)9(i(nd6s;gj|7@8IMVyiTc&CORy+whUeZEu z8!RZLA(Y4PulaFyBRj{x?ieT`_y6-4LNDFxm_@5u4osg##7l~>1+wb{!a@h_+IZ2@ z=W2=J%@$)kidM3K#>qZ!vFV63T3iJ8k*$`?s67>^i@@vQhZ$Z)(g6xH){h_Ww&h!` zXc(HJ31uivD6J}|L=0MOy!n=;)S|-H=4nrUpEyY4(X={KE2WTxo)Yl{x*!oKxSa8P z6+qAFZy?*O!tRKo!g2b>#Yagt&KYyvi!&xisTjDWCoY6sojn3T#HU_kmRDc_Y%aft zH(IBtnA)Bn{l?5 z8#e_1@k8v~X5@#X{uhDZVREHQS8C1-^lPtI;`_c4g{LesNdX1Ehe>bw-+V;Jp6?HG zwJ1?QIGz&sr1o=|PQ*X8sK%810{=(_{v}uE$AYeWERAIiH;OReNR2*V)jEx!=?ln2 zGwfP7mrI2#kV^#FIU!k7o;rz3EYyx~h?NKo94>RzHpn?YuOSC*T9}}9TSx9Fqp`p@`0o9a2B%-^6?owPToiGg0y26IW^ zJ!00Ueo(hS!V1iufY7u0g_g-c=S&w%NDz5j?#CJUK}0s4m*C4eUrX6|P z7#f}P(Wmxr=gHfM=fHY(jalweN|3Pn`FZq$*4)L|{EJZjh$}iNqBB7r5_TGm`kQ!{ zPd;cB1-&;*`VW_0k<%o}r<>Y@Z#!?;<;*UY-M#_yX5c%w5V$NeoF15bgLc#Z@Kl_| z1l&8IEez1UN)GL(zcRM|rjz|AZ|ncLWWRUUAD^&~6(P_?0T7n1mloR1?NaBS_haSD zGCB*%pf?grNk20hP`9*LY*~NH?7Ybke0!<*zS$qB!&#MYo>^%5oRADnR35?W^4bzM zq^rA~4R^GSjHq^gL(1brmkiS34o-2l1TRNuu3hQ$5FraFjw4~cmGJkOus|FP!HbJ;%?{JE6IPvH0OIp`sOTUg^R z@a`G|zo1vpim!jIIB*yKCl&H9Fbqr_`VaX3AV$8c=`NS;FI{9%n$LeT+up_BFoRU51fgXbGA>(0}ob+(qB5ult35&G`d; zx7zNmio5j{zf|<_{ZR3HwZ&cdpP8e-z%Vc@(A>aZqtZX)(Om_9rds~4K=|=p1-}w6 z?`pZ5arjG*$CDpt@kjpQUHsjIgkShn>7Q5OpY(*g;JYD%Utm7XAK*Iygu5E@8bV-!hfM*V9cOi`@dZByYN4qp5NiThQGmoxIlN&e?E$SM=u!v e{tN!sgXy^}Jan-5o^*@@^AQdPX4Ulj*8c%+K=Wn* literal 0 HcmV?d00001 From 0ac841e338884e7cac15ea11ff9d623fb4013b1b Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:13:40 -0800 Subject: [PATCH 002/153] Update Html.php --- src/PhpWord/Shared/Html.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 21d8404ddc..6f168164c6 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -109,7 +109,7 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit $dom->loadXML($html); static::$xpath = new DOMXPath($dom); $node = $dom->getElementsByTagName('html'); - if (count($node) === 0) { + if (count($node) === 0 || $node->item(0) === null) { $node = $dom->getElementsByTagName('body'); } From 39fbabc5e81512078d9f15251d957701da56dca0 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 9 May 2024 00:37:05 -0700 Subject: [PATCH 003/153] Load Html as Html Not Xml Better from a technical standpoint. Pre-processing much simplified. Some post-processing added because loadHTML preserveWhiteSpace=false does not work well. Not-UTF8 html can now be loaded. No need to surround snippets with body tag. --- src/PhpWord/Shared/Html.php | 69 +++++++++++++++--- .../PhpWordTests/Reader/Html/CharsetTest.php | 63 ++++++++++++++++ .../Reader/{ => Html}/HTMLTest.php | 4 +- tests/PhpWordTests/Shared/HtmlTest.php | 40 +++++++--- .../_files/html/charset.ISO-8859-1.html | 17 +++++ .../_files/html/charset.ISO-8859-1.html4.html | 17 +++++ .../_files/html/charset.ISO-8859-2.html | 17 +++++ .../_files/html/charset.UTF-16.bebom.html | Bin 0 -> 604 bytes .../_files/html/charset.UTF-16.lebom.html | Bin 0 -> 604 bytes .../_files/html/charset.UTF-8.bom.html | 16 ++++ .../_files/html/charset.UTF-8.html | 17 +++++ .../_files/html/charset.gb18030.html | 9 +++ .../_files/html/charset.nocharset.html | 8 ++ .../_files/html/charset.unknown.html | 17 +++++ 14 files changed, 270 insertions(+), 24 deletions(-) create mode 100644 tests/PhpWordTests/Reader/Html/CharsetTest.php rename tests/PhpWordTests/Reader/{ => Html}/HTMLTest.php (92%) create mode 100644 tests/PhpWordTests/_files/html/charset.ISO-8859-1.html create mode 100644 tests/PhpWordTests/_files/html/charset.ISO-8859-1.html4.html create mode 100644 tests/PhpWordTests/_files/html/charset.ISO-8859-2.html create mode 100644 tests/PhpWordTests/_files/html/charset.UTF-16.bebom.html create mode 100644 tests/PhpWordTests/_files/html/charset.UTF-16.lebom.html create mode 100644 tests/PhpWordTests/_files/html/charset.UTF-8.bom.html create mode 100644 tests/PhpWordTests/_files/html/charset.UTF-8.html create mode 100644 tests/PhpWordTests/_files/html/charset.gb18030.html create mode 100644 tests/PhpWordTests/_files/html/charset.nocharset.html create mode 100644 tests/PhpWordTests/_files/html/charset.unknown.html diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 6f168164c6..9210aec19c 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -46,6 +46,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*$/'; + private const DECLARES_CHARSET = '/ charset=/i'; + protected static $listIndex = 0; protected static $xpath; @@ -55,6 +57,9 @@ class Html /** @var ?DocInfo */ protected static $docInfo; + /** @var bool */ + private static $addbody = false; + /** * @var Css */ @@ -88,16 +93,14 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit } } - // Preprocess: remove all line ends, decode HTML entity, - // fix ampersand and angle brackets and add body tag for HTML fragments - $html = str_replace(["\n", "\r"], '', $html); - $html = str_replace(['<', '>', '&', '"'], ['_lt_', '_gt_', '_amp_', '_quot_'], $html); - $html = html_entity_decode($html, ENT_QUOTES, 'UTF-8'); - $html = str_replace('&', '&', $html); - $html = str_replace(['_lt_', '_gt_', '_amp_', '_quot_'], ['<', '>', '&', '"'], $html); - - if (false === $fullHTML) { - $html = '' . $html . ''; + if (substr($html, 0, 2) === "\xfe\xff" || substr($html, 0, 2) === "\xff\xfe") { + $html = mb_convert_encoding($html, 'UTF-8', 'UTF-16'); + } + if (substr($html, 0, 3) === "\xEF\xBB\xBF") { + $html = substr($html, 3); + } + if (self::$addbody && false === $fullHTML) { + $html = '' . $html . ''; // @codeCoverageIgnore } // Load DOM @@ -105,8 +108,20 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit $orignalLibEntityLoader = libxml_disable_entity_loader(true); // @codeCoverageIgnore } $dom = new DOMDocument(); + $html = self::replaceNonAsciiIfNeeded($html); $dom->preserveWhiteSpace = $preserveWhiteSpace; - $dom->loadXML($html); + + try { + $result = $dom->loadHTML($html); + $exceptionMessage = 'DOM loadHTML failed'; + } catch (Exception $e) { + $result = false; + $exceptionMessage = $e->getMessage(); + } + if ($result === false) { + throw new Exception($exceptionMessage); + } + self::removeAnnoyingWhitespaceTextNodes($dom); static::$xpath = new DOMXPath($dom); $node = $dom->getElementsByTagName('html'); if (count($node) === 0 || $node->item(0) === null) { @@ -119,6 +134,38 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit } } + // https://www.php.net/manual/en/domdocument.loadhtml.php + private static function removeAnnoyingWhitespaceTextNodes(DOMNode $node): void + { + if ($node->hasChildNodes()) { + for ($i = $node->childNodes->length - 1; $i >= 0; --$i) { + self::removeAnnoyingWhitespaceTextNodes($node->childNodes->item($i)); + } + } + if ($node->nodeType === XML_TEXT_NODE && !$node->hasChildNodes() && !$node->hasAttributes() && empty(trim($node->textContent))) { + $node->parentNode->removeChild($node); + } + } + + private static function replaceNonAscii(array $matches): string + { + return '&#' . mb_ord($matches[0], 'UTF-8') . ';'; + } + + private static function replaceNonAsciiIfNeeded(string $convert): ?string + { + if (preg_match(self::DECLARES_CHARSET, $convert) !== 1) { + $lowend = "\u{80}"; + $highend = "\u{10ffff}"; + $regexp = "/[$lowend-$highend]/u"; + /** @var callable $callback */ + $callback = [self::class, 'replaceNonAscii']; + $convert = preg_replace_callback($regexp, $callback, $convert); + } + + return $convert; + } + /** * parse Inline style of a node. * diff --git a/tests/PhpWordTests/Reader/Html/CharsetTest.php b/tests/PhpWordTests/Reader/Html/CharsetTest.php new file mode 100644 index 0000000000..60e80964a2 --- /dev/null +++ b/tests/PhpWordTests/Reader/Html/CharsetTest.php @@ -0,0 +1,63 @@ +expectException(Throwable::class); + $this->expectExceptionMessage('unknown encoding'); + } + $directory = 'tests/PhpWordTests/_files/html'; + $reader = new HTML(); + $doc = $reader->load("$directory/$filename"); + $sections = $doc->getSections(); + self::assertCount(1, $sections); + $section = $sections[0]; + $elements = $section->getElements(); + $element = $elements[0]; + self::assertInstanceOf(TextRun::class, $element); + self::assertSame($expectedResult, $element->getText()); + } + + public static function providerCharset(): array + { + return [ + ['charset.ISO-8859-1.html', 'À1'], + ['charset.ISO-8859-1.html4.html', 'À1'], + ['charset.ISO-8859-2.html', 'Ŕ1'], + ['charset.nocharset.html', 'À1'], + ['charset.UTF-8.html', 'À1'], + ['charset.UTF-8.bom.html', 'À1'], + ['charset.UTF-16.bebom.html', 'À1'], + ['charset.UTF-16.lebom.html', 'À1'], + ['charset.gb18030.html', '电视机'], + ['charset.unknown.html', 'exception'], + ]; + } +} diff --git a/tests/PhpWordTests/Reader/HTMLTest.php b/tests/PhpWordTests/Reader/Html/HTMLTest.php similarity index 92% rename from tests/PhpWordTests/Reader/HTMLTest.php rename to tests/PhpWordTests/Reader/Html/HTMLTest.php index f091e5a275..c0e150e495 100644 --- a/tests/PhpWordTests/Reader/HTMLTest.php +++ b/tests/PhpWordTests/Reader/Html/HTMLTest.php @@ -15,7 +15,7 @@ * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ -namespace PhpOffice\PhpWordTests\Reader; +namespace PhpOffice\PhpWordTests\Reader\Html; use Exception; use PhpOffice\PhpWord\IOFactory; @@ -34,7 +34,7 @@ class HTMLTest extends \PHPUnit\Framework\TestCase */ public function testLoad(): void { - $filename = __DIR__ . '/../_files/documents/reader.html'; + $filename = 'tests/PhpWordTests/_files/documents/reader.html'; $phpWord = IOFactory::load($filename, 'HTML'); self::assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); } diff --git a/tests/PhpWordTests/Shared/HtmlTest.php b/tests/PhpWordTests/Shared/HtmlTest.php index 765d79f906..76e8273ec4 100644 --- a/tests/PhpWordTests/Shared/HtmlTest.php +++ b/tests/PhpWordTests/Shared/HtmlTest.php @@ -23,6 +23,7 @@ use PhpOffice\PhpWord\Element\Text; use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Html; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\LineSpacingRule; @@ -52,6 +53,7 @@ protected function tearDown(): void */ public function testAddHtml(): void { + Settings::setOutputEscapingEnabled(true); $content = ''; // Default @@ -80,16 +82,31 @@ public function testAddHtml(): void // Other parts $section = $phpWord->addSection(); $content = ''; + $expectd = ''; $content .= '
HeaderContent
'; $content .= ''; $content .= '
  1. Bullet
'; $content .= "'Single Quoted Text'"; + $expectd .= "'Single Quoted Text'"; $content .= '"Double Quoted Text"'; - $content .= '& Ampersand'; + $expectd .= '"Double Quoted Text"'; + $content .= '& Ampersand'; + $expectd .= '& Ampersand'; $content .= '<>“‘’«»‹›'; + $expectd .= '<>“‘’«»‹›'; $content .= '&•°…™©®—'; + $expectd .= '&•°…™©®—'; $content .= '–   ²³¼½¾'; + $expectd .= "–\u{a0}  ²³¼½¾"; Html::addHtml($section, $content); + $elements = $section->getElements(); + foreach ($elements as $element) { + if ($element instanceof Text) { + self::assertSame($expectd, $element->getText()); + + break; + } + } } /** @@ -110,7 +127,7 @@ public function testParseFullHtml(): void */ public function testParseHtmlEntities(): void { - \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); + Settings::setOutputEscapingEnabled(true); $phpWord = new PhpWord(); $section = $phpWord->addSection(); Html::addHtml($section, 'text with entities <my text>'); @@ -138,13 +155,14 @@ public function testParseStyle(): void Html::addHtml($section, $html); $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); - self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]')); - self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r')); - self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:t')); - self::assertEquals('Calculator', $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:t')->nodeValue); - self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:rPr')); - self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:rPr/w:sz')); - self::assertEquals('22.5', $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:r/w:rPr/w:sz', 'w:val')); + $element = '/w:document/w:body/w:p'; + self::assertTrue($doc->elementExists($element)); + self::assertTrue($doc->elementExists("$element/w:r")); + self::assertTrue($doc->elementExists("$element/w:r/w:t")); + self::assertEquals('Calculator', $doc->getElement("$element/w:r/w:t")->nodeValue); + self::assertTrue($doc->elementExists("$element/w:r/w:rPr")); + self::assertTrue($doc->elementExists("$element/w:r/w:rPr/w:sz")); + self::assertEquals('22.5', $doc->getElementAttribute("$element/w:r/w:rPr/w:sz", 'w:val')); } public function testParseStyleTableClassName(): void @@ -778,7 +796,7 @@ public function testParseListWithFormat(): void { $phpWord = new PhpWord(); $section = $phpWord->addSection(); - $html = preg_replace('/\s+/', ' ', ''; Html::addHtml($section, $html, false, false); $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); diff --git a/tests/PhpWordTests/_files/html/charset.ISO-8859-1.html b/tests/PhpWordTests/_files/html/charset.ISO-8859-1.html new file mode 100644 index 0000000000..fd27c975f3 --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.ISO-8859-1.html @@ -0,0 +1,17 @@ + + + + + ISO-8859-1 + + +

1

+

B1

+

1

+

D1

+

2

+

B2

+

C2

+

2

+ + diff --git a/tests/PhpWordTests/_files/html/charset.ISO-8859-1.html4.html b/tests/PhpWordTests/_files/html/charset.ISO-8859-1.html4.html new file mode 100644 index 0000000000..8a14894517 --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.ISO-8859-1.html4.html @@ -0,0 +1,17 @@ + + + + + ISO-8859-1 Html4 Doctype and Meta + + +

1

+

B1

+

1

+

D1

+

2

+

B2

+

C2

+

2

+ + diff --git a/tests/PhpWordTests/_files/html/charset.ISO-8859-2.html b/tests/PhpWordTests/_files/html/charset.ISO-8859-2.html new file mode 100644 index 0000000000..c2b494ff99 --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.ISO-8859-2.html @@ -0,0 +1,17 @@ + + + + + ISO-8859-2 + + +

1

+

B1

+

1

+

D1

+

2

+

B2

+

C2

+

2

+ + diff --git a/tests/PhpWordTests/_files/html/charset.UTF-16.bebom.html b/tests/PhpWordTests/_files/html/charset.UTF-16.bebom.html new file mode 100644 index 0000000000000000000000000000000000000000..6b29e7d2b011f7d8099fd407244dad7ef0d3121a GIT binary patch literal 604 zcmezOpFsf%Z5R|8Tp0WroEbtGA{hc0T!G>l3?&S?3^@#T3|vU+V4?~@nM8&>hI9s7 z26cv1AWI!XWhzib3YusMLnhGN9H0(6hESkwZVb8%h74v5HVpbeafBSQo%%qxU^6R; zAs=XdC5jC&AHirFh614c0iYcq8$fJ?$uKoA8lu*T1hvmeQ0qd1+QSS+B)FG&waz4% UdjYFjP$*&xcaS(NZ2-d<0BnUsYybcN literal 0 HcmV?d00001 diff --git a/tests/PhpWordTests/_files/html/charset.UTF-16.lebom.html b/tests/PhpWordTests/_files/html/charset.UTF-16.lebom.html new file mode 100644 index 0000000000000000000000000000000000000000..4ba47a81395d2f8076299b471e0357e1287ce1e3 GIT binary patch literal 604 zcmezWPk{jfZ5R|8Tp0WroEbtGA{hc0T!G>l3?&S?3^@#T3|vrE3Jf-IQ3aq(B10ZS zI)g2PIzuXurH-L86{sQwO|*m|6KHM@P=_5uD9|=H23-b21~Z_^`ap4n9I~DIKwGhy zmBf$_G`|wX2AGdvv<*W6Q2qeW4v-BXHo|0>8W;^x>qLUu=On0gAwlh71|t&OOT1cV U63o4TRV^qKv4uNG967}R0Iu{zYybcN literal 0 HcmV?d00001 diff --git a/tests/PhpWordTests/_files/html/charset.UTF-8.bom.html b/tests/PhpWordTests/_files/html/charset.UTF-8.bom.html new file mode 100644 index 0000000000..5a49399018 --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.UTF-8.bom.html @@ -0,0 +1,16 @@ + + + + UTF-8 + + +

À1

+

B1

+

ç1

+

D1

+

Ã2

+

B2

+

C2

+

Ð2

+ + diff --git a/tests/PhpWordTests/_files/html/charset.UTF-8.html b/tests/PhpWordTests/_files/html/charset.UTF-8.html new file mode 100644 index 0000000000..9ae5a8e343 --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.UTF-8.html @@ -0,0 +1,17 @@ + + + + + UTF-8 + + +

À1

+

B1

+

ç1

+

D1

+

Ã2

+

B2

+

C2

+

Ð2

+ + diff --git a/tests/PhpWordTests/_files/html/charset.gb18030.html b/tests/PhpWordTests/_files/html/charset.gb18030.html new file mode 100644 index 0000000000..271a55fc54 --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.gb18030.html @@ -0,0 +1,9 @@ + + + +gb18030 + + +

ӻ

+ + diff --git a/tests/PhpWordTests/_files/html/charset.nocharset.html b/tests/PhpWordTests/_files/html/charset.nocharset.html new file mode 100644 index 0000000000..d6829b2edc --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.nocharset.html @@ -0,0 +1,8 @@ +

À1

+

B1

+

ç1

+

D1

+

Ã2

+

B2

+

C2

+

Ð2

diff --git a/tests/PhpWordTests/_files/html/charset.unknown.html b/tests/PhpWordTests/_files/html/charset.unknown.html new file mode 100644 index 0000000000..189638a80f --- /dev/null +++ b/tests/PhpWordTests/_files/html/charset.unknown.html @@ -0,0 +1,17 @@ + + + + + UTF-8 + + +

À1

+

B1

+

ç1

+

D1

+

Ã2

+

B2

+

C2

+

Ð2

+ + From cf1fd2a112419e72878b40a7589212f2dfd29c3c Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 9 May 2024 17:35:13 -0700 Subject: [PATCH 004/153] Permit Some Backwards Compatibility It's debatable, but allow unescaped ampersand and unknown charset. --- phpunit.xml.dist | 4 ++-- src/PhpWord/Shared/Html.php | 4 ++-- tests/PhpWordTests/Reader/Html/CharsetTest.php | 2 +- tests/PhpWordTests/Shared/HtmlTest.php | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6f1f5445ab..ff0c676fad 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -7,10 +7,10 @@ ./src/PhpWord/Shared/PCLZip - + diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 9210aec19c..2d68806f33 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -112,14 +112,14 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit $dom->preserveWhiteSpace = $preserveWhiteSpace; try { - $result = $dom->loadHTML($html); + $result = $dom->loadHTML($html, LIBXML_NOWARNING | LIBXML_NOERROR); $exceptionMessage = 'DOM loadHTML failed'; } catch (Exception $e) { $result = false; $exceptionMessage = $e->getMessage(); } if ($result === false) { - throw new Exception($exceptionMessage); + throw new Exception($exceptionMessage); // @codeCoverageIgnore } self::removeAnnoyingWhitespaceTextNodes($dom); static::$xpath = new DOMXPath($dom); diff --git a/tests/PhpWordTests/Reader/Html/CharsetTest.php b/tests/PhpWordTests/Reader/Html/CharsetTest.php index 60e80964a2..4c73d70bb0 100644 --- a/tests/PhpWordTests/Reader/Html/CharsetTest.php +++ b/tests/PhpWordTests/Reader/Html/CharsetTest.php @@ -57,7 +57,7 @@ public static function providerCharset(): array ['charset.UTF-16.bebom.html', 'À1'], ['charset.UTF-16.lebom.html', 'À1'], ['charset.gb18030.html', '电视机'], - ['charset.unknown.html', 'exception'], + 'loadhtml gives its best shot' => ['charset.unknown.html', "Ã\u{80}1"], ]; } } diff --git a/tests/PhpWordTests/Shared/HtmlTest.php b/tests/PhpWordTests/Shared/HtmlTest.php index 76e8273ec4..3fa4b5c1d0 100644 --- a/tests/PhpWordTests/Shared/HtmlTest.php +++ b/tests/PhpWordTests/Shared/HtmlTest.php @@ -90,14 +90,14 @@ public function testAddHtml(): void $expectd .= "'Single Quoted Text'"; $content .= '"Double Quoted Text"'; $expectd .= '"Double Quoted Text"'; - $content .= '& Ampersand'; + $content .= '& Ampersand'; $expectd .= '& Ampersand'; - $content .= '<>“‘’«»‹›'; - $expectd .= '<>“‘’«»‹›'; + $content .= '<>“”‘’«»‹›'; + $expectd .= '<>“”‘’«»‹›'; $content .= '&•°…™©®—'; $expectd .= '&•°…™©®—'; $content .= '–   ²³¼½¾'; - $expectd .= "–\u{a0}  ²³¼½¾"; + $expectd .= "–\u{a0}\u{2003}\u{2002}²³¼½¾"; Html::addHtml($section, $content); $elements = $section->getElements(); foreach ($elements as $element) { From 2f799c2ba9be781106ac84545a58b58b2fc00f98 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 16 Aug 2024 08:10:24 -0700 Subject: [PATCH 005/153] Phpstan False Positives --- src/PhpWord/Shared/Html.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index d78338526a..2d96c375f7 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -180,7 +180,7 @@ protected static function parseInlineStyle($node, &$styles) $attributes = $node->attributes; // get all the attributes(eg: id, class) $bidi = false; - $direction = isset($attributes['dir']) ? $attributes['dir']->value : ''; + $direction = isset($attributes['dir']) ? $attributes['dir']->value : ''; // @phpstan-ignore-line if ($direction === 'rtl') { $bidi = $styles['bidi'] = $styles['rtl'] = true; $styles['textDirection'] = TextDirection::RLTB; @@ -550,7 +550,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')->value; // @phpstan-ignore-line $newElement->getStyle()->setBorderSize((int) Converter::pixelToTwip($border)); $newElement->getStyle()->setBorderStyle(($border === 0) ? 'none' : 'single'); } From d9120c7f3782496d606ec087baa524ced709a51e Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 16 Aug 2024 08:39:59 -0700 Subject: [PATCH 006/153] Improve Resolution of Phpstan Problem --- src/PhpWord/Shared/Html.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 2d96c375f7..7fa9c6f36f 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -180,7 +180,8 @@ protected static function parseInlineStyle($node, &$styles) $attributes = $node->attributes; // get all the attributes(eg: id, class) $bidi = false; - $direction = isset($attributes['dir']) ? $attributes['dir']->value : ''; // @phpstan-ignore-line + $attrDir = $attributes->getNamedItem('dir'); + $direction = isset($attrDir) ? $attrDir->nodeValue : ''; if ($direction === 'rtl') { $bidi = $styles['bidi'] = $styles['rtl'] = true; $styles['textDirection'] = TextDirection::RLTB; @@ -550,7 +551,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; // @phpstan-ignore-line + $border = (int) $attributes->getNamedItem('border')->nodeValue; $newElement->getStyle()->setBorderSize((int) Converter::pixelToTwip($border)); $newElement->getStyle()->setBorderStyle(($border === 0) ? 'none' : 'single'); } From 1093a3b3ad5295a71cd3d7dbb5ed56d4f5de69c6 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 3 Sep 2024 23:34:34 -0700 Subject: [PATCH 007/153] Update Sample_45_RTLTitles.php --- samples/Sample_45_RTLTitles.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/Sample_45_RTLTitles.php b/samples/Sample_45_RTLTitles.php index 83dd9b9872..e39510c97f 100644 --- a/samples/Sample_45_RTLTitles.php +++ b/samples/Sample_45_RTLTitles.php @@ -6,7 +6,7 @@ use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Html as SharedHtml; -// Suggested by issue 2427. +// Suggested by issue #2427. echo date('H:i:s'), ' Create new PhpWord object', EOL; $phpWord = new PhpWord(); Settings::setDefaultRtl(true); From 74918d0909c8224b1a46a4dfbf1494f11e8dd5ed Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 3 Sep 2024 23:38:32 -0700 Subject: [PATCH 008/153] Update Sample_45_RTLTitles.php --- samples/Sample_45_RTLTitles.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/Sample_45_RTLTitles.php b/samples/Sample_45_RTLTitles.php index e39510c97f..83dd9b9872 100644 --- a/samples/Sample_45_RTLTitles.php +++ b/samples/Sample_45_RTLTitles.php @@ -6,7 +6,7 @@ use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Html as SharedHtml; -// Suggested by issue #2427. +// Suggested by issue 2427. echo date('H:i:s'), ' Create new PhpWord object', EOL; $phpWord = new PhpWord(); Settings::setDefaultRtl(true); From 28b1b08ee77462e87b0b69d364f1688d0e0eacd7 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 4 Sep 2024 00:20:17 -0700 Subject: [PATCH 009/153] Update Sample_45_RTLTitles.php --- samples/Sample_45_RTLTitles.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/Sample_45_RTLTitles.php b/samples/Sample_45_RTLTitles.php index 83dd9b9872..e39510c97f 100644 --- a/samples/Sample_45_RTLTitles.php +++ b/samples/Sample_45_RTLTitles.php @@ -6,7 +6,7 @@ use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Html as SharedHtml; -// Suggested by issue 2427. +// Suggested by issue #2427. echo date('H:i:s'), ' Create new PhpWord object', EOL; $phpWord = new PhpWord(); Settings::setDefaultRtl(true); From 32a73505a08fd5c2136d4c128046e0585c4be5ab Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:50:15 -0800 Subject: [PATCH 010/153] Update HTMLTest.php --- tests/PhpWordTests/Reader/Html/HTMLTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/PhpWordTests/Reader/Html/HTMLTest.php b/tests/PhpWordTests/Reader/Html/HTMLTest.php index c0e150e495..9b16662c08 100644 --- a/tests/PhpWordTests/Reader/Html/HTMLTest.php +++ b/tests/PhpWordTests/Reader/Html/HTMLTest.php @@ -1,4 +1,5 @@ Date: Thu, 9 Jan 2025 15:29:38 -0800 Subject: [PATCH 011/153] Move One Test to Avoid Merge Conflict --- tests/PhpWordTests/Reader/{Html => }/HTMLTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tests/PhpWordTests/Reader/{Html => }/HTMLTest.php (92%) diff --git a/tests/PhpWordTests/Reader/Html/HTMLTest.php b/tests/PhpWordTests/Reader/HTMLTest.php similarity index 92% rename from tests/PhpWordTests/Reader/Html/HTMLTest.php rename to tests/PhpWordTests/Reader/HTMLTest.php index 9b16662c08..7a35a06f78 100644 --- a/tests/PhpWordTests/Reader/Html/HTMLTest.php +++ b/tests/PhpWordTests/Reader/HTMLTest.php @@ -16,7 +16,7 @@ * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ -namespace PhpOffice\PhpWordTests\Reader\Html; +namespace PhpOffice\PhpWordTests\Reader; use Exception; use PhpOffice\PhpWord\IOFactory; @@ -35,7 +35,7 @@ class HTMLTest extends \PHPUnit\Framework\TestCase */ public function testLoad(): void { - $filename = 'tests/PhpWordTests/_files/documents/reader.html'; + $filename = __DIR__ . '/../_files/documents/reader.html'; $phpWord = IOFactory::load($filename, 'HTML'); self::assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); } From e9316c07c277036fb875b0f322e8da55533666ba Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 9 Jan 2025 15:48:25 -0800 Subject: [PATCH 012/153] Try to Correct Merge Conflicts in Writer/HTML/FontTest --- tests/PhpWordTests/Writer/HTML/FontTest.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/PhpWordTests/Writer/HTML/FontTest.php b/tests/PhpWordTests/Writer/HTML/FontTest.php index 08a8fca6a4..20ab8d9920 100644 --- a/tests/PhpWordTests/Writer/HTML/FontTest.php +++ b/tests/PhpWordTests/Writer/HTML/FontTest.php @@ -1,4 +1,5 @@ Date: Thu, 9 Jan 2025 16:03:07 -0800 Subject: [PATCH 013/153] Trying FontTest One More Time Before Giving Up --- tests/PhpWordTests/Writer/HTML/FontTest.php | 42 ++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/PhpWordTests/Writer/HTML/FontTest.php b/tests/PhpWordTests/Writer/HTML/FontTest.php index 20ab8d9920..6416a44d2f 100644 --- a/tests/PhpWordTests/Writer/HTML/FontTest.php +++ b/tests/PhpWordTests/Writer/HTML/FontTest.php @@ -87,27 +87,27 @@ public function testFontNames1(): void $style = Helper::getTextContent($xpath, '/html/head/style'); $prg = preg_match('/^body[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('body {font-family: \'Courier New\'; font-size: 12pt;}', $matches[0]); $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]); $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style2 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]); $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style3 {font-family: \'hack attempt'}; display:none\'; font-size: 10pt;}', $matches[0]); $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style4 {font-family: \'padmaa 1.1\'; font-size: 10pt; font-weight: bold;}', $matches[0]); $prg = preg_match('/^[.]style5[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style5 {font-family: \'MingLiU-ExtB\'; font-size: 10pt; font-weight: bold;}', $matches[0]); } @@ -143,23 +143,23 @@ public function testFontNames2(): void $style = Helper::getTextContent($xpath, '/html/head/style'); $prg = preg_match('/^body[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('body {font-family: \'Courier New\'; font-size: 12pt;}', $matches[0]); $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]); - self::assertNotEmpty($matches); $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); - self::assertSame(1, $prg); + self::assertNotEmpty($matches); + self::assertNotFalse($prg); self::assertEquals('.style2 {font-family: \'Arial\', sans-serif; font-size: 10pt;}', $matches[0]); $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style3 {font-family: \'DejaVu Sans Monospace\', monospace; font-size: 10pt;}', $matches[0]); $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style4 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]); } @@ -195,23 +195,23 @@ public function testFontNames3(): void $style = Helper::getTextContent($xpath, '/html/head/style'); $prg = preg_match('/^body[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('body {font-family: \'Courier New\', monospace; font-size: 12pt;}', $matches[0]); $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]); $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style2 {font-family: \'Arial\', sans-serif; font-size: 10pt;}', $matches[0]); $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style3 {font-family: \'DejaVu Sans Monospace\', monospace; font-size: 10pt;}', $matches[0]); $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style4 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]); } @@ -242,19 +242,19 @@ public function testWhiteSpace(): void self::assertEquals('body {font-family: \'Arial\'; font-size: 12pt; white-space: pre-wrap;}', $matches[0]); $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style1 {font-family: \'Courier New\'; font-size: 10pt; white-space: pre-wrap;}', $matches[0]); $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style2 {font-family: \'Courier New\'; font-size: 10pt;}', $matches[0]); $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style3 {font-family: \'Courier New\'; font-size: 10pt; white-space: normal;}', $matches[0]); $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); - self::assertSame(1, $prg); + self::assertNotFalse($prg); self::assertEquals('.style4 {font-family: \'Courier New\'; font-size: 10pt;}', $matches[0]); } From 8e6726c24a8e6a5db7c3dea0f6f3b17eb1064b5a Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:55:59 -0800 Subject: [PATCH 014/153] Try to Catch Up --- composer.json | 2 +- src/PhpWord/Shared/Html.php | 4 ++-- src/PhpWord/SimpleType/TextDirection.php | 1 + tests/PhpWordTests/Reader/Html/CharsetTest.php | 1 + tests/PhpWordTests/Reader/Word2007/StyleTableTest.php | 1 + tests/PhpWordTests/SettingsRtlTest.php | 1 + tests/PhpWordTests/Shared/Html2402Test.php | 1 + tests/PhpWordTests/Shared/HtmlFullTest.php | 1 + tests/PhpWordTests/Shared/HtmlHeadingsTest.php | 1 + tests/PhpWordTests/Shared/HtmlRtlTest.php | 5 +++-- tests/PhpWordTests/Shared/HtmlTest.php | 8 ++++---- tests/PhpWordTests/TemplateProcessorSectionTest.php | 1 + tests/PhpWordTests/Writer/RTF/RichTextTitleTest.php | 1 + 13 files changed, 19 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index efebe941e7..2d06f890b3 100644 --- a/composer.json +++ b/composer.json @@ -122,7 +122,7 @@ "phpmd/phpmd": "^2.13", "phpstan/phpstan": "^0.12.88 || ^1.0.0", "phpstan/phpstan-phpunit": "^1.0 || ^2.0", - "phpunit/phpunit": ">=7.0", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0 || ^10.0", "symfony/process": "^4.4 || ^5.0", "tecnickcom/tcpdf": "^6.5" }, diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 7c2fcab0e6..81573cd83d 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -417,7 +417,7 @@ protected static function parseChildNodes($node, $element, $styles, $data): void * @param AbstractContainer $element * @param array &$styles * - * @return \PhpOffice\PhpWord\Element\PageBreak|\PhpOffice\PhpWord\Element\TextRun + * @return \PhpOffice\PhpWord\Element\PageBreak|TextRun */ protected static function parseParagraph($node, $element, &$styles) { @@ -590,7 +590,7 @@ protected static function parseRow($node, $element, &$styles) * @param Table $element * @param array &$styles * - * @return \PhpOffice\PhpWord\Element\Cell|\PhpOffice\PhpWord\Element\TextRun $element + * @return \PhpOffice\PhpWord\Element\Cell|TextRun $element */ protected static function parseCell($node, $element, &$styles) { diff --git a/src/PhpWord/SimpleType/TextDirection.php b/src/PhpWord/SimpleType/TextDirection.php index 0797fa9294..ab1a045337 100644 --- a/src/PhpWord/SimpleType/TextDirection.php +++ b/src/PhpWord/SimpleType/TextDirection.php @@ -1,4 +1,5 @@ addSection([ - 'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE, + 'orientation' => Style\Section::ORIENTATION_LANDSCAPE, ]); // borders & backgrounds are here just for better visual comparison @@ -548,7 +548,7 @@ public function testParseTableRowHeight(): void { $phpWord = new PhpWord(); $section = $phpWord->addSection([ - 'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE, + 'orientation' => Style\Section::ORIENTATION_LANDSCAPE, ]); $html = <<addSection([ - 'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE, + 'orientation' => Style\Section::ORIENTATION_LANDSCAPE, ]); // borders & backgrounds are here just for better visual comparison @@ -670,7 +670,7 @@ public function testParseTableStyleAttributeInlineStyle(): void { $phpWord = new PhpWord(); $section = $phpWord->addSection([ - 'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE, + 'orientation' => Style\Section::ORIENTATION_LANDSCAPE, ]); $html = ' diff --git a/tests/PhpWordTests/TemplateProcessorSectionTest.php b/tests/PhpWordTests/TemplateProcessorSectionTest.php index 0402d4fc66..a6e9d61163 100644 --- a/tests/PhpWordTests/TemplateProcessorSectionTest.php +++ b/tests/PhpWordTests/TemplateProcessorSectionTest.php @@ -1,4 +1,5 @@ Date: Sun, 12 Jan 2025 14:45:02 -0800 Subject: [PATCH 015/153] Experiment with Different Phpunit Xml Dists --- .github/workflows/php.yml | 14 +++++++++++--- phpunit.7.8.xml.dist | 12 ++++++++++++ phpunit10.xml.dist | 20 ++++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 phpunit.7.8.xml.dist create mode 100644 phpunit10.xml.dist diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 0e2e9ea96d..b403e57d8a 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -83,11 +83,19 @@ jobs: - name: Composer Install run: composer install --ansi --prefer-dist --no-interaction --no-progress - - name: Run phpunit - if: matrix.php != '7.3' + - name: Run phpunit 7.1 7.2 + if: matrix.php == '7.1' || matrix.php == '7.2' + run: ./vendor/bin/phpunit -c phpunit.7.8.xml.dist --no-coverage + + - name: Run phpunit 7.4 8.0 + if: matrix.php == '7.4' || matrix.php == '8.0' run: ./vendor/bin/phpunit -c phpunit.xml.dist --no-coverage - - name: Run phpunit + - name: Run phpunit 8.1 8.2 8.3 8.4 + if: matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.3' || matrix.php == '8.4' + run: ./vendor/bin/phpunit -c phpunit10.xml.dist --no-coverage + + - name: Run phpunit 7.3 if: matrix.php == '7.3' run: ./vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover build/clover.xml diff --git a/phpunit.7.8.xml.dist b/phpunit.7.8.xml.dist new file mode 100644 index 0000000000..a8327fb286 --- /dev/null +++ b/phpunit.7.8.xml.dist @@ -0,0 +1,12 @@ + + + + + + + + ./tests/PhpWordTests + + + + diff --git a/phpunit10.xml.dist b/phpunit10.xml.dist new file mode 100644 index 0000000000..b97fbee3fb --- /dev/null +++ b/phpunit10.xml.dist @@ -0,0 +1,20 @@ + + + + + + + + ./tests/PhpWordTests + + + + + + ./src + + + ./src/PhpWord/Shared/PCLZip + + + From 737cb3708a9be2bd0f13c42b15ca8d635baa2e7b Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 12 Jan 2025 14:53:38 -0800 Subject: [PATCH 016/153] Remove Logging from 7.8 Config --- phpunit.7.8.xml.dist | 1 - 1 file changed, 1 deletion(-) diff --git a/phpunit.7.8.xml.dist b/phpunit.7.8.xml.dist index a8327fb286..22e77a0ea6 100644 --- a/phpunit.7.8.xml.dist +++ b/phpunit.7.8.xml.dist @@ -8,5 +8,4 @@ ./tests/PhpWordTests - From 508464223d3b5dae4e9524fd2ab3768248b2b62f Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 12 Jan 2025 15:13:50 -0800 Subject: [PATCH 017/153] More Phpunit Tweaking --- .github/workflows/php.yml | 4 ++-- phpunit.xml.dist | 22 +++++++++------------- phpunit9.xml.dist | 24 ++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 phpunit9.xml.dist diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index b403e57d8a..016120d913 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -89,7 +89,7 @@ jobs: - name: Run phpunit 7.4 8.0 if: matrix.php == '7.4' || matrix.php == '8.0' - run: ./vendor/bin/phpunit -c phpunit.xml.dist --no-coverage + run: ./vendor/bin/phpunit -c phpunit9.xml.dist --no-coverage - name: Run phpunit 8.1 8.2 8.3 8.4 if: matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.3' || matrix.php == '8.4' @@ -97,7 +97,7 @@ jobs: - name: Run phpunit 7.3 if: matrix.php == '7.3' - run: ./vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover build/clover.xml + run: ./vendor/bin/phpunit -c phpunit9.xml.dist --coverage-clover build/clover.xml - name: Upload coverage results to Coveralls if: matrix.php == '7.3' diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ff0c676fad..b97fbee3fb 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,17 +1,5 @@ - - - - ./src - - - ./src/PhpWord/Shared/PCLZip - - - + @@ -21,4 +9,12 @@ + + + ./src + + + ./src/PhpWord/Shared/PCLZip + + diff --git a/phpunit9.xml.dist b/phpunit9.xml.dist new file mode 100644 index 0000000000..ff0c676fad --- /dev/null +++ b/phpunit9.xml.dist @@ -0,0 +1,24 @@ + + + + + ./src + + + ./src/PhpWord/Shared/PCLZip + + + + + + + + + ./tests/PhpWordTests + + + + From d1a16fef6c785311810115213f68d4a04eaf4e3e Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:43:15 -0800 Subject: [PATCH 018/153] Update HtmlTest.php --- tests/PhpWordTests/Shared/HtmlTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PhpWordTests/Shared/HtmlTest.php b/tests/PhpWordTests/Shared/HtmlTest.php index cf4d627c92..bc0899a8ac 100644 --- a/tests/PhpWordTests/Shared/HtmlTest.php +++ b/tests/PhpWordTests/Shared/HtmlTest.php @@ -28,9 +28,9 @@ use PhpOffice\PhpWord\Shared\Html; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\LineSpacingRule; +use PhpOffice\PhpWord\SimpleType\TblWidth; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; -use PhpOffice\PhpWord\SimpleType\TblWidth; use PhpOffice\PhpWord\Style\Paragraph; use PhpOffice\PhpWordTests\AbstractWebServerEmbedded; use PhpOffice\PhpWordTests\TestHelperDOCX; From f82eb47f8f6327e8883a6d921ac3b73017d6b78b Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 30 Jan 2025 15:00:10 -0800 Subject: [PATCH 019/153] Eliminate JSON Dependency JSON is used in exactly 2 places in the code and should not be used in either. XMLWriter uses it to cast a float to string. Aside from being silly, this actually causes a problem in Shared/HtmlTest, where one of the results should be 4000 but is instead tested for 3999.9999... The error should have been immaterial (a simple cast will give the correct answer), but the test was wrong, insisting on an exact match for a floating point answer. It should use assertEqualsWithDelta. Next, the reason why JSON got it "wrong" was because the conversions in Shared/Converter use the wrong order of operations - multiplications should be performed before divisions to help avoid rounding problems, but Converter was doing the divisions first. All the conversions there are changed to multiply before dividing. Finally, Shared/Html checks for width units of points or pixels, but should check for inches and centimers as well. Writer/Html outputs tracking properties as a JSON string for some unfathomable reason. It is changed to output each of the three (author, id, and date) as its own discrete property. Tests didn't actually confirm the value of the properties; they do now. Oh, yes, htmlspecialchars is needed for author and id, otherwise the html may wind up broken. Some simple changes are made to TestHelperDOCX to avoid intermittent problems on Windows, and to make the code a little cleaner. --- .gitignore | 3 +- composer.json | 1 - src/PhpWord/Shared/Converter.php | 24 +++++++-------- src/PhpWord/Shared/Html.php | 6 ++++ src/PhpWord/Shared/XMLWriter.php | 2 +- src/PhpWord/Writer/HTML/Element/Text.php | 24 ++++++++------- tests/PhpWordTests/Shared/HtmlTest.php | 7 +++-- tests/PhpWordTests/TestHelperDOCX.php | 24 +++++++++++---- .../PhpWordTests/Writer/HTML/ElementTest.php | 30 ++++++++++++++++++- 9 files changed, 86 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 0b9d0608d0..3ebe0dc1a8 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ phpword.ini /nbproject /.php_cs.cache /.phpunit.result.cache -/public \ No newline at end of file +/.phpunit.cache +/public diff --git a/composer.json b/composer.json index 2d06f890b3..9487640f2a 100644 --- a/composer.json +++ b/composer.json @@ -109,7 +109,6 @@ "php": "^7.1|^8.0", "ext-dom": "*", "ext-gd": "*", - "ext-json": "*", "ext-xml": "*", "ext-zip": "*", "phpoffice/math": "^0.2" diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php index 17d2e1a05d..82e11c0790 100644 --- a/src/PhpWord/Shared/Converter.php +++ b/src/PhpWord/Shared/Converter.php @@ -40,7 +40,7 @@ class Converter */ public static function cmToTwip($centimeter = 1) { - return $centimeter / self::INCH_TO_CM * self::INCH_TO_TWIP; + return $centimeter * self::INCH_TO_TWIP / self::INCH_TO_CM; } /** @@ -64,7 +64,7 @@ public static function cmToInch($centimeter = 1) */ public static function cmToPixel($centimeter = 1) { - return $centimeter / self::INCH_TO_CM * self::INCH_TO_PIXEL; + return $centimeter * self::INCH_TO_PIXEL / self::INCH_TO_CM; } /** @@ -76,7 +76,7 @@ public static function cmToPixel($centimeter = 1) */ public static function cmToPoint($centimeter = 1) { - return $centimeter / self::INCH_TO_CM * self::INCH_TO_POINT; + return $centimeter * self::INCH_TO_POINT / self::INCH_TO_CM; } /** @@ -88,7 +88,7 @@ public static function cmToPoint($centimeter = 1) */ public static function cmToEmu($centimeter = 1) { - return round($centimeter / self::INCH_TO_CM * self::INCH_TO_PIXEL * self::PIXEL_TO_EMU); + return round($centimeter * self::INCH_TO_PIXEL * self::PIXEL_TO_EMU / self::INCH_TO_CM); } /** @@ -160,7 +160,7 @@ public static function inchToEmu($inch = 1) */ public static function pixelToTwip($pixel = 1) { - return $pixel / self::INCH_TO_PIXEL * self::INCH_TO_TWIP; + return $pixel * self::INCH_TO_TWIP / self::INCH_TO_PIXEL; } /** @@ -172,7 +172,7 @@ public static function pixelToTwip($pixel = 1) */ public static function pixelToCm($pixel = 1) { - return $pixel / self::INCH_TO_PIXEL * self::INCH_TO_CM; + return $pixel * self::INCH_TO_CM / self::INCH_TO_PIXEL; } /** @@ -184,7 +184,7 @@ public static function pixelToCm($pixel = 1) */ public static function pixelToPoint($pixel = 1) { - return $pixel / self::INCH_TO_PIXEL * self::INCH_TO_POINT; + return $pixel * self::INCH_TO_POINT / self::INCH_TO_PIXEL; } /** @@ -208,7 +208,7 @@ public static function pixelToEmu($pixel = 1) */ public static function pointToTwip($point = 1) { - return $point / self::INCH_TO_POINT * self::INCH_TO_TWIP; + return $point * self::INCH_TO_TWIP / self::INCH_TO_POINT; } /** @@ -220,7 +220,7 @@ public static function pointToTwip($point = 1) */ public static function pointToPixel($point = 1) { - return $point / self::INCH_TO_POINT * self::INCH_TO_PIXEL; + return $point * self::INCH_TO_PIXEL / self::INCH_TO_POINT; } /** @@ -232,7 +232,7 @@ public static function pointToPixel($point = 1) */ public static function pointToEmu($point = 1) { - return round($point / self::INCH_TO_POINT * self::INCH_TO_PIXEL * self::PIXEL_TO_EMU); + return round($point * self::INCH_TO_PIXEL * self::PIXEL_TO_EMU / self::INCH_TO_POINT); } /** @@ -244,7 +244,7 @@ public static function pointToEmu($point = 1) */ public static function pointToCm($point = 1) { - return $point / self::INCH_TO_POINT * self::INCH_TO_CM; + return $point * self::INCH_TO_CM / self::INCH_TO_POINT; } /** @@ -268,7 +268,7 @@ public static function emuToPixel($emu = 1) */ public static function picaToPoint($pica = 1) { - return $pica / self::INCH_TO_PICA * self::INCH_TO_POINT; + return $pica * self::INCH_TO_POINT / self::INCH_TO_PICA; } /** diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 9b57f77adc..d1bf13d7f7 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -1477,6 +1477,12 @@ protected static function convertHtmlSize(string $size): float if (false !== strpos($size, 'px')) { return (float) str_replace('px', '', $size); } + if (false !== strpos($size, 'cm')) { + return Converter::cmToPixel((float) str_replace('cm', '', $size)); + } + if (false !== strpos($size, 'in')) { + return Converter::inchToPixel((float) str_replace('in', '', $size)); + } return (float) $size; } diff --git a/src/PhpWord/Shared/XMLWriter.php b/src/PhpWord/Shared/XMLWriter.php index 8dc28e1184..8b267b694c 100644 --- a/src/PhpWord/Shared/XMLWriter.php +++ b/src/PhpWord/Shared/XMLWriter.php @@ -180,7 +180,7 @@ public function writeAttributeIf($condition, $attribute, $value): void public function writeAttribute($name, $value) { if (is_float($value)) { - $value = json_encode($value); + $value = (string) $value; } return parent::writeAttribute($name, $value ?? ''); diff --git a/src/PhpWord/Writer/HTML/Element/Text.php b/src/PhpWord/Writer/HTML/Element/Text.php index 312d3a19c2..c6363d1da4 100644 --- a/src/PhpWord/Writer/HTML/Element/Text.php +++ b/src/PhpWord/Writer/HTML/Element/Text.php @@ -163,20 +163,22 @@ private function writeTrackChangeOpening() $content = ''; if (($changed->getChangeType() == TrackChange::INSERTED)) { - $content .= 'getChangeType() == TrackChange::DELETED) { - $content .= ' ['author' => $changed->getAuthor(), 'id' => $this->element->getElementId()]]; - if ($changed->getDate() != null) { - $changedProp['changed']['date'] = $changed->getDate()->format('Y-m-d\TH:i:s\Z'); + $author = htmlspecialchars($changed->getAuthor(), ENT_QUOTES); + $content .= " data-phpword-chg-author='$author'"; + $elementId = htmlspecialchars($this->element->getElementId(), ENT_QUOTES); + $content .= " data-phpword-chg-id='$elementId'"; + $date = $changed->getDate(); + if ($date !== null) { + $dateout = $date->format('Y-m-d\TH:i:s\Z'); + $content .= " data-phpword-chg-timestamp='$dateout'"; } - $content .= json_encode($changedProp); - $content .= '\' '; - $content .= 'title="' . $changed->getAuthor(); - if ($changed->getDate() != null) { - $dateUser = $changed->getDate()->format('Y-m-d H:i:s'); + $content .= ' title="' . $author; + if ($date !== null) { + $dateUser = $date->format('Y-m-d H:i:s'); $content .= ' - ' . $dateUser; } $content .= '">'; diff --git a/tests/PhpWordTests/Shared/HtmlTest.php b/tests/PhpWordTests/Shared/HtmlTest.php index 142a93e5a5..5131d6ea44 100644 --- a/tests/PhpWordTests/Shared/HtmlTest.php +++ b/tests/PhpWordTests/Shared/HtmlTest.php @@ -256,7 +256,8 @@ public function testParseWidth(string $htmlSize, float $docxSize, string $docxUn $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); $xpath = '/w:document/w:body/w:tbl/w:tblPr/w:tblW'; self::assertTrue($doc->elementExists($xpath)); - self::assertEquals($docxSize, $doc->getElement($xpath)->getAttribute('w:w')); + $actual = (float) $doc->getElement($xpath)->getAttribute('w:w'); + self::assertEqualsWithDelta($docxSize, $actual, 1.0e-12); self::assertEquals($docxUnit, $doc->getElement($xpath)->getAttribute('w:type')); } @@ -1371,9 +1372,11 @@ public static function providerParseWidth(): array return [ ['auto', 5000, TblWidth::PERCENT], ['100%', 5000, TblWidth::PERCENT], - ['200pt', 3999.999999999999, TblWidth::TWIP], + ['200pt', 4000, TblWidth::TWIP], ['300px', 4500, TblWidth::TWIP], ['400', 6000, TblWidth::TWIP], + ['2in', 2880, TblWidth::TWIP], + ['2.54cm', 1440, TblWidth::TWIP], ]; } diff --git a/tests/PhpWordTests/TestHelperDOCX.php b/tests/PhpWordTests/TestHelperDOCX.php index 2a6fbabae0..5ebe799cfc 100644 --- a/tests/PhpWordTests/TestHelperDOCX.php +++ b/tests/PhpWordTests/TestHelperDOCX.php @@ -47,13 +47,14 @@ class TestHelperDOCX */ public static function getDocument(PhpWord $phpWord, $writerName = 'Word2007') { + $tempdir = self::getTempDirPhpunit(); self::$file = tempnam(Settings::getTempDir(), 'PhpWord'); if (false === self::$file) { throw new CreateTemporaryFileException(); } - if (!is_dir(Settings::getTempDir() . '/PhpWord_Unit_Test/')) { - mkdir(Settings::getTempDir() . '/PhpWord_Unit_Test/'); + if (!is_dir($tempdir)) { + mkdir($tempdir); } $xmlWriter = IOFactory::createWriter($phpWord, $writerName); @@ -62,11 +63,11 @@ public static function getDocument(PhpWord $phpWord, $writerName = 'Word2007') $zip = new ZipArchive(); $res = $zip->open(self::$file); if (true === $res) { - $zip->extractTo(Settings::getTempDir() . '/PhpWord_Unit_Test/'); + $zip->extractTo($tempdir); $zip->close(); } - $doc = new XmlDocument(Settings::getTempDir() . '/PhpWord_Unit_Test/'); + $doc = new XmlDocument($tempdir); if ($writerName === 'ODText') { $doc->setDefaultFile('content.xml'); } @@ -83,8 +84,9 @@ public static function clear(): void unlink(self::$file); self::$file = ''; } - if (is_dir(Settings::getTempDir() . '/PhpWord_Unit_Test/')) { - self::deleteDir(Settings::getTempDir() . '/PhpWord_Unit_Test/'); + $tempdir = self::getTempDirPhpunit(); + if (is_dir($tempdir)) { + self::deleteDir($tempdir); } } @@ -117,4 +119,14 @@ public static function getFile() { return self::$file; } + + /** + * Get temporary directory for PhpUnit. + * + * @return string + */ + private static function getTempDirPhpunit() + { + return Settings::getTempDir() . '/PhpWord_Unit_Test'; + } } diff --git a/tests/PhpWordTests/Writer/HTML/ElementTest.php b/tests/PhpWordTests/Writer/HTML/ElementTest.php index 3b2580381f..28e785a237 100644 --- a/tests/PhpWordTests/Writer/HTML/ElementTest.php +++ b/tests/PhpWordTests/Writer/HTML/ElementTest.php @@ -72,13 +72,41 @@ public function testWriteTrackChanges(): void $text = $section->addText('my dummy text'); $text->setChangeInfo(TrackChange::INSERTED, 'author name'); $text2 = $section->addText('my other text'); - $text2->setTrackChange(new TrackChange(TrackChange::DELETED, 'another author', new DateTime())); + $deleteTime = new DateTime(); + $deleteAuthor = "Spec O'char"; + $deleteTrack = new TrackChange(TrackChange::DELETED, $deleteAuthor, $deleteTime); + $text2->setTrackChange($deleteTrack); $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); self::assertEquals(1, $xpath->query('/html/body/div/p[1]/ins')->length); self::assertEquals(1, $xpath->query('/html/body/div/p[2]/del')->length); + $node = $xpath->query('/html/body/div/p[2]/del'); + self::assertNotFalse($node); + $allAttributes = $node[0]->attributes; + self::assertCount(4, $allAttributes); + $node = $xpath->query('/html/body/div/p[2]/del'); + self::assertNotFalse($node); + + $attributes = $node[0]->attributes[0]; + self::assertSame('data-phpword-chg-author', $attributes->name); + self::assertSame($deleteAuthor, $attributes->value); + + $text2Id = $text2->getElementId(); + $attributes = $node[0]->attributes[1]; + self::assertSame('data-phpword-chg-id', $attributes->name); + self::assertSame($text2Id, $attributes->value); + + $attributes = $node[0]->attributes[2]; + self::assertSame('data-phpword-chg-timestamp', $attributes->name); + self::assertSame($deleteTime->format('Y-m-d\TH:i:s\Z'), $attributes->value); + + $attributes = $node[0]->attributes[3]; + self::assertSame('title', $attributes->name); + $expected = $deleteAuthor . ' - ' + . $deleteTime->format('Y-m-d H:i:s'); + self::assertSame($expected, $attributes->value); } /** From 3ace7706180af59c7b10b3b0bab5a8615849c92a Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 30 Jan 2025 21:20:49 -0800 Subject: [PATCH 020/153] Improve Test Coverage --- src/PhpWord/Shared/Html.php | 10 +- src/PhpWord/Shared/HtmlColours.php | 5 - tests/PhpWordTests/Shared/Html2Test.php | 140 ++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 tests/PhpWordTests/Shared/Html2Test.php diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index d1bf13d7f7..26bc83e3bb 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -36,6 +36,7 @@ use PhpOffice\PhpWord\SimpleType\NumberFormat; use PhpOffice\PhpWord\SimpleType\TextDirection; use PhpOffice\PhpWord\Style\Paragraph; +use Throwable; /** * Common Html functions. @@ -114,20 +115,20 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit $dom->preserveWhiteSpace = $preserveWhiteSpace; try { - $result = $dom->loadHTML($html, LIBXML_NOWARNING | LIBXML_NOERROR); + $result = @$dom->loadHTML($html); $exceptionMessage = 'DOM loadHTML failed'; - } catch (Exception $e) { + } catch (Throwable $e) { $result = false; $exceptionMessage = $e->getMessage(); } if ($result === false) { - throw new Exception($exceptionMessage); // @codeCoverageIgnore + throw new Exception($exceptionMessage); } self::removeAnnoyingWhitespaceTextNodes($dom); static::$xpath = new DOMXPath($dom); $node = $dom->getElementsByTagName('html'); if (count($node) === 0 || $node->item(0) === null) { - $node = $dom->getElementsByTagName('body'); + $node = $dom->getElementsByTagName('body'); // @codeCoverageIgnore } static::parseNode($node->item(0), $element); @@ -1319,7 +1320,6 @@ protected static function mapListType($cssListType) return NumberFormat::LOWER_ROMAN; // i, ii, iii, iv, .. case 'I': return NumberFormat::UPPER_ROMAN; // I, II, III, IV, .. - case '1': default: return NumberFormat::DECIMAL; // 1, 2, 3, .. } diff --git a/src/PhpWord/Shared/HtmlColours.php b/src/PhpWord/Shared/HtmlColours.php index 40bc0096c6..c50acfd5da 100644 --- a/src/PhpWord/Shared/HtmlColours.php +++ b/src/PhpWord/Shared/HtmlColours.php @@ -524,11 +524,6 @@ class HtmlColours 'yellowgreen' => '9acd32', ]; - public static function colourNameLookup(string $colorName): string - { - return self::COLOUR_MAP[$colorName] ?? ''; - } - public static function convertColour(string $colorName): string { $colorName = trim($colorName); diff --git a/tests/PhpWordTests/Shared/Html2Test.php b/tests/PhpWordTests/Shared/Html2Test.php new file mode 100644 index 0000000000..95866d4b07 --- /dev/null +++ b/tests/PhpWordTests/Shared/Html2Test.php @@ -0,0 +1,140 @@ +expectException(Exception::class); + $this->expectExceptionMessage('loadHTML'); + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, ''); + } + + public function testCssOnIdElement(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '' + . '' + . 'Id Test' + . '' + . '' + . '

test1.

' + . ''; + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord); + $marginPath = '/w:document/w:body/w:p/w:pPr/w:spacing'; + self::assertSame('150', $doc->getElement($marginPath)->getAttribute('w:before')); + self::assertSame('150', $doc->getElement($marginPath)->getAttribute('w:after')); + $path = '/w:document/w:body/w:p/w:r'; + self::assertSame('test1.', $doc->getElement($path)->nodeValue); + $boldPath = $path . '/w:rPr/w:b'; + self::assertSame('1', $doc->getElement($boldPath)->getAttribute('w:val')); + } + + public function testListTypes(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '
  1. Decimal number first
  2. second
' + . '
  1. Lowercase first
  2. second
' + . '
  1. Uppercase first
  2. second
' + . '
  1. Lower roman first
  2. second
' + . '
  1. Upper roman first
  2. second
'; + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord); + + $item = 1; + $expected = '1'; + $path = "/w:document/w:body/w:p[$item]"; + self::assertSame('Decimal number first', $doc->getElement("$path/w:r")->nodeValue); + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + ++$item; + $path = "/w:document/w:body/w:p[$item]"; + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + + ++$item; + $expected = '2'; + $path = "/w:document/w:body/w:p[$item]"; + self::assertSame('Lowercase first', $doc->getElement("$path/w:r")->nodeValue); + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + ++$item; + $path = "/w:document/w:body/w:p[$item]"; + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + + ++$item; + $expected = '3'; + $path = "/w:document/w:body/w:p[$item]"; + self::assertSame('Uppercase first', $doc->getElement("$path/w:r")->nodeValue); + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + ++$item; + $path = "/w:document/w:body/w:p[$item]"; + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + + ++$item; + $expected = '4'; + $path = "/w:document/w:body/w:p[$item]"; + self::assertSame('Lower roman first', $doc->getElement("$path/w:r")->nodeValue); + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + ++$item; + $path = "/w:document/w:body/w:p[$item]"; + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + + ++$item; + $expected = '5'; + $path = "/w:document/w:body/w:p[$item]"; + self::assertSame('Upper roman first', $doc->getElement("$path/w:r")->nodeValue); + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + ++$item; + $path = "/w:document/w:body/w:p[$item]"; + $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; + self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); + } +} From b1720b95ddac49ee56d9a2b7cde3a736ced484a9 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 30 Jan 2025 22:18:41 -0800 Subject: [PATCH 021/153] A Bit More Coverage --- tests/PhpWordTests/Shared/Html2Test.php | 64 +++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/PhpWordTests/Shared/Html2Test.php b/tests/PhpWordTests/Shared/Html2Test.php index 95866d4b07..64a71932dd 100644 --- a/tests/PhpWordTests/Shared/Html2Test.php +++ b/tests/PhpWordTests/Shared/Html2Test.php @@ -137,4 +137,68 @@ public function testListTypes(): void $numIdPath = $path . '/w:pPr/w:numPr/w:numId'; self::assertSame($expected, $doc->getElement($numIdPath)->getAttribute('w:val')); } + + public function testPadding(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '
' + . '' + . '' + . '' + . '' + . '' + . '' + . '
2020 30
20 30 4020 30 40 50
'; + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord); + + $item = 1; + $td = 1; + $path = "/w:document/w:body/w:tbl/w:tr[$item]/w:tc[$td]"; + self::assertSame('20', $doc->getElement("$path/w:p/w:r")->nodeValue); + $tcMarPath = $path . '/w:tcPr/w:tcMar'; + self::assertSame('300', $doc->getElement($tcMarPath . '/w:top')->getAttribute('w:w')); + self::assertSame('300', $doc->getElement($tcMarPath . '/w:start')->getAttribute('w:w')); + self::assertSame('300', $doc->getElement($tcMarPath . '/w:bottom')->getAttribute('w:w')); + self::assertSame('300', $doc->getElement($tcMarPath . '/w:end')->getAttribute('w:w')); + + ++$td; + $path = "/w:document/w:body/w:tbl/w:tr[$item]/w:tc[$td]"; + self::assertSame('20 30', $doc->getElement("$path/w:p/w:r")->nodeValue); + $tcMarPath = $path . '/w:tcPr/w:tcMar'; + self::assertSame('300', $doc->getElement($tcMarPath . '/w:top')->getAttribute('w:w')); + self::assertSame('450', $doc->getElement($tcMarPath . '/w:start')->getAttribute('w:w')); + self::assertSame('300', $doc->getElement($tcMarPath . '/w:bottom')->getAttribute('w:w')); + self::assertSame('450', $doc->getElement($tcMarPath . '/w:end')->getAttribute('w:w')); + + $item = 1; + $td = 1; + $path = "/w:document/w:body/w:tbl/w:tr[$item]/w:tc[$td]"; + self::assertSame('20', $doc->getElement("$path/w:p/w:r")->nodeValue); + $tcMarPath = $path . '/w:tcPr/w:tcMar'; + self::assertSame('300', $doc->getElement($tcMarPath . '/w:top')->getAttribute('w:w')); + self::assertSame('300', $doc->getElement($tcMarPath . '/w:start')->getAttribute('w:w')); + self::assertSame('300', $doc->getElement($tcMarPath . '/w:bottom')->getAttribute('w:w')); + self::assertSame('300', $doc->getElement($tcMarPath . '/w:end')->getAttribute('w:w')); + + ++$item; + $td = 1; + $path = "/w:document/w:body/w:tbl/w:tr[$item]/w:tc[$td]"; + self::assertSame('20 30 40', $doc->getElement("$path/w:p/w:r")->nodeValue); + $tcMarPath = $path . '/w:tcPr/w:tcMar'; + self::assertSame('300', $doc->getElement($tcMarPath . '/w:top')->getAttribute('w:w')); + self::assertSame('450', $doc->getElement($tcMarPath . '/w:start')->getAttribute('w:w')); + self::assertSame('600', $doc->getElement($tcMarPath . '/w:bottom')->getAttribute('w:w')); + self::assertSame('450', $doc->getElement($tcMarPath . '/w:end')->getAttribute('w:w')); + + ++$td; + $path = "/w:document/w:body/w:tbl/w:tr[$item]/w:tc[$td]"; + self::assertSame('20 30 40 50', $doc->getElement("$path/w:p/w:r")->nodeValue); + $tcMarPath = $path . '/w:tcPr/w:tcMar'; + self::assertSame('300', $doc->getElement($tcMarPath . '/w:top')->getAttribute('w:w')); + self::assertSame('750', $doc->getElement($tcMarPath . '/w:start')->getAttribute('w:w')); + self::assertSame('600', $doc->getElement($tcMarPath . '/w:bottom')->getAttribute('w:w')); + self::assertSame('450', $doc->getElement($tcMarPath . '/w:end')->getAttribute('w:w')); + } } From 16904a2a0cea68176db99dbcc60c52e804b8212a Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 5 Feb 2025 22:24:41 -0800 Subject: [PATCH 022/153] Catch Up --- src/PhpWord/Shared/Html.php | 8 ++++---- src/PhpWord/Writer/HTML/Element/Title.php | 15 +++++++++------ src/PhpWord/Writer/HTML/Part/Head.php | 1 + tests/PhpWordTests/Shared/HtmlHeadingsTest.php | 9 ++++----- tests/PhpWordTests/Shared/HtmlTest.php | 5 +++++ tests/PhpWordTests/Writer/HTML/PartTest.php | 12 +++++------- 6 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index c574bd969d..9cc3abe5b7 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -470,10 +470,10 @@ protected static function parseInput($node, $element, &$styles): void */ protected static function parseHeading(DOMNode $node, AbstractContainer $element, array &$styles, string $headingStyle): TextRun { - self::parseInlineStyle($node, $styles['font']); - // Create a TextRun to hold styles and text - $styles['paragraph'] = $headingStyle; - $textRun = new TextRun($styles['paragraph']); + $style = new Paragraph(); + $style->setStyleName($headingStyle); + $style->setStyleByArray(self::parseInlineStyle($node, $styles['paragraph'])); + $textRun = new TextRun($style); // Create a title with level corresponding to number in heading style // (Eg, Heading1 = 1) diff --git a/src/PhpWord/Writer/HTML/Element/Title.php b/src/PhpWord/Writer/HTML/Element/Title.php index 1348a30a75..e759bfdcd6 100644 --- a/src/PhpWord/Writer/HTML/Element/Title.php +++ b/src/PhpWord/Writer/HTML/Element/Title.php @@ -19,9 +19,8 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; use PhpOffice\PhpWord\Element\Title as PhpWordTitle; -use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Writer\HTML; -use PhpOffice\PhpWord\Writer\HTML\Style\Font; +use PhpOffice\PhpWord\Writer\HTML\Style\Paragraph; /** * TextRun element HTML writer. @@ -44,17 +43,21 @@ public function write() $tag = 'h' . $this->element->getDepth(); $text = $this->element->getText(); + $paragraphStyle = null; if (is_string($text)) { $text = $this->parentWriter->escapeHTML($text); } else { + $paragraphStyle = $text->getParagraphStyle(); $writer = new Container($this->parentWriter, $text); $text = $writer->write(); } $css = ''; - $style = Style::getStyle('Heading_' . $this->element->getDepth()); - if ($style !== null) { - $styleWriter = new Font($style); - $css = ' style="' . $styleWriter->write() . '"'; + if (is_object($paragraphStyle)) { + $styleWriter = new Paragraph($paragraphStyle); + $write = $styleWriter->write(); + if ($write !== '') { + $css = " style=\"$write\""; + } } $content = "<{$tag}{$css}>{$text}" . PHP_EOL; diff --git a/src/PhpWord/Writer/HTML/Part/Head.php b/src/PhpWord/Writer/HTML/Part/Head.php index 0f295e58c3..f8af52ccb1 100644 --- a/src/PhpWord/Writer/HTML/Part/Head.php +++ b/src/PhpWord/Writer/HTML/Part/Head.php @@ -137,6 +137,7 @@ private function writeStyles(): string $styleWriter = new FontStyleWriter($style); if ($style->getStyleType() == 'title') { $name = str_replace('Heading_', 'h', $name); + $css .= "{$name} {" . $styleWriter->write() . '}' . PHP_EOL; $styleParagraph = $style->getParagraph(); $style = $styleParagraph; } else { diff --git a/tests/PhpWordTests/Shared/HtmlHeadingsTest.php b/tests/PhpWordTests/Shared/HtmlHeadingsTest.php index 2f11386d04..8ecc95b773 100644 --- a/tests/PhpWordTests/Shared/HtmlHeadingsTest.php +++ b/tests/PhpWordTests/Shared/HtmlHeadingsTest.php @@ -40,7 +40,7 @@ public function testRoundTripHeadings(): void $section = $originalDoc->addSection(); $expectedStrings = []; $section->addTitle('Title 1', 1); - $expectedStrings[] = '

Title 1

'; + $expectedStrings[] = '

Title 1

'; for ($i = 2; $i <= 6; ++$i) { $textRun = new TextRun(); $textRun->addText('Title '); @@ -59,9 +59,8 @@ public function testRoundTripHeadings(): void SharedHtml::addHtml($newSection, $content, true); $newWriter = new HtmlWriter($newDoc); $newContent = $newWriter->getContent(); - // Reader transforms Text to TextRun, - // but result is functionally the same. - $firstStringAsTextRun = '

Title 1

'; - self::assertSame($content, str_replace($firstStringAsTextRun, $expectedStrings[0], $newContent)); + + // This needs work + self::assertSame($newContent, str_replace('h1 {font-size: 20pt;}' . PHP_EOL, '', $content)); } } diff --git a/tests/PhpWordTests/Shared/HtmlTest.php b/tests/PhpWordTests/Shared/HtmlTest.php index d50a5176d7..50f1e699a6 100644 --- a/tests/PhpWordTests/Shared/HtmlTest.php +++ b/tests/PhpWordTests/Shared/HtmlTest.php @@ -24,6 +24,7 @@ use PhpOffice\PhpWord\Element\Table; use PhpOffice\PhpWord\Element\Text; use PhpOffice\PhpWord\Element\TextRun; +use PhpOffice\PhpWord\Element\Title; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Converter; @@ -134,6 +135,8 @@ public function testParseHeader(): void self::assertCount(1, $section->getElements()); $element = $section->getElement(0); + self::assertInstanceOf(Title::class, $element); + $element = $element->getText(); self::assertInstanceOf(TextRun::class, $element); self::assertInstanceOf(Paragraph::class, $element->getParagraphStyle()); self::assertEquals('Heading1', $element->getParagraphStyle()->getStyleName()); @@ -155,6 +158,8 @@ public function testParseHeaderStyle(): void self::assertCount(1, $section->getElements()); $element = $section->getElement(0); + self::assertInstanceOf(Title::class, $element); + $element = $element->getText(); self::assertInstanceOf(TextRun::class, $element); self::assertInstanceOf(Paragraph::class, $element->getParagraphStyle()); self::assertEquals('Heading1', $element->getParagraphStyle()->getStyleName()); diff --git a/tests/PhpWordTests/Writer/HTML/PartTest.php b/tests/PhpWordTests/Writer/HTML/PartTest.php index a9d91b9530..2ccb96a799 100644 --- a/tests/PhpWordTests/Writer/HTML/PartTest.php +++ b/tests/PhpWordTests/Writer/HTML/PartTest.php @@ -179,17 +179,15 @@ public function testTitleStyles(): void $xpath = new DOMXPath($dom); $style = Helper::getTextContent($xpath, '/html/head/style'); - //self::assertNotFalse(strpos($style, 'h1 {font-family: \'Calibri\'; font-weight: bold;}')); + self::assertNotFalse(strpos($style, 'h1 {font-family: \'Calibri\'; font-weight: bold;}')); self::assertNotFalse(strpos($style, 'h1 {margin-top: 0.5pt; margin-bottom: 0.5pt;}')); - //self::assertNotFalse(strpos($style, 'h2 {font-family: \'Times New Roman\'; font-style: italic;}')); + self::assertNotFalse(strpos($style, 'h2 {font-family: \'Times New Roman\'; font-style: italic;}')); self::assertNotFalse(strpos($style, 'h2 {margin-top: 0.25pt; margin-bottom: 0.25pt;}')); self::assertEquals(1, Helper::getLength($xpath, '/html/body/div/h1')); self::assertEquals(2, Helper::getLength($xpath, '/html/body/div/h2')); - // code for getNamedItem had been erroneous - self::assertSame("font-family: 'Calibri'; font-weight: bold;", Helper::getNamedItem($xpath, '/html/body/div/h1', 'style')->textContent); $html = Helper::getHtmlString($phpWord); - self::assertStringContainsString('

Header 1 #1

', $html); - self::assertStringContainsString('

Header 2 #1

', $html); - self::assertStringContainsString('

Header 2 #2

', $html); + self::assertStringContainsString('

Header 1 #1

', $html); + self::assertStringContainsString('

Header 2 #1

', $html); + self::assertStringContainsString('

Header 2 #2

', $html); } } From 92a4c5e1e45eb11ce5880671fd9a508a4bb2685f Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 6 Feb 2025 00:25:14 -0800 Subject: [PATCH 023/153] Html Writer Duplicate Header Styles in Style Tags Nominally redundant, but makes things easier for Html Reader. --- src/PhpWord/Writer/HTML/Element/Title.php | 19 +++++++++++++++---- .../PhpWordTests/Shared/HtmlHeadingsTest.php | 16 +++++++++++++--- tests/PhpWordTests/Writer/HTML/PartTest.php | 6 +++--- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/PhpWord/Writer/HTML/Element/Title.php b/src/PhpWord/Writer/HTML/Element/Title.php index e759bfdcd6..43806a648c 100644 --- a/src/PhpWord/Writer/HTML/Element/Title.php +++ b/src/PhpWord/Writer/HTML/Element/Title.php @@ -19,7 +19,9 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; use PhpOffice\PhpWord\Element\Title as PhpWordTitle; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Writer\HTML; +use PhpOffice\PhpWord\Writer\HTML\Style\Font; use PhpOffice\PhpWord\Writer\HTML\Style\Paragraph; /** @@ -51,14 +53,23 @@ public function write() $writer = new Container($this->parentWriter, $text); $text = $writer->write(); } - $css = ''; + $write1 = $write2 = $write3 = ''; + $style = Style::getStyle('Heading_' . $this->element->getDepth()); + if ($style !== null) { + $styleWriter = new Font($style); + $write1 = $styleWriter->write(); + } if (is_object($paragraphStyle)) { $styleWriter = new Paragraph($paragraphStyle); - $write = $styleWriter->write(); - if ($write !== '') { - $css = " style=\"$write\""; + $write3 = $styleWriter->write(); + if ($write1 !== '' && $write3 !== '') { + $write2 = ' '; } } + $css = "$write1$write2$write3"; + if ($css !== '') { + $css = " style=\"$css\""; + } $content = "<{$tag}{$css}>{$text}" . PHP_EOL; diff --git a/tests/PhpWordTests/Shared/HtmlHeadingsTest.php b/tests/PhpWordTests/Shared/HtmlHeadingsTest.php index 8ecc95b773..331935fbae 100644 --- a/tests/PhpWordTests/Shared/HtmlHeadingsTest.php +++ b/tests/PhpWordTests/Shared/HtmlHeadingsTest.php @@ -40,7 +40,7 @@ public function testRoundTripHeadings(): void $section = $originalDoc->addSection(); $expectedStrings = []; $section->addTitle('Title 1', 1); - $expectedStrings[] = '

Title 1

'; + $expectedStrings[] = '

Title 1

'; for ($i = 2; $i <= 6; ++$i) { $textRun = new TextRun(); $textRun->addText('Title '); @@ -59,8 +59,18 @@ public function testRoundTripHeadings(): void SharedHtml::addHtml($newSection, $content, true); $newWriter = new HtmlWriter($newDoc); $newContent = $newWriter->getContent(); + // Reader does not yet support h1 declaration in css. + $content = str_replace('h1 {font-size: 20pt;}' . PHP_EOL, '', $content); - // This needs work - self::assertSame($newContent, str_replace('h1 {font-size: 20pt;}' . PHP_EOL, '', $content)); + // Reader transforms Text to TextRun, + // but result is functionally the same. + self::assertSame( + $newContent, + str_replace( + '

Title 1

', + '

Title 1

', + $content + ) + ); } } diff --git a/tests/PhpWordTests/Writer/HTML/PartTest.php b/tests/PhpWordTests/Writer/HTML/PartTest.php index 2ccb96a799..0fe43c2350 100644 --- a/tests/PhpWordTests/Writer/HTML/PartTest.php +++ b/tests/PhpWordTests/Writer/HTML/PartTest.php @@ -186,8 +186,8 @@ public function testTitleStyles(): void self::assertEquals(1, Helper::getLength($xpath, '/html/body/div/h1')); self::assertEquals(2, Helper::getLength($xpath, '/html/body/div/h2')); $html = Helper::getHtmlString($phpWord); - self::assertStringContainsString('

Header 1 #1

', $html); - self::assertStringContainsString('

Header 2 #1

', $html); - self::assertStringContainsString('

Header 2 #2

', $html); + self::assertStringContainsString('

Header 1 #1

', $html); + self::assertStringContainsString('

Header 2 #1

', $html); + self::assertStringContainsString('

Header 2 #2

', $html); } } From 60dbd8cd26e1abcb966c5977a155dc6ff591e62f Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 12 Feb 2025 13:11:27 -0800 Subject: [PATCH 024/153] Keep Up With 2533 --- src/PhpWord/Writer/HTML/Element/Title.php | 4 ++-- src/PhpWord/Writer/HTML/Part/Head.php | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PhpWord/Writer/HTML/Element/Title.php b/src/PhpWord/Writer/HTML/Element/Title.php index 43806a648c..f3aeb395f4 100644 --- a/src/PhpWord/Writer/HTML/Element/Title.php +++ b/src/PhpWord/Writer/HTML/Element/Title.php @@ -18,7 +18,7 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; -use PhpOffice\PhpWord\Element\Title as PhpWordTitle; +use PhpOffice\PhpWord\Element\Title as ElementTitle; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Writer\HTML; use PhpOffice\PhpWord\Writer\HTML\Style\Font; @@ -38,7 +38,7 @@ class Title extends AbstractElement */ public function write() { - if (!$this->element instanceof PhpWordTitle) { + if (!$this->element instanceof ElementTitle) { return ''; } diff --git a/src/PhpWord/Writer/HTML/Part/Head.php b/src/PhpWord/Writer/HTML/Part/Head.php index f8af52ccb1..4da526075d 100644 --- a/src/PhpWord/Writer/HTML/Part/Head.php +++ b/src/PhpWord/Writer/HTML/Part/Head.php @@ -101,7 +101,6 @@ private function writeStyles(): string foreach ([ 'body' => $bodyarray, - //'*' => $astarray, 'a.NoteRef' => [ 'text-decoration' => 'none', ], From efd5701adc6a81ee7d4327f80596ae73364e5fa2 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 14 Apr 2025 10:01:06 -0700 Subject: [PATCH 025/153] Broken Test (not mine) --- .../Writer/Word2007/Element/TOCTest.php | 5 ++++- .../_files/html/loripsum.net.html | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/PhpWordTests/_files/html/loripsum.net.html diff --git a/tests/PhpWordTests/Writer/Word2007/Element/TOCTest.php b/tests/PhpWordTests/Writer/Word2007/Element/TOCTest.php index 95e79114aa..c7054c195a 100644 --- a/tests/PhpWordTests/Writer/Word2007/Element/TOCTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Element/TOCTest.php @@ -66,7 +66,10 @@ public function testWriteTitleWithoutpageNumber(): void //more than one title and random text for create more than one page for ($i = 1; $i <= 10; ++$i) { $section->addTitle('Title ' . $i, 1); - $content = file_get_contents('https://loripsum.net/api/10/long'); + // This server seems to have disappeared. + //$content = file_get_contents('https://loripsum.net/api/10/long'); + // Contents recovered using wayback machine. + $content = file_get_contents('tests/PhpWordTests/_files/html/loripsum.net.html'); \PhpOffice\PhpWord\Shared\Html::addHtml($section, $content ? $content : '', false, false); $section->addPageBreak(); } diff --git a/tests/PhpWordTests/_files/html/loripsum.net.html b/tests/PhpWordTests/_files/html/loripsum.net.html new file mode 100644 index 0000000000..7cf1357e71 --- /dev/null +++ b/tests/PhpWordTests/_files/html/loripsum.net.html @@ -0,0 +1,19 @@ +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cupiditates non Epicuri divisione finiebat, sed sua satietate. Hic, qui utrumque probat, ambobus debuit uti, sicut facit re, neque tamen dividit verbis. Quibus natura iure responderit non esse verum aliunde finem beate vivendi, a se principia rei gerendae peti; Quibus rebus vita consentiens virtutibusque respondens recta et honesta et constans et naturae congruens existimari potest. Illa videamus, quae a te de amicitia dicta sunt. Addo etiam illud, multa iam mihi dare signa puerum et pudoris et ingenii, sed aetatem vides. Quibus rebus vita consentiens virtutibusque respondens recta et honesta et constans et naturae congruens existimari potest. Duo Reges: constructio interrete. Quid, cum volumus nomina eorum, qui quid gesserint, nota nobis esse, parentes, patriam, multa praeterea minime necessaria? Apparet statim, quae sint officia, quae actiones.

+ +

Nam ante Aristippus, et ille melius. Quamquam id quidem licebit iis existimare, qui legerint. Inde igitur, inquit, ordiendum est. Quo modo autem optimum, si bonum praeterea nullum est? Non dolere, inquam, istud quam vim habeat postea videro; Perfecto enim et concluso neque virtutibus neque amicitiis usquam locum esse, si ad voluptatem omnia referantur, nihil praeterea est magnopere dicendum. Maximas vero virtutes iacere omnis necesse est voluptate dominante. Tertium autem omnibus aut maximis rebus iis, quae secundum naturam sint, fruentem vivere.

+ +

Huic ego, si negaret quicquam interesse ad beate vivendum quali uteretur victu, concederem, laudarem etiam; Maximas vero virtutes iacere omnis necesse est voluptate dominante. Polemoni et iam ante Aristoteli ea prima visa sunt, quae paulo ante dixi. Atqui haec patefactio quasi rerum opertarum, cum quid quidque sit aperitur, definitio est. Num igitur utiliorem tibi hunc Triarium putas esse posse, quam si tua sint Puteolis granaria?

+ +

Unum est sine dolore esse, alterum cum voluptate. Sed quoniam et advesperascit et mihi ad villam revertendum est, nunc quidem hactenus; Quid enim mihi potest esse optatius quam cum Catone, omnium virtutum auctore, de virtutibus disputare? Ergo infelix una molestia, fellx rursus, cum is ipse anulus in praecordiis piscis inventus est? Neque enim disputari sine reprehensione nec cum iracundia aut pertinacia recte disputari potest. Paulum, cum regem Persem captum adduceret, eodem flumine invectio?

+ +

Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; Res enim concurrent contrariae. Tenesne igitur, inquam, Hieronymus Rhodius quid dicat esse summum bonum, quo putet omnia referri oportere? Etenim nec iustitia nec amicitia esse omnino poterunt, nisi ipsae per se expetuntur. Invidiosum nomen est, infame, suspectum. Et ille ridens: Video, inquit, quid agas;

+ +

Satis est tibi in te, satis in legibus, satis in mediocribus amicitiis praesidii. Gracchum patrem non beatiorem fuisse quam fillum, cum alter stabilire rem publicam studuerit, alter evertere. Nunc dicam de voluptate, nihil scilicet novi, ea tamen, quae te ipsum probaturum esse confidam. Quid, si reviviscant Platonis illi et deinceps qui eorum auditores fuerunt, et tecum ita loquantur? Quid, si reviviscant Platonis illi et deinceps qui eorum auditores fuerunt, et tecum ita loquantur? Sed residamus, inquit, si placet. Illa videamus, quae a te de amicitia dicta sunt. A villa enim, credo, et: Si ibi te esse scissem, ad te ipse venissem. Si ista mala sunt, in quae potest incidere sapiens, sapientem esse non esse ad beate vivendum satis.

+ +

Intrandum est igitur in rerum naturam et penitus quid ea postulet pervidendum; Cum autem venissemus in Academiae non sine causa nobilitata spatia, solitudo erat ea, quam volueramus.

+ +

Non igitur potestis voluptate omnia dirigentes aut tueri aut retinere virtutem. Ne tum quidem te respicies et cogitabis sibi quemque natum esse et suis voluptatibus? Quae sunt igitur communia vobis cum antiquis, iis sic utamur quasi concessis; Scripsit enim et multis saepe verbis et breviter arteque in eo libro, quem modo nominavi, mortem nihil ad nos pertinere. An me, inquam, nisi te audire vellem, censes haec dicturum fuisse? Quae similitudo in genere etiam humano apparet.

+ +

Tum mihi Piso: Quid ergo? Ita multo sanguine profuso in laetitia et in victoria est mortuus. Et ille ridens: Video, inquit, quid agas; Sequitur disserendi ratio cognitioque naturae; Virtutis, magnitudinis animi, patientiae, fortitudinis fomentis dolor mitigari solet. -delector enim, quamquam te non possum, ut ais, corrumpere, delector, inquam, et familia vestra et nomine. Polemoni et iam ante Aristoteli ea prima visa sunt, quae paulo ante dixi. Graecum enim hunc versum nostis omnes-: Suavis laborum est praeteritorum memoria.

+ +

Itaque ne iustitiam quidem recte quis dixerit per se ipsam optabilem, sed quia iucunditatis vel plurimum afferat. An, partus ancillae sitne in fructu habendus, disseretur inter principes civitatis, P. Cum autem assumpta ratío est, tanto in dominatu locatur, ut omnia illa prima naturae hulus tutelae subiciantur. Similiter sensus, cum accessit ad naturam, tuetur illam quidem, sed etiam se tuetur; Non semper, inquam; Potius ergo illa dicantur: turpe esse, viri non esse debilitari dolore, frangi, succumbere. Quia voluptatem hanc esse sentiunt omnes, quam sensus accipiens movetur et iucunditate quadam perfunditur. Cum sciret confestim esse moriendum eamque mortem ardentiore studio peteret, quam Epicurus voluptatem petendam putat.

From 088ae3ca637e502093ce7f12a88a8b642c0f609a Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 30 May 2025 20:19:19 -0700 Subject: [PATCH 026/153] Update Html.php --- src/PhpWord/Shared/Html.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 9cc3abe5b7..f8ab6d57e5 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -97,7 +97,7 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit } if (substr($html, 0, 2) === "\xfe\xff" || substr($html, 0, 2) === "\xff\xfe") { - $html = mb_convert_encoding($html, 'UTF-8', 'UTF-16'); + $html = (string) mb_convert_encoding($html, 'UTF-8', 'UTF-16'); } if (substr($html, 0, 3) === "\xEF\xBB\xBF") { $html = substr($html, 3); From 1e001853f8817bb297ac0d75c27db5938d11b587 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:52:05 -0700 Subject: [PATCH 027/153] Catch up With Latest Commits to Master --- src/PhpWord/Shared/Html.php | 2 +- tests/PhpWordTests/Shared/HtmlTest.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index bd3ef55a44..52c55aaf3c 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -172,7 +172,7 @@ private static function replaceNonAsciiIfNeeded(string $convert): ?string * 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 */ diff --git a/tests/PhpWordTests/Shared/HtmlTest.php b/tests/PhpWordTests/Shared/HtmlTest.php index 50f1e699a6..2a8705b483 100644 --- a/tests/PhpWordTests/Shared/HtmlTest.php +++ b/tests/PhpWordTests/Shared/HtmlTest.php @@ -234,7 +234,6 @@ public function testSpanClassName(): void $html = '

This is bold text.

'; $section = $phpWord->addSection(); Html::addHtml($section, $html); - self::assertTrue(true); $element = $section->getElements()[0]; self::assertInstanceOf(TextRun::class, $element); $textElements = $element->getElements(); From 3bf1d9d3ceded0eea54afb061fa2827b6a4db2fa Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 23 Jul 2025 10:33:54 -0700 Subject: [PATCH 028/153] Require mbstring --- .github/workflows/php.yml | 2 +- composer.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 016120d913..dde3e7c0dc 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -75,7 +75,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: gd, xml, zip + extensions: dom, gd, mbstring, xml, zip coverage: ${{ (matrix.php == '7.3') && 'xdebug' || 'none' }} - uses: actions/checkout@v2 diff --git a/composer.json b/composer.json index abf006fd34..25e8e495bf 100644 --- a/composer.json +++ b/composer.json @@ -109,8 +109,9 @@ "php": "^7.1|^8.0", "ext-dom": "*", "ext-gd": "*", - "ext-zip": "*", + "ext-mbstring": "*", "ext-xml": "*", + "ext-zip": "*", "phpoffice/math": "^0.3" }, "require-dev": { From b7b30ac1a3cbe782da8756ee072d5584ec0aea83 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:31:34 -0700 Subject: [PATCH 029/153] See If I Can Run Nightly --- .github/workflows/php.yml | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index dde3e7c0dc..d90021c96c 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -64,12 +64,29 @@ jobs: run: ./vendor/bin/phpstan analyse -c phpstan.neon.dist phpunit: - name: PHPUnit ${{ matrix.php }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: - php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + experimental: + - false + php: + - '7.1' + - '7.2' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + - '8.2' + - '8.3' + - '8.4' + + include: + - php: 'nightly' + experimental: true + + name: PHPUnit ${{ matrix.php }} + steps: - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -95,6 +112,12 @@ jobs: if: matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.3' || matrix.php == '8.4' run: ./vendor/bin/phpunit -c phpunit10.xml.dist --no-coverage + - name: Run phpunit nightly experimental + if: matrix.php == 'nightly' + env: + FAILURE_ACTION: "${{ matrix.experimental == true }}" + run: ./vendor/bin/phpunit -c phpunit10.xml.dist --no-coverage + - name: Run phpunit 7.3 if: matrix.php == '7.3' run: ./vendor/bin/phpunit -c phpunit9.xml.dist --coverage-clover build/clover.xml From 2bb935a17cbdc9976f6d33b5838a0e758bc3b69e Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:26:33 -0700 Subject: [PATCH 030/153] Didn't Work Couldn't resolve a good version for mpdf. --- .github/workflows/php.yml | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index d90021c96c..dde3e7c0dc 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -64,29 +64,12 @@ jobs: run: ./vendor/bin/phpstan analyse -c phpstan.neon.dist phpunit: + name: PHPUnit ${{ matrix.php }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: - experimental: - - false - php: - - '7.1' - - '7.2' - - '7.3' - - '7.4' - - '8.0' - - '8.1' - - '8.2' - - '8.3' - - '8.4' - - include: - - php: 'nightly' - experimental: true - - name: PHPUnit ${{ matrix.php }} - + php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] steps: - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -112,12 +95,6 @@ jobs: if: matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.3' || matrix.php == '8.4' run: ./vendor/bin/phpunit -c phpunit10.xml.dist --no-coverage - - name: Run phpunit nightly experimental - if: matrix.php == 'nightly' - env: - FAILURE_ACTION: "${{ matrix.experimental == true }}" - run: ./vendor/bin/phpunit -c phpunit10.xml.dist --no-coverage - - name: Run phpunit 7.3 if: matrix.php == '7.3' run: ./vendor/bin/phpunit -c phpunit9.xml.dist --coverage-clover build/clover.xml From 76a7d28da996913f79b25e5ad14813cade45f17a Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 27 Jul 2025 14:28:03 -0700 Subject: [PATCH 031/153] Try Nightly Again --- .github/workflows/php.yml | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index dde3e7c0dc..1972127545 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -64,12 +64,29 @@ jobs: run: ./vendor/bin/phpstan analyse -c phpstan.neon.dist phpunit: - name: PHPUnit ${{ matrix.php }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: - php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + experimental: + - false + php: + - '7.1' + - '7.2' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + - '8.2' + - '8.3' + - '8.4' + + include: + - php: 'nightly' + experimental: true + + name: PHPUnit ${{ matrix.php }} + steps: - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -80,6 +97,10 @@ jobs: - uses: actions/checkout@v2 + - name: Composer Config nightly + if: matrix.php == 'nightly' + run: composer config platform.php 8.4.99 + - name: Composer Install run: composer install --ansi --prefer-dist --no-interaction --no-progress @@ -95,6 +116,12 @@ jobs: if: matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.3' || matrix.php == '8.4' run: ./vendor/bin/phpunit -c phpunit10.xml.dist --no-coverage + - name: Run phpunit nightly experimental + if: matrix.php == 'nightly' + env: + FAILURE_ACTION: "${{ matrix.experimental == true }}" + run: ./vendor/bin/phpunit -c phpunit10.xml.dist --no-coverage + - name: Run phpunit 7.3 if: matrix.php == '7.3' run: ./vendor/bin/phpunit -c phpunit9.xml.dist --coverage-clover build/clover.xml From 62383518d5b4d3cd21aff303ea80f02c7f720ad0 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 29 Jul 2025 00:17:47 -0700 Subject: [PATCH 032/153] Generate Samples for Nightly --- .github/workflows/php.yml | 25 +++++++++++++++++-- composer.json | 2 ++ samples/Sample_45_Autoloader.php | 2 +- ..._RTLTitles.php => Sample_47_RTLTitles.php} | 0 4 files changed, 26 insertions(+), 3 deletions(-) rename samples/{Sample_45_RTLTitles.php => Sample_47_RTLTitles.php} (100%) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 1972127545..a7b09847c4 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -141,7 +141,22 @@ jobs: strategy: fail-fast: false matrix: - php: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + experimental: + - false + php: + - '7.1' + - '7.2' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + - '8.2' + - '8.3' + - '8.4' + + include: + - php: 'nightly' + experimental: true steps: - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -152,8 +167,14 @@ jobs: - uses: actions/checkout@v2 + - name: Composer Config nightly + if: matrix.php == 'nightly' + run: composer config platform.php 8.4.99 + - name: Composer Install run: composer install --ansi --prefer-dist --no-interaction --no-progress - - name: Generate samples files + - name: "Generate samples files (Experimental: ${{ matrix.experimental }})" + env: + FAILURE_ACTION: "${{ matrix.experimental == true }}" run: composer run samples diff --git a/composer.json b/composer.json index 25e8e495bf..278b85f80d 100644 --- a/composer.json +++ b/composer.json @@ -97,6 +97,8 @@ "php samples/Sample_43_RTLDefault.php", "php samples/Sample_44_ExtractVariablesFromReaderWord2007.php", "php samples/Sample_45_Autoloader.php" + "php samples/Sample_46_RubyPhoneticGuide.php" + "php samples/Sample_47_RTLTitles.php" ] }, "scripts-descriptions": { diff --git a/samples/Sample_45_Autoloader.php b/samples/Sample_45_Autoloader.php index 71f443f770..bdd522f483 100644 --- a/samples/Sample_45_Autoloader.php +++ b/samples/Sample_45_Autoloader.php @@ -2,7 +2,7 @@ use PhpOffice\PhpWord\Style\Font; -define('USE_AUTOLOADER', true); +//define('USE_AUTOLOADER', true); include_once 'Sample_Header.php'; diff --git a/samples/Sample_45_RTLTitles.php b/samples/Sample_47_RTLTitles.php similarity index 100% rename from samples/Sample_45_RTLTitles.php rename to samples/Sample_47_RTLTitles.php From 7a9b50ec20a650598d097c113b6ef635aec3d1cb Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 29 Jul 2025 00:20:14 -0700 Subject: [PATCH 033/153] Correct Json --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 278b85f80d..c898a700dd 100644 --- a/composer.json +++ b/composer.json @@ -96,8 +96,8 @@ "php samples/Sample_42_TemplateSetCheckbox.php", "php samples/Sample_43_RTLDefault.php", "php samples/Sample_44_ExtractVariablesFromReaderWord2007.php", - "php samples/Sample_45_Autoloader.php" - "php samples/Sample_46_RubyPhoneticGuide.php" + "php samples/Sample_45_Autoloader.php", + "php samples/Sample_46_RubyPhoneticGuide.php", "php samples/Sample_47_RTLTitles.php" ] }, From 821e0a52e783b74e23b299d02223681082c5de07 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 29 Jul 2025 07:18:29 -0700 Subject: [PATCH 034/153] Tweak --- samples/Sample_45_Autoloader.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/samples/Sample_45_Autoloader.php b/samples/Sample_45_Autoloader.php index bdd522f483..ac53c05367 100644 --- a/samples/Sample_45_Autoloader.php +++ b/samples/Sample_45_Autoloader.php @@ -2,7 +2,10 @@ use PhpOffice\PhpWord\Style\Font; -//define('USE_AUTOLOADER', true); +// When following define is TRUE, +// this script will use PhpWord autoloader rather than Composer, +// and will not be able to find Pdf renderer. +define('USE_AUTOLOADER', PHP_MINOR_VERSION % 2 === 0); include_once 'Sample_Header.php'; From b2600118aebc8a04cf852ddd9e36c3d4641106aa Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 24 Aug 2025 17:07:55 -0700 Subject: [PATCH 035/153] Php8.5 Accommodations Dompdf (temporary till 8.5-compatible release available), `chr` in Reader/MsDoc, `setAccessible` in AbstractStyleTest. --- src/PhpWord/Reader/MsDoc.php | 4 +-- src/PhpWord/Writer/PDF/DomPDF.php | 29 +++++++++++++++++++ .../PhpWordTests/Style/AbstractStyleTest.php | 6 +++- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/PhpWord/Reader/MsDoc.php b/src/PhpWord/Reader/MsDoc.php index 7cbeb1a8ac..ad88f4adac 100644 --- a/src/PhpWord/Reader/MsDoc.php +++ b/src/PhpWord/Reader/MsDoc.php @@ -1218,7 +1218,7 @@ private function readRecordSttbfFfn(): void $char = self::getInt2d($this->data1Table, $posMem); $posMem += 2; if ($char > 0) { - $xszFfn .= chr($char); + $xszFfn .= mb_chr($char, 'UTF-8'); } } while ($char != 0); // xszAlt @@ -1230,7 +1230,7 @@ private function readRecordSttbfFfn(): void if ($char == 0) { break; } - $xszAlt .= chr($char); + $xszAlt .= mb_chr($char, 'UTF-8'); } while ($char != 0); } $this->arrayFonts[] = [ diff --git a/src/PhpWord/Writer/PDF/DomPDF.php b/src/PhpWord/Writer/PDF/DomPDF.php index 464dbfa59f..752f49ef6e 100644 --- a/src/PhpWord/Writer/PDF/DomPDF.php +++ b/src/PhpWord/Writer/PDF/DomPDF.php @@ -64,6 +64,14 @@ public function save(string $filename): void $orientation = 'portrait'; // Create PDF + $restoreHandler = false; + if (PHP_VERSION_ID >= self::$temporaryVersionCheck) { + // @codeCoverageIgnoreStart + $errhandler = [$this, 'specialErrorHandler']; + set_error_handler($errhandler); // @phpstan-ignore-line + $restoreHandler = true; + // @codeCoverageIgnoreEnd + } $pdf = $this->createExternalWriterInstance(); $pdf->setPaper(strtolower($paperSize), $orientation); $pdf->loadHtml(str_replace(PHP_EOL, '', $this->getContent())); @@ -72,6 +80,27 @@ public function save(string $filename): void // Write to file fwrite($fileHandle, $pdf->output()); + if ($restoreHandler) { + restore_error_handler(); // @codecoverageignore + } parent::restoreStateAfterSave($fileHandle); } + + protected static int $temporaryVersionCheck = 80500; + + /* + * Temporary handler for Php8.5 waiting for Dompdf release. + * + * @codeCoverageIgnore + */ + public function specialErrorHandler(int $errno, string $errstr, string $filename, int $lineno): bool + { + if ($errno === E_DEPRECATED) { + if (preg_match('/canonical|imagedestroy/', $errstr) === 1) { + return true; + } + } + + return false; // continue error handling + } } diff --git a/tests/PhpWordTests/Style/AbstractStyleTest.php b/tests/PhpWordTests/Style/AbstractStyleTest.php index 679c9f5e49..cf79519bac 100644 --- a/tests/PhpWordTests/Style/AbstractStyleTest.php +++ b/tests/PhpWordTests/Style/AbstractStyleTest.php @@ -124,6 +124,8 @@ public function testSetValEnumException(): void self::assertEquals('b', self::callProtectedMethod($stub, 'setEnumVal', ['z', ['a', 'b'], 'b'])); } + protected static int $temporaryVersionCheck = 80500; + /** * Helper function to call protected method. * @@ -134,7 +136,9 @@ public static function callProtectedMethod($object, $method, array $args = []) { $class = new ReflectionClass(get_class($object)); $method = $class->getMethod($method); - $method->setAccessible(true); + if (PHP_VERSION_ID < self::$temporaryVersionCheck) { + $method->setAccessible(true); + } return $method->invokeArgs($object, $args); } From ccc5bf4ddf3e8d8d0c3ee04a66534b75dd1e4279 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 24 Aug 2025 17:11:53 -0700 Subject: [PATCH 036/153] Accomodate Php7.1/2/3 --- tests/PhpWordTests/Style/AbstractStyleTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/PhpWordTests/Style/AbstractStyleTest.php b/tests/PhpWordTests/Style/AbstractStyleTest.php index cf79519bac..346fd06ce7 100644 --- a/tests/PhpWordTests/Style/AbstractStyleTest.php +++ b/tests/PhpWordTests/Style/AbstractStyleTest.php @@ -124,7 +124,8 @@ public function testSetValEnumException(): void self::assertEquals('b', self::callProtectedMethod($stub, 'setEnumVal', ['z', ['a', 'b'], 'b'])); } - protected static int $temporaryVersionCheck = 80500; + /** @var int */ + protected static $temporaryVersionCheck = 80500; /** * Helper function to call protected method. From 99fcc96f912df829006190670425f7c56e06d9b7 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 24 Aug 2025 17:13:32 -0700 Subject: [PATCH 037/153] More Php7.1/2/3 --- src/PhpWord/Writer/PDF/DomPDF.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/PDF/DomPDF.php b/src/PhpWord/Writer/PDF/DomPDF.php index 752f49ef6e..9f9d35dda8 100644 --- a/src/PhpWord/Writer/PDF/DomPDF.php +++ b/src/PhpWord/Writer/PDF/DomPDF.php @@ -86,7 +86,8 @@ public function save(string $filename): void parent::restoreStateAfterSave($fileHandle); } - protected static int $temporaryVersionCheck = 80500; + /** @var int */ + protected static $temporaryVersionCheck = 80500; /* * Temporary handler for Php8.5 waiting for Dompdf release. From cd209de7d07d0a0afe71f6f7f4908d93841b4d0e Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 24 Aug 2025 18:57:32 -0700 Subject: [PATCH 038/153] Change Case in Annotation --- src/PhpWord/Writer/PDF/DomPDF.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/PDF/DomPDF.php b/src/PhpWord/Writer/PDF/DomPDF.php index 9f9d35dda8..c918fe8a3b 100644 --- a/src/PhpWord/Writer/PDF/DomPDF.php +++ b/src/PhpWord/Writer/PDF/DomPDF.php @@ -81,7 +81,7 @@ public function save(string $filename): void fwrite($fileHandle, $pdf->output()); if ($restoreHandler) { - restore_error_handler(); // @codecoverageignore + restore_error_handler(); // @codeCoverageIgnore } parent::restoreStateAfterSave($fileHandle); } From 2242a8498d1935efd6d22e3aea9efc4b9c405041 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 24 Aug 2025 20:26:12 -0700 Subject: [PATCH 039/153] Need Double Asterisk in Doc-block --- src/PhpWord/Writer/PDF/DomPDF.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/PDF/DomPDF.php b/src/PhpWord/Writer/PDF/DomPDF.php index c918fe8a3b..3de2d68d58 100644 --- a/src/PhpWord/Writer/PDF/DomPDF.php +++ b/src/PhpWord/Writer/PDF/DomPDF.php @@ -89,7 +89,7 @@ public function save(string $filename): void /** @var int */ protected static $temporaryVersionCheck = 80500; - /* + /** * Temporary handler for Php8.5 waiting for Dompdf release. * * @codeCoverageIgnore From 3223dba6c558bd2d8eba2fc55790141f409e1e86 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 2 Sep 2025 00:14:15 -0700 Subject: [PATCH 040/153] Coverage Tweaks --- src/PhpWord/Reader/Word2007/Styles.php | 6 ++---- src/PhpWord/Writer/HTML/Element/Title.php | 6 ++---- src/PhpWord/Writer/HTML/Part/Head.php | 5 +---- tests/PhpWordTests/Writer/PDFTest.php | 1 + 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/PhpWord/Reader/Word2007/Styles.php b/src/PhpWord/Reader/Word2007/Styles.php index 81aaf203c1..0bcc96b75e 100644 --- a/src/PhpWord/Reader/Word2007/Styles.php +++ b/src/PhpWord/Reader/Word2007/Styles.php @@ -68,11 +68,9 @@ public function read(PhpWord $phpWord): void if ($nodes->length > 0) { foreach ($nodes as $node) { $type = $xmlReader->getAttribute('w:type', $node); - $name = $xmlReader->getAttribute('w:val', $node, 'w:name'); + $namex = $xmlReader->getAttribute('w:val', $node, 'w:name'); $styleId = $xmlReader->getAttribute('w:styleId', $node); - if (null === $name) { - $name = $styleId; - } + $name = ($namex !== null) ? $namex : $styleId; $headingMatches = []; preg_match('/Heading\s*(\d)/i', $name, $headingMatches); // $default = ($xmlReader->getAttribute('w:default', $node) == 1); diff --git a/src/PhpWord/Writer/HTML/Element/Title.php b/src/PhpWord/Writer/HTML/Element/Title.php index f3aeb395f4..edd114788c 100644 --- a/src/PhpWord/Writer/HTML/Element/Title.php +++ b/src/PhpWord/Writer/HTML/Element/Title.php @@ -53,7 +53,7 @@ public function write() $writer = new Container($this->parentWriter, $text); $text = $writer->write(); } - $write1 = $write2 = $write3 = ''; + $write1 = $write3 = ''; $style = Style::getStyle('Heading_' . $this->element->getDepth()); if ($style !== null) { $styleWriter = new Font($style); @@ -62,10 +62,8 @@ public function write() if (is_object($paragraphStyle)) { $styleWriter = new Paragraph($paragraphStyle); $write3 = $styleWriter->write(); - if ($write1 !== '' && $write3 !== '') { - $write2 = ' '; - } } + $write2 = ($write1 === '' || $write3 === '') ? '' : ' '; $css = "$write1$write2$write3"; if ($css !== '') { $css = " style=\"$css\""; diff --git a/src/PhpWord/Writer/HTML/Part/Head.php b/src/PhpWord/Writer/HTML/Part/Head.php index 4da526075d..f66b582001 100644 --- a/src/PhpWord/Writer/HTML/Part/Head.php +++ b/src/PhpWord/Writer/HTML/Part/Head.php @@ -88,7 +88,7 @@ private function writeStyles(): string $defaultFontColor = Settings::getDefaultFontColor(); // Default styles $astarray = [ - 'font-family' => $this->getFontFamily(Settings::getDefaultFontName(), $this->getParentWriter()->getDefaultGenericFont()), + 'font-family' => $this->getFontFamily(Settings::getDefaultFontName() ?: Settings::DEFAULT_FONT_NAME, $this->getParentWriter()->getDefaultGenericFont()), 'font-size' => Settings::getDefaultFontSize() . 'pt', 'color' => "#{$defaultFontColor}", ]; @@ -199,9 +199,6 @@ private function writeStyles(): string */ private function getFontFamily(string $font, string $genericFont): string { - if (empty($font)) { - return ''; - } $fontfamily = "'" . htmlspecialchars($font, ENT_QUOTES, 'UTF-8') . "'"; if (!empty($genericFont)) { $fontfamily .= ", $genericFont"; diff --git a/tests/PhpWordTests/Writer/PDFTest.php b/tests/PhpWordTests/Writer/PDFTest.php index b9e31511cf..c7e5376fab 100644 --- a/tests/PhpWordTests/Writer/PDFTest.php +++ b/tests/PhpWordTests/Writer/PDFTest.php @@ -41,6 +41,7 @@ public function testConstruct(): void $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); Settings::setPdfRenderer($rendererName, $rendererLibraryPath); $writer = new PDF(new PhpWord()); + $writer->setFont('xyz'); //* @phpstan-ignore-line $writer->save($file); self::assertFileExists($file); From d685340a1b7c706e63acc9e67dfd1200d3e79f75 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 2 Sep 2025 00:28:21 -0700 Subject: [PATCH 041/153] Php8.5 Dompdf Adjustment --- .github/workflows/php.yml | 2 +- src/PhpWord/Writer/PDF/DomPDF.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index a7b09847c4..fc3a074069 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -120,7 +120,7 @@ jobs: if: matrix.php == 'nightly' env: FAILURE_ACTION: "${{ matrix.experimental == true }}" - run: ./vendor/bin/phpunit -c phpunit10.xml.dist --no-coverage + run: ./vendor/bin/phpunit -c phpunit10.xml.dist --no-coverage || $FAILURE_ACTION - name: Run phpunit 7.3 if: matrix.php == '7.3' diff --git a/src/PhpWord/Writer/PDF/DomPDF.php b/src/PhpWord/Writer/PDF/DomPDF.php index 3de2d68d58..59c8ac9c87 100644 --- a/src/PhpWord/Writer/PDF/DomPDF.php +++ b/src/PhpWord/Writer/PDF/DomPDF.php @@ -97,7 +97,7 @@ public function save(string $filename): void public function specialErrorHandler(int $errno, string $errstr, string $filename, int $lineno): bool { if ($errno === E_DEPRECATED) { - if (preg_match('/canonical|imagedestroy/', $errstr) === 1) { + if (preg_match('/canonical|imagedestroy|http_response_header/', $errstr) === 1) { return true; } } From 5b6e415971815c72b718adca6941d1954c641c54 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 2 Sep 2025 01:02:35 -0700 Subject: [PATCH 042/153] Small yaml Change --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index fc3a074069..c9e2b0c97a 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -177,4 +177,4 @@ jobs: - name: "Generate samples files (Experimental: ${{ matrix.experimental }})" env: FAILURE_ACTION: "${{ matrix.experimental == true }}" - run: composer run samples + run: composer run samples || $FAILURE_ACTION From 644517671aff2bb97eb7bc2896539c1ac727f324 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 2 Sep 2025 08:18:09 -0700 Subject: [PATCH 043/153] Sample Warning in Php8.5 Sample_11_ReadWord2007 reads a file with color=auto, and RTF Writer doesn't handle it properly. Change to treat it as black. --- src/PhpWord/Writer/RTF/Part/Header.php | 6 ++- .../PhpWordTests/Writer/RTF/Sample11Test.php | 38 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 tests/PhpWordTests/Writer/RTF/Sample11Test.php diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index 97644fe4ac..e07f692b47 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -157,7 +157,11 @@ private function writeColorTable() $content .= '{'; $content .= '\colortbl;'; foreach ($this->colorTable as $color) { - [$red, $green, $blue] = Converter::htmlToRgb($color); + $temp = Converter::htmlToRgb($color); + $red = $green = $blue = 0; + if (is_array($temp)) { + [$red, $green, $blue] = $temp; + } $content .= "\\red{$red}\\green{$green}\\blue{$blue};"; } $content .= '}'; diff --git a/tests/PhpWordTests/Writer/RTF/Sample11Test.php b/tests/PhpWordTests/Writer/RTF/Sample11Test.php new file mode 100644 index 0000000000..92e6a8aa40 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Sample11Test.php @@ -0,0 +1,38 @@ +getContent(); + $expected = '{\colortbl;\red255\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;}'; + self::assertStringContainsString($expected, $content); + } +} From 6769df275319934315d6e3c903a38cf81138fec7 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 2 Sep 2025 20:38:16 -0700 Subject: [PATCH 044/153] Sample 11 Fixed for Odt and Docx, Partial for RTF and HTML Still not out of the woods. Write to Docx and Odt are fine. Write to RTF and HTML are fine only if they follow write to ODT. --- src/PhpWord/Style.php | 31 ++++++++++++++++++++- src/PhpWord/Writer/HTML/Part/Head.php | 3 +- src/PhpWord/Writer/ODText/Style/Font.php | 14 ++++++---- src/PhpWord/Writer/RTF/Element/Link.php | 16 ++++++++++- src/PhpWord/Writer/RTF/Element/Text.php | 15 ++++++++++ src/PhpWord/Writer/Word2007/Part/Styles.php | 5 ++++ src/PhpWord/Writer/Word2007/Style/Font.php | 3 +- 7 files changed, 76 insertions(+), 11 deletions(-) diff --git a/src/PhpWord/Style.php b/src/PhpWord/Style.php index 6552a72ce6..0b48a1df3e 100644 --- a/src/PhpWord/Style.php +++ b/src/PhpWord/Style.php @@ -178,15 +178,24 @@ public static function getStyles() /** * Get style by name. * - * @param string $styleName + * @param ?string $styleName * * @return ?AbstractStyle Paragraph|Font|Table|Numbering */ public static function getStyle($styleName) { + if ($styleName === null) { + return null; + } if (isset(self::$styles[$styleName])) { return self::$styles[$styleName]; } + foreach (self::$styles as $key => $value) { + $styleId = self::alternateName($key); + if ($styleId === $styleName) { + return $value; + } + } return null; } @@ -221,4 +230,24 @@ private static function setStyleValues($name, $style, $value = null) return self::getStyle($name); } + + /** + * Get alternate name for style names with embedded spaces. + * + * @param string $name + * + * @return string + */ + public static function alternateName($name) + { + $explode = explode(' ', $name); + if (count($explode) > 1) { + $name = ''; + foreach ($explode as $explodeItem) { + $name .= ucfirst($explodeItem); + } + } + + return $name; + } } diff --git a/src/PhpWord/Writer/HTML/Part/Head.php b/src/PhpWord/Writer/HTML/Part/Head.php index f66b582001..6c2165c0be 100644 --- a/src/PhpWord/Writer/HTML/Part/Head.php +++ b/src/PhpWord/Writer/HTML/Part/Head.php @@ -130,7 +130,8 @@ private function writeStyles(): string // Custom styles $customStyles = Style::getStyles(); if (is_array($customStyles)) { - foreach ($customStyles as $name => $style) { + foreach ($customStyles as $namex => $style) { + $name = Style::alternateName($namex); $styleParagraph = null; if ($style instanceof Font) { $styleWriter = new FontStyleWriter($style); diff --git a/src/PhpWord/Writer/ODText/Style/Font.php b/src/PhpWord/Writer/ODText/Style/Font.php index 95582ec48b..2e32dcc024 100644 --- a/src/PhpWord/Writer/ODText/Style/Font.php +++ b/src/PhpWord/Writer/ODText/Style/Font.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\ODText\Style; +use PhpOffice\PhpWord\Style; + /** * Font style writer. * @@ -31,13 +33,13 @@ class Font extends AbstractStyle public function write(): void { $style = $this->getStyle(); - if (!$style instanceof \PhpOffice\PhpWord\Style\Font) { + if (!$style instanceof Style\Font) { return; } $xmlWriter = $this->getXmlWriter(); $stylep = $style->getParagraph(); - if ($stylep instanceof \PhpOffice\PhpWord\Style\Paragraph) { + if ($stylep instanceof Style\Paragraph) { $temp1 = clone $stylep; $temp1->setStyleName($style->getStyleName()); $temp2 = new Paragraph($xmlWriter, $temp1); @@ -45,7 +47,7 @@ public function write(): void } $xmlWriter->startElement('style:style'); - $xmlWriter->writeAttribute('style:name', $style->getStyleName()); + $xmlWriter->writeAttribute('style:name', Style::alternateName($style->getStyleName())); $xmlWriter->writeAttribute('style:family', 'text'); $xmlWriter->startElement('style:text-properties'); @@ -100,9 +102,9 @@ public function write(): void $xmlWriter->writeAttribute('style:country-complex', 'none'); } - // @todo Foreground-Color - - // @todo Background color + // Foreground-Color (which is really background color) + $fgColor = $style->getFgColor(); + $xmlWriter->writeAttributeIf($fgColor != '', 'fo:background-color', '#' . \PhpOffice\PhpWord\Shared\Converter::stringToRgb($fgColor)); $xmlWriter->endElement(); // style:text-properties $xmlWriter->endElement(); // style:style diff --git a/src/PhpWord/Writer/RTF/Element/Link.php b/src/PhpWord/Writer/RTF/Element/Link.php index 1687d38bca..382099862a 100644 --- a/src/PhpWord/Writer/RTF/Element/Link.php +++ b/src/PhpWord/Writer/RTF/Element/Link.php @@ -41,7 +41,21 @@ public function write() $content = ''; $content .= $this->writeOpening(); $content .= '{\field {\*\fldinst {HYPERLINK "' . $this->element->getSource() . '"}}{\\fldrslt {'; - $content .= $this->writeFontStyle(); + $temp = $this->writeFontStyle(); + $content .= $temp; + if ($temp === '\cf0\f0 ') { + $content .= '\ul '; + $colors = $this->parentWriter->getColorTable(); + $count = count($colors); + for ($i = 0; $i < $count; ++$i) { + if ($colors[$i] === '0000FF') { + $j = $i + 1; + $content .= '\cf' . $j . ' '; + + break; + } + } + } $content .= $this->writeText($this->element->getText()); $content .= '}}}'; $content .= $this->writeClosing(); diff --git a/src/PhpWord/Writer/RTF/Element/Text.php b/src/PhpWord/Writer/RTF/Element/Text.php index 71d3a27ce8..8999ad75fd 100644 --- a/src/PhpWord/Writer/RTF/Element/Text.php +++ b/src/PhpWord/Writer/RTF/Element/Text.php @@ -45,6 +45,21 @@ public function write() $content .= $this->writeOpening(); $content .= '{'; $content .= $this->writeFontStyle(); + $change = $element->getTrackChange(); + if ($change !== null) { + if ($change->getChangeType() === 'DELETED') { + $content .= '\strike '; + } + } + if ($this->fontStyle !== null) { + $fgColor = $this->fontStyle->getFgColor(); + if ($fgColor !== null) { + $fgColorIndex = array_search($fgColor, $this->parentWriter->getColorTable()); + if ($fgColorIndex !== false) { + $content .= '\highlight' . ++$fgColorIndex . ' '; + } + } + } $content .= $this->writeText($element->getText()); $content .= '}'; $content .= $this->writeClosing(); diff --git a/src/PhpWord/Writer/Word2007/Part/Styles.php b/src/PhpWord/Writer/Word2007/Part/Styles.php index 00ab6280e8..4d55845d38 100644 --- a/src/PhpWord/Writer/Word2007/Part/Styles.php +++ b/src/PhpWord/Writer/Word2007/Part/Styles.php @@ -202,6 +202,11 @@ private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $sty } elseif (null !== $paragraphStyle) { // if type is 'paragraph' it should have a styleId $xmlWriter->writeAttribute('w:styleId', $styleName); + } else { + $styleId = Style::alternateName($styleName); + if ($styleName !== $styleId) { + $xmlWriter->writeAttribute('w:styleId', $styleId); + } } // Style name diff --git a/src/PhpWord/Writer/Word2007/Style/Font.php b/src/PhpWord/Writer/Word2007/Style/Font.php index f146307a6d..52b57bc6b1 100644 --- a/src/PhpWord/Writer/Word2007/Style/Font.php +++ b/src/PhpWord/Writer/Word2007/Style/Font.php @@ -39,8 +39,7 @@ public function write(): void { $xmlWriter = $this->getXmlWriter(); - $isStyleName = $this->isInline && null !== $this->style && is_string($this->style); - if ($isStyleName) { + if ($this->isInline && null !== $this->style && is_string($this->style)) { $xmlWriter->startElement('w:rPr'); $xmlWriter->startElement('w:rStyle'); $xmlWriter->writeAttribute('w:val', $this->style); From de2828b3ace59d7a217c8a6ed8e3c9492806182f Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 2 Sep 2025 20:46:10 -0700 Subject: [PATCH 045/153] Quick Fix --- src/PhpWord/Writer/RTF/Element/Text.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/RTF/Element/Text.php b/src/PhpWord/Writer/RTF/Element/Text.php index 8999ad75fd..00e7af677d 100644 --- a/src/PhpWord/Writer/RTF/Element/Text.php +++ b/src/PhpWord/Writer/RTF/Element/Text.php @@ -51,7 +51,7 @@ public function write() $content .= '\strike '; } } - if ($this->fontStyle !== null) { + if ($this->fontStyle !== null && method_exists($this->fontStyle, 'getFgColor')) { $fgColor = $this->fontStyle->getFgColor(); if ($fgColor !== null) { $fgColorIndex = array_search($fgColor, $this->parentWriter->getColorTable()); From 54906bafffc199792c813d7cb440a5ac2a7b6084 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 2 Sep 2025 21:58:35 -0700 Subject: [PATCH 046/153] More Sample11, Fix Html, RTF Still Incomplete --- src/PhpWord/Writer/HTML/Element/Text.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/PhpWord/Writer/HTML/Element/Text.php b/src/PhpWord/Writer/HTML/Element/Text.php index 2df802c904..01af064d6f 100644 --- a/src/PhpWord/Writer/HTML/Element/Text.php +++ b/src/PhpWord/Writer/HTML/Element/Text.php @@ -257,6 +257,11 @@ private function processFontStyle(): void $fontCSS = $styleWriter->write(); if ($fontCSS) { $attributeStyle = ' style="' . $fontCSS . '"'; + } else { + $className = $fontStyle->getStyleName(); + if ($className) { + $attributeStyle = ' class="' . $className . '"'; + } } // Attribute Lang $lang = $fontStyle->getLang(); From dfd34132ea9db9c577901f87fb5fae7dcfd2d096 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 3 Sep 2025 08:01:38 -0700 Subject: [PATCH 047/153] Final (I hope) Changes for Sample11 RTF now works. In the process of saving, ODText creates a lot of new styles. Because it was being done before RTF and HTML in the samples, it was masking problems in the other formats. It is now changed to happen after the other formats. --- samples/Sample_Header.php | 9 ++++++++- .../Writer/RTF/Element/AbstractElement.php | 5 +++++ src/PhpWord/Writer/RTF/Part/Header.php | 19 ++++++++++++++++--- .../PhpWordTests/Writer/RTF/Sample11Test.php | 6 +++++- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/samples/Sample_Header.php b/samples/Sample_Header.php index 57bb10a4c6..641220c0b8 100644 --- a/samples/Sample_Header.php +++ b/samples/Sample_Header.php @@ -31,7 +31,14 @@ } // Set writers -$writers = ['Word2007' => 'docx', 'ODText' => 'odt', 'RTF' => 'rtf', 'HTML' => 'html', 'PDF' => 'pdf', 'EPub3' => 'epub']; +$writers = [ + 'Word2007' => 'docx', + 'RTF' => 'rtf', + 'HTML' => 'html', + 'PDF' => 'pdf', + 'EPub3' => 'epub', + 'ODText' => 'odt', // creates a lot of extra style - do it last +]; // Set PDF renderer if (null === Settings::getPdfRendererPath()) { diff --git a/src/PhpWord/Writer/RTF/Element/AbstractElement.php b/src/PhpWord/Writer/RTF/Element/AbstractElement.php index e007e6aa26..ca669c1f79 100644 --- a/src/PhpWord/Writer/RTF/Element/AbstractElement.php +++ b/src/PhpWord/Writer/RTF/Element/AbstractElement.php @@ -107,6 +107,11 @@ protected function getStyles(): void $this->fontStyle = $element->getFontStyle(); if (is_string($this->fontStyle)) { $this->fontStyle = Style::getStyle($this->fontStyle); + } elseif ($this->fontStyle !== null && is_string($this->fontStyle->getStyleName())) { + $temp = Style::getStyle($this->fontStyle->getStyleName()); + if ($temp instanceof FontStyle) { + $this->fontStyle = $temp; + } } } diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index e07f692b47..05575acc23 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -205,9 +205,22 @@ private function registerFont(): void $elements = $section->getElements(); $this->registerBorderColor($section->getStyle()); foreach ($elements as $element) { - if (method_exists($element, 'getFontStyle')) { - $style = $element->getFontStyle(); - $this->registerFontItems($style); + $this->anythingToRegister($element); + } + } + } + + /** @param mixed $element */ + private function anythingToRegister($element): void + { + if (is_object($element)) { + if (method_exists($element, 'getFontStyle')) { + $style = $element->getFontStyle(); + $this->registerFontItems($style); + } + if (method_exists($element, 'getElements')) { + foreach ($element->getElements() as $subElement) { + $this->anythingToRegister($subElement); } } } diff --git a/tests/PhpWordTests/Writer/RTF/Sample11Test.php b/tests/PhpWordTests/Writer/RTF/Sample11Test.php index 92e6a8aa40..9fdadf8641 100644 --- a/tests/PhpWordTests/Writer/RTF/Sample11Test.php +++ b/tests/PhpWordTests/Writer/RTF/Sample11Test.php @@ -32,7 +32,11 @@ public function testSample11(): void $phpWord = IOFactory::load($source); $writer = new RTF($phpWord); $content = $writer->getContent(); - $expected = '{\colortbl;\red255\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;}'; + $expected = '{\colortbl;\red255\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;\red0\green176\blue80;\red255\green255\blue0;}'; + self::assertStringContainsString($expected, $content); + $expected = '\highlight5 highlighted'; + self::assertStringContainsString($expected, $content); + $expected = '\strike even '; self::assertStringContainsString($expected, $content); } } From 7cac06e563f4791e902adafca58eb444a3a4b47e Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 3 Sep 2025 09:53:01 -0700 Subject: [PATCH 048/153] Slight Coverage Improvement --- src/PhpWord/Writer/HTML/Element/Table.php | 5 ++- src/PhpWord/Writer/RTF/Element/Link.php | 16 +------ .../PhpWordTests/Writer/HTML/Sample11Test.php | 42 +++++++++++++++++++ 3 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 tests/PhpWordTests/Writer/HTML/Sample11Test.php diff --git a/src/PhpWord/Writer/HTML/Element/Table.php b/src/PhpWord/Writer/HTML/Element/Table.php index 98f69e3f3f..398987e0b5 100644 --- a/src/PhpWord/Writer/HTML/Element/Table.php +++ b/src/PhpWord/Writer/HTML/Element/Table.php @@ -57,8 +57,11 @@ public function write() $cellStyle = $rowCells[$j]->getStyle(); $cellStyleCss = $this->getTableStyle($cellStyle) ?: $tableCss; $cellBgColor = $cellStyle->getBgColor(); + if ($cellBgColor === 'auto') { + $cellBgColor = null; + } $cellFgColor = null; - if ($cellBgColor && $cellBgColor !== 'auto') { + if ($cellBgColor) { $red = hexdec(substr($cellBgColor, 0, 2)); $green = hexdec(substr($cellBgColor, 2, 2)); $blue = hexdec(substr($cellBgColor, 4, 2)); diff --git a/src/PhpWord/Writer/RTF/Element/Link.php b/src/PhpWord/Writer/RTF/Element/Link.php index 382099862a..1687d38bca 100644 --- a/src/PhpWord/Writer/RTF/Element/Link.php +++ b/src/PhpWord/Writer/RTF/Element/Link.php @@ -41,21 +41,7 @@ public function write() $content = ''; $content .= $this->writeOpening(); $content .= '{\field {\*\fldinst {HYPERLINK "' . $this->element->getSource() . '"}}{\\fldrslt {'; - $temp = $this->writeFontStyle(); - $content .= $temp; - if ($temp === '\cf0\f0 ') { - $content .= '\ul '; - $colors = $this->parentWriter->getColorTable(); - $count = count($colors); - for ($i = 0; $i < $count; ++$i) { - if ($colors[$i] === '0000FF') { - $j = $i + 1; - $content .= '\cf' . $j . ' '; - - break; - } - } - } + $content .= $this->writeFontStyle(); $content .= $this->writeText($this->element->getText()); $content .= '}}}'; $content .= $this->writeClosing(); diff --git a/tests/PhpWordTests/Writer/HTML/Sample11Test.php b/tests/PhpWordTests/Writer/HTML/Sample11Test.php new file mode 100644 index 0000000000..dde70b30bd --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Sample11Test.php @@ -0,0 +1,42 @@ +getContent(); + $expected = 'source file'; + self::assertStringContainsString($expected, $content); + $expected = 'source file'; + self::assertStringContainsString($expected, $content); + $expected = 'even '; + self::assertStringContainsString($expected, $content); + } +} From 25905559592507134e7b105fc45fcd9e4eb18580 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 3 Sep 2025 21:17:43 -0700 Subject: [PATCH 049/153] New Tests for Coverage Purposes --- src/PhpWord/Writer/RTF/Element/TextRun.php | 11 +++ .../Writer/HTML/Sample36RtlTest.php | 78 +++++++++++++++ .../PhpWordTests/Writer/RTF/Sample11Test.php | 94 +++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 tests/PhpWordTests/Writer/HTML/Sample36RtlTest.php diff --git a/src/PhpWord/Writer/RTF/Element/TextRun.php b/src/PhpWord/Writer/RTF/Element/TextRun.php index 6418de54a8..02816305c4 100644 --- a/src/PhpWord/Writer/RTF/Element/TextRun.php +++ b/src/PhpWord/Writer/RTF/Element/TextRun.php @@ -34,6 +34,17 @@ public function write() { $writer = new Container($this->parentWriter, $this->element); $this->getStyles(); + if ($this->paragraphStyle !== null && $this->paragraphStyle->hasPageBreakBefore()) { + $sect = $this->element->getParent(); + if ($sect instanceof \PhpOffice\PhpWord\Element\Section) { + $elems = $sect->getElements(); + if ($elems[0] === $this->element) { + $pStyle = clone $this->paragraphStyle; + $pStyle->setPageBreakBefore(false); + $this->paragraphStyle = $pStyle; + } + } + } $content = ''; $content .= $this->writeOpening(); diff --git a/tests/PhpWordTests/Writer/HTML/Sample36RtlTest.php b/tests/PhpWordTests/Writer/HTML/Sample36RtlTest.php new file mode 100644 index 0000000000..0acda2abdb --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Sample36RtlTest.php @@ -0,0 +1,78 @@ +addSection(); + + $textrun = $section->addTextRun(); + $textrun->addText('This is a Left to Right paragraph.'); + + $textrun = $section->addTextRun(['alignment' => Jc::END]); + $textrun->addText('سلام این یک پاراگراف راست به چپ است', ['rtl' => true]); + + $section->addText('Table visually presented as RTL'); + $style = ['rtl' => true, 'size' => 12]; + $tableStyle = [ + 'borderSize' => 6, + 'borderColor' => '000000', + 'width' => 5000, + 'unit' => TblWidth::PERCENT, + 'bidiVisual' => true, + ]; + + $table = $section->addTable($tableStyle); + $cellHCentered = ['alignment' => Jc::CENTER]; + $cellHEnd = ['alignment' => Jc::END]; + $cellVCentered = ['valign' => VerticalJc::CENTER]; + + //Vidually bidirectinal table + $table->addRow(); + $cell = $table->addCell(1500, $cellVCentered); + $textrun = $cell->addTextRun($cellHCentered); + $textrun->addText('ردیف', $style); + + $cell = $table->addCell(2000); + $textrun = $cell->addTextRun($cellHEnd); + $textrun->addText('سوالات', $style); + + $cell = $table->addCell(1000, $cellVCentered); + $textrun = $cell->addTextRun($cellHCentered); + $textrun->addText('بارم', $style); + $writer = new HTML($phpWord); + $content = $writer->getContent(); + $expected = 'hash = spl_object_id($this); // Open temporary storage if ($pTemporaryStorage == self::STORAGE_MEMORY) { $this->openMemory(); @@ -90,14 +94,34 @@ public function __destruct() if (empty($this->tempFileName)) { return; } + // This is needed only for Php7.1/2/3. + if ($this->hash !== spl_object_id($this)) { + throw new WordException('Unserialize not permitted1'); // @codeCoverageIgnore + } @unlink($this->tempFileName); } + /* public function __wakeup(): void { $this->tempFileName = ''; - throw new WordException('Unserialize not permitted'); + throw new WordException('Unserialize not permitted2'); + } + */ + + /** + * Unserialization can cause security exploit - don't allow it. + * __wakeup could also be used, but that's compile-time-deprecated for 8.5. + * But Php7.1/2/3 don't recognize unserialize. So, experiment for now. + * + * @param mixed[] $data + */ + public function __unserialize(array $data): void + { + $this->tempFileName = ''; + + throw new WordException('Unserialize not permitted3'); } /** From 01ed5587be901db8bd4ac5e753088b6c02323193 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 6 Sep 2025 18:17:28 -0700 Subject: [PATCH 053/153] Try Again --- .github/workflows/php.yml | 2 +- src/PhpWord/Shared/XMLWriter.php | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 18a181541e..dde30ddddb 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -93,7 +93,7 @@ jobs: with: php-version: ${{ matrix.php }} extensions: dom, gd, mbstring, xml, zip - coverage: ${{ (matrix.php == '7.3') && 'xdebug' || 'none' }} + coverage: ${{ (matrix.php == '8.3') && 'xdebug' || 'none' }} - uses: actions/checkout@v2 diff --git a/src/PhpWord/Shared/XMLWriter.php b/src/PhpWord/Shared/XMLWriter.php index ce919676b5..46f2b1eaac 100644 --- a/src/PhpWord/Shared/XMLWriter.php +++ b/src/PhpWord/Shared/XMLWriter.php @@ -101,14 +101,13 @@ public function __destruct() @unlink($this->tempFileName); } - /* + /** @codeCoverageIgnore */ public function __wakeup(): void { $this->tempFileName = ''; throw new WordException('Unserialize not permitted2'); } - */ /** * Unserialization can cause security exploit - don't allow it. From 287fbe1ff4e7a0e7250dca17cb4592c4a7ccdd78 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 6 Sep 2025 19:20:55 -0700 Subject: [PATCH 054/153] Coverage Tweaks --- src/PhpWord/Shared/XMLReader.php | 4 +- src/PhpWord/TemplateProcessor.php | 4 +- src/PhpWord/Writer/HTML/Style/Paragraph.php | 55 ++++++++------------- 3 files changed, 25 insertions(+), 38 deletions(-) diff --git a/src/PhpWord/Shared/XMLReader.php b/src/PhpWord/Shared/XMLReader.php index ec41f2d674..7ffe5413d4 100644 --- a/src/PhpWord/Shared/XMLReader.php +++ b/src/PhpWord/Shared/XMLReader.php @@ -91,12 +91,12 @@ public function getDomFromZip($zipFile, $xmlFile) public function getDomFromString($content) { if (\PHP_VERSION_ID < 80000) { - $originalLibXMLEntityValue = libxml_disable_entity_loader(true); + $originalLibXMLEntityValue = libxml_disable_entity_loader(true); //* @codeCoverageIgnore } $this->dom = new DOMDocument(); $this->dom->loadXML($content); if (\PHP_VERSION_ID < 80000) { - libxml_disable_entity_loader($originalLibXMLEntityValue); + libxml_disable_entity_loader($originalLibXMLEntityValue); //* @codeCoverageIgnore } return $this->dom; diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index dfe91ca489..effc83f3c4 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -189,7 +189,7 @@ protected function readPartWithRels($fileName) protected function transformSingleXml($xml, $xsltProcessor) { if (\PHP_VERSION_ID < 80000) { - $orignalLibEntityLoader = libxml_disable_entity_loader(true); + $orignalLibEntityLoader = libxml_disable_entity_loader(true); //* @codeCoverageIgnore } $domDocument = new DOMDocument(); if (false === $domDocument->loadXML($xml)) { @@ -201,7 +201,7 @@ protected function transformSingleXml($xml, $xsltProcessor) throw new Exception('Could not transform the given XML document.'); } if (\PHP_VERSION_ID < 80000) { - libxml_disable_entity_loader($orignalLibEntityLoader); + libxml_disable_entity_loader($orignalLibEntityLoader); //* @codeCoverageIgnore } return $transformedXml; diff --git a/src/PhpWord/Writer/HTML/Style/Paragraph.php b/src/PhpWord/Writer/HTML/Style/Paragraph.php index 22267e7b18..84cfb47427 100644 --- a/src/PhpWord/Writer/HTML/Style/Paragraph.php +++ b/src/PhpWord/Writer/HTML/Style/Paragraph.php @@ -29,6 +29,19 @@ */ class Paragraph extends AbstractStyle { + const ALIGNMENT_MAP = [ + Jc::CENTER => 'center', + Jc::MEDIUM_KASHIDA => 'right', + Jc::HIGH_KASHIDA => 'right', + Jc::LOW_KASHIDA => 'right', + Jc::RIGHT => 'right', + Jc::BOTH => 'justify', + Jc::DISTRIBUTE => 'justify', + Jc::THAI_DISTRIBUTE => 'justify', + Jc::JUSTIFY => 'justify', + Jc::LEFT => 'left', + ]; + /** * Write style. * @@ -43,40 +56,14 @@ public function write() $css = []; // Alignment - if ('' !== $style->getAlignment()) { - $textAlign = ''; - - switch ($style->getAlignment()) { - case Jc::CENTER: - $textAlign = 'center'; - - break; - case Jc::END: - $textAlign = $style->isBidi() ? 'left' : 'right'; - - break; - case Jc::MEDIUM_KASHIDA: - case Jc::HIGH_KASHIDA: - case Jc::LOW_KASHIDA: - case Jc::RIGHT: - $textAlign = 'right'; - - break; - case Jc::BOTH: - case Jc::DISTRIBUTE: - case Jc::THAI_DISTRIBUTE: - case Jc::JUSTIFY: - $textAlign = 'justify'; - - break; - case Jc::LEFT: - $textAlign = 'left'; - - break; - default: //all others, including Jc::START - $textAlign = $style->isBidi() ? 'right' : 'left'; - - break; + $alignment = $style->getAlignment(); + if ('' !== $alignment) { + if (array_key_exists($alignment, self::ALIGNMENT_MAP)) { + $textAlign = self::ALIGNMENT_MAP[$alignment]; + } elseif (Jc::END === $alignment) { + $textAlign = $style->isBidi() ? 'left' : 'right'; + } else { //all others, including Jc::START + $textAlign = $style->isBidi() ? 'right' : 'left'; } $css['text-align'] = $textAlign; From 21fc5abf498651a403ab38411dfd64beb8936edc Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 6 Sep 2025 21:17:23 -0700 Subject: [PATCH 055/153] More Coverage Tweaks --- src/PhpWord/Shared/XMLReader.php | 4 ++-- src/PhpWord/TemplateProcessor.php | 4 ++-- src/PhpWord/Writer/HTML/Style/Paragraph.php | 8 +++----- src/PhpWord/Writer/RTF/Element/Table.php | 15 ++++----------- 4 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/PhpWord/Shared/XMLReader.php b/src/PhpWord/Shared/XMLReader.php index 7ffe5413d4..819a755286 100644 --- a/src/PhpWord/Shared/XMLReader.php +++ b/src/PhpWord/Shared/XMLReader.php @@ -91,12 +91,12 @@ public function getDomFromZip($zipFile, $xmlFile) public function getDomFromString($content) { if (\PHP_VERSION_ID < 80000) { - $originalLibXMLEntityValue = libxml_disable_entity_loader(true); //* @codeCoverageIgnore + $originalLibXMLEntityValue = libxml_disable_entity_loader(true); // @codeCoverageIgnore } $this->dom = new DOMDocument(); $this->dom->loadXML($content); if (\PHP_VERSION_ID < 80000) { - libxml_disable_entity_loader($originalLibXMLEntityValue); //* @codeCoverageIgnore + libxml_disable_entity_loader($originalLibXMLEntityValue); // @codeCoverageIgnore } return $this->dom; diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index effc83f3c4..ffd8ba8374 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -189,7 +189,7 @@ protected function readPartWithRels($fileName) protected function transformSingleXml($xml, $xsltProcessor) { if (\PHP_VERSION_ID < 80000) { - $orignalLibEntityLoader = libxml_disable_entity_loader(true); //* @codeCoverageIgnore + $orignalLibEntityLoader = libxml_disable_entity_loader(true); // @codeCoverageIgnore } $domDocument = new DOMDocument(); if (false === $domDocument->loadXML($xml)) { @@ -201,7 +201,7 @@ protected function transformSingleXml($xml, $xsltProcessor) throw new Exception('Could not transform the given XML document.'); } if (\PHP_VERSION_ID < 80000) { - libxml_disable_entity_loader($orignalLibEntityLoader); //* @codeCoverageIgnore + libxml_disable_entity_loader($orignalLibEntityLoader); // @codeCoverageIgnore } return $transformedXml; diff --git a/src/PhpWord/Writer/HTML/Style/Paragraph.php b/src/PhpWord/Writer/HTML/Style/Paragraph.php index 84cfb47427..4e797c8dc9 100644 --- a/src/PhpWord/Writer/HTML/Style/Paragraph.php +++ b/src/PhpWord/Writer/HTML/Style/Paragraph.php @@ -58,12 +58,10 @@ public function write() // Alignment $alignment = $style->getAlignment(); if ('' !== $alignment) { - if (array_key_exists($alignment, self::ALIGNMENT_MAP)) { - $textAlign = self::ALIGNMENT_MAP[$alignment]; - } elseif (Jc::END === $alignment) { + if (Jc::END === $alignment) { $textAlign = $style->isBidi() ? 'left' : 'right'; - } else { //all others, including Jc::START - $textAlign = $style->isBidi() ? 'right' : 'left'; + } else { + $textAlign = self::ALIGNMENT_MAP[$alignment] ?? ($style->isBidi() ? 'right' : 'left'); } $css['text-align'] = $textAlign; diff --git a/src/PhpWord/Writer/RTF/Element/Table.php b/src/PhpWord/Writer/RTF/Element/Table.php index 3b08a5db86..7610ae33e4 100644 --- a/src/PhpWord/Writer/RTF/Element/Table.php +++ b/src/PhpWord/Writer/RTF/Element/Table.php @@ -175,6 +175,9 @@ private function writeCellStyle(CellStyle $cell, ?TableStyle $table): string return $content; } + const MAP_BORDER_TYPE = [ + Border::DOTTED => '\brdrdot', + ]; private function writeCellBorder(string $prefix, ?string $borderStyle, int $borderSize, ?string $borderColor): string { if ($borderSize == 0) { @@ -211,17 +214,7 @@ private function writeCellBorder(string $prefix, ?string $borderStyle, int $bord * \brdremboss Emboss border. * \brdrengrave Engrave border. */ - switch ($borderStyle) { - case Border::DOTTED: - $content .= '\brdrdot'; - - break; - case Border::SINGLE: - default: - $content .= '\brdrs'; - - break; - } + $content = self::MAP_BORDER_TYPE[$borderStyle] ?? '\brdrs'; // \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. From cb162298171715f615f53b8c7300a23b07f484c1 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 6 Sep 2025 22:00:44 -0700 Subject: [PATCH 056/153] Missed a Period --- src/PhpWord/Writer/RTF/Element/Table.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/RTF/Element/Table.php b/src/PhpWord/Writer/RTF/Element/Table.php index 7610ae33e4..8f6f134dd5 100644 --- a/src/PhpWord/Writer/RTF/Element/Table.php +++ b/src/PhpWord/Writer/RTF/Element/Table.php @@ -178,6 +178,7 @@ private function writeCellStyle(CellStyle $cell, ?TableStyle $table): string const MAP_BORDER_TYPE = [ Border::DOTTED => '\brdrdot', ]; + private function writeCellBorder(string $prefix, ?string $borderStyle, int $borderSize, ?string $borderColor): string { if ($borderSize == 0) { @@ -214,7 +215,7 @@ private function writeCellBorder(string $prefix, ?string $borderStyle, int $bord * \brdremboss Emboss border. * \brdrengrave Engrave border. */ - $content = self::MAP_BORDER_TYPE[$borderStyle] ?? '\brdrs'; + $content .= self::MAP_BORDER_TYPE[$borderStyle] ?? '\brdrs'; // \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. From e7b2e4750e1965227ecd8c716e9ed8d58b143759 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 6 Sep 2025 23:09:45 -0700 Subject: [PATCH 057/153] Cover 1 More Statement --- tests/PhpWordTests/Shared/Html2402Test.php | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/PhpWordTests/Shared/Html2402Test.php b/tests/PhpWordTests/Shared/Html2402Test.php index 043aa0361e..abb2c461cd 100644 --- a/tests/PhpWordTests/Shared/Html2402Test.php +++ b/tests/PhpWordTests/Shared/Html2402Test.php @@ -206,4 +206,36 @@ public function testParseTableStyleBorder2px(): void $substring2 = 'border-top-style: dotted; border-top-color: #ff0000; border-top-width: 1.75pt; border-left-style: dotted; border-left-color: #ff0000; border-left-width: 1.75pt; border-bottom-style: dotted; border-bottom-color: #ff0000; border-bottom-width: 1.75pt; border-right-style: dotted; border-right-color: #ff0000; border-right-width: 1.75pt;'; self::assertSame(1, substr_count($content, $substring2)); } + + public function testParseTableStyleBorderBadType(): void + { + $html = << + + + + + + + + + + + +
header aheader bheader c
12
This is bold text6
+HTML; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, $html, false, false); + $elements = $section->getElements(); + $table = $elements[0]; + self::assertInstanceOf(Table::class, $table); + $style = $table->getStyle(); + self::assertInstanceOf(TableStyle::class, $style); + self::assertNull($style->getBorderBottomStyle()); + self::assertSame('', $style->getBorderInsideHStyle()); + self::assertSame('', $style->getBorderInsideVStyle()); + self::assertNull($style->getBorderBottomSize()); + self::assertNull($style->getBorderBottomColor()); + } } From 2f34bcfd0255173b6f7585744b276f4b96ff5cfa Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:57:57 -0700 Subject: [PATCH 058/153] Limit Phpstan to One Run --- .github/workflows/php.yml | 3 +- phpstan-baseline.neon | 222 +----------------- phpstan.neon.dist | 25 -- src/PhpWord/Escaper/Rtf.php | 1 + src/PhpWord/Writer/ODText/Part/Content.php | 1 - src/PhpWord/Writer/PDF/DomPDF.php | 2 +- .../Writer/Word2007/Part/DocumentTest.php | 2 +- tests/bootstrap.php | 1 - 8 files changed, 6 insertions(+), 251 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index dde30ddddb..59f9a9a9ea 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -48,7 +48,8 @@ jobs: fail-fast: false matrix: # Disabled PHPStan in '7.1' - php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + #php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + php: ['8.4'] steps: - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3450b78153..153af7f2db 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -10,36 +10,16 @@ parameters: count: 1 path: src/PhpWord/Element/AbstractContainer.php - - - message: "#^Parameter \\#1 \\$string of function md5 expects string, int\\<0, max\\> given\\.$#" - count: 1 - path: src/PhpWord/Element/AbstractElement.php - - message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\|null given\\.$#" count: 1 path: src/PhpWord/Element/Cell.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:setOptions\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Field but returns array\\.$#" - count: 1 - path: src/PhpWord/Element/Field.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:setProperties\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Field but returns array\\.$#" - count: 1 - path: src/PhpWord/Element/Field.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\) does not accept null\\.$#" count: 1 path: src/PhpWord/Element/Field.php - - - message: "#^Result of \\|\\| is always true\\.$#" - count: 1 - path: src/PhpWord/Element/Field.php - - message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\|null given\\.$#" count: 1 @@ -65,11 +45,6 @@ parameters: count: 1 path: src/PhpWord/Element/Image.php - - - message: "#^Offset 'extension' does not exist on array\\{dirname\\?\\: string, basename\\: string, extension\\?\\: string, filename\\: string\\}\\.$#" - count: 2 - path: src/PhpWord/Element/OLEObject.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\OLEObject\\:\\:\\$icon \\(string\\) does not accept string\\|false\\.$#" count: 1 @@ -90,16 +65,6 @@ parameters: count: 1 path: src/PhpWord/Element/Section.php - - - message: "#^Parameter \\#3 \\$length of function substr expects int\\|null, int\\|false given\\.$#" - count: 1 - path: src/PhpWord/Element/Section.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeAsciiCharacter\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Escaper/Rtf.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeAsciiCharacter\\(\\) has parameter \\$code with no type specified\\.$#" count: 1 @@ -115,11 +80,6 @@ parameters: count: 1 path: src/PhpWord/Escaper/Rtf.php - - - message: "#^Cannot instantiate interface PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface\\.$#" - count: 1 - path: src/PhpWord/IOFactory.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createObject\\(\\) should return PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface but returns object\\.$#" count: 1 @@ -145,11 +105,6 @@ parameters: count: 1 path: src/PhpWord/PhpWord.php - - - message: "#^Parameter \\#1 \\$callback of function forward_static_call_array expects callable\\(\\)\\: mixed, array\\{'PhpOffice\\\\\\\\PhpWord…', string\\} given\\.$#" - count: 1 - path: src/PhpWord/PhpWord.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\AbstractReader\\:\\:openFile\\(\\) should return resource but return statement is missing\\.$#" count: 1 @@ -160,11 +115,6 @@ parameters: count: 1 path: src/PhpWord/Reader/HTML.php - - - message: "#^Offset 'textNodes' on array\\{changed\\: PhpOffice\\\\PhpWord\\\\Element\\\\TrackChange, textNodes\\: DOMNodeList\\\\} in isset\\(\\) always exists and is not nullable\\.$#" - count: 1 - path: src/PhpWord/Reader/ODText/Content.php - - message: "#^Parameter \\#2 \\$contextNode of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLReader\\:\\:getElements\\(\\) expects DOMElement\\|null, DOMNode\\|null given\\.$#" count: 2 @@ -195,21 +145,6 @@ parameters: count: 1 path: src/PhpWord/Reader/ReaderInterface.php - - - message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|false given\\.$#" - count: 2 - path: src/PhpWord/Reader/Word2007.php - - - - message: "#^Parameter \\#2 \\$xmlFile of method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\:\\:getRels\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007.php - - - - message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|false given\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007.php - - message: "#^Binary operation \"/\" between string\\|null and 2 results in an error\\.$#" count: 1 @@ -385,11 +320,6 @@ parameters: count: 1 path: src/PhpWord/Shared/Html.php - - - message: "#^If condition is always true\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:addHtml\\(\\) has parameter \\$options with no type specified\\.$#" count: 1 @@ -430,21 +360,11 @@ parameters: count: 1 path: src/PhpWord/Shared/Html.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseStyleDeclarations\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:recursiveParseStylesInHierarchy\\(\\) has no return type specified\\.$#" count: 1 path: src/PhpWord/Shared/Html.php - - - message: "#^Parameter \\#1 \\$attribute of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseStyle\\(\\) expects DOMAttr, DOMNode given\\.$#" - count: 3 - path: src/PhpWord/Shared/Html.php - - message: "#^Parameter \\#2 \\$element of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseNode\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\|PhpOffice\\\\PhpWord\\\\Element\\\\Row\\|PhpOffice\\\\PhpWord\\\\Element\\\\Table given\\.$#" count: 1 @@ -465,21 +385,6 @@ parameters: count: 1 path: src/PhpWord/Shared/Html.php - - - message: "#^Result of \\|\\| is always true\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Right side of && is always true\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Variable \\$cNodes in empty\\(\\) always exists and is not falsy\\.$#" - count: 2 - path: src/PhpWord/Shared/Html.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$encryptionMatrix has no type specified\\.$#" count: 1 @@ -540,11 +445,6 @@ parameters: count: 3 path: src/PhpWord/Shared/ZipArchive.php - - - message: "#^Comparison operation \"\\!\\=\" between array and 0 results in an error\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - message: "#^Constant PCLZIP_OPT_ADD_PATH not found\\.$#" count: 2 @@ -570,21 +470,11 @@ parameters: count: 1 path: src/PhpWord/Shared/ZipArchive.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:getFromName\\(\\) should return string but returns string\\|false\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:open\\(\\) should return bool but returns int\\|true\\.$#" count: 1 path: src/PhpWord/Shared/ZipArchive.php - - - message: "#^Offset 'dirname' does not exist on array\\{dirname\\?\\: string, basename\\: string, extension\\?\\: string, filename\\: string\\}\\.$#" - count: 3 - path: src/PhpWord/Shared/ZipArchive.php - - message: "#^PHPDoc tag @var for variable \\$zip contains unknown class PclZip\\.$#" count: 6 @@ -595,16 +485,6 @@ parameters: count: 1 path: src/PhpWord/Shared/ZipArchive.php - - - message: "#^Parameter \\#1 \\$stream of function fclose expects resource, resource\\|false given\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Parameter \\#1 \\$stream of function fwrite expects resource, resource\\|false given\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:\\$zip has unknown class PclZip as its type\\.$#" count: 1 @@ -655,16 +535,6 @@ parameters: count: 1 path: src/PhpWord/Style/AbstractStyle.php - - - message: "#^Parameter \\#3 \\$length of function substr expects int\\|null, int\\|false given\\.$#" - count: 1 - path: src/PhpWord/Style/AbstractStyle.php - - - - message: "#^Unreachable statement \\- code above always terminates\\.$#" - count: 1 - path: src/PhpWord/Style/AbstractStyle.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Border\\:\\:getBorderSize\\(\\) should return array\\ but returns array\\\\.$#" count: 1 @@ -710,11 +580,6 @@ parameters: count: 1 path: src/PhpWord/Style/Chart.php - - - message: "#^PHPDoc tag @param has invalid value \\(string\\)\\: Unexpected token \"\\\\n \\* \", expected variable at offset 250$#" - count: 1 - path: src/PhpWord/Style/Chart.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setAllCaps\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" count: 1 @@ -770,11 +635,6 @@ parameters: count: 1 path: src/PhpWord/Style/Paragraph.php - - - message: "#^Result of && is always false\\.$#" - count: 1 - path: src/PhpWord/Style/Paragraph.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Section\\:\\:setSettingValue\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Section but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" count: 1 @@ -960,11 +820,6 @@ parameters: count: 1 path: src/PhpWord/TemplateProcessor.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:indexClonedVariables\\(\\) should return string but returns array\\, string\\|null\\>\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has no return type specified\\.$#" count: 1 @@ -980,31 +835,11 @@ parameters: count: 1 path: src/PhpWord/TemplateProcessor.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:setValueForPart\\(\\) should return string but returns array\\\\|string\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:setValueForPart\\(\\) should return string but returns array\\\\|string\\|null\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - message: "#^Parameter \\#1 \\$element of method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\Chart\\:\\:setElement\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\Chart, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement given\\.$#" count: 1 path: src/PhpWord/TemplateProcessor.php - - - message: "#^Parameter \\#2 \\$array of function implode expects array\\|null, array\\\\|string given\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Parameter \\#2 \\$array of function implode expects array\\|null, string given\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$macroClosingChars has no type specified\\.$#" count: 1 @@ -1015,21 +850,6 @@ parameters: count: 1 path: src/PhpWord/TemplateProcessor.php - - - message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$tempDocumentFooters \\(array\\\\) does not accept string\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$tempDocumentHeaders \\(array\\\\) does not accept string\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: src/PhpWord/Writer/AbstractWriter.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\HTML\\\\Element\\\\AbstractElement\\:\\:write\\(\\) has no return type specified\\.$#" count: 1 @@ -1295,11 +1115,6 @@ parameters: count: 4 path: src/PhpWord/Writer/Word2007/Part/Chart.php - - - message: "#^Parameter \\#1 \\$string of function md5 expects string, int\\<0, max\\> given\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Part/Numbering.php - - message: "#^Parameter \\#1 \\$haystack of function strpos expects string, int given\\.$#" count: 1 @@ -1310,11 +1125,6 @@ parameters: count: 1 path: src/PhpWord/Writer/Word2007/Style/AbstractStyle.php - - - message: "#^Parameter \\#1 \\$styleName of static method PhpOffice\\\\PhpWord\\\\Style\\:\\:getStyle\\(\\) expects string, PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|string given\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Style/Font.php - - message: "#^Call to an undefined method object\\:\\:read\\(\\)\\.$#" count: 1 @@ -1360,11 +1170,6 @@ parameters: count: 2 path: tests/PhpWordTests/Element/CellTest.php - - - message: "#^Parameter \\#1 \\$text of method PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:setText\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string\\|null, array given\\.$#" - count: 1 - path: tests/PhpWordTests/Element/FieldTest.php - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$createFunction with no type specified\\.$#" count: 1 @@ -1400,16 +1205,6 @@ parameters: count: 1 path: tests/PhpWordTests/Element/ImageTest.php - - - message: "#^Parameter \\#1 \\$string of function md5 expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Element/ImageTest.php - - - - message: "#^Parameter \\#1 \\$string of function ucfirst expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Element/ImageTest.php - - message: "#^Parameter \\#3 \\$watermark of class PhpOffice\\\\PhpWord\\\\Element\\\\Image constructor expects bool, null given\\.$#" count: 1 @@ -1505,11 +1300,6 @@ parameters: count: 2 path: tests/PhpWordTests/Reader/Word2007/StyleTest.php - - - message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" count: 1 @@ -1585,11 +1375,6 @@ parameters: count: 1 path: tests/PhpWordTests/Shared/XMLWriterTest.php - - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: tests/PhpWordTests/Shared/ZipArchiveTest.php - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Style\\\\AbstractStyleTest\\:\\:callProtectedMethod\\(\\) has no return type specified\\.$#" count: 1 @@ -1670,11 +1455,6 @@ parameters: count: 1 path: tests/PhpWordTests/TemplateProcessorTest.php - - - message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: tests/PhpWordTests/TestHelperDOCX.php - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\TestableTemplateProcesor\\:\\:__construct\\(\\) has parameter \\$mainPart with no type specified\\.$#" count: 1 @@ -1856,7 +1636,7 @@ parameters: # https://github.com/phpstan/phpstan/issues/8770 - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\EPub3\\\\Style\\\\AbstractStyle is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 2 + count: 1 path: tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php # https://github.com/phpstan/phpstan/issues/8770 diff --git a/phpstan.neon.dist b/phpstan.neon.dist index cf26b6956b..46d77b13d4 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -14,32 +14,7 @@ parameters: - src/PhpWord/Writer/PDF/MPDF.php bootstrapFiles: - tests/bootstrap.php - ## <=PHP7.4 - reportUnmatchedIgnoredErrors: false treatPhpDocTypesAsCertain: false ignoreErrors: - identifier: missingType.iterableValue - - ## <=PHP7.4 - - - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' - path: src/PhpWord/Element/AbstractContainer.php - - - message: '#Parameter \#1 \$function of function call_user_func expects callable\(\): mixed, string given.#' - path: src/PhpWord/Element/Image.php - - - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' - path: src/PhpWord/IOFactory.php - - - message: '#Parameter \#1 \$function of function forward_static_call_array expects callable\(\): mixed, array{.+, string} given.#' - path: src/PhpWord/PhpWord.php - - - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{\$this\(PhpOffice\\PhpWord\\Shared\\ZipArchive\)\|PclZip\|ZipArchive, mixed} given.#' - path: src/PhpWord/Shared/ZipArchive.php - - - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{PhpOffice\\PhpWord\\Writer\\PDF\\AbstractRenderer, string} given.#' - path: src/PhpWord/Writer/PDF.php - - - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' - path: tests/PhpWordTests/Style/AbstractStyleTest.php diff --git a/src/PhpWord/Escaper/Rtf.php b/src/PhpWord/Escaper/Rtf.php index 815e3a2751..0bde66b470 100644 --- a/src/PhpWord/Escaper/Rtf.php +++ b/src/PhpWord/Escaper/Rtf.php @@ -25,6 +25,7 @@ */ class Rtf extends AbstractEscaper { + /** @return string */ protected function escapeAsciiCharacter($code) { if ($code == 9) { diff --git a/src/PhpWord/Writer/ODText/Part/Content.php b/src/PhpWord/Writer/ODText/Part/Content.php index b4958e84e6..6f42396471 100644 --- a/src/PhpWord/Writer/ODText/Part/Content.php +++ b/src/PhpWord/Writer/ODText/Part/Content.php @@ -107,7 +107,6 @@ public function write() } $xmlWriter->endElement(); // office:change-info if ($trackedChange->getChangeType() == TrackChange::DELETED && method_exists($trackedElement, 'getText')) { - // @phpstan-ignore-next-line $xmlWriter->writeElement('text:p', $trackedElement->getText()); } diff --git a/src/PhpWord/Writer/PDF/DomPDF.php b/src/PhpWord/Writer/PDF/DomPDF.php index 7b846f9cd0..1f25ad58cc 100644 --- a/src/PhpWord/Writer/PDF/DomPDF.php +++ b/src/PhpWord/Writer/PDF/DomPDF.php @@ -68,7 +68,7 @@ public function save(string $filename): void if (PHP_VERSION_ID >= self::$temporaryVersionCheck) { // @codeCoverageIgnoreStart $errhandler = [$this, 'specialErrorHandler']; - set_error_handler($errhandler); // @phpstan-ignore-line + set_error_handler($errhandler); $restoreHandler = true; // @codeCoverageIgnoreEnd } diff --git a/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php b/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php index d1f5b2585d..35d65f9238 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php @@ -410,7 +410,7 @@ public function testWriteImage(): void // @phpstan-ignore-next-line if (method_exists(self::class, 'assertMatchesRegularExpression')) { self::assertMatchesRegularExpression('/z\-index:\-[0-9]*/', $style); - } elseif (method_exists(self::class, 'assertRegExp')) { // @phpstan-ignore-line + } elseif (method_exists(self::class, 'assertRegExp')) { self::assertRegExp('/z\-index:\-[0-9]*/', $style); } else { self::fail('Unsure how to test regexp'); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d94d68d880..f9e0ca2388 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -59,7 +59,6 @@ function utf8decode(string $value, string $toEncoding = 'ISO-8859-1'): string return $result === false ? '' : $result; } -// @phpstan-ignore-next-line if (!method_exists(PHPUnit\Framework\TestCase::class, 'setOutputCallback')) { ini_set('error_reporting', (string) E_ALL); set_error_handler('phpunit10ErrorHandler'); From 09602d7a39c4dda6ec383e4352a9047aeb21d048 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Sep 2025 11:15:01 -0700 Subject: [PATCH 059/153] Multiple Phpstan's --- .github/workflows/php.yml | 11 +++++++--- phpstan.neon.php7.dist | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 phpstan.neon.php7.dist diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 59f9a9a9ea..0893d9dd19 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -48,8 +48,8 @@ jobs: fail-fast: false matrix: # Disabled PHPStan in '7.1' - #php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] - php: ['8.4'] + php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + #php: ['8.4'] steps: - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -61,7 +61,12 @@ jobs: - name: Composer Install run: composer install --ansi --prefer-dist --no-interaction --no-progress - - name: Run phpstan + - name: Run phpstan Php7 + if: matrix.php == '7.2' || matrix.php == '7.3' || matrix.php == '7.4' + run: ./vendor/bin/phpstan analyse -c phpstan.neon.php7.dist + + - name: Run phpstan Php8 + if: matrix.php == '8.0' || matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.3' || matrix.php == '8.4' run: ./vendor/bin/phpstan analyse -c phpstan.neon.dist phpunit: diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist new file mode 100644 index 0000000000..09f269a3fa --- /dev/null +++ b/phpstan.neon.php7.dist @@ -0,0 +1,45 @@ +includes: + - phpstan-baseline.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon +parameters: + level: 7 + paths: + - src/ + - tests/ + excludePaths: + - */pclzip.lib.php + - src/PhpWord/Shared/OLERead.php + - src/PhpWord/Reader/MsDoc.php + - src/PhpWord/Writer/PDF/MPDF.php + - src/PhpWord/Shared/XMLWriter.php + - src/PhpWord/Shared/XMLWriter.withwakeup.php + bootstrapFiles: + - tests/bootstrap.php + treatPhpDocTypesAsCertain: false + ignoreErrors: + - + identifier: missingType.iterableValue + + ## <=PHP7.4 + - + message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' + path: src/PhpWord/Element/AbstractContainer.php + - + message: '#Parameter \#1 \$function of function call_user_func expects callable\(\): mixed, string given.#' + path: src/PhpWord/Element/Image.php + - + message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' + path: src/PhpWord/IOFactory.php + - + message: '#Parameter \#1 \$function of function forward_static_call_array expects callable\(\): mixed, array{.+, string} given.#' + path: src/PhpWord/PhpWord.php + - + message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{\$this\(PhpOffice\\PhpWord\\Shared\\ZipArchive\)\|PclZip\|ZipArchive, mixed} given.#' + path: src/PhpWord/Shared/ZipArchive.php + - + message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{PhpOffice\\PhpWord\\Writer\\PDF\\AbstractRenderer, string} given.#' + path: src/PhpWord/Writer/PDF.php + - + message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' + path: tests/PhpWordTests/Style/AbstractStyleTest.php From c467856ecede12d5589608a671ea4d004016e380 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Sep 2025 12:21:27 -0700 Subject: [PATCH 060/153] Fix 8.0 Phpstan, Partial Fix for Php7 Phpstans --- phpstan-baseline.php7.neon | 1608 +++++++++++++++++ phpstan.neon.php7.dist | 10 +- .../Writer/Word2007/Part/DocumentTest.php | 19 +- tests/bootstrap.php | 8 +- 4 files changed, 1629 insertions(+), 16 deletions(-) create mode 100644 phpstan-baseline.php7.neon diff --git a/phpstan-baseline.php7.neon b/phpstan-baseline.php7.neon new file mode 100644 index 0000000000..948d9b58c2 --- /dev/null +++ b/phpstan-baseline.php7.neon @@ -0,0 +1,1608 @@ +parameters: + ignoreErrors: + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:__call\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement but returns null\\.$#" + count: 1 + path: src/PhpWord/Element/AbstractContainer.php + + - + message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\|null given\\.$#" + count: 1 + path: src/PhpWord/Element/Cell.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\) does not accept null\\.$#" + count: 1 + path: src/PhpWord/Element/Field.php + + - + message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Element/Footnote.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:getArchiveImageSize\\(\\) should return array\\|null but returns array\\|false\\|null\\.$#" + count: 1 + path: src/PhpWord/Element/Image.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:getImageString\\(\\) should return string\\|null but returns string\\|false\\|null\\.$#" + count: 1 + path: src/PhpWord/Element/Image.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:\\$source \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: src/PhpWord/Element/Image.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\OLEObject\\:\\:\\$icon \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: src/PhpWord/Element/OLEObject.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Section\\:\\:addHeader\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Header but returns PhpOffice\\\\PhpWord\\\\Element\\\\Footer\\.$#" + count: 1 + path: src/PhpWord/Element/Section.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Section\\:\\:addHeaderFooter\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Footer but returns PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\.$#" + count: 1 + path: src/PhpWord/Element/Section.php + + - + message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\|PhpOffice\\\\PhpWord\\\\Style\\\\Section\\|string given\\.$#" + count: 1 + path: src/PhpWord/Element/Section.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeAsciiCharacter\\(\\) has parameter \\$code with no type specified\\.$#" + count: 1 + path: src/PhpWord/Escaper/Rtf.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeMultibyteCharacter\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Escaper/Rtf.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeMultibyteCharacter\\(\\) has parameter \\$code with no type specified\\.$#" + count: 1 + path: src/PhpWord/Escaper/Rtf.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createObject\\(\\) should return PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface but returns object\\.$#" + count: 1 + path: src/PhpWord/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createReader\\(\\) should return PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface but returns PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface\\.$#" + count: 1 + path: src/PhpWord/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createWriter\\(\\) should return PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface but returns PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface\\.$#" + count: 1 + path: src/PhpWord/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:getDefaultFontSize\\(\\) should return int but returns float\\|int\\.$#" + count: 1 + path: src/PhpWord/PhpWord.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\AbstractReader\\:\\:openFile\\(\\) should return resource but return statement is missing\\.$#" + count: 1 + path: src/PhpWord/Reader/AbstractReader.php + + - + message: "#^Parameter \\#2 \\$html of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:addHtml\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpWord/Reader/HTML.php + + - + message: "#^Parameter \\#2 \\$contextNode of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLReader\\:\\:getElements\\(\\) expects DOMElement\\|null, DOMNode\\|null given\\.$#" + count: 2 + path: src/PhpWord/Reader/ODText/Content.php + + - + message: "#^Parameter \\#2 \\$depth of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addTitle\\(\\) expects int, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/ODText/Content.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$rtf \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: src/PhpWord/Reader/RTF.php + + - + message: "#^Cannot call method setStyleByArray\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" + count: 1 + path: src/PhpWord/Reader/RTF/Document.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$phpWord is never read, only written\\.$#" + count: 1 + path: src/PhpWord/Reader/RTF/Document.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\:\\:load\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Reader/ReaderInterface.php + + - + message: "#^Binary operation \"/\" between string\\|null and 2 results in an error\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Call to an undefined method DOMNode\\:\\:getAttribute\\(\\)\\.$#" + count: 2 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:getHeadingDepth\\(\\) never returns float so it can be removed from the return type\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:getHeadingDepth\\(\\) should return float\\|int\\|null but returns string\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:read\\(\\) has no return type specified\\.$#" + 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 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#1 \\$height of method PhpOffice\\\\PhpWord\\\\Element\\\\Table\\:\\:addRow\\(\\) expects int\\|null, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setRelationId\\(\\) expects int, string\\|null given\\.$#" + count: 2 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#1 \\$width of method PhpOffice\\\\PhpWord\\\\Element\\\\Row\\:\\:addCell\\(\\) expects int\\|null, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#2 \\$contextNode of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLReader\\:\\:getAttribute\\(\\) expects DOMElement\\|null, DOMNode\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#2 \\$depth of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addTitle\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and DOMElement will always evaluate to false\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#2 \\$relationId of method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\Footnotes\\:\\:getElement\\(\\) expects int, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Footnotes.php + + - + message: "#^Parameter \\#3 \\$levelId of method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\Numbering\\:\\:readLevel\\(\\) expects int, string\\|null given\\.$#" + count: 2 + path: src/PhpWord/Reader/Word2007/Numbering.php + + - + message: "#^Parameter \\#1 \\$comments of method PhpOffice\\\\PhpWord\\\\ComplexType\\\\TrackChangesView\\:\\:setComments\\(\\) expects bool\\|null, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Settings.php + + - + message: "#^Parameter \\#1 \\$consecutiveHyphenLimit of method PhpOffice\\\\PhpWord\\\\Metadata\\\\Settings\\:\\:setConsecutiveHyphenLimit\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Settings.php + + - + message: "#^Parameter \\#1 \\$hyphenationZone of method PhpOffice\\\\PhpWord\\\\Metadata\\\\Settings\\:\\:setHyphenationZone\\(\\) expects float\\|int\\|null, string given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Settings.php + + - + message: "#^Parameter \\#1 \\$filename of function parse_ini_file expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpWord/Settings.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\AbstractEnum\\:\\:getConstants\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/AbstractEnum.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\AbstractEnum\\:\\:\\$constCacheArray has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/AbstractEnum.php + + - + message: "#^Binary operation \"/\" between string and 10 results in an error\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:angleToDegree\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:cssToPoint\\(\\) should return float\\|null but returns string\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:htmlToRgb\\(\\) should return array but returns false\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:inchToEmu\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:pixelToEmu\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$centimeter of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:cmToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$inch of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:inchToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$pica of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:picaToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$pixel of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:pixelToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:angleToDegrees\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Drawing.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:emuToPixels\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Drawing.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:pixelsToEmu\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Drawing.php + + - + message: "#^Call to an undefined method DOMNode\\:\\:getAttribute\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Cannot call method setStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:addHtml\\(\\) has parameter \\$options with no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:filterOutNonInheritedStyles\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapBorderColor\\(\\) has parameter \\$cssBorderColor with no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapBorderColor\\(\\) has parameter \\$styles with no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapListType\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseLink\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseList\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseRuby\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:recursiveParseStylesInHierarchy\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Parameter \\#2 \\$element of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseNode\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\|PhpOffice\\\\PhpWord\\\\Element\\\\Row\\|PhpOffice\\\\PhpWord\\\\Element\\\\Table given\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$listIndex has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$options has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$xpath has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$encryptionMatrix has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$initialCodeArray has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$passwordMaxLength has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:getData\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/PhpWord/Shared/XMLWriter.php + + - + message: "#^Call to method add\\(\\) on an unknown class PclZip\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method addFromString\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method close\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method extract\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method extractByIndex\\(\\) on an unknown class PclZip\\.$#" + count: 3 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method extractTo\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method getFromName\\(\\) on an unknown class PclZip\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method listContent\\(\\) on an unknown class PclZip\\.$#" + count: 3 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_ADD_PATH not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_EXTRACT_AS_STRING not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_PATH not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_REMOVE_PATH not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Instantiated class PclZip not found\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:open\\(\\) should return bool but returns int\\|true\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^PHPDoc tag @var for variable \\$zip contains unknown class PclZip\\.$#" + count: 6 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Parameter \\#1 \\$callback of function call_user_func_array expects callable\\(\\)\\: mixed, array\\{\\$this\\(PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\)\\|PclZip\\|ZipArchive, mixed\\} given\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:\\$zip has unknown class PclZip as its type\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addFontStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addLinkStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addNumberingStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Numbering but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addParagraphStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addTableStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Table but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addTitleStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Call to an undefined method object\\:\\:setStyleByArray\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Style/AbstractStyle.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setFloatVal\\(\\) should return float\\|null but returns float\\|int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/AbstractStyle.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setNumericVal\\(\\) should return float\\|int\\|null but returns float\\|int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/AbstractStyle.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Border\\:\\:getBorderSize\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: src/PhpWord/Style/Border.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\:\\:getBgColor\\(\\) should return string but returns null\\.$#" + count: 1 + path: src/PhpWord/Style/Cell.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\:\\:setUnit\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Cell.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:getMajorTickPosition\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setCategoryAxisTitle\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setValueAxisTitle\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setValueLabelPosition\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:showAxisLabels\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:showGridY\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setAllCaps\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setBgColor\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Table but return statement is missing\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setDoubleStrikethrough\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSmallCaps\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setStrikethrough\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSubScript\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSuperScript\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Line\\:\\:\\$weight \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Line.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\ListItem\\:\\:getListTypeStyle\\(\\) should return array but empty return statement found\\.$#" + count: 1 + path: src/PhpWord/Style/ListItem.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\ListItem\\:\\:getListTypeStyle\\(\\) should return array but return statement is missing\\.$#" + count: 1 + path: src/PhpWord/Style/ListItem.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\:\\:setStyleValue\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Paragraph.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Section\\:\\:setSettingValue\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Section but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Section.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\TOC\\:\\:setTabLeader\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\TOC but returns PhpOffice\\\\PhpWord\\\\Style\\\\Tab\\.$#" + count: 1 + path: src/PhpWord/Style/TOC.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\TOC\\:\\:setTabPos\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\TOC but returns PhpOffice\\\\PhpWord\\\\Style\\\\Tab\\.$#" + count: 1 + path: src/PhpWord/Style/TOC.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideHColor\\(\\) should return string but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideHSize\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideVColor\\(\\) should return string but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideVSize\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderSize\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginBottom\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginLeft\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginRight\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginTop\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$bottomFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$leftFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$rightFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$tblpX \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$tblpY \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$topFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Cannot access offset 'end' on array\\\\|true\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Cannot access offset 'start' on array\\\\|true\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$imageMimeType with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$imgPath with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$partFileName with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$rid with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$baseValue with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$defaultValue with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$inlineValue with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$actualHeight with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$actualWidth with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$height with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$width with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getImageArgs\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getImageArgs\\(\\) has parameter \\$varNameWithArgs with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getNextRelationsIndex\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getNextRelationsIndex\\(\\) has parameter \\$documentPartName with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has parameter \\$replaceImage with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has parameter \\$varInlineArgs with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Parameter \\#1 \\$element of method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\Chart\\:\\:setElement\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\Chart, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement given\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$macroClosingChars has no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$macroOpeningChars has no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\HTML\\\\Element\\\\AbstractElement\\:\\:write\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/HTML/Element/AbstractElement.php + + - + message: "#^Variable \\$row in PHPDoc tag @var does not match assigned variable \\$rowStyle\\.$#" + count: 1 + path: src/PhpWord/Writer/HTML/Element/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Field\\:\\:writeDefault\\(\\) has parameter \\$type with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Field.php + + - + message: "#^Variable \\$row in PHPDoc tag @var does not match any variable in the foreach loop\\: \\$cell$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\AbstractElement\\:\\:replaceTabs\\(\\) has parameter \\$text with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\AbstractElement\\:\\:replaceTabs\\(\\) has parameter \\$xmlWriter with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Text\\:\\:writeChangeInsertion\\(\\) has parameter \\$start with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Text.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getParagraphStyle\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/TextRun.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setColumnWidths\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Parameter \\#1 \\$container of method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Content\\:\\:collectTrackedChanges\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement given\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Content\\:\\:\\$imageParagraphStyles has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 2 + path: src/PhpWord/Writer/ODText/Part/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Styles\\:\\:cvttwiptostr\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Styles.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:\\$renderer \\(PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\AbstractRenderer\\) does not accept object\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:loadHtml\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:output\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:render\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:setPaper\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Class PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF referenced with incorrect case\\: PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\Dompdf\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:createExternalWriterInstance\\(\\) should return PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF but returns Dompdf\\\\Dompdf\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Binary operation \"\\+\" between int\\|string and 1 results in an error\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Style\\\\Font\\:\\:setNameIndex\\(\\) expects int, int\\|string given\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|null\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|null\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept null\\.$#" + count: 2 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:write\\(\\) should return string but empty return statement found\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writeDate\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writeNumpages\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writePage\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getImageStringData\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Image.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getStyle\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Image.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getSource\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Link.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Link.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 2 + path: src/PhpWord/Writer/RTF/Part/Document.php + + - + message: "#^Binary operation \"\\+\" between int\\|string and 1 results in an error\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Style/Border.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\AbstractElement\\:\\:write\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\Field\\:\\:buildPropertiesAndOptions\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/Field.php + + - + message: "#^Parameter \\#1 \\$content of method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\AbstractElement\\:\\:writeText\\(\\) expects string, bool\\|int\\|string given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/FormField.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, bool\\|int\\|string given\\.$#" + count: 3 + path: src/PhpWord/Writer/Word2007/Element/FormField.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int given\\.$#" + count: 4 + path: src/PhpWord/Writer/Word2007/Element/FormField.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\ParagraphAlignment\\:\\:\\$attributes has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\ParagraphAlignment\\:\\:\\$name has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php + + - + message: "#^Parameter \\#2 \\$content of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, bool\\|int\\|string given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/SDT.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int\\<100000000, 999999999\\> given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/SDT.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\TableAlignment\\:\\:\\$attributes has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/TableAlignment.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\TableAlignment\\:\\:\\$name has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/TableAlignment.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:addBookmark\\(\\) invoked with 0 parameters, 1 required\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/Title.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\Chart\\:\\:writeAxisTitle\\(\\) has parameter \\$title with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Part/Chart.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int given\\.$#" + count: 9 + path: src/PhpWord/Writer/Word2007/Part/Chart.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int\\<0, max\\> given\\.$#" + count: 4 + path: src/PhpWord/Writer/Word2007/Part/Chart.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, int given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Part/Rels.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Style\\\\AbstractStyle\\:\\:write\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Style/AbstractStyle.php + + - + message: "#^Call to an undefined method object\\:\\:read\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractTestReader.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\AbstractTestReader\\:\\:\\$parts has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractTestReader.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getBaseUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteBmpImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteGifImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:\\$httpServer has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Parameter \\#1 \\$width of class PhpOffice\\\\PhpWord\\\\Element\\\\Cell constructor expects int\\|null, string given\\.$#" + count: 2 + path: tests/PhpWordTests/Element/CellTest.php + + - + message: "#^Parameter \\#2 \\$style of class PhpOffice\\\\PhpWord\\\\Element\\\\Cell constructor expects array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\|null, int given\\.$#" + count: 2 + path: tests/PhpWordTests/Element/CellTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$createFunction with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$extension with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$imageFunction with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$imageQuality with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$source with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$type with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Parameter \\#1 \\$source of class PhpOffice\\\\PhpWord\\\\Element\\\\Image constructor expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Parameter \\#3 \\$watermark of class PhpOffice\\\\PhpWord\\\\Element\\\\Image constructor expects bool, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Parameter \\#2 \\$style of class PhpOffice\\\\PhpWord\\\\Element\\\\Section constructor expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, PhpOffice\\\\PhpWord\\\\Style\\\\Section given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/SectionTest.php + + - + message: "#^Parameter \\#1 \\$text of class PhpOffice\\\\PhpWord\\\\Element\\\\Title constructor expects PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string, PhpOffice\\\\PhpWord\\\\Element\\\\PageBreak given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/TitleTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:escapestring\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:escapestring\\(\\) has parameter \\$str with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:expect\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:expect\\(\\) has parameter \\$str with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Parameter \\#1 \\$expected of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) expects class\\-string\\, string given\\.$#" + count: 1 + path: tests/PhpWordTests/IOFactoryTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/IOFactoryTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:undefinedMethod\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/PhpWordTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getRows\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/ElementTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/ElementTest.php + + - + message: "#^Cannot access offset 0 on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/ElementTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getElement\\(\\)\\.$#" + count: 2 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Cannot call method getElement\\(\\) on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string\\.$#" + count: 3 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Cannot call method isBold\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Variable \\$endnote in PHPDoc tag @var does not match assigned variable \\$documentEndnote\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Variable \\$footnote in PHPDoc tag @var does not match assigned variable \\$documentFootnote\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Cannot access offset 0 on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\.$#" + count: 2 + path: tests/PhpWordTests/Reader/Word2007/StyleTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$compatibility has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultFontName has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultFontSize has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultPaper has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$measurementUnit has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$pdfRendererName has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$pdfRendererPath has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$tempDir has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$zipClass has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Cannot call method getStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Shared/HtmlTest.php + + - + message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Text\\:\\:numberFormat\\(\\) expects float, string given\\.$#" + count: 2 + path: tests/PhpWordTests/Shared/TextTest.php + + - + message: "#^Parameter \\#2 \\$locale of function setlocale expects array\\|string\\|null, int given\\.$#" + count: 1 + path: tests/PhpWordTests/Shared/XMLWriterTest.php + + - + message: "#^Parameter \\#2 \\$locale of function setlocale expects string\\|null, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Shared/XMLWriterTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Style\\\\AbstractStyleTest\\:\\:callProtectedMethod\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Style/AbstractStyleTest.php + + - + message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|object, class\\-string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/AbstractStyleTest.php + + - + message: "#^Cannot call method setLineHeight\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Style/FontTest.php + + - + message: "#^Parameter \\#1 \\$type of class PhpOffice\\\\PhpWord\\\\Style\\\\Font constructor expects string, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/FontTest.php + + - + message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setStyleValue\\(\\) expects array\\|int\\|string, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/FontTest.php + + - + message: "#^Cannot call method setLineHeight\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Style/ParagraphTest.php + + - + message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setStyleValue\\(\\) expects array\\|int\\|string, bool given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/RowTest.php + + - + message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\Section\\:\\:setSettingValue\\(\\) expects array\\|int\\|string, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/SectionTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" + count: 2 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:AddFromString\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Cannot access offset 'end' on array\\\\|bool\\.$#" + count: 2 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Cannot access offset 'start' on array\\\\|bool\\.$#" + count: 2 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\) expects string, string\\|false given\\.$#" + count: 6 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringNotContainsString\\(\\) expects string, string\\|false given\\.$#" + count: 4 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Part \\$documentZip \\(ZipArchive\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Part \\$templateZip \\(ZipArchive\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\TestableTemplateProcesor\\:\\:__construct\\(\\) has parameter \\$mainPart with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/TestableTemplateProcesor.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\TestableTemplateProcesor\\:\\:__construct\\(\\) has parameter \\$settingsPart with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/TestableTemplateProcesor.php + + - + message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" + count: 9 + path: tests/PhpWordTests/Writer/HTML/ElementTest.php + + - + message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" + count: 2 + path: tests/PhpWordTests/Writer/HTML/Element/RubyTest.php + + - + message: "#^Cannot call method item\\(\\) on DOMNodeList\\\\|false\\.$#" + count: 11 + path: tests/PhpWordTests/Writer/HTML/ElementTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 3 + path: tests/PhpWordTests/Writer/HTML/Element/PageBreakTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\ODText\\\\Style\\\\FontTest\\:\\:providerAllNamedColors\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/ODText/Style/FontTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" + count: 2 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getOrientation\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getPaperSize\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getTempDir\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setFont\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setOrientation\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setPaperSize\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setTempDir\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 3 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/MPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/MPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 2 + path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDFTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/ElementTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/ElementTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/StyleTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/StyleTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\Writer\\\\Word2007\\\\Element\\\\ChartTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/ElementTest.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/StyleTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getElement\\(\\) should return DOMElement\\|null but returns DOMNode\\|null\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getNodeList\\(\\) should return DOMNodeList\\ but returns DOMNodeList\\\\|false\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$path \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$xpath \\(DOMXPath\\) does not accept null\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + # https://github.com/phpstan/phpstan/issues/8770 + - + message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\EPub3\\\\Style\\\\AbstractStyle is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" + count: 1 + path: tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php + + # https://github.com/phpstan/phpstan/issues/8770 + - + message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\AbstractPart is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" + count: 2 + path: tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist index 09f269a3fa..8d45e95f43 100644 --- a/phpstan.neon.php7.dist +++ b/phpstan.neon.php7.dist @@ -1,5 +1,5 @@ includes: - - phpstan-baseline.neon + - phpstan-baseline.php7.neon - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon parameters: @@ -12,8 +12,6 @@ parameters: - src/PhpWord/Shared/OLERead.php - src/PhpWord/Reader/MsDoc.php - src/PhpWord/Writer/PDF/MPDF.php - - src/PhpWord/Shared/XMLWriter.php - - src/PhpWord/Shared/XMLWriter.withwakeup.php bootstrapFiles: - tests/bootstrap.php treatPhpDocTypesAsCertain: false @@ -31,12 +29,6 @@ parameters: - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' path: src/PhpWord/IOFactory.php - - - message: '#Parameter \#1 \$function of function forward_static_call_array expects callable\(\): mixed, array{.+, string} given.#' - path: src/PhpWord/PhpWord.php - - - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{\$this\(PhpOffice\\PhpWord\\Shared\\ZipArchive\)\|PclZip\|ZipArchive, mixed} given.#' - path: src/PhpWord/Shared/ZipArchive.php - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{PhpOffice\\PhpWord\\Writer\\PDF\\AbstractRenderer, string} given.#' path: src/PhpWord/Writer/PDF.php diff --git a/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php b/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php index 35d65f9238..63c52749e5 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php @@ -383,6 +383,9 @@ public function testWriteTextBreak(): void self::assertEquals($pName, $element->getAttribute('w:val')); } + /** @var string[] */ + protected static $possibleMethods = ['assertMatchesRegularExpression', 'assertRegExp']; + /** * covers ::_writeImage. */ @@ -407,12 +410,16 @@ public function testWriteImage(): void // behind $element = $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:pict/v:shape'); $style = $element->getAttribute('style'); - // @phpstan-ignore-next-line - if (method_exists(self::class, 'assertMatchesRegularExpression')) { - self::assertMatchesRegularExpression('/z\-index:\-[0-9]*/', $style); - } elseif (method_exists(self::class, 'assertRegExp')) { - self::assertRegExp('/z\-index:\-[0-9]*/', $style); - } else { + $found = false; + foreach (self::$possibleMethods as $method) { + if (method_exists(self::class, $method)) { + self::$method('/z\-index:\-[0-9]*/', $style); + $found = true; + + break; + } + } + if (!$found) { self::fail('Unsure how to test regexp'); } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index f9e0ca2388..13dec158da 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -59,7 +59,13 @@ function utf8decode(string $value, string $toEncoding = 'ISO-8859-1'): string return $result === false ? '' : $result; } -if (!method_exists(PHPUnit\Framework\TestCase::class, 'setOutputCallback')) { +/** @param string $methodName */ +function methodFound($methodName): bool +{ + return method_exists(PHPUnit\Framework\TestCase::class, $methodName); +} + +if (!methodFound('setOutputCallback')) { ini_set('error_reporting', (string) E_ALL); set_error_handler('phpunit10ErrorHandler'); } From 898d85ec551fc5791dd4bac88f9c29d0d3b96368 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:28:26 -0700 Subject: [PATCH 061/153] See If We Can Fix Phpstan for Php 7.4 --- phpstan-baseline.php7.neon | 10 ---------- phpstan.neon.php7.dist | 17 ++++++++++++++++- tests/PhpWordTests/AutoloaderTest.php | 3 +-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/phpstan-baseline.php7.neon b/phpstan-baseline.php7.neon index 948d9b58c2..7d77dc120a 100644 --- a/phpstan-baseline.php7.neon +++ b/phpstan-baseline.php7.neon @@ -465,11 +465,6 @@ parameters: count: 6 path: src/PhpWord/Shared/ZipArchive.php - - - message: "#^Parameter \\#1 \\$callback of function call_user_func_array expects callable\\(\\)\\: mixed, array\\{\\$this\\(PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\)\\|PclZip\\|ZipArchive, mixed\\} given\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:\\$zip has unknown class PclZip as its type\\.$#" count: 1 @@ -1360,11 +1355,6 @@ parameters: count: 1 path: tests/PhpWordTests/Style/AbstractStyleTest.php - - - message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|object, class\\-string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Style/AbstractStyleTest.php - - message: "#^Cannot call method setLineHeight\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" count: 1 diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist index 8d45e95f43..d595ac9480 100644 --- a/phpstan.neon.php7.dist +++ b/phpstan.neon.php7.dist @@ -29,9 +29,24 @@ parameters: - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' path: src/PhpWord/IOFactory.php + - + message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Writer\\HTML\\Part\\AbstractPart is not subtype of native type PhpOffice\\PhpWord\\Writer\\HTML\\Part\\Body\|PhpOffice\\PhpWord\\Writer\\HTML\\Part\\Head.#' + path: src/PhpWord/Writer/HTML.php - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{PhpOffice\\PhpWord\\Writer\\PDF\\AbstractRenderer, string} given.#' path: src/PhpWord/Writer/PDF.php - - + - + message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractElement is not subtype of native type .*#' + path: tests/PhpWordTests/Style/AbstractElementTest.php + - + message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractPart is not subtype of native type .*#' + path: tests/PhpWordTests/Style/AbstractPartTest.php + - + message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractStyle is not subtype of native type .*#' + path: tests/PhpWordTests/Style/AbstractStyleTest.php + - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' path: tests/PhpWordTests/Style/AbstractStyleTest.php + - + message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array\{\$this\(PhpOffice\\PhpWord\\Shared\\ZipArchive\)\|PclZip|\ZipArchive, mixed\} given.#' + path: tests/PhpWordTests/Style/AbstractStyleTest.php diff --git a/tests/PhpWordTests/AutoloaderTest.php b/tests/PhpWordTests/AutoloaderTest.php index 076bc8ebca..6f1df1d71c 100644 --- a/tests/PhpWordTests/AutoloaderTest.php +++ b/tests/PhpWordTests/AutoloaderTest.php @@ -30,8 +30,7 @@ public function testRegister(): void { Autoloader::register(); $splFunctions = spl_autoload_functions(); - // @phpstan-ignore-next-line spl_autoload_functions return false < PHP 8.0 - if ($splFunctions === false) { + if (empty($splFunctions)) { $splFunctions = []; } From 923e7a6f3fdb67b09e1feff17ab3473d80267f85 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:34:29 -0700 Subject: [PATCH 062/153] Try again for 7.4 but at least one more will be needed --- phpstan.neon.php7.dist | 6 ------ 1 file changed, 6 deletions(-) diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist index d595ac9480..73b667d03e 100644 --- a/phpstan.neon.php7.dist +++ b/phpstan.neon.php7.dist @@ -35,12 +35,6 @@ parameters: - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{PhpOffice\\PhpWord\\Writer\\PDF\\AbstractRenderer, string} given.#' path: src/PhpWord/Writer/PDF.php - - - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractElement is not subtype of native type .*#' - path: tests/PhpWordTests/Style/AbstractElementTest.php - - - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractPart is not subtype of native type .*#' - path: tests/PhpWordTests/Style/AbstractPartTest.php - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractStyle is not subtype of native type .*#' path: tests/PhpWordTests/Style/AbstractStyleTest.php From 9de70773354bdeb02db3eb94ceb13f5b51965488 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:38:26 -0700 Subject: [PATCH 063/153] Keep Trying 7.4 Phpstan --- phpstan.neon.php7.dist | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist index 73b667d03e..42fa51e6af 100644 --- a/phpstan.neon.php7.dist +++ b/phpstan.neon.php7.dist @@ -35,6 +35,9 @@ parameters: - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{PhpOffice\\PhpWord\\Writer\\PDF\\AbstractRenderer, string} given.#' path: src/PhpWord/Writer/PDF.php + - + message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractElement is not subtype of native type .*#' + path: tests/PhpWordTests/Element/AbstractElementTest.php - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractStyle is not subtype of native type .*#' path: tests/PhpWordTests/Style/AbstractStyleTest.php @@ -44,3 +47,6 @@ parameters: - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array\{\$this\(PhpOffice\\PhpWord\\Shared\\ZipArchive\)\|PclZip|\ZipArchive, mixed\} given.#' path: tests/PhpWordTests/Style/AbstractStyleTest.php + - + message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractPart is not subtype of native type .*#' + path: tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php From 985cff4b60cd3d22db5762029a87c7e5d315f63a Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:51:00 -0700 Subject: [PATCH 064/153] Keep Trying --- phpstan.neon.php7.dist | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist index 42fa51e6af..7b48c04eb1 100644 --- a/phpstan.neon.php7.dist +++ b/phpstan.neon.php7.dist @@ -36,10 +36,10 @@ parameters: message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{PhpOffice\\PhpWord\\Writer\\PDF\\AbstractRenderer, string} given.#' path: src/PhpWord/Writer/PDF.php - - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractElement is not subtype of native type .*#' + message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractElement is not subtype of native type #' path: tests/PhpWordTests/Element/AbstractElementTest.php - - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractStyle is not subtype of native type .*#' + message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractStyle is not subtype of native type #' path: tests/PhpWordTests/Style/AbstractStyleTest.php - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' @@ -48,5 +48,5 @@ parameters: message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array\{\$this\(PhpOffice\\PhpWord\\Shared\\ZipArchive\)\|PclZip|\ZipArchive, mixed\} given.#' path: tests/PhpWordTests/Style/AbstractStyleTest.php - - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractPart is not subtype of native type .*#' + message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractPart is not subtype of native type #' path: tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php From 2e55a7c634e8d46b2e1a48865403aef55e5e7847 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:59:39 -0700 Subject: [PATCH 065/153] Getting Closer --- phpstan.neon.php7.dist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist index 7b48c04eb1..d1d4e71328 100644 --- a/phpstan.neon.php7.dist +++ b/phpstan.neon.php7.dist @@ -39,7 +39,7 @@ parameters: message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractElement is not subtype of native type #' path: tests/PhpWordTests/Element/AbstractElementTest.php - - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractStyle is not subtype of native type #' + message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Style\\AbstractStyle is not subtype of native type #' path: tests/PhpWordTests/Style/AbstractStyleTest.php - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' @@ -48,5 +48,5 @@ parameters: message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array\{\$this\(PhpOffice\\PhpWord\\Shared\\ZipArchive\)\|PclZip|\ZipArchive, mixed\} given.#' path: tests/PhpWordTests/Style/AbstractStyleTest.php - - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractPart is not subtype of native type #' + message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Word2007\\Part\\AbstractPart is not subtype of native type #' path: tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php From f74b583d6628ab2c58e578be06571bc27781462e Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:17:23 -0700 Subject: [PATCH 066/153] Maybe this time? --- phpstan.neon.php7.dist | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist index d1d4e71328..de73d28607 100644 --- a/phpstan.neon.php7.dist +++ b/phpstan.neon.php7.dist @@ -29,6 +29,9 @@ parameters: - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' path: src/PhpWord/IOFactory.php + - + message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed,#' + path: src/PhpWord/Shared/ZipArchive.php - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Writer\\HTML\\Part\\AbstractPart is not subtype of native type PhpOffice\\PhpWord\\Writer\\HTML\\Part\\Body\|PhpOffice\\PhpWord\\Writer\\HTML\\Part\\Head.#' path: src/PhpWord/Writer/HTML.php @@ -45,8 +48,5 @@ parameters: message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' path: tests/PhpWordTests/Style/AbstractStyleTest.php - - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array\{\$this\(PhpOffice\\PhpWord\\Shared\\ZipArchive\)\|PclZip|\ZipArchive, mixed\} given.#' - path: tests/PhpWordTests/Style/AbstractStyleTest.php - - - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Word2007\\Part\\AbstractPart is not subtype of native type #' + message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Writer\\Word2007\\Part\\AbstractPart is not subtype of native type #' path: tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php From 3637da10d7a79bc0eb21497a9cfb25815b807e13 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:43:01 -0700 Subject: [PATCH 067/153] Try to fix Phpstan Php7.2/3 --- .github/workflows/php.yml | 6 +- phpstan-baseline.php73.neon | 1598 +++++++++++++++++++++++++++++++++++ phpstan.neon.php73.dist | 53 ++ 3 files changed, 1656 insertions(+), 1 deletion(-) create mode 100644 phpstan-baseline.php73.neon create mode 100644 phpstan.neon.php73.dist diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 0893d9dd19..cda22f86c6 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -62,7 +62,11 @@ jobs: run: composer install --ansi --prefer-dist --no-interaction --no-progress - name: Run phpstan Php7 - if: matrix.php == '7.2' || matrix.php == '7.3' || matrix.php == '7.4' + if: matrix.php == '7.2' || matrix.php == '7.3' + run: ./vendor/bin/phpstan analyse -c phpstan.neon.php73.dist + + - name: Run phpstan Php7.4 + if: matrix.php == '7.4' run: ./vendor/bin/phpstan analyse -c phpstan.neon.php7.dist - name: Run phpstan Php8 diff --git a/phpstan-baseline.php73.neon b/phpstan-baseline.php73.neon new file mode 100644 index 0000000000..7d77dc120a --- /dev/null +++ b/phpstan-baseline.php73.neon @@ -0,0 +1,1598 @@ +parameters: + ignoreErrors: + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:__call\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement but returns null\\.$#" + count: 1 + path: src/PhpWord/Element/AbstractContainer.php + + - + message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\|null given\\.$#" + count: 1 + path: src/PhpWord/Element/Cell.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\) does not accept null\\.$#" + count: 1 + path: src/PhpWord/Element/Field.php + + - + message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Element/Footnote.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:getArchiveImageSize\\(\\) should return array\\|null but returns array\\|false\\|null\\.$#" + count: 1 + path: src/PhpWord/Element/Image.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:getImageString\\(\\) should return string\\|null but returns string\\|false\\|null\\.$#" + count: 1 + path: src/PhpWord/Element/Image.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:\\$source \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: src/PhpWord/Element/Image.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\OLEObject\\:\\:\\$icon \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: src/PhpWord/Element/OLEObject.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Section\\:\\:addHeader\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Header but returns PhpOffice\\\\PhpWord\\\\Element\\\\Footer\\.$#" + count: 1 + path: src/PhpWord/Element/Section.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Section\\:\\:addHeaderFooter\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Footer but returns PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\.$#" + count: 1 + path: src/PhpWord/Element/Section.php + + - + message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\|PhpOffice\\\\PhpWord\\\\Style\\\\Section\\|string given\\.$#" + count: 1 + path: src/PhpWord/Element/Section.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeAsciiCharacter\\(\\) has parameter \\$code with no type specified\\.$#" + count: 1 + path: src/PhpWord/Escaper/Rtf.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeMultibyteCharacter\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Escaper/Rtf.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeMultibyteCharacter\\(\\) has parameter \\$code with no type specified\\.$#" + count: 1 + path: src/PhpWord/Escaper/Rtf.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createObject\\(\\) should return PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface but returns object\\.$#" + count: 1 + path: src/PhpWord/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createReader\\(\\) should return PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface but returns PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface\\.$#" + count: 1 + path: src/PhpWord/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createWriter\\(\\) should return PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface but returns PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface\\.$#" + count: 1 + path: src/PhpWord/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:getDefaultFontSize\\(\\) should return int but returns float\\|int\\.$#" + count: 1 + path: src/PhpWord/PhpWord.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\AbstractReader\\:\\:openFile\\(\\) should return resource but return statement is missing\\.$#" + count: 1 + path: src/PhpWord/Reader/AbstractReader.php + + - + message: "#^Parameter \\#2 \\$html of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:addHtml\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpWord/Reader/HTML.php + + - + message: "#^Parameter \\#2 \\$contextNode of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLReader\\:\\:getElements\\(\\) expects DOMElement\\|null, DOMNode\\|null given\\.$#" + count: 2 + path: src/PhpWord/Reader/ODText/Content.php + + - + message: "#^Parameter \\#2 \\$depth of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addTitle\\(\\) expects int, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/ODText/Content.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$rtf \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: src/PhpWord/Reader/RTF.php + + - + message: "#^Cannot call method setStyleByArray\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" + count: 1 + path: src/PhpWord/Reader/RTF/Document.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$phpWord is never read, only written\\.$#" + count: 1 + path: src/PhpWord/Reader/RTF/Document.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\:\\:load\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Reader/ReaderInterface.php + + - + message: "#^Binary operation \"/\" between string\\|null and 2 results in an error\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Call to an undefined method DOMNode\\:\\:getAttribute\\(\\)\\.$#" + count: 2 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:getHeadingDepth\\(\\) never returns float so it can be removed from the return type\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:getHeadingDepth\\(\\) should return float\\|int\\|null but returns string\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:read\\(\\) has no return type specified\\.$#" + 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 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#1 \\$height of method PhpOffice\\\\PhpWord\\\\Element\\\\Table\\:\\:addRow\\(\\) expects int\\|null, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setRelationId\\(\\) expects int, string\\|null given\\.$#" + count: 2 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#1 \\$width of method PhpOffice\\\\PhpWord\\\\Element\\\\Row\\:\\:addCell\\(\\) expects int\\|null, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#2 \\$contextNode of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLReader\\:\\:getAttribute\\(\\) expects DOMElement\\|null, DOMNode\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#2 \\$depth of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addTitle\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and DOMElement will always evaluate to false\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#2 \\$relationId of method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\Footnotes\\:\\:getElement\\(\\) expects int, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Footnotes.php + + - + message: "#^Parameter \\#3 \\$levelId of method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\Numbering\\:\\:readLevel\\(\\) expects int, string\\|null given\\.$#" + count: 2 + path: src/PhpWord/Reader/Word2007/Numbering.php + + - + message: "#^Parameter \\#1 \\$comments of method PhpOffice\\\\PhpWord\\\\ComplexType\\\\TrackChangesView\\:\\:setComments\\(\\) expects bool\\|null, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Settings.php + + - + message: "#^Parameter \\#1 \\$consecutiveHyphenLimit of method PhpOffice\\\\PhpWord\\\\Metadata\\\\Settings\\:\\:setConsecutiveHyphenLimit\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Settings.php + + - + message: "#^Parameter \\#1 \\$hyphenationZone of method PhpOffice\\\\PhpWord\\\\Metadata\\\\Settings\\:\\:setHyphenationZone\\(\\) expects float\\|int\\|null, string given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Settings.php + + - + message: "#^Parameter \\#1 \\$filename of function parse_ini_file expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpWord/Settings.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\AbstractEnum\\:\\:getConstants\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/AbstractEnum.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\AbstractEnum\\:\\:\\$constCacheArray has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/AbstractEnum.php + + - + message: "#^Binary operation \"/\" between string and 10 results in an error\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:angleToDegree\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:cssToPoint\\(\\) should return float\\|null but returns string\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:htmlToRgb\\(\\) should return array but returns false\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:inchToEmu\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:pixelToEmu\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$centimeter of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:cmToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$inch of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:inchToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$pica of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:picaToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$pixel of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:pixelToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:angleToDegrees\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Drawing.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:emuToPixels\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Drawing.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:pixelsToEmu\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Drawing.php + + - + message: "#^Call to an undefined method DOMNode\\:\\:getAttribute\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Cannot call method setStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:addHtml\\(\\) has parameter \\$options with no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:filterOutNonInheritedStyles\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapBorderColor\\(\\) has parameter \\$cssBorderColor with no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapBorderColor\\(\\) has parameter \\$styles with no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapListType\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseLink\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseList\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseRuby\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:recursiveParseStylesInHierarchy\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Parameter \\#2 \\$element of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseNode\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\|PhpOffice\\\\PhpWord\\\\Element\\\\Row\\|PhpOffice\\\\PhpWord\\\\Element\\\\Table given\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$listIndex has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$options has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$xpath has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$encryptionMatrix has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$initialCodeArray has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$passwordMaxLength has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:getData\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/PhpWord/Shared/XMLWriter.php + + - + message: "#^Call to method add\\(\\) on an unknown class PclZip\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method addFromString\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method close\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method extract\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method extractByIndex\\(\\) on an unknown class PclZip\\.$#" + count: 3 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method extractTo\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method getFromName\\(\\) on an unknown class PclZip\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method listContent\\(\\) on an unknown class PclZip\\.$#" + count: 3 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_ADD_PATH not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_EXTRACT_AS_STRING not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_PATH not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_REMOVE_PATH not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Instantiated class PclZip not found\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:open\\(\\) should return bool but returns int\\|true\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^PHPDoc tag @var for variable \\$zip contains unknown class PclZip\\.$#" + count: 6 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:\\$zip has unknown class PclZip as its type\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addFontStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addLinkStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addNumberingStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Numbering but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addParagraphStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addTableStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Table but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addTitleStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Call to an undefined method object\\:\\:setStyleByArray\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Style/AbstractStyle.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setFloatVal\\(\\) should return float\\|null but returns float\\|int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/AbstractStyle.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setNumericVal\\(\\) should return float\\|int\\|null but returns float\\|int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/AbstractStyle.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Border\\:\\:getBorderSize\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: src/PhpWord/Style/Border.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\:\\:getBgColor\\(\\) should return string but returns null\\.$#" + count: 1 + path: src/PhpWord/Style/Cell.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\:\\:setUnit\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Cell.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:getMajorTickPosition\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setCategoryAxisTitle\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setValueAxisTitle\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setValueLabelPosition\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:showAxisLabels\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:showGridY\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setAllCaps\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setBgColor\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Table but return statement is missing\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setDoubleStrikethrough\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSmallCaps\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setStrikethrough\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSubScript\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSuperScript\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Line\\:\\:\\$weight \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Line.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\ListItem\\:\\:getListTypeStyle\\(\\) should return array but empty return statement found\\.$#" + count: 1 + path: src/PhpWord/Style/ListItem.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\ListItem\\:\\:getListTypeStyle\\(\\) should return array but return statement is missing\\.$#" + count: 1 + path: src/PhpWord/Style/ListItem.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\:\\:setStyleValue\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Paragraph.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Section\\:\\:setSettingValue\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Section but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Section.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\TOC\\:\\:setTabLeader\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\TOC but returns PhpOffice\\\\PhpWord\\\\Style\\\\Tab\\.$#" + count: 1 + path: src/PhpWord/Style/TOC.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\TOC\\:\\:setTabPos\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\TOC but returns PhpOffice\\\\PhpWord\\\\Style\\\\Tab\\.$#" + count: 1 + path: src/PhpWord/Style/TOC.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideHColor\\(\\) should return string but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideHSize\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideVColor\\(\\) should return string but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideVSize\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderSize\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginBottom\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginLeft\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginRight\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginTop\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$bottomFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$leftFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$rightFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$tblpX \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$tblpY \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$topFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Cannot access offset 'end' on array\\\\|true\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Cannot access offset 'start' on array\\\\|true\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$imageMimeType with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$imgPath with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$partFileName with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$rid with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$baseValue with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$defaultValue with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$inlineValue with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$actualHeight with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$actualWidth with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$height with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$width with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getImageArgs\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getImageArgs\\(\\) has parameter \\$varNameWithArgs with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getNextRelationsIndex\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getNextRelationsIndex\\(\\) has parameter \\$documentPartName with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has parameter \\$replaceImage with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has parameter \\$varInlineArgs with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Parameter \\#1 \\$element of method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\Chart\\:\\:setElement\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\Chart, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement given\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$macroClosingChars has no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$macroOpeningChars has no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\HTML\\\\Element\\\\AbstractElement\\:\\:write\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/HTML/Element/AbstractElement.php + + - + message: "#^Variable \\$row in PHPDoc tag @var does not match assigned variable \\$rowStyle\\.$#" + count: 1 + path: src/PhpWord/Writer/HTML/Element/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Field\\:\\:writeDefault\\(\\) has parameter \\$type with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Field.php + + - + message: "#^Variable \\$row in PHPDoc tag @var does not match any variable in the foreach loop\\: \\$cell$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\AbstractElement\\:\\:replaceTabs\\(\\) has parameter \\$text with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\AbstractElement\\:\\:replaceTabs\\(\\) has parameter \\$xmlWriter with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Text\\:\\:writeChangeInsertion\\(\\) has parameter \\$start with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Text.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getParagraphStyle\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/TextRun.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setColumnWidths\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Parameter \\#1 \\$container of method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Content\\:\\:collectTrackedChanges\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement given\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Content\\:\\:\\$imageParagraphStyles has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 2 + path: src/PhpWord/Writer/ODText/Part/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Styles\\:\\:cvttwiptostr\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Styles.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:\\$renderer \\(PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\AbstractRenderer\\) does not accept object\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:loadHtml\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:output\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:render\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:setPaper\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Class PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF referenced with incorrect case\\: PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\Dompdf\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:createExternalWriterInstance\\(\\) should return PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF but returns Dompdf\\\\Dompdf\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Binary operation \"\\+\" between int\\|string and 1 results in an error\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Style\\\\Font\\:\\:setNameIndex\\(\\) expects int, int\\|string given\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|null\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|null\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept null\\.$#" + count: 2 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:write\\(\\) should return string but empty return statement found\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writeDate\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writeNumpages\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writePage\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getImageStringData\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Image.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getStyle\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Image.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getSource\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Link.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Link.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 2 + path: src/PhpWord/Writer/RTF/Part/Document.php + + - + message: "#^Binary operation \"\\+\" between int\\|string and 1 results in an error\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Style/Border.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\AbstractElement\\:\\:write\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\Field\\:\\:buildPropertiesAndOptions\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/Field.php + + - + message: "#^Parameter \\#1 \\$content of method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\AbstractElement\\:\\:writeText\\(\\) expects string, bool\\|int\\|string given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/FormField.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, bool\\|int\\|string given\\.$#" + count: 3 + path: src/PhpWord/Writer/Word2007/Element/FormField.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int given\\.$#" + count: 4 + path: src/PhpWord/Writer/Word2007/Element/FormField.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\ParagraphAlignment\\:\\:\\$attributes has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\ParagraphAlignment\\:\\:\\$name has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php + + - + message: "#^Parameter \\#2 \\$content of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, bool\\|int\\|string given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/SDT.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int\\<100000000, 999999999\\> given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/SDT.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\TableAlignment\\:\\:\\$attributes has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/TableAlignment.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\TableAlignment\\:\\:\\$name has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/TableAlignment.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:addBookmark\\(\\) invoked with 0 parameters, 1 required\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/Title.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\Chart\\:\\:writeAxisTitle\\(\\) has parameter \\$title with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Part/Chart.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int given\\.$#" + count: 9 + path: src/PhpWord/Writer/Word2007/Part/Chart.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int\\<0, max\\> given\\.$#" + count: 4 + path: src/PhpWord/Writer/Word2007/Part/Chart.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, int given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Part/Rels.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Style\\\\AbstractStyle\\:\\:write\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Style/AbstractStyle.php + + - + message: "#^Call to an undefined method object\\:\\:read\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractTestReader.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\AbstractTestReader\\:\\:\\$parts has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractTestReader.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getBaseUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteBmpImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteGifImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:\\$httpServer has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbedded.php + + - + message: "#^Parameter \\#1 \\$width of class PhpOffice\\\\PhpWord\\\\Element\\\\Cell constructor expects int\\|null, string given\\.$#" + count: 2 + path: tests/PhpWordTests/Element/CellTest.php + + - + message: "#^Parameter \\#2 \\$style of class PhpOffice\\\\PhpWord\\\\Element\\\\Cell constructor expects array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\|null, int given\\.$#" + count: 2 + path: tests/PhpWordTests/Element/CellTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$createFunction with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$extension with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$imageFunction with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$imageQuality with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$source with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$type with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Parameter \\#1 \\$source of class PhpOffice\\\\PhpWord\\\\Element\\\\Image constructor expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Parameter \\#3 \\$watermark of class PhpOffice\\\\PhpWord\\\\Element\\\\Image constructor expects bool, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Parameter \\#2 \\$style of class PhpOffice\\\\PhpWord\\\\Element\\\\Section constructor expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, PhpOffice\\\\PhpWord\\\\Style\\\\Section given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/SectionTest.php + + - + message: "#^Parameter \\#1 \\$text of class PhpOffice\\\\PhpWord\\\\Element\\\\Title constructor expects PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string, PhpOffice\\\\PhpWord\\\\Element\\\\PageBreak given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/TitleTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:escapestring\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:escapestring\\(\\) has parameter \\$str with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:expect\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:expect\\(\\) has parameter \\$str with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Parameter \\#1 \\$expected of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) expects class\\-string\\, string given\\.$#" + count: 1 + path: tests/PhpWordTests/IOFactoryTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/IOFactoryTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:undefinedMethod\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/PhpWordTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getRows\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/ElementTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/ElementTest.php + + - + message: "#^Cannot access offset 0 on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/ElementTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getElement\\(\\)\\.$#" + count: 2 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Cannot call method getElement\\(\\) on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string\\.$#" + count: 3 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Cannot call method isBold\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Variable \\$endnote in PHPDoc tag @var does not match assigned variable \\$documentEndnote\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Variable \\$footnote in PHPDoc tag @var does not match assigned variable \\$documentFootnote\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Cannot access offset 0 on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\.$#" + count: 2 + path: tests/PhpWordTests/Reader/Word2007/StyleTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$compatibility has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultFontName has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultFontSize has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultPaper has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$measurementUnit has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$pdfRendererName has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$pdfRendererPath has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$tempDir has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$zipClass has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Cannot call method getStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Shared/HtmlTest.php + + - + message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Text\\:\\:numberFormat\\(\\) expects float, string given\\.$#" + count: 2 + path: tests/PhpWordTests/Shared/TextTest.php + + - + message: "#^Parameter \\#2 \\$locale of function setlocale expects array\\|string\\|null, int given\\.$#" + count: 1 + path: tests/PhpWordTests/Shared/XMLWriterTest.php + + - + message: "#^Parameter \\#2 \\$locale of function setlocale expects string\\|null, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Shared/XMLWriterTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Style\\\\AbstractStyleTest\\:\\:callProtectedMethod\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Style/AbstractStyleTest.php + + - + message: "#^Cannot call method setLineHeight\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Style/FontTest.php + + - + message: "#^Parameter \\#1 \\$type of class PhpOffice\\\\PhpWord\\\\Style\\\\Font constructor expects string, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/FontTest.php + + - + message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setStyleValue\\(\\) expects array\\|int\\|string, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/FontTest.php + + - + message: "#^Cannot call method setLineHeight\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Style/ParagraphTest.php + + - + message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setStyleValue\\(\\) expects array\\|int\\|string, bool given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/RowTest.php + + - + message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\Section\\:\\:setSettingValue\\(\\) expects array\\|int\\|string, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/SectionTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" + count: 2 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:AddFromString\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Cannot access offset 'end' on array\\\\|bool\\.$#" + count: 2 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Cannot access offset 'start' on array\\\\|bool\\.$#" + count: 2 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\) expects string, string\\|false given\\.$#" + count: 6 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringNotContainsString\\(\\) expects string, string\\|false given\\.$#" + count: 4 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Part \\$documentZip \\(ZipArchive\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Part \\$templateZip \\(ZipArchive\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\TestableTemplateProcesor\\:\\:__construct\\(\\) has parameter \\$mainPart with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/TestableTemplateProcesor.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\TestableTemplateProcesor\\:\\:__construct\\(\\) has parameter \\$settingsPart with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/TestableTemplateProcesor.php + + - + message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" + count: 9 + path: tests/PhpWordTests/Writer/HTML/ElementTest.php + + - + message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" + count: 2 + path: tests/PhpWordTests/Writer/HTML/Element/RubyTest.php + + - + message: "#^Cannot call method item\\(\\) on DOMNodeList\\\\|false\\.$#" + count: 11 + path: tests/PhpWordTests/Writer/HTML/ElementTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 3 + path: tests/PhpWordTests/Writer/HTML/Element/PageBreakTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\ODText\\\\Style\\\\FontTest\\:\\:providerAllNamedColors\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/ODText/Style/FontTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" + count: 2 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getOrientation\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getPaperSize\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getTempDir\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setFont\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setOrientation\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setPaperSize\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setTempDir\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 3 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/MPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/MPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 2 + path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDFTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/ElementTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/ElementTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/StyleTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/StyleTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\Writer\\\\Word2007\\\\Element\\\\ChartTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/ElementTest.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/StyleTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getElement\\(\\) should return DOMElement\\|null but returns DOMNode\\|null\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getNodeList\\(\\) should return DOMNodeList\\ but returns DOMNodeList\\\\|false\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$path \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$xpath \\(DOMXPath\\) does not accept null\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + # https://github.com/phpstan/phpstan/issues/8770 + - + message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\EPub3\\\\Style\\\\AbstractStyle is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" + count: 1 + path: tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php + + # https://github.com/phpstan/phpstan/issues/8770 + - + message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\AbstractPart is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" + count: 2 + path: tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php diff --git a/phpstan.neon.php73.dist b/phpstan.neon.php73.dist new file mode 100644 index 0000000000..7a6549b855 --- /dev/null +++ b/phpstan.neon.php73.dist @@ -0,0 +1,53 @@ +includes: + - phpstan-baseline.php7.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon +parameters: + level: 7 + paths: + - src/ + - tests/ + excludePaths: + - */pclzip.lib.php + - src/PhpWord/Shared/OLERead.php + - src/PhpWord/Reader/MsDoc.php + - src/PhpWord/Writer/PDF/MPDF.php + bootstrapFiles: + - tests/bootstrap.php + reportUnmatchedIgnoredErrors: false + treatPhpDocTypesAsCertain: false + ignoreErrors: + - + identifier: missingType.iterableValue + + ## <=PHP7.4 + - + message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' + path: src/PhpWord/Element/AbstractContainer.php + - + message: '#Parameter \#1 \$function of function call_user_func expects callable\(\): mixed, string given.#' + path: src/PhpWord/Element/Image.php + - + message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' + path: src/PhpWord/IOFactory.php + - + message: 'Cannot instantiate interface#' + path: src/PhpWord/IOFactory.php + - + message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed,#' + path: src/PhpWord/Shared/ZipArchive.php + - + message: '#Cannot call method getText#' + path: src/PhpWord/Writer/ODText/Part/Content.php + - + message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{PhpOffice\\PhpWord\\Writer\\PDF\\AbstractRenderer, string} given.#' + path: src/PhpWord/Writer/PDF.php + - + message: '#only iterables are supported#' + path: tests/PhpWordTests/Shared/ZipArchiveTest.php + - + message: '#only iterables are supported#' + path: tests/PhpWordTests/TestHelperDOCX.php + - + message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' + path: tests/PhpWordTests/Style/AbstractStyleTest.php From 2a5f1d4de9abc7fd37511a40d026e0e4730193f5 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:46:20 -0700 Subject: [PATCH 068/153] Fix Typo --- phpstan.neon.php73.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon.php73.dist b/phpstan.neon.php73.dist index 7a6549b855..ce8fc2f5de 100644 --- a/phpstan.neon.php73.dist +++ b/phpstan.neon.php73.dist @@ -31,7 +31,7 @@ parameters: message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' path: src/PhpWord/IOFactory.php - - message: 'Cannot instantiate interface#' + message: '#Cannot instantiate interface#' path: src/PhpWord/IOFactory.php - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed,#' From 80976c251f7b994395fd02c7b7ec2c8ea5fe0514 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:49:56 -0700 Subject: [PATCH 069/153] This might do it --- phpstan.neon.php73.dist | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phpstan.neon.php73.dist b/phpstan.neon.php73.dist index ce8fc2f5de..92a9441415 100644 --- a/phpstan.neon.php73.dist +++ b/phpstan.neon.php73.dist @@ -36,6 +36,9 @@ parameters: - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed,#' path: src/PhpWord/Shared/ZipArchive.php + - + message: '#only iterables are supported#' + path: src/PhpWord/Writer/AbstractWriter.php - message: '#Cannot call method getText#' path: src/PhpWord/Writer/ODText/Part/Content.php From 5472a46cb9f5062886f33a744722f4f4d056b645 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Sep 2025 15:01:47 -0700 Subject: [PATCH 070/153] Fix Php8.5 Deprecation --- src/PhpWord/Writer/RTF/Element/Table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/RTF/Element/Table.php b/src/PhpWord/Writer/RTF/Element/Table.php index 8f6f134dd5..7a4de347e9 100644 --- a/src/PhpWord/Writer/RTF/Element/Table.php +++ b/src/PhpWord/Writer/RTF/Element/Table.php @@ -215,7 +215,7 @@ private function writeCellBorder(string $prefix, ?string $borderStyle, int $bord * \brdremboss Emboss border. * \brdrengrave Engrave border. */ - $content .= self::MAP_BORDER_TYPE[$borderStyle] ?? '\brdrs'; + $content .= self::MAP_BORDER_TYPE[$borderStyle ?? ''] ?? '\brdrs'; // \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. From 1017979823810a20fe890396a61367f38ebf7a60 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 9 Sep 2025 08:14:39 -0700 Subject: [PATCH 071/153] Prep In Case Php8.5 Goes Ahead With Deprecating __wakeup --- phpstan.neon.dist | 4 +- phpstan.neon.php7.dist | 4 +- phpstan.neon.php73.dist | 4 +- src/PhpWord/Shared/XMLWriter.php | 213 +----------------- .../Shared/XMLWriter.withoutwakeup.php | 197 ++++++++++++++++ src/PhpWord/Shared/XMLWriter.withwakeup.php | 206 +++++++++++++++++ .../Element/AbstractElementTest.php | 15 +- tests/PhpWordTests/Shared/XMLReaderTest.php | 15 +- 8 files changed, 429 insertions(+), 229 deletions(-) create mode 100644 src/PhpWord/Shared/XMLWriter.withoutwakeup.php create mode 100644 src/PhpWord/Shared/XMLWriter.withwakeup.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 46d77b13d4..b45486e4e6 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -9,8 +9,10 @@ parameters: - tests/ excludePaths: - */pclzip.lib.php - - src/PhpWord/Shared/OLERead.php - src/PhpWord/Reader/MsDoc.php + - src/PhpWord/Shared/OLERead.php + - src/PhpWord/Shared/XMLWriter.php + - src/PhpWord/Shared/XMLWriter.withwakeup.php - src/PhpWord/Writer/PDF/MPDF.php bootstrapFiles: - tests/bootstrap.php diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist index de73d28607..a265040285 100644 --- a/phpstan.neon.php7.dist +++ b/phpstan.neon.php7.dist @@ -9,8 +9,10 @@ parameters: - tests/ excludePaths: - */pclzip.lib.php - - src/PhpWord/Shared/OLERead.php - src/PhpWord/Reader/MsDoc.php + - src/PhpWord/Shared/OLERead.php + - src/PhpWord/Shared/XMLWriter.php + - src/PhpWord/Shared/XMLWriter.withoutwakeup.php - src/PhpWord/Writer/PDF/MPDF.php bootstrapFiles: - tests/bootstrap.php diff --git a/phpstan.neon.php73.dist b/phpstan.neon.php73.dist index 92a9441415..9f5b0e67b5 100644 --- a/phpstan.neon.php73.dist +++ b/phpstan.neon.php73.dist @@ -9,8 +9,10 @@ parameters: - tests/ excludePaths: - */pclzip.lib.php - - src/PhpWord/Shared/OLERead.php - src/PhpWord/Reader/MsDoc.php + - src/PhpWord/Shared/OLERead.php + - src/PhpWord/Shared/XMLWriter.php + - src/PhpWord/Shared/XMLWriter.withoutwakeup.php - src/PhpWord/Writer/PDF/MPDF.php bootstrapFiles: - tests/bootstrap.php diff --git a/src/PhpWord/Shared/XMLWriter.php b/src/PhpWord/Shared/XMLWriter.php index 46f2b1eaac..3e7657dee3 100644 --- a/src/PhpWord/Shared/XMLWriter.php +++ b/src/PhpWord/Shared/XMLWriter.php @@ -1,212 +1,7 @@ hash = spl_object_id($this); - // Open temporary storage - if ($pTemporaryStorage == self::STORAGE_MEMORY) { - $this->openMemory(); - } else { - if (!$pTemporaryStorageDir || !is_dir($pTemporaryStorageDir)) { - $pTemporaryStorageDir = sys_get_temp_dir(); - } - // Create temporary filename - $this->tempFileName = @tempnam($pTemporaryStorageDir, 'xml'); - - // Open storage - $this->openUri($this->tempFileName); - } - - if ($compatibility) { - $this->setIndent(false); - $this->setIndentString(''); - } else { - $this->setIndent(true); - $this->setIndentString(' '); - } - } - - /** - * Destructor. - */ - public function __destruct() - { - // Unlink temporary files - if (empty($this->tempFileName)) { - return; - } - // This is needed only for Php7.1/2/3. - if ($this->hash !== spl_object_id($this)) { - throw new WordException('Unserialize not permitted1'); // @codeCoverageIgnore - } - @unlink($this->tempFileName); - } - - /** @codeCoverageIgnore */ - public function __wakeup(): void - { - $this->tempFileName = ''; - - throw new WordException('Unserialize not permitted2'); - } - - /** - * Unserialization can cause security exploit - don't allow it. - * __wakeup could also be used, but that's compile-time-deprecated for 8.5. - * But Php7.1/2/3 don't recognize unserialize. So, experiment for now. - * - * @param mixed[] $data - */ - public function __unserialize(array $data): void - { - $this->tempFileName = ''; - - throw new WordException('Unserialize not permitted3'); - } - - /** - * Get written data. - * - * @return string - */ - public function getData() - { - if ($this->tempFileName == '') { - return $this->outputMemory(true); - } - - $this->flush(); - - return file_get_contents($this->tempFileName); - } - - /** - * Write simple element and attribute(s) block. - * - * There are two options: - * 1. If the `$attributes` is an array, then it's an associative array of attributes - * 2. If not, then it's a simple attribute-value pair - * - * @param string $element - * @param array|string $attributes - * @param string $value - */ - public function writeElementBlock($element, $attributes, $value = null): void - { - $this->startElement($element); - if (!is_array($attributes)) { - $attributes = [$attributes => $value]; - } - foreach ($attributes as $attribute => $value) { - $this->writeAttribute($attribute, $value); - } - $this->endElement(); - } - - /** - * Write element if ... - * - * @param bool $condition - * @param string $element - * @param string $attribute - * @param mixed $value - */ - public function writeElementIf($condition, $element, $attribute = null, $value = null): void - { - if ($condition == true) { - if (null === $attribute) { - $this->writeElement($element, $value); - } else { - $this->startElement($element); - $this->writeAttribute($attribute, $value); - $this->endElement(); - } - } - } - - /** - * Write attribute if ... - * - * @param bool $condition - * @param string $attribute - * @param mixed $value - */ - public function writeAttributeIf($condition, $attribute, $value): void - { - if ($condition == true) { - $this->writeAttribute($attribute, $value); - } - } - - /** - * @param string $name - * @param mixed $value - */ - public function writeAttribute($name, $value): bool - { - if (is_float($value)) { - $value = (string) $value; - } - - return parent::writeAttribute($name, $value ?? ''); - } +if (\PHP_VERSION_ID < 80300) { + require_once __DIR__ . '/XMLWriter.withwakeup.php'; +} else { + require_once __DIR__ . '/XMLWriter.withoutwakeup.php'; } diff --git a/src/PhpWord/Shared/XMLWriter.withoutwakeup.php b/src/PhpWord/Shared/XMLWriter.withoutwakeup.php new file mode 100644 index 0000000000..6b3076ab54 --- /dev/null +++ b/src/PhpWord/Shared/XMLWriter.withoutwakeup.php @@ -0,0 +1,197 @@ +hash = spl_object_id($this); + // Open temporary storage + if ($pTemporaryStorage == self::STORAGE_MEMORY) { + $this->openMemory(); + } else { + if (!$pTemporaryStorageDir || !is_dir($pTemporaryStorageDir)) { + $pTemporaryStorageDir = sys_get_temp_dir(); + } + // Create temporary filename + $this->tempFileName = @tempnam($pTemporaryStorageDir, 'xml'); + + // Open storage + $this->openUri($this->tempFileName); + } + + if ($compatibility) { + $this->setIndent(false); + $this->setIndentString(''); + } else { + $this->setIndent(true); + $this->setIndentString(' '); + } + } + + /** + * Destructor. + */ + public function __destruct() + { + if ($this->hash !== spl_object_id($this)) { + throw new WordException('Unserialize not permitted4'); + } + // Unlink temporary files + if (empty($this->tempFileName)) { + return; + } + @unlink($this->tempFileName); + } + + /** @param mixed[] $data */ + public function __unserialize(array $data): void + { + $this->tempFileName = ''; + + throw new WordException('Unserialize not permitted5'); + } + + /** + * Get written data. + * + * @return string + */ + public function getData() + { + if ($this->tempFileName == '') { + return $this->outputMemory(true); + } + + $this->flush(); + + return (string) file_get_contents($this->tempFileName); + } + + /** + * Write simple element and attribute(s) block. + * + * There are two options: + * 1. If the `$attributes` is an array, then it's an associative array of attributes + * 2. If not, then it's a simple attribute-value pair + * + * @param string $element + * @param array|string $attributes + * @param string $value + */ + public function writeElementBlock($element, $attributes, $value = null): void + { + $this->startElement($element); + if (!is_array($attributes)) { + $attributes = [$attributes => $value]; + } + foreach ($attributes as $attribute => $value) { + $this->writeAttribute($attribute, $value); + } + $this->endElement(); + } + + /** + * Write element if ... + * + * @param bool $condition + * @param string $element + * @param string $attribute + * @param mixed $value + */ + public function writeElementIf($condition, $element, $attribute = null, $value = null): void + { + if ($condition == true) { + if (null === $attribute) { + $this->writeElement($element, $value); + } else { + $this->startElement($element); + $this->writeAttribute($attribute, $value); + $this->endElement(); + } + } + } + + /** + * Write attribute if ... + * + * @param bool $condition + * @param string $attribute + * @param mixed $value + */ + public function writeAttributeIf($condition, $attribute, $value): void + { + if ($condition == true) { + $this->writeAttribute($attribute, $value); + } + } + + /** + * @param string $name + * @param mixed $value + */ + public function writeAttribute($name, $value): bool + { + if (is_float($value)) { + $value = (string) $value; + } + + return parent::writeAttribute($name, $value ?? ''); + } +} diff --git a/src/PhpWord/Shared/XMLWriter.withwakeup.php b/src/PhpWord/Shared/XMLWriter.withwakeup.php new file mode 100644 index 0000000000..e772315883 --- /dev/null +++ b/src/PhpWord/Shared/XMLWriter.withwakeup.php @@ -0,0 +1,206 @@ +hash = spl_object_id($this); + // Open temporary storage + if ($pTemporaryStorage == self::STORAGE_MEMORY) { + $this->openMemory(); + } else { + if (!$pTemporaryStorageDir || !is_dir($pTemporaryStorageDir)) { + $pTemporaryStorageDir = sys_get_temp_dir(); + } + // Create temporary filename + $this->tempFileName = @tempnam($pTemporaryStorageDir, 'xml'); + + // Open storage + $this->openUri($this->tempFileName); + } + + if ($compatibility) { + $this->setIndent(false); + $this->setIndentString(''); + } else { + $this->setIndent(true); + $this->setIndentString(' '); + } + } + + /** + * Destructor. + */ + public function __destruct() + { + if ($this->hash !== spl_object_id($this)) { + throw new WordException('Unserialize not permitted1'); + } + // Unlink temporary files + if (empty($this->tempFileName)) { + return; + } + @unlink($this->tempFileName); + } + + public function __wakeup(): void + { + $this->tempFileName = ''; + + throw new WordException('Unserialize not permitted2'); + } + + /** @param mixed[] $data */ + public function __unserialize(array $data): void + { + $this->tempFileName = ''; + + throw new WordException('Unserialize not permitted3'); + } + + /** + * Get written data. + * + * @return string + */ + public function getData() + { + if ($this->tempFileName == '') { + return $this->outputMemory(true); + } + + $this->flush(); + + return (string) file_get_contents($this->tempFileName); + } + + /** + * Write simple element and attribute(s) block. + * + * There are two options: + * 1. If the `$attributes` is an array, then it's an associative array of attributes + * 2. If not, then it's a simple attribute-value pair + * + * @param string $element + * @param array|string $attributes + * @param string $value + */ + public function writeElementBlock($element, $attributes, $value = null): void + { + $this->startElement($element); + if (!is_array($attributes)) { + $attributes = [$attributes => $value]; + } + foreach ($attributes as $attribute => $value) { + $this->writeAttribute($attribute, $value); + } + $this->endElement(); + } + + /** + * Write element if ... + * + * @param bool $condition + * @param string $element + * @param string $attribute + * @param mixed $value + */ + public function writeElementIf($condition, $element, $attribute = null, $value = null): void + { + if ($condition == true) { + if (null === $attribute) { + $this->writeElement($element, $value); + } else { + $this->startElement($element); + $this->writeAttribute($attribute, $value); + $this->endElement(); + } + } + } + + /** + * Write attribute if ... + * + * @param bool $condition + * @param string $attribute + * @param mixed $value + */ + public function writeAttributeIf($condition, $attribute, $value): void + { + if ($condition == true) { + $this->writeAttribute($attribute, $value); + } + } + + /** + * @param string $name + * @param mixed $value + */ + public function writeAttribute($name, $value): bool + { + if (is_float($value)) { + $value = (string) $value; + } + + return parent::writeAttribute($name, $value ?? ''); + } +} diff --git a/tests/PhpWordTests/Element/AbstractElementTest.php b/tests/PhpWordTests/Element/AbstractElementTest.php index 9ce05750b8..f1819ecc76 100644 --- a/tests/PhpWordTests/Element/AbstractElementTest.php +++ b/tests/PhpWordTests/Element/AbstractElementTest.php @@ -25,14 +25,17 @@ */ class AbstractElementTest extends \PHPUnit\Framework\TestCase { + /** @var string */ + protected static $method = 'getMockForAbstractClass'; + /** * Test set/get element index. */ public function testElementIndex(): void { - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $stub = $this->getMockForAbstractClass(AbstractElement::class); + $method = self::$method; + if (method_exists($this, $method)) { + $stub = $this->$method(AbstractElement::class); } else { /** @var AbstractElement $stub */ $stub = new class() extends AbstractElement { @@ -48,9 +51,9 @@ public function testElementIndex(): void */ public function testElementId(): void { - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $stub = $this->getMockForAbstractClass(AbstractElement::class); + $method = self::$method; + if (method_exists($this, $method)) { + $stub = $this->$method(AbstractElement::class); } else { /** @var AbstractElement $stub */ $stub = new class() extends AbstractElement { diff --git a/tests/PhpWordTests/Shared/XMLReaderTest.php b/tests/PhpWordTests/Shared/XMLReaderTest.php index 18750b5ef9..977c9235e6 100644 --- a/tests/PhpWordTests/Shared/XMLReaderTest.php +++ b/tests/PhpWordTests/Shared/XMLReaderTest.php @@ -137,19 +137,12 @@ public function testReturnNullOnNonExistingNode(): void /** * Test that xpath fails if custom namespace is not registered. */ - public function testShouldThrowExceptionIfNamespaceIsNotKnown(): void + public function testShouldReturnFalseIfNamespaceIsNotKnown(): void { - try { - $reader = new XMLReader(); - $reader->getDomFromString('AAA'); + $reader = new XMLReader(); + $reader->getDomFromString('AAA'); - self::assertTrue($reader->elementExists('/element/test:child')); - self::assertEquals('AAA', $reader->getElement('/element/test:child')->textContent); - self::fail(); - } catch (Exception $e) { - // @phpstan-ignore-next-line - self::assertTrue(true); - } + self::assertFalse($reader->elementExists('/element/test:child')); } /** From 3b67bdd3f2f3089b535c6681617892cedb49329c Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:32:13 -0700 Subject: [PATCH 072/153] Phpstan Php7.3 Report Unmatched Ignored Errors Probably fixes 7.2 as well. Might cause minor breaks for 7.4. --- phpstan-baseline.php73.neon | 12 ------- phpstan.neon.php73.dist | 4 +-- src/PhpWord/Shared/XMLWriter.php | 2 +- .../Shared/XMLWriter.withoutwakeup.php | 2 +- .../PhpWordTests/Style/AbstractStyleTest.php | 33 ++++++++++++------- .../Writer/EPub3/Part/AbstractPartTest.php | 15 +++++++-- .../Writer/EPub3/Style/AbstractStyleTest.php | 15 +++++++-- .../Writer/ODText/Part/AbstractPartTest.php | 21 ++++++++---- .../Writer/Word2007/Part/AbstractPartTest.php | 21 ++++++++---- 9 files changed, 79 insertions(+), 46 deletions(-) diff --git a/phpstan-baseline.php73.neon b/phpstan-baseline.php73.neon index 7d77dc120a..e12561b099 100644 --- a/phpstan-baseline.php73.neon +++ b/phpstan-baseline.php73.neon @@ -1584,15 +1584,3 @@ parameters: message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$xpath \\(DOMXPath\\) does not accept null\\.$#" count: 1 path: tests/PhpWordTests/XmlDocument.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\EPub3\\\\Style\\\\AbstractStyle is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 1 - path: tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\AbstractPart is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 2 - path: tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php diff --git a/phpstan.neon.php73.dist b/phpstan.neon.php73.dist index 9f5b0e67b5..c7584c2d48 100644 --- a/phpstan.neon.php73.dist +++ b/phpstan.neon.php73.dist @@ -1,5 +1,5 @@ includes: - - phpstan-baseline.php7.neon + - phpstan-baseline.php73.neon - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon parameters: @@ -16,7 +16,7 @@ parameters: - src/PhpWord/Writer/PDF/MPDF.php bootstrapFiles: - tests/bootstrap.php - reportUnmatchedIgnoredErrors: false + reportUnmatchedIgnoredErrors: true treatPhpDocTypesAsCertain: false ignoreErrors: - diff --git a/src/PhpWord/Shared/XMLWriter.php b/src/PhpWord/Shared/XMLWriter.php index 3e7657dee3..1252d5606d 100644 --- a/src/PhpWord/Shared/XMLWriter.php +++ b/src/PhpWord/Shared/XMLWriter.php @@ -1,7 +1,7 @@ hash !== spl_object_id($this)) { - throw new WordException('Unserialize not permitted4'); + throw new WordException('Unserialize not permitted4'); // @codeCoverageIgnore } // Unlink temporary files if (empty($this->tempFileName)) { diff --git a/tests/PhpWordTests/Style/AbstractStyleTest.php b/tests/PhpWordTests/Style/AbstractStyleTest.php index 346fd06ce7..3b3cef900e 100644 --- a/tests/PhpWordTests/Style/AbstractStyleTest.php +++ b/tests/PhpWordTests/Style/AbstractStyleTest.php @@ -31,14 +31,23 @@ */ class AbstractStyleTest extends \PHPUnit\Framework\TestCase { + /** @var string */ + protected static $mockAbstract = 'getMockForAbstractClass'; + + /** @param string $method */ + private function methodFound($method): bool + { + return method_exists($this, $method); + } + /** * Test set style by array. */ public function testSetStyleByArray(): void { - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $stub = $this->getMockForAbstractClass(AbstractStyle::class); + $method = self::$mockAbstract; + if (self::methodFound($method)) { + $stub = $this->$method(AbstractStyle::class); } else { /** @var AbstractStyle $stub */ $stub = new class() extends AbstractStyle { @@ -70,9 +79,9 @@ public function testSetStyleByArrayWithAlignment(): void */ public function testSetValNormal(): void { - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $stub = $this->getMockForAbstractClass(AbstractStyle::class); + $method = self::$mockAbstract; + if (self::methodFound($method)) { + $stub = $this->$method(AbstractStyle::class); } else { /** @var AbstractStyle $stub */ $stub = new class() extends AbstractStyle { @@ -91,9 +100,9 @@ public function testSetValNormal(): void */ public function testSetValDefault(): void { - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $stub = $this->getMockForAbstractClass(AbstractStyle::class); + $method = self::$mockAbstract; + if (self::methodFound($method)) { + $stub = $this->$method(AbstractStyle::class); } else { /** @var AbstractStyle $stub */ $stub = new class() extends AbstractStyle { @@ -112,9 +121,9 @@ public function testSetValDefault(): void public function testSetValEnumException(): void { $this->expectException(InvalidArgumentException::class); - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $stub = $this->getMockForAbstractClass(AbstractStyle::class); + $method = self::$mockAbstract; + if (self::methodFound($method)) { + $stub = $this->$method(AbstractStyle::class); } else { /** @var AbstractStyle $stub */ $stub = new class() extends AbstractStyle { diff --git a/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartTest.php b/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartTest.php index 24db959f6e..6f4494afc4 100644 --- a/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartTest.php +++ b/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartTest.php @@ -13,11 +13,20 @@ class AbstractPartTest extends TestCase */ private $part; + /** @var string */ + protected static $mockAbstract = 'getMockForAbstractClass'; + + /** @param string $method */ + private function methodFound($method): bool + { + return method_exists($this, $method); + } + protected function setUp(): void { - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $this->part = $this->getMockForAbstractClass(AbstractPart::class); + $mockAbstract = self::$mockAbstract; + if ($this->methodFound($mockAbstract)) { + $this->part = $this->$mockAbstract(AbstractPart::class); } else { $this->part = new class() extends AbstractPart { public function write(): string diff --git a/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php b/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php index e06cef8a3b..7d634e5f63 100644 --- a/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php +++ b/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php @@ -8,15 +8,24 @@ class AbstractStyleTest extends TestCase { + /** @var string */ + protected static $mockAbstract = 'getMockForAbstractClass'; + + /** @param string $method */ + private function methodFound($method): bool + { + return method_exists($this, $method); + } + /** * Test setParentWriter and getParentWriter methods. */ public function testParentWriter(): void { $parentWriter = new EPub3(); - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $style = $this->getMockForAbstractClass(AbstractStyle::class); + $mockAbstract = self::$mockAbstract; + if ($this->methodFound($mockAbstract)) { + $style = $this->$mockAbstract(AbstractStyle::class); } else { /** @var AbstractStyle $style */ $style = new class() extends AbstractStyle { diff --git a/tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php b/tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php index 049b9e7cb4..39218f5604 100644 --- a/tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php +++ b/tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php @@ -32,11 +32,20 @@ class AbstractPartTest extends \PHPUnit\Framework\TestCase * covers ::setParentWriter * covers ::getParentWriter. */ + /** @var string */ + protected static $mockAbstract = 'getMockForAbstractClass'; + + /** @param string $method */ + private function methodFound($method): bool + { + return method_exists($this, $method); + } + public function testSetGetParentWriter(): void { - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $object = $this->getMockForAbstractClass(ODText\Part\AbstractPart::class); + $method = self::$mockAbstract; + if (self::methodFound($method)) { + $object = $this->$method(ODText\Part\AbstractPart::class); } else { /** @var ODText\Part\AbstractPart $object */ $object = new class() extends ODText\Part\AbstractPart { @@ -57,9 +66,9 @@ public function testSetGetParentWriterNull(): void { $this->expectException(Exception::class); $this->expectExceptionMessage('No parent WriterInterface assigned.'); - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $object = $this->getMockForAbstractClass(ODText\Part\AbstractPart::class); + $method = self::$mockAbstract; + if (self::methodFound($method)) { + $object = $this->$method(ODText\Part\AbstractPart::class); } else { /** @var ODText\Part\AbstractPart $object */ $object = new class() extends ODText\Part\AbstractPart { diff --git a/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php b/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php index 1bcd43f50e..6ffda0c019 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php @@ -30,11 +30,20 @@ class AbstractPartTest extends \PHPUnit\Framework\TestCase * covers ::setParentWriter * covers ::getParentWriter. */ + /** @var string */ + protected static $mockAbstract = 'getMockForAbstractClass'; + + /** @param string $method */ + private function methodFound($method): bool + { + return method_exists($this, $method); + } + public function testSetGetParentWriter(): void { - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $stub = $this->getMockForAbstractClass(Word2007\Part\AbstractPart::class); + $method = self::$mockAbstract; + if (self::methodFound($method)) { + $stub = $this->$method(Word2007\Part\AbstractPart::class); } else { /** @var Word2007\Part\AbstractPart $stub */ $stub = new class() extends Word2007\Part\AbstractPart { @@ -55,9 +64,9 @@ public function testSetGetParentWriterNull(): void { $this->expectException(Exception::class); $this->expectExceptionMessage('No parent WriterInterface assigned.'); - // @phpstan-ignore-next-line - if (method_exists($this, 'getMockForAbstractClass')) { - $stub = $this->getMockForAbstractClass(Word2007\Part\AbstractPart::class); + $method = self::$mockAbstract; + if (self::methodFound($method)) { + $stub = $this->$method(Word2007\Part\AbstractPart::class); } else { /** @var Word2007\Part\AbstractPart $stub */ $stub = new class() extends Word2007\Part\AbstractPart { From abe4268e4e2ef725a821d0612eb24ddb75d7976f Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 13 Sep 2025 14:32:18 -0700 Subject: [PATCH 073/153] Php8.5 __wakeup Deprecation Implemented No deprecation message if there is __unserialize in same class, so no need to duplicate XMLWriter. --- phpstan-baseline.neon | 5 - phpstan-baseline.php7.neon | 5 - phpstan-baseline.php73.neon | 5 - phpstan.neon.dist | 3 - phpstan.neon.php7.dist | 3 - phpstan.neon.php73.dist | 3 - src/PhpWord/Shared/XMLWriter.php | 208 +++++++++++++++++- .../Shared/XMLWriter.withoutwakeup.php | 197 ----------------- src/PhpWord/Shared/XMLWriter.withwakeup.php | 206 ----------------- src/PhpWord/Writer/PDF/MPDF.php | 36 +-- 10 files changed, 205 insertions(+), 466 deletions(-) delete mode 100644 src/PhpWord/Shared/XMLWriter.withoutwakeup.php delete mode 100644 src/PhpWord/Shared/XMLWriter.withwakeup.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 153af7f2db..a231e4cf01 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -400,11 +400,6 @@ parameters: count: 1 path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:getData\\(\\) should return string but returns string\\|false\\.$#" - count: 1 - path: src/PhpWord/Shared/XMLWriter.php - - message: "#^Call to method add\\(\\) on an unknown class PclZip\\.$#" count: 2 diff --git a/phpstan-baseline.php7.neon b/phpstan-baseline.php7.neon index 7d77dc120a..df47bf9ab5 100644 --- a/phpstan-baseline.php7.neon +++ b/phpstan-baseline.php7.neon @@ -385,11 +385,6 @@ parameters: count: 1 path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:getData\\(\\) should return string but returns string\\|false\\.$#" - count: 1 - path: src/PhpWord/Shared/XMLWriter.php - - message: "#^Call to method add\\(\\) on an unknown class PclZip\\.$#" count: 2 diff --git a/phpstan-baseline.php73.neon b/phpstan-baseline.php73.neon index e12561b099..ee5cc3064d 100644 --- a/phpstan-baseline.php73.neon +++ b/phpstan-baseline.php73.neon @@ -385,11 +385,6 @@ parameters: count: 1 path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:getData\\(\\) should return string but returns string\\|false\\.$#" - count: 1 - path: src/PhpWord/Shared/XMLWriter.php - - message: "#^Call to method add\\(\\) on an unknown class PclZip\\.$#" count: 2 diff --git a/phpstan.neon.dist b/phpstan.neon.dist index b45486e4e6..df80cc0f26 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -11,9 +11,6 @@ parameters: - */pclzip.lib.php - src/PhpWord/Reader/MsDoc.php - src/PhpWord/Shared/OLERead.php - - src/PhpWord/Shared/XMLWriter.php - - src/PhpWord/Shared/XMLWriter.withwakeup.php - - src/PhpWord/Writer/PDF/MPDF.php bootstrapFiles: - tests/bootstrap.php treatPhpDocTypesAsCertain: false diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist index a265040285..5b5db533a9 100644 --- a/phpstan.neon.php7.dist +++ b/phpstan.neon.php7.dist @@ -11,9 +11,6 @@ parameters: - */pclzip.lib.php - src/PhpWord/Reader/MsDoc.php - src/PhpWord/Shared/OLERead.php - - src/PhpWord/Shared/XMLWriter.php - - src/PhpWord/Shared/XMLWriter.withoutwakeup.php - - src/PhpWord/Writer/PDF/MPDF.php bootstrapFiles: - tests/bootstrap.php treatPhpDocTypesAsCertain: false diff --git a/phpstan.neon.php73.dist b/phpstan.neon.php73.dist index c7584c2d48..ce4f265a44 100644 --- a/phpstan.neon.php73.dist +++ b/phpstan.neon.php73.dist @@ -11,9 +11,6 @@ parameters: - */pclzip.lib.php - src/PhpWord/Reader/MsDoc.php - src/PhpWord/Shared/OLERead.php - - src/PhpWord/Shared/XMLWriter.php - - src/PhpWord/Shared/XMLWriter.withoutwakeup.php - - src/PhpWord/Writer/PDF/MPDF.php bootstrapFiles: - tests/bootstrap.php reportUnmatchedIgnoredErrors: true diff --git a/src/PhpWord/Shared/XMLWriter.php b/src/PhpWord/Shared/XMLWriter.php index 1252d5606d..e2461586e6 100644 --- a/src/PhpWord/Shared/XMLWriter.php +++ b/src/PhpWord/Shared/XMLWriter.php @@ -1,7 +1,207 @@ hash = spl_object_id($this); + // Open temporary storage + if ($pTemporaryStorage == self::STORAGE_MEMORY) { + $this->openMemory(); + } else { + if (!$pTemporaryStorageDir || !is_dir($pTemporaryStorageDir)) { + $pTemporaryStorageDir = sys_get_temp_dir(); + } + // Create temporary filename + $this->tempFileName = @tempnam($pTemporaryStorageDir, 'xml'); + + // Open storage + $this->openUri($this->tempFileName); + } + + if ($compatibility) { + $this->setIndent(false); + $this->setIndentString(''); + } else { + $this->setIndent(true); + $this->setIndentString(' '); + } + } + + /** + * Destructor. + */ + public function __destruct() + { + if ($this->hash !== spl_object_id($this)) { + throw new WordException('Unserialize not permitted1'); + } + // Unlink temporary files + if (empty($this->tempFileName)) { + return; + } + @unlink($this->tempFileName); + } + + /** @codeCoverageIgnore */ + public function __wakeup(): void + { + $this->tempFileName = ''; + + throw new WordException('Unserialize not permitted2'); + } + + /** @param mixed[] $data */ + public function __unserialize(array $data): void + { + $this->tempFileName = ''; + + throw new WordException('Unserialize not permitted3'); + } + + /** + * Get written data. + * + * @return string + */ + public function getData() + { + if ($this->tempFileName == '') { + return $this->outputMemory(true); + } + + $this->flush(); + + return (string) file_get_contents($this->tempFileName); + } + + /** + * Write simple element and attribute(s) block. + * + * There are two options: + * 1. If the `$attributes` is an array, then it's an associative array of attributes + * 2. If not, then it's a simple attribute-value pair + * + * @param string $element + * @param array|string $attributes + * @param string $value + */ + public function writeElementBlock($element, $attributes, $value = null): void + { + $this->startElement($element); + if (!is_array($attributes)) { + $attributes = [$attributes => $value]; + } + foreach ($attributes as $attribute => $value) { + $this->writeAttribute($attribute, $value); + } + $this->endElement(); + } + + /** + * Write element if ... + * + * @param bool $condition + * @param string $element + * @param string $attribute + * @param mixed $value + */ + public function writeElementIf($condition, $element, $attribute = null, $value = null): void + { + if ($condition == true) { + if (null === $attribute) { + $this->writeElement($element, $value); + } else { + $this->startElement($element); + $this->writeAttribute($attribute, $value); + $this->endElement(); + } + } + } + + /** + * Write attribute if ... + * + * @param bool $condition + * @param string $attribute + * @param mixed $value + */ + public function writeAttributeIf($condition, $attribute, $value): void + { + if ($condition == true) { + $this->writeAttribute($attribute, $value); + } + } + + /** + * @param string $name + * @param mixed $value + */ + public function writeAttribute($name, $value): bool + { + if (is_float($value)) { + $value = (string) $value; + } + + return parent::writeAttribute($name, $value ?? ''); + } } diff --git a/src/PhpWord/Shared/XMLWriter.withoutwakeup.php b/src/PhpWord/Shared/XMLWriter.withoutwakeup.php deleted file mode 100644 index c1c113c729..0000000000 --- a/src/PhpWord/Shared/XMLWriter.withoutwakeup.php +++ /dev/null @@ -1,197 +0,0 @@ -hash = spl_object_id($this); - // Open temporary storage - if ($pTemporaryStorage == self::STORAGE_MEMORY) { - $this->openMemory(); - } else { - if (!$pTemporaryStorageDir || !is_dir($pTemporaryStorageDir)) { - $pTemporaryStorageDir = sys_get_temp_dir(); - } - // Create temporary filename - $this->tempFileName = @tempnam($pTemporaryStorageDir, 'xml'); - - // Open storage - $this->openUri($this->tempFileName); - } - - if ($compatibility) { - $this->setIndent(false); - $this->setIndentString(''); - } else { - $this->setIndent(true); - $this->setIndentString(' '); - } - } - - /** - * Destructor. - */ - public function __destruct() - { - if ($this->hash !== spl_object_id($this)) { - throw new WordException('Unserialize not permitted4'); // @codeCoverageIgnore - } - // Unlink temporary files - if (empty($this->tempFileName)) { - return; - } - @unlink($this->tempFileName); - } - - /** @param mixed[] $data */ - public function __unserialize(array $data): void - { - $this->tempFileName = ''; - - throw new WordException('Unserialize not permitted5'); - } - - /** - * Get written data. - * - * @return string - */ - public function getData() - { - if ($this->tempFileName == '') { - return $this->outputMemory(true); - } - - $this->flush(); - - return (string) file_get_contents($this->tempFileName); - } - - /** - * Write simple element and attribute(s) block. - * - * There are two options: - * 1. If the `$attributes` is an array, then it's an associative array of attributes - * 2. If not, then it's a simple attribute-value pair - * - * @param string $element - * @param array|string $attributes - * @param string $value - */ - public function writeElementBlock($element, $attributes, $value = null): void - { - $this->startElement($element); - if (!is_array($attributes)) { - $attributes = [$attributes => $value]; - } - foreach ($attributes as $attribute => $value) { - $this->writeAttribute($attribute, $value); - } - $this->endElement(); - } - - /** - * Write element if ... - * - * @param bool $condition - * @param string $element - * @param string $attribute - * @param mixed $value - */ - public function writeElementIf($condition, $element, $attribute = null, $value = null): void - { - if ($condition == true) { - if (null === $attribute) { - $this->writeElement($element, $value); - } else { - $this->startElement($element); - $this->writeAttribute($attribute, $value); - $this->endElement(); - } - } - } - - /** - * Write attribute if ... - * - * @param bool $condition - * @param string $attribute - * @param mixed $value - */ - public function writeAttributeIf($condition, $attribute, $value): void - { - if ($condition == true) { - $this->writeAttribute($attribute, $value); - } - } - - /** - * @param string $name - * @param mixed $value - */ - public function writeAttribute($name, $value): bool - { - if (is_float($value)) { - $value = (string) $value; - } - - return parent::writeAttribute($name, $value ?? ''); - } -} diff --git a/src/PhpWord/Shared/XMLWriter.withwakeup.php b/src/PhpWord/Shared/XMLWriter.withwakeup.php deleted file mode 100644 index e772315883..0000000000 --- a/src/PhpWord/Shared/XMLWriter.withwakeup.php +++ /dev/null @@ -1,206 +0,0 @@ -hash = spl_object_id($this); - // Open temporary storage - if ($pTemporaryStorage == self::STORAGE_MEMORY) { - $this->openMemory(); - } else { - if (!$pTemporaryStorageDir || !is_dir($pTemporaryStorageDir)) { - $pTemporaryStorageDir = sys_get_temp_dir(); - } - // Create temporary filename - $this->tempFileName = @tempnam($pTemporaryStorageDir, 'xml'); - - // Open storage - $this->openUri($this->tempFileName); - } - - if ($compatibility) { - $this->setIndent(false); - $this->setIndentString(''); - } else { - $this->setIndent(true); - $this->setIndentString(' '); - } - } - - /** - * Destructor. - */ - public function __destruct() - { - if ($this->hash !== spl_object_id($this)) { - throw new WordException('Unserialize not permitted1'); - } - // Unlink temporary files - if (empty($this->tempFileName)) { - return; - } - @unlink($this->tempFileName); - } - - public function __wakeup(): void - { - $this->tempFileName = ''; - - throw new WordException('Unserialize not permitted2'); - } - - /** @param mixed[] $data */ - public function __unserialize(array $data): void - { - $this->tempFileName = ''; - - throw new WordException('Unserialize not permitted3'); - } - - /** - * Get written data. - * - * @return string - */ - public function getData() - { - if ($this->tempFileName == '') { - return $this->outputMemory(true); - } - - $this->flush(); - - return (string) file_get_contents($this->tempFileName); - } - - /** - * Write simple element and attribute(s) block. - * - * There are two options: - * 1. If the `$attributes` is an array, then it's an associative array of attributes - * 2. If not, then it's a simple attribute-value pair - * - * @param string $element - * @param array|string $attributes - * @param string $value - */ - public function writeElementBlock($element, $attributes, $value = null): void - { - $this->startElement($element); - if (!is_array($attributes)) { - $attributes = [$attributes => $value]; - } - foreach ($attributes as $attribute => $value) { - $this->writeAttribute($attribute, $value); - } - $this->endElement(); - } - - /** - * Write element if ... - * - * @param bool $condition - * @param string $element - * @param string $attribute - * @param mixed $value - */ - public function writeElementIf($condition, $element, $attribute = null, $value = null): void - { - if ($condition == true) { - if (null === $attribute) { - $this->writeElement($element, $value); - } else { - $this->startElement($element); - $this->writeAttribute($attribute, $value); - $this->endElement(); - } - } - } - - /** - * Write attribute if ... - * - * @param bool $condition - * @param string $attribute - * @param mixed $value - */ - public function writeAttributeIf($condition, $attribute, $value): void - { - if ($condition == true) { - $this->writeAttribute($attribute, $value); - } - } - - /** - * @param string $name - * @param mixed $value - */ - public function writeAttribute($name, $value): bool - { - if (is_float($value)) { - $value = (string) $value; - } - - return parent::writeAttribute($name, $value ?? ''); - } -} diff --git a/src/PhpWord/Writer/PDF/MPDF.php b/src/PhpWord/Writer/PDF/MPDF.php index 03ef1f3ad7..287f0f5488 100644 --- a/src/PhpWord/Writer/PDF/MPDF.php +++ b/src/PhpWord/Writer/PDF/MPDF.php @@ -33,20 +33,6 @@ class MPDF extends AbstractRenderer implements WriterInterface public const SIMULATED_BODY_START = ''; private const BODY_TAG = ''; - /** - * Overridden to set the correct includefile, only needed for MPDF 5. - * - * @codeCoverageIgnore - */ - public function __construct(PhpWord $phpWord) - { - if (file_exists(Settings::getPdfRendererPath() . '/mpdf.php')) { - // MPDF version 5.* needs this file to be included, later versions not - $this->includeFile = 'mpdf.php'; - } - parent::__construct($phpWord); - } - /** * Gets the implementation of external PDF library that should be used. * @@ -54,14 +40,12 @@ public function __construct(PhpWord $phpWord) */ protected function createExternalWriterInstance() { - $mPdfClass = $this->getMPdfClassName(); - $options = []; if ($this->getFont()) { $options['default_font'] = $this->getFont(); } - return new $mPdfClass($options); + return new \Mpdf\Mpdf($options); } /** @@ -113,22 +97,4 @@ public function save(string $filename): void parent::restoreStateAfterSave($fileHandle); } - - /** - * Return classname of MPDF to instantiate. - * - * @codeCoverageIgnore - * - * @return string - */ - private function getMPdfClassName() - { - if ($this->includeFile != null) { - // MPDF version 5.* - return '\mpdf'; - } - - // MPDF version > 6.* - return '\Mpdf\Mpdf'; - } } From 34a9094102bc0177544f0f90ed0fcb2e3661cfb4 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 13 Sep 2025 15:19:20 -0700 Subject: [PATCH 074/153] Upgrade Phpstan --- phpstan.neon.php7.dist | 6 +++--- src/PhpWord/Reader/Word2007/AbstractPart.php | 6 +++++- tests/PhpWordTests/Writer/HTML/Element/TableTest.php | 2 +- tests/PhpWordTests/Writer/HTML/FontTest.php | 2 +- tests/PhpWordTests/Writer/HTML/ParagraphTest.php | 6 +++--- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist index 5b5db533a9..69a7728ebf 100644 --- a/phpstan.neon.php7.dist +++ b/phpstan.neon.php7.dist @@ -20,13 +20,13 @@ parameters: ## <=PHP7.4 - - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' + message: '#Parameter \#1 \$objectOrClass of class ReflectionClass constructor expects class-string\|T of object, string given.#' path: src/PhpWord/Element/AbstractContainer.php - message: '#Parameter \#1 \$function of function call_user_func expects callable\(\): mixed, string given.#' path: src/PhpWord/Element/Image.php - - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' + message: '#Parameter \#1 \$objectOrClass of class ReflectionClass constructor expects class-string\|T of object, string given.#' path: src/PhpWord/IOFactory.php - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed,#' @@ -44,7 +44,7 @@ parameters: message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Style\\AbstractStyle is not subtype of native type #' path: tests/PhpWordTests/Style/AbstractStyleTest.php - - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' + message: '#Parameter \#1 \$objectOrClass of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' path: tests/PhpWordTests/Style/AbstractStyleTest.php - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Writer\\Word2007\\Part\\AbstractPart is not subtype of native type #' diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 3fe94f6c9d..d5f0610663 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -369,7 +369,11 @@ private function readFormField(XMLReader $xmlReader, array $domNodes, $parent, $ } $formField->setEntries($listEntries); if (null !== $formField->getValue()) { - $formField->setText($listEntries[$formField->getValue()]); + /** @var int */ + $temp = $formField->getValue(); + $formField->setText( + $listEntries[$temp] + ); } break; diff --git a/tests/PhpWordTests/Writer/HTML/Element/TableTest.php b/tests/PhpWordTests/Writer/HTML/Element/TableTest.php index cd0bafaab0..30410925e8 100644 --- a/tests/PhpWordTests/Writer/HTML/Element/TableTest.php +++ b/tests/PhpWordTests/Writer/HTML/Element/TableTest.php @@ -162,7 +162,7 @@ public function testWriteTableBorders(): void self::assertEquals('tstyle', Helper::getTextContent($xpath, '/html/body/div/table[6]', 'class')); $style = Helper::getTextContent($xpath, '/html/head/style'); self::assertNotFalse(preg_match('/^[.]tstyle[^\\r\\n]*/m', $style, $matches)); - self::assertEquals(".tstyle {table-layout: auto; $cssnone}", $matches[0]); + self::assertEquals(".tstyle {table-layout: auto; $cssnone}", $matches[0] ?? ''); } public function testWriteTableCellVAlign(): void diff --git a/tests/PhpWordTests/Writer/HTML/FontTest.php b/tests/PhpWordTests/Writer/HTML/FontTest.php index 485cd2ae82..6b6e920b89 100644 --- a/tests/PhpWordTests/Writer/HTML/FontTest.php +++ b/tests/PhpWordTests/Writer/HTML/FontTest.php @@ -275,7 +275,7 @@ public function testWhiteSpace(): void $style = Helper::getTextContent($xpath, '/html/head/style'); self::assertNotFalse(preg_match('/^body[^\\r\\n]*/m', $style, $matches)); - self::assertEquals('body {font-family: \'Arial\'; font-size: 12pt; color: #000000; white-space: pre-wrap;}', $matches[0]); + self::assertEquals('body {font-family: \'Arial\'; font-size: 12pt; color: #000000; white-space: pre-wrap;}', $matches[0] ?? ''); $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); self::assertNotEmpty($matches); self::assertNotFalse($prg); diff --git a/tests/PhpWordTests/Writer/HTML/ParagraphTest.php b/tests/PhpWordTests/Writer/HTML/ParagraphTest.php index 2b2724dba8..d972aa23d7 100644 --- a/tests/PhpWordTests/Writer/HTML/ParagraphTest.php +++ b/tests/PhpWordTests/Writer/HTML/ParagraphTest.php @@ -55,7 +55,7 @@ public function testParagraphStyles(): void $style = Helper::getTextContent($xpath, '/html/head/style'); self::assertNotFalse(preg_match('/^[.]indented[^\\r\\n]*/m', $style, $matches)); - self::assertEquals('.indented {margin-left: 0.5in; margin-right: 0.6in;}', $matches[0]); + self::assertEquals('.indented {margin-left: 0.5in; margin-right: 0.6in;}', $matches[0] ?? ''); } /** @@ -88,9 +88,9 @@ public function testParagraphAndFontStyles(): void $style = Helper::getTextContent($xpath, '/html/head/style'); self::assertNotFalse(preg_match('/^[.]indented[^\\r\\n]*/m', $style, $matches)); - self::assertEquals('.indented {margin-left: 0.5in; margin-right: 0.6in;}', $matches[0]); + self::assertEquals('.indented {margin-left: 0.5in; margin-right: 0.6in;}', $matches[0] ?? ''); self::assertNotFalse(preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches)); - self::assertEquals('.style1 {font-family: \'Courier New\', monospace; font-size: 10pt; white-space: pre-wrap;}', $matches[0]); + self::assertEquals('.style1 {font-family: \'Courier New\', monospace; font-size: 10pt; white-space: pre-wrap;}', $matches[0] ?? ''); } /** From c96dac1705f32c68b8ea5b4651da6927c2138f4c Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 13 Sep 2025 15:27:39 -0700 Subject: [PATCH 075/153] Fix Phpstan 7.2/3 --- phpstan.neon.php73.dist | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/phpstan.neon.php73.dist b/phpstan.neon.php73.dist index ce4f265a44..a82e1d3f4f 100644 --- a/phpstan.neon.php73.dist +++ b/phpstan.neon.php73.dist @@ -53,3 +53,12 @@ parameters: - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' path: tests/PhpWordTests/Style/AbstractStyleTest.php + - + message: '#always exists and is not nullable#' + path: tests/PhpWordTests/Writer/HTML/Element/TableTest.php + - + message: '#always exists and is not nullable#' + path: tests/PhpWordTests/Writer/HTML/FontTest.php + - + message: '#always exists and is not nullable#' + path: tests/PhpWordTests/Writer/HTML/ParagraphTest.php From bae770ece1ab3dae45636f80ba53a7bb810fd1e5 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 13 Sep 2025 21:03:01 -0700 Subject: [PATCH 076/153] Add Reader/MsDoc to Phpstan --- phpstan.neon.dist | 1 - phpstan.neon.php7.dist | 1 - phpstan.neon.php73.dist | 1 - src/PhpWord/Reader/MsDoc.php | 54 ++++++++++++++++++++++++++++-------- 4 files changed, 43 insertions(+), 14 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index df80cc0f26..a605acb6e0 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -9,7 +9,6 @@ parameters: - tests/ excludePaths: - */pclzip.lib.php - - src/PhpWord/Reader/MsDoc.php - src/PhpWord/Shared/OLERead.php bootstrapFiles: - tests/bootstrap.php diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist index 69a7728ebf..58e3c73b8d 100644 --- a/phpstan.neon.php7.dist +++ b/phpstan.neon.php7.dist @@ -9,7 +9,6 @@ parameters: - tests/ excludePaths: - */pclzip.lib.php - - src/PhpWord/Reader/MsDoc.php - src/PhpWord/Shared/OLERead.php bootstrapFiles: - tests/bootstrap.php diff --git a/phpstan.neon.php73.dist b/phpstan.neon.php73.dist index a82e1d3f4f..03f5909648 100644 --- a/phpstan.neon.php73.dist +++ b/phpstan.neon.php73.dist @@ -9,7 +9,6 @@ parameters: - tests/ excludePaths: - */pclzip.lib.php - - src/PhpWord/Reader/MsDoc.php - src/PhpWord/Shared/OLERead.php bootstrapFiles: - tests/bootstrap.php diff --git a/src/PhpWord/Reader/MsDoc.php b/src/PhpWord/Reader/MsDoc.php index ad88f4adac..caee531cda 100644 --- a/src/PhpWord/Reader/MsDoc.php +++ b/src/PhpWord/Reader/MsDoc.php @@ -40,23 +40,31 @@ class MsDoc extends AbstractReader implements ReaderInterface /** * WordDocument Stream. + * + * @var mixed */ - private $dataWorkDocument; + protected $dataWorkDocument; /** * 1Table Stream. + * + * @var mixed */ - private $data1Table; + protected $data1Table; /** * Data Stream. + * + * @var mixed */ - private $dataData; + protected $dataData; /** * Object Pool Stream. + * + * @var mixed */ - private $dataObjectPool; + protected $dataObjectPool; /** * @var stdClass[] @@ -84,10 +92,10 @@ class MsDoc extends AbstractReader implements ReaderInterface private $arraySections = []; /** @var string */ - private $summaryInformation; + protected $summaryInformation; /** @var string */ - private $documentSummaryInformation; + protected $documentSummaryInformation; const VERSION_97 = '97'; const VERSION_2000 = '2000'; @@ -162,11 +170,24 @@ private function loadOLE($filename): void $this->documentSummaryInformation = $ole->getStream($ole->docSummaryInfos); } + /** + * @param int $lcb + * @param int $iSize + * + * @return int + */ private function getNumInLcb($lcb, $iSize) { return ($lcb - 4) / (4 + $iSize); } + /** + * @param string $data + * @param int $posMem + * @param int $iNum + * + * @return array + */ private function getArrayCP($data, $posMem, $iNum) { $arrayCP = []; @@ -183,6 +204,8 @@ private function getArrayCP($data, $posMem, $iNum) * @see https://igor.io/2012/09/24/binary-parsing.html * * @param string $data + * + * @return int */ private function readFib($data) { @@ -368,6 +391,13 @@ private function readFib($data) return $pos; } + /** + * @param string $data + * @param int $pos + * @param string $version + * + * @return int + */ private function readBlockFibRgFcLcb($data, $pos, $version) { if ($version == self::VERSION_97) { @@ -1233,7 +1263,7 @@ private function readRecordSttbfFfn(): void $xszAlt .= mb_chr($char, 'UTF-8'); } while ($char != 0); } - $this->arrayFonts[] = [ + $this->arrayFonts[] = [ //* @phpstan-ignore-line 'main' => $xszFfn, 'alt' => $xszAlt, ]; @@ -1514,6 +1544,8 @@ private function readRecordPlcfBteChpx(): void } /** + * @param int $sprm + * * @return stdClass */ private function readSprm($sprm) @@ -1598,9 +1630,9 @@ private function readSprmSpra($data, $pos, $oSprm) } /** - * @param $data int - * @param $pos int - * @param $cbNum int + * @param string $data + * @param int $pos + * @param int $cbNum * * @return stdClass * @@ -1904,7 +1936,7 @@ private function readPrl($data, $pos, $cbNum) case 0x4F: $oStylePrl->styleFont['name'] = ''; if (isset($this->arrayFonts[$operand])) { - $oStylePrl->styleFont['name'] = $this->arrayFonts[$operand]['main']; + $oStylePrl->styleFont['name'] = $this->arrayFonts[$operand]['main']; //* @phpstan-ignore-line } break; From fc15bea9e3b7b12a6fc222efba0f615b699a1721 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 13 Sep 2025 21:48:23 -0700 Subject: [PATCH 077/153] Add Shared/OLERead to Phpstan --- phpstan.neon.dist | 1 - phpstan.neon.php7.dist | 1 - phpstan.neon.php73.dist | 1 - src/PhpWord/Shared/OLERead.php | 34 ++++++++++++++++++++++++++-------- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index a605acb6e0..295b623f51 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -9,7 +9,6 @@ parameters: - tests/ excludePaths: - */pclzip.lib.php - - src/PhpWord/Shared/OLERead.php bootstrapFiles: - tests/bootstrap.php treatPhpDocTypesAsCertain: false diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist index 58e3c73b8d..e7f4bdc454 100644 --- a/phpstan.neon.php7.dist +++ b/phpstan.neon.php7.dist @@ -9,7 +9,6 @@ parameters: - tests/ excludePaths: - */pclzip.lib.php - - src/PhpWord/Shared/OLERead.php bootstrapFiles: - tests/bootstrap.php treatPhpDocTypesAsCertain: false diff --git a/phpstan.neon.php73.dist b/phpstan.neon.php73.dist index 03f5909648..e522bf9edf 100644 --- a/phpstan.neon.php73.dist +++ b/phpstan.neon.php73.dist @@ -9,7 +9,6 @@ parameters: - tests/ excludePaths: - */pclzip.lib.php - - src/PhpWord/Shared/OLERead.php bootstrapFiles: - tests/bootstrap.php reportUnmatchedIgnoredErrors: true diff --git a/src/PhpWord/Shared/OLERead.php b/src/PhpWord/Shared/OLERead.php index d4399d6fb7..7285256906 100644 --- a/src/PhpWord/Shared/OLERead.php +++ b/src/PhpWord/Shared/OLERead.php @@ -24,6 +24,7 @@ class OLERead { + /** @var string */ private $data = ''; // OLE identifier @@ -55,32 +56,49 @@ class OLERead const START_BLOCK_POS = 0x74; const SIZE_POS = 0x78; + /** @var mixed */ public $wrkdocument = null; + /** @var mixed */ public $wrk1Table = null; + /** @var mixed */ public $wrkData = null; + /** @var mixed */ public $wrkObjectPool = null; + /** @var mixed */ public $summaryInformation = null; + /** @var mixed */ public $docSummaryInfos = null; + /** @var mixed */ public $numBigBlockDepotBlocks = null; + /** @var mixed */ public $rootStartBlock = null; + /** @var mixed */ public $sbdStartBlock = null; + /** @var mixed */ public $extensionBlock = null; + /** @var mixed */ public $numExtensionBlocks = null; + /** @var mixed */ public $bigBlockChain = null; - public $smallBlockChain = null; - public $entry = null; + /** @var mixed */ + public $smallBlockChain = null; + /** @var mixed */ + public $entry = null; + /** @var mixed */ public $rootentry = null; + /** @var mixed */ public $wrkObjectPoolelseif = null; + /** @var array */ public $props = array(); /** * Read the file * - * @param $sFileName string Filename + * @param string $sFileName Filename * * @throws Exception */ - public function read($sFileName) + public function read($sFileName): void { // Check if file exists and is readable if (!is_readable($sFileName)) { @@ -89,7 +107,7 @@ public function read($sFileName) // Get the file identifier // Don't bother reading the whole file until we know it's a valid OLE file - $this->data = file_get_contents($sFileName, false, null, 0, 8); + $this->data = (string) file_get_contents($sFileName, false, null, 0, 8); // Check OLE identifier if ($this->data != self::IDENTIFIER_OLE) { @@ -97,7 +115,7 @@ public function read($sFileName) } // Get the file data - $this->data = file_get_contents($sFileName); + $this->data = (string) file_get_contents($sFileName); // Total number of sectors used for the SAT $this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS); @@ -180,7 +198,7 @@ public function read($sFileName) * Extract binary stream data * * @param mixed $stream - * @return string + * @return ?string */ public function getStream($stream) { @@ -248,7 +266,7 @@ private function readData($blSectorId) /** * Read entries in the directory stream. */ - private function readPropertySets() + private function readPropertySets(): void { $offset = 0; From 6af79847d61a8302c2015816c90768437e6b3f01 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 14 Sep 2025 13:03:00 -0700 Subject: [PATCH 078/153] Run Samples as Part of Unit Tests --- .github/workflows/php.yml | 46 +------------ tests/PhpWordTests/SampleTest.php | 105 ++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 45 deletions(-) create mode 100644 tests/PhpWordTests/SampleTest.php diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index cda22f86c6..94f09973c3 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -109,7 +109,7 @@ jobs: - name: Composer Config nightly if: matrix.php == 'nightly' - run: composer config platform.php 8.4.99 + run: composer config platform.php 8.5.99 - name: Composer Install run: composer install --ansi --prefer-dist --no-interaction --no-progress @@ -144,47 +144,3 @@ jobs: wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar chmod +x php-coveralls.phar php php-coveralls.phar --coverage_clover=build/clover.xml --json_path=build/coveralls-upload.json -vvv - - samples: - name: Check samples - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - experimental: - - false - php: - - '7.1' - - '7.2' - - '7.3' - - '7.4' - - '8.0' - - '8.1' - - '8.2' - - '8.3' - - '8.4' - - include: - - php: 'nightly' - experimental: true - steps: - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: gd, xml, zip - coverage: xdebug - - - uses: actions/checkout@v2 - - - name: Composer Config nightly - if: matrix.php == 'nightly' - run: composer config platform.php 8.4.99 - - - name: Composer Install - run: composer install --ansi --prefer-dist --no-interaction --no-progress - - - name: "Generate samples files (Experimental: ${{ matrix.experimental }})" - env: - FAILURE_ACTION: "${{ matrix.experimental == true }}" - run: composer run samples || $FAILURE_ACTION diff --git a/tests/PhpWordTests/SampleTest.php b/tests/PhpWordTests/SampleTest.php new file mode 100644 index 0000000000..1d658b39b0 --- /dev/null +++ b/tests/PhpWordTests/SampleTest.php @@ -0,0 +1,105 @@ + $path] + */ + public static function getSamples(): array + { + // Populate samples + $baseDir = realpath('samples'); + if ($baseDir === false) { + // @codeCoverageIgnoreStart + throw new RuntimeException('realpath returned false'); + // @codeCoverageIgnoreEnd + } + $directory = new RecursiveDirectoryIterator($baseDir); + $iterator = new RecursiveIteratorIterator($directory); + $regex = new RegexIterator($iterator, '/Sample_\\d+_.+[.]php$/', RecursiveRegexIterator::GET_MATCH); + + $files = []; + /** @var string[] $file */ + foreach ($regex as $file) { + $file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0])); + $info = pathinfo($file); + $category = 'PhpWord'; + $name = str_replace('_', ' ', (string) preg_replace('/(|\.php)/', '', $info['filename'])); + if (!isset($files[$category])) { + $files[$category] = []; + } + $files[$category][$name] = $file; + } + + // Sort everything + ksort($files); + foreach ($files as &$f) { + asort($f); + } + + return $files; + } +} From b33163bdcb72c77c616af62f623edd6afe723703 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 14 Sep 2025 13:07:33 -0700 Subject: [PATCH 079/153] Adapt Code for Php 7.1/2/3 --- .github/workflows/php.yml | 2 +- tests/PhpWordTests/SampleTest.php | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 94f09973c3..be3dc5a626 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -109,7 +109,7 @@ jobs: - name: Composer Config nightly if: matrix.php == 'nightly' - run: composer config platform.php 8.5.99 + run: composer config platform.php 8.4.99 - name: Composer Install run: composer install --ansi --prefer-dist --no-interaction --no-progress diff --git a/tests/PhpWordTests/SampleTest.php b/tests/PhpWordTests/SampleTest.php index 1d658b39b0..139ce439b7 100644 --- a/tests/PhpWordTests/SampleTest.php +++ b/tests/PhpWordTests/SampleTest.php @@ -25,16 +25,20 @@ class SampleTest extends TestCase { - protected static bool $alwaysTrue = true; + /** @var bool */ + protected static $alwaysTrue = true; /** + * + * @param string $sample + * * @preserveGlobalState disabled * * @runInSeparateProcess * * @dataProvider providerSample */ - public function testSample(string $sample): void + public function testSample($sample): void { ob_start(); require $sample; From ffc33ca661cec2cdfeda3075db9e59fc0dd756c0 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 14 Sep 2025 13:26:37 -0700 Subject: [PATCH 080/153] Upgrade Symfony/Process --- composer.json | 2 +- tests/PhpWordTests/SampleTest.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index c898a700dd..db1ad060fc 100644 --- a/composer.json +++ b/composer.json @@ -125,7 +125,7 @@ "phpstan/phpstan": "^0.12.88 || ^1.0.0 || ^2.0.0", "phpstan/phpstan-phpunit": "^1.0 || ^2.0", "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0 || ^10.0", - "symfony/process": "^4.4 || ^5.0", + "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0", "tecnickcom/tcpdf": "^6.5" }, "suggest": { diff --git a/tests/PhpWordTests/SampleTest.php b/tests/PhpWordTests/SampleTest.php index 139ce439b7..358c4a773b 100644 --- a/tests/PhpWordTests/SampleTest.php +++ b/tests/PhpWordTests/SampleTest.php @@ -29,7 +29,6 @@ class SampleTest extends TestCase protected static $alwaysTrue = true; /** - * * @param string $sample * * @preserveGlobalState disabled From 469f31ee9ef4054bebb2dca029546e542b223412 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 14 Sep 2025 14:02:16 -0700 Subject: [PATCH 081/153] Php8.5 Will Need Unreleased Symfony/Process 7.3.4 --- tests/PhpWordTests/AbstractWebServerEmbedded.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/PhpWordTests/AbstractWebServerEmbedded.php b/tests/PhpWordTests/AbstractWebServerEmbedded.php index fde7e1007c..ebccaa632f 100644 --- a/tests/PhpWordTests/AbstractWebServerEmbedded.php +++ b/tests/PhpWordTests/AbstractWebServerEmbedded.php @@ -24,11 +24,19 @@ abstract class AbstractWebServerEmbedded extends \PHPUnit\Framework\TestCase { private static $httpServer; + /** @var int */ + protected static $wakeupDeprecatedVersion = 80500; + public static function setUpBeforeClass(): void { $commandLine = 'php -S localhost:8080 -t tests/PhpWordTests/_files'; - self::$httpServer = Process::fromShellCommandline($commandLine); + // need Symfony/Process 7.3.4 + if (PHP_VERSION_ID < self::$wakeupDeprecatedVersion) { + self::$httpServer = Process::fromShellCommandline($commandLine); + } else { + self::$httpServer = @Process::fromShellCommandline($commandLine); + } self::$httpServer->start(); while (!self::$httpServer->isRunning()) { usleep(1000); From ae88c4f4c1ab9ff7d2a96f5ba0b62e0caf8e4406 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 14 Sep 2025 15:22:03 -0700 Subject: [PATCH 082/153] Another Stab at Symfony/process --- composer.json | 2 +- tests/PhpWordTests/AbstractWebServerEmbedded.php | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index db1ad060fc..fb998082bc 100644 --- a/composer.json +++ b/composer.json @@ -125,7 +125,7 @@ "phpstan/phpstan": "^0.12.88 || ^1.0.0 || ^2.0.0", "phpstan/phpstan-phpunit": "^1.0 || ^2.0", "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0 || ^10.0", - "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0 || 7.4.x-dev", "tecnickcom/tcpdf": "^6.5" }, "suggest": { diff --git a/tests/PhpWordTests/AbstractWebServerEmbedded.php b/tests/PhpWordTests/AbstractWebServerEmbedded.php index ebccaa632f..fde7e1007c 100644 --- a/tests/PhpWordTests/AbstractWebServerEmbedded.php +++ b/tests/PhpWordTests/AbstractWebServerEmbedded.php @@ -24,19 +24,11 @@ abstract class AbstractWebServerEmbedded extends \PHPUnit\Framework\TestCase { private static $httpServer; - /** @var int */ - protected static $wakeupDeprecatedVersion = 80500; - public static function setUpBeforeClass(): void { $commandLine = 'php -S localhost:8080 -t tests/PhpWordTests/_files'; - // need Symfony/Process 7.3.4 - if (PHP_VERSION_ID < self::$wakeupDeprecatedVersion) { - self::$httpServer = Process::fromShellCommandline($commandLine); - } else { - self::$httpServer = @Process::fromShellCommandline($commandLine); - } + self::$httpServer = Process::fromShellCommandline($commandLine); self::$httpServer->start(); while (!self::$httpServer->isRunning()) { usleep(1000); From 18b1384cd0d95c0999208995dc592f5d8b62bc4a Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 15 Sep 2025 08:32:45 -0700 Subject: [PATCH 083/153] Rerun Coverage --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index be3dc5a626..94f09973c3 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -109,7 +109,7 @@ jobs: - name: Composer Config nightly if: matrix.php == 'nightly' - run: composer config platform.php 8.4.99 + run: composer config platform.php 8.5.99 - name: Composer Install run: composer install --ansi --prefer-dist --no-interaction --no-progress From 6024ee4f7b26553b9f132e5021376cf4e0469f5e Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 21 Sep 2025 21:39:48 -0700 Subject: [PATCH 084/153] Update Dompdf to Php8.5-compatible version --- src/PhpWord/Writer/PDF/DomPDF.php | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/PhpWord/Writer/PDF/DomPDF.php b/src/PhpWord/Writer/PDF/DomPDF.php index 1f25ad58cc..464dbfa59f 100644 --- a/src/PhpWord/Writer/PDF/DomPDF.php +++ b/src/PhpWord/Writer/PDF/DomPDF.php @@ -64,14 +64,6 @@ public function save(string $filename): void $orientation = 'portrait'; // Create PDF - $restoreHandler = false; - if (PHP_VERSION_ID >= self::$temporaryVersionCheck) { - // @codeCoverageIgnoreStart - $errhandler = [$this, 'specialErrorHandler']; - set_error_handler($errhandler); - $restoreHandler = true; - // @codeCoverageIgnoreEnd - } $pdf = $this->createExternalWriterInstance(); $pdf->setPaper(strtolower($paperSize), $orientation); $pdf->loadHtml(str_replace(PHP_EOL, '', $this->getContent())); @@ -80,28 +72,6 @@ public function save(string $filename): void // Write to file fwrite($fileHandle, $pdf->output()); - if ($restoreHandler) { - restore_error_handler(); // @codeCoverageIgnore - } parent::restoreStateAfterSave($fileHandle); } - - /** @var int */ - protected static $temporaryVersionCheck = 80500; - - /** - * Temporary handler for Php8.5 waiting for Dompdf release. - * - * @codeCoverageIgnore - */ - public function specialErrorHandler(int $errno, string $errstr, string $filename, int $lineno): bool - { - if ($errno === E_DEPRECATED) { - if (preg_match('/canonical|imagedestroy|http_response_header|Using null as an array offset/', $errstr) === 1) { - return true; - } - } - - return false; // continue error handling - } } From 8ee8fe8bfbcb4020d12e348e5594ecfccc28c39c Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 22 Sep 2025 01:34:41 -0700 Subject: [PATCH 085/153] Small Coverage Improvements --- src/PhpWord/TemplateProcessor.php | 3 - .../Writer/ODText/Style/Paragraph3Test.php | 65 +++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 tests/PhpWordTests/Writer/ODText/Style/Paragraph3Test.php diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index ffd8ba8374..92f0229e1d 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -282,9 +282,6 @@ public function setComplexValue($search, Element\AbstractElement $complexType, b { $originalSearch = $search; $elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1); - if ($elementName === 'Section') { - $elementName = 'Container'; - } $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; $xmlWriter = new XMLWriter(); diff --git a/tests/PhpWordTests/Writer/ODText/Style/Paragraph3Test.php b/tests/PhpWordTests/Writer/ODText/Style/Paragraph3Test.php new file mode 100644 index 0000000000..f8e5c3ffbe --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Style/Paragraph3Test.php @@ -0,0 +1,65 @@ +addParagraphStyle('alignCenter', [ + 'align' => 'center', + ]); + $alignCenter = new Paragraph(); + $alignCenter->setStyleName('alignCenter'); + $phpWord->addParagraphStyle('alignEnd', [ + 'align' => 'end', + ]); + $alignEnd = new Paragraph(); + $alignEnd->setStyleName('alignEnd'); + $section = $phpWord->addSection(); + $section->addText('Should be aligned center.', null, $alignCenter); + $tr = $section->addTextRun($alignEnd); + $tr->addText('Should be aligned right.'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:body/office:text/text:section'; + $element = "$s2a/text:p[2]"; + self::assertEquals('alignCenter', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:p[3]"; + self::assertEquals('alignEnd', $doc->getElementAttribute($element, 'text:style-name')); + + $doc->setDefaultFile('styles.xml'); + $element = '/office:document-styles/office:styles/style:style[1]'; + self::assertEquals('alignCenter', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('center', $doc->getElementAttribute($element, 'fo:text-align')); + $element = '/office:document-styles/office:styles/style:style[2]'; + self::assertEquals('alignEnd', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('end', $doc->getElementAttribute($element, 'fo:text-align')); + } +} From 40a8013828c891925e30b7cffb07f98b10021afe Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:54:44 -0700 Subject: [PATCH 086/153] More Coverage for Writer Html Ruby --- src/PhpWord/Writer/HTML/Element/Ruby.php | 21 +++++----------- .../Writer/HTML/Element/RubyTest.php | 24 ++++++++++++++++++- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/PhpWord/Writer/HTML/Element/Ruby.php b/src/PhpWord/Writer/HTML/Element/Ruby.php index 5648d85e2f..ddcaac5872 100644 --- a/src/PhpWord/Writer/HTML/Element/Ruby.php +++ b/src/PhpWord/Writer/HTML/Element/Ruby.php @@ -58,27 +58,18 @@ public function write() return $content; } + private const mapAlignment = [ + RubyProperties::ALIGNMENT_CENTER => 'center', + RubyProperties::ALIGNMENT_LEFT => 'start', + ]; + /** * Get property CSS for the tag. */ private function getPropertyCssForRubyTag(RubyProperties $properties): string { // alignment CSS: https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-align - $alignment = 'space-between'; - switch ($properties->getAlignment()) { - case RubyProperties::ALIGNMENT_CENTER: - $alignment = 'center'; - - break; - case RubyProperties::ALIGNMENT_LEFT: - $alignment = 'start'; - - break; - default: - $alignment = 'space-between'; - - break; - } + $alignment = self::mapAlignment[$properties->getAlignment()] ?? 'space-between'; return 'font-size:' . $properties->getFontSizeForBaseText() . 'pt' . ';' . diff --git a/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php b/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php index 2ca556bc51..9ac3dc1504 100644 --- a/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php +++ b/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php @@ -82,9 +82,19 @@ public function testWriteRubyHtmlParagraphStyle(): void $rubyTextRun->addText('わたし'); $section->addRuby($baseTextRun, $rubyTextRun, $properties); + $phpWord->addParagraphStyle('lineHeight10', [ + 'lineHeight' => '10', + ]); + $properties2 = new RubyProperties(); + $baseTextRun2 = new TextRun('lineHeight10'); + $baseTextRun2->addText('私'); + $rubyTextRun2 = new TextRun(['lineHeight' => '4']); + $rubyTextRun2->addText('わたし'); + $section->addRuby($baseTextRun2, $rubyTextRun2, $properties2); + $dom = Helper::getAsHTML($phpWord, '', '', ['ruby', 'rt', 'rp']); $xpath = new DOMXPath($dom); - self::assertEquals(1, $xpath->query('/html/body/div/ruby')->length); + self::assertEquals(2, $xpath->query('/html/body/div/ruby')->length); // ensure text is right $rubyElement = $dom->getElementsByTagName('ruby')->item(0); $rtElement = $dom->getElementsByTagName('rt')->item(0); @@ -95,5 +105,17 @@ public function testWriteRubyHtmlParagraphStyle(): void // check style self::assertEquals('line-height: 8;font-size:20pt;ruby-align:center;', $rubyElement->attributes->getNamedItem('style')->textContent); self::assertEquals('line-height: 4;font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); + + // ensure text is right + $rubyElement2 = $dom->getElementsByTagName('ruby')->item(1); + $rtElement2 = $dom->getElementsByTagName('rt')->item(1); + self::assertNotNull($rubyElement2); + self::assertNotNull($rtElement2); + self::assertEquals($baseTextRun2->getText() . ' (' . $rubyTextRun2->getText() . ')', $rubyElement2->textContent); + self::assertEquals($rubyTextRun2->getText(), $rtElement2->textContent); + // check style + self::assertEquals('font-size:24pt;ruby-align:space-between;', $rubyElement2->attributes->getNamedItem('style')->textContent); + self::assertEquals('lineHeight10', $rubyElement2->attributes->getNamedItem('class')->textContent); + self::assertEquals('line-height: 4;font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); } } From 45a9d32bb9cb883a6000f45a82733bd08f8989c8 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 22 Sep 2025 14:08:57 -0700 Subject: [PATCH 087/153] Fix PHPMD Problem --- src/PhpWord/Writer/HTML/Element/Ruby.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpWord/Writer/HTML/Element/Ruby.php b/src/PhpWord/Writer/HTML/Element/Ruby.php index ddcaac5872..db8d2f9d32 100644 --- a/src/PhpWord/Writer/HTML/Element/Ruby.php +++ b/src/PhpWord/Writer/HTML/Element/Ruby.php @@ -58,7 +58,7 @@ public function write() return $content; } - private const mapAlignment = [ + private const MAP_ALIGNMENT = [ RubyProperties::ALIGNMENT_CENTER => 'center', RubyProperties::ALIGNMENT_LEFT => 'start', ]; @@ -69,7 +69,7 @@ public function write() private function getPropertyCssForRubyTag(RubyProperties $properties): string { // alignment CSS: https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-align - $alignment = self::mapAlignment[$properties->getAlignment()] ?? 'space-between'; + $alignment = self::MAP_ALIGNMENT[$properties->getAlignment()] ?? 'space-between'; return 'font-size:' . $properties->getFontSizeForBaseText() . 'pt' . ';' . From 90758172fcb639965d740b77ee2a958ecae1efb6 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 23 Sep 2025 22:50:37 -0700 Subject: [PATCH 088/153] Test if Coveralls is Fixed --- .gitignore | 2 +- phpunit.xml.dist | 1 + .../Writer/HTML/Element/RubyTest.php | 30 +++++++++---------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index f7a641d0a9..5ccb032d24 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ Thumbs.db Desktop.ini .idea +/analysis _build /build phpunit.xml @@ -15,7 +16,6 @@ composer.phar composer.lock vendor /report -/build /samples/results /.settings phpword.ini diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b97fbee3fb..fd357d9ade 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,7 @@ + diff --git a/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php b/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php index 9ac3dc1504..1cf7bdb07c 100644 --- a/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php +++ b/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php @@ -49,17 +49,17 @@ public function testWriteRubyHtml(): void $dom = Helper::getAsHTML($phpWord, '', '', ['ruby', 'rt', 'rp']); $xpath = new DOMXPath($dom); - self::assertEquals(1, $xpath->query('/html/body/div/ruby')->length); + self::assertSame(1, $xpath->query('/html/body/div/ruby')->length); // ensure text is right $rubyElement = $dom->getElementsByTagName('ruby')->item(0); $rtElement = $dom->getElementsByTagName('rt')->item(0); self::assertNotNull($rubyElement); self::assertNotNull($rtElement); - self::assertEquals($baseTextRun->getText() . ' (' . $rubyTextRun->getText() . ')', $rubyElement->textContent); - self::assertEquals($rubyTextRun->getText(), $rtElement->textContent); + self::assertSame($baseTextRun->getText() . ' (' . $rubyTextRun->getText() . ')', $rubyElement->textContent); + self::assertSame($rubyTextRun->getText(), $rtElement->textContent); // check style - self::assertEquals('font-size:20pt;ruby-align:center;', $rubyElement->attributes->getNamedItem('style')->textContent); - self::assertEquals('font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); + self::assertSame('font-size:20pt;ruby-align:center;', $rubyElement->attributes->getNamedItem('style')->textContent); + self::assertSame('font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); } /** @@ -94,28 +94,28 @@ public function testWriteRubyHtmlParagraphStyle(): void $dom = Helper::getAsHTML($phpWord, '', '', ['ruby', 'rt', 'rp']); $xpath = new DOMXPath($dom); - self::assertEquals(2, $xpath->query('/html/body/div/ruby')->length); + self::assertSame(2, $xpath->query('/html/body/div/ruby')->length); // ensure text is right $rubyElement = $dom->getElementsByTagName('ruby')->item(0); $rtElement = $dom->getElementsByTagName('rt')->item(0); self::assertNotNull($rubyElement); self::assertNotNull($rtElement); - self::assertEquals($baseTextRun->getText() . ' (' . $rubyTextRun->getText() . ')', $rubyElement->textContent); - self::assertEquals($rubyTextRun->getText(), $rtElement->textContent); + self::assertSame($baseTextRun->getText() . ' (' . $rubyTextRun->getText() . ')', $rubyElement->textContent); + self::assertSame($rubyTextRun->getText(), $rtElement->textContent); // check style - self::assertEquals('line-height: 8;font-size:20pt;ruby-align:center;', $rubyElement->attributes->getNamedItem('style')->textContent); - self::assertEquals('line-height: 4;font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); + self::assertSame('line-height: 8;font-size:20pt;ruby-align:center;', $rubyElement->attributes->getNamedItem('style')->textContent); + self::assertSame('line-height: 4;font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); // ensure text is right $rubyElement2 = $dom->getElementsByTagName('ruby')->item(1); $rtElement2 = $dom->getElementsByTagName('rt')->item(1); self::assertNotNull($rubyElement2); self::assertNotNull($rtElement2); - self::assertEquals($baseTextRun2->getText() . ' (' . $rubyTextRun2->getText() . ')', $rubyElement2->textContent); - self::assertEquals($rubyTextRun2->getText(), $rtElement2->textContent); + self::assertSame($baseTextRun2->getText() . ' (' . $rubyTextRun2->getText() . ')', $rubyElement2->textContent); + self::assertSame($rubyTextRun2->getText(), $rtElement2->textContent); // check style - self::assertEquals('font-size:24pt;ruby-align:space-between;', $rubyElement2->attributes->getNamedItem('style')->textContent); - self::assertEquals('lineHeight10', $rubyElement2->attributes->getNamedItem('class')->textContent); - self::assertEquals('line-height: 4;font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); + self::assertSame('font-size:24pt;ruby-align:space-between;', $rubyElement2->attributes->getNamedItem('style')->textContent); + self::assertSame('lineHeight10', $rubyElement2->attributes->getNamedItem('class')->textContent); + self::assertSame('line-height: 4;font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); } } From 28c7540ea1b35b47ea441ce2d73d54140bdaa1fe Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 24 Sep 2025 00:09:28 -0700 Subject: [PATCH 089/153] More Coveralls Experiments --- .github/workflows/php.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 94f09973c3..88f6922212 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -138,9 +138,8 @@ jobs: - name: Upload coverage results to Coveralls if: matrix.php == '8.3' - env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar - chmod +x php-coveralls.phar - php php-coveralls.phar --coverage_clover=build/clover.xml --json_path=build/coveralls-upload.json -vvv + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + file: build/clover.xml + format: clover From c6403b0e366c5c11aa3885efa2ce956c3d2d3c86 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 24 Sep 2025 16:22:31 -0700 Subject: [PATCH 090/153] More Coveralls Experiments --- .github/workflows/php.yml | 12 +++++++----- .../Writer/Word2007/Element/ChartTest.php | 5 +++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 88f6922212..b5bca61ba1 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -138,8 +138,10 @@ jobs: - name: Upload coverage results to Coveralls if: matrix.php == '8.3' - uses: coverallsapp/github-action@v2 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - file: build/clover.xml - format: clover + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FAILURE_ACTION: "${{ matrix.php == '8.3' }}" + run: | + wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar + chmod +x php-coveralls.phar + php php-coveralls.phar --coverage_clover=build/clover.xml --json_path=build/coveralls-upload.json -vvv diff --git a/tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php b/tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php index 4951da39ce..d422ec9457 100644 --- a/tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php @@ -190,6 +190,7 @@ public function testValueAxisTitle(): void 'showGridY' => true, 'showLegend' => false, 'valueAxisTitle' => 'Values', + 'categoryAxisTitle' => 'Categories', ]; $chartType = 'line'; $categories = ['A', 'B', 'C', 'D', 'E']; @@ -208,6 +209,10 @@ public function testValueAxisTitle(): void self::assertTrue($doc->elementExists($element)); $element .= '/c:title/c:tx/c:rich/a:p/a:r/a:t'; self::assertEquals('Values', $doc->getElement($element)->nodeValue); + $element = "$path/c:catAx"; + self::assertTrue($doc->elementExists($element)); + $element .= '/c:title/c:tx/c:rich/a:p/a:r/a:t'; + self::assertEquals('Categories', $doc->getElement($element)->nodeValue); } public function testNoAxisLabels(): void From b5ff8d1919b5aad6ae338736592612b470248342 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 24 Sep 2025 17:12:38 -0700 Subject: [PATCH 091/153] Yet Another Coveralls Experiment --- .github/workflows/php.yml | 10 +++++----- src/PhpWord/TemplateProcessor.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index b5bca61ba1..7572ade658 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -139,9 +139,9 @@ jobs: - name: Upload coverage results to Coveralls if: matrix.php == '8.3' env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} FAILURE_ACTION: "${{ matrix.php == '8.3' }}" - run: | - wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar - chmod +x php-coveralls.phar - php php-coveralls.phar --coverage_clover=build/clover.xml --json_path=build/coveralls-upload.json -vvv + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + file: build/clover.xml + format: clover diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 92f0229e1d..31f06c14d4 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -243,7 +243,7 @@ public function applyXslStyleSheet($xslDomDocument, $xslOptions = [], $xslOption $xsltProcessor->importStylesheet($xslDomDocument); if (false === $xsltProcessor->setParameter($xslOptionsUri, $xslOptions)) { - throw new Exception('Could not set values for the given XSL style sheet parameters.'); + throw new Exception('Could not set values for the given XSL style sheet parameters.'); // @codeCoverageIgnore } $this->tempDocumentHeaders = $this->transformXml($this->tempDocumentHeaders, $xsltProcessor); From 0dad8de6d840b470e8a9e4522a7059b8bc4ead60 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 24 Sep 2025 21:04:24 -0700 Subject: [PATCH 092/153] Still More Coverage --- src/PhpWord/Element/AbstractElement.php | 8 ++++++++ src/PhpWord/Reader/Word2007/AbstractPart.php | 6 +----- tests/PhpWordTests/Element/TableTest.php | 3 ++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/PhpWord/Element/AbstractElement.php b/src/PhpWord/Element/AbstractElement.php index 3a29b68673..9b0aee6fa0 100644 --- a/src/PhpWord/Element/AbstractElement.php +++ b/src/PhpWord/Element/AbstractElement.php @@ -298,6 +298,10 @@ public function getCommentsRangeStart(): ?Comments /** * Get comment start. + * This code just plain doesn't work, but it's not clear what is should do. + * Unsurprisingly untested. + * + * @codeCoverageIgnore */ public function getCommentRangeStart(): ?Comment { @@ -342,6 +346,10 @@ public function getCommentsRangeEnd(): ?Comments /** * Get comment end. + * This code just plain doesn't work, but it's not clear what is should do. + * Unsurprisingly untested. + * + * @codeCoverageIgnore */ public function getCommentRangeEnd(): ?Comment { diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index d5f0610663..b59970cf54 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -575,11 +575,7 @@ protected function readRunChild(XMLReader $xmlReader, DOMElement $node, Abstract if ($runParent->nodeName == 'w:hyperlink') { $rId = $xmlReader->getAttribute('r:id', $runParent); $target = $this->getMediaTarget($docPart, $rId); - if (null !== $target) { - $parent->addLink($target, $textContent, $fontStyle, $paragraphStyle); - } else { - $parent->addText($textContent, $fontStyle, $paragraphStyle); - } + $parent->addLink($target ?? 'http://example.com', $textContent, $fontStyle, $paragraphStyle); } else { /** @var AbstractElement $element */ $element = $parent->addText($textContent, $fontStyle, $paragraphStyle); diff --git a/tests/PhpWordTests/Element/TableTest.php b/tests/PhpWordTests/Element/TableTest.php index 0628d269e6..c90e9ede32 100644 --- a/tests/PhpWordTests/Element/TableTest.php +++ b/tests/PhpWordTests/Element/TableTest.php @@ -91,8 +91,9 @@ public function testCell(): void { $oTable = new Table(); $oTable->addRow(); - $element = $oTable->addCell(); + $element = $oTable->addCell(1234); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Cell', $element); + self::assertSame(1234, $element->getWidth()); } /** From 7591e612358e7e061766ce8f50c3538f494cbca5 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 24 Sep 2025 22:27:14 -0700 Subject: [PATCH 093/153] A Little More Coverage --- tests/PhpWordTests/Element/TableTest.php | 5 +++-- tests/PhpWordTests/WriteReadback/Word2007Test.php | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/PhpWordTests/Element/TableTest.php b/tests/PhpWordTests/Element/TableTest.php index c90e9ede32..a5169040a9 100644 --- a/tests/PhpWordTests/Element/TableTest.php +++ b/tests/PhpWordTests/Element/TableTest.php @@ -91,9 +91,10 @@ public function testCell(): void { $oTable = new Table(); $oTable->addRow(); - $element = $oTable->addCell(1234); + $element = $oTable->addCell(); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Cell', $element); - self::assertSame(1234, $element->getWidth()); + $element->getStyle()->setWidth(1236); + self::assertSame(1236, $element->getStyle()->getWidth()); } /** diff --git a/tests/PhpWordTests/WriteReadback/Word2007Test.php b/tests/PhpWordTests/WriteReadback/Word2007Test.php index 572977ccf8..858faa77fe 100644 --- a/tests/PhpWordTests/WriteReadback/Word2007Test.php +++ b/tests/PhpWordTests/WriteReadback/Word2007Test.php @@ -129,6 +129,8 @@ public function testZoom(): void $phpWordWriter = new PhpWord(); $zoomLevel = 75; $phpWordWriter->getSettings()->setZoom($zoomLevel); + $phpWordWriter->getSettings()->setConsecutiveHyphenLimit(3); + $phpWordWriter->getSettings()->setAutoHyphenation(true); $writer = new Word2007($phpWordWriter); $file = __DIR__ . '/../_files/temp.docx'; @@ -139,6 +141,8 @@ public function testZoom(): void $phpWordReader = IOFactory::load($file, 'Word2007'); self::assertEquals($zoomLevel, $phpWordReader->getSettings()->getZoom()); + self::assertSame(3, $phpWordReader->getSettings()->getConsecutiveHyphenLimit()); + self::assertTrue($phpWordReader->getSettings()->hasAutoHyphenation()); unlink($file); } From 94cb3d6b46939c70fe05e6da3b7ac35e6744fcb8 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 26 Sep 2025 15:30:42 -0700 Subject: [PATCH 094/153] Recognize Type on Nested List --- samples/Sample_14_ListItem.php | 28 ++++++++++++++++++++ src/PhpWord/Shared/Html.php | 48 +++++++++++++++++----------------- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/samples/Sample_14_ListItem.php b/samples/Sample_14_ListItem.php index f82c3ec064..98c3042faf 100644 --- a/samples/Sample_14_ListItem.php +++ b/samples/Sample_14_ListItem.php @@ -95,6 +95,34 @@ $section->addTitle('Heading 2', 2); $section->addTitle('Heading 3', 3); +$html = ' +

Loading from html, including type on nested list.

+
    +
  1. Items in the first level of the list +
      +
    1. Sub-item 1 (lcroman)
    2. +
    +
  2. +
  3.  Another item at the first level
  4. +
  5. Yet another item at the first level
  6. +
      +
    1. Sub-item 1 (lcalpha)
    2. +
    3. Sub-item 2
    4. +
    +
+

End of html load.

+'; +PhpOffice\PhpWord\Shared\Html::addHtml($section, $html, false, false); +$section->addText('Testing setNumId for new numbering start (there are better ways to do this).'); +$predefinedStyle = ['listType' => PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER]; +$section->addText('First List'); +$section->addListItem('First List Item 1', 0, null, $predefinedStyle); +$section->addListItem('First List Item 2', 0, null, $predefinedStyle); +$section->addText('Second List'); +$temp = $section->addListItem('Second List Item 1', 0, null, $predefinedStyle); +$temp->getStyle()->setNumId(0); +$section->addText('End of setNumId test.'); + // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); if (!CLI) { diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 52c55aaf3c..ce4e922fa6 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -694,35 +694,35 @@ protected static function parseList($node, $element, &$styles, &$data) ++$data['listdepth']; } else { $data['listdepth'] = 0; - $styles['list'] = 'listStyle_' . self::$listIndex++; - $style = $element->getPhpWord()->addNumberingStyle($styles['list'], self::getListStyle($isOrderedList)); + } + $styles['list'] = 'listStyle_' . self::$listIndex++; + $style = $element->getPhpWord()->addNumberingStyle($styles['list'], self::getListStyle($isOrderedList)); - // extract attributes start & type e.g.
    - $start = 0; - $type = ''; - foreach ($node->attributes as $attribute) { - switch ($attribute->name) { - case 'start': - $start = (int) $attribute->value; + // extract attributes start & type e.g.
      + $start = 0; + $type = ''; + foreach ($node->attributes as $attribute) { + switch ($attribute->name) { + case 'start': + $start = (int) $attribute->value; - break; - case 'type': - $type = $attribute->value; + break; + case 'type': + $type = $attribute->value; - break; - } + break; } + } - $levels = $style->getLevels(); - /** @var \PhpOffice\PhpWord\Style\NumberingLevel */ - $level = $levels[0]; - if ($start > 0) { - $level->setStart($start); - } - $type = $type ? self::mapListType($type) : null; - if ($type) { - $level->setFormat($type); - } + $levels = $style->getLevels(); + /** @var \PhpOffice\PhpWord\Style\NumberingLevel */ + $level = $levels[$data['listdepth']]; + if ($start > 0) { + $level->setStart($start); + } + $type = $type ? self::mapListType($type) : null; + if ($type) { + $level->setFormat($type); } if ($node->parentNode->nodeName === 'li') { return $element->getParent(); From 88d9b7285c0fd77e285b061654169754c5b0fe11 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 27 Sep 2025 09:06:14 -0700 Subject: [PATCH 095/153] Autoloader Wasn't Really Tested --- .github/workflows/php.yml | 6 ++++++ samples/Sample_45_Autoloader.php | 2 +- src/PhpWord/Autoloader.php | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 7572ade658..0ad3bb87f4 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -132,6 +132,12 @@ jobs: FAILURE_ACTION: "${{ matrix.experimental == true }}" run: ./vendor/bin/phpunit -c phpunit10.xml.dist --no-coverage || $FAILURE_ACTION + - name: Test non-Composer Autoloader + if: matrix.php == 'nightly' + env: + FAILURE_ACTION: "${{ matrix.experimental == true }}" + run: php samples/Sample_45_Autoloader.php + - name: Run phpunit 8.3 if: matrix.php == '8.3' run: ./vendor/bin/phpunit -c phpunit10.xml.dist --coverage-clover build/clover.xml diff --git a/samples/Sample_45_Autoloader.php b/samples/Sample_45_Autoloader.php index ac53c05367..b6a375d04b 100644 --- a/samples/Sample_45_Autoloader.php +++ b/samples/Sample_45_Autoloader.php @@ -5,7 +5,7 @@ // When following define is TRUE, // this script will use PhpWord autoloader rather than Composer, // and will not be able to find Pdf renderer. -define('USE_AUTOLOADER', PHP_MINOR_VERSION % 2 === 0); +define('USE_AUTOLOADER', getenv('DONTUSEAUTOLOADER') !== '1'); include_once 'Sample_Header.php'; diff --git a/src/PhpWord/Autoloader.php b/src/PhpWord/Autoloader.php index 122d9c05fd..1fffc9cb66 100644 --- a/src/PhpWord/Autoloader.php +++ b/src/PhpWord/Autoloader.php @@ -38,10 +38,14 @@ public static function autoload(string $class): void if (!$file) { return; } + // We can't get here testing with Phpunit, which has + // already autoloaded everything relevant. + //* @codeCoverageIgnoreStart if (file_exists($file)) { /** @noinspection PhpIncludeInspection Dynamic includes */ require_once $file; } + //* @codeCoverageIgnoreEnd } } } From 78c407588fdaf6c2e47e39497085029d515f792e Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 27 Sep 2025 09:19:57 -0700 Subject: [PATCH 096/153] Retry Prior Change --- .github/workflows/php.yml | 1 - src/PhpWord/Autoloader.php | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 0ad3bb87f4..796814aa17 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -133,7 +133,6 @@ jobs: run: ./vendor/bin/phpunit -c phpunit10.xml.dist --no-coverage || $FAILURE_ACTION - name: Test non-Composer Autoloader - if: matrix.php == 'nightly' env: FAILURE_ACTION: "${{ matrix.experimental == true }}" run: php samples/Sample_45_Autoloader.php diff --git a/src/PhpWord/Autoloader.php b/src/PhpWord/Autoloader.php index 1fffc9cb66..5bd0176d30 100644 --- a/src/PhpWord/Autoloader.php +++ b/src/PhpWord/Autoloader.php @@ -40,12 +40,12 @@ public static function autoload(string $class): void } // We can't get here testing with Phpunit, which has // already autoloaded everything relevant. - //* @codeCoverageIgnoreStart + // @codeCoverageIgnoreStart if (file_exists($file)) { /** @noinspection PhpIncludeInspection Dynamic includes */ require_once $file; } - //* @codeCoverageIgnoreEnd + // @codeCoverageIgnoreEnd } } } From b8d51c256ab65202674d3a95d32feb58c62d6e57 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 27 Sep 2025 10:11:09 -0700 Subject: [PATCH 097/153] Replace spl_object_id with spl_object_hash Because we still have to support Php 7.1 :-( The reason Php 7.1 has succeeded in its unit tests till now is because symfony/polyfill-php72 is added during its Composer install step. This undocumented dependency should not be relied on. --- src/PhpWord/Shared/XMLWriter.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PhpWord/Shared/XMLWriter.php b/src/PhpWord/Shared/XMLWriter.php index e2461586e6..4bade2fb2d 100644 --- a/src/PhpWord/Shared/XMLWriter.php +++ b/src/PhpWord/Shared/XMLWriter.php @@ -51,7 +51,7 @@ class XMLWriter extends \XMLWriter */ private $tempFileName = ''; - /** @var int */ + /** @var string */ private $hash; /** @@ -63,7 +63,7 @@ class XMLWriter extends \XMLWriter */ public function __construct($pTemporaryStorage = self::STORAGE_MEMORY, $pTemporaryStorageDir = null, $compatibility = false) { - $this->hash = spl_object_id($this); + $this->hash = spl_object_hash($this); // Open temporary storage if ($pTemporaryStorage == self::STORAGE_MEMORY) { $this->openMemory(); @@ -92,7 +92,7 @@ public function __construct($pTemporaryStorage = self::STORAGE_MEMORY, $pTempora */ public function __destruct() { - if ($this->hash !== spl_object_id($this)) { + if ($this->hash !== spl_object_hash($this)) { throw new WordException('Unserialize not permitted1'); } // Unlink temporary files From dbd586380f710d7ecafcd08e1e5bcacad9995e6b Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 27 Sep 2025 12:44:10 -0700 Subject: [PATCH 098/153] Improve Ruby Support for Odtext --- src/PhpWord/Element/TextRun.php | 4 +- src/PhpWord/Writer/ODText/Element/Ruby.php | 41 ++++++++++++------- .../Writer/ODText/ElementTest.php | 37 ++++++++++++----- 3 files changed, 54 insertions(+), 28 deletions(-) diff --git a/src/PhpWord/Element/TextRun.php b/src/PhpWord/Element/TextRun.php index 0c9a2322a7..7b9e3055a0 100644 --- a/src/PhpWord/Element/TextRun.php +++ b/src/PhpWord/Element/TextRun.php @@ -40,7 +40,7 @@ class TextRun extends AbstractContainer /** * Create new instance. * - * @param array|Paragraph|string $paragraphStyle + * @param null|array|Paragraph|string $paragraphStyle */ public function __construct($paragraphStyle = null) { @@ -60,7 +60,7 @@ public function getParagraphStyle() /** * Set Paragraph style. * - * @param array|Paragraph|string $style + * @param null|array|Paragraph|string $style * * @return Paragraph|string */ diff --git a/src/PhpWord/Writer/ODText/Element/Ruby.php b/src/PhpWord/Writer/ODText/Element/Ruby.php index 41a86776d4..f03b6b4562 100644 --- a/src/PhpWord/Writer/ODText/Element/Ruby.php +++ b/src/PhpWord/Writer/ODText/Element/Ruby.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\ODText\Element; +use PhpOffice\PhpWord\Element\TextRun; + /** * Ruby element writer. * NOTE: This class will write out a Ruby element in the format {baseText} ({rubyText}) @@ -37,28 +39,37 @@ public function write(): void if (!$element instanceof \PhpOffice\PhpWord\Element\Ruby) { return; } - $paragraphStyle = $element->getBaseTextRun()->getParagraphStyle(); - if (!$this->withoutP) { $xmlWriter->startElement('text:p'); // text:p } - if (empty($paragraphStyle)) { - if (!$this->withoutP) { - $xmlWriter->writeAttribute('text:style-name', 'Normal'); - } - } elseif (is_string($paragraphStyle)) { - if (!$this->withoutP) { + $xmlWriter->startElement('text:ruby'); + $this->writeRuby($element->getBaseTextRun(), 'text:ruby-base'); + $this->writeRuby($element->getRubyTextRun(), 'text:ruby-text'); + $xmlWriter->endElement(); // text:ruby + if (!$this->withoutP) { + $xmlWriter->endElement(); // text:p + } + } + + /** + * @param TextRun $textRun + * @param string $tag + */ + private function writeRuby($textRun, $tag): void + { + $xmlWriter = $this->getXmlWriter(); + $paragraphStyle = $textRun->getParagraphStyle(); + + $xmlWriter->startElement($tag); // text:rubyBase or text:rubyText + if (is_string($paragraphStyle)) { + $paragraphStyle = trim($paragraphStyle); + if ($paragraphStyle !== '') { $xmlWriter->writeAttribute('text:style-name', $paragraphStyle); } } - $this->replaceTabs($element->getBaseTextRun()->getText(), $xmlWriter); - $this->writeText(' ('); - $this->replaceTabs($element->getRubyTextRun()->getText(), $xmlWriter); - $this->writeText(')'); + $this->replaceTabs($textRun->getText(), $xmlWriter); - if (!$this->withoutP) { - $xmlWriter->endElement(); // text:p - } + $xmlWriter->endElement(); // text:rubyBase or text:rubyText } } diff --git a/tests/PhpWordTests/Writer/ODText/ElementTest.php b/tests/PhpWordTests/Writer/ODText/ElementTest.php index 8ca327717c..1cbb7c0acc 100644 --- a/tests/PhpWordTests/Writer/ODText/ElementTest.php +++ b/tests/PhpWordTests/Writer/ODText/ElementTest.php @@ -23,6 +23,7 @@ use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\XMLWriter; use PhpOffice\PhpWordTests\TestHelperDOCX; @@ -31,11 +32,20 @@ */ class ElementTest extends \PHPUnit\Framework\TestCase { + /** @var bool */ + private $esc; + + protected function setUp(): void + { + $this->esc = Settings::isOutputEscapingEnabled(); + } + /** * Executed after each method of the class. */ protected function tearDown(): void { + Settings::setOutputEscapingEnabled($this->esc); TestHelperDOCX::clear(); } @@ -241,15 +251,13 @@ public function testTextRunTitle(): void */ public function testTextWithAmpersand(): void { - $esc = \PhpOffice\PhpWord\Settings::isOutputEscapingEnabled(); - \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); + Settings::setOutputEscapingEnabled(true); $phpWord = new PhpWord(); $section = $phpWord->addSection(); $txt = 'this text contains an & (ampersand)'; $section->addText($txt); $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); - \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled($esc); $p2t = '/office:document-content/office:body/office:text/text:section'; $element = "$p2t/text:p[2]"; self::assertTrue($doc->elementExists($element)); @@ -340,8 +348,7 @@ public function testTrackedChanges(): void */ public function testRubyText(): void { - $esc = \PhpOffice\PhpWord\Settings::isOutputEscapingEnabled(); - \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); + Settings::setOutputEscapingEnabled(true); $phpWord = new PhpWord(); $section = $phpWord->addSection(); $properties = new RubyProperties(); @@ -351,17 +358,25 @@ public function testRubyText(): void $properties->setFontSizeForBaseText(18); $properties->setLanguageId('ja-JP'); - $baseTextRun = new TextRun(null); + $phpWord->addParagraphStyle('lineHeight10', [ + 'lineHeight' => '10', + ]); + $baseTextRun = new TextRun('lineHeight10'); $baseTextRun->addText('私'); - $rubyTextRun = new TextRun(null); + $rubyTextRun = new TextRun(['lineHeight' => '4']); $rubyTextRun->addText('わたし'); $section->addRuby($baseTextRun, $rubyTextRun, $properties); $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); - \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled($esc); $p2t = '/office:document-content/office:body/office:text/text:section'; - $element = "$p2t/text:p[2]"; - self::assertTrue($doc->elementExists($element)); - self::assertEquals('私 (わたし)', $doc->getElement($element)->nodeValue); + $textRuby = "$p2t/text:p[2]/text:ruby"; + self::assertTrue($doc->elementExists($textRuby)); + $textRubyBase = "$textRuby/text:ruby-base"; + self::assertTrue($doc->elementExists($textRubyBase)); + self::assertEquals('私', $doc->getElement($textRubyBase)->nodeValue); + self::assertEquals('lineHeight10', $doc->getElementAttribute($textRubyBase, 'text:style-name')); + $textRubyText = "$textRuby/text:ruby-text"; + self::assertTrue($doc->elementExists($textRubyText)); + self::assertEquals('わたし', $doc->getElement($textRubyText)->nodeValue); } } From d716bebd3b8f6ef1ff37ea086f6364285c4d92d9 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 27 Sep 2025 16:22:55 -0700 Subject: [PATCH 099/153] More Coverage --- src/PhpWord/Writer/Word2007/Element/Field.php | 29 ++++++--------- tests/PhpWordTests/Writer/RTF/NoParent.php | 35 +++++++++++++++++++ .../PhpWordTests/Writer/RTF/NoParentTest.php | 32 +++++++++++++++++ 3 files changed, 78 insertions(+), 18 deletions(-) create mode 100644 tests/PhpWordTests/Writer/RTF/NoParent.php create mode 100644 tests/PhpWordTests/Writer/RTF/NoParentTest.php diff --git a/src/PhpWord/Writer/Word2007/Element/Field.php b/src/PhpWord/Writer/Word2007/Element/Field.php index c02bbdbcc9..f2c6355150 100644 --- a/src/PhpWord/Writer/Word2007/Element/Field.php +++ b/src/PhpWord/Writer/Word2007/Element/Field.php @@ -305,29 +305,22 @@ protected function writeRef(ElementField $element): void $this->endElementP(); // w:p } + private const OPTION_VALUES = [ + 'IncrementAndInsertText' => '\\f', + 'CreateHyperLink' => '\\h', + 'NoTrailingPeriod' => '\\n', + 'IncludeAboveOrBelow' => '\\p', + 'InsertParagraphNumberRelativeContext' => '\\r', + 'SuppressNonDelimiterNonNumericalText' => '\\t', + 'InsertParagraphNumberFullContext' => '\\w', + ]; + 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 ''; - } + return self::OPTION_VALUES[$optionValue] ?? ''; } } diff --git a/tests/PhpWordTests/Writer/RTF/NoParent.php b/tests/PhpWordTests/Writer/RTF/NoParent.php new file mode 100644 index 0000000000..eeb7c1ad5c --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/NoParent.php @@ -0,0 +1,35 @@ +expectException(WordException::class); + $this->expectExceptionMessage('No parent WriterInterface assigned.'); + $noParent = new NoParent(); + $noParent->getParentWriter(); + } +} From 129d43bfc7c953362c55a62f1a8096e3b3e57254 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 27 Sep 2025 19:03:09 -0700 Subject: [PATCH 100/153] Field Updates --- src/PhpWord/Element/Field.php | 7 ++++++- src/PhpWord/Writer/Word2007/Element/Field.php | 9 +++++++-- tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php | 3 ++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/PhpWord/Element/Field.php b/src/PhpWord/Element/Field.php index 592d6e421c..ccaf29509c 100644 --- a/src/PhpWord/Element/Field.php +++ b/src/PhpWord/Element/Field.php @@ -94,7 +94,12 @@ class Field extends AbstractElement ], 'REF' => [ 'properties' => ['name' => ''], - 'options' => ['f', 'h', 'n', 'p', 'r', 't', 'w'], + 'options' => [ + 'f', 'h', 'n', 'p', 'r', 't', 'w', + 'd' => [',', '.', ''], + 'NumberSeperatorSequence' => [',', '.', ''], + 'NumberSeparatorSequence' => [',', '.', ''], + ], ], ]; diff --git a/src/PhpWord/Writer/Word2007/Element/Field.php b/src/PhpWord/Writer/Word2007/Element/Field.php index f2c6355150..466fc19c67 100644 --- a/src/PhpWord/Writer/Word2007/Element/Field.php +++ b/src/PhpWord/Writer/Word2007/Element/Field.php @@ -262,6 +262,8 @@ protected function writeRef(ElementField $element): void $xmlWriter->endElement(); // w:instrText $xmlWriter->endElement(); // w:r + // I do not believe that REF allows for a text component. + /* if ($element->getText() != null) { if ($element->getText() instanceof TextRun) { $containerWriter = new Container($xmlWriter, $element->getText(), true); @@ -281,6 +283,7 @@ protected function writeRef(ElementField $element): void $xmlWriter->endElement(); // w:r } } + */ $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:fldChar'); @@ -315,12 +318,14 @@ protected function writeRef(ElementField $element): void 'InsertParagraphNumberFullContext' => '\\w', ]; + private const NUMBER_SEPARATOR_SEQUENCE = ['NumberSeperatorSequence', 'NumberSeparatorSequence', '\\d']; + private function convertRefOption(string $optionKey, string $optionValue): string { - if ($optionKey === 'NumberSeperatorSequence') { + if (in_array($optionKey, self::NUMBER_SEPARATOR_SEQUENCE, true)) { return '\\d ' . $optionValue; } - return self::OPTION_VALUES[$optionValue] ?? ''; + return self::OPTION_VALUES[$optionValue] ?? (in_array($optionValue, self::OPTION_VALUES, true) ? $optionValue : ''); } } diff --git a/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php b/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php index d3128e8007..cabd2fd8c2 100644 --- a/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php @@ -50,6 +50,7 @@ public function testWriteWithRefType(): void [ 'InsertParagraphNumberRelativeContext', 'CreateHyperLink', + 'NumberSeperatorSequence' => ',', ] ); @@ -65,7 +66,7 @@ public function testWriteWithRefType(): void $bookMarkElement = $doc->getElement($refFieldPath); self::assertNotNull($bookMarkElement); - self::assertEquals(' REF my-bookmark \r \h ', $bookMarkElement->textContent); + self::assertEquals(' REF my-bookmark \r \h \d , ', $bookMarkElement->textContent); $bookmarkPath = '/w:document/w:body/w:bookmarkStart'; self::assertTrue($doc->elementExists($bookmarkPath)); From 7fa0337b34e0c01aa82695e3c7986e63c8c2990c Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 27 Sep 2025 23:06:23 -0700 Subject: [PATCH 101/153] Experiment with symfony/polyfill-php72 It will only be loaded for Php7.1 which we still support. --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fb998082bc..8d3842016f 100644 --- a/composer.json +++ b/composer.json @@ -114,7 +114,8 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-zip": "*", - "phpoffice/math": "^0.3" + "phpoffice/math": "^0.3", + "symfony/polyfill-php72": "^1.30" }, "require-dev": { "ext-libxml": "*", From 5ace17f027c673041b228ea991dd13accd8a65dd Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 28 Sep 2025 17:08:17 -0700 Subject: [PATCH 102/153] Some Tweaks for Fields --- src/PhpWord/Element/Field.php | 5 +++-- src/PhpWord/Writer/Word2007/Element/Field.php | 6 ++++-- tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/PhpWord/Element/Field.php b/src/PhpWord/Element/Field.php index ccaf29509c..eba067e165 100644 --- a/src/PhpWord/Element/Field.php +++ b/src/PhpWord/Element/Field.php @@ -97,7 +97,7 @@ class Field extends AbstractElement 'options' => [ 'f', 'h', 'n', 'p', 'r', 't', 'w', 'd' => [',', '.', ''], - 'NumberSeperatorSequence' => [',', '.', ''], + 'NumberSeperatorSequence' => [',', '.', ''], // grandfather typo 'NumberSeparatorSequence' => [',', '.', ''], ], ], @@ -254,7 +254,8 @@ public function getProperties() public function setOptions(array $options = []) { foreach (array_keys($options) as $optionkey) { - if (!(isset($this->fieldsArray[$this->type]['options'][$optionkey])) && substr($optionkey, 0, 1) !== '\\') { + $optionkey = preg_replace('/^[\\\\](.)$/', '$1', $optionkey) ?? $optionkey; + if (!(isset($this->fieldsArray[$this->type]['options'][$optionkey]))) { throw new InvalidArgumentException("Invalid option '$optionkey', possible values are " . implode(', ', $this->fieldsArray[$this->type]['options'])); } } diff --git a/src/PhpWord/Writer/Word2007/Element/Field.php b/src/PhpWord/Writer/Word2007/Element/Field.php index 466fc19c67..2e1b4ed214 100644 --- a/src/PhpWord/Writer/Word2007/Element/Field.php +++ b/src/PhpWord/Writer/Word2007/Element/Field.php @@ -224,6 +224,7 @@ private function buildPropertiesAndOptions(ElementField $element) break; default: + $option = preg_replace('/^(.)$/', '\\\\$1', $option) ?? $option; $propertiesAndOptions .= $option . ' '; } } @@ -318,13 +319,14 @@ protected function writeRef(ElementField $element): void 'InsertParagraphNumberFullContext' => '\\w', ]; - private const NUMBER_SEPARATOR_SEQUENCE = ['NumberSeperatorSequence', 'NumberSeparatorSequence', '\\d']; + private const NUMBER_SEPARATOR_SEQUENCE = ['NumberSeperatorSequence', 'NumberSeparatorSequence', '\\d', 'd']; private function convertRefOption(string $optionKey, string $optionValue): string { if (in_array($optionKey, self::NUMBER_SEPARATOR_SEQUENCE, true)) { - return '\\d ' . $optionValue; + return ($optionValue === '') ? '' : ('\\d ' . $optionValue); } + $optionValue = preg_replace('/^(.)$/', '\\\\$1', $optionValue) ?? $optionValue; return self::OPTION_VALUES[$optionValue] ?? (in_array($optionValue, self::OPTION_VALUES, true) ? $optionValue : ''); } diff --git a/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php b/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php index cabd2fd8c2..cfad51263d 100644 --- a/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php @@ -49,7 +49,7 @@ public function testWriteWithRefType(): void ], [ 'InsertParagraphNumberRelativeContext', - 'CreateHyperLink', + 'h', // could have been supplied as 'CreateHyperLink' or '\h' 'NumberSeperatorSequence' => ',', ] ); From 29cfaaf744a569885d53ae8585494e4c3952a13f Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 28 Sep 2025 21:51:53 -0700 Subject: [PATCH 103/153] More Coverage --- phpstan-baseline.neon | 20 ---------- phpstan-baseline.php7.neon | 20 ---------- phpstan-baseline.php73.neon | 20 ---------- .../Shared/Microsoft/PasswordEncoder.php | 14 ++++++- src/PhpWord/Writer/ODText.php | 19 ++++------ .../Shared/Microsoft/PasswordEncoder2.php | 37 +++++++++++++++++++ .../Shared/Microsoft/PasswordEncoderTest.php | 13 +++++++ tests/PhpWordTests/Style/LanguageTest.php | 10 +++++ .../Writer/EPub3/Part/ContentXhtmlTest.php | 18 +++++++++ tests/PhpWordTests/Writer/PDF/MPDFTest.php | 4 +- tests/PhpWordTests/Writer/PDF/TCPDFTest.php | 22 ++++++++++- 11 files changed, 122 insertions(+), 75 deletions(-) create mode 100644 tests/PhpWordTests/Shared/Microsoft/PasswordEncoder2.php create mode 100644 tests/PhpWordTests/Writer/EPub3/Part/ContentXhtmlTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a231e4cf01..5b2e6f6a2f 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1530,26 +1530,6 @@ parameters: count: 3 path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/MPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/MPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 2 - path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" count: 1 diff --git a/phpstan-baseline.php7.neon b/phpstan-baseline.php7.neon index df47bf9ab5..385f7bcf39 100644 --- a/phpstan-baseline.php7.neon +++ b/phpstan-baseline.php7.neon @@ -1500,26 +1500,6 @@ parameters: count: 3 path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/MPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/MPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 2 - path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" count: 1 diff --git a/phpstan-baseline.php73.neon b/phpstan-baseline.php73.neon index ee5cc3064d..a03c5f63fa 100644 --- a/phpstan-baseline.php73.neon +++ b/phpstan-baseline.php73.neon @@ -1500,26 +1500,6 @@ parameters: count: 3 path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/MPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/MPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 2 - path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" count: 1 diff --git a/src/PhpWord/Shared/Microsoft/PasswordEncoder.php b/src/PhpWord/Shared/Microsoft/PasswordEncoder.php index 4762cc7104..e493cc11bb 100644 --- a/src/PhpWord/Shared/Microsoft/PasswordEncoder.php +++ b/src/PhpWord/Shared/Microsoft/PasswordEncoder.php @@ -99,6 +99,18 @@ class PasswordEncoder private static $passwordMaxLength = 15; + /** + * @param string $password + * @param string $to + * @param string $from + * + * @return false|string + */ + protected static function mbConvertEncoding($password, $to, $from) + { + return mb_convert_encoding($password, $to, $from); + } + /** * Create a hashed password that MS Word will be able to work with. * @@ -120,7 +132,7 @@ public static function hashPassword($password, $algorithmName = self::ALGORITHM_ // Get the single-byte values by iterating through the Unicode characters of the truncated password. // For each character, if the low byte is not equal to 0, take it. Otherwise, take the high byte. - $passUtf8 = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8'); + $passUtf8 = static::mbConvertEncoding($password, 'UCS-2LE', 'UTF-8'); if (!is_string($passUtf8)) { throw new Exception('Failed to convert password to UCS-2LE'); } diff --git a/src/PhpWord/Writer/ODText.php b/src/PhpWord/Writer/ODText.php index c9a524e882..431327fdc3 100644 --- a/src/PhpWord/Writer/ODText.php +++ b/src/PhpWord/Writer/ODText.php @@ -83,19 +83,14 @@ public function save(string $filename): void // Write parts foreach ($this->parts as $partName => $fileName) { - if ($fileName === '') { - continue; - } - $part = $this->getWriterPart($partName); - if (!$part instanceof AbstractPart) { - continue; + if ($fileName !== '') { + $part = $this->getWriterPart($partName); + if ($part instanceof AbstractPart) { + $part->setObjects($this->objects); + $zip->addFromString($fileName, $part->write()); + $this->objects = $part->getObjects(); + } } - - $part->setObjects($this->objects); - - $zip->addFromString($fileName, $part->write()); - - $this->objects = $part->getObjects(); } // Write objects charts diff --git a/tests/PhpWordTests/Shared/Microsoft/PasswordEncoder2.php b/tests/PhpWordTests/Shared/Microsoft/PasswordEncoder2.php new file mode 100644 index 0000000000..a80e0b9823 --- /dev/null +++ b/tests/PhpWordTests/Shared/Microsoft/PasswordEncoder2.php @@ -0,0 +1,37 @@ +expectException(WordException::class); + $this->expectExceptionMessage('Failed to convert password to UCS-2LE'); + $password = 'hello'; + $salt = base64_decode('uq81pJRRGFIY5U+E9gt8tA=='); + PasswordEncoder2::hashPassword($password, PasswordEncoder::ALGORITHM_MAC, $salt, 1); + } } diff --git a/tests/PhpWordTests/Style/LanguageTest.php b/tests/PhpWordTests/Style/LanguageTest.php index 848284e5e3..a237126ccb 100644 --- a/tests/PhpWordTests/Style/LanguageTest.php +++ b/tests/PhpWordTests/Style/LanguageTest.php @@ -89,4 +89,14 @@ public function testShortLanguage(): void //then Assert::assertEquals('fr-FR', $language->getLatin()); } + + public function testUndefined(): void + { + //when + $language = new Language(); + $language->setLatin('und'); + + //then + Assert::assertEquals('en-EN', $language->getLatin()); + } } diff --git a/tests/PhpWordTests/Writer/EPub3/Part/ContentXhtmlTest.php b/tests/PhpWordTests/Writer/EPub3/Part/ContentXhtmlTest.php new file mode 100644 index 0000000000..08ad30f41e --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Part/ContentXhtmlTest.php @@ -0,0 +1,18 @@ +expectException(WordException::class); + $this->expectExceptionMessage('No PhpWord assigned.'); + $object = new ContentXhtml(); + $object->write(); + } +} diff --git a/tests/PhpWordTests/Writer/PDF/MPDFTest.php b/tests/PhpWordTests/Writer/PDF/MPDFTest.php index dc779bd51f..c17d0601e2 100644 --- a/tests/PhpWordTests/Writer/PDF/MPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/MPDFTest.php @@ -48,6 +48,7 @@ public function testConstruct(): void $section->addText('Section 2 - landscape'); $writer = new MPDF($phpWord); + $writer->setFont('xyz'); $writer->save($file); self::assertFileExists($file); @@ -106,11 +107,12 @@ public function testSetGetAbstractRendererOptions(): void { $rendererName = Settings::PDF_RENDERER_MPDF; $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/mpdf/mpdf'); + self::assertNotFalse($rendererLibraryPath); Settings::setPdfRenderer($rendererName, $rendererLibraryPath); Settings::setPdfRendererOptions([ 'font' => 'Arial', ]); $writer = new PDF(new PhpWord()); - self::assertEquals('Arial', $writer->getFont()); + self::assertEquals('Arial', $writer->getFont()); //* @phpstan-ignore-line } } diff --git a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php index 16887507a9..01a0a3d662 100644 --- a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php @@ -43,6 +43,7 @@ public function testConstruct(): void $rendererName = Settings::PDF_RENDERER_TCPDF; $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/tecnickcom/tcpdf'); + self::assertNotFalse($rendererLibraryPath); Settings::setPdfRenderer($rendererName, $rendererLibraryPath); $writer = new PDF($phpWord); $writer->save($file); @@ -59,11 +60,30 @@ public function testSetGetAbstractRendererOptions(): void { $rendererName = Settings::PDF_RENDERER_TCPDF; $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/tecnickcom/tcpdf'); + self::assertNotFalse($rendererLibraryPath); Settings::setPdfRenderer($rendererName, $rendererLibraryPath); Settings::setPdfRendererOptions([ 'font' => 'Arial', ]); $writer = new PDF(new PhpWord()); - self::assertEquals('Arial', $writer->getFont()); + self::assertEquals('Arial', $writer->getFont()); //* @phpstan-ignore-line + } + + public function testSectionPageBreak(): void + { + $rendererName = Settings::PDF_RENDERER_TCPDF; + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/tecnickcom/tcpdf'); + self::assertNotFalse($rendererLibraryPath); + Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + $phpWord = new PhpWord(); + $section1 = $phpWord->addSection(); + $section1->addText('This is section 1.'); + $section2 = $phpWord->addSection(); + $section2->addText('This is section 2.'); + $writer = new PDF($phpWord); + $writer->setFont('Helvetica'); //* @phpstan-ignore-line + $content = $writer->getContent(); //* @phpstan-ignore-line + self::assertStringContainsString("
      ", $content); + self::assertStringContainsString('
      ', $content); } } From b5f4c2a8943c3adaff1b8aff72220c0450f8d4c7 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 29 Sep 2025 11:49:59 -0700 Subject: [PATCH 104/153] Pdf Improvements --- phpstan-baseline.neon | 70 ------------------ phpstan-baseline.php7.neon | 70 ------------------ phpstan-baseline.php73.neon | 70 ------------------ src/PhpWord/Element/AbstractElement.php | 3 +- src/PhpWord/Element/Section.php | 2 +- src/PhpWord/PhpWord.php | 3 +- src/PhpWord/Writer/ODText/Element/Formula.php | 8 +- .../Writer/ODText/Element/ListItemRun.php | 4 +- src/PhpWord/Writer/ODText/Element/Ruby.php | 4 +- src/PhpWord/Writer/ODText/Style/Numbering.php | 5 +- src/PhpWord/Writer/PDF.php | 4 + src/PhpWord/Writer/PDF/AbstractRenderer.php | 19 ----- src/PhpWord/Writer/PDF/DomPDF.php | 7 -- src/PhpWord/Writer/PDF/TCPDF.php | 10 +-- src/PhpWord/Writer/PDF/TcpdfNoDie.php | 50 +++++++++++++ .../Writer/Word2007/Element/Formula.php | 4 +- src/PhpWord/Writer/Word2007/Element/Ruby.php | 5 +- tests/PhpWordTests/Writer/PDF/DomPDFTest.php | 14 +--- tests/PhpWordTests/Writer/PDF/MPDFTest.php | 6 +- tests/PhpWordTests/Writer/PDF/TCPDFTest.php | 31 ++++++-- tests/PhpWordTests/Writer/PDFTest.php | 39 +++++++++- tests/PhpWordTests/_files/tcpdf.pdf | Bin 22 files changed, 136 insertions(+), 292 deletions(-) create mode 100644 src/PhpWord/Writer/PDF/TcpdfNoDie.php create mode 100644 tests/PhpWordTests/_files/tcpdf.pdf diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5b2e6f6a2f..dc469bc4b3 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -10,21 +10,11 @@ parameters: count: 1 path: src/PhpWord/Element/AbstractContainer.php - - - message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\|null given\\.$#" - count: 1 - path: src/PhpWord/Element/Cell.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\) does not accept null\\.$#" count: 1 path: src/PhpWord/Element/Field.php - - - message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\|null given\\.$#" - count: 1 - path: src/PhpWord/Element/Footnote.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:getArchiveImageSize\\(\\) should return array\\|null but returns array\\|false\\|null\\.$#" count: 1 @@ -60,11 +50,6 @@ parameters: count: 1 path: src/PhpWord/Element/Section.php - - - message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\|PhpOffice\\\\PhpWord\\\\Style\\\\Section\\|string given\\.$#" - count: 1 - path: src/PhpWord/Element/Section.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeAsciiCharacter\\(\\) has parameter \\$code with no type specified\\.$#" count: 1 @@ -1205,11 +1190,6 @@ parameters: count: 1 path: tests/PhpWordTests/Element/ImageTest.php - - - message: "#^Parameter \\#2 \\$style of class PhpOffice\\\\PhpWord\\\\Element\\\\Section constructor expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, PhpOffice\\\\PhpWord\\\\Style\\\\Section given\\.$#" - count: 1 - path: tests/PhpWordTests/Element/SectionTest.php - - message: "#^Parameter \\#1 \\$text of class PhpOffice\\\\PhpWord\\\\Element\\\\Title constructor expects PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string, PhpOffice\\\\PhpWord\\\\Element\\\\PageBreak given\\.$#" count: 1 @@ -1485,56 +1465,6 @@ parameters: count: 1 path: tests/PhpWordTests/Writer/ODText/Style/FontTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" - count: 2 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getOrientation\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getPaperSize\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getTempDir\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setFont\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setOrientation\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setPaperSize\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setTempDir\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 3 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDFTest.php - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" count: 1 diff --git a/phpstan-baseline.php7.neon b/phpstan-baseline.php7.neon index 385f7bcf39..f8939671a5 100644 --- a/phpstan-baseline.php7.neon +++ b/phpstan-baseline.php7.neon @@ -5,21 +5,11 @@ parameters: count: 1 path: src/PhpWord/Element/AbstractContainer.php - - - message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\|null given\\.$#" - count: 1 - path: src/PhpWord/Element/Cell.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\) does not accept null\\.$#" count: 1 path: src/PhpWord/Element/Field.php - - - message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\|null given\\.$#" - count: 1 - path: src/PhpWord/Element/Footnote.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:getArchiveImageSize\\(\\) should return array\\|null but returns array\\|false\\|null\\.$#" count: 1 @@ -50,11 +40,6 @@ parameters: count: 1 path: src/PhpWord/Element/Section.php - - - message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\|PhpOffice\\\\PhpWord\\\\Style\\\\Section\\|string given\\.$#" - count: 1 - path: src/PhpWord/Element/Section.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeAsciiCharacter\\(\\) has parameter \\$code with no type specified\\.$#" count: 1 @@ -1180,11 +1165,6 @@ parameters: count: 1 path: tests/PhpWordTests/Element/ImageTest.php - - - message: "#^Parameter \\#2 \\$style of class PhpOffice\\\\PhpWord\\\\Element\\\\Section constructor expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, PhpOffice\\\\PhpWord\\\\Style\\\\Section given\\.$#" - count: 1 - path: tests/PhpWordTests/Element/SectionTest.php - - message: "#^Parameter \\#1 \\$text of class PhpOffice\\\\PhpWord\\\\Element\\\\Title constructor expects PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string, PhpOffice\\\\PhpWord\\\\Element\\\\PageBreak given\\.$#" count: 1 @@ -1455,56 +1435,6 @@ parameters: count: 1 path: tests/PhpWordTests/Writer/ODText/Style/FontTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" - count: 2 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getOrientation\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getPaperSize\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getTempDir\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setFont\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setOrientation\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setPaperSize\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setTempDir\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 3 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDFTest.php - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" count: 1 diff --git a/phpstan-baseline.php73.neon b/phpstan-baseline.php73.neon index a03c5f63fa..1f5d7e7766 100644 --- a/phpstan-baseline.php73.neon +++ b/phpstan-baseline.php73.neon @@ -5,21 +5,11 @@ parameters: count: 1 path: src/PhpWord/Element/AbstractContainer.php - - - message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\|null given\\.$#" - count: 1 - path: src/PhpWord/Element/Cell.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\) does not accept null\\.$#" count: 1 path: src/PhpWord/Element/Field.php - - - message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\|null given\\.$#" - count: 1 - path: src/PhpWord/Element/Footnote.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:getArchiveImageSize\\(\\) should return array\\|null but returns array\\|false\\|null\\.$#" count: 1 @@ -50,11 +40,6 @@ parameters: count: 1 path: src/PhpWord/Element/Section.php - - - message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\|PhpOffice\\\\PhpWord\\\\Style\\\\Section\\|string given\\.$#" - count: 1 - path: src/PhpWord/Element/Section.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeAsciiCharacter\\(\\) has parameter \\$code with no type specified\\.$#" count: 1 @@ -1180,11 +1165,6 @@ parameters: count: 1 path: tests/PhpWordTests/Element/ImageTest.php - - - message: "#^Parameter \\#2 \\$style of class PhpOffice\\\\PhpWord\\\\Element\\\\Section constructor expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, PhpOffice\\\\PhpWord\\\\Style\\\\Section given\\.$#" - count: 1 - path: tests/PhpWordTests/Element/SectionTest.php - - message: "#^Parameter \\#1 \\$text of class PhpOffice\\\\PhpWord\\\\Element\\\\Title constructor expects PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string, PhpOffice\\\\PhpWord\\\\Element\\\\PageBreak given\\.$#" count: 1 @@ -1455,56 +1435,6 @@ parameters: count: 1 path: tests/PhpWordTests/Writer/ODText/Style/FontTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" - count: 2 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getOrientation\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getPaperSize\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getTempDir\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setFont\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setOrientation\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setPaperSize\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setTempDir\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 3 - path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/PDFTest.php - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" count: 1 diff --git a/src/PhpWord/Element/AbstractElement.php b/src/PhpWord/Element/AbstractElement.php index 9b0aee6fa0..607325489b 100644 --- a/src/PhpWord/Element/AbstractElement.php +++ b/src/PhpWord/Element/AbstractElement.php @@ -24,6 +24,7 @@ use PhpOffice\PhpWord\Media; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\Style\AbstractStyle; /** * Element abstract class. @@ -481,7 +482,7 @@ public function isInSection() * Set new style value. * * @param mixed $styleObject Style object - * @param null|array|string|Style $styleValue Style value + * @param null|AbstractStyle|array|string $styleValue Style value * @param bool $returnObject Always return object * * @return mixed diff --git a/src/PhpWord/Element/Section.php b/src/PhpWord/Element/Section.php index 0ae00aa9f6..dc5d4a5177 100644 --- a/src/PhpWord/Element/Section.php +++ b/src/PhpWord/Element/Section.php @@ -61,7 +61,7 @@ class Section extends AbstractContainer * Create new instance. * * @param int $sectionCount - * @param null|array|\PhpOffice\PhpWord\Style|string $style + * @param null|array|SectionStyle|string $style */ public function __construct($sectionCount, $style = null) { diff --git a/src/PhpWord/PhpWord.php b/src/PhpWord/PhpWord.php index 11e678a1ff..3e23d52fe6 100644 --- a/src/PhpWord/PhpWord.php +++ b/src/PhpWord/PhpWord.php @@ -22,6 +22,7 @@ use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\Style\Font; +use PhpOffice\PhpWord\Style\Section as StyleSection; /** * PHPWord main class. @@ -213,7 +214,7 @@ public function getSection($index) /** * Create new section. * - * @param null|array|string $style + * @param null|array|string|StyleSection $style * * @return Section */ diff --git a/src/PhpWord/Writer/ODText/Element/Formula.php b/src/PhpWord/Writer/ODText/Element/Formula.php index 2c7ce3aaf1..d1444a093a 100644 --- a/src/PhpWord/Writer/ODText/Element/Formula.php +++ b/src/PhpWord/Writer/ODText/Element/Formula.php @@ -35,15 +35,11 @@ class Formula extends AbstractElement public function write(): void { $xmlWriter = $this->getXmlWriter(); + /** @var ElementFormula */ $element = $this->getElement(); - if (!$element instanceof ElementFormula) { - return; - } + /** @var AbstractPart */ $part = $this->getPart(); - if (!$part instanceof AbstractPart) { - return; - } $objectIdx = $part->addObject($element); diff --git a/src/PhpWord/Writer/ODText/Element/ListItemRun.php b/src/PhpWord/Writer/ODText/Element/ListItemRun.php index 1319e48577..a52093ddac 100644 --- a/src/PhpWord/Writer/ODText/Element/ListItemRun.php +++ b/src/PhpWord/Writer/ODText/Element/ListItemRun.php @@ -32,10 +32,8 @@ class ListItemRun extends AbstractElement */ public function write(): void { + /** @var ListItemRunElement */ $element = $this->getElement(); - if (!$element instanceof ListItemRunElement) { - return; - } $depth = $element->getDepth() + 1; $xmlWriter = $this->getXmlWriter(); diff --git a/src/PhpWord/Writer/ODText/Element/Ruby.php b/src/PhpWord/Writer/ODText/Element/Ruby.php index f03b6b4562..795f288df3 100644 --- a/src/PhpWord/Writer/ODText/Element/Ruby.php +++ b/src/PhpWord/Writer/ODText/Element/Ruby.php @@ -35,10 +35,8 @@ class Ruby extends AbstractElement public function write(): void { $xmlWriter = $this->getXmlWriter(); + /** @var \PhpOffice\PhpWord\Element\Ruby */ $element = $this->getElement(); - if (!$element instanceof \PhpOffice\PhpWord\Element\Ruby) { - return; - } if (!$this->withoutP) { $xmlWriter->startElement('text:p'); // text:p } diff --git a/src/PhpWord/Writer/ODText/Style/Numbering.php b/src/PhpWord/Writer/ODText/Style/Numbering.php index 287b25b34a..b13bea93fd 100644 --- a/src/PhpWord/Writer/ODText/Style/Numbering.php +++ b/src/PhpWord/Writer/ODText/Style/Numbering.php @@ -31,11 +31,8 @@ class Numbering extends AbstractStyle */ public function write(): void { - /** @var StyleNumbering $style Type hint */ + /** @var StyleNumbering */ $style = $this->getStyle(); - if (!$style instanceof StyleNumbering) { - return; - } $xmlWriter = $this->getXmlWriter(); $xmlWriter->startElement('text:list-style'); diff --git a/src/PhpWord/Writer/PDF.php b/src/PhpWord/Writer/PDF.php index 4f7f1be99a..b58476d4f7 100644 --- a/src/PhpWord/Writer/PDF.php +++ b/src/PhpWord/Writer/PDF.php @@ -26,6 +26,10 @@ /** * PDF Writer. * + * @method string getFont() + * @method self setFont(string $font) + * @method string getContent() + * * @since 0.10.0 */ class PDF diff --git a/src/PhpWord/Writer/PDF/AbstractRenderer.php b/src/PhpWord/Writer/PDF/AbstractRenderer.php index 125bf4fa43..8a15a60623 100644 --- a/src/PhpWord/Writer/PDF/AbstractRenderer.php +++ b/src/PhpWord/Writer/PDF/AbstractRenderer.php @@ -30,13 +30,6 @@ */ abstract class AbstractRenderer extends HTML { - /** - * Name of renderer include file. - * - * @var string - */ - protected $includeFile; - /** * Temporary storage directory. * @@ -83,18 +76,6 @@ public function __construct(PhpWord $phpWord) { parent::__construct($phpWord); $this->isPdf = true; - if ($this->includeFile != null) { - $includeFile = Settings::getPdfRendererPath() . '/' . $this->includeFile; - if (file_exists($includeFile)) { - /** @noinspection PhpIncludeInspection Dynamic includes */ - require_once $includeFile; - } else { - // @codeCoverageIgnoreStart - // Can't find any test case. Uncomment when found. - throw new Exception('Unable to load PDF Rendering library'); - // @codeCoverageIgnoreEnd - } - } // Configuration $options = Settings::getPdfRendererOptions(); diff --git a/src/PhpWord/Writer/PDF/DomPDF.php b/src/PhpWord/Writer/PDF/DomPDF.php index 464dbfa59f..85e1b03d7b 100644 --- a/src/PhpWord/Writer/PDF/DomPDF.php +++ b/src/PhpWord/Writer/PDF/DomPDF.php @@ -30,13 +30,6 @@ */ class DomPDF extends AbstractRenderer implements WriterInterface { - /** - * Name of renderer include file. - * - * @var string - */ - protected $includeFile; - /** * Gets the implementation of external PDF library that should be used. * diff --git a/src/PhpWord/Writer/PDF/TCPDF.php b/src/PhpWord/Writer/PDF/TCPDF.php index 93bdd58528..2e42945281 100644 --- a/src/PhpWord/Writer/PDF/TCPDF.php +++ b/src/PhpWord/Writer/PDF/TCPDF.php @@ -33,12 +33,9 @@ */ class TCPDF extends AbstractRenderer implements WriterInterface { - /** - * Name of renderer include file. - * - * @var string - */ - protected $includeFile = 'tcpdf.php'; + protected function defines(): void + { + } /** * Gets the implementation of external PDF library that should be used. @@ -51,6 +48,7 @@ class TCPDF extends AbstractRenderer implements WriterInterface */ protected function createExternalWriterInstance($orientation, $unit, $paperSize) { + $this->defines(); $instance = new TCPDFBase($orientation, $unit, $paperSize); if ($this->getFont()) { diff --git a/src/PhpWord/Writer/PDF/TcpdfNoDie.php b/src/PhpWord/Writer/PDF/TcpdfNoDie.php new file mode 100644 index 0000000000..1e1e1e97e0 --- /dev/null +++ b/src/PhpWord/Writer/PDF/TcpdfNoDie.php @@ -0,0 +1,50 @@ +getElement(); - if (!$element instanceof FormulaElement) { - return; - } $this->startElementP(); diff --git a/src/PhpWord/Writer/Word2007/Element/Ruby.php b/src/PhpWord/Writer/Word2007/Element/Ruby.php index f30a5f7e84..f887151356 100644 --- a/src/PhpWord/Writer/Word2007/Element/Ruby.php +++ b/src/PhpWord/Writer/Word2007/Element/Ruby.php @@ -29,11 +29,8 @@ class Ruby extends AbstractElement public function write(): void { $xmlWriter = $this->getXmlWriter(); + /** @var \PhpOffice\PhpWord\Element\Ruby */ $element = $this->getElement(); - if (!$element instanceof \PhpOffice\PhpWord\Element\Ruby) { - return; - } - /** @var \PhpOffice\PhpWord\Element\Ruby $element */ $this->startElementP(); $xmlWriter->startElement('w:r'); diff --git a/tests/PhpWordTests/Writer/PDF/DomPDFTest.php b/tests/PhpWordTests/Writer/PDF/DomPDFTest.php index 37ea762802..4c5c6ba0d9 100644 --- a/tests/PhpWordTests/Writer/PDF/DomPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/DomPDFTest.php @@ -34,17 +34,13 @@ class DomPDFTest extends \PHPUnit\Framework\TestCase */ public function testConstruct(): void { - define('DOMPDF_ENABLE_AUTOLOAD', false); $file = __DIR__ . '/../../_files/dompdf.pdf'; $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText('Test 1'); - $rendererName = Settings::PDF_RENDERER_DOMPDF; - $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); - Settings::setPdfRenderer($rendererName, $rendererLibraryPath); - $writer = new PDF($phpWord); + $writer = new PDF\DomPDF($phpWord); $writer->save($file); self::assertFileExists($file); @@ -57,12 +53,7 @@ public function testConstruct(): void */ public function testSetGetAbstractRendererProperties(): void { - define('DOMPDF_ENABLE_AUTOLOAD', false); - - $rendererName = Settings::PDF_RENDERER_DOMPDF; - $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); - Settings::setPdfRenderer($rendererName, $rendererLibraryPath); - $writer = new PDF(new PhpWord()); + $writer = new PDF\DomPDF(new PhpWord()); $writer->setFont('arial'); self::assertEquals('arial', $writer->getFont()); @@ -86,6 +77,7 @@ public function testSetGetAbstractRendererOptions(): void $rendererName = Settings::PDF_RENDERER_DOMPDF; $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); + self::assertNotFalse($rendererLibraryPath); Settings::setPdfRenderer($rendererName, $rendererLibraryPath); Settings::setPdfRendererOptions([ 'font' => 'Arial', diff --git a/tests/PhpWordTests/Writer/PDF/MPDFTest.php b/tests/PhpWordTests/Writer/PDF/MPDFTest.php index c17d0601e2..90048c4163 100644 --- a/tests/PhpWordTests/Writer/PDF/MPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/MPDFTest.php @@ -44,7 +44,7 @@ public function testConstruct(): void $section->addText('Test 2'); $oSettings = new \PhpOffice\PhpWord\Style\Section(); $oSettings->setSettingValue('orientation', 'landscape'); - $section = $phpWord->addSection($oSettings); // @phpstan-ignore-line + $section = $phpWord->addSection($oSettings); $section->addText('Section 2 - landscape'); $writer = new MPDF($phpWord); @@ -67,7 +67,7 @@ public function testEditCallback(): void $section->addText('Test 2'); $oSettings = new \PhpOffice\PhpWord\Style\Section(); $oSettings->setSettingValue('orientation', 'landscape'); - $section = $phpWord->addSection($oSettings); // @phpstan-ignore-line + $section = $phpWord->addSection($oSettings); $section->addText('Section 2 - landscape'); $writer = new MPDF($phpWord); @@ -113,6 +113,6 @@ public function testSetGetAbstractRendererOptions(): void 'font' => 'Arial', ]); $writer = new PDF(new PhpWord()); - self::assertEquals('Arial', $writer->getFont()); //* @phpstan-ignore-line + self::assertEquals('Arial', $writer->getFont()); } } diff --git a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php index 01a0a3d662..886eb17230 100644 --- a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWordTests\Writer\PDF; +use Exception; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Writer\PDF; @@ -41,11 +42,8 @@ public function testConstruct(): void $section = $phpWord->addSection(); $section->addText('Test 1'); - $rendererName = Settings::PDF_RENDERER_TCPDF; - $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/tecnickcom/tcpdf'); - self::assertNotFalse($rendererLibraryPath); - Settings::setPdfRenderer($rendererName, $rendererLibraryPath); - $writer = new PDF($phpWord); + $writer = new PDF\TCPDF($phpWord); + $writer->setFont('Helvetica'); $writer->save($file); self::assertFileExists($file); @@ -66,7 +64,7 @@ public function testSetGetAbstractRendererOptions(): void 'font' => 'Arial', ]); $writer = new PDF(new PhpWord()); - self::assertEquals('Arial', $writer->getFont()); //* @phpstan-ignore-line + self::assertEquals('Arial', $writer->getFont()); } public function testSectionPageBreak(): void @@ -81,9 +79,26 @@ public function testSectionPageBreak(): void $section2 = $phpWord->addSection(); $section2->addText('This is section 2.'); $writer = new PDF($phpWord); - $writer->setFont('Helvetica'); //* @phpstan-ignore-line - $content = $writer->getContent(); //* @phpstan-ignore-line + $content = $writer->getContent(); self::assertStringContainsString("
      ", $content); self::assertStringContainsString('
      ', $content); } + + /** @runInSeparateProcess */ + public function testExcpetionRatherThanDie(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Could not include font definition file'); + $file = __DIR__ . '/../../_files/tcpdf.pdf'; + $phpWord = new PhpWord(); + $section1 = $phpWord->addSection(); + $section1->addText('This is section 1.'); + $section2 = $phpWord->addSection(); + $section2->addText('This is section 2.'); + $writer = new PDF\TcpdfNoDie($phpWord); + $writer->setFont('xyz'); + $writer->save($file); + self::assertFileExists($file); + unlink($file); + } } diff --git a/tests/PhpWordTests/Writer/PDFTest.php b/tests/PhpWordTests/Writer/PDFTest.php index c7e5376fab..c5c8f3b7aa 100644 --- a/tests/PhpWordTests/Writer/PDFTest.php +++ b/tests/PhpWordTests/Writer/PDFTest.php @@ -32,16 +32,51 @@ class PDFTest extends \PHPUnit\Framework\TestCase /** * Test normal construct. */ - public function testConstruct(): void + public function testConstructDompdf(): void { define('DOMPDF_ENABLE_AUTOLOAD', false); $file = __DIR__ . '/../_files/temp.pdf'; $rendererName = Settings::PDF_RENDERER_DOMPDF; $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); + self::assertNotFalse($rendererLibraryPath); Settings::setPdfRenderer($rendererName, $rendererLibraryPath); $writer = new PDF(new PhpWord()); - $writer->setFont('xyz'); //* @phpstan-ignore-line + $writer->setFont('xyz'); + $writer->save($file); + + self::assertFileExists($file); + + unlink($file); + } + + public function testConstructMpdf(): void + { + $file = __DIR__ . '/../_files/temp.pdf'; + + $rendererName = Settings::PDF_RENDERER_MPDF; + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/mpdf/mpdf'); + self::assertNotFalse($rendererLibraryPath); + Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + $writer = new PDF(new PhpWord()); + $writer->setFont('xyz'); + $writer->save($file); + + self::assertFileExists($file); + + unlink($file); + } + + public function testConstructTcpdf(): void + { + $file = __DIR__ . '/../_files/temp.pdf'; + + $rendererName = Settings::PDF_RENDERER_TCPDF; + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/tecnickcom/tcpdf'); + self::assertNotFalse($rendererLibraryPath); + Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + $writer = new PDF(new PhpWord()); + $writer->setFont('Helvetica'); $writer->save($file); self::assertFileExists($file); diff --git a/tests/PhpWordTests/_files/tcpdf.pdf b/tests/PhpWordTests/_files/tcpdf.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 From 9408c1d24ad3ff1f598357ddb4ab66afb134d9fe Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 29 Sep 2025 14:41:22 -0700 Subject: [PATCH 105/153] Still More Coverage --- src/PhpWord/Reader/Word2007/Footnotes.php | 7 ++-- src/PhpWord/Shared/Css.php | 22 ++++++------- src/PhpWord/Shared/Text.php | 31 +++++++++++------- src/PhpWord/Writer/EPub3.php | 12 +++---- tests/PhpWordTests/Shared/Text2.php | 32 +++++++++++++++++++ tests/PhpWordTests/Shared/TextTest.php | 8 +++++ .../{SampleTest.php => ZampleTest.php} | 2 +- 7 files changed, 80 insertions(+), 34 deletions(-) create mode 100644 tests/PhpWordTests/Shared/Text2.php rename tests/PhpWordTests/{SampleTest.php => ZampleTest.php} (98%) diff --git a/src/PhpWord/Reader/Word2007/Footnotes.php b/src/PhpWord/Reader/Word2007/Footnotes.php index 0b259e926a..ee2e182e10 100644 --- a/src/PhpWord/Reader/Word2007/Footnotes.php +++ b/src/PhpWord/Reader/Word2007/Footnotes.php @@ -81,16 +81,19 @@ public function read(PhpWord $phpWord): void */ private function getElement(PhpWord $phpWord, $relationId) { + $returnVal = null; $getMethod = "get{$this->collection}"; $collection = $phpWord->$getMethod()->getItems(); //not found by key, looping to search by relationId foreach ($collection as $collectionElement) { if ($collectionElement->getRelationId() == $relationId) { - return $collectionElement; + $returnVal = $collectionElement; + + break; } } - return null; + return $returnVal; } } diff --git a/src/PhpWord/Shared/Css.php b/src/PhpWord/Shared/Css.php index 93f69000b4..c26940b1a6 100644 --- a/src/PhpWord/Shared/Css.php +++ b/src/PhpWord/Shared/Css.php @@ -41,19 +41,17 @@ public function process(): void $cssContent = str_replace(["\r", "\n"], '', $this->cssContent); preg_match_all('/(.+?)\s?\{\s?(.+?)\s?\}/', $cssContent, $cssExtracted); // Check if there are x selectors and x rules - if (count($cssExtracted[1]) != count($cssExtracted[2])) { - return; - } - - foreach ($cssExtracted[1] as $key => $selector) { - $rules = trim($cssExtracted[2][$key]); - $rules = explode(';', $rules); - foreach ($rules as $rule) { - if (empty($rule)) { - continue; + if (count($cssExtracted[1]) === count($cssExtracted[2])) { + foreach ($cssExtracted[1] as $key => $selector) { + $rules = trim($cssExtracted[2][$key]); + $rules = explode(';', $rules); + foreach ($rules as $rule) { + if (empty($rule)) { + continue; + } + [$key, $value] = explode(':', trim($rule)); + $this->styles[$this->sanitize($selector)][$this->sanitize($key)] = $this->sanitize($value); } - [$key, $value] = explode(':', trim($rule)); - $this->styles[$this->sanitize($selector)][$this->sanitize($key)] = $this->sanitize($value); } } } diff --git a/src/PhpWord/Shared/Text.php b/src/PhpWord/Shared/Text.php index 251764b3dd..de5e5fb2f8 100644 --- a/src/PhpWord/Shared/Text.php +++ b/src/PhpWord/Shared/Text.php @@ -18,11 +18,8 @@ namespace PhpOffice\PhpWord\Shared; -use PhpOffice\PhpWord\Exception\Exception; +use PhpOffice\PhpWord\Exception\Exception as WordException; -/** - * Text. - */ class Text { /** @@ -37,6 +34,9 @@ class Text */ private static function buildControlCharacters(): void { + if (!empty(self::$controlCharacters)) { + return; + } for ($i = 0; $i <= 19; ++$i) { if ($i != 9 && $i != 10 && $i != 13) { $find = '_x' . sprintf('%04s', strtoupper(dechex($i))) . '_'; @@ -63,9 +63,7 @@ private static function buildControlCharacters(): void */ public static function controlCharacterPHP2OOXML($value = '') { - if (empty(self::$controlCharacters)) { - self::buildControlCharacters(); - } + self::buildControlCharacters(); return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $value); } @@ -119,9 +117,7 @@ public static function chr($dec) */ public static function controlCharacterOOXML2PHP($value = '') { - if (empty(self::$controlCharacters)) { - self::buildControlCharacters(); - } + self::buildControlCharacters(); return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $value); } @@ -149,15 +145,26 @@ public static function toUTF8($value = '') { if (null !== $value && !self::isUTF8($value)) { // PHP8.2 : utf8_encode is deprecated, but mb_convert_encoding always usable - $value = (function_exists('mb_convert_encoding')) ? mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1') : utf8_encode($value); + $value = static::mbConvertEncoding($value); if ($value === false) { - throw new Exception('Unable to convert text to UTF-8'); + throw new WordException('Unable to convert text to UTF-8'); } } return $value; } + /** + * @param string $value + * + * @return false|string + */ + protected static function mbConvertEncoding($value) + { + // PHP8.2 : utf8_encode is deprecated, but mb_convert_encoding always usable + return (function_exists('mb_convert_encoding')) ? mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1') : utf8_encode($value); + } + /** * Returns unicode from UTF8 text. * diff --git a/src/PhpWord/Writer/EPub3.php b/src/PhpWord/Writer/EPub3.php index b2ed9700d1..71227a1745 100644 --- a/src/PhpWord/Writer/EPub3.php +++ b/src/PhpWord/Writer/EPub3.php @@ -72,14 +72,12 @@ public function save(string $filename): void // Add other files foreach ($this->parts as $partName => $fileName) { - if ($fileName === '') { - continue; + if ($fileName !== '') { + $part = $this->getWriterPart($partName); + if ($part instanceof AbstractPart) { + $zip->addFromString($fileName, $part->write()); + } } - $part = $this->getWriterPart($partName); - if (!$part instanceof AbstractPart) { - continue; - } - $zip->addFromString($fileName, $part->write()); } // Close zip archive diff --git a/tests/PhpWordTests/Shared/Text2.php b/tests/PhpWordTests/Shared/Text2.php new file mode 100644 index 0000000000..2d59a2a2ab --- /dev/null +++ b/tests/PhpWordTests/Shared/Text2.php @@ -0,0 +1,32 @@ +expectException(WordException::class); + $this->expectExceptionMessage('Unable to convert text to UTF-8'); + Text2::toUTF8("\x80"); + } } diff --git a/tests/PhpWordTests/SampleTest.php b/tests/PhpWordTests/ZampleTest.php similarity index 98% rename from tests/PhpWordTests/SampleTest.php rename to tests/PhpWordTests/ZampleTest.php index 358c4a773b..b5312b0636 100644 --- a/tests/PhpWordTests/SampleTest.php +++ b/tests/PhpWordTests/ZampleTest.php @@ -23,7 +23,7 @@ use RegexIterator; use RuntimeException; -class SampleTest extends TestCase +class ZampleTest extends TestCase { /** @var bool */ protected static $alwaysTrue = true; From a96f0a3841fddcdb9ed61a71879ab5b0ee2e04ac Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 29 Sep 2025 19:56:08 -0700 Subject: [PATCH 106/153] Tweak Text Should be my last change here for a while. --- src/PhpWord/Shared/Text.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/PhpWord/Shared/Text.php b/src/PhpWord/Shared/Text.php index de5e5fb2f8..eeaaa5ad9f 100644 --- a/src/PhpWord/Shared/Text.php +++ b/src/PhpWord/Shared/Text.php @@ -144,7 +144,6 @@ public static function isUTF8($value = '') public static function toUTF8($value = '') { if (null !== $value && !self::isUTF8($value)) { - // PHP8.2 : utf8_encode is deprecated, but mb_convert_encoding always usable $value = static::mbConvertEncoding($value); if ($value === false) { throw new WordException('Unable to convert text to UTF-8'); @@ -161,8 +160,7 @@ public static function toUTF8($value = '') */ protected static function mbConvertEncoding($value) { - // PHP8.2 : utf8_encode is deprecated, but mb_convert_encoding always usable - return (function_exists('mb_convert_encoding')) ? mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1') : utf8_encode($value); + return mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1'); } /** From 6536f5c180784a3f0e180da3c7164554e012d5be Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 30 Sep 2025 00:17:52 -0700 Subject: [PATCH 107/153] Minor Tweaks --- src/PhpWord/Writer/PDF/TCPDF.php | 1 - src/PhpWord/Writer/PDF/TcpdfNoDie.php | 7 ------- tests/PhpWordTests/Writer/PDF/TCPDFTest.php | 9 ++------- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/PhpWord/Writer/PDF/TCPDF.php b/src/PhpWord/Writer/PDF/TCPDF.php index 2e42945281..2add7e3c89 100644 --- a/src/PhpWord/Writer/PDF/TCPDF.php +++ b/src/PhpWord/Writer/PDF/TCPDF.php @@ -27,7 +27,6 @@ /** * TCPDF writer. * - * @deprecated 0.13.0 Use `DomPDF` or `MPDF` instead. * @see http://www.tcpdf.org/ * @since 0.11.0 */ diff --git a/src/PhpWord/Writer/PDF/TcpdfNoDie.php b/src/PhpWord/Writer/PDF/TcpdfNoDie.php index 1e1e1e97e0..2e85d702f6 100644 --- a/src/PhpWord/Writer/PDF/TcpdfNoDie.php +++ b/src/PhpWord/Writer/PDF/TcpdfNoDie.php @@ -18,13 +18,6 @@ namespace PhpOffice\PhpWord\Writer\PDF; -/** - * TCPDF writer. - * - * @deprecated 0.13.0 Use `DomPDF` or `MPDF` instead. - * @see http://www.tcpdf.org/ - * @since 0.11.0 - */ class TcpdfNoDie extends TCPDF { /** diff --git a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php index 886eb17230..f6ce12f035 100644 --- a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php @@ -25,8 +25,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\PDF\TCPDF. - * - * @runTestsInSeparateProcesses */ class TCPDFTest extends \PHPUnit\Framework\TestCase { @@ -85,11 +83,10 @@ public function testSectionPageBreak(): void } /** @runInSeparateProcess */ - public function testExcpetionRatherThanDie(): void + public function testExceptionRatherThanDie(): void { $this->expectException(Exception::class); $this->expectExceptionMessage('Could not include font definition file'); - $file = __DIR__ . '/../../_files/tcpdf.pdf'; $phpWord = new PhpWord(); $section1 = $phpWord->addSection(); $section1->addText('This is section 1.'); @@ -97,8 +94,6 @@ public function testExcpetionRatherThanDie(): void $section2->addText('This is section 2.'); $writer = new PDF\TcpdfNoDie($phpWord); $writer->setFont('xyz'); - $writer->save($file); - self::assertFileExists($file); - unlink($file); + $writer->save('php://memory'); } } From 1921cbd7198632d6d6fc431dc273f8f44d34a186 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 30 Sep 2025 00:48:38 -0700 Subject: [PATCH 108/153] Mysterious Failure with PhpUnit 7/8/9 Corresponding to Php7.* and Php8.0. Did not seem to recognize runInSeparateProcess for test method. --- tests/PhpWordTests/Writer/PDF/TCPDFTest.php | 3 ++- tests/PhpWordTests/_files/tcpdf.pdf | Bin 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 tests/PhpWordTests/_files/tcpdf.pdf diff --git a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php index f6ce12f035..2bc2cd552c 100644 --- a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php @@ -25,6 +25,8 @@ /** * Test class for PhpOffice\PhpWord\Writer\PDF\TCPDF. + * + * @runTestsInSeparateProcesses */ class TCPDFTest extends \PHPUnit\Framework\TestCase { @@ -82,7 +84,6 @@ public function testSectionPageBreak(): void self::assertStringContainsString('
      ', $content); } - /** @runInSeparateProcess */ public function testExceptionRatherThanDie(): void { $this->expectException(Exception::class); diff --git a/tests/PhpWordTests/_files/tcpdf.pdf b/tests/PhpWordTests/_files/tcpdf.pdf deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 From 9ee33497b09d3630fe60f83acb511d2312e44dd0 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 30 Sep 2025 01:30:17 -0700 Subject: [PATCH 109/153] Try preserveGlobalState Not sure why this should be needed for Phpunit 9 but not 10. No harm specifying it in 10. --- tests/PhpWordTests/Writer/PDF/TCPDFTest.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php index 2bc2cd552c..de8ac147af 100644 --- a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php @@ -25,8 +25,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\PDF\TCPDF. - * - * @runTestsInSeparateProcesses */ class TCPDFTest extends \PHPUnit\Framework\TestCase { @@ -84,6 +82,11 @@ public function testSectionPageBreak(): void self::assertStringContainsString('
      ', $content); } + /** + * @runInSeparateProcess + * + * @preserveGlobalState disabled + */ public function testExceptionRatherThanDie(): void { $this->expectException(Exception::class); From c7f6a8073e7a59dc549d2c371c02e888aec4efbd Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:50:47 -0700 Subject: [PATCH 110/153] Start to Eliminate Most Tests in Separate Processes Writer/Html/FontTest was already bad, testing for the wrong default font values because a prior test had overlaid those; a full unit test would succeed, but just testing the Writer/Html directory would not. Better test planning eliminates that possibility, without the need to resort to resource-intensive separate processes. --- phpstan-baseline.neon | 55 ----------------- phpstan-baseline.php7.neon | 55 ----------------- phpstan-baseline.php73.neon | 55 ----------------- src/PhpWord/Settings.php | 17 ++++++ tests/PhpWordTests/PhpWordTest.php | 11 +++- tests/PhpWordTests/SettingsTest.php | 61 +------------------ .../WriteReadback/Word2007Test.php | 11 +++- tests/PhpWordTests/Writer/HTML/FontTest.php | 28 ++------- .../Writer/ODText/Part/ContentTest.php | 13 +--- .../Writer/ODText/Style/FontTest.php | 2 + tests/PhpWordTests/Writer/PDF/DomPDFTest.php | 9 +-- tests/PhpWordTests/Writer/PDF/MPDFTest.php | 29 ++++----- tests/PhpWordTests/Writer/PDF/TCPDFTest.php | 16 ++--- tests/PhpWordTests/Writer/PDFTest.php | 39 ++++++------ .../Writer/Word2007/Part/StylesTest.php | 4 +- 15 files changed, 93 insertions(+), 312 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index dc469bc4b3..bd9206dd06 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1275,61 +1275,6 @@ parameters: count: 2 path: tests/PhpWordTests/Reader/Word2007/StyleTest.php - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$compatibility has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultFontName has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultFontSize has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultPaper has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$measurementUnit has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$pdfRendererName has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$pdfRendererPath has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$tempDir has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$zipClass has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - message: "#^Cannot call method getStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" count: 1 diff --git a/phpstan-baseline.php7.neon b/phpstan-baseline.php7.neon index f8939671a5..93a737665a 100644 --- a/phpstan-baseline.php7.neon +++ b/phpstan-baseline.php7.neon @@ -1250,61 +1250,6 @@ parameters: count: 2 path: tests/PhpWordTests/Reader/Word2007/StyleTest.php - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$compatibility has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultFontName has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultFontSize has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultPaper has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$measurementUnit has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$pdfRendererName has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$pdfRendererPath has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$tempDir has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$zipClass has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - message: "#^Cannot call method getStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" count: 1 diff --git a/phpstan-baseline.php73.neon b/phpstan-baseline.php73.neon index 1f5d7e7766..5ebf3d7727 100644 --- a/phpstan-baseline.php73.neon +++ b/phpstan-baseline.php73.neon @@ -1250,61 +1250,6 @@ parameters: count: 2 path: tests/PhpWordTests/Reader/Word2007/StyleTest.php - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$compatibility has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultFontName has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultFontSize has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultPaper has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$measurementUnit has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$pdfRendererName has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$pdfRendererPath has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$tempDir has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$zipClass has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/SettingsTest.php - - message: "#^Cannot call method getStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" count: 1 diff --git a/src/PhpWord/Settings.php b/src/PhpWord/Settings.php index c4b7c38dfc..d8956d8428 100644 --- a/src/PhpWord/Settings.php +++ b/src/PhpWord/Settings.php @@ -529,4 +529,21 @@ public static function setDefaultPaper(string $value): bool return false; } + + public static function restoreDefaults(): void + { + self::$defaultAsianFontName = self::DEFAULT_FONT_NAME; + self::$defaultFontColor = self::DEFAULT_FONT_COLOR; + self::$defaultFontName = self::DEFAULT_FONT_NAME; + self::$defaultFontSize = self::DEFAULT_FONT_SIZE; + self::$defaultPaper = self::DEFAULT_PAPER; + self::$measurementUnit = self::UNIT_TWIP; + self::$outputEscapingEnabled = false; + self::$pdfRendererName = null; + self::$pdfRendererOptions = []; + self::$pdfRendererPath = null; + self::$tempDir = ''; + self::$xmlWriterCompatibility = true; + self::$zipClass = self::ZIPARCHIVE; + } } diff --git a/tests/PhpWordTests/PhpWordTest.php b/tests/PhpWordTests/PhpWordTest.php index 76919bbdf3..0e99909aa9 100644 --- a/tests/PhpWordTests/PhpWordTest.php +++ b/tests/PhpWordTests/PhpWordTest.php @@ -27,16 +27,23 @@ /** * Test class for PhpOffice\PhpWord\PhpWord. - * - * @runTestsInSeparateProcesses */ class PhpWordTest extends \PHPUnit\Framework\TestCase { + /** + * Executed after each method of the class. + */ + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Test object creation. */ public function testConstruct(): void { + Settings::restoreDefaults(); do { $dtStart = new DateTimeImmutable(); $startSecond = $dtStart->format('s'); diff --git a/tests/PhpWordTests/SettingsTest.php b/tests/PhpWordTests/SettingsTest.php index cc18383f8d..fb51bc0b11 100644 --- a/tests/PhpWordTests/SettingsTest.php +++ b/tests/PhpWordTests/SettingsTest.php @@ -24,69 +24,12 @@ * Test class for PhpOffice\PhpWord\Settings. * * @coversDefaultClass \PhpOffice\PhpWord\Settings - * - * @runTestsInSeparateProcesses */ class SettingsTest extends TestCase { - private $compatibility; - - /** @var string */ - private $defaultFontColor; - - private $defaultFontSize; - - private $defaultFontName; - - private $defaultPaper; - - private $measurementUnit; - - private $outputEscapingEnabled; - - private $pdfRendererName; - - /** - * @var array - */ - private $pdfRendererOptions; - - private $pdfRendererPath; - - private $tempDir; - - private $zipClass; - - protected function setUp(): void - { - $this->compatibility = Settings::hasCompatibility(); - $this->defaultFontColor = Settings::getDefaultFontColor(); - $this->defaultFontSize = Settings::getDefaultFontSize(); - $this->defaultFontName = Settings::getDefaultFontName(); - $this->defaultPaper = Settings::getDefaultPaper(); - $this->measurementUnit = Settings::getMeasurementUnit(); - $this->outputEscapingEnabled = Settings::isOutputEscapingEnabled(); - $this->pdfRendererName = Settings::getPdfRendererName(); - $this->pdfRendererOptions = Settings::getPdfRendererOptions(); - $this->pdfRendererPath = Settings::getPdfRendererPath(); - $this->tempDir = Settings::getTempDir(); - $this->zipClass = Settings::getZipClass(); - } - protected function tearDown(): void { - Settings::setCompatibility($this->compatibility); - Settings::setDefaultFontColor($this->defaultFontColor); - Settings::setDefaultFontSize($this->defaultFontSize); - Settings::setDefaultFontName($this->defaultFontName); - Settings::setDefaultPaper($this->defaultPaper); - Settings::setMeasurementUnit($this->measurementUnit); - Settings::setOutputEscapingEnabled($this->outputEscapingEnabled); - Settings::setPdfRendererName($this->pdfRendererName); - Settings::setPdfRendererOptions($this->pdfRendererOptions); - Settings::setPdfRendererPath($this->pdfRendererPath); - Settings::setTempDir($this->tempDir); - Settings::setZipClass($this->zipClass); + Settings::restoreDefaults(); } /** @@ -94,6 +37,7 @@ protected function tearDown(): void */ public function testSetGetCompatibility(): void { + Settings::restoreDefaults(); self::assertTrue(Settings::hasCompatibility()); self::assertTrue(Settings::setCompatibility(false)); self::assertFalse(Settings::hasCompatibility()); @@ -129,6 +73,7 @@ public function testSetGetZipClass(): void public function testSetGetPdfRenderer(): void { $domPdfPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); + self::assertNotFalse($domPdfPath); self::assertFalse(Settings::setPdfRenderer('FOO', 'dummy/path')); self::assertTrue(Settings::setPdfRenderer(Settings::PDF_RENDERER_DOMPDF, $domPdfPath)); diff --git a/tests/PhpWordTests/WriteReadback/Word2007Test.php b/tests/PhpWordTests/WriteReadback/Word2007Test.php index 858faa77fe..1b21a77ada 100644 --- a/tests/PhpWordTests/WriteReadback/Word2007Test.php +++ b/tests/PhpWordTests/WriteReadback/Word2007Test.php @@ -21,6 +21,7 @@ use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\IOFactory; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Writer\Word2007; @@ -28,11 +29,17 @@ * Test class for PhpOffice\PhpWord\Reader\Word2007 and PhpOffice\PhpWord\Writer\Word2007. * * @coversDefaultClass \PhpOffice\PhpWord\Reader\Word2007 - * - * @runTestsInSeparateProcesses */ class Word2007Test extends \PHPUnit\Framework\TestCase { + /** + * Executed after each method of the class. + */ + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Test default font name. */ diff --git a/tests/PhpWordTests/Writer/HTML/FontTest.php b/tests/PhpWordTests/Writer/HTML/FontTest.php index 6b6e920b89..6b4f44854e 100644 --- a/tests/PhpWordTests/Writer/HTML/FontTest.php +++ b/tests/PhpWordTests/Writer/HTML/FontTest.php @@ -28,37 +28,17 @@ */ class FontTest extends \PHPUnit\Framework\TestCase { - /** @var string */ - private $defaultFontName; - - /** @var float|int */ - private $defaultFontSize; - - /** @var string */ - private $defaultFontColor; - - /** - * Executed before each method of the class. - */ - protected function setUp(): void - { - $this->defaultFontName = Settings::getDefaultFontName(); - $this->defaultFontSize = Settings::getDefaultFontSize(); - $this->defaultFontColor = Settings::getDefaultFontColor(); - } - /** * Executed after each method of the class. */ protected function tearDown(): void { - Settings::setDefaultFontName($this->defaultFontName); - Settings::setDefaultFontSize($this->defaultFontSize); - Settings::setDefaultFontColor($this->defaultFontColor); + Settings::restoreDefaults(); } public function testDefaultDefaults(): void { + Settings::restoreDefaults(); $phpWord = new PhpWord(); $dom = Helper::getAsHTML($phpWord); @@ -68,7 +48,7 @@ public function testDefaultDefaults(): void $prg = preg_match('/body {(.*?)}/', $style, $matches); self::assertNotEmpty($matches); self::assertNotFalse($prg); - self::assertEquals('body {font-family: \'Arial\'; font-size: 12pt; color: #000000;}', $matches[0]); + self::assertEquals('body {font-family: \'Arial\'; font-size: 10pt; color: #000000;}', $matches[0]); } public function testSettingDefaultFontColor(): void @@ -85,7 +65,7 @@ public function testSettingDefaultFontColor(): void $prg = preg_match('/body {(.*?)}/', $style, $matches); self::assertNotEmpty($matches); self::assertNotFalse($prg); - self::assertEquals('body {font-family: \'Arial\'; font-size: 12pt; color: #00FF00;}', $matches[0]); + self::assertEquals('body {font-family: \'Arial\'; font-size: 10pt; color: #00FF00;}', $matches[0]); } /** diff --git a/tests/PhpWordTests/Writer/ODText/Part/ContentTest.php b/tests/PhpWordTests/Writer/ODText/Part/ContentTest.php index e6cad15ea0..412197f2c6 100644 --- a/tests/PhpWordTests/Writer/ODText/Part/ContentTest.php +++ b/tests/PhpWordTests/Writer/ODText/Part/ContentTest.php @@ -30,20 +30,9 @@ */ class ContentTest extends \PHPUnit\Framework\TestCase { - /** @var string */ - private $defaultFontName; - - /** - * Executed before each method of the class. - */ - protected function setUp(): void - { - $this->defaultFontName = Settings::getDefaultFontName(); - } - protected function tearDown(): void { - Settings::setDefaultFontName($this->defaultFontName); + Settings::restoreDefaults(); TestHelperDOCX::clear(); } diff --git a/tests/PhpWordTests/Writer/ODText/Style/FontTest.php b/tests/PhpWordTests/Writer/ODText/Style/FontTest.php index b377ff4fcd..f238777cf4 100644 --- a/tests/PhpWordTests/Writer/ODText/Style/FontTest.php +++ b/tests/PhpWordTests/Writer/ODText/Style/FontTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWordTests\Writer\ODText\Style; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWordTests\TestHelperDOCX; @@ -31,6 +32,7 @@ class FontTest extends \PHPUnit\Framework\TestCase */ protected function tearDown(): void { + Settings::restoreDefaults(); TestHelperDOCX::clear(); } diff --git a/tests/PhpWordTests/Writer/PDF/DomPDFTest.php b/tests/PhpWordTests/Writer/PDF/DomPDFTest.php index 4c5c6ba0d9..870e417d3b 100644 --- a/tests/PhpWordTests/Writer/PDF/DomPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/DomPDFTest.php @@ -24,11 +24,14 @@ /** * Test class for PhpOffice\PhpWord\Writer\PDF\DomPDF. - * - * @runTestsInSeparateProcesses */ class DomPDFTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Test construct. */ @@ -73,8 +76,6 @@ public function testSetGetAbstractRendererProperties(): void */ public function testSetGetAbstractRendererOptions(): void { - define('DOMPDF_ENABLE_AUTOLOAD', false); - $rendererName = Settings::PDF_RENDERER_DOMPDF; $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); self::assertNotFalse($rendererLibraryPath); diff --git a/tests/PhpWordTests/Writer/PDF/MPDFTest.php b/tests/PhpWordTests/Writer/PDF/MPDFTest.php index 90048c4163..e48b961785 100644 --- a/tests/PhpWordTests/Writer/PDF/MPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/MPDFTest.php @@ -25,18 +25,19 @@ /** * Test class for PhpOffice\PhpWord\Writer\PDF\MPDF. - * - * @runTestsInSeparateProcesses */ class MPDFTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Test construct. */ public function testConstruct(): void { - $file = __DIR__ . '/../../_files/mpdf.pdf'; - $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText('Test 1'); @@ -49,17 +50,14 @@ public function testConstruct(): void $writer = new MPDF($phpWord); $writer->setFont('xyz'); - $writer->save($file); - - self::assertFileExists($file); - - unlink($file); + ob_start(); + $writer->save('php://output'); + $contents = (string) ob_get_clean(); + self::assertSame('%PDF', substr($contents, 0, 4)); } public function testEditCallback(): void { - $file = __DIR__ . '/../../_files/mpdf.pdf'; - $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText('Test 1'); @@ -74,11 +72,10 @@ public function testEditCallback(): void /** @var callable */ $callback = [self::class, 'cbEditContent']; $writer->setEditCallback($callback); - $writer->save($file); - - self::assertFileExists($file); - - unlink($file); + ob_start(); + $writer->save('php://output'); + $contents = (string) ob_get_clean(); + self::assertSame('%PDF', substr($contents, 0, 4)); } // add a footer diff --git a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php index de8ac147af..2a623ec6ce 100644 --- a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php @@ -28,13 +28,16 @@ */ class TCPDFTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Test construct. */ public function testConstruct(): void { - $file = __DIR__ . '/../../_files/tcpdf.pdf'; - $phpWord = new PhpWord(); $phpWord->setDefaultParagraphStyle(['spaceBefore' => 0, 'spaceAfter' => 0]); $section = $phpWord->addSection(); @@ -42,11 +45,10 @@ public function testConstruct(): void $writer = new PDF\TCPDF($phpWord); $writer->setFont('Helvetica'); - $writer->save($file); - - self::assertFileExists($file); - - unlink($file); + ob_start(); + $writer->save('php://output'); + $contents = (string) ob_get_clean(); + self::assertSame('%PDF', substr($contents, 0, 4)); } /** diff --git a/tests/PhpWordTests/Writer/PDFTest.php b/tests/PhpWordTests/Writer/PDFTest.php index c5c8f3b7aa..6e1fbc1c74 100644 --- a/tests/PhpWordTests/Writer/PDFTest.php +++ b/tests/PhpWordTests/Writer/PDFTest.php @@ -24,30 +24,30 @@ /** * Test class for PhpOffice\PhpWord\Writer\PDF. - * - * @runTestsInSeparateProcesses */ class PDFTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Test normal construct. */ public function testConstructDompdf(): void { - define('DOMPDF_ENABLE_AUTOLOAD', false); - $file = __DIR__ . '/../_files/temp.pdf'; - + Settings::restoreDefaults(); $rendererName = Settings::PDF_RENDERER_DOMPDF; $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); self::assertNotFalse($rendererLibraryPath); Settings::setPdfRenderer($rendererName, $rendererLibraryPath); $writer = new PDF(new PhpWord()); $writer->setFont('xyz'); - $writer->save($file); - - self::assertFileExists($file); - - unlink($file); + ob_start(); + $writer->save('php://output'); + $contents = (string) ob_get_clean(); + self::assertSame('%PDF', substr($contents, 0, 4)); } public function testConstructMpdf(): void @@ -60,11 +60,10 @@ public function testConstructMpdf(): void Settings::setPdfRenderer($rendererName, $rendererLibraryPath); $writer = new PDF(new PhpWord()); $writer->setFont('xyz'); - $writer->save($file); - - self::assertFileExists($file); - - unlink($file); + ob_start(); + $writer->save('php://output'); + $contents = (string) ob_get_clean(); + self::assertSame('%PDF', substr($contents, 0, 4)); } public function testConstructTcpdf(): void @@ -77,11 +76,10 @@ public function testConstructTcpdf(): void Settings::setPdfRenderer($rendererName, $rendererLibraryPath); $writer = new PDF(new PhpWord()); $writer->setFont('Helvetica'); - $writer->save($file); - - self::assertFileExists($file); - - unlink($file); + ob_start(); + $writer->save('php://output'); + $contents = (string) ob_get_clean(); + self::assertSame('%PDF', substr($contents, 0, 4)); } /** @@ -89,6 +87,7 @@ public function testConstructTcpdf(): void */ public function testConstructException(): void { + Settings::restoreDefaults(); $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); $this->expectExceptionMessage('PDF rendering library or library path has not been defined.'); $writer = new PDF(new PhpWord()); diff --git a/tests/PhpWordTests/Writer/Word2007/Part/StylesTest.php b/tests/PhpWordTests/Writer/Word2007/Part/StylesTest.php index 09936d6d33..88274b3622 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/StylesTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/StylesTest.php @@ -19,6 +19,7 @@ namespace PhpOffice\PhpWordTests\Writer\Word2007\Part; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; @@ -28,8 +29,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Part\Styles. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Part\Styles - * - * @runTestsInSeparateProcesses */ class StylesTest extends \PHPUnit\Framework\TestCase { @@ -38,6 +37,7 @@ class StylesTest extends \PHPUnit\Framework\TestCase */ protected function tearDown(): void { + Settings::restoreDefaults(); TestHelperDOCX::clear(); } From ec23a9c4de51c609afc2ecc28ee3e821aef391a5 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:29:35 -0700 Subject: [PATCH 111/153] Weirdness in One Test for Phpunit 9 (Php7.* and Php8.0) PhpWordTest tries to save as if it were a browser even though that is not the case. This causes a problem for Phpunit 9 (Php 7.* or 8.0) if not run in separate process. So let it run separate. No harm for Php8.1+, except for a slight performance penalty. --- tests/PhpWordTests/PhpWordTest.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/PhpWordTests/PhpWordTest.php b/tests/PhpWordTests/PhpWordTest.php index 0e99909aa9..5150862c4e 100644 --- a/tests/PhpWordTests/PhpWordTest.php +++ b/tests/PhpWordTests/PhpWordTest.php @@ -157,7 +157,14 @@ public function testAddTitleStyle(): void } /** - * Test save. + * Weird test - tries to save as if it were a browser even though that + * is not the case. This causes a problem for Phpunit 9 (Php 7.* or 8.0) + * if not run in separate process. So let it run separate. No harm + * for Php8.1+, except for a slight performance penalty. + * + * @runInSeparateProcess + * + * @preserveGlobalState disabled */ public function testSave(): void { From 763e34c14d0ce4bb03e1f55a0d32399da8375bd1 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 30 Sep 2025 21:36:52 -0700 Subject: [PATCH 112/153] Convert More Separate Process Tests --- .../ComplexType/FootnotePropertiesTest.php | 2 - .../ComplexType/RubyPropertiesTest.php | 2 - .../PhpWordTests/Exception/ExceptionTest.php | 2 - .../Exception/InvalidImageExceptionTest.php | 2 - .../Exception/InvalidStyleExceptionTest.php | 2 - .../UnsupportedImageTypeExceptionTest.php | 2 - tests/PhpWordTests/IOFactoryTest.php | 7 +- tests/PhpWordTests/MediaTest.php | 2 - tests/PhpWordTests/Metadata/DocInfoTest.php | 2 - tests/PhpWordTests/Metadata/SettingsTest.php | 2 - tests/PhpWordTests/SettingsRtlTest.php | 2 - tests/PhpWordTests/Shared/ZipArchiveTest.php | 75 ++++++++++--------- tests/PhpWordTests/StyleTest.php | 2 - .../TemplateProcessorSectionTest.php | 2 - .../PhpWordTests/WriteReadback/ODTextTest.php | 2 - 15 files changed, 46 insertions(+), 62 deletions(-) diff --git a/tests/PhpWordTests/ComplexType/FootnotePropertiesTest.php b/tests/PhpWordTests/ComplexType/FootnotePropertiesTest.php index 6472968d3b..22b8edab27 100644 --- a/tests/PhpWordTests/ComplexType/FootnotePropertiesTest.php +++ b/tests/PhpWordTests/ComplexType/FootnotePropertiesTest.php @@ -26,8 +26,6 @@ * Test class for PhpOffice\PhpWord\ComplexType\FootnoteProperties. * * @coversDefaultClass \PhpOffice\PhpWord\ComplexType\FootnoteProperties - * - * @runTestsInSeparateProcesses */ class FootnotePropertiesTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/ComplexType/RubyPropertiesTest.php b/tests/PhpWordTests/ComplexType/RubyPropertiesTest.php index 62bc0b0739..d494b2ab0d 100644 --- a/tests/PhpWordTests/ComplexType/RubyPropertiesTest.php +++ b/tests/PhpWordTests/ComplexType/RubyPropertiesTest.php @@ -23,8 +23,6 @@ /** * Test class for PhpOffice\PhpWord\ComplexType\RubyProperties. - * - * @runTestsInSeparateProcesses */ class RubyPropertiesTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Exception/ExceptionTest.php b/tests/PhpWordTests/Exception/ExceptionTest.php index 09ee933c86..8bbd03190a 100644 --- a/tests/PhpWordTests/Exception/ExceptionTest.php +++ b/tests/PhpWordTests/Exception/ExceptionTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Exception\Exception. * * @coversDefaultClass \Exception - * - * @runTestsInSeparateProcesses */ class ExceptionTest extends TestCase { diff --git a/tests/PhpWordTests/Exception/InvalidImageExceptionTest.php b/tests/PhpWordTests/Exception/InvalidImageExceptionTest.php index dea167bb25..045d7124a0 100644 --- a/tests/PhpWordTests/Exception/InvalidImageExceptionTest.php +++ b/tests/PhpWordTests/Exception/InvalidImageExceptionTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Exception\InvalidImageException. * * @coversDefaultClass \PhpOffice\PhpWord\Exception\InvalidImageException - * - * @runTestsInSeparateProcesses */ class InvalidImageExceptionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Exception/InvalidStyleExceptionTest.php b/tests/PhpWordTests/Exception/InvalidStyleExceptionTest.php index 7f2a1650e4..f38fc3cf44 100644 --- a/tests/PhpWordTests/Exception/InvalidStyleExceptionTest.php +++ b/tests/PhpWordTests/Exception/InvalidStyleExceptionTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Exception\InvalidStyleException. * * @coversDefaultClass \PhpOffice\PhpWord\Exception\InvalidStyleException - * - * @runTestsInSeparateProcesses */ class InvalidStyleExceptionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Exception/UnsupportedImageTypeExceptionTest.php b/tests/PhpWordTests/Exception/UnsupportedImageTypeExceptionTest.php index 2252b874ef..d40dc8ae9a 100644 --- a/tests/PhpWordTests/Exception/UnsupportedImageTypeExceptionTest.php +++ b/tests/PhpWordTests/Exception/UnsupportedImageTypeExceptionTest.php @@ -22,8 +22,6 @@ /** * Test class for PhpOffice\PhpWord\Exception\UnsupportedImageTypeExceptionTest. - * - * @runTestsInSeparateProcesses */ class UnsupportedImageTypeExceptionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/IOFactoryTest.php b/tests/PhpWordTests/IOFactoryTest.php index 6a8d746bd0..bf69703336 100644 --- a/tests/PhpWordTests/IOFactoryTest.php +++ b/tests/PhpWordTests/IOFactoryTest.php @@ -31,8 +31,6 @@ /** * Test class for PhpOffice\PhpWord\IOFactory. - * - * @runTestsInSeparateProcesses */ class IOFactoryTest extends TestCase { @@ -43,6 +41,11 @@ protected function setUp(): void Settings::setPdfRenderer($rendererName, $rendererLibraryPath); } + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Create all possible writers. * diff --git a/tests/PhpWordTests/MediaTest.php b/tests/PhpWordTests/MediaTest.php index 2eb60a858b..2774e46e2a 100644 --- a/tests/PhpWordTests/MediaTest.php +++ b/tests/PhpWordTests/MediaTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Media. - * - * @runTestsInSeparateProcesses */ class MediaTest extends AbstractWebServerEmbedded { diff --git a/tests/PhpWordTests/Metadata/DocInfoTest.php b/tests/PhpWordTests/Metadata/DocInfoTest.php index d5c9eb5124..f6a9911fb4 100644 --- a/tests/PhpWordTests/Metadata/DocInfoTest.php +++ b/tests/PhpWordTests/Metadata/DocInfoTest.php @@ -22,8 +22,6 @@ /** * Test class for PhpOffice\PhpWord\Metadata\DocInfo. - * - * @runTestsInSeparateProcesses */ class DocInfoTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Metadata/SettingsTest.php b/tests/PhpWordTests/Metadata/SettingsTest.php index 0c5a6f1593..9da794c685 100644 --- a/tests/PhpWordTests/Metadata/SettingsTest.php +++ b/tests/PhpWordTests/Metadata/SettingsTest.php @@ -26,8 +26,6 @@ /** * Test class for PhpOffice\PhpWord\Metadata\Settings. - * - * @runTestsInSeparateProcesses */ class SettingsTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/SettingsRtlTest.php b/tests/PhpWordTests/SettingsRtlTest.php index 67747c7c0a..b424c123d1 100644 --- a/tests/PhpWordTests/SettingsRtlTest.php +++ b/tests/PhpWordTests/SettingsRtlTest.php @@ -28,8 +28,6 @@ * Test class for PhpOffice\PhpWord\Settings. * * @coversDefaultClass \PhpOffice\PhpWord\Settings - * - * @runTestsInSeparateProcesses */ class SettingsRtlTest extends TestCase { diff --git a/tests/PhpWordTests/Shared/ZipArchiveTest.php b/tests/PhpWordTests/Shared/ZipArchiveTest.php index 3f998c26ef..5cc6249780 100644 --- a/tests/PhpWordTests/Shared/ZipArchiveTest.php +++ b/tests/PhpWordTests/Shared/ZipArchiveTest.php @@ -25,39 +25,47 @@ * Test class for PhpOffice\PhpWord\Shared\ZipArchive. * * @coversDefaultClass \PhpOffice\PhpWord\Shared\ZipArchive - * - * @runTestsInSeparateProcesses */ class ZipArchiveTest extends \PHPUnit\Framework\TestCase { -// /** -// * Test close method exception: Working in local, not working in Travis -// * -// * expectedException \PhpOffice\PhpWord\Exception\Exception -// * expectedExceptionMessage Could not close zip file -// * covers ::close -// */ -// public function testCloseException() -// { -// $zipFile = __DIR__ . "/../_files/documents/ziptest.zip"; - -// $object = new ZipArchive(); -// $object->open($zipFile, ZipArchive::CREATE); -// $object->addFromString('content/string.txt', 'Test'); - -// // Lock the file -// $resource = fopen($zipFile, "w"); -// flock($resource, LOCK_EX); - -// // Closing the file should throws an exception -// $object->close(); - -// // Unlock the file -// flock($resource, LOCK_UN); -// fclose($resource); - -// @unlink($zipFile); -// } + /** @var string */ + private $zipFile; + + protected function tearDown(): void + { + if (file_exists($this->zipFile)) { + unlink($this->zipFile); + } + Settings::restoreDefaults(); + } + + /** + * Test close method exception. + * + * covers ::close + */ + public function testCloseException(): void + { + $this->zipFile = __DIR__ . '/../_files/documents/ziptest1.zip'; + $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectExceptionMessage('Could not close zip file'); + + $object = new ZipArchive(); + $object->open($this->zipFile, ZipArchive::CREATE); + $object->addFromString('content/string.txt', 'Test'); + + // Lock the file + $resource = fopen($this->zipFile, 'wb'); + self::assertNotFalse($resource); + flock($resource, LOCK_EX); + + // Closing the file should throws an exception + $object->close(); + + // Unlock the file + flock($resource, LOCK_UN); + fclose($resource); + } /** * Test all methods. @@ -68,7 +76,7 @@ public function testZipArchive($zipClass = 'ZipArchive'): void { // Preparation $existingFile = __DIR__ . '/../_files/documents/sheet.xls'; - $zipFile = __DIR__ . '/../_files/documents/ziptest.zip'; + $this->zipFile = __DIR__ . '/../_files/documents/ziptest2.zip'; $destination1 = __DIR__ . '/../_files/documents/extract1'; $destination2 = __DIR__ . '/../_files/documents/extract2'; @mkdir($destination1); @@ -77,11 +85,11 @@ public function testZipArchive($zipClass = 'ZipArchive'): void Settings::setZipClass($zipClass); $object = new ZipArchive(); - $object->open($zipFile, ZipArchive::CREATE); + $object->open($this->zipFile, ZipArchive::CREATE); $object->addFile($existingFile, 'xls/new.xls'); $object->addFromString('content/string.txt', 'Test'); $object->close(); - $object->open($zipFile); + $object->open($this->zipFile); // Run tests self::assertEquals(0, $object->locateName('xls/new.xls')); @@ -101,7 +109,6 @@ public function testZipArchive($zipClass = 'ZipArchive'): void // Cleanup $this->deleteDir($destination1); $this->deleteDir($destination2); - @unlink($zipFile); } /** diff --git a/tests/PhpWordTests/StyleTest.php b/tests/PhpWordTests/StyleTest.php index 6115e04426..32d61e09f6 100644 --- a/tests/PhpWordTests/StyleTest.php +++ b/tests/PhpWordTests/StyleTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Style. * * @coversDefaultClass \PhpOffice\PhpWord\Style - * - * @runTestsInSeparateProcesses */ class StyleTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/TemplateProcessorSectionTest.php b/tests/PhpWordTests/TemplateProcessorSectionTest.php index a6e9d61163..211566f4c3 100644 --- a/tests/PhpWordTests/TemplateProcessorSectionTest.php +++ b/tests/PhpWordTests/TemplateProcessorSectionTest.php @@ -27,8 +27,6 @@ * @covers \PhpOffice\PhpWord\TemplateProcessor * * @coversDefaultClass \PhpOffice\PhpWord\TemplateProcessor - * - * @runTestsInSeparateProcesses */ final class TemplateProcessorSectionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/WriteReadback/ODTextTest.php b/tests/PhpWordTests/WriteReadback/ODTextTest.php index cee0f38301..613eb8e508 100644 --- a/tests/PhpWordTests/WriteReadback/ODTextTest.php +++ b/tests/PhpWordTests/WriteReadback/ODTextTest.php @@ -27,8 +27,6 @@ * Test class for PhpOffice\PhpWord\Reader\ODText and PhpOffice\PhpWord\Writer\ODText. * * @coversDefaultClass \PhpOffice\PhpWord\Reader\ODText - * - * @runTestsInSeparateProcesses */ class ODTextTest extends \PHPUnit\Framework\TestCase { From 9309e04c15e8cbb338b3bcb2628d92b1f631c81f Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 30 Sep 2025 22:27:27 -0700 Subject: [PATCH 113/153] Try Another Means to Generate Zip Close Failure --- tests/PhpWordTests/Shared/ZipArchiveTest.php | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tests/PhpWordTests/Shared/ZipArchiveTest.php b/tests/PhpWordTests/Shared/ZipArchiveTest.php index 5cc6249780..9e285f0e4c 100644 --- a/tests/PhpWordTests/Shared/ZipArchiveTest.php +++ b/tests/PhpWordTests/Shared/ZipArchiveTest.php @@ -53,18 +53,10 @@ public function testCloseException(): void $object = new ZipArchive(); $object->open($this->zipFile, ZipArchive::CREATE); $object->addFromString('content/string.txt', 'Test'); - - // Lock the file - $resource = fopen($this->zipFile, 'wb'); - self::assertNotFalse($resource); - flock($resource, LOCK_EX); - - // Closing the file should throws an exception + // Should not be able to add a directory like this + $object->addFile('.', '.'); + // But error doesn't express itself till close is attempted $object->close(); - - // Unlock the file - flock($resource, LOCK_UN); - fclose($resource); } /** From 30a56b059f6fa7ec39ac10622a00df511c0d63f8 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 1 Oct 2025 08:39:16 -0700 Subject: [PATCH 114/153] Eliminate More Test Separate Processes Removed from Element and Reader tests. Style and Writer remain for now. --- tests/PhpWordTests/Element/BookmarkTest.php | 2 -- tests/PhpWordTests/Element/CellTest.php | 2 -- tests/PhpWordTests/Element/CheckBoxTest.php | 2 -- tests/PhpWordTests/Element/CommentTest.php | 2 -- tests/PhpWordTests/Element/FieldTest.php | 2 -- tests/PhpWordTests/Element/FooterTest.php | 2 -- tests/PhpWordTests/Element/FootnoteTest.php | 2 -- tests/PhpWordTests/Element/FormulaTest.php | 2 -- tests/PhpWordTests/Element/HeaderTest.php | 2 -- tests/PhpWordTests/Element/LineTest.php | 2 -- tests/PhpWordTests/Element/LinkTest.php | 2 -- tests/PhpWordTests/Element/ListItemRunTest.php | 2 -- tests/PhpWordTests/Element/ListItemTest.php | 2 -- tests/PhpWordTests/Element/ObjectTest.php | 2 -- tests/PhpWordTests/Element/PreserveTextTest.php | 2 -- tests/PhpWordTests/Element/RowTest.php | 2 -- tests/PhpWordTests/Element/RubyTest.php | 2 -- tests/PhpWordTests/Element/SectionTest.php | 2 -- tests/PhpWordTests/Element/TOCTest.php | 2 -- tests/PhpWordTests/Element/TableTest.php | 2 -- tests/PhpWordTests/Element/TextBoxTest.php | 2 -- tests/PhpWordTests/Element/TextBreakTest.php | 2 -- tests/PhpWordTests/Element/TextRunTest.php | 2 -- tests/PhpWordTests/Element/TextTest.php | 2 -- tests/PhpWordTests/Element/TitleTest.php | 2 -- tests/PhpWordTests/Element/TrackChangeTest.php | 2 -- tests/PhpWordTests/Reader/HTMLTest.php | 2 -- tests/PhpWordTests/Reader/MsDocTest.php | 2 -- tests/PhpWordTests/Reader/ODTextTest.php | 2 -- tests/PhpWordTests/Reader/RTFTest.php | 2 -- tests/PhpWordTests/Reader/Word2007/StyleTableTest.php | 2 -- tests/PhpWordTests/Reader/Word2007Test.php | 2 -- 32 files changed, 64 deletions(-) diff --git a/tests/PhpWordTests/Element/BookmarkTest.php b/tests/PhpWordTests/Element/BookmarkTest.php index 097166736f..5499b18a96 100644 --- a/tests/PhpWordTests/Element/BookmarkTest.php +++ b/tests/PhpWordTests/Element/BookmarkTest.php @@ -22,8 +22,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Footer. - * - * @runTestsInSeparateProcesses */ class BookmarkTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/CellTest.php b/tests/PhpWordTests/Element/CellTest.php index 2fedcafc24..a29a864483 100644 --- a/tests/PhpWordTests/Element/CellTest.php +++ b/tests/PhpWordTests/Element/CellTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Cell. - * - * @runTestsInSeparateProcesses */ class CellTest extends AbstractWebServerEmbedded { diff --git a/tests/PhpWordTests/Element/CheckBoxTest.php b/tests/PhpWordTests/Element/CheckBoxTest.php index fbdbf36aa3..1b808ee53c 100644 --- a/tests/PhpWordTests/Element/CheckBoxTest.php +++ b/tests/PhpWordTests/Element/CheckBoxTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\CheckBox. - * - * @runTestsInSeparateProcesses */ class CheckBoxTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/CommentTest.php b/tests/PhpWordTests/Element/CommentTest.php index f76316d890..c066429839 100644 --- a/tests/PhpWordTests/Element/CommentTest.php +++ b/tests/PhpWordTests/Element/CommentTest.php @@ -26,8 +26,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Header. - * - * @runTestsInSeparateProcesses */ class CommentTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/FieldTest.php b/tests/PhpWordTests/Element/FieldTest.php index f624e9294d..d0018f1e1c 100644 --- a/tests/PhpWordTests/Element/FieldTest.php +++ b/tests/PhpWordTests/Element/FieldTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Field. - * - * @runTestsInSeparateProcesses */ class FieldTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/FooterTest.php b/tests/PhpWordTests/Element/FooterTest.php index f97b159cb9..7b97d52633 100644 --- a/tests/PhpWordTests/Element/FooterTest.php +++ b/tests/PhpWordTests/Element/FooterTest.php @@ -23,8 +23,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Footer. - * - * @runTestsInSeparateProcesses */ class FooterTest extends AbstractWebServerEmbedded { diff --git a/tests/PhpWordTests/Element/FootnoteTest.php b/tests/PhpWordTests/Element/FootnoteTest.php index c6297cfc32..b51130532f 100644 --- a/tests/PhpWordTests/Element/FootnoteTest.php +++ b/tests/PhpWordTests/Element/FootnoteTest.php @@ -22,8 +22,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Footnote. - * - * @runTestsInSeparateProcesses */ class FootnoteTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/FormulaTest.php b/tests/PhpWordTests/Element/FormulaTest.php index dcb730e9cc..6a3fe44570 100644 --- a/tests/PhpWordTests/Element/FormulaTest.php +++ b/tests/PhpWordTests/Element/FormulaTest.php @@ -25,8 +25,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Formula. - * - * @runTestsInSeparateProcesses */ class FormulaTest extends AbstractWebServerEmbedded { diff --git a/tests/PhpWordTests/Element/HeaderTest.php b/tests/PhpWordTests/Element/HeaderTest.php index 6d659dbd7c..323a43a52d 100644 --- a/tests/PhpWordTests/Element/HeaderTest.php +++ b/tests/PhpWordTests/Element/HeaderTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Header. - * - * @runTestsInSeparateProcesses */ class HeaderTest extends AbstractWebServerEmbedded { diff --git a/tests/PhpWordTests/Element/LineTest.php b/tests/PhpWordTests/Element/LineTest.php index f4a39b38d1..e9eac1743f 100644 --- a/tests/PhpWordTests/Element/LineTest.php +++ b/tests/PhpWordTests/Element/LineTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Element\Line. * * @coversDefaultClass \PhpOffice\PhpWord\Element\Line - * - * @runTestsInSeparateProcesses */ class LineTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/LinkTest.php b/tests/PhpWordTests/Element/LinkTest.php index 5b5c6f77bf..91e1b787eb 100644 --- a/tests/PhpWordTests/Element/LinkTest.php +++ b/tests/PhpWordTests/Element/LinkTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Element\Link. * * @coversDefaultClass \PhpOffice\PhpWord\Element\Link - * - * @runTestsInSeparateProcesses */ class LinkTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/ListItemRunTest.php b/tests/PhpWordTests/Element/ListItemRunTest.php index 69b5f990b0..8c9d0d6b14 100644 --- a/tests/PhpWordTests/Element/ListItemRunTest.php +++ b/tests/PhpWordTests/Element/ListItemRunTest.php @@ -22,8 +22,6 @@ /** * Test class for PhpOffice\PhpWord\Element\ListItemRun. - * - * @runTestsInSeparateProcesses */ class ListItemRunTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/ListItemTest.php b/tests/PhpWordTests/Element/ListItemTest.php index 3da9a8d101..f38040c345 100644 --- a/tests/PhpWordTests/Element/ListItemTest.php +++ b/tests/PhpWordTests/Element/ListItemTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Element\ListItem. * * @coversDefaultClass \PhpOffice\PhpWord\Element\ListItem - * - * @runTestsInSeparateProcesses */ class ListItemTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/ObjectTest.php b/tests/PhpWordTests/Element/ObjectTest.php index 2ef05567bc..c18bdaa373 100644 --- a/tests/PhpWordTests/Element/ObjectTest.php +++ b/tests/PhpWordTests/Element/ObjectTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Element\OLEObject. * * @coversDefaultClass \PhpOffice\PhpWord\Element\OLEObject - * - * @runTestsInSeparateProcesses */ class ObjectTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/PreserveTextTest.php b/tests/PhpWordTests/Element/PreserveTextTest.php index 746db6aeee..9bcb0b5b3b 100644 --- a/tests/PhpWordTests/Element/PreserveTextTest.php +++ b/tests/PhpWordTests/Element/PreserveTextTest.php @@ -23,8 +23,6 @@ /** * Test class for PhpOffice\PhpWord\Element\PreserveText. - * - * @runTestsInSeparateProcesses */ class PreserveTextTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/RowTest.php b/tests/PhpWordTests/Element/RowTest.php index 6dc8335ffd..793914efdc 100644 --- a/tests/PhpWordTests/Element/RowTest.php +++ b/tests/PhpWordTests/Element/RowTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Element\Row. * * @coversDefaultClass \PhpOffice\PhpWord\Element\Row - * - * @runTestsInSeparateProcesses */ class RowTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/RubyTest.php b/tests/PhpWordTests/Element/RubyTest.php index 44d2e5ba3c..16cdea0c3e 100644 --- a/tests/PhpWordTests/Element/RubyTest.php +++ b/tests/PhpWordTests/Element/RubyTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Text. - * - * @runTestsInSeparateProcesses */ class RubyTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/SectionTest.php b/tests/PhpWordTests/Element/SectionTest.php index bf38a33300..1929c6c186 100644 --- a/tests/PhpWordTests/Element/SectionTest.php +++ b/tests/PhpWordTests/Element/SectionTest.php @@ -29,8 +29,6 @@ * @covers \PhpOffice\PhpWord\Element\Section * * @coversDefaultClass \PhpOffice\PhpWord\Element\Section - * - * @runTestsInSeparateProcesses */ class SectionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/TOCTest.php b/tests/PhpWordTests/Element/TOCTest.php index 3770823f5f..c880ba6f77 100644 --- a/tests/PhpWordTests/Element/TOCTest.php +++ b/tests/PhpWordTests/Element/TOCTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\TOC. - * - * @runTestsInSeparateProcesses */ class TOCTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/TableTest.php b/tests/PhpWordTests/Element/TableTest.php index a5169040a9..2643f7faee 100644 --- a/tests/PhpWordTests/Element/TableTest.php +++ b/tests/PhpWordTests/Element/TableTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Element\Table. * * @coversDefaultClass \PhpOffice\PhpWord\Element\Table - * - * @runTestsInSeparateProcesses */ class TableTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/TextBoxTest.php b/tests/PhpWordTests/Element/TextBoxTest.php index 9289d1ad47..3623cd3ef8 100644 --- a/tests/PhpWordTests/Element/TextBoxTest.php +++ b/tests/PhpWordTests/Element/TextBoxTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Element\TextBox. * * @coversDefaultClass \PhpOffice\PhpWord\Element\TextBox - * - * @runTestsInSeparateProcesses */ class TextBoxTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/TextBreakTest.php b/tests/PhpWordTests/Element/TextBreakTest.php index 10ca058180..bf99c2a652 100644 --- a/tests/PhpWordTests/Element/TextBreakTest.php +++ b/tests/PhpWordTests/Element/TextBreakTest.php @@ -26,8 +26,6 @@ * Test class for PhpOffice\PhpWord\Element\TextBreak. * * @coversDefaultClass \PhpOffice\PhpWord\Element\TextBreak - * - * @runTestsInSeparateProcesses */ class TextBreakTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/TextRunTest.php b/tests/PhpWordTests/Element/TextRunTest.php index b18eca99e4..b18db03794 100644 --- a/tests/PhpWordTests/Element/TextRunTest.php +++ b/tests/PhpWordTests/Element/TextRunTest.php @@ -26,8 +26,6 @@ /** * Test class for PhpOffice\PhpWord\Element\TextRun. - * - * @runTestsInSeparateProcesses */ class TextRunTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/TextTest.php b/tests/PhpWordTests/Element/TextTest.php index 693430a87f..d8c5b02eef 100644 --- a/tests/PhpWordTests/Element/TextTest.php +++ b/tests/PhpWordTests/Element/TextTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Text. - * - * @runTestsInSeparateProcesses */ class TextTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/TitleTest.php b/tests/PhpWordTests/Element/TitleTest.php index ab1e38102f..aea0aa7974 100644 --- a/tests/PhpWordTests/Element/TitleTest.php +++ b/tests/PhpWordTests/Element/TitleTest.php @@ -28,8 +28,6 @@ * Test class for PhpOffice\PhpWord\Element\Title. * * @coversDefaultClass \PhpOffice\PhpWord\Element\Title - * - * @runTestsInSeparateProcesses */ class TitleTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Element/TrackChangeTest.php b/tests/PhpWordTests/Element/TrackChangeTest.php index 1d37fb3dd4..6304578633 100644 --- a/tests/PhpWordTests/Element/TrackChangeTest.php +++ b/tests/PhpWordTests/Element/TrackChangeTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Element\TrackChange. - * - * @runTestsInSeparateProcesses */ class TrackChangeTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Reader/HTMLTest.php b/tests/PhpWordTests/Reader/HTMLTest.php index 7a35a06f78..e9228cf840 100644 --- a/tests/PhpWordTests/Reader/HTMLTest.php +++ b/tests/PhpWordTests/Reader/HTMLTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Reader\HTML. * * @coversDefaultClass \PhpOffice\PhpWord\Reader\HTML - * - * @runTestsInSeparateProcesses */ class HTMLTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Reader/MsDocTest.php b/tests/PhpWordTests/Reader/MsDocTest.php index 3552271823..32aae8fe33 100644 --- a/tests/PhpWordTests/Reader/MsDocTest.php +++ b/tests/PhpWordTests/Reader/MsDocTest.php @@ -28,8 +28,6 @@ * Test class for PhpOffice\PhpWord\Reader\MsDoc. * * @coversDefaultClass \PhpOffice\PhpWord\Reader\MsDoc - * - * @runTestsInSeparateProcesses */ class MsDocTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Reader/ODTextTest.php b/tests/PhpWordTests/Reader/ODTextTest.php index c05705a439..61c597cecf 100644 --- a/tests/PhpWordTests/Reader/ODTextTest.php +++ b/tests/PhpWordTests/Reader/ODTextTest.php @@ -28,8 +28,6 @@ * Test class for PhpOffice\PhpWord\Reader\ODText. * * @coversDefaultClass \PhpOffice\PhpWord\Reader\ODText - * - * @runTestsInSeparateProcesses */ class ODTextTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Reader/RTFTest.php b/tests/PhpWordTests/Reader/RTFTest.php index a8f472571b..4b9e900033 100644 --- a/tests/PhpWordTests/Reader/RTFTest.php +++ b/tests/PhpWordTests/Reader/RTFTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Reader\RTF. * * @coversDefaultClass \PhpOffice\PhpWord\Reader\RTF - * - * @runTestsInSeparateProcesses */ class RTFTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Reader/Word2007/StyleTableTest.php b/tests/PhpWordTests/Reader/Word2007/StyleTableTest.php index cb8c255018..c6288cf719 100644 --- a/tests/PhpWordTests/Reader/Word2007/StyleTableTest.php +++ b/tests/PhpWordTests/Reader/Word2007/StyleTableTest.php @@ -26,8 +26,6 @@ /** * Test class for PhpOffice\PhpWord\Reader\Word2007\Styles. * Run in separate process because doc changes default font. - * - * @runTestsInSeparateProcesses */ class StyleTableTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Reader/Word2007Test.php b/tests/PhpWordTests/Reader/Word2007Test.php index 65c8a4a71b..cdc8cbc1a8 100644 --- a/tests/PhpWordTests/Reader/Word2007Test.php +++ b/tests/PhpWordTests/Reader/Word2007Test.php @@ -37,8 +37,6 @@ * Test class for PhpOffice\PhpWord\Reader\Word2007. * * @coversDefaultClass \PhpOffice\PhpWord\Reader\Word2007 - * - * @runTestsInSeparateProcesses */ class Word2007Test extends \PHPUnit\Framework\TestCase { From 908a70be749a19a425c4e8c383ed93262f4c2343 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 1 Oct 2025 09:16:02 -0700 Subject: [PATCH 115/153] 3 Related Test Failures All call getRemoteGifImageUrl. Try setting those methods to separate process. --- tests/PhpWordTests/Element/CellTest.php | 3 +++ tests/PhpWordTests/Element/FooterTest.php | 2 ++ tests/PhpWordTests/Element/HeaderTest.php | 2 ++ 3 files changed, 7 insertions(+) diff --git a/tests/PhpWordTests/Element/CellTest.php b/tests/PhpWordTests/Element/CellTest.php index a29a864483..8ef1f49c08 100644 --- a/tests/PhpWordTests/Element/CellTest.php +++ b/tests/PhpWordTests/Element/CellTest.php @@ -163,6 +163,9 @@ public function testAddImageFooter(): void /** * Add image section by URL. + * Method getRemoteGifImageUrl seems to require separate process. + * + * @runInSeparateProcess */ public function testAddImageSectionByUrl(): void { diff --git a/tests/PhpWordTests/Element/FooterTest.php b/tests/PhpWordTests/Element/FooterTest.php index 7b97d52633..6cfbe6fce0 100644 --- a/tests/PhpWordTests/Element/FooterTest.php +++ b/tests/PhpWordTests/Element/FooterTest.php @@ -113,6 +113,8 @@ public function testAddImage(): void /** * Add image by URL. + * + * @runInSeparateProcess */ public function testAddImageByUrl(): void { diff --git a/tests/PhpWordTests/Element/HeaderTest.php b/tests/PhpWordTests/Element/HeaderTest.php index 323a43a52d..477f52dcf0 100644 --- a/tests/PhpWordTests/Element/HeaderTest.php +++ b/tests/PhpWordTests/Element/HeaderTest.php @@ -123,6 +123,8 @@ public function testAddImage(): void /** * Add image by URL. + * + * @runInSeparateProcess */ public function testAddImageByUrl(): void { From dc176e6bd1db828895b621cc6c0fea5c8ee6b112 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 2 Oct 2025 20:46:01 -0700 Subject: [PATCH 116/153] Finish Unneeded Separate Processes --- src/PhpWord/Settings.php | 1 + .../AbstractWebServerEmbedded.php | 6 ++--- tests/PhpWordTests/Element/FooterTest.php | 1 + tests/PhpWordTests/Element/HeaderTest.php | 1 + tests/PhpWordTests/PhpWordTest.php | 1 + .../Reader/Word2007/StyleTableTest.php | 8 +++++- tests/PhpWordTests/SettingsRtlTest.php | 11 ++------ .../PhpWordTests/Style/AbstractStyleTest.php | 2 -- tests/PhpWordTests/Style/CellTest.php | 2 -- tests/PhpWordTests/Style/ChartTest.php | 2 -- tests/PhpWordTests/Style/FontTest.php | 2 -- tests/PhpWordTests/Style/ImageTest.php | 2 -- tests/PhpWordTests/Style/LineTest.php | 2 -- tests/PhpWordTests/Style/ListItemTest.php | 2 -- .../PhpWordTests/Style/NumberingLevelTest.php | 2 -- tests/PhpWordTests/Style/PaperTest.php | 2 -- tests/PhpWordTests/Style/ParagraphTest.php | 5 +--- tests/PhpWordTests/Style/RowTest.php | 2 -- tests/PhpWordTests/Style/SectionTest.php | 2 -- .../PhpWordTests/Style/TablePositionTest.php | 2 -- tests/PhpWordTests/Style/TableTest.php | 7 +++-- tests/PhpWordTests/Style/TextBoxTest.php | 2 -- tests/PhpWordTests/StyleTest.php | 26 +++++++++++-------- tests/PhpWordTests/Writer/EPub3Test.php | 2 -- tests/PhpWordTests/Writer/HTML/PartTest.php | 12 ++++++--- tests/PhpWordTests/Writer/HTMLTest.php | 5 ++-- .../Writer/ODText/Style/Paragraph2Test.php | 5 ++++ .../Writer/ODText/Style/ParagraphTest.php | 2 ++ tests/PhpWordTests/Writer/ODTextTest.php | 2 -- tests/PhpWordTests/Writer/PDF/TCPDFTest.php | 2 ++ tests/PhpWordTests/Writer/RTFTest.php | 2 -- .../Writer/Word2007/Part/AbstractPartTest.php | 3 --- .../Writer/Word2007/Part/CommentsTest.php | 2 -- .../Writer/Word2007/Part/DocumentTest.php | 2 -- .../Writer/Word2007/Part/FooterTest.php | 2 -- .../Writer/Word2007/Part/FootnotesTest.php | 2 -- .../Writer/Word2007/Part/HeaderTest.php | 2 -- .../Writer/Word2007/Part/NumberingTest.php | 2 -- .../Writer/Word2007/Part/StylesTest.php | 2 ++ .../Writer/Word2007/Style/FontTest.php | 2 -- .../Writer/Word2007/Style/ImageTest.php | 2 -- .../Writer/Word2007/Style/ParagraphTest.php | 2 -- .../Writer/Word2007/Style/SectionTest.php | 2 -- .../Writer/Word2007/Style/TableCellTest.php | 2 -- .../Writer/Word2007/Style/TableTest.php | 2 -- tests/PhpWordTests/Writer/Word2007Test.php | 5 ++-- 46 files changed, 63 insertions(+), 96 deletions(-) diff --git a/src/PhpWord/Settings.php b/src/PhpWord/Settings.php index d8956d8428..89f8c48884 100644 --- a/src/PhpWord/Settings.php +++ b/src/PhpWord/Settings.php @@ -537,6 +537,7 @@ public static function restoreDefaults(): void self::$defaultFontName = self::DEFAULT_FONT_NAME; self::$defaultFontSize = self::DEFAULT_FONT_SIZE; self::$defaultPaper = self::DEFAULT_PAPER; + self::$defaultRtl = null; self::$measurementUnit = self::UNIT_TWIP; self::$outputEscapingEnabled = false; self::$pdfRendererName = null; diff --git a/tests/PhpWordTests/AbstractWebServerEmbedded.php b/tests/PhpWordTests/AbstractWebServerEmbedded.php index fde7e1007c..e55c5c57e7 100644 --- a/tests/PhpWordTests/AbstractWebServerEmbedded.php +++ b/tests/PhpWordTests/AbstractWebServerEmbedded.php @@ -51,7 +51,7 @@ protected static function getRemoteImageUrl() return self::getBaseUrl() . '/images/new-php-logo.png'; } - return 'http://php.net/images/logos/new-php-logo.png'; + return 'https://www.php.net/images/logos/new-php-logo.png'; } protected static function getRemoteImageUrlWithoutExtension(): string @@ -69,7 +69,7 @@ protected static function getRemoteGifImageUrl() return self::getBaseUrl() . '/images/mario.gif'; } - return 'http://php.net/images/logos/php-med-trans-light.gif'; + return 'https://www.php.net/images/logos/php-med-trans-light.gif'; } protected static function getRemoteBmpImageUrl() @@ -78,6 +78,6 @@ protected static function getRemoteBmpImageUrl() return self::getBaseUrl() . '/images/duke_nukem.bmp'; } - return 'https://samples.libav.org/image-samples/RACECAR.BMP'; + return 'https://examplefiles.org/files/images/bmp-example-file-download-500x500.bmp'; } } diff --git a/tests/PhpWordTests/Element/FooterTest.php b/tests/PhpWordTests/Element/FooterTest.php index 6cfbe6fce0..1d368834b1 100644 --- a/tests/PhpWordTests/Element/FooterTest.php +++ b/tests/PhpWordTests/Element/FooterTest.php @@ -113,6 +113,7 @@ public function testAddImage(): void /** * Add image by URL. + * Method getRemoteGifImageUrl seems to require separate process. * * @runInSeparateProcess */ diff --git a/tests/PhpWordTests/Element/HeaderTest.php b/tests/PhpWordTests/Element/HeaderTest.php index 477f52dcf0..8e3d7e500c 100644 --- a/tests/PhpWordTests/Element/HeaderTest.php +++ b/tests/PhpWordTests/Element/HeaderTest.php @@ -123,6 +123,7 @@ public function testAddImage(): void /** * Add image by URL. + * Method getRemoteGifImageUrl seems to require separate process. * * @runInSeparateProcess */ diff --git a/tests/PhpWordTests/PhpWordTest.php b/tests/PhpWordTests/PhpWordTest.php index 5150862c4e..8726b936b9 100644 --- a/tests/PhpWordTests/PhpWordTest.php +++ b/tests/PhpWordTests/PhpWordTest.php @@ -35,6 +35,7 @@ class PhpWordTest extends \PHPUnit\Framework\TestCase */ protected function tearDown(): void { + Style::resetStyles(); Settings::restoreDefaults(); } diff --git a/tests/PhpWordTests/Reader/Word2007/StyleTableTest.php b/tests/PhpWordTests/Reader/Word2007/StyleTableTest.php index c6288cf719..932cc3b595 100644 --- a/tests/PhpWordTests/Reader/Word2007/StyleTableTest.php +++ b/tests/PhpWordTests/Reader/Word2007/StyleTableTest.php @@ -20,15 +20,21 @@ use PhpOffice\PhpWord\Element\Table; use PhpOffice\PhpWord\Reader\Word2007 as DocxReader; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Table as TableStyle; /** * Test class for PhpOffice\PhpWord\Reader\Word2007\Styles. - * Run in separate process because doc changes default font. */ class StyleTableTest extends \PHPUnit\Framework\TestCase { + // Reading this doc changes default font. + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Test reading of table layout. */ diff --git a/tests/PhpWordTests/SettingsRtlTest.php b/tests/PhpWordTests/SettingsRtlTest.php index b424c123d1..a68c8e0459 100644 --- a/tests/PhpWordTests/SettingsRtlTest.php +++ b/tests/PhpWordTests/SettingsRtlTest.php @@ -31,17 +31,10 @@ */ class SettingsRtlTest extends TestCase { - /** @var bool */ - private $defaultRtl; - - protected function setUp(): void - { - $this->defaultRtl = Settings::isDefaultRtl(); - } - protected function tearDown(): void { - Settings::setDefaultRtl($this->defaultRtl); + Settings::restoreDefaults(); + Style::resetStyles(); } public function testSetGetDefaultRtl(): void diff --git a/tests/PhpWordTests/Style/AbstractStyleTest.php b/tests/PhpWordTests/Style/AbstractStyleTest.php index 3b3cef900e..74de4fc29e 100644 --- a/tests/PhpWordTests/Style/AbstractStyleTest.php +++ b/tests/PhpWordTests/Style/AbstractStyleTest.php @@ -26,8 +26,6 @@ /** * Test class for PhpOffice\PhpWord\Style\AbstractStyle. - * - * @runTestsInSeparateProcesses */ class AbstractStyleTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/CellTest.php b/tests/PhpWordTests/Style/CellTest.php index 56b099c05f..847ed484bf 100644 --- a/tests/PhpWordTests/Style/CellTest.php +++ b/tests/PhpWordTests/Style/CellTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Style\Cell. * * @coversDefaultClass \PhpOffice\PhpWord\Style\Cell - * - * @runTestsInSeparateProcesses */ class CellTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/ChartTest.php b/tests/PhpWordTests/Style/ChartTest.php index e8e75901d7..8390bd53c7 100644 --- a/tests/PhpWordTests/Style/ChartTest.php +++ b/tests/PhpWordTests/Style/ChartTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Style\Chart. * * @coversDefaultClass \PhpOffice\PhpWord\Style\Chart - * - * @runTestsInSeparateProcesses */ class ChartTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/FontTest.php b/tests/PhpWordTests/Style/FontTest.php index 4ba6a762f3..4a271d258e 100644 --- a/tests/PhpWordTests/Style/FontTest.php +++ b/tests/PhpWordTests/Style/FontTest.php @@ -27,8 +27,6 @@ /** * Test class for PhpOffice\PhpWord\Style\Font. - * - * @runTestsInSeparateProcesses */ class FontTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/ImageTest.php b/tests/PhpWordTests/Style/ImageTest.php index 759a85441e..489abe4cf4 100644 --- a/tests/PhpWordTests/Style/ImageTest.php +++ b/tests/PhpWordTests/Style/ImageTest.php @@ -26,8 +26,6 @@ * Test class for PhpOffice\PhpWord\Style\Image. * * @coversDefaultClass \PhpOffice\PhpWord\Style\Image - * - * @runTestsInSeparateProcesses */ class ImageTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/LineTest.php b/tests/PhpWordTests/Style/LineTest.php index 26049d5cd8..1fa9a0e19d 100644 --- a/tests/PhpWordTests/Style/LineTest.php +++ b/tests/PhpWordTests/Style/LineTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Style\Image. * * @coversDefaultClass \PhpOffice\PhpWord\Style\Image - * - * @runTestsInSeparateProcesses */ class LineTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/ListItemTest.php b/tests/PhpWordTests/Style/ListItemTest.php index 59cdfeeda1..4cf03aea0f 100644 --- a/tests/PhpWordTests/Style/ListItemTest.php +++ b/tests/PhpWordTests/Style/ListItemTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Style\ListItem. * * @coversDefaultClass \PhpOffice\PhpWord\Style\ListItem - * - * @runTestsInSeparateProcesses */ class ListItemTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/NumberingLevelTest.php b/tests/PhpWordTests/Style/NumberingLevelTest.php index b34e445e94..7c905e00ed 100644 --- a/tests/PhpWordTests/Style/NumberingLevelTest.php +++ b/tests/PhpWordTests/Style/NumberingLevelTest.php @@ -23,8 +23,6 @@ /** * Test class for PhpOffice\PhpWord\Style\NumberingLevel. - * - * @runTestsInSeparateProcesses */ class NumberingLevelTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/PaperTest.php b/tests/PhpWordTests/Style/PaperTest.php index ea5a4fb674..9ba64862d7 100644 --- a/tests/PhpWordTests/Style/PaperTest.php +++ b/tests/PhpWordTests/Style/PaperTest.php @@ -23,8 +23,6 @@ /** * Test class for PhpOffice\PhpWord\Style\Paper. - * - * @runTestsInSeparateProcesses */ class PaperTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/ParagraphTest.php b/tests/PhpWordTests/Style/ParagraphTest.php index 67645fa4d8..c01569beac 100644 --- a/tests/PhpWordTests/Style/ParagraphTest.php +++ b/tests/PhpWordTests/Style/ParagraphTest.php @@ -27,8 +27,6 @@ /** * Test class for PhpOffice\PhpWord\Style\Paragraph. - * - * @runTestsInSeparateProcesses */ class ParagraphTest extends \PHPUnit\Framework\TestCase { @@ -37,6 +35,7 @@ class ParagraphTest extends \PHPUnit\Framework\TestCase */ protected function tearDown(): void { + Settings::restoreDefaults(); TestHelperDOCX::clear(); } @@ -379,7 +378,5 @@ public function testBidiVisualSettings(): void Settings::setDefaultRtl(false); $object = new Paragraph(); self::assertFalse($object->isBidi()); - - Settings::setDefaultRtl(null); } } diff --git a/tests/PhpWordTests/Style/RowTest.php b/tests/PhpWordTests/Style/RowTest.php index d3cc480ac7..1c6506eebd 100644 --- a/tests/PhpWordTests/Style/RowTest.php +++ b/tests/PhpWordTests/Style/RowTest.php @@ -24,8 +24,6 @@ * Test class for PhpOffice\PhpWord\Style\Row. * * @coversDefaultClass \PhpOffice\PhpWord\Style\Row - * - * @runTestsInSeparateProcesses */ class RowTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/SectionTest.php b/tests/PhpWordTests/Style/SectionTest.php index e03dbb7596..6d7692a750 100644 --- a/tests/PhpWordTests/Style/SectionTest.php +++ b/tests/PhpWordTests/Style/SectionTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Style\Section. * * @coversDefaultClass \PhpOffice\PhpWord\Element\Section - * - * @runTestsInSeparateProcesses */ class SectionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/TablePositionTest.php b/tests/PhpWordTests/Style/TablePositionTest.php index 695bb3d297..ab97ea91fb 100644 --- a/tests/PhpWordTests/Style/TablePositionTest.php +++ b/tests/PhpWordTests/Style/TablePositionTest.php @@ -22,8 +22,6 @@ /** * Test class for PhpOffice\PhpWord\Style\Table. - * - * @runTestsInSeparateProcesses */ class TablePositionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Style/TableTest.php b/tests/PhpWordTests/Style/TableTest.php index e53a51f5df..2049f59c72 100644 --- a/tests/PhpWordTests/Style/TableTest.php +++ b/tests/PhpWordTests/Style/TableTest.php @@ -27,11 +27,14 @@ /** * Test class for PhpOffice\PhpWord\Style\Table. - * - * @runTestsInSeparateProcesses */ class TableTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Settings::restoreDefaults(); + } + /** * Test class construction. * diff --git a/tests/PhpWordTests/Style/TextBoxTest.php b/tests/PhpWordTests/Style/TextBoxTest.php index 30c01bd368..f57861babd 100644 --- a/tests/PhpWordTests/Style/TextBoxTest.php +++ b/tests/PhpWordTests/Style/TextBoxTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Style\Image. * * @coversDefaultClass \PhpOffice\PhpWord\Style\Image - * - * @runTestsInSeparateProcesses */ class TextBoxTest extends TestCase { diff --git a/tests/PhpWordTests/StyleTest.php b/tests/PhpWordTests/StyleTest.php index 32d61e09f6..64a67168d4 100644 --- a/tests/PhpWordTests/StyleTest.php +++ b/tests/PhpWordTests/StyleTest.php @@ -28,6 +28,11 @@ */ class StyleTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Style::resetStyles(); + } + /** * Add and get paragraph, font, link, title, and table styles. * @@ -45,6 +50,8 @@ class StyleTest extends \PHPUnit\Framework\TestCase */ public function testStyles(): void { + Style::resetStyles(); + self::assertCount(0, Style::getStyles()); $paragraph = ['alignment' => Jc::CENTER]; $font = ['italic' => true, '_bold' => true]; $table = ['bgColor' => 'CCCCCC']; @@ -63,13 +70,13 @@ public function testStyles(): void ]; $styles = [ - 'Paragraph' => 'Paragraph', - 'Font' => 'Font', - 'Link' => 'Font', - 'Table' => 'Table', - 'Heading_1' => 'Font', - 'Normal' => 'Paragraph', - 'Numbering' => 'Numbering', + 'Paragraph' => Style\Paragraph::class, + 'Font' => Style\Font::class, + 'Link' => Style\Font::class, + 'Table' => Style\Table::class, + 'Heading_1' => Style\Font::class, + 'Normal' => Style\Paragraph::class, + 'Numbering' => Style\Numbering::class, ]; Style::addParagraphStyle('Paragraph', $paragraph); @@ -82,12 +89,9 @@ public function testStyles(): void self::assertCount(count($styles), Style::getStyles()); foreach ($styles as $name => $style) { - self::assertInstanceOf("PhpOffice\\PhpWord\\Style\\{$style}", Style::getStyle($name)); + self::assertInstanceOf($style, Style::getStyle($name)); } self::assertNull(Style::getStyle('Unknown')); - - Style::resetStyles(); - self::assertCount(0, Style::getStyles()); } /** diff --git a/tests/PhpWordTests/Writer/EPub3Test.php b/tests/PhpWordTests/Writer/EPub3Test.php index acdbb5e32f..7f3336c6c4 100644 --- a/tests/PhpWordTests/Writer/EPub3Test.php +++ b/tests/PhpWordTests/Writer/EPub3Test.php @@ -25,8 +25,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\Epub3. - * - * @runTestsInSeparateProcesses */ class EPub3Test extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/HTML/PartTest.php b/tests/PhpWordTests/Writer/HTML/PartTest.php index 0fe43c2350..d5b842af64 100644 --- a/tests/PhpWordTests/Writer/HTML/PartTest.php +++ b/tests/PhpWordTests/Writer/HTML/PartTest.php @@ -21,6 +21,7 @@ use DOMXPath; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Shared\Converter; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Writer\HTML\Part\Body; /** @@ -28,6 +29,11 @@ */ class PartTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Style::resetStyles(); + } + /** * Test get parent writer exception. */ @@ -44,7 +50,7 @@ public function testGetParentWriterException(): void public function testWriteSections(): void { $phpWord = new PhpWord(); - $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language('en-US')); + $phpWord->getSettings()->setThemeFontLang(new Style\Language('en-US')); $section1 = $phpWord->addSection(); $mtop = 0.5 * Converter::INCH_TO_TWIP; $mbot = 0.5 * Converter::INCH_TO_TWIP; @@ -96,7 +102,7 @@ public function testWriteSections(): void public function testThemeFontEastAsian(): void { $phpWord = new PhpWord(); - $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language('', 'hi-IN')); + $phpWord->getSettings()->setThemeFontLang(new Style\Language('', 'hi-IN')); $section1 = $phpWord->addSection(); $section1->addText('??? ????? ???'); @@ -112,7 +118,7 @@ public function testThemeFontEastAsian(): void public function testThemeBidirecional(): void { $phpWord = new PhpWord(); - $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language('', '', 'he-IL')); + $phpWord->getSettings()->setThemeFontLang(new Style\Language('', '', 'he-IL')); $section1 = $phpWord->addSection(); $section1->addText('????'); diff --git a/tests/PhpWordTests/Writer/HTMLTest.php b/tests/PhpWordTests/Writer/HTMLTest.php index cedd9f2f32..b7168ea9c1 100644 --- a/tests/PhpWordTests/Writer/HTMLTest.php +++ b/tests/PhpWordTests/Writer/HTMLTest.php @@ -26,8 +26,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\HTML. - * - * @runTestsInSeparateProcesses */ class HTMLTest extends AbstractWebServerEmbedded { @@ -95,6 +93,9 @@ public function testDefaultWhiteSpace(): void /** * Save. + * Method getRemoteGifImageUrl seems to require separate process. + * + * @runInSeparateProcess */ public function testSave(): void { diff --git a/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php b/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php index 4d59e1a90e..6024823332 100644 --- a/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php +++ b/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php @@ -25,6 +25,11 @@ class Paragraph2Test extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Style::resetStyles(); + } + /** * Test textAlign. */ diff --git a/tests/PhpWordTests/Writer/ODText/Style/ParagraphTest.php b/tests/PhpWordTests/Writer/ODText/Style/ParagraphTest.php index ecadd387d5..26cae1ea00 100644 --- a/tests/PhpWordTests/Writer/ODText/Style/ParagraphTest.php +++ b/tests/PhpWordTests/Writer/ODText/Style/ParagraphTest.php @@ -20,6 +20,7 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Shared\Converter; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWordTests\TestHelperDOCX; /** @@ -32,6 +33,7 @@ class ParagraphTest extends \PHPUnit\Framework\TestCase */ protected function tearDown(): void { + Style::resetStyles(); TestHelperDOCX::clear(); } diff --git a/tests/PhpWordTests/Writer/ODTextTest.php b/tests/PhpWordTests/Writer/ODTextTest.php index 9746791379..9450be4176 100644 --- a/tests/PhpWordTests/Writer/ODTextTest.php +++ b/tests/PhpWordTests/Writer/ODTextTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\ODText. - * - * @runTestsInSeparateProcesses */ class ODTextTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php index 2a623ec6ce..424aed9410 100644 --- a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php @@ -21,6 +21,7 @@ use Exception; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Writer\PDF; /** @@ -31,6 +32,7 @@ class TCPDFTest extends \PHPUnit\Framework\TestCase protected function tearDown(): void { Settings::restoreDefaults(); + Style::resetStyles(); } /** diff --git a/tests/PhpWordTests/Writer/RTFTest.php b/tests/PhpWordTests/Writer/RTFTest.php index 4f9f8944ac..06372030be 100644 --- a/tests/PhpWordTests/Writer/RTFTest.php +++ b/tests/PhpWordTests/Writer/RTFTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\RTF. - * - * @runTestsInSeparateProcesses */ class RTFTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php b/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php index 6ffda0c019..e429f7e8b5 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php @@ -21,9 +21,6 @@ use Exception; use PhpOffice\PhpWord\Writer\Word2007; -/** - * @runTestsInSeparateProcesses - */ class AbstractPartTest extends \PHPUnit\Framework\TestCase { /** diff --git a/tests/PhpWordTests/Writer/Word2007/Part/CommentsTest.php b/tests/PhpWordTests/Writer/Word2007/Part/CommentsTest.php index 8f8c8240a9..c7c9a70e66 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/CommentsTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/CommentsTest.php @@ -24,8 +24,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\Word2007\Part\Comment. - * - * @runTestsInSeparateProcesses */ class CommentsTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php b/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php index 63c52749e5..d13a975d53 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php @@ -28,8 +28,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\Word2007\Part\Document. - * - * @runTestsInSeparateProcesses */ class DocumentTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php b/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php index beb6e971e2..aef48c6cc4 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php @@ -27,8 +27,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Part\Footer. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Part\Footer - * - * @runTestsInSeparateProcesses */ class FooterTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php b/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php index d8bf678541..d795491cdc 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php @@ -24,8 +24,6 @@ /** * @coversNothing - * - * @runTestsInSeparateProcesses */ class FootnotesTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php b/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php index 164365f113..b506949a51 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php @@ -25,8 +25,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\Word2007\Part\Header. - * - * @runTestsInSeparateProcesses */ class HeaderTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Part/NumberingTest.php b/tests/PhpWordTests/Writer/Word2007/Part/NumberingTest.php index 1cf596fc85..5c75d951fa 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/NumberingTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/NumberingTest.php @@ -28,8 +28,6 @@ * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Part\Numbering * - * @runTestsInSeparateProcesses - * * @since 0.10.0 */ class NumberingTest extends \PHPUnit\Framework\TestCase diff --git a/tests/PhpWordTests/Writer/Word2007/Part/StylesTest.php b/tests/PhpWordTests/Writer/Word2007/Part/StylesTest.php index 88274b3622..563b147884 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/StylesTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/StylesTest.php @@ -21,6 +21,7 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Jc; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; use PhpOffice\PhpWordTests\TestHelperDOCX; @@ -38,6 +39,7 @@ class StylesTest extends \PHPUnit\Framework\TestCase protected function tearDown(): void { Settings::restoreDefaults(); + Style::resetStyles(); TestHelperDOCX::clear(); } diff --git a/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php b/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php index c0e2653a7c..5cd3eef011 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php @@ -26,8 +26,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Style\Font. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Style\Font - * - * @runTestsInSeparateProcesses */ class FontTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Style/ImageTest.php b/tests/PhpWordTests/Writer/Word2007/Style/ImageTest.php index 2b5c25e01f..a14549410c 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/ImageTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/ImageTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Style\Font. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Style\Frame - * - * @runTestsInSeparateProcesses */ class ImageTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Style/ParagraphTest.php b/tests/PhpWordTests/Writer/Word2007/Style/ParagraphTest.php index c1f985ccd2..238d2a2d28 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/ParagraphTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/ParagraphTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Style\Paragraph. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Style\Paragraph - * - * @runTestsInSeparateProcesses */ class ParagraphTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Style/SectionTest.php b/tests/PhpWordTests/Writer/Word2007/Style/SectionTest.php index f527ded190..80f0c1f989 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/SectionTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/SectionTest.php @@ -25,8 +25,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Style\Section. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Style\Section - * - * @runTestsInSeparateProcesses */ class SectionTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Style/TableCellTest.php b/tests/PhpWordTests/Writer/Word2007/Style/TableCellTest.php index bd3f587b75..25b5ec9355 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/TableCellTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/TableCellTest.php @@ -26,8 +26,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Style\Table. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Style\Table - * - * @runTestsInSeparateProcesses */ class TableCellTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007/Style/TableTest.php b/tests/PhpWordTests/Writer/Word2007/Style/TableTest.php index b10d836568..58b5186342 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/TableTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/TableTest.php @@ -28,8 +28,6 @@ * Test class for PhpOffice\PhpWord\Writer\Word2007\Style\Table. * * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Style\Table - * - * @runTestsInSeparateProcesses */ class TableTest extends \PHPUnit\Framework\TestCase { diff --git a/tests/PhpWordTests/Writer/Word2007Test.php b/tests/PhpWordTests/Writer/Word2007Test.php index 64f6a29b52..6cac9727ed 100644 --- a/tests/PhpWordTests/Writer/Word2007Test.php +++ b/tests/PhpWordTests/Writer/Word2007Test.php @@ -28,8 +28,6 @@ /** * Test class for PhpOffice\PhpWord\Writer\Word2007. - * - * @runTestsInSeparateProcesses */ class Word2007Test extends AbstractWebServerEmbedded { @@ -76,6 +74,9 @@ public function testConstruct(): void /** * Save. + * Method getRemoteGifImageUrl seems to require separate process. + * + * @runInSeparateProcess */ public function testSave(): void { From f4aea9649cad98ebd9486f8e46b868f4b7e29ef5 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 3 Oct 2025 20:27:04 -0700 Subject: [PATCH 117/153] Different Way to Deal With Coveralls Failure --- .github/workflows/php.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 796814aa17..48cf29ac69 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -143,10 +143,9 @@ jobs: - name: Upload coverage results to Coveralls if: matrix.php == '8.3' - env: - FAILURE_ACTION: "${{ matrix.php == '8.3' }}" uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} file: build/clover.xml format: clover + fail-on-error: false From 2e61f1022ff933e381ba1755f004ec89f70baf88 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 4 Oct 2025 01:14:41 -0700 Subject: [PATCH 118/153] Confirm Warnings etc. Become Exceptions --- src/PhpWord/Helper/Handler.php | 47 +++++++++++++ tests/PhpWordTests/Helper/HandlerTest.php | 80 +++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 src/PhpWord/Helper/Handler.php create mode 100644 tests/PhpWordTests/Helper/HandlerTest.php diff --git a/src/PhpWord/Helper/Handler.php b/src/PhpWord/Helper/Handler.php new file mode 100644 index 0000000000..e95d871e00 --- /dev/null +++ b/src/PhpWord/Helper/Handler.php @@ -0,0 +1,47 @@ +getMessage()); + } + } + + public function testNotice(): void + { + try { + Handler::notice('invalidtz'); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('Timezone', $e->getMessage()); + } + } + + public function testWarning(): void + { + try { + Handler::warning(); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('ailed to open stream', $e->getMessage()); + } + } + + public function testUserDeprecated(): void + { + try { + Handler::userDeprecated(); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('hello', $e->getMessage()); + } + } + + public function testUserNotice(): void + { + try { + Handler::userNotice(); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('userNotice', $e->getMessage()); + } + } + + public function testUserWarning(): void + { + try { + Handler::userWarning(); + self::fail('Expected error/exception did not happen'); + } catch (Throwable $e) { + self::assertStringContainsString('userWarning', $e->getMessage()); + } + } +} From 3af64e43505a11eaa28397bcb94a9bd8d93c2444 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 4 Oct 2025 02:20:34 -0700 Subject: [PATCH 119/153] More Coverage --- phpstan-baseline.neon | 10 ---------- phpstan-baseline.php7.neon | 10 ---------- phpstan-baseline.php73.neon | 10 ---------- src/PhpWord/Shared/ZipArchive.php | 5 +++-- tests/PhpWordTests/TemplateProcessorTest.php | 3 ++- 5 files changed, 5 insertions(+), 33 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index bd9206dd06..ad7b752455 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -390,11 +390,6 @@ parameters: count: 2 path: src/PhpWord/Shared/ZipArchive.php - - - message: "#^Call to method addFromString\\(\\) on an unknown class PclZip\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - message: "#^Call to method close\\(\\) on an unknown class PclZip\\.$#" count: 1 @@ -1340,11 +1335,6 @@ parameters: count: 2 path: tests/PhpWordTests/TemplateProcessorTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:AddFromString\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/TemplateProcessorTest.php - - message: "#^Cannot access offset 'end' on array\\\\|bool\\.$#" count: 2 diff --git a/phpstan-baseline.php7.neon b/phpstan-baseline.php7.neon index 93a737665a..58783bba94 100644 --- a/phpstan-baseline.php7.neon +++ b/phpstan-baseline.php7.neon @@ -375,11 +375,6 @@ parameters: count: 2 path: src/PhpWord/Shared/ZipArchive.php - - - message: "#^Call to method addFromString\\(\\) on an unknown class PclZip\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - message: "#^Call to method close\\(\\) on an unknown class PclZip\\.$#" count: 1 @@ -1310,11 +1305,6 @@ parameters: count: 2 path: tests/PhpWordTests/TemplateProcessorTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:AddFromString\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/TemplateProcessorTest.php - - message: "#^Cannot access offset 'end' on array\\\\|bool\\.$#" count: 2 diff --git a/phpstan-baseline.php73.neon b/phpstan-baseline.php73.neon index 5ebf3d7727..fccd6c38df 100644 --- a/phpstan-baseline.php73.neon +++ b/phpstan-baseline.php73.neon @@ -375,11 +375,6 @@ parameters: count: 2 path: src/PhpWord/Shared/ZipArchive.php - - - message: "#^Call to method addFromString\\(\\) on an unknown class PclZip\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - message: "#^Call to method close\\(\\) on an unknown class PclZip\\.$#" count: 1 @@ -1310,11 +1305,6 @@ parameters: count: 2 path: tests/PhpWordTests/TemplateProcessorTest.php - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:AddFromString\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/TemplateProcessorTest.php - - message: "#^Cannot access offset 'end' on array\\\\|bool\\.$#" count: 2 diff --git a/src/PhpWord/Shared/ZipArchive.php b/src/PhpWord/Shared/ZipArchive.php index 462206c7f5..def835786c 100644 --- a/src/PhpWord/Shared/ZipArchive.php +++ b/src/PhpWord/Shared/ZipArchive.php @@ -236,7 +236,6 @@ public function getFromName($filename) */ public function pclzipAddFile($filename, $localname = null) { - /** @var PclZip $zip Type hint */ $zip = $this->zip; // Bugfix GH-261 https://github.com/PHPOffice/PHPWord/pull/261 @@ -267,8 +266,10 @@ public function pclzipAddFile($filename, $localname = null) if (!$this->usePclzip) { $pathAdded = $pathAdded . '/' . ltrim(str_replace('\\', '/', substr($filename, strlen($pathRemoved))), '/'); //$res = $zip->addFile($filename, $pathAdded); - $res = $zip->addFromString($pathAdded, file_get_contents($filename)); // addFile can't use subfolders in some cases + /** @var \ZipArchive $zip */ + $res = $zip->addFromString($pathAdded, (string) file_get_contents($filename)); // addFile can't use subfolders in some cases } else { + /** @var PclZip $zip */ $res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded); } diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index 8ae4dfa59a..ee582c0b6f 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -96,7 +96,7 @@ public function xtestTemplateCanBeSavedInTemporaryLocation(string $templateFqfn, } $embeddingText = 'The quick Brown Fox jumped over the lazy^H^H^H^Htired unitTester'; - $templateProcessor->zip()->AddFromString('word/embeddings/fox.bin', $embeddingText); + $templateProcessor->zip()->addFromString('word/embeddings/fox.bin', $embeddingText); $documentFqfn = $templateProcessor->save(); self::assertNotEmpty($documentFqfn, 'FQFN of the saved document is empty.'); @@ -855,6 +855,7 @@ public function testSetCheckboxWithCustomMacro(): void /** * @covers ::setImageValue + * @covers \PhpOffice\PhpWord\Shared\ZipArchive::pclzipAddFile */ public function testSetImageValue(): void { From ca6d2f0dd77e86b6e61f9fcbbc14478b88bb9a66 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 9 Oct 2025 08:37:06 -0700 Subject: [PATCH 120/153] Replace obsolete php-cs-fixer option --- .php-cs-fixer.dist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index cd46cac83e..66c1d345a9 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -80,6 +80,7 @@ 'method_argument_space' => true, 'method_chaining_indentation' => true, 'modernize_types_casting' => true, + 'modifier_keywords' => ['elements' => ['property', 'method']], // not const 'multiline_comment_opening_closing' => true, 'multiline_whitespace_before_semicolons' => true, 'native_constant_invocation' => false, // Micro optimization that look messy @@ -218,7 +219,6 @@ 'trailing_comma_in_multiline' => true, 'trim_array_spaces' => true, 'unary_operator_spaces' => true, - 'visibility_required' => ['elements' => ['property', 'method']], // not const 'void_return' => true, 'whitespace_after_comma_in_array' => true, 'yoda_style' => false, From 6bdb27a9be27f08d31998c6ba11ca9325d0a1d61 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 15 Nov 2025 19:37:50 -0800 Subject: [PATCH 121/153] No Need for getMockForAbstractClass Fix #2851. No source changes, only tests. Since unit tests run with most recent components, and since Phpstan has been very capricious lately (178 new errors!), some other tests accompany this PR. --- .php-cs-fixer.dist.php | 2 +- phpstan-baseline.neon | 65 ------------------- phpstan-baseline.php7.neon | 22 ------- phpstan-baseline.php73.neon | 10 --- .../Element/AbstractElementClass.php | 25 +++++++ .../Element/AbstractElementTest.php | 23 +------ .../PhpWordTests/Style/AbstractStyleClass.php | 28 ++++++++ .../PhpWordTests/Style/AbstractStyleTest.php | 51 ++------------- tests/PhpWordTests/Style/FontTest.php | 8 ++- tests/PhpWordTests/Style/TableTest.php | 36 ++++++---- tests/PhpWordTests/Style/TextBoxTest.php | 36 ++++++---- .../Writer/EPub3/Part/AbstractPartClass.php | 13 ++++ .../Writer/EPub3/Part/AbstractPartTest.php | 35 +--------- .../Writer/EPub3/Style/AbstractStyleClass.php | 13 ++++ .../Writer/EPub3/Style/AbstractStyleTest.php | 24 +------ .../Writer/HTML/Element/RubyTest.php | 11 +++- .../Writer/HTML/Element/TableTest.php | 10 ++- .../PhpWordTests/Writer/HTML/ElementTest.php | 62 ++++++++++++------ tests/PhpWordTests/Writer/HTML/Helper.php | 4 ++ .../Writer/ODText/Part/AbstractPartClass.php | 29 +++++++++ .../Writer/ODText/Part/AbstractPartTest.php | 35 +--------- .../Word2007/Part/AbstractPartClass.php | 29 +++++++++ .../Writer/Word2007/Part/AbstractPartTest.php | 35 +--------- tests/PhpWordTests/XmlDocument.php | 9 ++- 24 files changed, 277 insertions(+), 338 deletions(-) create mode 100644 tests/PhpWordTests/Element/AbstractElementClass.php create mode 100644 tests/PhpWordTests/Style/AbstractStyleClass.php create mode 100644 tests/PhpWordTests/Writer/EPub3/Part/AbstractPartClass.php create mode 100644 tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleClass.php create mode 100644 tests/PhpWordTests/Writer/ODText/Part/AbstractPartClass.php create mode 100644 tests/PhpWordTests/Writer/Word2007/Part/AbstractPartClass.php diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 66c1d345a9..cd46cac83e 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -80,7 +80,6 @@ 'method_argument_space' => true, 'method_chaining_indentation' => true, 'modernize_types_casting' => true, - 'modifier_keywords' => ['elements' => ['property', 'method']], // not const 'multiline_comment_opening_closing' => true, 'multiline_whitespace_before_semicolons' => true, 'native_constant_invocation' => false, // Micro optimization that look messy @@ -219,6 +218,7 @@ 'trailing_comma_in_multiline' => true, 'trim_array_spaces' => true, 'unary_operator_spaces' => true, + 'visibility_required' => ['elements' => ['property', 'method']], // not const 'void_return' => true, 'whitespace_after_comma_in_array' => true, 'yoda_style' => false, diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ad7b752455..fb5c58147f 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1290,11 +1290,6 @@ parameters: count: 1 path: tests/PhpWordTests/Shared/XMLWriterTest.php - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Style\\\\AbstractStyleTest\\:\\:callProtectedMethod\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Style/AbstractStyleTest.php - - message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|object, class\\-string\\|false given\\.$#" count: 1 @@ -1375,21 +1370,6 @@ parameters: count: 1 path: tests/PhpWordTests/TestableTemplateProcesor.php - - - message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" - count: 9 - path: tests/PhpWordTests/Writer/HTML/ElementTest.php - - - - message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" - count: 2 - path: tests/PhpWordTests/Writer/HTML/Element/RubyTest.php - - - - message: "#^Cannot call method item\\(\\) on DOMNodeList\\\\|false\\.$#" - count: 11 - path: tests/PhpWordTests/Writer/HTML/ElementTest.php - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" count: 3 @@ -1425,26 +1405,11 @@ parameters: count: 1 path: tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php - - - message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/Word2007/ElementTest.php - - - - message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/Word2007/StyleTest.php - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getElement\\(\\) should return DOMElement\\|null but returns DOMNode\\|null\\.$#" count: 1 path: tests/PhpWordTests/XmlDocument.php - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getNodeList\\(\\) should return DOMNodeList\\ but returns DOMNodeList\\\\|false\\.$#" - count: 1 - path: tests/PhpWordTests/XmlDocument.php - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$path \\(string\\) does not accept string\\|false\\.$#" count: 1 @@ -1460,33 +1425,3 @@ parameters: message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\HTML\\\\Part\\\\AbstractPart is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:|]+$#" count: 1 path: src/PhpWord/Writer/HTML.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 2 - path: tests/PhpWordTests/Element/AbstractElementTest.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 4 - path: tests/PhpWordTests/Style/AbstractStyleTest.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\EPub3\\\\Style\\\\AbstractStyle is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 1 - path: tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\AbstractPart is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 2 - path: tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\AbstractPart is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 2 - path: tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php diff --git a/phpstan-baseline.php7.neon b/phpstan-baseline.php7.neon index 58783bba94..3d11dbeb00 100644 --- a/phpstan-baseline.php7.neon +++ b/phpstan-baseline.php7.neon @@ -1265,11 +1265,6 @@ parameters: count: 1 path: tests/PhpWordTests/Shared/XMLWriterTest.php - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Style\\\\AbstractStyleTest\\:\\:callProtectedMethod\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Style/AbstractStyleTest.php - - message: "#^Cannot call method setLineHeight\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" count: 1 @@ -1349,11 +1344,6 @@ parameters: message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" count: 9 path: tests/PhpWordTests/Writer/HTML/ElementTest.php - - - - message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" - count: 2 - path: tests/PhpWordTests/Writer/HTML/Element/RubyTest.php - message: "#^Cannot call method item\\(\\) on DOMNodeList\\\\|false\\.$#" @@ -1424,15 +1414,3 @@ parameters: message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$xpath \\(DOMXPath\\) does not accept null\\.$#" count: 1 path: tests/PhpWordTests/XmlDocument.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\EPub3\\\\Style\\\\AbstractStyle is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 1 - path: tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleTest.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\AbstractPart is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:]+$#" - count: 2 - path: tests/PhpWordTests/Writer/ODText/Part/AbstractPartTest.php diff --git a/phpstan-baseline.php73.neon b/phpstan-baseline.php73.neon index fccd6c38df..3d11dbeb00 100644 --- a/phpstan-baseline.php73.neon +++ b/phpstan-baseline.php73.neon @@ -1265,11 +1265,6 @@ parameters: count: 1 path: tests/PhpWordTests/Shared/XMLWriterTest.php - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Style\\\\AbstractStyleTest\\:\\:callProtectedMethod\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Style/AbstractStyleTest.php - - message: "#^Cannot call method setLineHeight\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" count: 1 @@ -1349,11 +1344,6 @@ parameters: message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" count: 9 path: tests/PhpWordTests/Writer/HTML/ElementTest.php - - - - message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" - count: 2 - path: tests/PhpWordTests/Writer/HTML/Element/RubyTest.php - message: "#^Cannot call method item\\(\\) on DOMNodeList\\\\|false\\.$#" diff --git a/tests/PhpWordTests/Element/AbstractElementClass.php b/tests/PhpWordTests/Element/AbstractElementClass.php new file mode 100644 index 0000000000..407e8c934c --- /dev/null +++ b/tests/PhpWordTests/Element/AbstractElementClass.php @@ -0,0 +1,25 @@ +$method(AbstractElement::class); - } else { - /** @var AbstractElement $stub */ - $stub = new class() extends AbstractElement { - }; - } + $stub = new AbstractElementClass(); $ival = mt_rand(0, 100); $stub->setElementIndex($ival); self::assertEquals($ival, $stub->getElementIndex()); @@ -51,14 +39,7 @@ public function testElementIndex(): void */ public function testElementId(): void { - $method = self::$method; - if (method_exists($this, $method)) { - $stub = $this->$method(AbstractElement::class); - } else { - /** @var AbstractElement $stub */ - $stub = new class() extends AbstractElement { - }; - } + $stub = new AbstractElementClass(); $stub->setElementId(); self::assertEquals(6, strlen($stub->getElementId())); } diff --git a/tests/PhpWordTests/Style/AbstractStyleClass.php b/tests/PhpWordTests/Style/AbstractStyleClass.php new file mode 100644 index 0000000000..91c576c338 --- /dev/null +++ b/tests/PhpWordTests/Style/AbstractStyleClass.php @@ -0,0 +1,28 @@ +$method(AbstractStyle::class); - } else { - /** @var AbstractStyle $stub */ - $stub = new class() extends AbstractStyle { - }; - } + $stub = new AbstractStyleClass(); $stub->setStyleByArray(['index' => 1]); self::assertEquals(1, $stub->getIndex()); @@ -77,15 +60,7 @@ public function testSetStyleByArrayWithAlignment(): void */ public function testSetValNormal(): void { - $method = self::$mockAbstract; - if (self::methodFound($method)) { - $stub = $this->$method(AbstractStyle::class); - } else { - /** @var AbstractStyle $stub */ - $stub = new class() extends AbstractStyle { - }; - } - + $stub = new AbstractStyleClass(); self::assertTrue(self::callProtectedMethod($stub, 'setBoolVal', [true, false])); self::assertEquals(12, self::callProtectedMethod($stub, 'setIntVal', [12, 200])); self::assertEquals(871.1, self::callProtectedMethod($stub, 'setFloatVal', [871.1, 2.1])); @@ -98,15 +73,7 @@ public function testSetValNormal(): void */ public function testSetValDefault(): void { - $method = self::$mockAbstract; - if (self::methodFound($method)) { - $stub = $this->$method(AbstractStyle::class); - } else { - /** @var AbstractStyle $stub */ - $stub = new class() extends AbstractStyle { - }; - } - + $stub = new AbstractStyleClass(); self::assertNotTrue(self::callProtectedMethod($stub, 'setBoolVal', ['a', false])); self::assertEquals(200, self::callProtectedMethod($stub, 'setIntVal', ['foo', 200])); self::assertEquals(2.1, self::callProtectedMethod($stub, 'setFloatVal', ['foo', 2.1])); @@ -119,15 +86,7 @@ public function testSetValDefault(): void public function testSetValEnumException(): void { $this->expectException(InvalidArgumentException::class); - $method = self::$mockAbstract; - if (self::methodFound($method)) { - $stub = $this->$method(AbstractStyle::class); - } else { - /** @var AbstractStyle $stub */ - $stub = new class() extends AbstractStyle { - }; - } - + $stub = new AbstractStyleClass(); self::assertEquals('b', self::callProtectedMethod($stub, 'setEnumVal', ['z', ['a', 'b'], 'b'])); } @@ -139,6 +98,8 @@ public function testSetValEnumException(): void * * @param mixed $object * @param string $method + * + * @return mixed */ public static function callProtectedMethod($object, $method, array $args = []) { diff --git a/tests/PhpWordTests/Style/FontTest.php b/tests/PhpWordTests/Style/FontTest.php index 4a271d258e..02d63a1ce9 100644 --- a/tests/PhpWordTests/Style/FontTest.php +++ b/tests/PhpWordTests/Style/FontTest.php @@ -83,7 +83,9 @@ public function testSetStyleValueWithNullOrEmpty(): void 'fallbackFont' => '', ]; foreach ($attributes as $key => $default) { - $get = is_bool($default) ? "is{$key}" : "get{$key}"; + $uckey = ucfirst($key); + $get = is_bool($default) ? "is{$uckey}" : "get{$uckey}"; + self::assertTrue(method_exists($object, $get)); self::assertEquals($default, $object->$get()); $object->setStyleValue($key, null); self::assertEquals($default, $object->$get()); @@ -128,7 +130,9 @@ public function testSetStyleValueNormal(): void ]; $object->setStyleByArray($attributes); foreach ($attributes as $key => $value) { - $get = is_bool($value) ? "is{$key}" : "get{$key}"; + $uckey = ucfirst($key); + $get = is_bool($value) ? "is{$uckey}" : "get{$uckey}"; + self::assertTrue(method_exists($object, $get)); self::assertEquals($value, $object->$get()); } } diff --git a/tests/PhpWordTests/Style/TableTest.php b/tests/PhpWordTests/Style/TableTest.php index 2049f59c72..4b7906101b 100644 --- a/tests/PhpWordTests/Style/TableTest.php +++ b/tests/PhpWordTests/Style/TableTest.php @@ -75,32 +75,42 @@ public function testSetGetNormal(): void { $object = new Table(); - $attributes = [ + $stringAttributes = [ 'bgColor' => 'FF0000', - 'borderTopSize' => 4, 'borderTopColor' => 'FF0000', - 'borderLeftSize' => 4, 'borderLeftColor' => 'FF0000', - 'borderRightSize' => 4, 'borderRightColor' => 'FF0000', - 'borderBottomSize' => 4, 'borderBottomColor' => 'FF0000', - 'borderInsideHSize' => 4, 'borderInsideHColor' => 'FF0000', - 'borderInsideVSize' => 4, 'borderInsideVColor' => 'FF0000', + 'alignment' => JcTable::CENTER, + 'unit' => 'pct', + 'layout' => Table::LAYOUT_FIXED, + ]; + $intAttributes = [ + 'borderTopSize' => 4, + 'borderLeftSize' => 4, + 'borderRightSize' => 4, + 'borderBottomSize' => 4, + 'borderInsideHSize' => 4, + 'borderInsideVSize' => 4, 'cellMarginTop' => 240, 'cellMarginLeft' => 240, 'cellMarginRight' => 240, 'cellMarginBottom' => 240, - 'alignment' => JcTable::CENTER, 'width' => 100, - 'unit' => 'pct', - 'layout' => Table::LAYOUT_FIXED, ]; - foreach ($attributes as $key => $value) { - $set = "set{$key}"; - $get = "get{$key}"; + foreach ($stringAttributes as $key => $value) { + $uckey = ucfirst($key); + $set = "set{$uckey}"; + $get = "get{$uckey}"; + $object->$set($value); + self::assertEquals($value, $object->$get()); + } + foreach ($intAttributes as $key => $value) { + $uckey = ucfirst($key); + $set = "set{$uckey}"; + $get = "get{$uckey}"; $object->$set($value); self::assertEquals($value, $object->$get()); } diff --git a/tests/PhpWordTests/Style/TextBoxTest.php b/tests/PhpWordTests/Style/TextBoxTest.php index f57861babd..cc665524f2 100644 --- a/tests/PhpWordTests/Style/TextBoxTest.php +++ b/tests/PhpWordTests/Style/TextBoxTest.php @@ -35,29 +35,39 @@ public function testSetGetNormal(): void { $object = new TextBox(); - $properties = [ - 'width' => 200, - 'height' => 200, + $stringProperties = [ 'alignment' => Jc::START, - 'marginTop' => 240, - 'marginLeft' => 240, 'wrappingStyle' => 'inline', 'positioning' => 'absolute', 'posHorizontal' => 'center', 'posVertical' => 'top', 'posHorizontalRel' => 'margin', 'posVerticalRel' => 'page', - 'innerMarginTop' => '5', - 'innerMarginRight' => '5', - 'innerMarginBottom' => '5', - 'innerMarginLeft' => '5', - 'borderSize' => '2', 'borderColor' => 'red', 'bgColor' => 'blue', ]; - foreach ($properties as $key => $value) { - $set = "set{$key}"; - $get = "get{$key}"; + foreach ($stringProperties as $key => $value) { + $uckey = ucfirst($key); + $set = "set{$uckey}"; + $get = "get{$uckey}"; + $object->$set($value); + self::assertEquals($value, $object->$get()); + } + $intProperties = [ + 'width' => 200, + 'height' => 200, + 'marginTop' => 240, + 'marginLeft' => 240, + 'innerMarginTop' => 5, + 'innerMarginRight' => 5, + 'innerMarginBottom' => 5, + 'innerMarginLeft' => 5, + 'borderSize' => 2, + ]; + foreach ($intProperties as $key => $value) { + $uckey = ucfirst($key); + $set = "set{$uckey}"; + $get = "get{$uckey}"; $object->$set($value); self::assertEquals($value, $object->$get()); } diff --git a/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartClass.php b/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartClass.php new file mode 100644 index 0000000000..056ec6e1be --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Part/AbstractPartClass.php @@ -0,0 +1,13 @@ +methodFound($mockAbstract)) { - $this->part = $this->$mockAbstract(AbstractPart::class); - } else { - $this->part = new class() extends AbstractPart { - public function write(): string - { - return ''; - } - }; - } - } - public function testParentWriter(): void { + $part = new AbstractPartClass(); $writer = new EPub3(); - $this->part->setParentWriter($writer); + $part->setParentWriter($writer); - self::assertInstanceOf(EPub3::class, $this->part->getParentWriter()); + self::assertInstanceOf(EPub3::class, $part->getParentWriter()); } } diff --git a/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleClass.php b/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleClass.php new file mode 100644 index 0000000000..2657e12693 --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Style/AbstractStyleClass.php @@ -0,0 +1,13 @@ +methodFound($mockAbstract)) { - $style = $this->$mockAbstract(AbstractStyle::class); - } else { - /** @var AbstractStyle $style */ - $style = new class() extends AbstractStyle { - public function write(): string - { - return ''; - } - }; - } + + $style = new AbstractStyleClass(); $result = $style->setParentWriter($parentWriter); diff --git a/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php b/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php index 1cf7bdb07c..64509b7c9c 100644 --- a/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php +++ b/tests/PhpWordTests/Writer/HTML/Element/RubyTest.php @@ -49,7 +49,7 @@ public function testWriteRubyHtml(): void $dom = Helper::getAsHTML($phpWord, '', '', ['ruby', 'rt', 'rp']); $xpath = new DOMXPath($dom); - self::assertSame(1, $xpath->query('/html/body/div/ruby')->length); + $this->checkLength(1, $xpath, '/html/body/div/ruby'); // ensure text is right $rubyElement = $dom->getElementsByTagName('ruby')->item(0); $rtElement = $dom->getElementsByTagName('rt')->item(0); @@ -94,7 +94,7 @@ public function testWriteRubyHtmlParagraphStyle(): void $dom = Helper::getAsHTML($phpWord, '', '', ['ruby', 'rt', 'rp']); $xpath = new DOMXPath($dom); - self::assertSame(2, $xpath->query('/html/body/div/ruby')->length); + $this->checkLength(2, $xpath, '/html/body/div/ruby'); // ensure text is right $rubyElement = $dom->getElementsByTagName('ruby')->item(0); $rtElement = $dom->getElementsByTagName('rt')->item(0); @@ -118,4 +118,11 @@ public function testWriteRubyHtmlParagraphStyle(): void self::assertSame('lineHeight10', $rubyElement2->attributes->getNamedItem('class')->textContent); self::assertSame('line-height: 4;font-size:10pt;', $rtElement->attributes->getNamedItem('style')->textContent); } + + private function checkLength(int $expected, DOMXPath $xpath, string $location): void + { + $temp = $xpath->query($location); + self::assertNotFalse($temp); + self::assertSame($expected, $temp->length); + } } diff --git a/tests/PhpWordTests/Writer/HTML/Element/TableTest.php b/tests/PhpWordTests/Writer/HTML/Element/TableTest.php index 30410925e8..df625d7fcc 100644 --- a/tests/PhpWordTests/Writer/HTML/Element/TableTest.php +++ b/tests/PhpWordTests/Writer/HTML/Element/TableTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWordTests\Writer\HTML\Element; +use DOMElement; use DOMXPath; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\SimpleType\VerticalJc; @@ -198,8 +199,9 @@ public function testWriteTableCellVAlign(): void self::assertNotFalse($cell3Query); self::assertCount(1, $cell3Query); - $cell3Style = $cell3Query->item(0)->attributes->getNamedItem('style'); - self::assertNull($cell3Style); + $temp = $cell3Query->item(0); + self::assertInstanceOf(DOMElement::class, $temp); + self::assertNull($temp->attributes->getNamedItem('style')); } public function testWriteTableCellVMerge(): void @@ -230,6 +232,8 @@ public function testWriteTableCellVMerge(): void $cell3Query = $xpath->query('//table/tr[3]/td[1]'); self::assertNotFalse($cell3Query); self::assertCount(1, $cell3Query); - self::assertNull($cell3Query->item(0)->attributes->getNamedItem('rowspan')); + $temp = $cell3Query->item(0); + self::assertInstanceOf(DOMElement::class, $temp); + self::assertNull($temp->attributes->getNamedItem('rowspan')); } } diff --git a/tests/PhpWordTests/Writer/HTML/ElementTest.php b/tests/PhpWordTests/Writer/HTML/ElementTest.php index 28e785a237..0a71df4f56 100644 --- a/tests/PhpWordTests/Writer/HTML/ElementTest.php +++ b/tests/PhpWordTests/Writer/HTML/ElementTest.php @@ -20,6 +20,7 @@ use DateTime; use DOMDocument; +use DOMElement; use DOMXPath; use PhpOffice\PhpWord\Element\Text as TextElement; use PhpOffice\PhpWord\Element\TextRun; @@ -80,8 +81,8 @@ public function testWriteTrackChanges(): void $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals(1, $xpath->query('/html/body/div/p[1]/ins')->length); - self::assertEquals(1, $xpath->query('/html/body/div/p[2]/del')->length); + $this->checkLength(1, $xpath, '/html/body/div/p[1]/ins'); + $this->checkLength(1, $xpath, '/html/body/div/p[2]/del'); $node = $xpath->query('/html/body/div/p[2]/del'); self::assertNotFalse($node); $allAttributes = $node[0]->attributes; @@ -129,14 +130,14 @@ public function testWriteColSpan(): void $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals(1, $xpath->query('/html/body/div/table/tr[1]/td')->length); - self::assertEquals('2', $xpath->query('/html/body/div/table/tr/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); - self::assertEquals(2, $xpath->query('/html/body/div/table/tr[2]/td')->length); + $this->checkLength(1, $xpath, '/html/body/div/table/tr[1]/td'); + $this->checkContent('2', $xpath, '/html/body/div/table/tr/td[1]', 0, 'colspan'); + $this->checkLength(2, $xpath, '/html/body/div/table/tr[2]/td'); - self::assertEquals('#6086B8', $xpath->query('/html/body/div/table/tr[1]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent); - self::assertEquals('#ffffff', $xpath->query('/html/body/div/table/tr[1]/td')->item(0)->attributes->getNamedItem('color')->textContent); - self::assertEquals('#ffffff', $xpath->query('/html/body/div/table/tr[2]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent); - self::assertNull($xpath->query('/html/body/div/table/tr[2]/td')->item(0)->attributes->getNamedItem('color')); + $this->checkContent('#6086B8', $xpath, '/html/body/div/table/tr[1]/td', 0, 'bgcolor'); + $this->checkContent('#ffffff', $xpath, '/html/body/div/table/tr[1]/td', 0, 'color'); + $this->checkContent('#ffffff', $xpath, '/html/body/div/table/tr[2]/td', 0, 'bgcolor'); + $this->checkContent(null, $xpath, '/html/body/div/table/tr[2]/td', 0, 'color'); } /** @@ -163,9 +164,9 @@ public function testWriteRowSpan(): void $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals(2, $xpath->query('/html/body/div/table/tr[1]/td')->length); - self::assertEquals('3', $xpath->query('/html/body/div/table/tr[1]/td[1]')->item(0)->attributes->getNamedItem('rowspan')->textContent); - self::assertEquals(1, $xpath->query('/html/body/div/table/tr[2]/td')->length); + $this->checkLength(2, $xpath, '/html/body/div/table/tr[1]/td'); + $this->checkContent('3', $xpath, '/html/body/div/table/tr[1]/td[1]', 0, 'rowspan'); + $this->checkLength(1, $xpath, '/html/body/div/table/tr[2]/td'); } /** @@ -195,14 +196,14 @@ public function testWriteRowSpanAndColSpan(): void $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals(3, $xpath->query('/html/body/div/table/tr[1]/td')->length); - self::assertEquals('2', $xpath->query('/html/body/div/table/tr[1]/td[2]')->item(0)->attributes->getNamedItem('colspan')->textContent); - self::assertEquals('3', $xpath->query('/html/body/div/table/tr[1]/td[3]')->item(0)->attributes->getNamedItem('rowspan')->textContent); + $this->checkLength(3, $xpath, '/html/body/div/table/tr[1]/td'); + $this->checkContent('2', $xpath, '/html/body/div/table/tr[1]/td[2]', 0, 'colspan'); + $this->checkContent('3', $xpath, '/html/body/div/table/tr[1]/td[3]', 0, 'rowspan'); - self::assertEquals(1, $xpath->query('/html/body/div/table/tr[2]/td')->length); - self::assertEquals('3', $xpath->query('/html/body/div/table/tr[2]/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); + $this->checkLength(1, $xpath, '/html/body/div/table/tr[2]/td'); + $this->checkContent('3', $xpath, '/html/body/div/table/tr[2]/td[1]', 0, 'colspan'); - self::assertEquals(3, $xpath->query('/html/body/div/table/tr[3]/td')->length); + $this->checkLength(3, $xpath, '/html/body/div/table/tr[3]/td'); } public function testWriteTitleTextRun(): void @@ -268,7 +269,28 @@ public function testWriteTableLayout(): void $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals('table-layout: fixed;', $xpath->query('/html/body/div/table[1]')->item(0)->attributes->getNamedItem('style')->textContent); - self::assertEquals('table-layout: auto;', $xpath->query('/html/body/div/table[2]')->item(0)->attributes->getNamedItem('style')->textContent); + $this->checkContent('table-layout: fixed;', $xpath, '/html/body/div/table[1]', 0, 'style'); + $this->checkContent('table-layout: auto;', $xpath, '/html/body/div/table[2]', 0, 'style'); + } + + private function checkContent(?string $expected, DOMXPath $xpath, string $location, int $index, string $namedItem): void + { + $temp = $xpath->query($location); + self::assertNotFalse($temp); + $temp = $temp->item($index); + self::assertInstanceOf(DOMElement::class, $temp); + $temp = $temp->attributes->getNamedItem($namedItem); + if ($temp === null) { + self::assertNull($expected); + } else { + self::assertSame($expected, $temp->textContent); + } + } + + private function checkLength(int $expected, DOMXPath $xpath, string $location): void + { + $temp = $xpath->query($location); + self::assertNotFalse($temp); + self::assertSame($expected, $temp->length); } } diff --git a/tests/PhpWordTests/Writer/HTML/Helper.php b/tests/PhpWordTests/Writer/HTML/Helper.php index 2e7ee343c3..40acd8ae5c 100644 --- a/tests/PhpWordTests/Writer/HTML/Helper.php +++ b/tests/PhpWordTests/Writer/HTML/Helper.php @@ -19,6 +19,7 @@ namespace PhpOffice\PhpWordTests\Writer\HTML; use DOMDocument; +use DOMElement; use DOMXPath; use Exception; use LibXMLError; @@ -41,6 +42,7 @@ public static function getTextContent(DOMXPath $xpath, string $query, string $na if ($item2 === null) { self::fail('Unexpected null return requesting item'); } elseif ($namedItem !== '') { + self::assertInstanceOf(DOMElement::class, $item2); $item3 = $item2->attributes->getNamedItem($namedItem); if ($item3 === null) { self::fail('Unexpected null return requesting namedItem'); @@ -48,6 +50,7 @@ public static function getTextContent(DOMXPath $xpath, string $query, string $na $returnVal = $item3->textContent; } } else { + self::assertInstanceOf(DOMElement::class, $item2); $returnVal = $item2->textContent; } } @@ -67,6 +70,7 @@ public static function getNamedItem(DOMXPath $xpath, string $query, string $name if ($item2 === null) { self::fail('Unexpected null return requesting item'); } else { + self::assertInstanceOf(DOMElement::class, $item2); $returnVal = $item2->attributes->getNamedItem($namedItem); } } diff --git a/tests/PhpWordTests/Writer/ODText/Part/AbstractPartClass.php b/tests/PhpWordTests/Writer/ODText/Part/AbstractPartClass.php new file mode 100644 index 0000000000..f366a90253 --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Part/AbstractPartClass.php @@ -0,0 +1,29 @@ +$method(ODText\Part\AbstractPart::class); - } else { - /** @var ODText\Part\AbstractPart $object */ - $object = new class() extends ODText\Part\AbstractPart { - public function write(): string - { - return ''; - } - }; - } + $object = new AbstractPartClass(); $object->setParentWriter(new ODText()); self::assertEquals(new ODText(), $object->getParentWriter()); } @@ -66,18 +46,7 @@ public function testSetGetParentWriterNull(): void { $this->expectException(Exception::class); $this->expectExceptionMessage('No parent WriterInterface assigned.'); - $method = self::$mockAbstract; - if (self::methodFound($method)) { - $object = $this->$method(ODText\Part\AbstractPart::class); - } else { - /** @var ODText\Part\AbstractPart $object */ - $object = new class() extends ODText\Part\AbstractPart { - public function write(): string - { - return ''; - } - }; - } + $object = new AbstractPartClass(); $object->getParentWriter(); } } diff --git a/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartClass.php b/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartClass.php new file mode 100644 index 0000000000..395c0f80da --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Part/AbstractPartClass.php @@ -0,0 +1,29 @@ +$method(Word2007\Part\AbstractPart::class); - } else { - /** @var Word2007\Part\AbstractPart $stub */ - $stub = new class() extends Word2007\Part\AbstractPart { - public function write(): string - { - return ''; - } - }; - } + $stub = new AbstractPartClass(); $stub->setParentWriter(new Word2007()); self::assertEquals(new Word2007(), $stub->getParentWriter()); } @@ -61,18 +41,7 @@ public function testSetGetParentWriterNull(): void { $this->expectException(Exception::class); $this->expectExceptionMessage('No parent WriterInterface assigned.'); - $method = self::$mockAbstract; - if (self::methodFound($method)) { - $stub = $this->$method(Word2007\Part\AbstractPart::class); - } else { - /** @var Word2007\Part\AbstractPart $stub */ - $stub = new class() extends Word2007\Part\AbstractPart { - public function write(): string - { - return ''; - } - }; - } + $stub = new AbstractPartClass(); $stub->getParentWriter(); } } diff --git a/tests/PhpWordTests/XmlDocument.php b/tests/PhpWordTests/XmlDocument.php index 8c865932ba..7a76c85dde 100644 --- a/tests/PhpWordTests/XmlDocument.php +++ b/tests/PhpWordTests/XmlDocument.php @@ -23,6 +23,7 @@ use DOMNode; use DOMNodeList; use DOMXPath; +use Exception; /** * DOM wrapper class. @@ -157,7 +158,13 @@ public function getNodeList(string $path, string $file = ''): DOMNodeList $this->xpath->registerNamespace('w14', 'http://schemas.microsoft.com/office/word/2010/wordml'); } - return $this->xpath->query($path); + $temp = $this->xpath->query($path); + if ($temp === false) { + throw new Exception('xpath query returned false'); + } + + /** @var DOMNodeList $temp */ + return $temp; } /** From 8bbd65d436c8e004d0fa11b71bd77943b5e3e67a Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 15 Nov 2025 20:01:55 -0800 Subject: [PATCH 122/153] Fix Phpstan for Php7.2/3 --- phpstan-baseline.php7.neon | 25 ------------------------- phpstan-baseline.php73.neon | 15 --------------- phpstan.neon.php7.dist | 9 --------- 3 files changed, 49 deletions(-) diff --git a/phpstan-baseline.php7.neon b/phpstan-baseline.php7.neon index 3d11dbeb00..4768548812 100644 --- a/phpstan-baseline.php7.neon +++ b/phpstan-baseline.php7.neon @@ -1340,16 +1340,6 @@ parameters: count: 1 path: tests/PhpWordTests/TestableTemplateProcesor.php - - - message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" - count: 9 - path: tests/PhpWordTests/Writer/HTML/ElementTest.php - - - - message: "#^Cannot call method item\\(\\) on DOMNodeList\\\\|false\\.$#" - count: 11 - path: tests/PhpWordTests/Writer/HTML/ElementTest.php - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" count: 3 @@ -1385,26 +1375,11 @@ parameters: count: 1 path: tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php - - - message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/Word2007/ElementTest.php - - - - message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/Word2007/StyleTest.php - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getElement\\(\\) should return DOMElement\\|null but returns DOMNode\\|null\\.$#" count: 1 path: tests/PhpWordTests/XmlDocument.php - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getNodeList\\(\\) should return DOMNodeList\\ but returns DOMNodeList\\\\|false\\.$#" - count: 1 - path: tests/PhpWordTests/XmlDocument.php - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$path \\(string\\) does not accept string\\|false\\.$#" count: 1 diff --git a/phpstan-baseline.php73.neon b/phpstan-baseline.php73.neon index 3d11dbeb00..beb197fa09 100644 --- a/phpstan-baseline.php73.neon +++ b/phpstan-baseline.php73.neon @@ -1340,16 +1340,6 @@ parameters: count: 1 path: tests/PhpWordTests/TestableTemplateProcesor.php - - - message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" - count: 9 - path: tests/PhpWordTests/Writer/HTML/ElementTest.php - - - - message: "#^Cannot call method item\\(\\) on DOMNodeList\\\\|false\\.$#" - count: 11 - path: tests/PhpWordTests/Writer/HTML/ElementTest.php - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" count: 3 @@ -1400,11 +1390,6 @@ parameters: count: 1 path: tests/PhpWordTests/XmlDocument.php - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getNodeList\\(\\) should return DOMNodeList\\ but returns DOMNodeList\\\\|false\\.$#" - count: 1 - path: tests/PhpWordTests/XmlDocument.php - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$path \\(string\\) does not accept string\\|false\\.$#" count: 1 diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist index e7f4bdc454..138268cf8a 100644 --- a/phpstan.neon.php7.dist +++ b/phpstan.neon.php7.dist @@ -35,15 +35,6 @@ parameters: - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{PhpOffice\\PhpWord\\Writer\\PDF\\AbstractRenderer, string} given.#' path: src/PhpWord/Writer/PDF.php - - - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Element\\AbstractElement is not subtype of native type #' - path: tests/PhpWordTests/Element/AbstractElementTest.php - - - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Style\\AbstractStyle is not subtype of native type #' - path: tests/PhpWordTests/Style/AbstractStyleTest.php - message: '#Parameter \#1 \$objectOrClass of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' path: tests/PhpWordTests/Style/AbstractStyleTest.php - - - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Writer\\Word2007\\Part\\AbstractPart is not subtype of native type #' - path: tests/PhpWordTests/Writer/Word2007/Part/AbstractPartTest.php From 61f6b83cdb32b1126cfe0a1817b081ae7af667ee Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 16 Nov 2025 06:15:41 -0800 Subject: [PATCH 123/153] Make Phpstan Upgrades Manual --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8d3842016f..15d3598d1c 100644 --- a/composer.json +++ b/composer.json @@ -123,7 +123,7 @@ "friendsofphp/php-cs-fixer": "^3.3", "mpdf/mpdf": "^7.0 || ^8.0", "phpmd/phpmd": "^2.13", - "phpstan/phpstan": "^0.12.88 || ^1.0.0 || ^2.0.0", + "phpstan/phpstan": "<=2.1.32", "phpstan/phpstan-phpunit": "^1.0 || ^2.0", "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0 || ^10.0", "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0 || 7.4.x-dev", From 89168b3703901ae5e1bb08e6fb27915d86cbda43 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 16 Nov 2025 06:34:49 -0800 Subject: [PATCH 124/153] See if Phpstan 7.4 Can Run Same as Phpstan 8.0+ --- .github/workflows/php.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 48cf29ac69..e9bf125fcb 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -66,11 +66,11 @@ jobs: run: ./vendor/bin/phpstan analyse -c phpstan.neon.php73.dist - name: Run phpstan Php7.4 - if: matrix.php == '7.4' + if: matrix.php == '7.44' run: ./vendor/bin/phpstan analyse -c phpstan.neon.php7.dist - name: Run phpstan Php8 - if: matrix.php == '8.0' || matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.3' || matrix.php == '8.4' + if: matrix.php == '8.0' || matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.3' || matrix.php == '8.4' || matrix.php == '7.4' run: ./vendor/bin/phpstan analyse -c phpstan.neon.dist phpunit: From 5197f167535d600f0b080e5daaa1c3372ab0a0f0 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 16 Nov 2025 07:05:44 -0800 Subject: [PATCH 125/153] Phpstan for 7.4 Can Run Same Action as for 8.0+ --- .github/workflows/php.yml | 10 +- phpstan-baseline.neon | 15 - phpstan-baseline.php7.neon | 1391 ----------------------------- phpstan.neon.php7.dist | 40 - src/PhpWord/Element/Image.php | 4 +- src/PhpWord/Shared/ZipArchive.php | 4 +- src/PhpWord/Writer/PDF.php | 4 +- 7 files changed, 12 insertions(+), 1456 deletions(-) delete mode 100644 phpstan-baseline.php7.neon delete mode 100644 phpstan.neon.php7.dist diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index e9bf125fcb..7363a0fc50 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -61,16 +61,12 @@ jobs: - name: Composer Install run: composer install --ansi --prefer-dist --no-interaction --no-progress - - name: Run phpstan Php7 + - name: Run phpstan Php7.2/Php7.3 if: matrix.php == '7.2' || matrix.php == '7.3' run: ./vendor/bin/phpstan analyse -c phpstan.neon.php73.dist - - name: Run phpstan Php7.4 - if: matrix.php == '7.44' - run: ./vendor/bin/phpstan analyse -c phpstan.neon.php7.dist - - - name: Run phpstan Php8 - if: matrix.php == '8.0' || matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.3' || matrix.php == '8.4' || matrix.php == '7.4' + - name: Run phpstan Php8/Php7.4 + if: matrix.php == '7.4' || matrix.php == '8.0' || matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.3' || matrix.php == '8.4' run: ./vendor/bin/phpstan analyse -c phpstan.neon.dist phpunit: diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fb5c58147f..e4c7483316 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -25,11 +25,6 @@ parameters: count: 1 path: src/PhpWord/Element/Image.php - - - message: "#^Parameter \\#1 \\$callback of function call_user_func expects callable\\(\\)\\: mixed, string given\\.$#" - count: 1 - path: src/PhpWord/Element/Image.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:\\$source \\(string\\) does not accept string\\|false\\.$#" count: 1 @@ -455,11 +450,6 @@ parameters: count: 6 path: src/PhpWord/Shared/ZipArchive.php - - - message: "#^Parameter \\#1 \\$callback of function call_user_func_array expects callable\\(\\)\\: mixed, array\\{\\$this\\(PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\)\\|PclZip\\|ZipArchive, mixed\\} given\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:\\$zip has unknown class PclZip as its type\\.$#" count: 1 @@ -890,11 +880,6 @@ parameters: count: 1 path: src/PhpWord/Writer/ODText/Part/Styles.php - - - message: "#^Parameter \\#1 \\$callback of function call_user_func_array expects callable\\(\\)\\: mixed, array\\{PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\AbstractRenderer, string\\} 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 diff --git a/phpstan-baseline.php7.neon b/phpstan-baseline.php7.neon deleted file mode 100644 index 4768548812..0000000000 --- a/phpstan-baseline.php7.neon +++ /dev/null @@ -1,1391 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:__call\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement but returns null\\.$#" - count: 1 - path: src/PhpWord/Element/AbstractContainer.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\) does not accept null\\.$#" - count: 1 - path: src/PhpWord/Element/Field.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:getArchiveImageSize\\(\\) should return array\\|null but returns array\\|false\\|null\\.$#" - count: 1 - path: src/PhpWord/Element/Image.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:getImageString\\(\\) should return string\\|null but returns string\\|false\\|null\\.$#" - count: 1 - path: src/PhpWord/Element/Image.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:\\$source \\(string\\) does not accept string\\|false\\.$#" - count: 1 - path: src/PhpWord/Element/Image.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\OLEObject\\:\\:\\$icon \\(string\\) does not accept string\\|false\\.$#" - count: 1 - path: src/PhpWord/Element/OLEObject.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Section\\:\\:addHeader\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Header but returns PhpOffice\\\\PhpWord\\\\Element\\\\Footer\\.$#" - count: 1 - path: src/PhpWord/Element/Section.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Section\\:\\:addHeaderFooter\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Footer but returns PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\.$#" - count: 1 - path: src/PhpWord/Element/Section.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeAsciiCharacter\\(\\) has parameter \\$code with no type specified\\.$#" - count: 1 - path: src/PhpWord/Escaper/Rtf.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeMultibyteCharacter\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Escaper/Rtf.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeMultibyteCharacter\\(\\) has parameter \\$code with no type specified\\.$#" - count: 1 - path: src/PhpWord/Escaper/Rtf.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createObject\\(\\) should return PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface but returns object\\.$#" - count: 1 - path: src/PhpWord/IOFactory.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createReader\\(\\) should return PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface but returns PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface\\.$#" - count: 1 - path: src/PhpWord/IOFactory.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createWriter\\(\\) should return PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface but returns PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface\\.$#" - count: 1 - path: src/PhpWord/IOFactory.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:getDefaultFontSize\\(\\) should return int but returns float\\|int\\.$#" - count: 1 - path: src/PhpWord/PhpWord.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\AbstractReader\\:\\:openFile\\(\\) should return resource but return statement is missing\\.$#" - count: 1 - path: src/PhpWord/Reader/AbstractReader.php - - - - message: "#^Parameter \\#2 \\$html of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:addHtml\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: src/PhpWord/Reader/HTML.php - - - - message: "#^Parameter \\#2 \\$contextNode of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLReader\\:\\:getElements\\(\\) expects DOMElement\\|null, DOMNode\\|null given\\.$#" - count: 2 - path: src/PhpWord/Reader/ODText/Content.php - - - - message: "#^Parameter \\#2 \\$depth of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addTitle\\(\\) expects int, string\\|null given\\.$#" - count: 1 - path: src/PhpWord/Reader/ODText/Content.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$rtf \\(string\\) does not accept string\\|false\\.$#" - count: 1 - path: src/PhpWord/Reader/RTF.php - - - - message: "#^Cannot call method setStyleByArray\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" - count: 1 - path: src/PhpWord/Reader/RTF/Document.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$phpWord is never read, only written\\.$#" - count: 1 - path: src/PhpWord/Reader/RTF/Document.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\:\\:load\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Reader/ReaderInterface.php - - - - message: "#^Binary operation \"/\" between string\\|null and 2 results in an error\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007/AbstractPart.php - - - - message: "#^Call to an undefined method DOMNode\\:\\:getAttribute\\(\\)\\.$#" - count: 2 - path: src/PhpWord/Reader/Word2007/AbstractPart.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:getHeadingDepth\\(\\) never returns float so it can be removed from the return type\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007/AbstractPart.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:getHeadingDepth\\(\\) should return float\\|int\\|null but returns string\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007/AbstractPart.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:read\\(\\) has no return type specified\\.$#" - 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 - path: src/PhpWord/Reader/Word2007/AbstractPart.php - - - - message: "#^Parameter \\#1 \\$height of method PhpOffice\\\\PhpWord\\\\Element\\\\Table\\:\\:addRow\\(\\) expects int\\|null, string\\|null given\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007/AbstractPart.php - - - - message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setRelationId\\(\\) expects int, string\\|null given\\.$#" - count: 2 - path: src/PhpWord/Reader/Word2007/AbstractPart.php - - - - message: "#^Parameter \\#1 \\$width of method PhpOffice\\\\PhpWord\\\\Element\\\\Row\\:\\:addCell\\(\\) expects int\\|null, string\\|null given\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007/AbstractPart.php - - - - message: "#^Parameter \\#2 \\$contextNode of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLReader\\:\\:getAttribute\\(\\) expects DOMElement\\|null, DOMNode\\|null given\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007/AbstractPart.php - - - - message: "#^Parameter \\#2 \\$depth of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addTitle\\(\\) expects int, float\\|int given\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007/AbstractPart.php - - - - message: "#^Strict comparison using \\=\\=\\= between null and DOMElement will always evaluate to false\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007/AbstractPart.php - - - - message: "#^Parameter \\#2 \\$relationId of method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\Footnotes\\:\\:getElement\\(\\) expects int, string\\|null given\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007/Footnotes.php - - - - message: "#^Parameter \\#3 \\$levelId of method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\Numbering\\:\\:readLevel\\(\\) expects int, string\\|null given\\.$#" - count: 2 - path: src/PhpWord/Reader/Word2007/Numbering.php - - - - message: "#^Parameter \\#1 \\$comments of method PhpOffice\\\\PhpWord\\\\ComplexType\\\\TrackChangesView\\:\\:setComments\\(\\) expects bool\\|null, string\\|null given\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007/Settings.php - - - - message: "#^Parameter \\#1 \\$consecutiveHyphenLimit of method PhpOffice\\\\PhpWord\\\\Metadata\\\\Settings\\:\\:setConsecutiveHyphenLimit\\(\\) expects int, string given\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007/Settings.php - - - - message: "#^Parameter \\#1 \\$hyphenationZone of method PhpOffice\\\\PhpWord\\\\Metadata\\\\Settings\\:\\:setHyphenationZone\\(\\) expects float\\|int\\|null, string given\\.$#" - count: 1 - path: src/PhpWord/Reader/Word2007/Settings.php - - - - message: "#^Parameter \\#1 \\$filename of function parse_ini_file expects string, string\\|false given\\.$#" - count: 1 - path: src/PhpWord/Settings.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\AbstractEnum\\:\\:getConstants\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/AbstractEnum.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\AbstractEnum\\:\\:\\$constCacheArray has no type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/AbstractEnum.php - - - - message: "#^Binary operation \"/\" between string and 10 results in an error\\.$#" - count: 1 - path: src/PhpWord/Shared/Converter.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:angleToDegree\\(\\) should return int but returns float\\.$#" - count: 1 - path: src/PhpWord/Shared/Converter.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:cssToPoint\\(\\) should return float\\|null but returns string\\.$#" - count: 1 - path: src/PhpWord/Shared/Converter.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:htmlToRgb\\(\\) should return array but returns false\\.$#" - count: 1 - path: src/PhpWord/Shared/Converter.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:inchToEmu\\(\\) should return int but returns float\\.$#" - count: 1 - path: src/PhpWord/Shared/Converter.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:pixelToEmu\\(\\) should return int but returns float\\.$#" - count: 1 - path: src/PhpWord/Shared/Converter.php - - - - message: "#^Parameter \\#1 \\$centimeter of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:cmToPoint\\(\\) expects float, string given\\.$#" - count: 1 - path: src/PhpWord/Shared/Converter.php - - - - message: "#^Parameter \\#1 \\$inch of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:inchToPoint\\(\\) expects float, string given\\.$#" - count: 1 - path: src/PhpWord/Shared/Converter.php - - - - message: "#^Parameter \\#1 \\$pica of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:picaToPoint\\(\\) expects float, string given\\.$#" - count: 1 - path: src/PhpWord/Shared/Converter.php - - - - message: "#^Parameter \\#1 \\$pixel of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:pixelToPoint\\(\\) expects float, string given\\.$#" - count: 1 - path: src/PhpWord/Shared/Converter.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:angleToDegrees\\(\\) should return int but returns float\\.$#" - count: 1 - path: src/PhpWord/Shared/Drawing.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:emuToPixels\\(\\) should return int but returns float\\.$#" - count: 1 - path: src/PhpWord/Shared/Drawing.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:pixelsToEmu\\(\\) should return int but returns float\\.$#" - count: 1 - path: src/PhpWord/Shared/Drawing.php - - - - message: "#^Call to an undefined method DOMNode\\:\\:getAttribute\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Cannot call method setStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:addHtml\\(\\) has parameter \\$options with no type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:filterOutNonInheritedStyles\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapBorderColor\\(\\) has parameter \\$cssBorderColor with no type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapBorderColor\\(\\) has parameter \\$styles with no type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapListType\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseLink\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseList\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseRuby\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:recursiveParseStylesInHierarchy\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Parameter \\#2 \\$element of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseNode\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\|PhpOffice\\\\PhpWord\\\\Element\\\\Row\\|PhpOffice\\\\PhpWord\\\\Element\\\\Table given\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$listIndex has no type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$options has no type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$xpath has no type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Html.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$encryptionMatrix has no type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$initialCodeArray has no type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$passwordMaxLength has no type specified\\.$#" - count: 1 - path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php - - - - message: "#^Call to method add\\(\\) on an unknown class PclZip\\.$#" - count: 2 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Call to method close\\(\\) on an unknown class PclZip\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Call to method extract\\(\\) on an unknown class PclZip\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Call to method extractByIndex\\(\\) on an unknown class PclZip\\.$#" - count: 3 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Call to method extractTo\\(\\) on an unknown class PclZip\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Call to method getFromName\\(\\) on an unknown class PclZip\\.$#" - count: 2 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Call to method listContent\\(\\) on an unknown class PclZip\\.$#" - count: 3 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Constant PCLZIP_OPT_ADD_PATH not found\\.$#" - count: 2 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Constant PCLZIP_OPT_EXTRACT_AS_STRING not found\\.$#" - count: 2 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Constant PCLZIP_OPT_PATH not found\\.$#" - count: 2 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Constant PCLZIP_OPT_REMOVE_PATH not found\\.$#" - count: 2 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Instantiated class PclZip not found\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:open\\(\\) should return bool but returns int\\|true\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^PHPDoc tag @var for variable \\$zip contains unknown class PclZip\\.$#" - count: 6 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:\\$zip has unknown class PclZip as its type\\.$#" - count: 1 - path: src/PhpWord/Shared/ZipArchive.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addFontStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" - count: 1 - path: src/PhpWord/Style.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addLinkStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" - count: 1 - path: src/PhpWord/Style.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addNumberingStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Numbering but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" - count: 1 - path: src/PhpWord/Style.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addParagraphStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" - count: 1 - path: src/PhpWord/Style.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addTableStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Table but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" - count: 1 - path: src/PhpWord/Style.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addTitleStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" - count: 1 - path: src/PhpWord/Style.php - - - - message: "#^Call to an undefined method object\\:\\:setStyleByArray\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Style/AbstractStyle.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setFloatVal\\(\\) should return float\\|null but returns float\\|int\\|string\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/AbstractStyle.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setNumericVal\\(\\) should return float\\|int\\|null but returns float\\|int\\|string\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/AbstractStyle.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Border\\:\\:getBorderSize\\(\\) should return array\\ but returns array\\\\.$#" - count: 1 - path: src/PhpWord/Style/Border.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\:\\:getBgColor\\(\\) should return string but returns null\\.$#" - count: 1 - path: src/PhpWord/Style/Cell.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\:\\:setUnit\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Style/Cell.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:getMajorTickPosition\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Style/Chart.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setCategoryAxisTitle\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Style/Chart.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setValueAxisTitle\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Style/Chart.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setValueLabelPosition\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Style/Chart.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:showAxisLabels\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Style/Chart.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:showGridY\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Style/Chart.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setAllCaps\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" - count: 1 - path: src/PhpWord/Style/Font.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setBgColor\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Table but return statement is missing\\.$#" - count: 1 - path: src/PhpWord/Style/Font.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setDoubleStrikethrough\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" - count: 1 - path: src/PhpWord/Style/Font.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSmallCaps\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" - count: 1 - path: src/PhpWord/Style/Font.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setStrikethrough\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" - count: 1 - path: src/PhpWord/Style/Font.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSubScript\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" - count: 1 - path: src/PhpWord/Style/Font.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSuperScript\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" - count: 1 - path: src/PhpWord/Style/Font.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Line\\:\\:\\$weight \\(int\\) does not accept float\\|int\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/Line.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\ListItem\\:\\:getListTypeStyle\\(\\) should return array but empty return statement found\\.$#" - count: 1 - path: src/PhpWord/Style/ListItem.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\ListItem\\:\\:getListTypeStyle\\(\\) should return array but return statement is missing\\.$#" - count: 1 - path: src/PhpWord/Style/ListItem.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\:\\:setStyleValue\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" - count: 1 - path: src/PhpWord/Style/Paragraph.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Section\\:\\:setSettingValue\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Section but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" - count: 1 - path: src/PhpWord/Style/Section.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\TOC\\:\\:setTabLeader\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\TOC but returns PhpOffice\\\\PhpWord\\\\Style\\\\Tab\\.$#" - count: 1 - path: src/PhpWord/Style/TOC.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\TOC\\:\\:setTabPos\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\TOC but returns PhpOffice\\\\PhpWord\\\\Style\\\\Tab\\.$#" - count: 1 - path: src/PhpWord/Style/TOC.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideHColor\\(\\) should return string but returns int\\|string\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/Table.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideHSize\\(\\) should return int but returns int\\|string\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/Table.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideVColor\\(\\) should return string but returns int\\|string\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/Table.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideVSize\\(\\) should return int but returns int\\|string\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/Table.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderSize\\(\\) should return array\\ but returns array\\\\.$#" - count: 1 - path: src/PhpWord/Style/Table.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginBottom\\(\\) should return int but returns int\\|string\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/Table.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginLeft\\(\\) should return int but returns int\\|string\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/Table.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginRight\\(\\) should return int but returns int\\|string\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/Table.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginTop\\(\\) should return int but returns int\\|string\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/Table.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$bottomFromText \\(int\\) does not accept float\\|int\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/TablePosition.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$leftFromText \\(int\\) does not accept float\\|int\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/TablePosition.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$rightFromText \\(int\\) does not accept float\\|int\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/TablePosition.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$tblpX \\(int\\) does not accept float\\|int\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/TablePosition.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$tblpY \\(int\\) does not accept float\\|int\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/TablePosition.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$topFromText \\(int\\) does not accept float\\|int\\|null\\.$#" - count: 1 - path: src/PhpWord/Style/TablePosition.php - - - - message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Cannot access offset 'end' on array\\\\|true\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Cannot access offset 'start' on array\\\\|true\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$imageMimeType with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$imgPath with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$partFileName with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$rid with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$baseValue with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$defaultValue with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$inlineValue with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$actualHeight with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$actualWidth with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$height with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$width with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getImageArgs\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getImageArgs\\(\\) has parameter \\$varNameWithArgs with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getNextRelationsIndex\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getNextRelationsIndex\\(\\) has parameter \\$documentPartName with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has parameter \\$replaceImage with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has parameter \\$varInlineArgs with no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Parameter \\#1 \\$element of method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\Chart\\:\\:setElement\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\Chart, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement given\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$macroClosingChars has no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$macroOpeningChars has no type specified\\.$#" - count: 1 - path: src/PhpWord/TemplateProcessor.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\HTML\\\\Element\\\\AbstractElement\\:\\:write\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/HTML/Element/AbstractElement.php - - - - message: "#^Variable \\$row in PHPDoc tag @var does not match assigned variable \\$rowStyle\\.$#" - count: 1 - path: src/PhpWord/Writer/HTML/Element/Table.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Field\\:\\:writeDefault\\(\\) has parameter \\$type with no type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/ODText/Element/Field.php - - - - message: "#^Variable \\$row in PHPDoc tag @var does not match any variable in the foreach loop\\: \\$cell$#" - count: 1 - path: src/PhpWord/Writer/ODText/Element/Table.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\AbstractElement\\:\\:replaceTabs\\(\\) has parameter \\$text with no type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/ODText/Element/AbstractElement.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\AbstractElement\\:\\:replaceTabs\\(\\) has parameter \\$xmlWriter with no type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/ODText/Element/AbstractElement.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Text\\:\\:writeChangeInsertion\\(\\) has parameter \\$start with no type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/ODText/Element/Text.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getParagraphStyle\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/ODText/Element/TextRun.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setColumnWidths\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/ODText/Part/Content.php - - - - message: "#^Parameter \\#1 \\$container of method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Content\\:\\:collectTrackedChanges\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement given\\.$#" - count: 1 - path: src/PhpWord/Writer/ODText/Part/Content.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Content\\:\\:\\$imageParagraphStyles has no type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/ODText/Part/Content.php - - - - message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" - count: 2 - path: src/PhpWord/Writer/ODText/Part/Styles.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Styles\\:\\:cvttwiptostr\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/ODText/Part/Styles.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:\\$renderer \\(PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\AbstractRenderer\\) does not accept object\\.$#" - count: 1 - path: src/PhpWord/Writer/PDF.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:loadHtml\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/PDF/DomPDF.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:output\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/PDF/DomPDF.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:render\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/PDF/DomPDF.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:setPaper\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/PDF/DomPDF.php - - - - message: "#^Class PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF referenced with incorrect case\\: PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\Dompdf\\.$#" - count: 1 - path: src/PhpWord/Writer/PDF/DomPDF.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:createExternalWriterInstance\\(\\) should return PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF but returns Dompdf\\\\Dompdf\\.$#" - count: 1 - path: src/PhpWord/Writer/PDF/DomPDF.php - - - - message: "#^Binary operation \"\\+\" between int\\|string and 1 results in an error\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Style\\\\Font\\:\\:setNameIndex\\(\\) expects int, int\\|string given\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|null\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|null\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept null\\.$#" - count: 2 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:write\\(\\) should return string but empty return statement found\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/Field.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writeDate\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/Field.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writeNumpages\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/Field.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writePage\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/Field.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getImageStringData\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/Image.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getStyle\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/Image.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getSource\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/Link.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/Link.php - - - - message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" - count: 2 - path: src/PhpWord/Writer/RTF/Part/Document.php - - - - message: "#^Binary operation \"\\+\" between int\\|string and 1 results in an error\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Style/Border.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\AbstractElement\\:\\:write\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Element/AbstractElement.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\Field\\:\\:buildPropertiesAndOptions\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Element/Field.php - - - - message: "#^Parameter \\#1 \\$content of method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\AbstractElement\\:\\:writeText\\(\\) expects string, bool\\|int\\|string given\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Element/FormField.php - - - - message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, bool\\|int\\|string given\\.$#" - count: 3 - path: src/PhpWord/Writer/Word2007/Element/FormField.php - - - - message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int given\\.$#" - count: 4 - path: src/PhpWord/Writer/Word2007/Element/FormField.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\ParagraphAlignment\\:\\:\\$attributes has no type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\ParagraphAlignment\\:\\:\\$name has no type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php - - - - message: "#^Parameter \\#2 \\$content of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, bool\\|int\\|string given\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Element/SDT.php - - - - message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int\\<100000000, 999999999\\> given\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Element/SDT.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\TableAlignment\\:\\:\\$attributes has no type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Element/TableAlignment.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\TableAlignment\\:\\:\\$name has no type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Element/TableAlignment.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:addBookmark\\(\\) invoked with 0 parameters, 1 required\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Element/Title.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\Chart\\:\\:writeAxisTitle\\(\\) has parameter \\$title with no type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Part/Chart.php - - - - message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int given\\.$#" - count: 9 - path: src/PhpWord/Writer/Word2007/Part/Chart.php - - - - message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int\\<0, max\\> given\\.$#" - count: 4 - path: src/PhpWord/Writer/Word2007/Part/Chart.php - - - - message: "#^Parameter \\#1 \\$haystack of function strpos expects string, int given\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Part/Rels.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Style\\\\AbstractStyle\\:\\:write\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Writer/Word2007/Style/AbstractStyle.php - - - - message: "#^Call to an undefined method object\\:\\:read\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/AbstractTestReader.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\AbstractTestReader\\:\\:\\$parts has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/AbstractTestReader.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getBaseUrl\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/AbstractWebServerEmbedded.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteBmpImageUrl\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/AbstractWebServerEmbedded.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteGifImageUrl\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/AbstractWebServerEmbedded.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:getRemoteImageUrl\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/AbstractWebServerEmbedded.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbedded\\:\\:\\$httpServer has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/AbstractWebServerEmbedded.php - - - - message: "#^Parameter \\#1 \\$width of class PhpOffice\\\\PhpWord\\\\Element\\\\Cell constructor expects int\\|null, string given\\.$#" - count: 2 - path: tests/PhpWordTests/Element/CellTest.php - - - - message: "#^Parameter \\#2 \\$style of class PhpOffice\\\\PhpWord\\\\Element\\\\Cell constructor expects array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\|null, int given\\.$#" - count: 2 - path: tests/PhpWordTests/Element/CellTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$createFunction with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Element/ImageTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$extension with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Element/ImageTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$imageFunction with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Element/ImageTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$imageQuality with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Element/ImageTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$source with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Element/ImageTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$type with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Element/ImageTest.php - - - - message: "#^Parameter \\#1 \\$source of class PhpOffice\\\\PhpWord\\\\Element\\\\Image constructor expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Element/ImageTest.php - - - - message: "#^Parameter \\#3 \\$watermark of class PhpOffice\\\\PhpWord\\\\Element\\\\Image constructor expects bool, null given\\.$#" - count: 1 - path: tests/PhpWordTests/Element/ImageTest.php - - - - message: "#^Parameter \\#1 \\$text of class PhpOffice\\\\PhpWord\\\\Element\\\\Title constructor expects PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string, PhpOffice\\\\PhpWord\\\\Element\\\\PageBreak given\\.$#" - count: 1 - path: tests/PhpWordTests/Element/TitleTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:escapestring\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:escapestring\\(\\) has parameter \\$str with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:expect\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:expect\\(\\) has parameter \\$str with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php - - - - message: "#^Parameter \\#1 \\$expected of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) expects class\\-string\\, string given\\.$#" - count: 1 - path: tests/PhpWordTests/IOFactoryTest.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/IOFactoryTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:undefinedMethod\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/PhpWordTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getRows\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Reader/Word2007/ElementTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" - count: 1 - path: tests/PhpWordTests/Reader/Word2007/ElementTest.php - - - - message: "#^Cannot access offset 0 on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\.$#" - count: 1 - path: tests/PhpWordTests/Reader/Word2007/ElementTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getElement\\(\\)\\.$#" - count: 2 - path: tests/PhpWordTests/Reader/Word2007/PartTest.php - - - - message: "#^Cannot call method getElement\\(\\) on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string\\.$#" - count: 3 - path: tests/PhpWordTests/Reader/Word2007/PartTest.php - - - - message: "#^Cannot call method isBold\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" - count: 1 - path: tests/PhpWordTests/Reader/Word2007/PartTest.php - - - - message: "#^Variable \\$endnote in PHPDoc tag @var does not match assigned variable \\$documentEndnote\\.$#" - count: 1 - path: tests/PhpWordTests/Reader/Word2007/PartTest.php - - - - message: "#^Variable \\$footnote in PHPDoc tag @var does not match assigned variable \\$documentFootnote\\.$#" - count: 1 - path: tests/PhpWordTests/Reader/Word2007/PartTest.php - - - - message: "#^Cannot access offset 0 on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\.$#" - count: 2 - path: tests/PhpWordTests/Reader/Word2007/StyleTest.php - - - - message: "#^Cannot call method getStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" - count: 1 - path: tests/PhpWordTests/Shared/HtmlTest.php - - - - message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Text\\:\\:numberFormat\\(\\) expects float, string given\\.$#" - count: 2 - path: tests/PhpWordTests/Shared/TextTest.php - - - - message: "#^Parameter \\#2 \\$locale of function setlocale expects array\\|string\\|null, int given\\.$#" - count: 1 - path: tests/PhpWordTests/Shared/XMLWriterTest.php - - - - message: "#^Parameter \\#2 \\$locale of function setlocale expects string\\|null, string\\|false given\\.$#" - count: 1 - path: tests/PhpWordTests/Shared/XMLWriterTest.php - - - - message: "#^Cannot call method setLineHeight\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" - count: 1 - path: tests/PhpWordTests/Style/FontTest.php - - - - message: "#^Parameter \\#1 \\$type of class PhpOffice\\\\PhpWord\\\\Style\\\\Font constructor expects string, null given\\.$#" - count: 1 - path: tests/PhpWordTests/Style/FontTest.php - - - - message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setStyleValue\\(\\) expects array\\|int\\|string, null given\\.$#" - count: 1 - path: tests/PhpWordTests/Style/FontTest.php - - - - message: "#^Cannot call method setLineHeight\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\.$#" - count: 1 - path: tests/PhpWordTests/Style/ParagraphTest.php - - - - message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setStyleValue\\(\\) expects array\\|int\\|string, bool given\\.$#" - count: 1 - path: tests/PhpWordTests/Style/RowTest.php - - - - message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\Section\\:\\:setSettingValue\\(\\) expects array\\|int\\|string, null given\\.$#" - count: 1 - path: tests/PhpWordTests/Style/SectionTest.php - - - - message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" - count: 2 - path: tests/PhpWordTests/TemplateProcessorTest.php - - - - message: "#^Cannot access offset 'end' on array\\\\|bool\\.$#" - count: 2 - path: tests/PhpWordTests/TemplateProcessorTest.php - - - - message: "#^Cannot access offset 'start' on array\\\\|bool\\.$#" - count: 2 - path: tests/PhpWordTests/TemplateProcessorTest.php - - - - message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\) expects string, string\\|false given\\.$#" - count: 6 - path: tests/PhpWordTests/TemplateProcessorTest.php - - - - message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringNotContainsString\\(\\) expects string, string\\|false given\\.$#" - count: 4 - path: tests/PhpWordTests/TemplateProcessorTest.php - - - - message: "#^Part \\$documentZip \\(ZipArchive\\) of encapsed string cannot be cast to string\\.$#" - count: 1 - path: tests/PhpWordTests/TemplateProcessorTest.php - - - - message: "#^Part \\$templateZip \\(ZipArchive\\) of encapsed string cannot be cast to string\\.$#" - count: 1 - path: tests/PhpWordTests/TemplateProcessorTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\TestableTemplateProcesor\\:\\:__construct\\(\\) has parameter \\$mainPart with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/TestableTemplateProcesor.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\TestableTemplateProcesor\\:\\:__construct\\(\\) has parameter \\$settingsPart with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/TestableTemplateProcesor.php - - - - message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" - count: 3 - path: tests/PhpWordTests/Writer/HTML/Element/PageBreakTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\ODText\\\\Style\\\\FontTest\\:\\:providerAllNamedColors\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/ODText/Style/FontTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/ElementTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/ElementTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/StyleTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/StyleTest.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\Writer\\\\Word2007\\\\Element\\\\ChartTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getElement\\(\\) should return DOMElement\\|null but returns DOMNode\\|null\\.$#" - count: 1 - path: tests/PhpWordTests/XmlDocument.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$path \\(string\\) does not accept string\\|false\\.$#" - count: 1 - path: tests/PhpWordTests/XmlDocument.php - - - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$xpath \\(DOMXPath\\) does not accept null\\.$#" - count: 1 - path: tests/PhpWordTests/XmlDocument.php diff --git a/phpstan.neon.php7.dist b/phpstan.neon.php7.dist deleted file mode 100644 index 138268cf8a..0000000000 --- a/phpstan.neon.php7.dist +++ /dev/null @@ -1,40 +0,0 @@ -includes: - - phpstan-baseline.php7.neon - - vendor/phpstan/phpstan-phpunit/extension.neon - - vendor/phpstan/phpstan-phpunit/rules.neon -parameters: - level: 7 - paths: - - src/ - - tests/ - excludePaths: - - */pclzip.lib.php - bootstrapFiles: - - tests/bootstrap.php - treatPhpDocTypesAsCertain: false - ignoreErrors: - - - identifier: missingType.iterableValue - - ## <=PHP7.4 - - - message: '#Parameter \#1 \$objectOrClass of class ReflectionClass constructor expects class-string\|T of object, string given.#' - path: src/PhpWord/Element/AbstractContainer.php - - - message: '#Parameter \#1 \$function of function call_user_func expects callable\(\): mixed, string given.#' - path: src/PhpWord/Element/Image.php - - - message: '#Parameter \#1 \$objectOrClass of class ReflectionClass constructor expects class-string\|T of object, string given.#' - path: src/PhpWord/IOFactory.php - - - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed,#' - path: src/PhpWord/Shared/ZipArchive.php - - - message: '#PHPDoc tag @var with type PhpOffice\\PhpWord\\Writer\\HTML\\Part\\AbstractPart is not subtype of native type PhpOffice\\PhpWord\\Writer\\HTML\\Part\\Body\|PhpOffice\\PhpWord\\Writer\\HTML\\Part\\Head.#' - path: src/PhpWord/Writer/HTML.php - - - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{PhpOffice\\PhpWord\\Writer\\PDF\\AbstractRenderer, string} given.#' - path: src/PhpWord/Writer/PDF.php - - - message: '#Parameter \#1 \$objectOrClass of class ReflectionClass constructor expects class-string\|object, class-string\|false given.#' - path: tests/PhpWordTests/Style/AbstractStyleTest.php diff --git a/src/PhpWord/Element/Image.php b/src/PhpWord/Element/Image.php index c47e5effa5..88259067af 100644 --- a/src/PhpWord/Element/Image.php +++ b/src/PhpWord/Element/Image.php @@ -373,7 +373,9 @@ public function getImageString(): ?string // Read image binary data and convert to hex/base64 string if ($this->sourceType == self::SOURCE_GD) { - $imageResource = call_user_func($this->imageCreateFunc, $actualSource); + /** @var callable */ + $callable = $this->imageCreateFunc; + $imageResource = call_user_func($callable, $actualSource); if ($this->imageType === 'image/png') { // PNG images need to preserve alpha channel information imagesavealpha($imageResource, true); diff --git a/src/PhpWord/Shared/ZipArchive.php b/src/PhpWord/Shared/ZipArchive.php index def835786c..2c612b1278 100644 --- a/src/PhpWord/Shared/ZipArchive.php +++ b/src/PhpWord/Shared/ZipArchive.php @@ -116,7 +116,9 @@ public function __call($function, $args) // Run function $result = false; if (method_exists($zipObject, $zipFunction)) { - $result = @call_user_func_array([$zipObject, $zipFunction], $args); + /** @var callable */ + $callable = [$zipObject, $zipFunction]; + $result = @call_user_func_array($callable, $args); } return $result; diff --git a/src/PhpWord/Writer/PDF.php b/src/PhpWord/Writer/PDF.php index b58476d4f7..45cfc7c1e4 100644 --- a/src/PhpWord/Writer/PDF.php +++ b/src/PhpWord/Writer/PDF.php @@ -76,8 +76,10 @@ public function __call($name, $arguments) // if ($this->renderer === null) { // throw new Exception("PDF Rendering library has not been defined."); // } + /** @var callable */ + $callable = [$this->getRenderer(), $name]; - return call_user_func_array([$this->getRenderer(), $name], $arguments); + return call_user_func_array($callable, $arguments); } public function save(string $filename): void From 8545130d4c9b32a317761b801f745756e5578ef8 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 16 Nov 2025 07:11:39 -0800 Subject: [PATCH 126/153] Update Phpstan for Php 7.2/7.3 --- phpstan.neon.php73.dist | 9 --------- 1 file changed, 9 deletions(-) diff --git a/phpstan.neon.php73.dist b/phpstan.neon.php73.dist index e522bf9edf..f68f9b95d4 100644 --- a/phpstan.neon.php73.dist +++ b/phpstan.neon.php73.dist @@ -21,27 +21,18 @@ parameters: - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' path: src/PhpWord/Element/AbstractContainer.php - - - message: '#Parameter \#1 \$function of function call_user_func expects callable\(\): mixed, string given.#' - path: src/PhpWord/Element/Image.php - message: '#Parameter \#1 \$argument of class ReflectionClass constructor expects class-string\|T of object, string given.#' path: src/PhpWord/IOFactory.php - message: '#Cannot instantiate interface#' path: src/PhpWord/IOFactory.php - - - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed,#' - path: src/PhpWord/Shared/ZipArchive.php - message: '#only iterables are supported#' path: src/PhpWord/Writer/AbstractWriter.php - message: '#Cannot call method getText#' path: src/PhpWord/Writer/ODText/Part/Content.php - - - message: '#Parameter \#1 \$function of function call_user_func_array expects callable\(\): mixed, array{PhpOffice\\PhpWord\\Writer\\PDF\\AbstractRenderer, string} given.#' - path: src/PhpWord/Writer/PDF.php - message: '#only iterables are supported#' path: tests/PhpWordTests/Shared/ZipArchiveTest.php From 882f98f5f00fc7760d076fa7eb863f4a255d1cbe Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 16 Nov 2025 08:23:28 -0800 Subject: [PATCH 127/153] Tweak --- phpstan-baseline.neon | 6 ------ src/PhpWord/Writer/HTML.php | 4 +--- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e4c7483316..a0d53e92fc 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1404,9 +1404,3 @@ parameters: message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$xpath \\(DOMXPath\\) does not accept null\\.$#" count: 1 path: tests/PhpWordTests/XmlDocument.php - - # https://github.com/phpstan/phpstan/issues/8770 - - - message: "#^PHPDoc tag @var with type PhpOffice\\\\PhpWord\\\\Writer\\\\HTML\\\\Part\\\\AbstractPart is not subtype of native type[\\sA-Za-z\\@\\\\\\/0-9\\.:|]+$#" - count: 1 - path: src/PhpWord/Writer/HTML.php diff --git a/src/PhpWord/Writer/HTML.php b/src/PhpWord/Writer/HTML.php index acaa3c4800..e5d91adfb2 100644 --- a/src/PhpWord/Writer/HTML.php +++ b/src/PhpWord/Writer/HTML.php @@ -21,7 +21,6 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Validate; -use PhpOffice\PhpWord\Writer\HTML\Part\AbstractPart; /** * HTML writer. @@ -77,8 +76,7 @@ public function __construct(?PhpWord $phpWord = null) $this->parts = ['Head', 'Body']; foreach ($this->parts as $partName) { $partClass = self::class . '\\Part\\' . $partName; - if (class_exists($partClass)) { - /** @var AbstractPart $part Type hint */ + if (class_exists($partClass) && method_exists($partClass, 'setParentWriter')) { $part = new $partClass(); $part->setParentWriter($this); $this->writerParts[strtolower($partName)] = $part; From b5775471e45eb3b29af379571bd4d4abb539c386 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 17 Nov 2025 10:05:35 -0800 Subject: [PATCH 128/153] Rasamassen PR's 2815, 2816, 2830, 2840 I made RTF usable some time ago. @rasamassen has recently done a lot of useful work to cover many missing areas. Since who knows when those changes will be merged, I intend to incorporate much of that work. I will do this with several pushes, each based on one or more of those changes. This one is based on PR #2815 (partially addressing issue #862), PR #2816, PR #2830 (partially addressing issue #1656), and PR #2840 (partially addressing issue #909). --- samples/Sample_09_Tables.php | 4 +- src/PhpWord/Style/Paper.php | 35 ++++-- src/PhpWord/Writer/RTF/Element/TextBreak.php | 4 + src/PhpWord/Writer/RTF/Style/Tab.php | 24 +++- tests/PhpWordTests/Style/PaperTest.php | 47 +++++-- .../Writer/RTF/Element/TextBreakTest.php | 61 +++++++++ .../PhpWordTests/Writer/RTF/Style/RtlTest.php | 70 +++++++++++ .../PhpWordTests/Writer/RTF/Style/TabTest.php | 119 ++++++++++++++++++ tests/PhpWordTests/Writer/RTF/StyleTest.php | 75 +---------- 9 files changed, 341 insertions(+), 98 deletions(-) create mode 100644 tests/PhpWordTests/Writer/RTF/Element/TextBreakTest.php create mode 100644 tests/PhpWordTests/Writer/RTF/Style/RtlTest.php create mode 100644 tests/PhpWordTests/Writer/RTF/Style/TabTest.php diff --git a/samples/Sample_09_Tables.php b/samples/Sample_09_Tables.php index 3a887468b0..4c4f68f83d 100644 --- a/samples/Sample_09_Tables.php +++ b/samples/Sample_09_Tables.php @@ -125,8 +125,8 @@ $row = $table->addRow(); $row->addCell(1000, ['vMerge' => 'continue']); -$row->addCell(1000)->addText('C'); -$row->addCell(1000)->addText('D'); +$row->addCell(500)->addText('C'); +$row->addCell(500)->addText('D'); $row->addCell(1000)->addText('3'); // 5. Nested table diff --git a/src/PhpWord/Style/Paper.php b/src/PhpWord/Style/Paper.php index c59ea42d7b..daecdec436 100644 --- a/src/PhpWord/Style/Paper.php +++ b/src/PhpWord/Style/Paper.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Style; +use InvalidArgumentException; use PhpOffice\PhpWord\Shared\Converter; /** @@ -97,16 +98,21 @@ class Paper extends AbstractStyle /** * Paper sizes. * - * @var array + * @var array */ private $sizes = [ 'A3' => [297, 420, 'mm'], 'A4' => [210, 297, 'mm'], 'A5' => [148, 210, 'mm'], + 'B4' => [250, 353, 'mm'], 'B5' => [176, 250, 'mm'], + 'Executive' => [7.25, 10.5, 'in'], 'Folio' => [8.5, 13, 'in'], + 'Ledger' => [17, 11, 'in'], 'Legal' => [8.5, 14, 'in'], 'Letter' => [8.5, 11, 'in'], + 'Statement' => [5.5, 8.5, 'in'], + 'Tabloid' => [11, 17, 'in'], ]; /** @@ -151,24 +157,37 @@ public function getSize() } /** - * Set size. + * Set size. Normally called with 1 parameter, corresponding to a key + * in the $sizes array. However, for sizes not in that table, + * it can be called with explicit width, height, and unit. * * @param string $size + * @param float|int $width + * @param float|int $height + * @param string $unit * * @return self */ - public function setSize($size) + public function setSize($size, $width = 0, $height = 0, $unit = '') { - $this->size = $this->setEnumVal($size, array_keys($this->sizes), $this->size); - - [$width, $height, $unit] = $this->sizes[$this->size]; + if ($width != 0 || $height != 0 || $unit !== '') { + $this->size = $size; + } else { + $this->size = $this->setEnumVal($size, array_keys($this->sizes), $this->size); + [$width, $height, $unit] = $this->sizes[$this->size]; + } - if ($unit == 'mm') { + if ($width <= 0 || $height <= 0) { + throw new InvalidArgumentException('width and height must be positive'); + } + if ($unit === 'mm') { $this->width = Converter::cmToTwip($width / 10); $this->height = Converter::cmToTwip($height / 10); - } else { + } elseif ($unit === 'in') { $this->width = Converter::inchToTwip($width); $this->height = Converter::inchToTwip($height); + } else { + throw new InvalidArgumentException('unit must be mm or in'); } return $this; diff --git a/src/PhpWord/Writer/RTF/Element/TextBreak.php b/src/PhpWord/Writer/RTF/Element/TextBreak.php index d74bf23dcb..c533a32f45 100644 --- a/src/PhpWord/Writer/RTF/Element/TextBreak.php +++ b/src/PhpWord/Writer/RTF/Element/TextBreak.php @@ -36,6 +36,10 @@ public function write() $parentWriter = $this->parentWriter; $parentWriter->setLastParagraphStyle(); + if ($this->withoutP) { + return '\line' . PHP_EOL; + } + return '\pard\par' . PHP_EOL; } } diff --git a/src/PhpWord/Writer/RTF/Style/Tab.php b/src/PhpWord/Writer/RTF/Style/Tab.php index 95e1f10a5c..bc914f223c 100644 --- a/src/PhpWord/Writer/RTF/Style/Tab.php +++ b/src/PhpWord/Writer/RTF/Style/Tab.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\RTF\Style; +use PhpOffice\PhpWord\Style\Tab as TabStyle; + /** * Line numbering style writer. * @@ -31,19 +33,31 @@ class Tab extends AbstractStyle public function write() { $style = $this->getStyle(); - if (!$style instanceof \PhpOffice\PhpWord\Style\Tab) { + if (!$style instanceof TabStyle) { return; } $tabs = [ - \PhpOffice\PhpWord\Style\Tab::TAB_STOP_RIGHT => '\tqr', - \PhpOffice\PhpWord\Style\Tab::TAB_STOP_CENTER => '\tqc', - \PhpOffice\PhpWord\Style\Tab::TAB_STOP_DECIMAL => '\tqdec', + TabStyle::TAB_STOP_RIGHT => '\tqr', + TabStyle::TAB_STOP_CENTER => '\tqc', + TabStyle::TAB_STOP_DECIMAL => '\tqdec', + TabStyle::TAB_LEADER_DOT => '\tldot', + TabStyle::TAB_LEADER_HYPHEN => '\tlhyph', + TabStyle::TAB_LEADER_UNDERSCORE => '\tlul', + TabStyle::TAB_LEADER_HEAVY => '\tlth', + TabStyle::TAB_LEADER_MIDDLEDOT => '\tlmdot', ]; $content = ''; if (isset($tabs[$style->getType()])) { $content .= $tabs[$style->getType()]; } - $content .= '\tx' . round($style->getPosition()); + if (isset($tabs[$style->getLeader()]) && $style->getType() !== TabStyle::TAB_STOP_BAR) { + $content .= $tabs[$style->getLeader()]; + } + if ($style->getType() == TabStyle::TAB_STOP_BAR) { + $content .= '\tb' . round($style->getPosition()); + } else { + $content .= '\tx' . round($style->getPosition()); + } return $content; } diff --git a/tests/PhpWordTests/Style/PaperTest.php b/tests/PhpWordTests/Style/PaperTest.php index 9ba64862d7..4996317b84 100644 --- a/tests/PhpWordTests/Style/PaperTest.php +++ b/tests/PhpWordTests/Style/PaperTest.php @@ -18,22 +18,14 @@ namespace PhpOffice\PhpWordTests\Style; +use InvalidArgumentException; use PhpOffice\PhpWord\Style\Paper; -use PhpOffice\PhpWordTests\TestHelperDOCX; /** * Test class for PhpOffice\PhpWord\Style\Paper. */ class PaperTest extends \PHPUnit\Framework\TestCase { - /** - * Tear down after each test. - */ - protected function tearDown(): void - { - TestHelperDOCX::clear(); - } - /** * Test initiation for paper. */ @@ -68,4 +60,41 @@ public function testFolioSize(): void self::assertEqualsWithDelta(12240, $object->getWidth(), 0.1); self::assertEqualsWithDelta(18720, $object->getHeight(), 0.1); } + + /** + * Test paper size for Folio format invoked with explicit values. + */ + public function testFolioSizeExplicit(): void + { + $object = new Paper(); + $object->setSize('XFolio', 8.5, 13, 'in'); + + self::assertEquals('XFolio', $object->getSize()); + self::assertEqualsWithDelta(12240, $object->getWidth(), 0.1); + self::assertEqualsWithDelta(18720, $object->getHeight(), 0.1); + } + + public function testBadUnit(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('unit must be mm or in'); + $object = new Paper(); + $object->setSize('XFolio', 8.5, 13, 'inx'); + } + + public function testBadWidthHeight(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('width and height must be positive'); + $object = new Paper(); + $object->setSize('XFolio', 0, 13, 'inx'); + } + + public function testUnknownSize(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid style value'); + $object = new Paper(); + $object->setSize('XFolio'); + } } diff --git a/tests/PhpWordTests/Writer/RTF/Element/TextBreakTest.php b/tests/PhpWordTests/Writer/RTF/Element/TextBreakTest.php new file mode 100644 index 0000000000..ca152a0e54 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Element/TextBreakTest.php @@ -0,0 +1,61 @@ +write()); + } + + /** + * Test a normal textBreak. + * See page 142-143 of RTF Specification 1.9.1. + */ + public function testTextBreakParagraph(): void + { + $parentWriter = new RTF(); + $element = new TextBreakElement(); + $writer = new TextBreakWriter($parentWriter, $element); + $expect = "\\pard\\par\n"; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test a textBreak as a line break. + * See page 142-143 of RTF Specification 1.9.1. + */ + public function testTextBreakLine(): void + { + $parentWriter = new RTF(); + $element = new TextBreakElement(); + $writer = new TextBreakWriter($parentWriter, $element, true); + $expect = "\\line\n"; + self::assertEquals($expect, $this->removeCr($writer)); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Style/RtlTest.php b/tests/PhpWordTests/Writer/RTF/Style/RtlTest.php new file mode 100644 index 0000000000..9528f7a1af --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/RtlTest.php @@ -0,0 +1,70 @@ +write()); + } + + public function testRTL(): void + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('אב גד', ['RTL' => true]); + $text = new RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\nowidctlpar {\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } + + public function testRTL2(): void + { + Settings::setDefaultRtl(true); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('אב גד'); + $text = new RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\qr{\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } + + public function testPageBreakLineHeight2(): void + { + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); + $text = new RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\ql\\sl259\\slmult1\\page{\\cf0\\f0 New page}\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Style/TabTest.php b/tests/PhpWordTests/Writer/RTF/Style/TabTest.php new file mode 100644 index 0000000000..809224f679 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/TabTest.php @@ -0,0 +1,119 @@ +write()); + } + + /** + * Test tab stops. + * See page 83 of RTF Specification 1.9.1 for Tabs. + */ + public function testTabStop(): void + { + $parentWriter = new RTF(); + $style = new TabStyle(); + $writer = new TabWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setPosition(3000); + $style->setType($style::TAB_STOP_CLEAR); + $expect = '\tx3000'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setType(TabStyle::TAB_STOP_LEFT); + $expect = '\tx3000'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setType(TabStyle::TAB_STOP_CENTER); + $expect = '\tqc\tx3000'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setType(TabStyle::TAB_STOP_RIGHT); + $expect = '\tqr\tx3000'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setType(TabStyle::TAB_STOP_DECIMAL); + $expect = '\tqdec\tx3000'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setType(TabStyle::TAB_STOP_BAR); + $expect = '\tb3000'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setType(TabStyle::TAB_STOP_NUM); // No equivalent specified in RTF + $expect = '\tx3000'; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test tab leaders. + * See page 83 of RTF Specification 1.9.1 for Tabs. + */ + public function testTabLeader(): void + { + $parentWriter = new RTF(); + $style = new TabStyle(); + $writer = new TabWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setPosition(600); + $style->setLeader(TabStyle::TAB_LEADER_NONE); + $expect = '\tx600'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setLeader(TabStyle::TAB_LEADER_DOT); + $expect = '\tldot\tx600'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setLeader(TabStyle::TAB_LEADER_HYPHEN); + $expect = '\tlhyph\tx600'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setLeader(TabStyle::TAB_LEADER_UNDERSCORE); + $expect = '\tlul\tx600'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setLeader(TabStyle::TAB_LEADER_HEAVY); + $expect = '\tlth\tx600'; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setLeader(TabStyle::TAB_LEADER_MIDDLEDOT); + $expect = '\tlmdot\tx600'; + self::assertEquals($expect, $this->removeCr($writer)); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/StyleTest.php b/tests/PhpWordTests/Writer/RTF/StyleTest.php index 8ba2bcb9c9..f586b55209 100644 --- a/tests/PhpWordTests/Writer/RTF/StyleTest.php +++ b/tests/PhpWordTests/Writer/RTF/StyleTest.php @@ -18,21 +18,14 @@ namespace PhpOffice\PhpWordTests\Writer\RTF; -use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Writer\RTF; use PhpOffice\PhpWord\Writer\RTF\Style\Border; -use PHPUnit\Framework\Assert; /** * Test class for PhpOffice\PhpWord\Writer\RTF\Style subnamespace. */ class StyleTest extends \PHPUnit\Framework\TestCase { - protected function tearDown(): void - { - Settings::setDefaultRtl(null); - } - public function removeCr($field) { return str_replace("\r\n", "\n", $field->write()); @@ -81,63 +74,7 @@ public function testIndentation(): void $indentWriter->setParentWriter(new RTF()); $result = $indentWriter->write(); - Assert::assertEquals('\fi3\li1\ri2 ', $result); - } - - public function testRightTab(): void - { - $tabRight = new \PhpOffice\PhpWord\Style\Tab(); - $tabRight->setType(\PhpOffice\PhpWord\Style\Tab::TAB_STOP_RIGHT); - $tabRight->setPosition(5); - - $tabWriter = new RTF\Style\Tab($tabRight); - $tabWriter->setParentWriter(new RTF()); - $result = $tabWriter->write(); - - Assert::assertEquals('\tqr\tx5', $result); - } - - public function testCenterTab(): void - { - $tabRight = new \PhpOffice\PhpWord\Style\Tab(); - $tabRight->setType(\PhpOffice\PhpWord\Style\Tab::TAB_STOP_CENTER); - - $tabWriter = new RTF\Style\Tab($tabRight); - $tabWriter->setParentWriter(new RTF()); - $result = $tabWriter->write(); - - Assert::assertEquals('\tqc\tx0', $result); - } - - public function testDecimalTab(): void - { - $tabRight = new \PhpOffice\PhpWord\Style\Tab(); - $tabRight->setType(\PhpOffice\PhpWord\Style\Tab::TAB_STOP_DECIMAL); - - $tabWriter = new RTF\Style\Tab($tabRight); - $tabWriter->setParentWriter(new RTF()); - $result = $tabWriter->write(); - - Assert::assertEquals('\tqdec\tx0', $result); - } - - public function testRTL(): void - { - $parentWriter = new RTF(); - $element = new \PhpOffice\PhpWord\Element\Text('אב גד', ['RTL' => true]); - $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar {\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; - self::assertEquals($expect, $this->removeCr($text)); - } - - public function testRTL2(): void - { - Settings::setDefaultRtl(true); - $parentWriter = new RTF(); - $element = new \PhpOffice\PhpWord\Element\Text('אב גד'); - $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\qr{\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; - self::assertEquals($expect, $this->removeCr($text)); + self::assertSame('\fi3\li1\ri2 ', $result); } public function testPageBreakLineHeight(): void @@ -149,16 +86,6 @@ public function testPageBreakLineHeight(): void self::assertEquals($expect, $this->removeCr($text)); } - public function testPageBreakLineHeight2(): void - { - Settings::setDefaultRtl(false); - $parentWriter = new RTF(); - $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); - $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\ql\\sl259\\slmult1\\page{\\cf0\\f0 New page}\\par\n"; - self::assertEquals($expect, $this->removeCr($text)); - } - public function testPageNumberRestart(): void { //$parentWriter = new RTF(); From a5dd3105a1c9d5e2d605fc9830db4acd7eb1b974 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 18 Nov 2025 14:49:38 -0800 Subject: [PATCH 129/153] Rasamassen PR's 2819 and part of 2821 I made RTF usable some time ago. @rasamassen has recently done a lot of useful work to cover many missing areas. Since who knows when those changes will be merged, I intend to incorporate much of that work. I will do this with several pushes, each based on one or more of those changes. This one is based on PR #2819 (fixes issue #321), and the Escaper portion of PR #2821. --- phpstan-baseline.neon | 55 ---- phpstan-baseline.php73.neon | 55 ---- src/PhpWord/Escaper/Rtf.php | 52 +--- src/PhpWord/Style/Language.php | 143 ++++++++-- .../Writer/RTF/Element/AbstractElement.php | 36 ++- src/PhpWord/Writer/RTF/Element/Text.php | 9 - src/PhpWord/Writer/RTF/Element/TextRun.php | 2 +- src/PhpWord/Writer/RTF/Part/Document.php | 6 +- src/PhpWord/Writer/RTF/Part/Header.php | 2 + src/PhpWord/Writer/RTF/Style/Font.php | 119 +++++++- .../PhpWordTests/Escaper/RtfEscaper2Test.php | 27 +- .../PhpWordTests/Escaper/RtfEscaper3Test.php | 9 +- tests/PhpWordTests/Style/LanguageTest.php | 48 ++-- .../Writer/RTF/{ => Element}/Element2Test.php | 18 +- .../Writer/RTF/{ => Element}/ElementTest.php | 35 ++- .../Writer/RTF/Element/TableTest.php | 24 +- .../PhpWordTests/Writer/RTF/Sample11Test.php | 22 +- .../Writer/RTF/Style/FontTest.php | 257 ++++++++++++++++++ .../PhpWordTests/Writer/RTF/Style/RtlTest.php | 6 +- .../Writer/RTF/{ => Style}/StyleTest.php | 7 +- 20 files changed, 634 insertions(+), 298 deletions(-) rename tests/PhpWordTests/Writer/RTF/{ => Element}/Element2Test.php (78%) rename tests/PhpWordTests/Writer/RTF/{ => Element}/ElementTest.php (89%) create mode 100644 tests/PhpWordTests/Writer/RTF/Style/FontTest.php rename tests/PhpWordTests/Writer/RTF/{ => Style}/StyleTest.php (95%) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a0d53e92fc..3175cd9899 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -915,41 +915,6 @@ parameters: count: 1 path: src/PhpWord/Writer/PDF/DomPDF.php - - - message: "#^Binary operation \"\\+\" between int\\|string and 1 results in an error\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Style\\\\Font\\:\\:setNameIndex\\(\\) expects int, int\\|string given\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|null\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|null\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept null\\.$#" - count: 2 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:write\\(\\) should return string but empty return statement found\\.$#" count: 1 @@ -1365,26 +1330,6 @@ parameters: count: 1 path: tests/PhpWordTests/Writer/ODText/Style/FontTest.php - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/ElementTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/ElementTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/StyleTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/StyleTest.php - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\Writer\\\\Word2007\\\\Element\\\\ChartTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" count: 1 diff --git a/phpstan-baseline.php73.neon b/phpstan-baseline.php73.neon index beb197fa09..04880c4002 100644 --- a/phpstan-baseline.php73.neon +++ b/phpstan-baseline.php73.neon @@ -905,41 +905,6 @@ parameters: count: 1 path: src/PhpWord/Writer/PDF/DomPDF.php - - - message: "#^Binary operation \"\\+\" between int\\|string and 1 results in an error\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Style\\\\Font\\:\\:setNameIndex\\(\\) expects int, int\\|string given\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|null\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|null\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\.$#" - count: 1 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept null\\.$#" - count: 2 - path: src/PhpWord/Writer/RTF/Element/AbstractElement.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:write\\(\\) should return string but empty return statement found\\.$#" count: 1 @@ -1350,26 +1315,6 @@ parameters: count: 1 path: tests/PhpWordTests/Writer/ODText/Style/FontTest.php - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/ElementTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/ElementTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/StyleTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" - count: 1 - path: tests/PhpWordTests/Writer/RTF/StyleTest.php - - message: "#^Property PhpOffice\\\\PhpWordTests\\\\Writer\\\\Word2007\\\\Element\\\\ChartTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" count: 1 diff --git a/src/PhpWord/Escaper/Rtf.php b/src/PhpWord/Escaper/Rtf.php index 0bde66b470..f7cd22a48f 100644 --- a/src/PhpWord/Escaper/Rtf.php +++ b/src/PhpWord/Escaper/Rtf.php @@ -43,55 +43,27 @@ protected function escapeAsciiCharacter($code) protected function escapeMultibyteCharacter($code) { - return '\\uc0{\\u' . $code . '}'; + if ($code > 32767) { + return '\\uc0\\u' . ((int) $code - 65536) . ' '; + } + + return '\\uc0\\u' . $code . ' '; } /** - * @see http://www.randomchaos.com/documents/?source=php_and_unicode - * * @param ?string $input */ protected function escapeSingleValue($input) { $escapedValue = ''; - - $numberOfBytes = 1; - $bytes = []; - for ($i = 0; $i < strlen($input); ++$i) { - $character = $input[$i]; - $asciiCode = ord($character); - - if ($asciiCode < 128) { - $escapedValue .= $this->escapeAsciiCharacter($asciiCode); + $utf16 = mb_convert_encoding($input, 'UTF-16BE', 'UTF-8'); + $utf16len = strlen($utf16); + for ($i = 0; $i < $utf16len; $i += 2) { + $code = (ord($utf16[$i]) << 8) | ord($utf16[$i + 1]); + if ($code <= 0x7f) { + $escapedValue .= $this->escapeAsciiCharacter($code); } else { - if (0 == count($bytes)) { - if ($asciiCode < 224) { - $numberOfBytes = 2; - } elseif ($asciiCode < 240) { - $numberOfBytes = 3; - } elseif ($asciiCode < 248) { - $numberOfBytes = 4; - } - } - - $bytes[] = $asciiCode; - - if ($numberOfBytes == count($bytes)) { - if (4 == $numberOfBytes) { - $multibyteCode = ($bytes[0] % 8) * 262144 + ($bytes[1] % 64) * 4096 + ($bytes[2] % 64) * 64 + ($bytes[3] % 64); - } elseif (3 == $numberOfBytes) { - $multibyteCode = ($bytes[0] % 16) * 4096 + ($bytes[1] % 64) * 64 + ($bytes[2] % 64); - } else { - $multibyteCode = ($bytes[0] % 32) * 64 + ($bytes[1] % 64); - } - - if (65279 != $multibyteCode) { - $escapedValue .= $multibyteCode < 128 ? $this->escapeAsciiCharacter($multibyteCode) : $this->escapeMultibyteCharacter($multibyteCode); - } - - $numberOfBytes = 1; - $bytes = []; - } + $escapedValue .= $this->escapeMultibyteCharacter($code); } } diff --git a/src/PhpWord/Style/Language.php b/src/PhpWord/Style/Language.php index 641ed7b41e..b4887e2757 100644 --- a/src/PhpWord/Style/Language.php +++ b/src/PhpWord/Style/Language.php @@ -29,14 +29,32 @@ */ final class Language extends AbstractStyle { - const EN_US = 'en-US'; - const EN_US_ID = 1033; + const AR_SA = 'ar-SA'; + const AR_SA_ID = 1025; + + const BG_BG = 'bg-BG'; + const BG_BG_ID = 1026; + + const CS_CZ = 'cs-CZ'; + const CS_CZ_ID = 1029; + + const DA_DK = 'da-DK'; + const DA_DK_ID = 1030; + + const DE_CH = 'de-CH'; + const DE_CH_ID = 2055; + + const DE_DE = 'de-DE'; + const DE_DE_ID = 1031; const EN_GB = 'en-GB'; const EN_GB_ID = 2057; - const FR_FR = 'fr-FR'; - const FR_FR_ID = 1036; + const EN_US = 'en-US'; + const EN_US_ID = 1033; + + const ES_ES = 'es-ES'; + const ES_ES_ID = 3082; const FR_BE = 'fr-BE'; const FR_BE_ID = 2060; @@ -44,51 +62,99 @@ final class Language extends AbstractStyle const FR_CH = 'fr-CH'; const FR_CH_ID = 4108; - const ES_ES = 'es-ES'; - const ES_ES_ID = 3082; - - const DE_DE = 'de-DE'; - const DE_DE_ID = 1031; - - const DE_CH = 'de-CH'; - const DE_CH_ID = 2055; + const FR_FR = 'fr-FR'; + const FR_FR_ID = 1036; const HE_IL = 'he-IL'; const HE_IL_ID = 1037; - const IT_IT = 'it-IT'; - const IT_IT_ID = 1040; + const HI_IN = 'hi-IN'; + const HI_IN_ID = 1081; + + const HR_HR = 'hr-HR'; + const HR_HR_ID = 1050; + + const HU_HU = 'hu-HU'; + const HU_HU_ID = 1038; + + const ID_ID = 'id-ID'; + const ID_ID_ID = 1057; const IT_CH = 'it-CH'; const IT_CH_ID = 2064; + const IT_IT = 'it-IT'; + const IT_IT_ID = 1040; + const JA_JP = 'ja-JP'; const JA_JP_ID = 1041; + const KK_KZ = 'kk-KZ'; + const KK_KK_ID = 1087; + const KO_KR = 'ko-KR'; const KO_KR_ID = 1042; - const ZH_CN = 'zh-CN'; - const ZH_CN_ID = 2052; + const LT_LT = 'lt-LT'; + const LT_LT_ID = 1063; - const HI_IN = 'hi-IN'; - const HI_IN_ID = 1081; + const LV_LV = 'lv-LV'; + const LV_LV_ID = 1062; - const PT_BR = 'pt-BR'; - const PT_BR_ID = 1046; + const MS_MY = 'ms-MY'; + const MS_MY_ID = 1086; + + const NB_NO = 'nb-NO'; + const NB_NO_ID = 1044; const NL_NL = 'nl-NL'; const NL_NL_ID = 1043; + const PL_PL = 'pl-PL'; + const PL_PL_ID = 1045; + + const PT_BR = 'pt-BR'; + const PT_BR_ID = 1046; + + const PT_PT = 'pt-PT'; + const PT_PT_ID = 2070; + + const RO_RO = 'ro-RO'; + const RO_RO_ID = 1048; + + const SL_SI = 'sl-SI'; + const SL_SI_ID = 1046; + + const SK_SK = 'sk-SK'; + const SK_SK_ID = 1051; + + const SR_LATN_RS = 'sr-latn-RS'; + const SR_LATN_RS_ID = 2074; + const SV_SE = 'sv-SE'; const SV_SE_ID = 1053; + const TH_TH = 'th-TH'; + const TH_TH_ID = 1054; + + const TR_TR = 'tr-TR'; + const TR_TR_ID = 1055; + const UK_UA = 'uk-UA'; const UK_UA_ID = 1058; const RU_RU = 'ru-RU'; const RU_RU_ID = 1049; + const VI_VN = 'vi-VN'; + const VI_VN_ID = 1066; + + const ZH_CN = 'zh-CN'; + const ZH_CN_ID = 2052; + + const ZH_TW = 'zh-TW'; + const ZH_TW_ID = 1028; + /** * Language ID, used for RTF document generation. * @@ -122,16 +188,20 @@ final class Language extends AbstractStyle /** * Constructor. */ - public function __construct(?string $latin = null, ?string $eastAsia = null, ?string $bidirectional = null) + public function __construct(string $latin = '', string $eastAsia = '', string $bidirectional = '', int $langId = 0) { + $this->langId = $langId; if (!empty($latin)) { $this->setLatin($latin); + $this->convertLangId($latin); } if (!empty($eastAsia)) { $this->setEastAsia($eastAsia); + $this->convertLangId($eastAsia); } if (!empty($bidirectional)) { $this->setBidirectional($bidirectional); + $this->convertLangId($bidirectional); } } @@ -141,7 +211,7 @@ public function __construct(?string $latin = null, ?string $eastAsia = null, ?st * @param string $latin * The value for the latin language */ - public function setLatin(?string $latin): self + public function setLatin(string $latin): self { $this->latin = $this->validateLocale($latin); @@ -236,26 +306,39 @@ public function getBidirectional() /** * Validates that the language passed is in the format xx-xx. * - * @param string $locale + * @param ?string $locale + * @param bool $throw * - * @return string + * @return ?string */ - private function validateLocale($locale) + private function validateLocale($locale, $throw = true) { - if ($locale !== null) { - $locale = str_replace('_', '-', $locale); + if ($locale === null) { + return null; } + $locale = str_replace('_', '-', $locale); - if ($locale !== null && strlen($locale) === 2) { + if (strlen($locale) === 2) { return strtolower($locale) . '-' . strtoupper($locale); } if ($locale === 'und') { - return 'en-EN'; + return 'en-GB'; } - if ($locale !== null && $locale !== 'zxx' && strstr($locale, '-') === false) { + if ($throw && $locale !== 'zxx' && strstr($locale, '-') === false) { throw new InvalidArgumentException($locale . ' is not a valid language code'); } return $locale; } + + private function convertLangId(string $locale): void + { + if ($this->langId === 0 && $locale !== '') { + $locale = $this->validateLocale($locale, false); + $locale = strtoupper(str_replace('-', '_', $locale)) . '_ID'; + if (defined("self::$locale")) { + $this->langId = constant("self::$locale"); + } + } + } } diff --git a/src/PhpWord/Writer/RTF/Element/AbstractElement.php b/src/PhpWord/Writer/RTF/Element/AbstractElement.php index ca669c1f79..effadc9c6e 100644 --- a/src/PhpWord/Writer/RTF/Element/AbstractElement.php +++ b/src/PhpWord/Writer/RTF/Element/AbstractElement.php @@ -20,8 +20,6 @@ use PhpOffice\PhpWord\Element\AbstractElement as Element; use PhpOffice\PhpWord\Escaper\Rtf; -use PhpOffice\PhpWord\Settings; -use PhpOffice\PhpWord\Shared\Text as SharedText; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font as FontStyle; use PhpOffice\PhpWord\Style\Paragraph as ParagraphStyle; @@ -67,14 +65,14 @@ abstract public function write(); /** * Font style. * - * @var FontStyle + * @var null|FontStyle|string */ protected $fontStyle; /** * Paragraph style. * - * @var ParagraphStyle + * @var null|ParagraphStyle|string */ protected $paragraphStyle; @@ -106,7 +104,9 @@ protected function getStyles(): void if (method_exists($element, 'getFontStyle')) { $this->fontStyle = $element->getFontStyle(); if (is_string($this->fontStyle)) { - $this->fontStyle = Style::getStyle($this->fontStyle); + /** @var FontStyle */ + $temp = Style::getStyle($this->fontStyle); + $this->fontStyle = $temp; } elseif ($this->fontStyle !== null && is_string($this->fontStyle->getStyleName())) { $temp = Style::getStyle($this->fontStyle->getStyleName()); if ($temp instanceof FontStyle) { @@ -119,7 +119,9 @@ protected function getStyles(): void if (method_exists($element, 'getParagraphStyle')) { $this->paragraphStyle = $element->getParagraphStyle(); if (is_string($this->paragraphStyle)) { - $this->paragraphStyle = Style::getStyle($this->paragraphStyle); + /** @var null|ParagraphStyle */ + $temp = Style::getStyle($this->paragraphStyle); + $this->paragraphStyle = $temp; } if ($this->paragraphStyle !== null && !$this->withoutP) { @@ -162,11 +164,7 @@ protected function writeOpening() */ protected function writeText($text) { - if (Settings::isOutputEscapingEnabled()) { - return $this->escaper->escape($text); - } - - return SharedText::toUnicode($text); // todo: replace with `return $text;` later. + return $this->escaper->escape($text); } /** @@ -202,13 +200,25 @@ protected function writeFontStyle() if ($this->fontStyle->getColor() != null) { $colorIndex = array_search($this->fontStyle->getColor(), $parentWriter->getColorTable()); if ($colorIndex !== false) { - $styleWriter->setColorIndex($colorIndex + 1); + $styleWriter->setColorIndex((int) $colorIndex + 1); + } + } + if ($this->fontStyle->getFgColor() != null) { + $colorIndex = array_search($this->fontStyle->getFgColor(), $parentWriter->getColorTable()); + if ($colorIndex !== false) { + $styleWriter->setFgColorIndex((int) $colorIndex + 1); + } + } + if ($this->fontStyle->getBgColor() != null) { + $colorIndex = array_search($this->fontStyle->getBgColor(), $parentWriter->getColorTable()); + if ($colorIndex !== false) { + $styleWriter->setBgColorIndex((int) $colorIndex + 1); } } if ($this->fontStyle->getName() != null) { $fontIndex = array_search($this->fontStyle->getName(), $parentWriter->getFontTable()); if ($fontIndex !== false) { - $styleWriter->setNameIndex($fontIndex); + $styleWriter->setNameIndex((int) $fontIndex); } } diff --git a/src/PhpWord/Writer/RTF/Element/Text.php b/src/PhpWord/Writer/RTF/Element/Text.php index 00e7af677d..4570a5d4a2 100644 --- a/src/PhpWord/Writer/RTF/Element/Text.php +++ b/src/PhpWord/Writer/RTF/Element/Text.php @@ -51,15 +51,6 @@ public function write() $content .= '\strike '; } } - if ($this->fontStyle !== null && method_exists($this->fontStyle, 'getFgColor')) { - $fgColor = $this->fontStyle->getFgColor(); - if ($fgColor !== null) { - $fgColorIndex = array_search($fgColor, $this->parentWriter->getColorTable()); - if ($fgColorIndex !== false) { - $content .= '\highlight' . ++$fgColorIndex . ' '; - } - } - } $content .= $this->writeText($element->getText()); $content .= '}'; $content .= $this->writeClosing(); diff --git a/src/PhpWord/Writer/RTF/Element/TextRun.php b/src/PhpWord/Writer/RTF/Element/TextRun.php index 02816305c4..68606b1f3a 100644 --- a/src/PhpWord/Writer/RTF/Element/TextRun.php +++ b/src/PhpWord/Writer/RTF/Element/TextRun.php @@ -34,7 +34,7 @@ public function write() { $writer = new Container($this->parentWriter, $this->element); $this->getStyles(); - if ($this->paragraphStyle !== null && $this->paragraphStyle->hasPageBreakBefore()) { + if ($this->paragraphStyle instanceof \PhpOffice\PhpWord\Style\Paragraph && $this->paragraphStyle->hasPageBreakBefore()) { $sect = $this->element->getParent(); if ($sect instanceof \PhpOffice\PhpWord\Element\Section) { $elems = $sect->getElements(); diff --git a/src/PhpWord/Writer/RTF/Part/Document.php b/src/PhpWord/Writer/RTF/Part/Document.php index 484393477d..8c7207af76 100644 --- a/src/PhpWord/Writer/RTF/Part/Document.php +++ b/src/PhpWord/Writer/RTF/Part/Document.php @@ -78,12 +78,14 @@ private function writeInfo() $method = 'get' . $propertyMethod; $value = $docProps->$method(); - if (!in_array($property, $dateFields) && Settings::isOutputEscapingEnabled()) { + if (!in_array($property, $dateFields)) { $value = $this->escaper->escape($value); } $value = in_array($property, $dateFields) ? $this->getDateValue($value) : $value; - $content .= "{\\{$property} {$value}}"; + if ($value !== '') { + $content .= "{\\{$property} {$value}}"; + } } $content .= '}'; $content .= PHP_EOL; diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index 05575acc23..75a7661082 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -192,6 +192,7 @@ private function registerFont(): void { $phpWord = $this->getParentWriter()->getPhpWord(); $this->fontTable[] = Settings::getDefaultFontName(); + $this->colorTable[] = Settings::getDefaultFontColor(); // Search named styles $styles = Style::getStyles(); @@ -255,6 +256,7 @@ 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); + $this->registerTableItem($this->colorTable, $style->getBgColor(), $defaultColor); return; } diff --git a/src/PhpWord/Writer/RTF/Style/Font.php b/src/PhpWord/Writer/RTF/Style/Font.php index f343c0502f..d10f86c8d3 100644 --- a/src/PhpWord/Writer/RTF/Style/Font.php +++ b/src/PhpWord/Writer/RTF/Style/Font.php @@ -37,6 +37,16 @@ class Font extends AbstractStyle */ private $colorIndex = 0; + /** + * @var int Font foreground color index + */ + private $fgColorIndex = 0; + + /** + * @var int Font background color index + */ + private $bgColorIndex = 0; + /** * Write style. * @@ -49,22 +59,97 @@ public function write() return ''; } + // Underline Keywords + $underlines = [ + FontStyle::UNDERLINE_DASH => '\uldash', + FontStyle::UNDERLINE_DASHHEAVY => '\ulthdash', + FontStyle::UNDERLINE_DASHLONG => '\ulldash', + FontStyle::UNDERLINE_DASHLONGHEAVY => '\ulthldash', + FontStyle::UNDERLINE_DOUBLE => '\uldb', + FontStyle::UNDERLINE_DOTDASH => '\uldashd', + FontStyle::UNDERLINE_DOTDASHHEAVY => '\ulthdashd', + FontStyle::UNDERLINE_DOTDOTDASH => '\uldashdd', + FontStyle::UNDERLINE_DOTDOTDASHHEAVY => '\ulthdashdd', + FontStyle::UNDERLINE_DOTTED => '\uld', + FontStyle::UNDERLINE_DOTTEDHEAVY => '\ulthd', + FontStyle::UNDERLINE_HEAVY => '\ulth', + FontStyle::UNDERLINE_SINGLE => '\ul', + FontStyle::UNDERLINE_WAVY => '\ulwave', + FontStyle::UNDERLINE_WAVYDOUBLE => '\ululdbwave', + FontStyle::UNDERLINE_WAVYHEAVY => '\ulhwave', + FontStyle::UNDERLINE_WORDS => '\ulw', + ]; + $content = ''; - $content .= $this->getValueIf($style->isRTL(), '\rtlch'); - $content .= '\cf' . $this->colorIndex; - $content .= '\f' . $this->nameIndex; + // Font name/family + $content .= $this->getValueIf($style->getName() !== null, '\f' . $this->nameIndex); + + // Language + if ($style->getLang() !== null) { + if ($style->isNoProof()) { + $content .= $this->getValueIf($style->getLang()->getLangId() !== 0, '\langnp' . $style->getLang()->getLangId()); + } else { + $content .= $this->getValueIf($style->getLang()->getLangId() !== 0, '\lang' . $style->getLang()->getLangId()); + } + } - $size = $style->getSize(); - $content .= $this->getValueIf(is_numeric($size), '\fs' . round($size * 2)); + // Color + $content .= $this->getValueIf($style->getColor() !== null, '\cf' . $this->colorIndex); + // Size + $content .= $this->getValueIf($style->getSize() !== null, '\fs' . round($style->getSize() * 2)); + + // Bold, italic $content .= $this->getValueIf($style->isBold(), '\b'); + $content .= $this->getValueIf($style->isBold() === false, '\b0'); $content .= $this->getValueIf($style->isItalic(), '\i'); - $content .= $this->getValueIf($style->getUnderline() != FontStyle::UNDERLINE_NONE, '\ul'); + $content .= $this->getValueIf($style->isItalic() === false, '\i0'); + + // Strikethrough, double strikethrough $content .= $this->getValueIf($style->isStrikethrough(), '\strike'); + $content .= $this->getValueIf($style->isDoubleStrikethrough(), '\striked1'); + $content .= $this->getValueIf($style->isStrikethrough() === false && $style->isDoubleStrikethrough() === false, '\strike0'); + + // Small caps, all caps + $content .= $this->getValueIf($style->isSmallCaps(), '\scaps'); + $content .= $this->getValueIf($style->isAllCaps(), '\caps'); + + // Hidden text + $content .= $this->getValueIf($style->isHidden(), '\v'); + $content .= $this->getValueIf($style->isHidden() === false, '\v0'); + + // Underline + if (isset($underlines[$style->getUnderline()])) { + $content .= $underlines[$style->getUnderline()]; + } + + // Foreground color + $content .= $this->getValueIf($style->getFgColor() !== null, '\highlight' . $this->fgColorIndex); + + // Superscript/subscript $content .= $this->getValueIf($style->isSuperScript(), '\super'); $content .= $this->getValueIf($style->isSubScript(), '\sub'); - return $content . ' '; + // Spacing + $content .= $this->getValueIf($style->getScale() !== null, '\charscalex' . round($style->getScale() ?? 0)); + $content .= $this->getValueIf($style->getSpacing() !== null, '\expnd' . round($style->getSpacing() * 0.2)); + $content .= $this->getValueIf($style->getSpacing() !== null, '\expndtw' . round($style->getSpacing() ?? 0)); + $content .= $this->getValueIf($style->getKerning() !== null, '\kerning' . round($style->getKerning() * 2)); + + // noProof + $content .= $this->getValueIf($style->isNoProof(), '\noproof\lang1024'); + + // Background-Color + $content .= $this->getValueIf($style->getBgColor() !== null, '\chshdng0\chcbpat' . $this->bgColorIndex . '\cb' . $this->bgColorIndex); + + // RTL + $content .= $this->getValueIf($style->isRTL(), '\rtlch'); + $content .= $this->getValueIf($style->isRTL() === false, '\ltrch'); + + // Position + $content .= $this->getValueIf($style->getPosition() !== null, '\up' . $style->getPosition()); + + return ($content === '') ? '' : ("$content "); } /** @@ -86,4 +171,24 @@ public function setColorIndex($value = 0): void { $this->colorIndex = $value; } + + /** + * Set font foreground color index. + * + * @param int $value + */ + public function setFgColorIndex($value = 0): void + { + $this->fgColorIndex = $value; + } + + /** + * Set font background color index. + * + * @param int $value + */ + public function setBgColorIndex($value = 0): void + { + $this->bgColorIndex = $value; + } } diff --git a/tests/PhpWordTests/Escaper/RtfEscaper2Test.php b/tests/PhpWordTests/Escaper/RtfEscaper2Test.php index d65b543f21..49cd5ec4ab 100644 --- a/tests/PhpWordTests/Escaper/RtfEscaper2Test.php +++ b/tests/PhpWordTests/Escaper/RtfEscaper2Test.php @@ -23,12 +23,11 @@ */ class RtfEscaper2Test extends \PHPUnit\Framework\TestCase { - const HEADER = '\\pard\\nowidctlpar {\\cf0\\f0 '; + const HEADER = '\\pard\\nowidctlpar {'; const TRAILER = '}\\par'; public function escapestring($str) { - \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); $parentWriter = new \PhpOffice\PhpWord\Writer\RTF(); $element = new \PhpOffice\PhpWord\Element\Text($str); $txt = new \PhpOffice\PhpWord\Writer\RTF\Element\Text($parentWriter, $element); @@ -58,7 +57,7 @@ public function testSpecial(): void public function testAccent(): void { $str = 'Voilà - string with accented char'; - $expect = $this->expect('Voil\\uc0{\\u224} - string with accented char'); + $expect = $this->expect('Voil\\uc0\\u224 - string with accented char'); self::assertEquals($expect, $this->escapestring($str)); } @@ -68,7 +67,27 @@ public function testAccent(): void public function testHebrew(): void { $str = 'Hebrew - שלום'; - $expect = $this->expect('Hebrew - \\uc0{\\u1513}\\uc0{\\u1500}\\uc0{\\u1493}\\uc0{\\u1501}'); + $expect = $this->expect('Hebrew - \\uc0\\u1513 \\uc0\\u1500 \\uc0\\u1493 \\uc0\\u1501 '); + self::assertEquals($expect, $this->escapestring($str)); + } + + /** + * Test Chinese. + */ + public function test3ByteCharacters(): void + { + $str = 'Chinese - 耀老耂'; + $expect = $this->expect('Chinese - \\uc0\\u-32768 \\uc0\\u-32767 \\uc0\\u-32766 '); + self::assertEquals($expect, $this->escapestring($str)); + } + + /** + * Test Osmanya, not in BMP. + */ + public function testBeyondBmp(): void + { + $str = 'Osmanya - 𐐀𐐁𐐂'; + $expect = $this->expect('Osmanya - \uc0\u-10239 \uc0\u-9216 \uc0\u-10239 \uc0\u-9215 \uc0\u-10239 \uc0\u-9214 '); self::assertEquals($expect, $this->escapestring($str)); } diff --git a/tests/PhpWordTests/Escaper/RtfEscaper3Test.php b/tests/PhpWordTests/Escaper/RtfEscaper3Test.php index 1aebea52f0..1a4da4bc60 100644 --- a/tests/PhpWordTests/Escaper/RtfEscaper3Test.php +++ b/tests/PhpWordTests/Escaper/RtfEscaper3Test.php @@ -25,8 +25,8 @@ */ class RtfEscaper3Test extends \PHPUnit\Framework\TestCase { - const HEADER = '\\pard\\nowidctlpar \ql{\\cf0\\f0 '; - const HEADER_RTL = '\\pard\\nowidctlpar \qr{\\rtlch\\cf0\\f0 '; + const HEADER = '\\pard\\nowidctlpar \ql{\ltrch '; + const HEADER_RTL = '\\pard\\nowidctlpar \qr{\\rtlch '; const TRAILER = '}\\par'; protected function tearDown(): void @@ -36,7 +36,6 @@ protected function tearDown(): void public function escapestring(string $str): string { - Settings::setOutputEscapingEnabled(true); $parentWriter = new \PhpOffice\PhpWord\Writer\RTF(); $element = new \PhpOffice\PhpWord\Element\Text($str); $txt = new \PhpOffice\PhpWord\Writer\RTF\Element\Text($parentWriter, $element); @@ -68,7 +67,7 @@ public function testAccent(): void { Settings::setDefaultRtl(false); $str = 'Voilà - string with accented char'; - $expect = $this->expect('Voil\\uc0{\\u224} - string with accented char'); + $expect = $this->expect('Voil\\uc0\\u224 - string with accented char'); self::assertEquals($expect, $this->escapestring($str)); } @@ -79,7 +78,7 @@ public function testHebrew(): void { Settings::setDefaultRtl(true); $str = 'Hebrew - שלום'; - $expect = $this->expect('Hebrew - \\uc0{\\u1513}\\uc0{\\u1500}\\uc0{\\u1493}\\uc0{\\u1501}', true); + $expect = $this->expect('Hebrew - \\uc0\\u1513 \\uc0\\u1500 \\uc0\\u1493 \\uc0\\u1501 ', true); self::assertEquals($expect, $this->escapestring($str)); } diff --git a/tests/PhpWordTests/Style/LanguageTest.php b/tests/PhpWordTests/Style/LanguageTest.php index a237126ccb..81446473b6 100644 --- a/tests/PhpWordTests/Style/LanguageTest.php +++ b/tests/PhpWordTests/Style/LanguageTest.php @@ -20,7 +20,6 @@ use InvalidArgumentException; use PhpOffice\PhpWord\Style\Language; -use PHPUnit\Framework\Assert; /** * Test class for PhpOffice\PhpWord\Style\Language. @@ -31,39 +30,39 @@ class LanguageTest extends \PHPUnit\Framework\TestCase { public function testGetSetPropertiesInt(): void { - $object = new Language(); foreach ([ - 'langId' => [null, 1036], + 'LangId' => [0, 1036], ] as $property => $value) { + $object = new Language(); [$default, $expected] = $value; $get = "get{$property}"; $set = "set{$property}"; - self::assertEquals($default, $object->$get()); // Default value + self::assertSame($default, $object->$get()); // Default value $object->$set($expected); - self::assertEquals($expected, $object->$get()); // New value + self::assertSame($expected, $object->$get()); // New value } } public function testGetSetPropertiesString(): void { - $object = new Language(); foreach ([ - 'latin' => [null, 'fr-BE'], - 'eastAsia' => [null, 'ja-JP'], - 'bidirectional' => [null, 'ar-SA'], + 'Latin' => [null, 'fr-BE'], + 'EastAsia' => [null, 'ja-JP'], + 'Bidirectional' => [null, 'ar-SA'], ] as $property => $value) { + $object = new Language(); [$default, $expected] = $value; $get = "get{$property}"; $set = "set{$property}"; - self::assertEquals($default, $object->$get()); // Default value + self::assertSame($default, $object->$get()); // Default value $object->$set($expected); - self::assertEquals($expected, $object->$get()); // New value + self::assertSame($expected, $object->$get()); // New value } } @@ -73,6 +72,7 @@ public function testGetSetPropertiesString(): void public function testWrongLanguage(): void { $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('not a valid language code'); $language = new Language(); $language->setLatin('fra'); } @@ -83,20 +83,34 @@ public function testWrongLanguage(): void public function testShortLanguage(): void { //when - $language = new Language(); - $language->setLatin('fr'); + $language = new Language('fr'); //then - Assert::assertEquals('fr-FR', $language->getLatin()); + self::assertSame('fr-FR', $language->getLatin()); + self::assertSame(1036, $language->getLangId()); } public function testUndefined(): void { //when - $language = new Language(); - $language->setLatin('und'); + $language = new Language('und'); //then - Assert::assertEquals('en-EN', $language->getLatin()); + self::assertSame('en-GB', $language->getLatin()); + self::assertSame(2057, $language->getLangId()); + } + + public function testLangId(): void + { + $language = new Language('it-IT'); + self::assertSame(1040, $language->getLangId()); + $language = new Language('xt-IT'); + self::assertSame(0, $language->getLangId()); + $language = new Language('xt-IT', '', '', 1234); + self::assertSame(1234, $language->getLangId()); + $language = new Language('', 'hi-IN'); + self::assertSame(1081, $language->getLangId()); + $language = new Language('', '', 'he-IL'); + self::assertSame(1037, $language->getLangId()); } } diff --git a/tests/PhpWordTests/Writer/RTF/Element2Test.php b/tests/PhpWordTests/Writer/RTF/Element/Element2Test.php similarity index 78% rename from tests/PhpWordTests/Writer/RTF/Element2Test.php rename to tests/PhpWordTests/Writer/RTF/Element/Element2Test.php index 2220d93b68..cbd99599d3 100644 --- a/tests/PhpWordTests/Writer/RTF/Element2Test.php +++ b/tests/PhpWordTests/Writer/RTF/Element/Element2Test.php @@ -16,9 +16,8 @@ * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ -namespace PhpOffice\PhpWordTests\Writer\RTF; +namespace PhpOffice\PhpWordTests\Writer\RTF\Element; -use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Writer\RTF; use PhpOffice\PhpWord\Writer\RTF\Element\TextRun as WriterTextRun; use PhpOffice\PhpWord\Writer\RTF\Element\Title as WriterTitle; @@ -28,11 +27,6 @@ */ class Element2Test extends \PHPUnit\Framework\TestCase { - protected function tearDown(): void - { - Settings::setDefaultRtl(null); - } - /** @param WriterTextRun|WriterTitle $field */ public function removeCr($field): string { @@ -41,25 +35,23 @@ public function removeCr($field): string public function testTextRun(): void { - Settings::setDefaultRtl(false); $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\TextRun(); $element->addText('Hello '); $element->addText('there.'); $textrun = new WriterTextRun($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\ql{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; + $expect = "\\pard\\nowidctlpar {{Hello }{there.}}\\par\n"; self::assertEquals($expect, $this->removeCr($textrun)); } public function testTextRunParagraphStyle(): void { - Settings::setDefaultRtl(false); $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\TextRun(['spaceBefore' => 0, 'spaceAfter' => 0]); $element->addText('Hello '); $element->addText('there.'); $textrun = new WriterTextRun($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\ql\\sb0\\sa0{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; + $expect = "\\pard\\nowidctlpar \\sb0\\sa0{{Hello }{there.}}\\par\n"; self::assertEquals($expect, $this->removeCr($textrun)); } @@ -67,13 +59,11 @@ public function testTitle(): void { $parentWriter = new RTF(); $phpWord = new \PhpOffice\PhpWord\PhpWord(); - Settings::setDefaultRtl(false); $phpWord->addTitleStyle(1, [], ['spaceBefore' => 0, 'spaceAfter' => 0]); $section = $phpWord->addSection(); $element = $section->addTitle('First Heading', 1); $elwrite = new WriterTitle($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\ql\\sb0\\sa0{\\outlinelevel0{\\cf0\\f0 First Heading}\\par\n}"; + $expect = "\\pard\\nowidctlpar \\sb0\\sa0{\\outlinelevel0{First Heading}\\par\n}"; self::assertEquals($expect, $this->removeCr($elwrite)); - Settings::setDefaultRtl(null); } } diff --git a/tests/PhpWordTests/Writer/RTF/ElementTest.php b/tests/PhpWordTests/Writer/RTF/Element/ElementTest.php similarity index 89% rename from tests/PhpWordTests/Writer/RTF/ElementTest.php rename to tests/PhpWordTests/Writer/RTF/Element/ElementTest.php index 36504a18f8..f59f420705 100644 --- a/tests/PhpWordTests/Writer/RTF/ElementTest.php +++ b/tests/PhpWordTests/Writer/RTF/Element/ElementTest.php @@ -16,7 +16,7 @@ * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ -namespace PhpOffice\PhpWordTests\Writer\RTF; +namespace PhpOffice\PhpWordTests\Writer\RTF\Element; use PhpOffice\PhpWord\Writer\RTF; @@ -25,6 +25,11 @@ */ class ElementTest extends \PHPUnit\Framework\TestCase { + /** + * @param RTF\Element\Field|RTF\Element\Table|RTF\Element\TextRun|RTF\Element\Title $field + * + * @return string + */ public function removeCr($field) { return str_replace("\r\n", "\n", $field->write()); @@ -121,25 +126,25 @@ public function testTable(): void '\\pard', "\\trowd \\cellx$width \\cellx$width2 ", '\\intbl', - '{\\cf0\\f0 1}\\par', + '{1}\\par', '\\cell', '\\intbl', - '{\\cf0\\f0 2}\\par', + '{2}\\par', '\\cell', '\\row', "\\trowd \\cellx$width \\cellx$width2 ", '\\intbl', - '{\\cf0\\f0 3}\\par', + '{3}\\par', '\\cell', '\\intbl', - '{\\cf0\\f0 4}\par', + '{4}\\par', '\\cell', '\\row', '\\pard', '', ]); - self::assertEquals($expect, $this->removeCr($table)); + self::assertSame($expect, $this->removeCr($table)); } public function testTextRun(): void @@ -149,8 +154,8 @@ public function testTextRun(): void $element->addText('Hello '); $element->addText('there.'); $textrun = new RTF\Element\TextRun($parentWriter, $element); - $expect = "\\pard\\nowidctlpar {{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; - self::assertEquals($expect, $this->removeCr($textrun)); + $expect = "\\pard\\nowidctlpar {{Hello }{there.}}\\par\n"; + self::assertSame($expect, $this->removeCr($textrun)); } public function testTextRunParagraphStyle(): void @@ -160,8 +165,8 @@ public function testTextRunParagraphStyle(): void $element->addText('Hello '); $element->addText('there.'); $textrun = new RTF\Element\TextRun($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sb0\\sa0{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; - self::assertEquals($expect, $this->removeCr($textrun)); + $expect = "\\pard\\nowidctlpar \\sb0\\sa0{{Hello }{there.}}\\par\n"; + self::assertSame($expect, $this->removeCr($textrun)); } public function testTitle(): void @@ -172,8 +177,8 @@ public function testTitle(): void $section = $phpWord->addSection(); $element = $section->addTitle('First Heading', 1); $elwrite = new RTF\Element\Title($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sb0\\sa0{\\outlinelevel0{\\cf0\\f0 First Heading}\\par\n}"; - self::assertEquals($expect, $this->removeCr($elwrite)); + $expect = "\\pard\\nowidctlpar \\sb0\\sa0{\\outlinelevel0{First Heading}\\par\n}"; + self::assertSame($expect, $this->removeCr($elwrite)); } public function testRuby(): void @@ -189,7 +194,7 @@ public function testRuby(): void $textrun = new RTF\Element\TextRun($parentWriter, $element); $expect = "\\pard\\nowidctlpar {{base text (ruby)}}\\par\n"; - self::assertEquals($expect, $this->removeCr($textrun)); + self::assertSame($expect, $this->removeCr($textrun)); } public function testRubyTitle(): void @@ -213,7 +218,7 @@ public function testRubyTitle(): void $element = $section->addTitle($textRun, 1); $elwrite = new RTF\Element\Title($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sb0\\sa2{\\outlinelevel0{\\cf0\\f0\\fs48\\b base text (ruby)}\\par\n}"; - self::assertEquals($expect, $this->removeCr($elwrite)); + $expect = "\\pard\\nowidctlpar \\sb0\\sa2{\\outlinelevel0{\\cf0\\fs48\\b base text (ruby)}\\par\n}"; + self::assertSame($expect, $this->removeCr($elwrite)); } } diff --git a/tests/PhpWordTests/Writer/RTF/Element/TableTest.php b/tests/PhpWordTests/Writer/RTF/Element/TableTest.php index 7c1ceac68e..38abf782d1 100644 --- a/tests/PhpWordTests/Writer/RTF/Element/TableTest.php +++ b/tests/PhpWordTests/Writer/RTF/Element/TableTest.php @@ -19,7 +19,6 @@ namespace PhpOffice\PhpWordTests\Writer\RTF\Element; use PhpOffice\PhpWord\Element\Table; -use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Border; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Writer\RTF; @@ -27,11 +26,6 @@ class TableTest extends \PHPUnit\Framework\TestCase { - protected function tearDown(): void - { - Settings::setDefaultRtl(null); - } - public function removeCr(WriterTable $field): string { return str_replace("\r\n", "\n", $field->write()); @@ -39,7 +33,6 @@ public function removeCr(WriterTable $field): string public function testTable(): void { - Settings::setDefaultRtl(false); $parentWriter = new RTF(); $element = new Table(); $width = 100; @@ -59,18 +52,18 @@ public function testTable(): void '\\pard', "\\trowd \\cellx$width \\cellx$width2 ", '\\intbl', - '\\ql{\\cf0\\f0 1}\\par', + '{1}\\par', '\\cell', '\\intbl', - '{\\cf0\\f0 2}\\par', + '{2}\\par', '\\cell', '\\row', "\\trowd \\cellx$width \\cellx$width2 ", '\\intbl', - '\\ql{\\cf0\\f0 3}\\par', + '{3}\\par', '\\cell', '\\intbl', - '{\\cf0\\f0 4}\par', + '{4}\\par', '\\cell', '\\row', '\\pard', @@ -84,7 +77,6 @@ public function testTableStyle(): void { $width = 100; - Settings::setDefaultRtl(false); $parentWriter = new RTF(); Style::addTableStyle('TableStyle', ['borderSize' => 6, 'borderColor' => '006699']); @@ -102,7 +94,7 @@ public function testTableStyle(): void '\\clbrdrr\\brdrs\\brdrw2\\brdrcf0', "\\cellx$width ", '\\intbl', - '\\ql{\\cf0\\f0 1}\\par', + '{1}\\par', '\\cell', '\\row', '\\pard', @@ -116,7 +108,6 @@ public function testTableStyleNotExisting(): void { $width = 100; - Settings::setDefaultRtl(false); $parentWriter = new RTF(); $element = new Table('TableStyleNotExisting'); @@ -128,7 +119,7 @@ public function testTableStyleNotExisting(): void '\\pard', "\\trowd \\cellx$width ", '\\intbl', - '\\ql{\\cf0\\f0 1}\\par', + '{1}\\par', '\\cell', '\\row', '\\pard', @@ -142,7 +133,6 @@ public function testTableCellStyle(): void { $width = 100; - Settings::setDefaultRtl(false); $parentWriter = new RTF(); $element = new Table(); @@ -158,7 +148,7 @@ public function testTableCellStyle(): void '\\clbrdrr\\brdrdot\\brdrw2\\brdrcf0', "\\cellx$width ", '\\intbl', - '\\ql{\\cf0\\f0 1}\\par', + '{1}\\par', '\\cell', '\\row', '\\pard', diff --git a/tests/PhpWordTests/Writer/RTF/Sample11Test.php b/tests/PhpWordTests/Writer/RTF/Sample11Test.php index 6e8c2a516b..7b85399835 100644 --- a/tests/PhpWordTests/Writer/RTF/Sample11Test.php +++ b/tests/PhpWordTests/Writer/RTF/Sample11Test.php @@ -34,9 +34,9 @@ public function testSample11(): void $phpWord = IOFactory::load($source); $writer = new RTF($phpWord); $content = $writer->getContent(); - $expected = '{\colortbl;\red255\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;\red0\green176\blue80;\red255\green255\blue0;}'; + $expected = '{\colortbl;\red0\green0\blue0;\red255\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;\red0\green176\blue80;\red255\green255\blue0;}'; self::assertStringContainsString($expected, $content); - $expected = '\highlight5 highlighted'; + $expected = '\highlight6 highlighted'; self::assertStringContainsString($expected, $content); $expected = '\strike even '; self::assertStringContainsString($expected, $content); @@ -51,7 +51,9 @@ public function testBorderColor(): void ->setBorderColor('FF00FF'); $writer = new RTF($phpWord); $content = $writer->getContent(); - $expected = '{\colortbl;\red255\green0\blue255;}'; + $expected = '{\colortbl;\red0\green0\blue0;\red255\green0\blue255;}'; + self::assertStringContainsString($expected, $content); + $expected = '\brdrcf2'; self::assertStringContainsString($expected, $content); } @@ -81,11 +83,11 @@ public function testFooters(): void $section->addText('This should be page 5'); $writer = new RTF($phpWord); $content = $writer->getContent(); - $expected = '{\footerf\pard\nowidctlpar {{\cf0\f0 First Footer}}\par'; + $expected = '{\footerf\pard\nowidctlpar {{First Footer}}\par'; self::assertStringContainsString($expected, $content); - $expected = '{\footerl\pard\nowidctlpar {{\cf0\f0 Even Footer}}\par'; + $expected = '{\footerl\pard\nowidctlpar {{Even Footer}}\par'; self::assertStringContainsString($expected, $content); - $expected = '{\footerr\pard\nowidctlpar {{\cf0\f0 Odd Footer}}\par'; + $expected = '{\footerr\pard\nowidctlpar {{Odd Footer}}\par'; self::assertStringContainsString($expected, $content); } @@ -105,9 +107,9 @@ public function testPageBreakBeforeTextRun(): void $textRun3->addText('Section 2 Paragraph 2'); $writer = new RTF($phpWord); $content = $writer->getContent(); - $expected = '\pard\nowidctlpar {{\cf0\f0 Section 2 Paragraph 1}}\par'; + $expected = '\pard\nowidctlpar {{Section 2 Paragraph 1}}\par'; self::assertStringContainsString($expected, $content, 'no page break'); - $expected = '\pard\nowidctlpar \page{{\cf0\f0 Section 2 Paragraph 2}}\par'; + $expected = '\pard\nowidctlpar \page{{Section 2 Paragraph 2}}\par'; self::assertStringContainsString($expected, $content, 'page break'); } @@ -128,9 +130,9 @@ public function testPageBreakBeforeTitle(): void $section2->addTitle('Heading1 with pbb not first element in section', 1); $writer = new RTF($phpWord); $content = $writer->getContent(); - $expected = '\pard\nowidctlpar {\outlinelevel0{\cf0\f0\b Heading1 with pbb first element in section}'; + $expected = '\pard\nowidctlpar {\outlinelevel0{\b Heading1 with pbb first element in section}'; self::assertStringContainsString($expected, $content, 'no page break'); - $expected = '\pard\nowidctlpar \page{\outlinelevel0{\cf0\f0\b Heading1 with pbb not first element in section}'; + $expected = '\pard\nowidctlpar \page{\outlinelevel0{\b Heading1 with pbb not first element in section}'; self::assertStringContainsString($expected, $content, 'page break'); } } diff --git a/tests/PhpWordTests/Writer/RTF/Style/FontTest.php b/tests/PhpWordTests/Writer/RTF/Style/FontTest.php new file mode 100644 index 0000000000..fc3bf10b46 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/FontTest.php @@ -0,0 +1,257 @@ +write()); + } + + /** + * Test font and color. + * See page 131 of RTF Specification 1.9.1 for Font (Character). + * See page 142 of RTF Specification 1.9.1 for Highlighting. + */ + public function testFontColor(): void + { + $parentWriter = new RTF(); + $style = new FontStyle(); + $writer = new FontWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setName('Times New Roman'); + $style->setFallbackFont('serif'); // currently does nothing in RTF, see pg. 18 of spec for implementation + $style->setSize(24); + $style->setColor($style::FGCOLOR_YELLOW); + $style->setFgColor($style::FGCOLOR_RED); + $style->setBgColor('#123456'); + $expect = '\f0\cf0\fs48\highlight0\chshdng0\chcbpat0\cb0 '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test font and color after registering header tables. + */ + public function testFontColorRegistered(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $parentWriter = new RTF($phpWord); + $style = new FontStyle(); + $element = new TextElement(); + $writer = new TextWriter($parentWriter, $element, true); + + $style->setName('Times New Roman'); + $style->setFallbackFont('serif'); // currently does nothing in RTF, see pg. 18 of spec for implementation + $style->setSize(24); + $style->setColor($style::FGCOLOR_YELLOW); + $style->setFgColor($style::FGCOLOR_RED); + $style->setBgColor('#123456'); + + $phpWord->addFontStyle('style1', $style); + $parentWriter->getWriterPart('Header')->write(); + + $element->setText('Test'); + $element->setFontStyle($style); + + $expect = '{\f1\cf2\fs48\highlight3\chshdng0\chcbpat4\cb4 Test}'; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test formatting. + * See page 130-133 of RTF Specification 1.9.1 for Font (Character). + */ + public function testFontFormatting(): void + { + $parentWriter = new RTF(); + $style = new FontStyle(); + $writer = new FontWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setAllCaps(true); + $style->setBold(true); + $style->setdoubleStrikethrough(true); + $style->setHidden(true); + $style->setItalic(true); + $style->setNoProof(true); + $style->setSubScript(true); + $expect = '\b\i\striked1\caps\v\sub\noproof\lang1024 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setSmallCaps(true); + $style->setStrikethrough(true); + $style->setSuperScript(true); + $style->setNoProof(false); + $expect = '\b\i\strike\scaps\v\super '; + self::assertEquals($expect, $this->removeCr($writer)); + + // Disable styles (in case default is enabled) + $style->setBold(false); + $style->setHidden(false); + $style->setItalic(false); + $style->setSmallCaps(false); // should display \caps0\scaps0; currently doesn't work + $style->setStrikethrough(false); + $style->setSuperScript(false); // should display \nosupersub; currently doesn't work + $expect = '\b0\i0\strike0\v0 '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test underline. + * See page 132-133 of RTF Specification 1.9.1 for Formatting. + */ + public function testFontUnderline(): void + { + $parentWriter = new RTF(); + $style = new FontStyle(); + $writer = new FontWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setUnderline($style::UNDERLINE_DASH); + $expect = '\uldash '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DASHHEAVY); + $expect = '\ulthdash '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DASHLONG); + $expect = '\ulldash '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DASHLONGHEAVY); + $expect = '\ulthldash '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DOUBLE); + $expect = '\uldb '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DOTDASH); + $expect = '\uldashd '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DOTDASHHEAVY); + $expect = '\ulthdashd '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DOTDOTDASH); + $expect = '\uldashdd '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DOTDOTDASHHEAVY); + $expect = '\ulthdashdd '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DOTTED); + $expect = '\uld '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_DOTTEDHEAVY); + $expect = '\ulthd '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_HEAVY); + $expect = '\ulth '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_SINGLE); + $expect = '\ul '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_WAVY); + $expect = '\ulwave '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_WAVYDOUBLE); + $expect = '\ululdbwave '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_WAVYHEAVY); + $expect = '\ulhwave '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_WORDS); + $expect = '\ulw '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setUnderline($style::UNDERLINE_NONE); // should display \ulnone\ul0; currently doesn't work + $expect = ''; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test language. + * See page 132 of RTF Specification 1.9.1 for Spacing. + */ + public function testFontLang(): void + { + $parentWriter = new RTF(); + $style = new FontStyle(); + $writer = new FontWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setRTL(true); + $style->setLang(Language::KO_KR); + $expect = '\lang1042\rtlch '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setRTL(false); + $style->setLang(Language::EN_US); + $expect = '\lang1033\ltrch '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test font spacing settings. + */ + public function testFontSpacing(): void + { + $parentWriter = new RTF(); + $style = new FontStyle(); + $writer = new FontWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setScale(5); + $style->setSpacing(4); + $style->setKerning(100); + $style->setPosition(10); + $expect = '\charscalex5\expnd1\expndtw4\kerning200\up10 '; + self::assertEquals($expect, $this->removeCr($writer)); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Style/RtlTest.php b/tests/PhpWordTests/Writer/RTF/Style/RtlTest.php index 9528f7a1af..2c87ae8fb0 100644 --- a/tests/PhpWordTests/Writer/RTF/Style/RtlTest.php +++ b/tests/PhpWordTests/Writer/RTF/Style/RtlTest.php @@ -44,7 +44,7 @@ public function testRTL(): void $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\Text('אב גד', ['RTL' => true]); $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar {\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; + $expect = "\\pard\\nowidctlpar {\\rtlch \\uc0\\u1488 \\uc0\\u1489 \\uc0\\u1490 \\uc0\\u1491 }\\par\n"; self::assertEquals($expect, $this->removeCr($text)); } @@ -54,7 +54,7 @@ public function testRTL2(): void $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\Text('אב גד'); $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\qr{\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; + $expect = "\\pard\\nowidctlpar \\qr{\\rtlch \\uc0\\u1488 \\uc0\\u1489 \\uc0\\u1490 \\uc0\\u1491 }\\par\n"; self::assertEquals($expect, $this->removeCr($text)); } @@ -64,7 +64,7 @@ public function testPageBreakLineHeight2(): void $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\ql\\sl259\\slmult1\\page{\\cf0\\f0 New page}\\par\n"; + $expect = "\\pard\\nowidctlpar \\ql\\sl259\\slmult1\\page{\\ltrch New page}\\par\n"; self::assertEquals($expect, $this->removeCr($text)); } } diff --git a/tests/PhpWordTests/Writer/RTF/StyleTest.php b/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php similarity index 95% rename from tests/PhpWordTests/Writer/RTF/StyleTest.php rename to tests/PhpWordTests/Writer/RTF/Style/StyleTest.php index f586b55209..b993c0e5e3 100644 --- a/tests/PhpWordTests/Writer/RTF/StyleTest.php +++ b/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php @@ -26,6 +26,11 @@ */ class StyleTest extends \PHPUnit\Framework\TestCase { + /** + * @param RTF\Element\Text|RTF\Style\Section $field + * + * @return string + */ public function removeCr($field) { return str_replace("\r\n", "\n", $field->write()); @@ -82,7 +87,7 @@ public function testPageBreakLineHeight(): void $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sl259\\slmult1\\page{\\cf0\\f0 New page}\\par\n"; + $expect = "\\pard\\nowidctlpar \\sl259\\slmult1\\page{New page}\\par\n"; self::assertEquals($expect, $this->removeCr($text)); } From fffe56d80a82e9b58180edb71f8d258a6e7c5f99 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 18 Nov 2025 16:11:03 -0800 Subject: [PATCH 130/153] Phpstan 7.2/7.3/7.4 --- src/PhpWord/Escaper/Rtf.php | 2 +- src/PhpWord/Writer/RTF/Style/Font.php | 2 ++ tests/PhpWordTests/Writer/RTF/Style/FontTest.php | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/PhpWord/Escaper/Rtf.php b/src/PhpWord/Escaper/Rtf.php index f7cd22a48f..e43dbd860a 100644 --- a/src/PhpWord/Escaper/Rtf.php +++ b/src/PhpWord/Escaper/Rtf.php @@ -56,7 +56,7 @@ protected function escapeMultibyteCharacter($code) protected function escapeSingleValue($input) { $escapedValue = ''; - $utf16 = mb_convert_encoding($input, 'UTF-16BE', 'UTF-8'); + $utf16 = (string) mb_convert_encoding($input, 'UTF-16BE', 'UTF-8'); $utf16len = strlen($utf16); for ($i = 0; $i < $utf16len; $i += 2) { $code = (ord($utf16[$i]) << 8) | ord($utf16[$i + 1]); diff --git a/src/PhpWord/Writer/RTF/Style/Font.php b/src/PhpWord/Writer/RTF/Style/Font.php index d10f86c8d3..a78dd3d811 100644 --- a/src/PhpWord/Writer/RTF/Style/Font.php +++ b/src/PhpWord/Writer/RTF/Style/Font.php @@ -137,6 +137,8 @@ public function write() $content .= $this->getValueIf($style->getKerning() !== null, '\kerning' . round($style->getKerning() * 2)); // noProof + // This is also specified above as \\langnp{$langId} + // Not sure why, or if, both are needed. $content .= $this->getValueIf($style->isNoProof(), '\noproof\lang1024'); // Background-Color diff --git a/tests/PhpWordTests/Writer/RTF/Style/FontTest.php b/tests/PhpWordTests/Writer/RTF/Style/FontTest.php index fc3bf10b46..52364745ed 100644 --- a/tests/PhpWordTests/Writer/RTF/Style/FontTest.php +++ b/tests/PhpWordTests/Writer/RTF/Style/FontTest.php @@ -235,6 +235,12 @@ public function testFontLang(): void $style->setLang(Language::EN_US); $expect = '\lang1033\ltrch '; self::assertEquals($expect, $this->removeCr($writer)); + + $style->setRTL(false); + $style->setLang(Language::EN_US); + $style->setNoProof(true); + $expect = '\langnp1033\noproof\lang1024\ltrch '; + self::assertEquals($expect, $this->removeCr($writer)); } /** From c775a2e1e71db15cc5dac8b027b05e4e40e212c7 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 19 Nov 2025 01:09:53 -0800 Subject: [PATCH 131/153] Rasamassen PR's 2821 and 2823 I made RTF usable some time ago. @rasamassen has recently done a lot of useful work to cover many missing areas. Since who knows when those changes will be merged, I intend to incorporate much of that work. I will do this with several pushes, each based on one or more of those changes. This one is based on PR #2821 (fixes issue #1106), and PR #2823. --- src/PhpWord/Escaper/Rtf.php | 3 + src/PhpWord/Style/ListItem.php | 15 + src/PhpWord/Writer/RTF.php | 10 + src/PhpWord/Writer/RTF/Element/Container.php | 2 +- src/PhpWord/Writer/RTF/Element/ListItem.php | 45 +++ .../Writer/RTF/Element/ListItemRun.php | 67 +++++ src/PhpWord/Writer/RTF/Part/Header.php | 270 +++++++++++++++++- .../Writer/RTF/Element/ListItemTest.php | 53 ++++ 8 files changed, 458 insertions(+), 7 deletions(-) create mode 100644 src/PhpWord/Writer/RTF/Element/ListItemRun.php create mode 100644 tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php diff --git a/src/PhpWord/Escaper/Rtf.php b/src/PhpWord/Escaper/Rtf.php index e43dbd860a..4e5b833b3d 100644 --- a/src/PhpWord/Escaper/Rtf.php +++ b/src/PhpWord/Escaper/Rtf.php @@ -31,6 +31,9 @@ protected function escapeAsciiCharacter($code) if ($code == 9) { return '{\\tab}'; } + if ($code === 10) { + return ''; // or maybe '\par' + } if (0x20 > $code || $code >= 0x80) { return '{\\u' . $code . '}'; } diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index e34aeb7c7c..842f3f53f3 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -115,6 +115,21 @@ public function getNumStyle() return $this->numStyle; } + /** + * Get numbering style. + * + * @return ?Numbering + */ + public function getNumbering() + { + $numStyleObject = Style::getStyle($this->numStyle); + if ($numStyleObject instanceof Numbering) { + return $numStyleObject; + } + + return null; + } + /** * Set numbering style name. * diff --git a/src/PhpWord/Writer/RTF.php b/src/PhpWord/Writer/RTF.php index 91784e982d..246574a902 100644 --- a/src/PhpWord/Writer/RTF.php +++ b/src/PhpWord/Writer/RTF.php @@ -101,6 +101,16 @@ public function getColorTable() return $this->getWriterPart('Header')->getColorTable(); } + /** + * Get list table. + * + * @return array + */ + public function getListTable() + { + return $this->getWriterPart('Header')->getListTable(); + } + /** * Get last paragraph style. * diff --git a/src/PhpWord/Writer/RTF/Element/Container.php b/src/PhpWord/Writer/RTF/Element/Container.php index dcac8ec071..8a5ba87da8 100644 --- a/src/PhpWord/Writer/RTF/Element/Container.php +++ b/src/PhpWord/Writer/RTF/Element/Container.php @@ -46,7 +46,7 @@ public function write() return ''; } $containerClass = substr(get_class($container), strrpos(get_class($container), '\\') + 1); - $withoutP = in_array($containerClass, ['TextRun', 'Footnote', 'Endnote']) ? true : false; + $withoutP = in_array($containerClass, ['TextRun', 'Footnote', 'Endnote', 'ListItemRun', 'Field']) ? true : false; $content = ''; $elements = $container->getElements(); diff --git a/src/PhpWord/Writer/RTF/Element/ListItem.php b/src/PhpWord/Writer/RTF/Element/ListItem.php index 8ce7d0d063..8aea99b88c 100644 --- a/src/PhpWord/Writer/RTF/Element/ListItem.php +++ b/src/PhpWord/Writer/RTF/Element/ListItem.php @@ -25,4 +25,49 @@ */ class ListItem extends Text { + /** + * Write list item element. + */ + public function write() + { + /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + $element = $this->element; + if (!$element instanceof \PhpOffice\PhpWord\Element\ListItem) { + return ''; + } + + $this->getStyles(); + + $depth = (int) $element->getDepth(); + $style = $element->getStyle(); + $text = $element->getTextObject(); + + // Bullet List + $content = ''; + $content .= $this->writeOpening(); + if ($style instanceof \PhpOffice\PhpWord\Style\ListItem) { + $numStyle = $style->getNumbering(); + if ($numStyle->getType() == 'singleLevel') { + $depth = 0; + } + $levels = $numStyle->getLevels(); + $content .= '\ilvl' . $element->getDepth(); + $content .= '\ls' . $style->getNumId(); + $content .= '\tx' . $levels[$depth]->getTabPos(); + $content .= '\fi' . $levels[$depth]->getHanging() * -1; + $content .= '\li' . $levels[$depth]->getLeft(); + $content .= '\lin' . $levels[$depth]->getLeft(); + } + $content .= $this->writeFontStyle(); // Doesn't work. Don't know why. Probalby something to do with \PphOffice\PhpWord\Element\ListItem storing styles in a textObject type \PphOffice\PhpWord\Element\Text rather than within the Element itself + $content .= PHP_EOL; + /* $content .= '{\listtext\f2 \\\'b7\tab }'; // Not sure if needed for listItemRun + $content .= PHP_EOL; */ + $content .= '{'; + $content .= $this->writeText($element->getText()); + $content .= '}'; + $content .= PHP_EOL; + $content .= $this->writeClosing(); + + return $content; + } } diff --git a/src/PhpWord/Writer/RTF/Element/ListItemRun.php b/src/PhpWord/Writer/RTF/Element/ListItemRun.php new file mode 100644 index 0000000000..27ffa9252c --- /dev/null +++ b/src/PhpWord/Writer/RTF/Element/ListItemRun.php @@ -0,0 +1,67 @@ +element; + if (!$element instanceof \PhpOffice\PhpWord\Element\ListItemRun) { + return ''; + } + + $writer = new Container($this->parentWriter, $element); + $this->getStyles(); + + $depth = (int) $element->getDepth(); + $style = $element->getStyle(); + + $content = ''; + $content .= $this->writeOpening(); + if ($style instanceof \PhpOffice\PhpWord\Style\ListItem) { + $numStyle = $style->getNumbering(); + $levels = $numStyle->getLevels(); + $content .= '\ilvl' . $element->getDepth(); + $content .= '\ls' . $style->getNumId(); + $content .= '\tx' . $levels[$depth]->getTabPos(); + $hanging = $levels[$depth]->getLeft() + $levels[$depth]->getHanging(); + $left = 0 - $levels[$depth]->getHanging(); + $content .= '\fi' . $left; + $content .= '\li' . $hanging; + $content .= '\lin' . $hanging; + } + $content .= '{'; + $content .= $writer->write(); + $content .= '}'; + $content .= $this->writeClosing(); + + return $content; + } +} diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index 75a7661082..092e49d206 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -20,8 +20,10 @@ use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Converter; +use PhpOffice\PhpWord\SimpleType\NumberFormat; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; +use PhpOffice\PhpWord\Style\Numbering; use PhpOffice\PhpWord\Style\Table; /** @@ -53,6 +55,13 @@ class Header extends AbstractPart */ private $colorTable = []; + /** + * List table. + * + * @var array + */ + private $listTable = []; + /** * Get font table. * @@ -73,6 +82,16 @@ public function getColorTable() return $this->colorTable; } + /** + * Get list table. + * + * @return array + */ + public function getListTable() + { + return $this->listTable; + } + /** * Write part. * @@ -80,7 +99,7 @@ public function getColorTable() */ public function write() { - $this->registerFont(); + $this->registerHeader(); $content = ''; @@ -88,6 +107,7 @@ public function write() $content .= $this->writeDefaults(); $content .= $this->writeFontTable(); $content .= $this->writeColorTable(); + $content .= $this->writeListTable(); $content .= $this->writeGenerator(); $content .= PHP_EOL; @@ -170,6 +190,185 @@ private function writeColorTable() return $content; } + /** + * Write list table. + * + * @return string + */ + private function writeListTable() + { + $content = ''; + + $listType = [ + 'singleLevel' => '\listsimple1', + 'multilevel' => '\listsimple0', + 'hybridMultilevel' => '\listhybrid', + ]; + + $numberType = [ + NumberFormat::DECIMAL => '0', + NumberFormat::UPPER_ROMAN => '1', + NumberFormat::LOWER_ROMAN => '2', + NumberFormat::UPPER_LETTER => '3', + NumberFormat::LOWER_LETTER => '4', + NumberFormat::ORDINAL => '5', + NumberFormat::CARDINAL_TEXT => '6', + NumberFormat::ORDINAL_TEXT => '7', + /* NumberFormat::HEX => 'hex', + NumberFormat::CHICAGO => 'chicago', + NumberFormat::IDEOGRAPH_DIGITAL => 'ideographDigital', + NumberFormat::JAPANESE_COUNTING => 'japaneseCounting', */ + NumberFormat::AIUEO => '12', + NumberFormat::IROHA => '13', + /* NumberFormat::DECIMAL_FULL_WIDTH => 'decimalFullWidth', + NumberFormat::DECIMAL_HALF_WIDTH => 'decimalHalfWidth', + NumberFormat::JAPANESE_LEGAL => 'japaneseLegal', + NumberFormat::JAPANESE_DIGITAL_TEN_THOUSAND => 'japaneseDigitalTenThousand', + NumberFormat::DECIMAL_ENCLOSED_CIRCLE => 'decimalEnclosedCircle', + NumberFormat::DECIMAL_FULL_WIDTH2 => 'decimalFullWidth2', */ + NumberFormat::AIUEO_FULL_WIDTH => '20', + NumberFormat::IROHA_FULL_WIDTH => '21', + // NumberFormat::DECIMAL_ZERO => 'decimalZero', + NumberFormat::BULLET => '23', + /* NumberFormat::GANADA => 'ganada', + NumberFormat::CHOSUNG => 'chosung', + NumberFormat::DECIMAL_ENCLOSED_FULL_STOP => 'decimalEnclosedFullstop', + NumberFormat::DECIMAL_ENCLOSED_PAREN => 'decimalEnclosedParen', + NumberFormat::DECIMAL_ENCLOSED_CIRCLE_CHINESE => 'decimalEnclosedCircleChinese', + NumberFormat::IDEOGRAPHENCLOSEDCIRCLE => 'ideographEnclosedCircle', + NumberFormat::IDEOGRAPH_TRADITIONAL => 'ideographTraditional', + NumberFormat::IDEOGRAPH_ZODIAC => 'ideographZodiac', + NumberFormat::IDEOGRAPH_ZODIAC_TRADITIONAL => 'ideographZodiacTraditional', + NumberFormat::TAIWANESE_COUNTING => 'taiwaneseCounting', + NumberFormat::IDEOGRAPH_LEGAL_TRADITIONAL => 'ideographLegalTraditional', + NumberFormat::TAIWANESE_COUNTING_THOUSAND => 'taiwaneseCountingThousand', + NumberFormat::TAIWANESE_DIGITAL => 'taiwaneseDigital', + NumberFormat::CHINESE_COUNTING => 'chineseCounting', + NumberFormat::CHINESE_LEGAL_SIMPLIFIED => 'chineseLegalSimplified', + NumberFormat::CHINESE_COUNTING_THOUSAND => 'chineseCountingThousand', + NumberFormat::KOREAN_DIGITAL => 'koreanDigital', + NumberFormat::KOREAN_COUNTING => 'koreanCounting', + NumberFormat::KOREAN_LEGAL => 'koreanLegal', + NumberFormat::KOREAN_DIGITAL2 => 'koreanDigital2', */ + NumberFormat::VIETNAMESE_COUNTING => '56', + NumberFormat::RUSSIAN_LOWER => '58', + NumberFormat::RUSSIAN_UPPER => '59', + NumberFormat::NONE => '255', + /* NumberFormat::NUMBER_IN_DASH => 'numberInDash', + NumberFormat::HEBREW1 => 'hebrew1', + NumberFormat::HEBREW2 => 'hebrew2', + NumberFormat::ARABIC_ALPHA => 'arabicAlpha', */ + NumberFormat::ARABIC_ABJAD => '48', + NumberFormat::HINDI_VOWELS => '49', + NumberFormat::HINDI_CONSONANTS => '50', + NumberFormat::HINDI_NUMBERS => '51', + NumberFormat::HINDI_COUNTING => '52', + NumberFormat::THAI_LETTERS => '53', + NumberFormat::THAI_NUMBERS => '54', + NumberFormat::THAI_COUNTING => '55', + ]; + + $listAlignment = [ + 'left' => '0', + 'center' => '1', + 'right' => '2', + ]; + + $content .= '{'; + $content .= '\*\listtable' . PHP_EOL; + + foreach ($this->listTable as $list) { + $content .= '{'; + $content .= '\list\listtemplateid' . $list['numId']; + if (isset($listType[$list['type']])) { + $content .= $listType[$list['type']]; + } + $content .= PHP_EOL; + foreach ($list['listItems'] as $listItem) { + $content .= '{'; + $content .= '\listlevel'; + if (isset($numberType[$listItem['format']])) { + $content .= '\levelnfc' . $numberType[$listItem['format']]; + $content .= '\levelnfcn' . $numberType[$listItem['format']]; + } + if (isset($listAlignment[$listItem['alignment']])) { + $content .= '\leveljc' . $listAlignment[$listItem['alignment']]; + $content .= '\leveljcn' . $listAlignment[$listItem['alignment']]; + } + $content .= '\levelstartat' . $listItem['start']; + if (isset($listItem['restart'])) { + $content .= '\levelnorestart' . $listItem['restart']; + } + + // Level Text and Numbers + $positions = []; + $level = ''; + $strLength = ''; + $listText = (string) $listItem['text']; + if (strpos($listText, '%') !== false) { + $level = $this->lowerDigitsByOne(str_replace('%', '\\\'0', $listText)); + $levelNumbers = preg_replace('/\d/', 'X', str_replace('%', '', $listText)); + $offset = 0; + while (($pos = strpos($levelNumbers, 'X', $offset)) !== false) { + $positions[] = $pos; + $offset = $pos + 1; + } + $strLength = (string) sprintf('%02d', strlen($levelNumbers)); + } else { + $level = $this->escaper->escape($listText); + $strLength = (string) sprintf('%02d', mb_strlen($listText)); + } + + $content .= '{'; + $content .= '\leveltext \\\'' . $strLength . $level; + $content .= ';}'; + $content .= '{'; + $content .= '\levelnumbers '; + foreach ($positions as $position) { + ++$position; + $content .= '\\\'0' . $position; + } + $content .= ';}'; + + // Font settings for Level Numbers + if (isset($listItem['font']) && !empty($listItem['font'])) { + $fontKey = array_search($listItem['font'], $this->fontTable); + if ($fontKey !== false) { + $content .= '\f' . $fontKey; + } + } + + // Tabs, Hanging, and First Line + $content .= '\levelfollow' . '0'; + $content .= '\jclisttab'; + $content .= '\tx' . $listItem['tabPos']; + $content .= '\fi' . $listItem['hanging'] * -1; + $content .= '\li' . $listItem['left']; + $content .= '\lin' . $listItem['left']; + $content .= '}'; + $content .= PHP_EOL; + } + $content .= '\listid' . $list['numId'] . '}'; + $content .= PHP_EOL; + } + $content .= '}'; + $content .= PHP_EOL . PHP_EOL; + + $content .= '{'; + $content .= '\*\listoverridetable' . PHP_EOL; + foreach ($this->listTable as $list) { + $content .= '{'; + $content .= '\listoverride\listid' . $list['numId']; + $content .= '\listoverridecount0\ls' . $list['numId']; + $content .= '}'; + $content .= PHP_EOL; + } + $content .= '}'; + $content .= PHP_EOL . PHP_EOL; + + return $content; + } + /** * Write. * @@ -186,9 +385,9 @@ private function writeGenerator() } /** - * Register all fonts and colors in both named and inline styles to appropriate header table. + * Register all fonts, colors, and lists in both named and inline styles to appropriate header table. */ - private function registerFont(): void + private function registerHeader(): void { $phpWord = $this->getParentWriter()->getPhpWord(); $this->fontTable[] = Settings::getDefaultFontName(); @@ -197,7 +396,7 @@ private function registerFont(): void // Search named styles $styles = Style::getStyles(); foreach ($styles as $style) { - $this->registerFontItems($style); + $this->registerHeaderItems($style); } // Search inline styles @@ -217,7 +416,7 @@ private function anythingToRegister($element): void if (is_object($element)) { if (method_exists($element, 'getFontStyle')) { $style = $element->getFontStyle(); - $this->registerFontItems($style); + $this->registerHeaderItems($style); } if (method_exists($element, 'getElements')) { foreach ($element->getElements() as $subElement) { @@ -247,7 +446,7 @@ private function registerBorderColor($style): void * * @param Style\AbstractStyle $style */ - private function registerFontItems($style): void + private function registerHeaderItems($style): void { $defaultFont = Settings::getDefaultFontName(); $defaultColor = Settings::DEFAULT_FONT_COLOR; @@ -266,6 +465,9 @@ private function registerFontItems($style): void $this->registerTableItem($this->colorTable, $style->getBorderLeftColor(), $defaultColor); $this->registerTableItem($this->colorTable, $style->getBorderBottomColor(), $defaultColor); } + if ($style instanceof Numbering) { + $this->registerList($this->listTable, $style, $defaultFont); + } } /** @@ -281,4 +483,60 @@ private function registerTableItem(&$table, $value, $default = null): void $table[] = $value; } } + + /** + * Register lists and fonts within lists. + * + * @param array &$table + * @param Numbering $style + * @param string $defaultFont + */ + private function registerList(&$table, $style, $defaultFont = null): void + { + $listItems = []; + + $levels = $style->getLevels(); + foreach ($levels as $level) { + $this->registerTableItem($this->fontTable, $level->getFont(), $defaultFont); + + $listItem = [ + 'level' => $level->getLevel(), + 'start' => $level->getStart(), + 'restart' => $level->getRestart(), + 'format' => $level->getFormat(), + 'pStyle' => $level->getPStyle(), + 'suffix' => $level->getSuffix(), + 'text' => $level->getText(), + 'alignment' => $level->getAlignment(), + 'left' => $level->getLeft(), + 'hanging' => $level->getHanging(), + 'tabPos' => $level->getTabPos(), + 'font' => $level->getFont(), + 'hint' => $level->getHint(), + ]; + array_push($listItems, $listItem); + } + + $list = [ + 'numId' => $style->getNumId(), + 'type' => $style->getType(), + 'listItems' => $listItems, + ]; + $table[] = $list; + } + + /** + * NumberingLevel->getText() returns levels a step higher than expected in RTF \leveltext, (1-9) instead of (0-8). + * Thus all the digits need to be reduced by 1. + * + * @param string $string + */ + private function lowerDigitsByOne($string): string + { + return preg_replace_callback('/\d/', function ($matches) { + $digit = (int) $matches[0]; + + return ($digit > 0) ? (string) ($digit - 1) : (string) $digit; + }, $string); + } } diff --git a/tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php b/tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php new file mode 100644 index 0000000000..f9bb09cad9 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php @@ -0,0 +1,53 @@ +addSection(); + $section->addText('Basic simple bulleted list.'); + $section->addListItem('List Item 1'); + $section->addListItem('List Item 2'); + $section->addListItem('List Item 3'); + $writer = new RtfWriter($phpWord); + $content = $writer->getContent(); + $content = str_replace("\r\n", "\n", $content); + $expectedArray = [ + '\pard\nowidctlpar {Basic simple bulleted list.}\par', + '\ilvl0\ls1\tx720\fi-360\li720\lin720', + '{List Item 1}', + '\par', + '\ilvl0\ls1\tx720\fi-360\li720\lin720', + '{List Item 2}', + '\par', + '\ilvl0\ls1\tx720\fi-360\li720\lin720', + '{List Item 3}', + '\par', + ]; + $expected = implode("\n", $expectedArray); + self::assertStringContainsString($expected, $content); + } +} From acb2e3714352ca0285d9c95c46d2d3483d2315a0 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 19 Nov 2025 03:33:54 -0800 Subject: [PATCH 132/153] Coverage Tweaks --- src/PhpWord/Style/ListItem.php | 5 +-- src/PhpWord/Writer/RTF/Element/ListItem.php | 16 +++++--- .../Writer/RTF/Element/ListItemRun.php | 13 ++++-- src/PhpWord/Writer/RTF/Style/Font.php | 3 +- .../Writer/RTF/Element/ListItemTest.php | 41 +++++++++++++++++++ 5 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index 842f3f53f3..2c579938e4 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -123,11 +123,8 @@ public function getNumStyle() public function getNumbering() { $numStyleObject = Style::getStyle($this->numStyle); - if ($numStyleObject instanceof Numbering) { - return $numStyleObject; - } - return null; + return ($numStyleObject instanceof Numbering) ? $numStyleObject : null; } /** diff --git a/src/PhpWord/Writer/RTF/Element/ListItem.php b/src/PhpWord/Writer/RTF/Element/ListItem.php index 8aea99b88c..92c432eebd 100644 --- a/src/PhpWord/Writer/RTF/Element/ListItem.php +++ b/src/PhpWord/Writer/RTF/Element/ListItem.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\RTF\Element; +use PhpOffice\PhpWord\Element\ListItem as Li; + /** * ListItem element RTF writer; extends from text. * @@ -30,12 +32,16 @@ class ListItem extends Text */ public function write() { - /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ $element = $this->element; - if (!$element instanceof \PhpOffice\PhpWord\Element\ListItem) { - return ''; - } + return ($element instanceof Li) ? $this->writeElement($element) : ''; + } + + /** + * @return string + */ + private function writeElement(Li $element) + { $this->getStyles(); $depth = (int) $element->getDepth(); @@ -58,7 +64,7 @@ public function write() $content .= '\li' . $levels[$depth]->getLeft(); $content .= '\lin' . $levels[$depth]->getLeft(); } - $content .= $this->writeFontStyle(); // Doesn't work. Don't know why. Probalby something to do with \PphOffice\PhpWord\Element\ListItem storing styles in a textObject type \PphOffice\PhpWord\Element\Text rather than within the Element itself + $content .= $this->writeFontStyle(); // Doesn't work. Don't know why. Probably something to do with \PhpOffice\PhpWord\Element\ListItem storing styles in a textObject type \PhpOffice\PhpWord\Element\Text rather than within the Element itself $content .= PHP_EOL; /* $content .= '{\listtext\f2 \\\'b7\tab }'; // Not sure if needed for listItemRun $content .= PHP_EOL; */ diff --git a/src/PhpWord/Writer/RTF/Element/ListItemRun.php b/src/PhpWord/Writer/RTF/Element/ListItemRun.php index 27ffa9252c..dda67029a8 100644 --- a/src/PhpWord/Writer/RTF/Element/ListItemRun.php +++ b/src/PhpWord/Writer/RTF/Element/ListItemRun.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\RTF\Element; +use PhpOffice\PhpWord\Element\ListItemRun as Lir; + /** * ListItem element HTML writer. * @@ -33,10 +35,15 @@ class ListItemRun extends TextRun public function write() { $element = $this->element; - if (!$element instanceof \PhpOffice\PhpWord\Element\ListItemRun) { - return ''; - } + return ($element instanceof Lir) ? $this->writeElement($element) : ''; + } + + /** + * @return string + */ + private function writeElement(Lir $element) + { $writer = new Container($this->parentWriter, $element); $this->getStyles(); diff --git a/src/PhpWord/Writer/RTF/Style/Font.php b/src/PhpWord/Writer/RTF/Style/Font.php index a78dd3d811..1fba3b2dea 100644 --- a/src/PhpWord/Writer/RTF/Style/Font.php +++ b/src/PhpWord/Writer/RTF/Style/Font.php @@ -138,7 +138,8 @@ public function write() // noProof // This is also specified above as \\langnp{$langId} - // Not sure why, or if, both are needed. + // RTF spec suggests using this for backwards compatibility. + // So perhaps earlier can be omitted, but it seems harmless regardless. $content .= $this->getValueIf($style->isNoProof(), '\noproof\lang1024'); // Background-Color diff --git a/tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php b/tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php index f9bb09cad9..f2fbb343a8 100644 --- a/tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php +++ b/tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php @@ -50,4 +50,45 @@ public function testListItemBasic(): void $expected = implode("\n", $expectedArray); self::assertStringContainsString($expected, $content); } + + public function testListItemSingleLevel(): void + { + $phpWord = new PhpWord(); + $singlelevelNumberingStyleName = 'singleLevel'; + $phpWord->addNumberingStyle( + $singlelevelNumberingStyleName, + [ + 'type' => 'singleLevel', + 'levels' => [ + ['format' => 'decimal', 'text' => '%1.', 'left' => 360, 'hanging' => 360, 'tabPos' => 360, 'restart' => 1], + ], + ] + ); + $section = $phpWord->addSection(); + $section->addText('SingleLevel formatted list.'); + $section->addListItem('List Item 1', 0, null, $singlelevelNumberingStyleName); + $section->addListItem('List Item 2', 0, null, $singlelevelNumberingStyleName); + $section->addListItem('List Item 3', 0, null, $singlelevelNumberingStyleName); + $writer = new RtfWriter($phpWord); + $content = $writer->getContent(); + $content = str_replace("\r\n", "\n", $content); + $expectedArray = [ + '\pard\nowidctlpar {SingleLevel formatted list.}\par', + '\ilvl0\ls1\tx360\fi-360\li360\lin360', + '{List Item 1}', + '\par', + '\ilvl0\ls1\tx360\fi-360\li360\lin360', + '{List Item 2}', + '\par', + '\ilvl0\ls1\tx360\fi-360\li360\lin360', + '{List Item 3}', + '\par', + ]; + $expected = implode("\n", $expectedArray); + self::assertStringContainsString($expected, $content); + self::assertStringContainsString('\levelnorestart1', $content); + $table = $writer->getListTable(); + self::assertCount(1, $table); + self::assertSame('singleLevel', $table[0]['type']); + } } From ba408a807b788a845f1e18de9b7138b4e8055a4b Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:58:36 -0800 Subject: [PATCH 133/153] RTF Writer Support ListItem Font Styles ListItem stores its font style with its text in a TextObject. Use that when writing out its text. @rasamassen, you will want to add this change to one of your PRs, probably PR #2019. --- README.md | 3 +-- docs/usage/template.md | 2 +- src/PhpWord/Writer/RTF/Element/ListItem.php | 28 ++++++++++++++++++--- src/PhpWord/Writer/RTF/Element/Ruby.php | 15 +++++++---- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e080d32da8..4a2e461f93 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,6 @@ PHPWord requires the following: - PHP 7.1+ - [XML Parser extension](http://www.php.net/manual/en/xml.installation.php) -- [Laminas Escaper component](https://docs.laminas.dev/laminas-escaper/intro/) - [Zip extension](http://php.net/manual/en/book.zip.php) (optional, used to write OOXML and ODF) - [GD extension](http://php.net/manual/en/book.image.php) (optional, used to add images) - [XMLWriter extension](http://php.net/manual/en/book.xmlwriter.php) (optional, used to write OOXML and ODF) @@ -129,7 +128,7 @@ $fontStyle = new \PhpOffice\PhpWord\Style\Font(); $fontStyle->setBold(true); $fontStyle->setName('Tahoma'); $fontStyle->setSize(13); -$myTextElement = $section->addText('"Believe you can and you\'re halfway there." (Theodor Roosevelt)'); +$myTextElement = $section->addText('"Believe you can and you\'re halfway there." (Theodore Roosevelt)'); $myTextElement->setFontStyle($fontStyle); // Saving the document as OOXML file... diff --git a/docs/usage/template.md b/docs/usage/template.md index a0c885e75e..05f9abe6ea 100644 --- a/docs/usage/template.md +++ b/docs/usage/template.md @@ -166,7 +166,7 @@ Customer: ${customer_name#3} Address: ${customer_address#3} ``` -It is also possible to pass an array with the values to replace the marcros with. +It is also possible to pass an array with the values to replace the macros with. If an array with replacements is passed, the ``count`` argument is ignored, it is the size of the array that counts. ``` php diff --git a/src/PhpWord/Writer/RTF/Element/ListItem.php b/src/PhpWord/Writer/RTF/Element/ListItem.php index 92c432eebd..dab99e5f54 100644 --- a/src/PhpWord/Writer/RTF/Element/ListItem.php +++ b/src/PhpWord/Writer/RTF/Element/ListItem.php @@ -19,6 +19,7 @@ namespace PhpOffice\PhpWord\Writer\RTF\Element; use PhpOffice\PhpWord\Element\ListItem as Li; +use PhpOffice\PhpWord\Style; /** * ListItem element RTF writer; extends from text. @@ -51,7 +52,7 @@ private function writeElement(Li $element) // Bullet List $content = ''; $content .= $this->writeOpening(); - if ($style instanceof \PhpOffice\PhpWord\Style\ListItem) { + if ($style instanceof Style\ListItem) { $numStyle = $style->getNumbering(); if ($numStyle->getType() == 'singleLevel') { $depth = 0; @@ -64,13 +65,34 @@ private function writeElement(Li $element) $content .= '\li' . $levels[$depth]->getLeft(); $content .= '\lin' . $levels[$depth]->getLeft(); } - $content .= $this->writeFontStyle(); // Doesn't work. Don't know why. Probably something to do with \PhpOffice\PhpWord\Element\ListItem storing styles in a textObject type \PhpOffice\PhpWord\Element\Text rather than within the Element itself + $content .= $this->writeFontStyle(); // ListItem Text has its own font style applied later. $content .= PHP_EOL; /* $content .= '{\listtext\f2 \\\'b7\tab }'; // Not sure if needed for listItemRun $content .= PHP_EOL; */ $content .= '{'; - $content .= $this->writeText($element->getText()); + + $textStart = $textStyle = $textEnd = ''; + $textFontStyle = null; + $textObject = $element->getTextObject(); + if ($textObject !== null) { + $textFontStyle = $textObject->getFontStyle(); + if (is_string($textFontStyle)) { + $textFontStyle = Style::getStyle($textFontStyle); + } + } + if ($textFontStyle instanceof Style\Font) { + $this->fontStyle = $textFontStyle; + $textStyle = $this->writeFontStyle(); + if ($textStyle !== '') { + $textStart = '{'; + $textEnd = '}'; + } + } + + $content .= $textStart . $textStyle . $this->writeText($element->getText()) . $textEnd; + $content .= '}'; + $content .= PHP_EOL; $content .= $this->writeClosing(); diff --git a/src/PhpWord/Writer/RTF/Element/Ruby.php b/src/PhpWord/Writer/RTF/Element/Ruby.php index e2b617c556..c236c1e5a0 100644 --- a/src/PhpWord/Writer/RTF/Element/Ruby.php +++ b/src/PhpWord/Writer/RTF/Element/Ruby.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\RTF\Element; +use PhpOffice\PhpWord\Element\Ruby as RubyElement; + /** * Ruby element RTF writer. Writes {baseText} ({rubyText}) in current paragraph style * because RTF does not natively support ruby text. @@ -31,13 +33,16 @@ class Ruby extends AbstractElement */ public function write() { - /** @var \PhpOffice\PhpWord\Element\Ruby $element */ $element = $this->element; - $elementClass = str_replace('\\Writer\\RTF', '', static::class); - if (!$element instanceof $elementClass || !is_string($element->getBaseTextRun()->getText())) { - return ''; - } + return ($element instanceof RubyElement) ? $this->writeElement($element) : ''; + } + + /** + * @return string + */ + private function writeElement(RubyElement $element) + { $this->getStyles(); $content = ''; From 7d3e009eed29a2ae6c94fdef03c25c6e307596ae Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:44:19 -0800 Subject: [PATCH 134/153] Rasamassen PR's 2824 and 2826 I made RTF usable some time ago. @rasamassen has recently done a lot of useful work to cover many missing areas. Since who knows when those changes will be merged, I intend to incorporate much of that work. I will do this with several pushes, each based on one or more of those changes. This one is based on PR #2824 (fixes issue #344), and PR #2826. Widow-orphan control may be affected by this change; based on comments in 2824, I have added a document-level widow-control used only by Rtf, but do not, as yet, use that value to do anything other than read/write an appropriate instruction in the file header. --- phpstan-baseline.neon | 20 ----- phpstan-baseline.php73.neon | 20 ----- src/PhpWord/Metadata/Settings.php | 20 +++++ src/PhpWord/Reader/RTF.php | 2 +- src/PhpWord/Reader/RTF/Document.php | 6 +- src/PhpWord/Reader/ReaderInterface.php | 4 + .../Writer/RTF/Element/PreserveText.php | 64 ++++++++++++++ src/PhpWord/Writer/RTF/Part/Document.php | 5 +- .../Writer/RTF/Style/AbstractStyle.php | 23 +++++ src/PhpWord/Writer/RTF/Style/Paragraph.php | 84 ++++++++++--------- src/PhpWord/Writer/RTF/Style/Spacing.php | 72 ++++++++++++++++ .../PhpWordTests/Escaper/RtfEscaper2Test.php | 2 +- .../PhpWordTests/Escaper/RtfEscaper3Test.php | 4 +- .../WriteReadback/RtfWidowTest.php | 73 ++++++++++++++++ .../Writer/RTF/Element/Element2Test.php | 6 +- .../Writer/RTF/Element/ElementTest.php | 14 ++-- .../Writer/RTF/Element/ListItemTest.php | 4 +- .../Writer/RTF/Element/PreserveTextTest.php | 83 ++++++++++++++++++ .../Writer/RTF/Element/TableTest.php | 10 +-- .../PhpWordTests/Writer/RTF/Sample11Test.php | 14 ++-- .../PhpWordTests/Writer/RTF/Style/RtlTest.php | 6 +- .../Writer/RTF/Style/StyleTest.php | 2 +- tests/PhpWordTests/Writer/RTF/WidowTest.php | 53 ++++++++++++ 23 files changed, 475 insertions(+), 116 deletions(-) create mode 100644 src/PhpWord/Writer/RTF/Element/PreserveText.php create mode 100644 src/PhpWord/Writer/RTF/Style/Spacing.php create mode 100644 tests/PhpWordTests/WriteReadback/RtfWidowTest.php create mode 100644 tests/PhpWordTests/Writer/RTF/Element/PreserveTextTest.php create mode 100644 tests/PhpWordTests/Writer/RTF/WidowTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3175cd9899..94f20157ad 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -105,26 +105,6 @@ parameters: count: 1 path: src/PhpWord/Reader/ODText/Content.php - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$rtf \\(string\\) does not accept string\\|false\\.$#" - count: 1 - path: src/PhpWord/Reader/RTF.php - - - - message: "#^Cannot call method setStyleByArray\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" - count: 1 - path: src/PhpWord/Reader/RTF/Document.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$phpWord is never read, only written\\.$#" - count: 1 - path: src/PhpWord/Reader/RTF/Document.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\:\\:load\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Reader/ReaderInterface.php - - message: "#^Binary operation \"/\" between string\\|null and 2 results in an error\\.$#" count: 1 diff --git a/phpstan-baseline.php73.neon b/phpstan-baseline.php73.neon index 04880c4002..983a7ed033 100644 --- a/phpstan-baseline.php73.neon +++ b/phpstan-baseline.php73.neon @@ -95,26 +95,6 @@ parameters: count: 1 path: src/PhpWord/Reader/ODText/Content.php - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$rtf \\(string\\) does not accept string\\|false\\.$#" - count: 1 - path: src/PhpWord/Reader/RTF.php - - - - message: "#^Cannot call method setStyleByArray\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" - count: 1 - path: src/PhpWord/Reader/RTF/Document.php - - - - message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$phpWord is never read, only written\\.$#" - count: 1 - path: src/PhpWord/Reader/RTF/Document.php - - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\:\\:load\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpWord/Reader/ReaderInterface.php - - message: "#^Binary operation \"/\" between string\\|null and 2 results in an error\\.$#" count: 1 diff --git a/src/PhpWord/Metadata/Settings.php b/src/PhpWord/Metadata/Settings.php index 0e30ec76d6..2fb65145a6 100644 --- a/src/PhpWord/Metadata/Settings.php +++ b/src/PhpWord/Metadata/Settings.php @@ -168,6 +168,14 @@ class Settings */ private $bookFoldPrinting = false; + /** + * RTF, unlike other formats, sets widow/orphan control to off by default. + * This setting allows changing that default for a whole document. + * + * @var bool + */ + private $rtfWidowControl = false; + /** * @return Protection */ @@ -497,4 +505,16 @@ public function setBookFoldPrinting(bool $bookFoldPrinting): self return $this; } + + public function hasRtfWidowControl(): bool + { + return $this->rtfWidowControl; + } + + public function setRtfWidowControl(bool $rtfWidowControl): self + { + $this->rtfWidowControl = $rtfWidowControl; + + return $this; + } } diff --git a/src/PhpWord/Reader/RTF.php b/src/PhpWord/Reader/RTF.php index dbf1ebd7ad..eb7317fb5a 100644 --- a/src/PhpWord/Reader/RTF.php +++ b/src/PhpWord/Reader/RTF.php @@ -42,7 +42,7 @@ public function load($docFile) if ($this->canRead($docFile)) { $doc = new Document(); - $doc->rtf = file_get_contents($docFile); + $doc->rtf = (string) file_get_contents($docFile); $doc->read($phpWord); } else { throw new Exception("Cannot read {$docFile}."); diff --git a/src/PhpWord/Reader/RTF/Document.php b/src/PhpWord/Reader/RTF/Document.php index 59f0a7dd59..e282ebcd10 100644 --- a/src/PhpWord/Reader/RTF/Document.php +++ b/src/PhpWord/Reader/RTF/Document.php @@ -344,6 +344,8 @@ private function parseControl($control, $parameter): void array_shift($directives); // remove the function variable; we won't need it $this->$function($directives); } + } elseif ($control === 'widowctrl') { + $this->phpWord->getSettings()->setRtfWidowControl(true); } } @@ -389,7 +391,9 @@ private function readText(): void { $text = $this->textrun->addText($this->text); if (isset($this->flags['styles']['font'])) { - $text->getFontStyle()->setStyleByArray($this->flags['styles']['font']); + /** @var \PhpOffice\PhpWord\Style\Font */ + $temp = $text->getFontStyle(); + $temp->setStyleByArray($this->flags['styles']['font']); } } } diff --git a/src/PhpWord/Reader/ReaderInterface.php b/src/PhpWord/Reader/ReaderInterface.php index 1dced12aba..f1f4fb9e90 100644 --- a/src/PhpWord/Reader/ReaderInterface.php +++ b/src/PhpWord/Reader/ReaderInterface.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Reader; +use PhpOffice\PhpWord\PhpWord; + /** * Reader interface. * @@ -38,6 +40,8 @@ public function canRead($filename); * Loads PhpWord from file. * * @param string $filename + * + * @return PhpWord */ public function load($filename); } diff --git a/src/PhpWord/Writer/RTF/Element/PreserveText.php b/src/PhpWord/Writer/RTF/Element/PreserveText.php new file mode 100644 index 0000000000..5422f0d8fa --- /dev/null +++ b/src/PhpWord/Writer/RTF/Element/PreserveText.php @@ -0,0 +1,64 @@ +element; + if (!$element instanceof \PhpOffice\PhpWord\Element\PreserveText) { + return ''; + } + + $this->getStyles(); + + $content = ''; + $content .= $this->writeOpening(); + $content .= '{'; + $content .= $this->writeFontStyle(); + if (is_array($element->getText())) { + foreach ($element->getText() as $text) { + if (preg_match('/[{}]/', $text) == 1) { + $text = str_replace(['{', '}'], '', $text); + $content .= '{\field {\*\fldinst {' . $text . '}}{\\fldrslt {}}}'; + } else { + $content .= $this->writeText($text); + } + } + } else { + $content .= $this->writeText($element->getText()); + } + $content .= '}'; + $content .= $this->writeClosing(); + + return $content; + } +} diff --git a/src/PhpWord/Writer/RTF/Part/Document.php b/src/PhpWord/Writer/RTF/Part/Document.php index 8c7207af76..ac1636aa4b 100644 --- a/src/PhpWord/Writer/RTF/Part/Document.php +++ b/src/PhpWord/Writer/RTF/Part/Document.php @@ -110,8 +110,9 @@ private function writeFormatting() $content .= '\viewkind1'; // Set the view mode of the document $content .= '\uc1'; // Set the numberof bytes that follows a unicode character - $content .= '\pard'; // Resets to default paragraph properties. - $content .= '\nowidctlpar'; // No widow/orphan control + if ($docSettings->hasRtfWidowControl()) { + $content .= '\widowctrl'; + } $content .= '\lang' . $langId; $content .= '\kerning1'; // Point size (in half-points) above which to kern character pairs $content .= '\fs' . (Settings::getDefaultFontSize() * 2); // Set the font size in half-points diff --git a/src/PhpWord/Writer/RTF/Style/AbstractStyle.php b/src/PhpWord/Writer/RTF/Style/AbstractStyle.php index 00e148dfe9..9017f2bce7 100644 --- a/src/PhpWord/Writer/RTF/Style/AbstractStyle.php +++ b/src/PhpWord/Writer/RTF/Style/AbstractStyle.php @@ -105,4 +105,27 @@ protected function getValueIf($condition, $value) { return $condition == true ? $value : ''; } + + /** + * Write child style. + * + * @param string $name + * @param mixed $style + * + * @return string + */ + protected function writeChildStyle($name, $style = null) + { + $stylePath = 'PhpOffice\\PhpWord\\Style\\' . ucfirst($name); + if (isset($style) && $style instanceof $stylePath) { + $writerPath = 'PhpOffice\\PhpWord\\Writer\\RTF\\Style\\' . ucfirst($name); + $writer = new $writerPath($style); + + if (method_exists($writer, 'write')) { + return $writer->write(); + } + } + + return ''; + } } diff --git a/src/PhpWord/Writer/RTF/Style/Paragraph.php b/src/PhpWord/Writer/RTF/Style/Paragraph.php index 040c60b5aa..a75a077794 100644 --- a/src/PhpWord/Writer/RTF/Style/Paragraph.php +++ b/src/PhpWord/Writer/RTF/Style/Paragraph.php @@ -36,10 +36,6 @@ class Paragraph extends AbstractStyle */ private $nestedLevel = 0; - private const LEFT = Jc::LEFT; - private const RIGHT = Jc::RIGHT; - private const JUSTIFY = Jc::JUSTIFY; - /** * Write style. * @@ -57,69 +53,75 @@ public function write() Jc::END => '\qr', Jc::CENTER => '\qc', Jc::BOTH => '\qj', - self::LEFT => '\ql', - self::RIGHT => '\qr', - self::JUSTIFY => '\qj', + Jc::LEFT => '\ql', + Jc::RIGHT => '\qr', + Jc::JUSTIFY => '\qj', + Jc::DISTRIBUTE => '\qd', + Jc::THAI_DISTRIBUTE => '\qt', + Jc::HIGH_KASHIDA => '\qk20', + Jc::MEDIUM_KASHIDA => '\qk10', + Jc::LOW_KASHIDA => '\qk0', ]; $bidiAlignments = [ Jc::START => '\qr', Jc::END => '\ql', Jc::CENTER => '\qc', Jc::BOTH => '\qj', - self::LEFT => '\ql', - self::RIGHT => '\qr', - self::JUSTIFY => '\qj', + Jc::LEFT => '\ql', + Jc::RIGHT => '\qr', + Jc::JUSTIFY => '\qj', + Jc::DISTRIBUTE => '\qd', + Jc::THAI_DISTRIBUTE => '\qt', + Jc::HIGH_KASHIDA => '\qk20', + Jc::MEDIUM_KASHIDA => '\qk10', + Jc::LOW_KASHIDA => '\qk0', ]; - $spaceAfter = $style->getSpaceAfter(); - $spaceBefore = $style->getSpaceBefore(); - $content = ''; if ($this->nestedLevel == 0) { - $content .= '\pard\nowidctlpar '; + $content .= '\pard'; } + + // Alignment $alignment = $style->getAlignment(); $bidi = $style->isBidi(); if ($alignment === '' && $bidi !== null) { $alignment = Jc::START; } + + // Right to left if (isset($alignments[$alignment])) { $content .= $bidi ? $bidiAlignments[$alignment] : $alignments[$alignment]; } - $content .= $this->writeIndentation($style->getIndentation()); - $content .= $this->getValueIf($spaceBefore !== null, '\sb' . round($spaceBefore ?? 0)); - $content .= $this->getValueIf($spaceAfter !== null, '\sa' . round($spaceAfter ?? 0)); - $lineHeight = $style->getLineHeight(); - if ($lineHeight) { - $lineHeightAdjusted = (int) ($lineHeight * 240); - $content .= "\\sl$lineHeightAdjusted\\slmult1"; - } - if ($style->hasPageBreakBefore()) { - $content .= '\\page'; - } + $content .= $this->getValueIf($style->isBidi(), '\rtlpar'); + + // Indentation + $content .= $this->writeChildStyle('Indentation', $style->getIndentation()); + $content .= $this->writeChildStyle('Spacing', $style->getSpace()); + // Future: Add Shading $styles = $style->getStyleValues(); $content .= $this->writeTabs($styles['tabs']); - return $content; - } + // Contextual Spacing + $content .= $this->getValueIf($style->hasContextualSpacing(), '\contextualspace'); + // Future: Add Text Alignment - /** - * Writes an \PhpOffice\PhpWord\Style\Indentation. - * - * @param null|\PhpOffice\PhpWord\Style\Indentation $indent - * - * @return string - */ - private function writeIndentation($indent = null) - { - if (isset($indent) && $indent instanceof \PhpOffice\PhpWord\Style\Indentation) { - $writer = new Indentation($indent); + // Pagination + $content .= $this->getValueIf($style->hasWidowControl(), '\widctlpar'); + $content .= $this->getValueIf(!$style->hasWidowControl(), '\nowidctlpar'); + $content .= $this->getValueIf($style->isKeepNext(), '\keepn'); + $content .= $this->getValueIf($style->isKeepLines(), '\keep'); + $content .= $this->getValueIf($style->hasPageBreakBefore(), '\pagebb'); - return $writer->write(); - } + // Hyphenation + $content .= $this->getValueIf($style->hasSuppressAutoHyphens(), '\hyphpar0'); + + // Tabs + $styles = $style->getStyleValues(); + $content .= $this->writeTabs($styles['tabs']); - return ''; + return $content . ' '; } /** diff --git a/src/PhpWord/Writer/RTF/Style/Spacing.php b/src/PhpWord/Writer/RTF/Style/Spacing.php new file mode 100644 index 0000000000..b85cbe4e9f --- /dev/null +++ b/src/PhpWord/Writer/RTF/Style/Spacing.php @@ -0,0 +1,72 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Spacing) { + return ''; + } + + $spacingRules = [ + LineSpacingRule::AUTO => '\slmult1', + LineSpacingRule::EXACT => '\slmult0', + LineSpacingRule::AT_LEAST => '\slmult0', + ]; + + $content = ''; + + // Space Before and After + $content .= $this->getValueIf($style->getBefore() !== null, '\sb' . round($style->getBefore() ?? 0)); + $content .= $this->getValueIf($style->getAfter() !== null, '\sa' . round($style->getAfter() ?? 0)); + + // Space Between Lines + $line = $style->getLine(); + if (null !== $line && $style->getLineRule() === LineSpacingRule::AUTO) { + $line += \PhpOffice\PhpWord\Style\Paragraph::LINE_HEIGHT; + } + // Exact is specified by using negative numbers + if ($style->getLineRule() === LineSpacingRule::EXACT && $line > 0) { + $line = $line * -1; + } + $content .= $this->getValueIf($line !== null, '\sl' . round($line ?? 0)); + + // Spacing Multiple + if ($line !== null) { + $content .= $this->getValueIf(isset($spacingRules[$style->getLineRule()]), $spacingRules[$style->getLineRule()]); + } + + return $content; + } +} diff --git a/tests/PhpWordTests/Escaper/RtfEscaper2Test.php b/tests/PhpWordTests/Escaper/RtfEscaper2Test.php index 49cd5ec4ab..1863f93a16 100644 --- a/tests/PhpWordTests/Escaper/RtfEscaper2Test.php +++ b/tests/PhpWordTests/Escaper/RtfEscaper2Test.php @@ -23,7 +23,7 @@ */ class RtfEscaper2Test extends \PHPUnit\Framework\TestCase { - const HEADER = '\\pard\\nowidctlpar {'; + const HEADER = '\\pard\\widctlpar {'; const TRAILER = '}\\par'; public function escapestring($str) diff --git a/tests/PhpWordTests/Escaper/RtfEscaper3Test.php b/tests/PhpWordTests/Escaper/RtfEscaper3Test.php index 1a4da4bc60..fad9f3cd7e 100644 --- a/tests/PhpWordTests/Escaper/RtfEscaper3Test.php +++ b/tests/PhpWordTests/Escaper/RtfEscaper3Test.php @@ -25,8 +25,8 @@ */ class RtfEscaper3Test extends \PHPUnit\Framework\TestCase { - const HEADER = '\\pard\\nowidctlpar \ql{\ltrch '; - const HEADER_RTL = '\\pard\\nowidctlpar \qr{\\rtlch '; + const HEADER = '\\pard\\ql\\widctlpar {\\ltrch '; + const HEADER_RTL = '\\pard\\qr\\rtlpar\\widctlpar {\\rtlch '; const TRAILER = '}\\par'; protected function tearDown(): void diff --git a/tests/PhpWordTests/WriteReadback/RtfWidowTest.php b/tests/PhpWordTests/WriteReadback/RtfWidowTest.php new file mode 100644 index 0000000000..ce213107fc --- /dev/null +++ b/tests/PhpWordTests/WriteReadback/RtfWidowTest.php @@ -0,0 +1,73 @@ +getSettings()->setRtfWidowControl(true); + $testText = 'Hello World!'; + $sectionWriter = $phpWordWriter->addSection(); + $sectionWriter->addText($testText); + + $writer = new RTF($phpWordWriter); + $file = __DIR__ . '/../_files/temp.rtf'; + $writer->save($file); + + self::assertFileExists($file); + + $phpWordReader = IOFactory::load($file, 'RTF'); + unlink($file); + + self::assertTrue($phpWordReader->getSettings()->hasRtfWidowControl()); + } + + public function testDefaultWidowControl(): void + { + $phpWordWriter = new PhpWord(); + $testText = 'Hello World!'; + $sectionWriter = $phpWordWriter->addSection(); + $sectionWriter->addText($testText); + + $writer = new RTF($phpWordWriter); + $file = __DIR__ . '/../_files/temp.rtf'; + $writer->save($file); + + self::assertFileExists($file); + + $phpWordReader = IOFactory::load($file, 'RTF'); + unlink($file); + + self::assertCount(1, $phpWordReader->getSections()); + self::assertCount(2, $phpWordReader->getSections()[0]->getElements()); + self::assertInstanceOf(TextRun::class, $phpWordReader->getSections()[0]->getElements()[0]); + self::assertEquals($testText, $phpWordReader->getSections()[0]->getElements()[0]->getText()); + self::assertFalse($phpWordReader->getSettings()->hasRtfWidowControl()); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Element/Element2Test.php b/tests/PhpWordTests/Writer/RTF/Element/Element2Test.php index cbd99599d3..5301e336fc 100644 --- a/tests/PhpWordTests/Writer/RTF/Element/Element2Test.php +++ b/tests/PhpWordTests/Writer/RTF/Element/Element2Test.php @@ -40,7 +40,7 @@ public function testTextRun(): void $element->addText('Hello '); $element->addText('there.'); $textrun = new WriterTextRun($parentWriter, $element); - $expect = "\\pard\\nowidctlpar {{Hello }{there.}}\\par\n"; + $expect = "\\pard\\widctlpar {{Hello }{there.}}\\par\n"; self::assertEquals($expect, $this->removeCr($textrun)); } @@ -51,7 +51,7 @@ public function testTextRunParagraphStyle(): void $element->addText('Hello '); $element->addText('there.'); $textrun = new WriterTextRun($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sb0\\sa0{{Hello }{there.}}\\par\n"; + $expect = "\\pard\\sb0\\sa0\\widctlpar {{Hello }{there.}}\\par\n"; self::assertEquals($expect, $this->removeCr($textrun)); } @@ -63,7 +63,7 @@ public function testTitle(): void $section = $phpWord->addSection(); $element = $section->addTitle('First Heading', 1); $elwrite = new WriterTitle($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sb0\\sa0{\\outlinelevel0{First Heading}\\par\n}"; + $expect = "\\pard\\sb0\\sa0\\widctlpar {\\outlinelevel0{First Heading}\\par\n}"; self::assertEquals($expect, $this->removeCr($elwrite)); } } diff --git a/tests/PhpWordTests/Writer/RTF/Element/ElementTest.php b/tests/PhpWordTests/Writer/RTF/Element/ElementTest.php index f59f420705..e9668e4f65 100644 --- a/tests/PhpWordTests/Writer/RTF/Element/ElementTest.php +++ b/tests/PhpWordTests/Writer/RTF/Element/ElementTest.php @@ -126,7 +126,7 @@ public function testTable(): void '\\pard', "\\trowd \\cellx$width \\cellx$width2 ", '\\intbl', - '{1}\\par', + '\\widctlpar {1}\\par', '\\cell', '\\intbl', '{2}\\par', @@ -134,7 +134,7 @@ public function testTable(): void '\\row', "\\trowd \\cellx$width \\cellx$width2 ", '\\intbl', - '{3}\\par', + '\\widctlpar {3}\\par', '\\cell', '\\intbl', '{4}\\par', @@ -154,7 +154,7 @@ public function testTextRun(): void $element->addText('Hello '); $element->addText('there.'); $textrun = new RTF\Element\TextRun($parentWriter, $element); - $expect = "\\pard\\nowidctlpar {{Hello }{there.}}\\par\n"; + $expect = "\\pard\\widctlpar {{Hello }{there.}}\\par\n"; self::assertSame($expect, $this->removeCr($textrun)); } @@ -165,7 +165,7 @@ public function testTextRunParagraphStyle(): void $element->addText('Hello '); $element->addText('there.'); $textrun = new RTF\Element\TextRun($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sb0\\sa0{{Hello }{there.}}\\par\n"; + $expect = "\\pard\\sb0\\sa0\\widctlpar {{Hello }{there.}}\\par\n"; self::assertSame($expect, $this->removeCr($textrun)); } @@ -177,7 +177,7 @@ public function testTitle(): void $section = $phpWord->addSection(); $element = $section->addTitle('First Heading', 1); $elwrite = new RTF\Element\Title($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sb0\\sa0{\\outlinelevel0{First Heading}\\par\n}"; + $expect = "\\pard\\sb0\\sa0\\widctlpar {\\outlinelevel0{First Heading}\\par\n}"; self::assertSame($expect, $this->removeCr($elwrite)); } @@ -193,7 +193,7 @@ public function testRuby(): void $element->addRuby($baseTextRun, $rubyTextRun, $properties); $textrun = new RTF\Element\TextRun($parentWriter, $element); - $expect = "\\pard\\nowidctlpar {{base text (ruby)}}\\par\n"; + $expect = "\\pard\\widctlpar {{base text (ruby)}}\\par\n"; self::assertSame($expect, $this->removeCr($textrun)); } @@ -218,7 +218,7 @@ public function testRubyTitle(): void $element = $section->addTitle($textRun, 1); $elwrite = new RTF\Element\Title($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sb0\\sa2{\\outlinelevel0{\\cf0\\fs48\\b base text (ruby)}\\par\n}"; + $expect = "\\pard\\sb0\\sa2\\widctlpar {\\outlinelevel0{\\cf0\\fs48\\b base text (ruby)}\\par\n}"; self::assertSame($expect, $this->removeCr($elwrite)); } } diff --git a/tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php b/tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php index f2fbb343a8..907d815464 100644 --- a/tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php +++ b/tests/PhpWordTests/Writer/RTF/Element/ListItemTest.php @@ -36,7 +36,7 @@ public function testListItemBasic(): void $content = $writer->getContent(); $content = str_replace("\r\n", "\n", $content); $expectedArray = [ - '\pard\nowidctlpar {Basic simple bulleted list.}\par', + '\pard\widctlpar {Basic simple bulleted list.}\par', '\ilvl0\ls1\tx720\fi-360\li720\lin720', '{List Item 1}', '\par', @@ -73,7 +73,7 @@ public function testListItemSingleLevel(): void $content = $writer->getContent(); $content = str_replace("\r\n", "\n", $content); $expectedArray = [ - '\pard\nowidctlpar {SingleLevel formatted list.}\par', + '\pard\widctlpar {SingleLevel formatted list.}\par', '\ilvl0\ls1\tx360\fi-360\li360\lin360', '{List Item 1}', '\par', diff --git a/tests/PhpWordTests/Writer/RTF/Element/PreserveTextTest.php b/tests/PhpWordTests/Writer/RTF/Element/PreserveTextTest.php new file mode 100644 index 0000000000..4bce9c8653 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Element/PreserveTextTest.php @@ -0,0 +1,83 @@ +addSection(); + $footer = $section->addFooter(); + $footer->addPreserveText('Page {PAGE} of {NUMPAGES}'); + $section->addText('First paragraph'); + $section->addPageBreak(); + $section->addText('Second paragraph'); + $section->addPageBreak(); + $section->addText('Third paragraph'); + + $writer = new RtfWriter($phpWord); + $content = $writer->getContent(); + $content = str_replace("\r\n", "\n", $content); + $expected = '\footer{Page {\field {\*\fldinst {PAGE}}{\fldrslt {}}} of {\field {\*\fldinst {NUMPAGES}}{\fldrslt {}}}}'; + self::assertStringContainsString($expected, $content); + } + + public function testPreserveTextText(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $footer = $section->addFooter(); + $footer->addPreserveText('Footertext'); + $section->addText('First paragraph'); + $section->addPageBreak(); + $section->addText('Second paragraph'); + $section->addPageBreak(); + $section->addText('Third paragraph'); + + $writer = new RtfWriter($phpWord); + $content = $writer->getContent(); + $content = str_replace("\r\n", "\n", $content); + $expected = '\footer{Footertext}'; + self::assertStringContainsString($expected, $content); + } + + public function testPreserveTextEmpty(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $footer = $section->addFooter(); + $footer->addPreserveText(''); + $section->addText('First paragraph'); + $section->addPageBreak(); + $section->addText('Second paragraph'); + $section->addPageBreak(); + $section->addText('Third paragraph'); + + $writer = new RtfWriter($phpWord); + $content = $writer->getContent(); + $content = str_replace("\r\n", "\n", $content); + $expected = '\footer{}'; + self::assertStringContainsString($expected, $content); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Element/TableTest.php b/tests/PhpWordTests/Writer/RTF/Element/TableTest.php index 38abf782d1..c7955e3082 100644 --- a/tests/PhpWordTests/Writer/RTF/Element/TableTest.php +++ b/tests/PhpWordTests/Writer/RTF/Element/TableTest.php @@ -52,7 +52,7 @@ public function testTable(): void '\\pard', "\\trowd \\cellx$width \\cellx$width2 ", '\\intbl', - '{1}\\par', + '\\widctlpar {1}\\par', '\\cell', '\\intbl', '{2}\\par', @@ -60,7 +60,7 @@ public function testTable(): void '\\row', "\\trowd \\cellx$width \\cellx$width2 ", '\\intbl', - '{3}\\par', + '\\widctlpar {3}\\par', '\\cell', '\\intbl', '{4}\\par', @@ -94,7 +94,7 @@ public function testTableStyle(): void '\\clbrdrr\\brdrs\\brdrw2\\brdrcf0', "\\cellx$width ", '\\intbl', - '{1}\\par', + '\\widctlpar {1}\\par', '\\cell', '\\row', '\\pard', @@ -119,7 +119,7 @@ public function testTableStyleNotExisting(): void '\\pard', "\\trowd \\cellx$width ", '\\intbl', - '{1}\\par', + '\\widctlpar {1}\\par', '\\cell', '\\row', '\\pard', @@ -148,7 +148,7 @@ public function testTableCellStyle(): void '\\clbrdrr\\brdrdot\\brdrw2\\brdrcf0', "\\cellx$width ", '\\intbl', - '{1}\\par', + '\\widctlpar {1}\\par', '\\cell', '\\row', '\\pard', diff --git a/tests/PhpWordTests/Writer/RTF/Sample11Test.php b/tests/PhpWordTests/Writer/RTF/Sample11Test.php index 7b85399835..ad6d71b0b7 100644 --- a/tests/PhpWordTests/Writer/RTF/Sample11Test.php +++ b/tests/PhpWordTests/Writer/RTF/Sample11Test.php @@ -83,11 +83,11 @@ public function testFooters(): void $section->addText('This should be page 5'); $writer = new RTF($phpWord); $content = $writer->getContent(); - $expected = '{\footerf\pard\nowidctlpar {{First Footer}}\par'; + $expected = '{\footerf\pard\widctlpar {{First Footer}}\par'; self::assertStringContainsString($expected, $content); - $expected = '{\footerl\pard\nowidctlpar {{Even Footer}}\par'; + $expected = '{\footerl\pard\widctlpar {{Even Footer}}\par'; self::assertStringContainsString($expected, $content); - $expected = '{\footerr\pard\nowidctlpar {{Odd Footer}}\par'; + $expected = '{\footerr\pard\widctlpar {{Odd Footer}}\par'; self::assertStringContainsString($expected, $content); } @@ -107,9 +107,9 @@ public function testPageBreakBeforeTextRun(): void $textRun3->addText('Section 2 Paragraph 2'); $writer = new RTF($phpWord); $content = $writer->getContent(); - $expected = '\pard\nowidctlpar {{Section 2 Paragraph 1}}\par'; + $expected = '\pard\widctlpar {{Section 2 Paragraph 1}}\par'; self::assertStringContainsString($expected, $content, 'no page break'); - $expected = '\pard\nowidctlpar \page{{Section 2 Paragraph 2}}\par'; + $expected = '\pard\widctlpar\pagebb {{Section 2 Paragraph 2}}\par'; self::assertStringContainsString($expected, $content, 'page break'); } @@ -130,9 +130,9 @@ public function testPageBreakBeforeTitle(): void $section2->addTitle('Heading1 with pbb not first element in section', 1); $writer = new RTF($phpWord); $content = $writer->getContent(); - $expected = '\pard\nowidctlpar {\outlinelevel0{\b Heading1 with pbb first element in section}'; + $expected = '\pard\widctlpar {\outlinelevel0{\b Heading1 with pbb first element in section}'; self::assertStringContainsString($expected, $content, 'no page break'); - $expected = '\pard\nowidctlpar \page{\outlinelevel0{\b Heading1 with pbb not first element in section}'; + $expected = '\pard\widctlpar\pagebb {\outlinelevel0{\b Heading1 with pbb not first element in section}'; self::assertStringContainsString($expected, $content, 'page break'); } } diff --git a/tests/PhpWordTests/Writer/RTF/Style/RtlTest.php b/tests/PhpWordTests/Writer/RTF/Style/RtlTest.php index 2c87ae8fb0..d21581a1bb 100644 --- a/tests/PhpWordTests/Writer/RTF/Style/RtlTest.php +++ b/tests/PhpWordTests/Writer/RTF/Style/RtlTest.php @@ -44,7 +44,7 @@ public function testRTL(): void $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\Text('אב גד', ['RTL' => true]); $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar {\\rtlch \\uc0\\u1488 \\uc0\\u1489 \\uc0\\u1490 \\uc0\\u1491 }\\par\n"; + $expect = "\\pard\\widctlpar {\\rtlch \\uc0\\u1488 \\uc0\\u1489 \\uc0\\u1490 \\uc0\\u1491 }\\par\n"; self::assertEquals($expect, $this->removeCr($text)); } @@ -54,7 +54,7 @@ public function testRTL2(): void $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\Text('אב גד'); $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\qr{\\rtlch \\uc0\\u1488 \\uc0\\u1489 \\uc0\\u1490 \\uc0\\u1491 }\\par\n"; + $expect = "\\pard\\qr\\rtlpar\\widctlpar {\\rtlch \\uc0\\u1488 \\uc0\\u1489 \\uc0\\u1490 \\uc0\\u1491 }\\par\n"; self::assertEquals($expect, $this->removeCr($text)); } @@ -64,7 +64,7 @@ public function testPageBreakLineHeight2(): void $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\ql\\sl259\\slmult1\\page{\\ltrch New page}\\par\n"; + $expect = "\\pard\\ql\\sl259\\slmult1\\widctlpar\\pagebb {\\ltrch New page}\\par\n"; self::assertEquals($expect, $this->removeCr($text)); } } diff --git a/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php b/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php index b993c0e5e3..5545aaf043 100644 --- a/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php +++ b/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php @@ -87,7 +87,7 @@ public function testPageBreakLineHeight(): void $parentWriter = new RTF(); $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); $text = new RTF\Element\Text($parentWriter, $element); - $expect = "\\pard\\nowidctlpar \\sl259\\slmult1\\page{New page}\\par\n"; + $expect = "\\pard\\sl259\\slmult1\\widctlpar\\pagebb {New page}\\par\n"; self::assertEquals($expect, $this->removeCr($text)); } diff --git a/tests/PhpWordTests/Writer/RTF/WidowTest.php b/tests/PhpWordTests/Writer/RTF/WidowTest.php new file mode 100644 index 0000000000..9214ec3211 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/WidowTest.php @@ -0,0 +1,53 @@ +addSection(); + $textRun1 = $section1->addTextRun(); + $textRun1->addText('Section 1 Paragraph 1'); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + $expected = '\widowctrl'; + self::assertStringNotContainsString($expected, $content, 'should not contain widowctrl'); + } + + public function testTrueWidow(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setRtfWidowControl(true); + $section1 = $phpWord->addSection(); + $textRun1 = $section1->addTextRun(); + $textRun1->addText('Section 1 Paragraph 1'); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + $expected = '\deftab720\viewkind1\uc1\widowctrl\lang1036\kerning1\fs20'; + self::assertStringContainsString($expected, $content, 'should contain widowctrl'); + } +} From 38d1a6aa2dc12e5a71225739695fe8d7e16eef66 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:37:06 -0800 Subject: [PATCH 135/153] Coverage Tweaks --- .../Writer/RTF/Element/PreserveText.php | 11 +++-- src/PhpWord/Writer/RTF/Style/Spacing.php | 9 ++-- .../Writer/RTF/Style/SpacingTest.php | 46 +++++++++++++++++++ 3 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 tests/PhpWordTests/Writer/RTF/Style/SpacingTest.php diff --git a/src/PhpWord/Writer/RTF/Element/PreserveText.php b/src/PhpWord/Writer/RTF/Element/PreserveText.php index 5422f0d8fa..4f3d6cdc91 100644 --- a/src/PhpWord/Writer/RTF/Element/PreserveText.php +++ b/src/PhpWord/Writer/RTF/Element/PreserveText.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\RTF\Element; +use PhpOffice\PhpWord\Element\PreserveText as PreserveTextElement; + /** * Text element RTF writer. * @@ -32,12 +34,11 @@ class PreserveText extends AbstractElement */ public function write() { - /** @var \PhpOffice\PhpWord\Element\PreserveText $element Type hint */ - $element = $this->element; - if (!$element instanceof \PhpOffice\PhpWord\Element\PreserveText) { - return ''; - } + return ($this->element instanceof PreserveTextElement) ? $this->writeElement($this->element) : ''; + } + private function writeElement(PreserveTextElement $element): string + { $this->getStyles(); $content = ''; diff --git a/src/PhpWord/Writer/RTF/Style/Spacing.php b/src/PhpWord/Writer/RTF/Style/Spacing.php index b85cbe4e9f..6600976e4b 100644 --- a/src/PhpWord/Writer/RTF/Style/Spacing.php +++ b/src/PhpWord/Writer/RTF/Style/Spacing.php @@ -19,6 +19,7 @@ namespace PhpOffice\PhpWord\Writer\RTF\Style; use PhpOffice\PhpWord\SimpleType\LineSpacingRule; +use PhpOffice\PhpWord\Style\Spacing as SpacingStyle; /** * RTF Spacing style writer. @@ -35,10 +36,12 @@ class Spacing extends AbstractStyle public function write() { $style = $this->getStyle(); - if (!$style instanceof \PhpOffice\PhpWord\Style\Spacing) { - return ''; - } + return ($style instanceof SpacingStyle) ? $this->writeStyle($style) : ''; + } + + private function writeStyle(SpacingStyle $style): string + { $spacingRules = [ LineSpacingRule::AUTO => '\slmult1', LineSpacingRule::EXACT => '\slmult0', diff --git a/tests/PhpWordTests/Writer/RTF/Style/SpacingTest.php b/tests/PhpWordTests/Writer/RTF/Style/SpacingTest.php new file mode 100644 index 0000000000..e27d36df50 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/SpacingTest.php @@ -0,0 +1,46 @@ +write()); + } + + public function testSpacing(): void + { + $parentWriter = new RTF(); + $style = new SpacingStyle(); + $writer = new SpacingWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setLineRule(LineSpacingRule::EXACT); + $style->setLine(5); + $expect = '\sl-5\slmult0'; + self::assertEquals($expect, $this->removeCr($writer)); + } +} From 598f6aea2a8c36e55a0ea932e848ce736fbe8049 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 21 Nov 2025 23:30:03 -0800 Subject: [PATCH 136/153] Formal Support for Php8.5 --- .github/workflows/php.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 7363a0fc50..81f75d8e04 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -48,8 +48,7 @@ jobs: fail-fast: false matrix: # Disabled PHPStan in '7.1' - php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] - #php: ['8.4'] + php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] steps: - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -66,7 +65,7 @@ jobs: run: ./vendor/bin/phpstan analyse -c phpstan.neon.php73.dist - name: Run phpstan Php8/Php7.4 - if: matrix.php == '7.4' || matrix.php == '8.0' || matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.3' || matrix.php == '8.4' + if: matrix.php == '7.4' || matrix.php == '8.0' || matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.3' || matrix.php == '8.4' || matrix.php == '8.5' run: ./vendor/bin/phpstan analyse -c phpstan.neon.dist phpunit: @@ -86,6 +85,7 @@ jobs: - '8.2' - '8.3' - '8.4' + - '8.5' include: - php: 'nightly' @@ -118,8 +118,8 @@ jobs: if: matrix.php == '7.3' || matrix.php == '7.4' || matrix.php == '8.0' run: ./vendor/bin/phpunit -c phpunit9.xml.dist --no-coverage - - name: Run phpunit 8.1 8.2 8.4 - if: matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.4' + - name: Run phpunit 8.1 8.2 8.4 8.5 + if: matrix.php == '8.1' || matrix.php == '8.2' || matrix.php == '8.4' || matrix.php == '8.5' run: ./vendor/bin/phpunit -c phpunit10.xml.dist --no-coverage - name: Run phpunit nightly experimental From 3efc30e4239223d00ddf5dd2dbbf2391b60345b5 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 22 Nov 2025 18:03:18 -0800 Subject: [PATCH 137/153] Rasamassen PR's 2836, 2839, and 2850 I made RTF usable some time ago. @rasamassen has recently done a lot of useful work to cover many missing areas. Since who knows when those changes will be merged, I intend to incorporate much of that work. I will do this with several pushes, each based on one or more of those changes. This one is based on PR #2836, PR #2839, and PR #2850. --- samples/Sample_20_BGColor.php | 2 +- src/PhpWord/Shared/Converter.php | 80 ++++++++++------- src/PhpWord/SimpleType/Color.php | 77 ++++++++++++++++ src/PhpWord/Style/Font.php | 45 +++++++--- src/PhpWord/Writer/HTML/Style/Font.php | 24 ++++- src/PhpWord/Writer/ODText/Style/Font.php | 38 +++++++- src/PhpWord/Writer/RTF/Style/Indentation.php | 20 +++-- tests/PhpWordTests/Shared/ConverterTest.php | 88 ++++++++++++++++++- tests/PhpWordTests/Style/FontTest.php | 3 +- tests/PhpWordTests/Writer/HTMLTest.php | 6 +- .../Writer/ODText/Style/FontTest.php | 56 ++++++++---- .../Writer/RTF/Style/FontTest.php | 15 ++-- .../Writer/RTF/Style/IndentationTest.php | 73 +++++++++++++++ .../Writer/RTF/Style/StyleTest.php | 14 --- .../Writer/Word2007/Style/FontTest.php | 23 +++++ 15 files changed, 463 insertions(+), 101 deletions(-) create mode 100644 src/PhpWord/SimpleType/Color.php create mode 100644 tests/PhpWordTests/Writer/RTF/Style/IndentationTest.php diff --git a/samples/Sample_20_BGColor.php b/samples/Sample_20_BGColor.php index e0ce4d6436..c72ae85a3f 100644 --- a/samples/Sample_20_BGColor.php +++ b/samples/Sample_20_BGColor.php @@ -11,7 +11,7 @@ $section->addText( 'This is some text highlighted using fgColor (limited to 15 colors)', - ['fgColor' => PhpOffice\PhpWord\Style\Font::FGCOLOR_YELLOW] + ['fgColor' => PhpOffice\PhpWord\SimpleType\Color::YELLOW] ); $section->addText('This one uses bgColor and is using hex value (0xfbbb10)', ['bgColor' => 'fbbb10']); $section->addText('Compatible with font colors', ['color' => '0000ff', 'bgColor' => 'fbbb10']); diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php index 82e11c0790..e4d49e96c5 100644 --- a/src/PhpWord/Shared/Converter.php +++ b/src/PhpWord/Shared/Converter.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Shared; +use PhpOffice\PhpWord\SimpleType\Color; + /** * Common converter functions. */ @@ -295,6 +297,49 @@ public static function angleToDegree($angle = 1) return round($angle / self::DEGREE_TO_ANGLE); } + private const STRING_TO_RGB = [ + Color::AQUA => '00FFFF', + Color::BLACK => '000000', + Color::BLUE => '0000FF', + Color::BROWN => 'A52A2A', + Color::CYAN => '00FFFF', + Color::DARKBLUE => '00008B', + Color::DARKCYAN => '008B8B', + Color::DARKGRAY => 'A9A9A9', + Color::DARKGREEN => '006400', + Color::DARKMAGENTA => '8B008B', + Color::DARKORANGE => 'FF8C00', + Color::DARKRED => '8B0000', + Color::DARKVIOLET => '9400D3', + Color::DARKYELLOW => '808000', + Color::FUCHSIA => 'FF00FF', + Color::GOLD => 'FFD700', + Color::GRAY => '808080', + Color::GREEN => '008000', + Color::LIGHTBLUE => 'ADD8E6', + Color::LIGHTCYAN => 'E0FFFF', + Color::LIGHTGRAY => 'D3D3D3', + Color::LIGHTGREEN => '90EE90', + Color::LIGHTPINK => 'FFB6C1', + Color::LIGHTYELLOW => 'FFFFE0', + Color::LIME => '00FF00', + Color::MAGENTA => 'FF00FF', + Color::MAROON => '800000', + Color::NAVY => '000080', + Color::OLIVE => '808000', + Color::ORANGE => 'FFA500', + Color::PINK => 'FFC0CB', + Color::PURPLE => '800080', + Color::RED => 'FF0000', + Color::SILVER => 'C0C0C0', + Color::TAN => 'D2B48C', + Color::TEAL => '008080', + Color::TURQUOISE => '40E0D0', + Color::VIOLET => 'EE82EE', + Color::WHITE => 'FFFFFF', + Color::YELLOW => 'FFFF00', + ]; + /** * Convert colorname as string to RGB. * @@ -304,40 +349,7 @@ public static function angleToDegree($angle = 1) */ public static function stringToRgb($value) { - switch ($value) { - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_YELLOW: - return 'FFFF00'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_LIGHTGREEN: - return '90EE90'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_CYAN: - return '00FFFF'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_MAGENTA: - return 'FF00FF'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_BLUE: - return '0000FF'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_RED: - return 'FF0000'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKBLUE: - return '00008B'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKCYAN: - return '008B8B'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKGREEN: - return '006400'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKMAGENTA: - return '8B008B'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKRED: - return '8B0000'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKYELLOW: - return '8B8B00'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKGRAY: - return 'A9A9A9'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_LIGHTGRAY: - return 'D3D3D3'; - case \PhpOffice\PhpWord\Style\Font::FGCOLOR_BLACK: - return '000000'; - } - - return $value; + return self::STRING_TO_RGB[$value] ?? $value; } /** diff --git a/src/PhpWord/SimpleType/Color.php b/src/PhpWord/SimpleType/Color.php new file mode 100644 index 0000000000..09e985ad68 --- /dev/null +++ b/src/PhpWord/SimpleType/Color.php @@ -0,0 +1,77 @@ + 'underline dashed ', + FontStyle::UNDERLINE_DASHDOTDOTHEAVY => 'underline dotted 2px ', + FontStyle::UNDERLINE_DASHDOTHEAVY => 'underline dotted 2px ', + FontStyle::UNDERLINE_DASHEDHEAVY => 'underline dashed 2px ', + FontStyle::UNDERLINE_DASHLONG => 'underline dashed ', + FontStyle::UNDERLINE_DASHLONGHEAVY => 'underline dashed 2px ', + FontStyle::UNDERLINE_DOTDASH => 'underline dotted ', + FontStyle::UNDERLINE_DOTDOTDASH => 'underline dotted ', + FontStyle::UNDERLINE_DOTTED => 'underline dotted ', + FontStyle::UNDERLINE_DOTTEDHEAVY => 'underline dotted 2px ', + FontStyle::UNDERLINE_DOUBLE => 'underline double ', + FontStyle::UNDERLINE_HEAVY => 'underline solid 2px ', + FontStyle::UNDERLINE_SINGLE => 'underline solid ', + FontStyle::UNDERLINE_WAVY => 'underline wavy ', + FontStyle::UNDERLINE_WAVYDOUBLE => 'underline wavy ', + FontStyle::UNDERLINE_WAVYHEAVY => 'underline wavy 2px ', + FontStyle::UNDERLINE_WORDS => 'underline solid ', + ]; + /** * Write style. * @@ -57,7 +77,9 @@ public function write() $css['vertical-align'] .= $this->getValueIf($style->isSuperScript(), 'super'); $css['vertical-align'] .= $this->getValueIf($style->isSubScript(), 'sub'); $css['text-decoration'] = ''; - $css['text-decoration'] .= $this->getValueIf($underline, 'underline '); + if (isset(self::UNDERLINES[$style->getUnderline()])) { + $css['text-decoration'] .= $this->getValueIf($underline, self::UNDERLINES[$style->getUnderline()]); + } $css['text-decoration'] .= $this->getValueIf($lineThrough, 'line-through '); $css['text-transform'] = $this->getValueIf($style->isAllCaps(), 'uppercase'); $css['font-variant'] = $this->getValueIf($style->isSmallCaps(), 'small-caps'); diff --git a/src/PhpWord/Writer/ODText/Style/Font.php b/src/PhpWord/Writer/ODText/Style/Font.php index 2e32dcc024..6c3d1a452c 100644 --- a/src/PhpWord/Writer/ODText/Style/Font.php +++ b/src/PhpWord/Writer/ODText/Style/Font.php @@ -19,6 +19,7 @@ namespace PhpOffice\PhpWord\Writer\ODText\Style; use PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\Style\Font as FontStyle; /** * Font style writer. @@ -27,15 +28,39 @@ */ class Font extends AbstractStyle { + private const UNDERLINES = [ + FontStyle::UNDERLINE_DASH => 'dash', + FontStyle::UNDERLINE_DASHDOTDOTHEAVY => 'dot-dot-dash', + FontStyle::UNDERLINE_DASHDOTHEAVY => 'dot-dash', + FontStyle::UNDERLINE_DASHEDHEAVY => 'dash', + FontStyle::UNDERLINE_DASHLONG => 'long-dash', + FontStyle::UNDERLINE_DASHLONGHEAVY => 'long-dash', + FontStyle::UNDERLINE_DOTDASH => 'dot-dash', + FontStyle::UNDERLINE_DOTDOTDASH => 'dot-dot-dash', + FontStyle::UNDERLINE_DOTTED => 'dotted', + FontStyle::UNDERLINE_DOTTEDHEAVY => 'dotted', + FontStyle::UNDERLINE_DOUBLE => 'solid', + FontStyle::UNDERLINE_HEAVY => 'solid', + FontStyle::UNDERLINE_SINGLE => 'solid', + FontStyle::UNDERLINE_WAVY => 'wave', + FontStyle::UNDERLINE_WAVYDOUBLE => 'wave', + FontStyle::UNDERLINE_WAVYHEAVY => 'wave', + FontStyle::UNDERLINE_WORDS => 'solid', + ]; + /** * Write style. */ public function write(): void { $style = $this->getStyle(); - if (!$style instanceof Style\Font) { - return; + if ($style instanceof FontStyle) { + $this->writeStyle($style); } + } + + private function writeStyle(FontStyle $style): void + { $xmlWriter = $this->getXmlWriter(); $stylep = $style->getParagraph(); @@ -74,9 +99,14 @@ public function write(): void $xmlWriter->writeAttributeIf($style->isItalic(), 'style:font-style-complex', 'italic'); // Underline - // @todo Various mode of underline $underline = $style->getUnderline(); - $xmlWriter->writeAttributeIf($underline != 'none', 'style:text-underline-style', 'solid'); + if (isset(self::UNDERLINES[$underline])) { + $xmlWriter->writeAttribute('style:text-underline-style', self::UNDERLINES[$underline]); + $xmlWriter->writeAttributeIf(strpos(strtolower($underline), 'heavy') !== false, 'style:text-underline-width', 'bold'); + $xmlWriter->writeAttributeIf(strpos(strtolower($underline), 'thick') !== false, 'style:text-underline-width', 'bold'); + $xmlWriter->writeAttributeIf(strpos(strtolower($underline), 'double') !== false, 'style:text-underline-type', 'double'); + $xmlWriter->writeAttributeIf(strpos(strtolower($underline), 'words') !== false, 'style:text-underline-mode', 'skip-white-space'); + } // Strikethrough, double strikethrough $xmlWriter->writeAttributeIf($style->isStrikethrough(), 'style:text-line-through-type', 'single'); diff --git a/src/PhpWord/Writer/RTF/Style/Indentation.php b/src/PhpWord/Writer/RTF/Style/Indentation.php index 589125a26a..74e84db112 100644 --- a/src/PhpWord/Writer/RTF/Style/Indentation.php +++ b/src/PhpWord/Writer/RTF/Style/Indentation.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\RTF\Style; +use PhpOffice\PhpWord\Style\Indentation as IndentationStyle; + /** * RTF indentation style writer. * @@ -33,13 +35,19 @@ class Indentation extends AbstractStyle public function write() { $style = $this->getStyle(); - if (!$style instanceof \PhpOffice\PhpWord\Style\Indentation) { - return ''; - } - $content = '\fi' . round($style->getFirstLine()); - $content .= '\li' . round($style->getLeft()); - $content .= '\ri' . round($style->getRight()); + return ($style instanceof IndentationStyle) ? $this->writeStyle($style) : ''; + } + + private function writeStyle(IndentationStyle $style): string + { + $content = ''; + + $content .= $this->getValueIf($style->getFirstLine() != 0, '\fi' . round($style->getFirstLine())); + $content .= $this->getValueIf($style->getFirstLineChars() != 0, '\cufi' . round($style->getFirstLineChars())); + $content .= $this->getValueIf($style->getHanging() != 0, '\fi-' . round($style->getHanging())); + $content .= $this->getValueIf($style->getLeft() != 0, '\li' . round($style->getLeft())); + $content .= $this->getValueIf($style->getRight() != 0, '\ri' . round($style->getRight())); return $content . ' '; } diff --git a/tests/PhpWordTests/Shared/ConverterTest.php b/tests/PhpWordTests/Shared/ConverterTest.php index 36ba797aeb..c3533ad0cd 100644 --- a/tests/PhpWordTests/Shared/ConverterTest.php +++ b/tests/PhpWordTests/Shared/ConverterTest.php @@ -19,6 +19,8 @@ namespace PhpOffice\PhpWordTests\Shared; use PhpOffice\PhpWord\Shared\Converter; +use PhpOffice\PhpWord\SimpleType\Color; +use PhpOffice\PhpWord\Style\Font; /** * Test class for PhpOffice\PhpWord\Shared\Converter. @@ -117,7 +119,91 @@ public function testHtmlToRGB(): void self::assertEquals([102, 119, 136], Converter::htmlToRgb('678')); // 3 characters self::assertEquals($flse, Converter::htmlToRgb('0F9D')); // 4 characters self::assertEquals([0, 0, 0], Converter::htmlToRgb('unknow')); // 6 characters, invalid - self::assertEquals([139, 0, 139], Converter::htmlToRgb(\PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKMAGENTA)); // Constant + self::assertEquals([139, 0, 139], Converter::htmlToRgb(Color::DARKMAGENTA)); // Constant + } + + /** + * Test SimpleType::Color. Ensure all colors come out to correct values. + * Verified against https://c-rex.net/samples/ooxml/e1/Part4/OOXML_P4_DOCX_ST_PresetColorVal_topic_ID0ELA5NB.html. + */ + public function testBuiltInColors(): void + { + self::assertSame([0, 255, 255], Converter::htmlToRgb(Color::AQUA)); + self::assertSame([0, 0, 0], Converter::htmlToRgb(Color::BLACK)); + self::assertSame([0, 0, 255], Converter::htmlToRgb(Color::BLUE)); + self::assertSame([165, 42, 42], Converter::htmlToRgb(Color::BROWN)); + self::assertSame([0, 255, 255], Converter::htmlToRgb(Color::CYAN)); + self::assertSame([0, 0, 139], Converter::htmlToRgb(Color::DARKBLUE)); + self::assertSame([0, 139, 139], Converter::htmlToRgb(Color::DARKCYAN)); + self::assertSame([169, 169, 169], Converter::htmlToRgb(Color::DARKGRAY)); + self::assertSame([0, 100, 0], Converter::htmlToRgb(Color::DARKGREEN)); + self::assertSame([139, 0, 139], Converter::htmlToRgb(Color::DARKMAGENTA)); + self::assertSame([255, 140, 0], Converter::htmlToRgb(Color::DARKORANGE)); + self::assertSame([139, 0, 0], Converter::htmlToRgb(Color::DARKRED)); + self::assertSame([148, 0, 211], Converter::htmlToRgb(Color::DARKVIOLET)); + self::assertSame([128, 128, 0], Converter::htmlToRgb(Color::DARKYELLOW)); + self::assertSame([255, 0, 255], Converter::htmlToRgb(Color::FUCHSIA)); + self::assertSame([255, 215, 0], Converter::htmlToRgb(Color::GOLD)); + self::assertSame([128, 128, 128], Converter::htmlToRgb(Color::GRAY)); + self::assertSame([0, 128, 0], Converter::htmlToRgb(Color::GREEN)); + self::assertSame([173, 216, 230], Converter::htmlToRgb(Color::LIGHTBLUE)); + self::assertSame([224, 255, 255], Converter::htmlToRgb(Color::LIGHTCYAN)); + self::assertSame([211, 211, 211], Converter::htmlToRgb(Color::LIGHTGRAY)); + self::assertSame([144, 238, 144], Converter::htmlToRgb(Color::LIGHTGREEN)); + self::assertSame([255, 182, 193], Converter::htmlToRgb(Color::LIGHTPINK)); + self::assertSame([255, 255, 224], Converter::htmlToRgb(Color::LIGHTYELLOW)); + self::assertSame([0, 255, 0], Converter::htmlToRgb(Color::LIME)); + self::assertSame([255, 0, 255], Converter::htmlToRgb(Color::MAGENTA)); + self::assertSame([128, 0, 0], Converter::htmlToRgb(Color::MAROON)); + self::assertSame([0, 0, 128], Converter::htmlToRgb(Color::NAVY)); + self::assertSame([128, 128, 0], Converter::htmlToRgb(Color::OLIVE)); + self::assertSame([255, 165, 0], Converter::htmlToRgb(Color::ORANGE)); + self::assertSame([255, 192, 203], Converter::htmlToRgb(Color::PINK)); + self::assertSame([128, 0, 128], Converter::htmlToRgb(Color::PURPLE)); + self::assertSame([255, 0, 0], Converter::htmlToRgb(Color::RED)); + self::assertSame([192, 192, 192], Converter::htmlToRgb(Color::SILVER)); + self::assertSame([210, 180, 140], Converter::htmlToRgb(Color::TAN)); + self::assertSame([0, 128, 128], Converter::htmlToRgb(Color::TEAL)); + self::assertSame([64, 224, 208], Converter::htmlToRgb(Color::TURQUOISE)); + self::assertSame([238, 130, 238], Converter::htmlToRgb(Color::VIOLET)); + self::assertSame([255, 255, 255], Converter::htmlToRgb(Color::WHITE)); + self::assertSame([255, 255, 0], Converter::htmlToRgb(Color::YELLOW)); + } + + /** + * Make sure deprecated colors are properly represented in non-deprecated. + * + * @dataProvider providerDeprecatedColorNames + */ + public function testDeprecatedColorNames(string $deprecatedName, string $newName): void + { + self::assertSame($deprecatedName, $newName); + } + + public function testDeprecatedColorNamesCount(): void + { + self::assertCount(15, self::providerDeprecatedColorNames()); + } + + public static function providerDeprecatedColorNames(): array + { + return [ + [Font::FGCOLOR_BLACK, Color::BLACK], + [Font::FGCOLOR_BLUE, Color::BLUE], + [Font::FGCOLOR_CYAN, Color::CYAN], + [Font::FGCOLOR_DARKBLUE, Color::DARKBLUE], + [Font::FGCOLOR_DARKCYAN, Color::DARKCYAN], + [Font::FGCOLOR_DARKGRAY, Color::DARKGRAY], + [Font::FGCOLOR_DARKGREEN, Color::DARKGREEN], + [Font::FGCOLOR_DARKMAGENTA, Color::DARKMAGENTA], + [Font::FGCOLOR_DARKRED, Color::DARKRED], + [Font::FGCOLOR_DARKYELLOW, Color::DARKYELLOW], + [Font::FGCOLOR_LIGHTGRAY, Color::LIGHTGRAY], + [Font::FGCOLOR_LIGHTGREEN, Color::LIGHTGREEN], + [Font::FGCOLOR_MAGENTA, Color::MAGENTA], + [Font::FGCOLOR_RED, Color::RED], + [Font::FGCOLOR_YELLOW, Color::YELLOW], + ]; } /** diff --git a/tests/PhpWordTests/Style/FontTest.php b/tests/PhpWordTests/Style/FontTest.php index 02d63a1ce9..607021ef8c 100644 --- a/tests/PhpWordTests/Style/FontTest.php +++ b/tests/PhpWordTests/Style/FontTest.php @@ -20,6 +20,7 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\SimpleType\Color; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Language; @@ -115,7 +116,7 @@ public function testSetStyleValueNormal(): void 'doubleStrikethrough' => false, 'smallCaps' => true, 'allCaps' => false, - 'fgColor' => Font::FGCOLOR_YELLOW, + 'fgColor' => Color::YELLOW, 'bgColor' => 'FFFF00', 'lineHeight' => 2, 'scale' => 150, diff --git a/tests/PhpWordTests/Writer/HTMLTest.php b/tests/PhpWordTests/Writer/HTMLTest.php index b7168ea9c1..df348ae9c5 100644 --- a/tests/PhpWordTests/Writer/HTMLTest.php +++ b/tests/PhpWordTests/Writer/HTMLTest.php @@ -21,6 +21,7 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Jc; +use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Writer\HTML; use PhpOffice\PhpWordTests\AbstractWebServerEmbedded; @@ -162,7 +163,7 @@ public function testSave(): void $cell = $table->addRow()->addCell(); $cell->addText( htmlspecialchars('Test 1', ENT_COMPAT, 'UTF-8'), - ['superscript' => true, 'underline' => 'dash', 'strikethrough' => true] + ['superscript' => true, 'underline' => Font::UNDERLINE_DASHDOTDOTHEAVY, 'strikethrough' => true] ); $cell->addTextRun(); $cell->addLink('https://github.com/PHPOffice/PHPWord'); @@ -176,6 +177,9 @@ public function testSave(): void $section->addLink('top', 'back to top', null, null, true); $writer = new HTML($phpWord); + $content = $writer->getContent(); + $expected = '

      Test 1

      '; + self::assertStringContainsString($expected, $content); $writer->save($file); self::assertFileExists($file); diff --git a/tests/PhpWordTests/Writer/ODText/Style/FontTest.php b/tests/PhpWordTests/Writer/ODText/Style/FontTest.php index f238777cf4..970857f58c 100644 --- a/tests/PhpWordTests/Writer/ODText/Style/FontTest.php +++ b/tests/PhpWordTests/Writer/ODText/Style/FontTest.php @@ -19,6 +19,7 @@ namespace PhpOffice\PhpWordTests\Writer\ODText\Style; use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\SimpleType\Color; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWordTests\TestHelperDOCX; @@ -79,7 +80,7 @@ public function testColors(): void $section = $phpWord->addSection(); $section->addText('This is red (800) in rtf/html, default in docx/odt', ['color' => '800']); $section->addText('This should be cyanish (008787)', ['color' => '008787']); - $section->addText('This should be dark green (FGCOLOR_DARKGREEN)', ['color' => Font::FGCOLOR_DARKGREEN]); + $section->addText('This should be dark green (Color::DARKGREEN)', ['color' => Color::DARKGREEN]); $section->addText('This color is default (unknow)', ['color' => 'unknow']); $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); @@ -108,27 +109,27 @@ public function testColors(): void $span = "$s2t/text:p[4]/text:span"; self::assertTrue($doc->elementExists($span)); self::assertEquals($style, $doc->getElementAttribute($span, 'text:style-name')); - self::assertEquals('This should be dark green (FGCOLOR_DARKGREEN)', $doc->getElement($span)->nodeValue); + self::assertEquals('This should be dark green (Color::DARKGREEN)', $doc->getElement($span)->nodeValue); } public static function providerAllNamedColors() { return [ - [Font::FGCOLOR_YELLOW, 'FFFF00'], - [Font::FGCOLOR_LIGHTGREEN, '90EE90'], - [Font::FGCOLOR_CYAN, '00FFFF'], - [Font::FGCOLOR_MAGENTA, 'FF00FF'], - [Font::FGCOLOR_BLUE, '0000FF'], - [Font::FGCOLOR_RED, 'FF0000'], - [Font::FGCOLOR_DARKBLUE, '00008B'], - [Font::FGCOLOR_DARKCYAN, '008B8B'], - [Font::FGCOLOR_DARKGREEN, '006400'], - [Font::FGCOLOR_DARKMAGENTA, '8B008B'], - [Font::FGCOLOR_DARKRED, '8B0000'], - [Font::FGCOLOR_DARKYELLOW, '8B8B00'], - [Font::FGCOLOR_DARKGRAY, 'A9A9A9'], - [Font::FGCOLOR_LIGHTGRAY, 'D3D3D3'], - [Font::FGCOLOR_BLACK, '000000'], + [Color::YELLOW, 'FFFF00'], + [Color::LIGHTGREEN, '90EE90'], + [Color::CYAN, '00FFFF'], + [Color::MAGENTA, 'FF00FF'], + [Color::BLUE, '0000FF'], + [Color::RED, 'FF0000'], + [Color::DARKBLUE, '00008B'], + [Color::DARKCYAN, '008B8B'], + [Color::DARKGREEN, '006400'], + [Color::DARKMAGENTA, '8B008B'], + [Color::DARKRED, '8B0000'], + [Color::DARKYELLOW, '808000'], + [Color::DARKGRAY, 'A9A9A9'], + [Color::LIGHTGRAY, 'D3D3D3'], + [Color::BLACK, '000000'], ['unknow', 'unknow'], ['unknown', 'unknown'], ]; @@ -286,4 +287,25 @@ public function testFieldStyles(): void $element = "$s2t/text:p[5]/text:span"; self::assertEquals('namedstyle', $doc->getElementAttribute($element, 'text:style-name')); } + + public function testUnderline(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $phpWord->addFontStyle('underlineStyle', [ + 'underline' => Font::UNDERLINE_DOTDOTDASH, + ]); + $section = $phpWord->addSection(); + $section->addText('Sample text.', 'underlineStyle'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $doc->setDefaultFile('styles.xml'); + $s2a = '/office:document-styles/office:styles'; + self::assertTrue($doc->elementExists($s2a)); + $style = "$s2a/style:style"; + self::assertTrue($doc->elementExists($style)); + self::assertSame('underlineStyle', $doc->getElementAttribute($style, 'style:name')); + $properties = "$style/style:text-properties"; + self::assertTrue($doc->elementExists($properties)); + self::assertSame('dot-dot-dash', $doc->getElementAttribute($properties, 'style:text-underline-style')); + } } diff --git a/tests/PhpWordTests/Writer/RTF/Style/FontTest.php b/tests/PhpWordTests/Writer/RTF/Style/FontTest.php index 52364745ed..c66d531c4d 100644 --- a/tests/PhpWordTests/Writer/RTF/Style/FontTest.php +++ b/tests/PhpWordTests/Writer/RTF/Style/FontTest.php @@ -20,6 +20,7 @@ use PhpOffice\PhpWord\Element\Text as TextElement; use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\SimpleType\Color; use PhpOffice\PhpWord\Style\Font as FontStyle; use PhpOffice\PhpWord\Style\Language; use PhpOffice\PhpWord\Writer\RTF; @@ -57,8 +58,8 @@ public function testFontColor(): void $style->setName('Times New Roman'); $style->setFallbackFont('serif'); // currently does nothing in RTF, see pg. 18 of spec for implementation $style->setSize(24); - $style->setColor($style::FGCOLOR_YELLOW); - $style->setFgColor($style::FGCOLOR_RED); + $style->setColor(Color::YELLOW); + $style->setFgColor(Color::RED); $style->setBgColor('#123456'); $expect = '\f0\cf0\fs48\highlight0\chshdng0\chcbpat0\cb0 '; self::assertEquals($expect, $this->removeCr($writer)); @@ -78,8 +79,8 @@ public function testFontColorRegistered(): void $style->setName('Times New Roman'); $style->setFallbackFont('serif'); // currently does nothing in RTF, see pg. 18 of spec for implementation $style->setSize(24); - $style->setColor($style::FGCOLOR_YELLOW); - $style->setFgColor($style::FGCOLOR_RED); + $style->setColor(Color::YELLOW); + $style->setFgColor(Color::RED); $style->setBgColor('#123456'); $phpWord->addFontStyle('style1', $style); @@ -146,7 +147,7 @@ public function testFontUnderline(): void $expect = '\uldash '; self::assertEquals($expect, $this->removeCr($writer)); - $style->setUnderline($style::UNDERLINE_DASHHEAVY); + $style->setUnderline($style::UNDERLINE_DASHEDHEAVY); $expect = '\ulthdash '; self::assertEquals($expect, $this->removeCr($writer)); @@ -166,7 +167,7 @@ public function testFontUnderline(): void $expect = '\uldashd '; self::assertEquals($expect, $this->removeCr($writer)); - $style->setUnderline($style::UNDERLINE_DOTDASHHEAVY); + $style->setUnderline($style::UNDERLINE_DASHDOTHEAVY); $expect = '\ulthdashd '; self::assertEquals($expect, $this->removeCr($writer)); @@ -174,7 +175,7 @@ public function testFontUnderline(): void $expect = '\uldashdd '; self::assertEquals($expect, $this->removeCr($writer)); - $style->setUnderline($style::UNDERLINE_DOTDOTDASHHEAVY); + $style->setUnderline($style::UNDERLINE_DASHDOTDOTHEAVY); $expect = '\ulthdashdd '; self::assertEquals($expect, $this->removeCr($writer)); diff --git a/tests/PhpWordTests/Writer/RTF/Style/IndentationTest.php b/tests/PhpWordTests/Writer/RTF/Style/IndentationTest.php new file mode 100644 index 0000000000..f555263ddb --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/IndentationTest.php @@ -0,0 +1,73 @@ +write()); + } + + /** + * Test indentation. + * See page 79 of RTF Specification 1.9.1 for Indentation. + */ + public function testIndentation(): void + { + $parentWriter = new RTF(); + $style = new IndentationStyle(); + $writer = new IndentationWriter($style); + $writer->setParentWriter($parentWriter); + + $expect = ' '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setLeft(1440); + $style->setRight(720); + $style->setFirstLine(360); + $style->setFirstLineChars(180); + $expect = '\fi360\cufi180\li1440\ri720 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style = new IndentationStyle(); + $writer = new IndentationWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setLeft(1440); + $style->setRight(720); + $style->setHanging(360); + $expect = '\fi-360\li1440\ri720 '; + self::assertEquals($expect, $this->removeCr($writer)); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php b/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php index 5545aaf043..253a174cda 100644 --- a/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php +++ b/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php @@ -68,20 +68,6 @@ public function testBorderWithNonRegisteredColors(): void self::assertEquals($expected, $content); } - public function testIndentation(): void - { - $indentation = new \PhpOffice\PhpWord\Style\Indentation(); - $indentation->setLeft(1); - $indentation->setRight(2); - $indentation->setFirstLine(3); - - $indentWriter = new RTF\Style\Indentation($indentation); - $indentWriter->setParentWriter(new RTF()); - $result = $indentWriter->write(); - - self::assertSame('\fi3\li1\ri2 ', $result); - } - public function testPageBreakLineHeight(): void { $parentWriter = new RTF(); diff --git a/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php b/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php index 5cd3eef011..6b1fd98905 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php @@ -20,6 +20,7 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Shared\Html; +use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWordTests\TestHelperDOCX; /** @@ -196,4 +197,26 @@ public static function testRgb(): void self::assertTrue($doc->elementExists($styelem . '/w:color')); self::assertSame('A7D9C1', $doc->getElementAttribute($styelem . '/w:color', 'w:val')); } + + public function testUnderline(): void + { + $phpWord = new PhpWord(); + $phpWord->addFontStyle('underlineStyle', [ + 'underline' => Font::UNDERLINE_DOTDOTDASH, + ]); + $section = $phpWord->addSection(); + $section->addText('Sample text.', 'underlineStyle'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + $doc->setDefaultFile('word/styles.xml'); + $s2a = '/w:styles/w:style[3]'; + self::assertTrue($doc->elementExists($s2a)); + self::assertSame('character', $doc->getElementAttribute($s2a, 'w:type')); + $name = "$s2a/w:name"; + self::assertTrue($doc->elementExists($name)); + self::assertSame('underlineStyle', $doc->getElementAttribute($name, 'w:val')); + $u = "$s2a/w:rPr/w:u"; + self::assertTrue($doc->elementExists($u)); + self::assertSame('dotDotDash', $doc->getElementAttribute($u, 'w:val')); + } } From cbda0b6846018932b9344b64ca2898169143077a Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 22 Nov 2025 18:31:15 -0800 Subject: [PATCH 138/153] Avoid Php8.5 Deprecations --- src/PhpWord/Writer/ODText/Style/Font.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PhpWord/Writer/ODText/Style/Font.php b/src/PhpWord/Writer/ODText/Style/Font.php index 6c3d1a452c..5551c2b6f2 100644 --- a/src/PhpWord/Writer/ODText/Style/Font.php +++ b/src/PhpWord/Writer/ODText/Style/Font.php @@ -88,8 +88,8 @@ private function writeStyle(FontStyle $style): void $xmlWriter->writeAttributeIf(is_numeric($size), 'style:font-size-complex', $size . 'pt'); // Color - $color = $style->getColor(); - $xmlWriter->writeAttributeIf($color != '', 'fo:color', '#' . \PhpOffice\PhpWord\Shared\Converter::stringToRgb($color)); + $color = (string) $style->getColor(); + $xmlWriter->writeAttributeIf($color !== '', 'fo:color', '#' . \PhpOffice\PhpWord\Shared\Converter::stringToRgb($color)); // Bold & italic $xmlWriter->writeAttributeIf($style->isBold(), 'fo:font-weight', 'bold'); @@ -133,8 +133,8 @@ private function writeStyle(FontStyle $style): void } // Foreground-Color (which is really background color) - $fgColor = $style->getFgColor(); - $xmlWriter->writeAttributeIf($fgColor != '', 'fo:background-color', '#' . \PhpOffice\PhpWord\Shared\Converter::stringToRgb($fgColor)); + $fgColor = (string) $style->getFgColor(); + $xmlWriter->writeAttributeIf($fgColor !== '', 'fo:background-color', '#' . \PhpOffice\PhpWord\Shared\Converter::stringToRgb($fgColor)); $xmlWriter->endElement(); // style:text-properties $xmlWriter->endElement(); // style:style From 1fc73d45492cba539f727229d4c3935aca41f7c0 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 22 Nov 2025 22:55:00 -0800 Subject: [PATCH 139/153] Rasamassen PR's 2817, 2834, and 2835 I made RTF usable some time ago. @rasamassen has recently done a lot of useful work to cover many missing areas. Since who knows when those changes will be merged, I intend to incorporate much of that work. I will do this with several pushes, each based on one or more of those changes. This one is based on PR #2817, PR #2834, and PR #2835. --- docs/usage/introduction.md | 2 +- src/PhpWord/Writer/RTF/Part/Document.php | 8 +- src/PhpWord/Writer/RTF/Style/Section.php | 38 +++++- .../Writer/RTF/Part/BookFoldTest.php | 56 ++++++++ .../Writer/RTF/Style/SectionTest.php | 124 ++++++++++++++++++ 5 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 tests/PhpWordTests/Writer/RTF/Part/BookFoldTest.php create mode 100644 tests/PhpWordTests/Writer/RTF/Style/SectionTest.php diff --git a/docs/usage/introduction.md b/docs/usage/introduction.md index 19d6aff51f..228aff4795 100644 --- a/docs/usage/introduction.md +++ b/docs/usage/introduction.md @@ -55,7 +55,7 @@ $fontStyle = new \PhpOffice\PhpWord\Style\Font(); $fontStyle->setBold(true); $fontStyle->setName('Tahoma'); $fontStyle->setSize(13); -$myTextElement = $section->addText('"Believe you can and you\'re halfway there." (Theodor Roosevelt)'); +$myTextElement = $section->addText('"Believe you can and you\'re halfway there." (Theodore Roosevelt)'); $myTextElement->setFontStyle($fontStyle); // Saving the document as OOXML file... diff --git a/src/PhpWord/Writer/RTF/Part/Document.php b/src/PhpWord/Writer/RTF/Part/Document.php index ac1636aa4b..22a9049209 100644 --- a/src/PhpWord/Writer/RTF/Part/Document.php +++ b/src/PhpWord/Writer/RTF/Part/Document.php @@ -116,9 +116,15 @@ private function writeFormatting() $content .= '\lang' . $langId; $content .= '\kerning1'; // Point size (in half-points) above which to kern character pairs $content .= '\fs' . (Settings::getDefaultFontSize() * 2); // Set the font size in half-points - if ($docSettings->hasEvenAndOddHeaders()) { + if ($docSettings->hasEvenAndOddHeaders() || $docSettings->hasMirrorMargins()) { $content .= '\\facingp'; } + if ($docSettings->hasMirrorMargins()) { + $content .= '\\margmirror'; + } + if ($docSettings->hasBookFoldPrinting()) { + $content .= '\\bookfold\\landscape'; + } $content .= PHP_EOL; return $content; diff --git a/src/PhpWord/Writer/RTF/Style/Section.php b/src/PhpWord/Writer/RTF/Style/Section.php index 598015ed4d..1f6372a9a9 100644 --- a/src/PhpWord/Writer/RTF/Style/Section.php +++ b/src/PhpWord/Writer/RTF/Style/Section.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Writer\RTF\Style; +use PhpOffice\PhpWord\SimpleType\VerticalJc; use PhpOffice\PhpWord\Style\Section as SectionStyle; /** @@ -44,17 +45,48 @@ public function write() $content .= '\sectd '; // Size & margin - $content .= $this->getValueIf($style->getPageSizeW() !== null, '\pgwsxn' . round($style->getPageSizeW())); - $content .= $this->getValueIf($style->getPageSizeH() !== null, '\pghsxn' . round($style->getPageSizeH())); + if ($this->getParentWriter() !== null && $this->getParentWriter()->getPhpWord()->getSettings()->hasBookFoldPrinting()) { + $style->setOrientation(SectionStyle::ORIENTATION_LANDSCAPE); + $content .= $this->getValueIf($style->getPageSizeW() !== null, '\pgwsxn' . round($style->getPageSizeW()) / 2); + $content .= $this->getValueIf($style->getPageSizeH() !== null, '\pghsxn' . round($style->getPageSizeH())); + } else { + $content .= $this->getValueIf($style->getPageSizeW() !== null, '\pgwsxn' . round($style->getPageSizeW())); + $content .= $this->getValueIf($style->getPageSizeH() !== null, '\pghsxn' . round($style->getPageSizeH())); + } $content .= ' '; + $content .= $this->getValueIf($style->getMarginTop() !== null, '\margtsxn' . round($style->getMarginTop())); $content .= $this->getValueIf($style->getMarginRight() !== null, '\margrsxn' . round($style->getMarginRight())); $content .= $this->getValueIf($style->getMarginBottom() !== null, '\margbsxn' . round($style->getMarginBottom())); $content .= $this->getValueIf($style->getMarginLeft() !== null, '\marglsxn' . round($style->getMarginLeft())); $content .= $this->getValueIf($style->getHeaderHeight() !== null, '\headery' . round($style->getHeaderHeight())); $content .= $this->getValueIf($style->getFooterHeight() !== null, '\footery' . round($style->getFooterHeight())); - $content .= $this->getValueIf($style->getGutter() !== null, '\guttersxn' . round($style->getGutter())); + $content .= $this->getValueIf($style->getGutter() !== null && $style->getGutter() !== SectionStyle::DEFAULT_GUTTER, '\guttersxn' . round($style->getGutter())); $content .= $this->getValueIf($style->getPageNumberingStart() !== null, '\pgnstarts' . $style->getPageNumberingStart() . '\pgnrestart'); + + $content .= $this->getValueIf($style->getColsNum() !== null && $style->getColsNum() !== SectionStyle::DEFAULT_COLUMN_COUNT, '\cols' . $style->getColsNum()); + $content .= $this->getValueIf($style->getColsSpace() !== null && $style->getColsNum() !== SectionStyle::DEFAULT_COLUMN_COUNT, '\colsx' . round($style->getColsSpace())); + + // Break Type + $breakTypes = [ + 'nextPage' => '\sbkpage', + 'nextColumn' => '\sbkcol', + 'continuous' => '\sbknone', + 'evenPage' => '\sbkeven', + 'oddPage' => '\sbkodd', + ]; + if (isset($breakTypes[$style->getBreakType()])) { + $content .= $breakTypes[$style->getBreakType()]; + } + + // Vertical Align + $verticalAlign = [ + VerticalJc::TOP => '\vertalt', + VerticalJc::CENTER => '\vertalc', + VerticalJc::BOTH => '\vertalj', + VerticalJc::BOTTOM => '\vertalb', + ]; + $content .= $verticalAlign[(string) $style->getVAlign()] ?? ''; $content .= ' '; // Borders diff --git a/tests/PhpWordTests/Writer/RTF/Part/BookFoldTest.php b/tests/PhpWordTests/Writer/RTF/Part/BookFoldTest.php new file mode 100644 index 0000000000..1ba4bf3bf3 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Part/BookFoldTest.php @@ -0,0 +1,56 @@ +getSettings()->setBookFoldPrinting(true); + $section1 = $phpWord->addSection(); + $textRun1 = $section1->addTextRun(); + $textRun1->addText('Section 1 Paragraph 1'); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + $expected = '\bookfold\landscape'; + self::assertStringContainsString($expected, $content); + } + + public function testMirrorMargins(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setMirrorMargins(true); + $section1 = $phpWord->addSection(); + $textRun1 = $section1->addTextRun(); + $textRun1->addText('Section 1 Paragraph 1'); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + $expected = '\margmirror'; + self::assertStringContainsString($expected, $content); + $expected = '\facingp'; + self::assertStringContainsString($expected, $content); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Style/SectionTest.php b/tests/PhpWordTests/Writer/RTF/Style/SectionTest.php new file mode 100644 index 0000000000..f0f3a829e2 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/SectionTest.php @@ -0,0 +1,124 @@ +addSection(); + $style = $section->getStyle(); + $style->setVAlign($vAlign); + $writer = new RTF($phpWord); + self::assertStringContainsString($expect, $writer->getContent()); + } + + public static function verticalAlignProvider(): array + { + return [ + [VerticalJc::TOP, '\vertalt'], + [VerticalJc::CENTER, '\vertalc'], + [VerticalJc::BOTH, '\vertalj'], + [VerticalJc::BOTTOM, '\vertalb'], + ]; + } + + public function testNoVerticalAlignNoBreakType(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $style = $section->getStyle(); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + self::assertStringNotContainsString('vert', $content); + self::assertStringNotContainsString('sbk', $content); + } + + /** @dataProvider breakTypeProvider */ + public function testBreakType(string $breakType, string $expect): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $style = $section->getStyle(); + $style->setBreakType($breakType); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + self::assertStringContainsString($expect, $content); + } + + public static function breakTypeProvider(): array + { + return [ + ['nextPage', '\sbkpage'], + ['nextColumn', '\sbkcol'], + ['continuous', '\sbknone'], + ['evenPage', '\sbkeven'], + ['oddPage', '\sbkodd'], + ]; + } + + public function testColsNum(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $style = $section->getStyle(); + $style->setColsNum(5); + $style->setColsSpace(7); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + self::assertStringContainsString('\cols5', $content); + self::assertStringContainsString('\colsx7', $content); + } + + public function testPageSize(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $temp1 = $section->getStyle()->getOrientation(); + self::assertSame('portrait', $temp1); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + + self::assertStringContainsString('\sectd \pgwsxn11906\pghsxn16838', $content); + $temp2 = $section->getStyle()->getOrientation(); + self::assertSame('portrait', $temp2); + } + + public function testPageSizeBookFold(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $phpWord->getSettings()->setBookFoldPrinting(true); + $temp1 = $section->getStyle()->getOrientation(); + self::assertSame('portrait', $temp1); + $writer = new RTF($phpWord); + $content = $writer->getContent(); + + self::assertStringContainsString('\sectd \pgwsxn8419\pghsxn11906', $content); + $temp2 = $section->getStyle()->getOrientation(); + self::assertSame('landscape', $temp2); + } +} From a081351b878fdd562397327953ea5b93fb27dee8 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:21:01 -0800 Subject: [PATCH 140/153] Another Php8.5 Deprecation --- src/PhpWord/Writer/RTF/Style/Section.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/PhpWord/Writer/RTF/Style/Section.php b/src/PhpWord/Writer/RTF/Style/Section.php index 1f6372a9a9..d8d44a7222 100644 --- a/src/PhpWord/Writer/RTF/Style/Section.php +++ b/src/PhpWord/Writer/RTF/Style/Section.php @@ -75,9 +75,7 @@ public function write() 'evenPage' => '\sbkeven', 'oddPage' => '\sbkodd', ]; - if (isset($breakTypes[$style->getBreakType()])) { - $content .= $breakTypes[$style->getBreakType()]; - } + $content .= $breakTypes[(string) $style->getBreakType()] ?? ''; // Vertical Align $verticalAlign = [ From e592785b1ff2e343154fedd70186996a8ecf79c2 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:25:28 -0800 Subject: [PATCH 141/153] Mysterious Php8.0 Failure Try again with a meaningless comment change. --- src/PhpWord/Writer/RTF/Part/Header.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index 092e49d206..23aff155a0 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -442,7 +442,7 @@ private function registerBorderColor($style): void } /** - * Register fonts and colors. + * Register both fonts and colors. * * @param Style\AbstractStyle $style */ From 6297738b6ce4bfb90f2597d32ac660a032451f0f Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 23 Nov 2025 18:47:32 -0800 Subject: [PATCH 142/153] Rasamassen PR 2838 I made RTF usable some time ago. @rasamassen has recently done a lot of useful work to cover many missing areas. Since who knows when those changes will be merged, I intend to incorporate much of that work. I will do this with several pushes, each based on one or more of those changes. This one is based on PR #2838. --- phpstan-baseline.neon | 5 - phpstan-baseline.php73.neon | 5 - src/PhpWord/SimpleType/Border.php | 2 + src/PhpWord/Style/Border.php | 170 +++++++++- src/PhpWord/Writer/RTF/Style/Border.php | 158 +++++++--- src/PhpWord/Writer/RTF/Style/Paragraph.php | 11 +- src/PhpWord/Writer/RTF/Style/Section.php | 3 +- .../Writer/Word2007/Style/MarginBorder.php | 6 +- tests/PhpWordTests/Element/CommentTest.php | 13 + .../Writer/RTF/Style/BorderTest.php | 295 ++++++++++++++++++ .../Writer/RTF/Style/StyleTest.php | 19 -- 11 files changed, 592 insertions(+), 95 deletions(-) create mode 100644 tests/PhpWordTests/Writer/RTF/Style/BorderTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 94f20157ad..1d8df28ae7 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -480,11 +480,6 @@ parameters: count: 1 path: src/PhpWord/Style/AbstractStyle.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Border\\:\\:getBorderSize\\(\\) should return array\\ but returns array\\\\.$#" - count: 1 - path: src/PhpWord/Style/Border.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\:\\:getBgColor\\(\\) should return string but returns null\\.$#" count: 1 diff --git a/phpstan-baseline.php73.neon b/phpstan-baseline.php73.neon index 983a7ed033..db9f20c925 100644 --- a/phpstan-baseline.php73.neon +++ b/phpstan-baseline.php73.neon @@ -470,11 +470,6 @@ parameters: count: 1 path: src/PhpWord/Style/AbstractStyle.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Border\\:\\:getBorderSize\\(\\) should return array\\ but returns array\\\\.$#" - count: 1 - path: src/PhpWord/Style/Border.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\:\\:getBgColor\\(\\) should return string but returns null\\.$#" count: 1 diff --git a/src/PhpWord/SimpleType/Border.php b/src/PhpWord/SimpleType/Border.php index acd1c1a1b1..b3298fb7ed 100644 --- a/src/PhpWord/SimpleType/Border.php +++ b/src/PhpWord/SimpleType/Border.php @@ -48,7 +48,9 @@ final class Border extends AbstractEnum const THIN_THICK_LARGE_GAP = 'thinThickLargeGap'; //A thin line contained within a thick line with a large-sized intermediate gap const THIN_THICK_MEDIUM_GAP = 'thinThickMediumGap'; //A thick line contained within a thin line with a medium-sized intermediate gap const THIN_THICK_SMALL_GAP = 'thinThickSmallGap'; //A thick line contained within a thin line with a small intermediate gap + /** @deprecated 2.0 use THIN_THICK_THIN_LARGE_GAP */ const THIN_THICK_THINLARGE_GAP = 'thinThickThinLargeGap'; //A thin-thick-thin line with a large gap + const THIN_THICK_THIN_LARGE_GAP = 'thinThickThinLargeGap'; //A thin-thick-thin line with a large gap const THIN_THICK_THIN_MEDIUM_GAP = 'thinThickThinMediumGap'; //A thin-thick-thin line with a medium gap const THIN_THICK_THIN_SMALL_GAP = 'thinThickThinSmallGap'; //A thin-thick-thin line with a small gap const THREE_D_EMBOSS = 'threeDEmboss'; //A three-staged gradient line, getting darker towards the paragraph diff --git a/src/PhpWord/Style/Border.php b/src/PhpWord/Style/Border.php index d97b884fdb..aa92ad386f 100644 --- a/src/PhpWord/Style/Border.php +++ b/src/PhpWord/Style/Border.php @@ -46,6 +46,13 @@ class Border extends AbstractStyle */ protected $borderTopStyle; + /** + * Border Top Space. For section and paragraph borders only. + * + * @var float|int + */ + protected $borderTopSpace; + /** * Border Left Size. * @@ -67,6 +74,13 @@ class Border extends AbstractStyle */ protected $borderLeftStyle; + /** + * Border Left Space. For section and paragraph borders only. + * + * @var float|int + */ + protected $borderLeftSpace; + /** * Border Right Size. * @@ -88,6 +102,13 @@ class Border extends AbstractStyle */ protected $borderRightStyle; + /** + * Border Right Space. For section and paragraph borders only. + * + * @var float|int + */ + protected $borderRightSpace; + /** * Border Bottom Size. * @@ -109,6 +130,13 @@ class Border extends AbstractStyle */ protected $borderBottomStyle; + /** + * Border Bottom Space. For section and paragraph borders only. + * + * @var float|int + */ + protected $borderBottomSpace; + /** * Top margin spacing. * @@ -140,7 +168,7 @@ class Border extends AbstractStyle /** * Get border size. * - * @return int[] + * @return array */ public function getBorderSize() { @@ -233,6 +261,38 @@ public function setBorderStyle($value = null) return $this; } + /** + * Get border space. + * + * @return array + */ + public function getBorderSpace() + { + return [ + $this->getBorderTopSpace(), + $this->getBorderLeftSpace(), + $this->getBorderRightSpace(), + $this->getBorderBottomSpace(), + ]; + } + + /** + * Set border space. + * + * @param float|int $value + * + * @return self + */ + public function setBorderSpace($value = null) + { + $this->setBorderTopSpace($value); + $this->setBorderLeftSpace($value); + $this->setBorderRightSpace($value); + $this->setBorderBottomSpace($value); + + return $this; + } + /** * Get border top size. * @@ -292,7 +352,7 @@ public function getBorderTopStyle() } /** - * Set border top Style. + * Set border top style. * * @param string $value * @@ -305,6 +365,30 @@ public function setBorderTopStyle($value = null) return $this; } + /** + * Get border top space. + * + * @return float|int + */ + public function getBorderTopSpace() + { + return $this->borderTopSpace; + } + + /** + * Set border top space. + * + * @param float|int $value + * + * @return self + */ + public function setBorderTopSpace($value = null) + { + $this->borderTopSpace = $value; + + return $this; + } + /** * Get border left size. * @@ -377,6 +461,30 @@ public function setBorderLeftStyle($value = null) return $this; } + /** + * Get border left space. + * + * @return float|int + */ + public function getBorderLeftSpace() + { + return $this->borderLeftSpace; + } + + /** + * Set border left space. + * + * @param float|int $value + * + * @return self + */ + public function setBorderLeftSpace($value = null) + { + $this->borderLeftSpace = $value; + + return $this; + } + /** * Get border right size. * @@ -449,6 +557,30 @@ public function setBorderRightStyle($value = null) return $this; } + /** + * Get border right space. + * + * @return float|int + */ + public function getBorderRightSpace() + { + return $this->borderRightSpace; + } + + /** + * Set border right space. + * + * @param float|int $value + * + * @return self + */ + public function setBorderRightSpace($value = null) + { + $this->borderRightSpace = $value; + + return $this; + } + /** * Get border bottom size. * @@ -521,6 +653,30 @@ public function setBorderBottomStyle($value = null) return $this; } + /** + * Get border bottom space. + * + * @return float|int + */ + public function getBorderBottomSpace() + { + return $this->borderBottomSpace; + } + + /** + * Set border bottom space. + * + * @param float|int $value + * + * @return self + */ + public function setBorderBottomSpace($value = null) + { + $this->borderBottomSpace = $value; + + return $this; + } + /** * Check if any of the border is not null. * @@ -528,15 +684,7 @@ public function setBorderBottomStyle($value = null) */ public function hasBorder() { - $borders = $this->getBorderSize(); - if ($borders !== array_filter($borders, 'is_null')) { - return true; - } - $borders = $this->getBorderColor(); - if ($borders !== array_filter($borders, 'is_null')) { - return true; - } - $borders = $this->getBorderStyle(); + $borders = array_merge($this->getBorderSize(), $this->getBorderColor(), $this->getBorderStyle(), $this->getBorderSpace()); return $borders !== array_filter($borders, 'is_null'); } diff --git a/src/PhpWord/Writer/RTF/Style/Border.php b/src/PhpWord/Writer/RTF/Style/Border.php index c8dc943579..00bc9d3d32 100644 --- a/src/PhpWord/Writer/RTF/Style/Border.php +++ b/src/PhpWord/Writer/RTF/Style/Border.php @@ -18,6 +18,9 @@ namespace PhpOffice\PhpWord\Writer\RTF\Style; +use PhpOffice\PhpWord\SimpleType\Border as BorderType; +use PhpOffice\PhpWord\Style\Border as BorderStyle; + /** * Border style writer. * @@ -26,18 +29,11 @@ class Border extends AbstractStyle { /** - * Sizes. - * - * @var array - */ - private $sizes = []; - - /** - * Colors. + * Type. Can be section, paragraph, font, row, or cell. * - * @var array + * @var string */ - private $colors = []; + private $type = 'paragraph'; /** * Write style. @@ -45,24 +41,32 @@ class Border extends AbstractStyle * @return string */ public function write() + { + $style = $this->getStyle(); + + return ($style instanceof BorderStyle) ? $this->writeStyle($style) : ''; + } + + private function writeStyle(BorderStyle $style): string { $content = ''; + if ($this->type == 'section') { + // Page border measure + // 8 = from text, infront off; 32 = from edge, infront on; 40 = from edge, infront off + $content .= '\pgbrdropt32'; + } + $sides = ['top', 'left', 'right', 'bottom']; - $sizeCount = count($this->sizes); + $sizeCount = ($this->type === 'font') ? 1 : 4; - // Page border measure - // 8 = from text, infront off; 32 = from edge, infront on; 40 = from edge, infront off - $content .= '\pgbrdropt32'; + $sizes = $style->getBorderSize(); + $colors = $style->getBorderColor(); + $styles = $style->getBorderStyle(); + $spaces = $style->getBorderSpace(); for ($i = 0; $i < $sizeCount; ++$i) { - if ($this->sizes[$i] !== null) { - $color = null; - if (isset($this->colors[$i])) { - $color = $this->colors[$i]; - } - $content .= $this->writeSide($sides[$i], $this->sizes[$i], $color); - } + $content .= $this->writeSide($sides[$i], $sizes[$i], $colors[$i], $styles[$i], $spaces[$i]); } return $content; @@ -72,53 +76,113 @@ public function write() * Write side. * * @param string $side - * @param int $width + * @param float|int $width * @param string $color + * @param string $style + * @param float|int $space * * @return string */ - private function writeSide($side, $width, $color = '') + private function writeSide($side, $width, $color, $style, $space) { - /** @var \PhpOffice\PhpWord\Writer\RTF $rtfWriter */ - $rtfWriter = $this->getParentWriter(); + if ($width == null && $color === null && $style === null) { + if ($this->type == 'font' || $this->type == 'row' || $this->type == 'cell') { + return ''; + } elseif ($space === null) { + return ''; + } + } + + $types = [ + 'section' => '\pgbrdr', + 'paragraph' => '\brdr', + 'font' => '\chbrdr', + 'row' => '\trbrdr', + 'cell' => '\clbrdr', + ]; + + $styles = [ + BorderType::SINGLE => '\brdrs', + BorderType::DASH_DOT_STROKED => '\brdrdashdotstr', + BorderType::DASHED => '\brdrdash', + BorderType::DASH_SMALL_GAP => '\brdrdashsm', + BorderType::DOT_DASH => '\brdrdashd', + BorderType::DOT_DOT_DASH => '\brdrdashdd', + BorderType::DOTTED => '\brdrdot', + BorderType::DOUBLE => '\brdrdb', + BorderType::DOUBLE_WAVE => '\brdrwavydb', + BorderType::INSET => '\brdrinset', + BorderType::NIL => '\brdrnil', + BorderType::NONE => '\brdrnone', + BorderType::OUTSET => '\brdroutset', + BorderType::THICK => '\brdrth', + BorderType::THICK_THIN_LARGE_GAP => '\brdrtnthlg', + BorderType::THICK_THIN_MEDIUM_GAP => '\brdrtnthmg', + BorderType::THICK_THIN_SMALL_GAP => '\brdrtnthsg', + BorderType::THIN_THICK_LARGE_GAP => '\brdrthtnlg', + BorderType::THIN_THICK_MEDIUM_GAP => '\brdrthtnmg', + BorderType::THIN_THICK_SMALL_GAP => '\brdrthtnsg', + BorderType::THIN_THICK_THIN_LARGE_GAP => '\brdrtnthtnlg', + BorderType::THIN_THICK_THIN_MEDIUM_GAP => '\brdrtnthtnmg', + BorderType::THIN_THICK_THIN_SMALL_GAP => '\brdrtnthtnsg', + BorderType::THREE_D_EMBOSS => '\brdremboss', + BorderType::THREE_D_ENGRAVE => '\brdrengrave', + BorderType::TRIPLE => '\brdrtriple', + BorderType::WAVE => '\brdrwavy', + ]; + + /** @var \PhpOffice\PhpWord\Writer\RTF $parentWriter */ + $parentWriter = $this->getParentWriter(); $colorIndex = 0; - if ($rtfWriter !== null) { - $colorTable = $rtfWriter->getColorTable(); - $index = array_search($color, $colorTable); + if ($parentWriter !== null) { + $index = array_search($color, $parentWriter->getColorTable()); if ($index !== false) { $colorIndex = $index + 1; } } $content = ''; + if (isset($types[$this->type])) { + if ($this->type == 'font') { + $content .= $types[$this->type]; // character borders cannot vary by side + } else { + $content .= $types[$this->type] . substr($side, 0, 1); + } + } else { + return ''; + } - $content .= '\pgbrdr' . substr($side, 0, 1); - $content .= '\brdrs'; // Single-thickness border; @todo Get other type of border - $content .= '\brdrw' . round($width); // Width - $content .= '\brdrcf' . $colorIndex; // Color - $content .= '\brsp480'; // Space in twips between borders and the paragraph (24pt, following OOXML) + if (isset($styles[$style])) { + $content .= $styles[$style]; + } else { + $content .= '\brdrs'; // default style + } + $content .= $this->getValueIf($width !== null, '\brdrw' . round($width ?? 0)); // Width + $content .= $this->getValueIf($color !== null, '\brdrcf' . $colorIndex); // Color + + // Space + if ($this->type == 'section') { + $space = $space !== null ? $space : '480'; // section default is 480 + } elseif ($this->type == 'paragraph') { + if ($side == 'top' || $side == 'bottom') { + $space = $space !== null ? $space : '20'; // paragraph top|bottom default is 20 + } elseif ($side == 'left' || $side == 'right') { + $space = $space !== null ? $space : '80'; // paragraph left|rigth default is 80 + } + } + $content .= $this->getValueIf($space !== null, '\brsp' . round($space ?? 0)); $content .= ' '; return $content; } /** - * Set sizes. - * - * @param int[] $value - */ - public function setSizes($value): void - { - $this->sizes = $value; - } - - /** - * Set colors. + * Set type. * - * @param string[] $value + * @param string $value */ - public function setColors($value): void + public function setType($value = 'paragraph'): void { - $this->colors = $value; + $this->type = $value; } } diff --git a/src/PhpWord/Writer/RTF/Style/Paragraph.php b/src/PhpWord/Writer/RTF/Style/Paragraph.php index a75a077794..33b33cdda0 100644 --- a/src/PhpWord/Writer/RTF/Style/Paragraph.php +++ b/src/PhpWord/Writer/RTF/Style/Paragraph.php @@ -100,9 +100,6 @@ public function write() $content .= $this->writeChildStyle('Spacing', $style->getSpace()); // Future: Add Shading - $styles = $style->getStyleValues(); - $content .= $this->writeTabs($styles['tabs']); - // Contextual Spacing $content .= $this->getValueIf($style->hasContextualSpacing(), '\contextualspace'); // Future: Add Text Alignment @@ -121,6 +118,14 @@ public function write() $styles = $style->getStyleValues(); $content .= $this->writeTabs($styles['tabs']); + // Borders + if ($style->hasBorder()) { + $styleWriter = new Border($style); + $styleWriter->setParentWriter($this->getParentWriter()); + $styleWriter->setType('paragraph'); + $content .= $styleWriter->write(); + } + return $content . ' '; } diff --git a/src/PhpWord/Writer/RTF/Style/Section.php b/src/PhpWord/Writer/RTF/Style/Section.php index d8d44a7222..fd147366e3 100644 --- a/src/PhpWord/Writer/RTF/Style/Section.php +++ b/src/PhpWord/Writer/RTF/Style/Section.php @@ -91,8 +91,7 @@ public function write() if ($style->hasBorder()) { $styleWriter = new Border($style); $styleWriter->setParentWriter($this->getParentWriter()); - $styleWriter->setSizes($style->getBorderSize()); - $styleWriter->setColors($style->getBorderColor()); + $styleWriter->setType('section'); $content .= $styleWriter->write(); } diff --git a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php index ed405442a1..4705b2e776 100644 --- a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php +++ b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php @@ -30,7 +30,7 @@ class MarginBorder extends AbstractStyle /** * Sizes. * - * @var int[] + * @var array */ private $sizes = []; @@ -78,7 +78,7 @@ public function write(): void * Write side. * * @param string $side - * @param ?int $width + * @param float|int $width * @param ?string $color * @param string $borderStyle */ @@ -109,7 +109,7 @@ private function writeSide(XMLWriter $xmlWriter, $side, $width, $color = null, $ /** * Set sizes. * - * @param int[] $value + * @param array $value */ public function setSizes($value): void { diff --git a/tests/PhpWordTests/Element/CommentTest.php b/tests/PhpWordTests/Element/CommentTest.php index c066429839..5b5aaeacfe 100644 --- a/tests/PhpWordTests/Element/CommentTest.php +++ b/tests/PhpWordTests/Element/CommentTest.php @@ -83,6 +83,19 @@ public function testTwoCommentsOnSameText(): void self::assertEquals($text->getCommentsRangeEnd()->getItem(1)->getElementId(), $comment2->getElementId()); } + public function testElementId(): void + { + $section = new Section(0); + $text = $section->addText('Text'); + + $comment1 = new Comment('Author1', new DateTime(), 'A1'); + $comment1->addText('Comment1'); + $temp1 = $comment1->getElementId(); + self::assertNull($temp1); + $text->setCommentRangeEnd($comment1); + self::assertNotNull($comment1->getElementId()); + } + /** * Add text. */ diff --git a/tests/PhpWordTests/Writer/RTF/Style/BorderTest.php b/tests/PhpWordTests/Writer/RTF/Style/BorderTest.php new file mode 100644 index 0000000000..2db4407b16 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Style/BorderTest.php @@ -0,0 +1,295 @@ +write()); + } + + /** + * Test Border styles in paragraph. + * See page 89-90 of RTF Specification 1.9.1 for Paragraph Borders. + */ + public function testBorderBasic(): void + { + $parentWriter = new RTF(); + $style = new BorderStyle(); + $writer = new BorderWriter($style); + $writer->setParentWriter($parentWriter); + + $expect = ''; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderSize(1); + $expect = '\brdrt\brdrs\brdrw1\brsp20 '; + $expect .= '\brdrl\brdrs\brdrw1\brsp80 '; + $expect .= '\brdrr\brdrs\brdrw1\brsp80 '; + $expect .= '\brdrb\brdrs\brdrw1\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test Border not all all four sides. + * See page 89-90 of RTF Specification 1.9.1 for Paragraph Borders. + */ + public function testBorderSide(): void + { + $parentWriter = new RTF(); + $style = new BorderStyle(); + $writer = new BorderWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setBorderLeftSize(1); + $expect = '\brdrl\brdrs\brdrw1\brsp80 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderBottomSize(100); + $expect = '\brdrl\brdrs\brdrw1\brsp80 '; + $expect .= '\brdrb\brdrs\brdrw100\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test Border styles in paragraph. + * See page 76 of RTF Specification 1.9.1 for Page Borders. + * See page 89-90 of RTF Specification 1.9.1 for Paragraph Borders. + * See page 103 of RTF Specification 1.9.1 for Row Borders and Cell Borders. + * See page 139 of RTF Specification 1.9.1 for Character Borders. + */ + public function testBorderType(): void + { + $parentWriter = new RTF(); + $style = new BorderStyle(); + $writer = new BorderWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setBorderSize(1); + + $writer->setType('section'); + $expect = '\pgbrdropt32'; + $expect .= '\pgbrdrt\brdrs\brdrw1\brsp480 '; + $expect .= '\pgbrdrl\brdrs\brdrw1\brsp480 '; + $expect .= '\pgbrdrr\brdrs\brdrw1\brsp480 '; + $expect .= '\pgbrdrb\brdrs\brdrw1\brsp480 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $writer->setType('paragraph'); + $expect = '\brdrt\brdrs\brdrw1\brsp20 '; + $expect .= '\brdrl\brdrs\brdrw1\brsp80 '; + $expect .= '\brdrr\brdrs\brdrw1\brsp80 '; + $expect .= '\brdrb\brdrs\brdrw1\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $writer->setType('font'); + $expect = '\chbrdr\brdrs\brdrw1 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $writer->setType('row'); + $expect = '\trbrdrt\brdrs\brdrw1 '; + $expect .= '\trbrdrl\brdrs\brdrw1 '; + $expect .= '\trbrdrr\brdrs\brdrw1 '; + $expect .= '\trbrdrb\brdrs\brdrw1 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $writer->setType('cell'); + $expect = '\clbrdrt\brdrs\brdrw1 '; + $expect .= '\clbrdrl\brdrs\brdrw1 '; + $expect .= '\clbrdrr\brdrs\brdrw1 '; + $expect .= '\clbrdrb\brdrs\brdrw1 '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test Border style. + * See page 89-90 of RTF Specification 1.9.1 for Paragraph Borders. + */ + public function testBorderStyle(): void + { + $parentWriter = new RTF(); + $style = new BorderStyle(); + $writer = new BorderWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setBorderStyle(BorderType::DASH_DOT_STROKED); + $expect = '\brdrt\brdrdashdotstr\brsp20 '; + $expect .= '\brdrl\brdrdashdotstr\brsp80 '; + $expect .= '\brdrr\brdrdashdotstr\brsp80 '; + $expect .= '\brdrb\brdrdashdotstr\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopStyle(BorderType::DASHED); + $style->setBorderLeftStyle(BorderType::DASH_SMALL_GAP); + $style->setBorderRightStyle(BorderType::DOT_DASH); + $style->setBorderBottomStyle(BorderType::DOT_DOT_DASH); + $expect = '\brdrt\brdrdash\brsp20 '; + $expect .= '\brdrl\brdrdashsm\brsp80 '; + $expect .= '\brdrr\brdrdashd\brsp80 '; + $expect .= '\brdrb\brdrdashdd\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopStyle(BorderType::DOTTED); + $style->setBorderLeftStyle(BorderType::DOUBLE); + $style->setBorderRightStyle(BorderType::DOUBLE_WAVE); + $style->setBorderBottomStyle(BorderType::INSET); + $expect = '\brdrt\brdrdot\brsp20 '; + $expect .= '\brdrl\brdrdb\brsp80 '; + $expect .= '\brdrr\brdrwavydb\brsp80 '; + $expect .= '\brdrb\brdrinset\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopStyle(BorderType::NIL); + $style->setBorderLeftStyle(BorderType::NONE); + $style->setBorderRightStyle(BorderType::OUTSET); + $style->setBorderBottomStyle(BorderType::THICK); + $expect = '\brdrt\brdrnil\brsp20 '; + $expect .= '\brdrl\brdrnone\brsp80 '; + $expect .= '\brdrr\brdroutset\brsp80 '; + $expect .= '\brdrb\brdrth\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopStyle(BorderType::THICK_THIN_LARGE_GAP); + $style->setBorderLeftStyle(BorderType::THICK_THIN_MEDIUM_GAP); + $style->setBorderRightStyle(BorderType::THICK_THIN_SMALL_GAP); + $style->setBorderBottomStyle(BorderType::THIN_THICK_LARGE_GAP); + $expect = '\brdrt\brdrtnthlg\brsp20 '; + $expect .= '\brdrl\brdrtnthmg\brsp80 '; + $expect .= '\brdrr\brdrtnthsg\brsp80 '; + $expect .= '\brdrb\brdrthtnlg\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopStyle(BorderType::THIN_THICK_MEDIUM_GAP); + $style->setBorderLeftStyle(BorderType::THIN_THICK_SMALL_GAP); + $style->setBorderRightStyle(BorderType::THIN_THICK_THIN_LARGE_GAP); + $style->setBorderBottomStyle(BorderType::THIN_THICK_THIN_MEDIUM_GAP); + $expect = '\brdrt\brdrthtnmg\brsp20 '; + $expect .= '\brdrl\brdrthtnsg\brsp80 '; + $expect .= '\brdrr\brdrtnthtnlg\brsp80 '; + $expect .= '\brdrb\brdrtnthtnmg\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopStyle(BorderType::THIN_THICK_THIN_SMALL_GAP); + $style->setBorderLeftStyle(BorderType::THREE_D_EMBOSS); + $style->setBorderRightStyle(BorderType::THREE_D_ENGRAVE); + $style->setBorderBottomStyle(BorderType::TRIPLE); + $expect = '\brdrt\brdrtnthtnsg\brsp20 '; + $expect .= '\brdrl\brdremboss\brsp80 '; + $expect .= '\brdrr\brdrengrave\brsp80 '; + $expect .= '\brdrb\brdrtriple\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderStyle(BorderType::WAVE); + $expect = '\brdrt\brdrwavy\brsp20 '; + $expect .= '\brdrl\brdrwavy\brsp80 '; + $expect .= '\brdrr\brdrwavy\brsp80 '; + $expect .= '\brdrb\brdrwavy\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test Border size. + * See page 89-90 of RTF Specification 1.9.1 for Paragraph Borders. + */ + public function testBorderSize(): void + { + $parentWriter = new RTF(); + $style = new BorderStyle(); + $writer = new BorderWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setBorderSize(100); + $expect = '\brdrt\brdrs\brdrw100\brsp20 '; + $expect .= '\brdrl\brdrs\brdrw100\brsp80 '; + $expect .= '\brdrr\brdrs\brdrw100\brsp80 '; + $expect .= '\brdrb\brdrs\brdrw100\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopSize(200); + $style->setBorderLeftSize(150); + $style->setBorderRightSize(50); + $style->setBorderBottomSize(20); + $expect = '\brdrt\brdrs\brdrw200\brsp20 '; + $expect .= '\brdrl\brdrs\brdrw150\brsp80 '; + $expect .= '\brdrr\brdrs\brdrw50\brsp80 '; + $expect .= '\brdrb\brdrs\brdrw20\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + } + + /** + * Test Border colors. + * See page 89-90 of RTF Specification 1.9.1 for Paragraph Borders. + * + * Create test when paragraph inherits border. + */ + + /** + * Test Border space. + * See page 89-90 of RTF Specification 1.9.1 for Paragraph Borders. + */ + public function testBorderSpace(): void + { + $parentWriter = new RTF(); + $style = new BorderStyle(); + $writer = new BorderWriter($style); + $writer->setParentWriter($parentWriter); + + $style->setBorderSpace(100); + $expect = '\brdrt\brdrs\brsp100 '; + $expect .= '\brdrl\brdrs\brsp100 '; + $expect .= '\brdrr\brdrs\brsp100 '; + $expect .= '\brdrb\brdrs\brsp100 '; + self::assertEquals($expect, $this->removeCr($writer)); + + $style->setBorderTopSpace(200); + $style->setBorderLeftSpace(150); + $style->setBorderRightSpace(50); + $style->setBorderBottomSpace(20); + $expect = '\brdrt\brdrs\brsp200 '; + $expect .= '\brdrl\brdrs\brsp150 '; + $expect .= '\brdrr\brdrs\brsp50 '; + $expect .= '\brdrb\brdrs\brsp20 '; + self::assertEquals($expect, $this->removeCr($writer)); + + // Space doesn't matter for fonts. + $writer->setType('font'); + $expect = ''; + self::assertEquals($expect, $this->removeCr($writer)); + + // Space doesn't matter for rows. + $writer->setType('row'); + $expect = ''; + self::assertEquals($expect, $this->removeCr($writer)); + + // Space doesn't matter for cells. + $writer->setType('cell'); + $expect = ''; + self::assertEquals($expect, $this->removeCr($writer)); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php b/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php index 253a174cda..019a3f8134 100644 --- a/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php +++ b/tests/PhpWordTests/Writer/RTF/Style/StyleTest.php @@ -19,7 +19,6 @@ namespace PhpOffice\PhpWordTests\Writer\RTF; use PhpOffice\PhpWord\Writer\RTF; -use PhpOffice\PhpWord\Writer\RTF\Style\Border; /** * Test class for PhpOffice\PhpWord\Writer\RTF\Style subnamespace. @@ -50,24 +49,6 @@ public function testEmptyStyles(): void } } - public function testBorderWithNonRegisteredColors(): void - { - $border = new Border(); - $border->setSizes([1, 2, 3, 4]); - $border->setColors(['#FF0000', '#FF0000', '#FF0000', '#FF0000']); - $border->setSizes([20, 20, 20, 20]); - - $content = $border->write(); - - $expected = '\pgbrdropt32'; - $expected .= '\pgbrdrt\brdrs\brdrw20\brdrcf0\brsp480 '; - $expected .= '\pgbrdrl\brdrs\brdrw20\brdrcf0\brsp480 '; - $expected .= '\pgbrdrr\brdrs\brdrw20\brdrcf0\brsp480 '; - $expected .= '\pgbrdrb\brdrs\brdrw20\brdrcf0\brsp480 '; - - self::assertEquals($expected, $content); - } - public function testPageBreakLineHeight(): void { $parentWriter = new RTF(); From 7422bceac40ac06f7caaacb6a2dcda52d86a5245 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 23 Nov 2025 20:13:12 -0800 Subject: [PATCH 143/153] Another Php8.5 Deprecation --- src/PhpWord/Writer/RTF/Part/Header.php | 4 +++ src/PhpWord/Writer/RTF/Style/Border.php | 40 ++++++++++++------------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index 23aff155a0..505bc65d99 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -24,6 +24,7 @@ use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Numbering; +use PhpOffice\PhpWord\Style\Paragraph; use PhpOffice\PhpWord\Style\Table; /** @@ -468,6 +469,9 @@ private function registerHeaderItems($style): void if ($style instanceof Numbering) { $this->registerList($this->listTable, $style, $defaultFont); } + if ($style instanceof Paragraph) { + $this->registerBorderColor($style); + } } /** diff --git a/src/PhpWord/Writer/RTF/Style/Border.php b/src/PhpWord/Writer/RTF/Style/Border.php index 00bc9d3d32..6514010c17 100644 --- a/src/PhpWord/Writer/RTF/Style/Border.php +++ b/src/PhpWord/Writer/RTF/Style/Border.php @@ -148,30 +148,28 @@ private function writeSide($side, $width, $color, $style, $space) } else { $content .= $types[$this->type] . substr($side, 0, 1); } - } else { - return ''; - } - - if (isset($styles[$style])) { - $content .= $styles[$style]; - } else { - $content .= '\brdrs'; // default style - } - $content .= $this->getValueIf($width !== null, '\brdrw' . round($width ?? 0)); // Width - $content .= $this->getValueIf($color !== null, '\brdrcf' . $colorIndex); // Color - // Space - if ($this->type == 'section') { - $space = $space !== null ? $space : '480'; // section default is 480 - } elseif ($this->type == 'paragraph') { - if ($side == 'top' || $side == 'bottom') { - $space = $space !== null ? $space : '20'; // paragraph top|bottom default is 20 - } elseif ($side == 'left' || $side == 'right') { - $space = $space !== null ? $space : '80'; // paragraph left|rigth default is 80 + if (isset($style, $styles[$style])) { + $content .= $styles[$style]; + } else { + $content .= '\brdrs'; // default style + } + $content .= $this->getValueIf($width !== null, '\brdrw' . round($width ?? 0)); // Width + $content .= $this->getValueIf($color !== null, '\brdrcf' . $colorIndex); // Color + + // Space + if ($this->type == 'section') { + $space = $space !== null ? $space : '480'; // section default is 480 + } elseif ($this->type == 'paragraph') { + if ($side == 'top' || $side == 'bottom') { + $space = $space !== null ? $space : '20'; // paragraph top|bottom default is 20 + } elseif ($side == 'left' || $side == 'right') { + $space = $space !== null ? $space : '80'; // paragraph left|rigth default is 80 + } } + $content .= $this->getValueIf($space !== null, '\brsp' . round($space ?? 0)); + $content .= ' '; } - $content .= $this->getValueIf($space !== null, '\brsp' . round($space ?? 0)); - $content .= ' '; return $content; } From 35efca03059a1f874deb38bde9d9d2bf4960943b Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 23 Nov 2025 21:08:01 -0800 Subject: [PATCH 144/153] RTF Writer Border Styles for Paragraphs --- .../Writer/RTF/Element/AbstractElement.php | 1 + .../Writer/RTF/Style/BorderTest.php | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/PhpWord/Writer/RTF/Element/AbstractElement.php b/src/PhpWord/Writer/RTF/Element/AbstractElement.php index effadc9c6e..ea6026898b 100644 --- a/src/PhpWord/Writer/RTF/Element/AbstractElement.php +++ b/src/PhpWord/Writer/RTF/Element/AbstractElement.php @@ -150,6 +150,7 @@ protected function writeOpening() } $styleWriter = new ParagraphStyleWriter($this->paragraphStyle); + $styleWriter->setParentWriter($this->parentWriter); $styleWriter->setNestedLevel($this->element->getNestedLevel()); return $styleWriter->write(); diff --git a/tests/PhpWordTests/Writer/RTF/Style/BorderTest.php b/tests/PhpWordTests/Writer/RTF/Style/BorderTest.php index 2db4407b16..75660cf6be 100644 --- a/tests/PhpWordTests/Writer/RTF/Style/BorderTest.php +++ b/tests/PhpWordTests/Writer/RTF/Style/BorderTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWordTests\Writer\RTF\Style; +use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\SimpleType\Border as BorderType; use PhpOffice\PhpWord\Style\Border as BorderStyle; use PhpOffice\PhpWord\Writer\RTF; @@ -292,4 +293,33 @@ public function testBorderSpace(): void $expect = ''; self::assertEquals($expect, $this->removeCr($writer)); } + + public function testBorderColor(): void + { + $phpWord = new PhpWord(); + + $paragraphStyleName = 'P-Style'; + $pstyle = $phpWord->addParagraphStyle($paragraphStyleName, [ + 'spaceAfter' => 95, + 'borderTopSize' => 12, + 'borderTopColor' => 'FF0000', + 'borderBottomSize' => 12, + 'borderBottomColor' => '00FF00', + 'borderLeftSize' => 12, + 'borderLeftColor' => '0000FF', + 'borderRightSize' => 12, + 'borderRightColor' => 'FFFF00', + ]); + + $section = $phpWord->addSection(); + $section->addText('Hello', null, $pstyle); + $section->addText('Goodbye'); + + $writer = new RTF($phpWord); + $content = $writer->getContent(); + $expected = '{\colortbl;\red0\green0\blue0;\red255\green0\blue0;\red0\green0\blue255;\red255\green255\blue0;\red0\green255\blue0;}'; + self::assertStringContainsString($expected, $content); + $expected = '\pard\sa95\widctlpar\brdrt\brdrs\brdrw12\brdrcf2\brsp20 \brdrl\brdrs\brdrw12\brdrcf3\brsp80 \brdrr\brdrs\brdrw12\brdrcf4\brsp80 \brdrb\brdrs\brdrw12\brdrcf5\brsp20 {Hello}\par'; + self::assertStringContainsString($expected, $content); + } } From 21711d167ddf82e07276821347378e73b231d7e2 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:55:12 -0800 Subject: [PATCH 145/153] Some Fixes Fix #2490. Fix #2188. --- phpstan-baseline.neon | 5 - phpstan-baseline.php73.neon | 5 - src/PhpWord/IOFactory.php | 2 +- src/PhpWord/PhpWord.php | 12 +- src/PhpWord/Reader/MsDoc.php | 6 +- src/PhpWord/TemplateProcessor.php | 1 + src/PhpWord/Writer/EPub3.php | 1 + src/PhpWord/Writer/HTML.php | 1 + .../Writer/HTML/Element/PreserveText.php | 196 ++++++++++++++++++ src/PhpWord/Writer/ODText.php | 1 + src/PhpWord/Writer/PDF.php | 1 + src/PhpWord/Writer/PDF/DomPDF.php | 2 + src/PhpWord/Writer/PDF/MPDF.php | 1 + src/PhpWord/Writer/PDF/TCPDF.php | 1 + src/PhpWord/Writer/RTF.php | 1 + .../Writer/RTF/Element/AbstractElement.php | 1 + .../Writer/RTF/Style/AbstractStyle.php | 4 +- src/PhpWord/Writer/RTF/Style/Paragraph.php | 1 + src/PhpWord/Writer/Word2007.php | 1 + tests/PhpWordTests/TemplateProcessorTest.php | 28 ++- .../Writer/HTML/Element/PreserveTextTest.php | 42 ++++ tests/PhpWordTests/Writer/NoPharTest.php | 65 ++++++ 22 files changed, 360 insertions(+), 18 deletions(-) create mode 100644 src/PhpWord/Writer/HTML/Element/PreserveText.php create mode 100644 tests/PhpWordTests/Writer/HTML/Element/PreserveTextTest.php create mode 100644 tests/PhpWordTests/Writer/NoPharTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 1d8df28ae7..db563fa121 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -80,11 +80,6 @@ parameters: count: 1 path: src/PhpWord/IOFactory.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:getDefaultFontSize\\(\\) should return int but returns float\\|int\\.$#" - count: 1 - path: src/PhpWord/PhpWord.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\AbstractReader\\:\\:openFile\\(\\) should return resource but return statement is missing\\.$#" count: 1 diff --git a/phpstan-baseline.php73.neon b/phpstan-baseline.php73.neon index db9f20c925..683d13703f 100644 --- a/phpstan-baseline.php73.neon +++ b/phpstan-baseline.php73.neon @@ -70,11 +70,6 @@ parameters: count: 1 path: src/PhpWord/IOFactory.php - - - message: "#^Method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:getDefaultFontSize\\(\\) should return int but returns float\\|int\\.$#" - count: 1 - path: src/PhpWord/PhpWord.php - - message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\AbstractReader\\:\\:openFile\\(\\) should return resource but return statement is missing\\.$#" count: 1 diff --git a/src/PhpWord/IOFactory.php b/src/PhpWord/IOFactory.php index 50c419cae2..64431e9d74 100644 --- a/src/PhpWord/IOFactory.php +++ b/src/PhpWord/IOFactory.php @@ -36,7 +36,7 @@ abstract class IOFactory */ public static function createWriter(PhpWord $phpWord, $name = 'Word2007') { - if ($name !== 'WriterInterface' && !in_array($name, ['ODText', 'RTF', 'Word2007', 'HTML', 'PDF', 'EPub3'], true)) { + if ($name !== 'WriterInterface' && !in_array($name, ['ODText', 'RTF', 'Word2007', 'HTML', 'PDF', 'EPub3', 'PDF\DomPDF', 'PDF\MPDF', 'PDF\TCPDF'], true)) { throw new Exception("\"{$name}\" is not a valid writer."); } diff --git a/src/PhpWord/PhpWord.php b/src/PhpWord/PhpWord.php index 3e23d52fe6..de7f3e0874 100644 --- a/src/PhpWord/PhpWord.php +++ b/src/PhpWord/PhpWord.php @@ -296,7 +296,7 @@ public function getDefaultFontColor(): string /** * Get default font size. * - * @return int + * @return float|int */ public function getDefaultFontSize() { @@ -363,6 +363,16 @@ public function save($filename, $format = 'Word2007', $download = false) return true; } + public static function noPhar(string $path): void + { + $dontUse = false; + if (filter_var($path, FILTER_VALIDATE_URL) || (preg_match('/^([\w\s\x00-\x1f]+):/u', $path) && !preg_match('/^([\w]+):/u', $path))) { + if (!preg_match('~^((s3):)|(php://(output|memory|temp)$)~', $path)) { + throw new Exception('Invalid protocol used in filename'); + } + } + } + /** * Create new section. * diff --git a/src/PhpWord/Reader/MsDoc.php b/src/PhpWord/Reader/MsDoc.php index caee531cda..3db4e5a721 100644 --- a/src/PhpWord/Reader/MsDoc.php +++ b/src/PhpWord/Reader/MsDoc.php @@ -77,7 +77,7 @@ class MsDoc extends AbstractReader implements ReaderInterface private $arrayFib = []; /** - * @var string[] + * @var array> */ private $arrayFonts = []; @@ -1263,7 +1263,7 @@ private function readRecordSttbfFfn(): void $xszAlt .= mb_chr($char, 'UTF-8'); } while ($char != 0); } - $this->arrayFonts[] = [ //* @phpstan-ignore-line + $this->arrayFonts[] = [ 'main' => $xszFfn, 'alt' => $xszAlt, ]; @@ -1936,7 +1936,7 @@ private function readPrl($data, $pos, $cbNum) case 0x4F: $oStylePrl->styleFont['name'] = ''; if (isset($this->arrayFonts[$operand])) { - $oStylePrl->styleFont['name'] = $this->arrayFonts[$operand]['main']; //* @phpstan-ignore-line + $oStylePrl->styleFont['name'] = $this->arrayFonts[$operand]['main']; } break; diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 31f06c14d4..75e60baa75 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -1046,6 +1046,7 @@ protected function savePartWithRels($fileName, $xml): void */ public function saveAs($fileName): void { + PhpWord::noPhar($fileName); $tempFileName = $this->save(); if (file_exists($fileName)) { diff --git a/src/PhpWord/Writer/EPub3.php b/src/PhpWord/Writer/EPub3.php index 71227a1745..7ad32bd435 100644 --- a/src/PhpWord/Writer/EPub3.php +++ b/src/PhpWord/Writer/EPub3.php @@ -63,6 +63,7 @@ public function __construct(?PhpWord $phpWord = null) */ public function save(string $filename): void { + PhpWord::noPhar($filename); $filename = $this->getTempFile($filename); $zip = $this->getZipArchive($filename); diff --git a/src/PhpWord/Writer/HTML.php b/src/PhpWord/Writer/HTML.php index e5d91adfb2..b67a008e40 100644 --- a/src/PhpWord/Writer/HTML.php +++ b/src/PhpWord/Writer/HTML.php @@ -89,6 +89,7 @@ public function __construct(?PhpWord $phpWord = null) */ public function save(string $filename): void { + PhpWord::noPhar($filename); $this->writeFile($this->openFile($filename), $this->getContent()); } diff --git a/src/PhpWord/Writer/HTML/Element/PreserveText.php b/src/PhpWord/Writer/HTML/Element/PreserveText.php new file mode 100644 index 0000000000..6b46988fff --- /dev/null +++ b/src/PhpWord/Writer/HTML/Element/PreserveText.php @@ -0,0 +1,196 @@ +processFontStyle(); + + /** @var \PhpOffice\PhpWord\Element\PreserveText */ + $element = $this->element; + $text = $element->getText(); + if (is_array($text)) { + $text = implode('', $text); + } + + $text = $this->parentWriter->escapeHTML($text ?? ''); + if (!$this->withoutP && !trim($text)) { + $text = ' '; + } + + $content = ''; + $content .= $this->writeOpening(); + $content .= $this->openingTags; + $content .= $text; + $content .= $this->closingTags; + $content .= $this->writeClosing(); + + return $content; + } + + /** + * Write opening. + * + * @return string + */ + protected function writeOpening() + { + $content = ''; + if (!$this->withoutP) { + $style = $this->getParagraphStyle(); + $content .= ""; + } + + return $content; + } + + /** + * Write ending. + * + * @return string + */ + protected function writeClosing() + { + $content = ''; + + if (!$this->withoutP) { + $content .= '

      ' . PHP_EOL; + } + + return $content; + } + + /** + * Write paragraph style. + * + * @return string + */ + private function getParagraphStyle() + { + /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + $element = $this->element; + $style = ''; + if (!method_exists($element, 'getParagraphStyle')) { + return $style; + } + + $paragraphStyle = $element->getParagraphStyle(); + $pStyleIsObject = ($paragraphStyle instanceof Paragraph); + if ($pStyleIsObject) { + $styleWriter = new ParagraphStyleWriter($paragraphStyle); + $styleWriter->setParentWriter($this->parentWriter); + $style = $styleWriter->write(); + } elseif (is_string($paragraphStyle)) { + $style = $paragraphStyle; + } + if ($style) { + $attribute = $pStyleIsObject ? 'style' : 'class'; + $style = " {$attribute}=\"{$style}\""; + } + + return $style; + } + + /** + * Get font style. + */ + private function processFontStyle(): void + { + /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + $element = $this->element; + + $attributeStyle = $attributeLang = ''; + $lang = null; + + $fontStyle = $element->getFontStyle(); + if ($fontStyle instanceof Font) { + // Attribute style + $styleWriter = new FontStyleWriter($fontStyle); + $fontCSS = $styleWriter->write(); + if ($fontCSS) { + $attributeStyle = ' style="' . $fontCSS . '"'; + } else { + $className = $fontStyle->getStyleName(); + if ($className) { + $attributeStyle = ' class="' . $className . '"'; + } + } + // Attribute Lang + $lang = $fontStyle->getLang(); + } elseif (!empty($fontStyle)) { + // Attribute class + $attributeStyle = ' class="' . $fontStyle . '"'; + // Attribute Lang + /** @var Font $cssClassStyle */ + $cssClassStyle = Style::getStyle($fontStyle); + if ($cssClassStyle !== null && method_exists($cssClassStyle, 'getLang')) { + $lang = $cssClassStyle->getLang(); + } + } + + if ($lang) { + $attributeLang = $lang->getLatin(); + if (!$attributeLang) { + $attributeLang = $lang->getEastAsia(); + } + if (!$attributeLang) { + $attributeLang = $lang->getBidirectional(); + } + if ($attributeLang) { + $attributeLang = " lang='$attributeLang'"; + } + } + + if ($attributeStyle || $attributeLang) { + $this->openingTags = ""; + $this->closingTags = ''; + } + } +} diff --git a/src/PhpWord/Writer/ODText.php b/src/PhpWord/Writer/ODText.php index 431327fdc3..7b6dea7d69 100644 --- a/src/PhpWord/Writer/ODText.php +++ b/src/PhpWord/Writer/ODText.php @@ -72,6 +72,7 @@ public function __construct(?PhpWord $phpWord = null) */ public function save(string $filename): void { + PhpWord::noPhar($filename); $filename = $this->getTempFile($filename); $zip = $this->getZipArchive($filename); diff --git a/src/PhpWord/Writer/PDF.php b/src/PhpWord/Writer/PDF.php index 45cfc7c1e4..bf493675f4 100644 --- a/src/PhpWord/Writer/PDF.php +++ b/src/PhpWord/Writer/PDF.php @@ -84,6 +84,7 @@ public function __call($name, $arguments) public function save(string $filename): void { + PhpWord::noPhar($filename); $this->getRenderer()->save($filename); } diff --git a/src/PhpWord/Writer/PDF/DomPDF.php b/src/PhpWord/Writer/PDF/DomPDF.php index 85e1b03d7b..96b213f381 100644 --- a/src/PhpWord/Writer/PDF/DomPDF.php +++ b/src/PhpWord/Writer/PDF/DomPDF.php @@ -20,6 +20,7 @@ use Dompdf\Dompdf as DompdfLib; use Dompdf\Options; +use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Writer\WriterInterface; /** @@ -50,6 +51,7 @@ protected function createExternalWriterInstance() */ public function save(string $filename): void { + PhpWord::noPhar($filename); $fileHandle = parent::prepareForSave($filename); // PDF settings diff --git a/src/PhpWord/Writer/PDF/MPDF.php b/src/PhpWord/Writer/PDF/MPDF.php index 287f0f5488..4c24a70aa0 100644 --- a/src/PhpWord/Writer/PDF/MPDF.php +++ b/src/PhpWord/Writer/PDF/MPDF.php @@ -53,6 +53,7 @@ protected function createExternalWriterInstance() */ public function save(string $filename): void { + PhpWord::noPhar($filename); $fileHandle = parent::prepareForSave($filename); // PDF settings diff --git a/src/PhpWord/Writer/PDF/TCPDF.php b/src/PhpWord/Writer/PDF/TCPDF.php index 2add7e3c89..2b39acd025 100644 --- a/src/PhpWord/Writer/PDF/TCPDF.php +++ b/src/PhpWord/Writer/PDF/TCPDF.php @@ -90,6 +90,7 @@ protected function prepareToWrite(TCPDFBase $pdf): void */ public function save(string $filename): void { + PhpWord::noPhar($filename); $fileHandle = parent::prepareForSave($filename); // PDF settings diff --git a/src/PhpWord/Writer/RTF.php b/src/PhpWord/Writer/RTF.php index 246574a902..dbe26d4004 100644 --- a/src/PhpWord/Writer/RTF.php +++ b/src/PhpWord/Writer/RTF.php @@ -58,6 +58,7 @@ public function __construct(?PhpWord $phpWord = null) */ public function save(string $filename): void { + PhpWord::noPhar($filename); $this->writeFile($this->openFile($filename), $this->getContent()); } diff --git a/src/PhpWord/Writer/RTF/Element/AbstractElement.php b/src/PhpWord/Writer/RTF/Element/AbstractElement.php index ea6026898b..5a07ed8ead 100644 --- a/src/PhpWord/Writer/RTF/Element/AbstractElement.php +++ b/src/PhpWord/Writer/RTF/Element/AbstractElement.php @@ -198,6 +198,7 @@ protected function writeFontStyle() // Create style writer and set color/name index $styleWriter = new FontStyleWriter($this->fontStyle); + $styleWriter->setParentWriter($this->parentWriter); if ($this->fontStyle->getColor() != null) { $colorIndex = array_search($this->fontStyle->getColor(), $parentWriter->getColorTable()); if ($colorIndex !== false) { diff --git a/src/PhpWord/Writer/RTF/Style/AbstractStyle.php b/src/PhpWord/Writer/RTF/Style/AbstractStyle.php index 9017f2bce7..bd095df14c 100644 --- a/src/PhpWord/Writer/RTF/Style/AbstractStyle.php +++ b/src/PhpWord/Writer/RTF/Style/AbstractStyle.php @@ -120,7 +120,9 @@ protected function writeChildStyle($name, $style = null) if (isset($style) && $style instanceof $stylePath) { $writerPath = 'PhpOffice\\PhpWord\\Writer\\RTF\\Style\\' . ucfirst($name); $writer = new $writerPath($style); - + if (method_exists($writer, 'setParentWriter')) { + $writer->setParentWriter($this->parentWriter); + } if (method_exists($writer, 'write')) { return $writer->write(); } diff --git a/src/PhpWord/Writer/RTF/Style/Paragraph.php b/src/PhpWord/Writer/RTF/Style/Paragraph.php index 33b33cdda0..82e543d1fa 100644 --- a/src/PhpWord/Writer/RTF/Style/Paragraph.php +++ b/src/PhpWord/Writer/RTF/Style/Paragraph.php @@ -142,6 +142,7 @@ private function writeTabs($tabs = null) if (!empty($tabs)) { foreach ($tabs as $tab) { $styleWriter = new Tab($tab); + $styleWriter->setParentWriter($this->getParentWriter()); $content .= $styleWriter->write(); } } diff --git a/src/PhpWord/Writer/Word2007.php b/src/PhpWord/Writer/Word2007.php index 9702c210bd..ad38458d95 100644 --- a/src/PhpWord/Writer/Word2007.php +++ b/src/PhpWord/Writer/Word2007.php @@ -93,6 +93,7 @@ public function __construct(?PhpWord $phpWord = null) */ public function save(string $filename): void { + PhpWord::noPhar($filename); $filename = $this->getTempFile($filename); $zip = $this->getZipArchive($filename); $phpWord = $this->getPhpWord(); diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index ee582c0b6f..a06f8782b5 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -22,6 +22,7 @@ use Exception; use PhpOffice\PhpWord\Element\Text; use PhpOffice\PhpWord\Element\TextRun; +use PhpOffice\PhpWord\Exception\Exception as PhpWordException; use PhpOffice\PhpWord\IOFactory; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; @@ -177,7 +178,7 @@ public function testXslStyleSheetCanNotBeAppliedOnFailureOfSettingParameterValue $this->expectException(TypeError::class); $this->expectExceptionMessage('must contain only string keys'); } else { - $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectException(PhpWordException::class); $this->expectExceptionMessage('Could not set values for the given XSL style sheet parameters.'); } @@ -200,7 +201,7 @@ public function testXslStyleSheetCanNotBeAppliedOnFailureOfSettingParameterValue */ public function testXslStyleSheetCanNotBeAppliedOnFailureOfLoadingXmlFromTemplate(): void { - $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectException(PhpWordException::class); $this->expectExceptionMessage('Could not load the given XML document.'); $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/corrupted_main_document_part.docx'); @@ -240,6 +241,29 @@ public function testDeleteRow(): void self::assertTrue($docFound); } + public function testNoPhar(): void + { + $this->expectException(PhpWordException::class); + $this->expectExceptionMessage('Invalid protocol'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/delete-row.docx'); + + self::assertEquals( + ['deleteMe', 'deleteMeToo'], + $templateProcessor->getVariables() + ); + + $docName = 'phar://poc.docx'; + $templateProcessor->deleteRow('deleteMe'); + self::assertEquals( + [], + $templateProcessor->getVariables() + ); + $templateProcessor->saveAs($docName); + $docFound = file_exists($docName); + unlink($docName); + self::assertTrue($docFound); + } + /** * @covers ::cloneRow * @covers ::saveAs diff --git a/tests/PhpWordTests/Writer/HTML/Element/PreserveTextTest.php b/tests/PhpWordTests/Writer/HTML/Element/PreserveTextTest.php new file mode 100644 index 0000000000..9c57e9308e --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Element/PreserveTextTest.php @@ -0,0 +1,42 @@ +addSection(); + $text1 = 'This text is missing in the HTML output.'; + $section->addPreserveText($text1); + $text2 = 'Likewise page {PAGE} of {NUMPAGES}'; + $section->addPreserveText($text2); + $writer = new HTML($phpWord); + $content = $writer->getContent(); + $expected = "

      $text1

      "; + self::assertStringContainsString($expected, $content); + $expected = "

      $text2

      "; + self::assertStringContainsString($expected, $content); + } +} diff --git a/tests/PhpWordTests/Writer/NoPharTest.php b/tests/PhpWordTests/Writer/NoPharTest.php new file mode 100644 index 0000000000..254640c8b1 --- /dev/null +++ b/tests/PhpWordTests/Writer/NoPharTest.php @@ -0,0 +1,65 @@ +expectException(PhpWordException::class); + $this->expectExceptionMessage('Invalid protocol'); + $phpWord = new PhpWord(); + if ($fileType === 'PDF') { + $rendererName = Settings::PDF_RENDERER_DOMPDF; + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); + self::assertNotFalse($rendererLibraryPath); + Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + } + $writer = IOFactory::createWriter($phpWord, $fileType); + $writer->save('phar://poc.docx'); + } + + public static function providerFileType(): array + { + return [ + ['EPub3'], + ['HTML'], + ['ODText'], + ['RTF'], + ['Word2007'], + ['PDF\DomPDF'], + ['PDF\MPDF'], + ['PDF\TCPDF'], + ['PDF'], + ]; + } +} From 3961854268aa4688e19522352a70286828f47417 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 25 Nov 2025 18:24:59 -0800 Subject: [PATCH 146/153] Improve Coverage --- src/PhpWord/Shared/Converter.php | 5 + .../Writer/HTML/Element/PreserveText.php | 108 +----------------- src/PhpWord/Writer/HTML/Element/Text.php | 16 +-- src/PhpWord/Writer/HTML/Style/Font.php | 5 +- .../Writer/HTML/Element/PreserveTextTest.php | 21 ++++ 5 files changed, 40 insertions(+), 115 deletions(-) diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php index e4d49e96c5..861cfa2d28 100644 --- a/src/PhpWord/Shared/Converter.php +++ b/src/PhpWord/Shared/Converter.php @@ -340,6 +340,11 @@ public static function angleToDegree($angle = 1) Color::YELLOW => 'FFFF00', ]; + public static function validStringColor(string $value): bool + { + return (self::STRING_TO_RGB[$value] ?? null) !== null; + } + /** * Convert colorname as string to RGB. * diff --git a/src/PhpWord/Writer/HTML/Element/PreserveText.php b/src/PhpWord/Writer/HTML/Element/PreserveText.php index 6b46988fff..7c7603927e 100644 --- a/src/PhpWord/Writer/HTML/Element/PreserveText.php +++ b/src/PhpWord/Writer/HTML/Element/PreserveText.php @@ -18,32 +18,13 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; -use PhpOffice\PhpWord\Style; -use PhpOffice\PhpWord\Style\Font; -use PhpOffice\PhpWord\Style\Paragraph; use PhpOffice\PhpWord\Writer\HTML; -use PhpOffice\PhpWord\Writer\HTML\Style\Font as FontStyleWriter; -use PhpOffice\PhpWord\Writer\HTML\Style\Paragraph as ParagraphStyleWriter; /** * PreserveText element HTML writer. Issue 2188. */ -class PreserveText extends AbstractElement +class PreserveText extends Text { - /** - * Opening tags. - * - * @var string - */ - private $openingTags = ''; - - /** - * Closing tag. - * - * @var string - */ - private $closingTags = ''; - /** * Write text. * @@ -106,91 +87,4 @@ protected function writeClosing() return $content; } - - /** - * Write paragraph style. - * - * @return string - */ - private function getParagraphStyle() - { - /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ - $element = $this->element; - $style = ''; - if (!method_exists($element, 'getParagraphStyle')) { - return $style; - } - - $paragraphStyle = $element->getParagraphStyle(); - $pStyleIsObject = ($paragraphStyle instanceof Paragraph); - if ($pStyleIsObject) { - $styleWriter = new ParagraphStyleWriter($paragraphStyle); - $styleWriter->setParentWriter($this->parentWriter); - $style = $styleWriter->write(); - } elseif (is_string($paragraphStyle)) { - $style = $paragraphStyle; - } - if ($style) { - $attribute = $pStyleIsObject ? 'style' : 'class'; - $style = " {$attribute}=\"{$style}\""; - } - - return $style; - } - - /** - * Get font style. - */ - private function processFontStyle(): void - { - /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ - $element = $this->element; - - $attributeStyle = $attributeLang = ''; - $lang = null; - - $fontStyle = $element->getFontStyle(); - if ($fontStyle instanceof Font) { - // Attribute style - $styleWriter = new FontStyleWriter($fontStyle); - $fontCSS = $styleWriter->write(); - if ($fontCSS) { - $attributeStyle = ' style="' . $fontCSS . '"'; - } else { - $className = $fontStyle->getStyleName(); - if ($className) { - $attributeStyle = ' class="' . $className . '"'; - } - } - // Attribute Lang - $lang = $fontStyle->getLang(); - } elseif (!empty($fontStyle)) { - // Attribute class - $attributeStyle = ' class="' . $fontStyle . '"'; - // Attribute Lang - /** @var Font $cssClassStyle */ - $cssClassStyle = Style::getStyle($fontStyle); - if ($cssClassStyle !== null && method_exists($cssClassStyle, 'getLang')) { - $lang = $cssClassStyle->getLang(); - } - } - - if ($lang) { - $attributeLang = $lang->getLatin(); - if (!$attributeLang) { - $attributeLang = $lang->getEastAsia(); - } - if (!$attributeLang) { - $attributeLang = $lang->getBidirectional(); - } - if ($attributeLang) { - $attributeLang = " lang='$attributeLang'"; - } - } - - if ($attributeStyle || $attributeLang) { - $this->openingTags = ""; - $this->closingTags = ''; - } - } } diff --git a/src/PhpWord/Writer/HTML/Element/Text.php b/src/PhpWord/Writer/HTML/Element/Text.php index 01af064d6f..a53a53fa54 100644 --- a/src/PhpWord/Writer/HTML/Element/Text.php +++ b/src/PhpWord/Writer/HTML/Element/Text.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; +use PhpOffice\PhpWord\Element\PreserveText as PreserveTextElement; +use PhpOffice\PhpWord\Element\Text as TextElement; use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; @@ -52,14 +54,14 @@ class Text extends AbstractElement * * @var string */ - private $openingTags = ''; + protected $openingTags = ''; /** * Closing tag. * * @var string */ - private $closingTags = ''; + protected $closingTags = ''; /** * Write text. @@ -70,7 +72,7 @@ public function write() { $this->processFontStyle(); - /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + /** @var TextElement */ $element = $this->element; $text = $this->parentWriter->escapeHTML($element->getText() ?? ''); @@ -213,9 +215,9 @@ private function writeTrackChangeClosing() * * @return string */ - private function getParagraphStyle() + protected function getParagraphStyle() { - /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + /** @var PreserveTextElement|TextElement */ $element = $this->element; $style = ''; if (!method_exists($element, 'getParagraphStyle')) { @@ -242,9 +244,9 @@ private function getParagraphStyle() /** * Get font style. */ - private function processFontStyle(): void + protected function processFontStyle(): void { - /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + /** @var PreserveTextElement|TextElement */ $element = $this->element; $attributeStyle = $attributeLang = ''; diff --git a/src/PhpWord/Writer/HTML/Style/Font.php b/src/PhpWord/Writer/HTML/Style/Font.php index 6c0f9db360..9e27e7d982 100644 --- a/src/PhpWord/Writer/HTML/Style/Font.php +++ b/src/PhpWord/Writer/HTML/Style/Font.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Writer\HTML\Style; +use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\Style\Font as FontStyle; /** @@ -69,7 +70,9 @@ public function write() $css['font-family'] = $this->getValueIf(!empty($font), $font); $css['font-size'] = $this->getValueIf($size !== null, "{$size}pt"); - $css['color'] = $this->getValueIf($color !== null, "#{$color}"); + if ($color !== null) { + $css['color'] = Converter::validStringColor($color) ? $color : "#$color"; + } $css['background'] = $this->getValueIf($fgColor != '', $fgColor); $css['font-weight'] = $this->getValueIf($style->isBold(), 'bold'); $css['font-style'] = $this->getValueIf($style->isItalic(), 'italic'); diff --git a/tests/PhpWordTests/Writer/HTML/Element/PreserveTextTest.php b/tests/PhpWordTests/Writer/HTML/Element/PreserveTextTest.php index 9c57e9308e..e77876125c 100644 --- a/tests/PhpWordTests/Writer/HTML/Element/PreserveTextTest.php +++ b/tests/PhpWordTests/Writer/HTML/Element/PreserveTextTest.php @@ -39,4 +39,25 @@ public function testPreserveText(): void $expected = "

      $text2

      "; self::assertStringContainsString($expected, $content); } + + public function testPreserveTextStyle(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $text1 = 'This text is missing in the HTML output.'; + $fontStyle = ['color' => 'red']; + $section->addPreserveText($text1, $fontStyle); + $paragraphStyle = ['align' => 'center']; + $text2 = 'Likewise page {PAGE} of {NUMPAGES}'; + $section->addPreserveText($text2, null, $paragraphStyle); + $section->addPreserveText(''); + $writer = new HTML($phpWord); + $content = $writer->getContent(); + $expected = "

      $text1

      "; + self::assertStringContainsString($expected, $content); + $expected = "

      $text2

      "; + self::assertStringContainsString($expected, $content); + $expected = '

       

      '; + self::assertStringContainsString($expected, $content); + } } From 4f228894a8030502a549328b13f5820cd3c6024b Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:04:34 -0800 Subject: [PATCH 147/153] Another Coverage Tweak --- .../WriteReadback/RtfBackslashTest.php | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/PhpWordTests/WriteReadback/RtfBackslashTest.php diff --git a/tests/PhpWordTests/WriteReadback/RtfBackslashTest.php b/tests/PhpWordTests/WriteReadback/RtfBackslashTest.php new file mode 100644 index 0000000000..8913dd6c90 --- /dev/null +++ b/tests/PhpWordTests/WriteReadback/RtfBackslashTest.php @@ -0,0 +1,49 @@ +addSection(); + $sectionWriter->addText($textWithBackslash); + + $writer = new RTF($phpWordWriter); + $file = __DIR__ . '/../_files/temp.rtf'; + $writer->save($file); + self::assertFileExists($file); + $phpWordReader = IOFactory::load($file, 'RTF'); + unlink($file); + + $sections = $phpWordReader->getSections(); + self::assertCount(1, $sections); + $elements = $sections[0]->getElements(); + $element = $elements[0]; + self::assertInstanceOf(TextRun::class, $element); + self::assertSame($textWithBackslash, $element->getText()); + } +} From 64f0de9fb613b46258cda31e90c81d7a9e312710 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 29 Nov 2025 10:08:07 -0800 Subject: [PATCH 148/153] Handle Style Objects Passed to Element Constructors Original PR #2685 by @hazington. --- src/PhpWord/Element/AbstractElement.php | 14 +++--- tests/PhpWordTests/Element/CellTest.php | 11 +++++ .../Writer/EPub3/Element/ImageTest.php | 49 ++++++------------- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/src/PhpWord/Element/AbstractElement.php b/src/PhpWord/Element/AbstractElement.php index 607325489b..2413a4c196 100644 --- a/src/PhpWord/Element/AbstractElement.php +++ b/src/PhpWord/Element/AbstractElement.php @@ -479,7 +479,7 @@ public function isInSection() } /** - * Set new style value. + * Set new style value. PR #2685. * * @param mixed $styleObject Style object * @param null|AbstractStyle|array|string $styleValue Style value @@ -489,14 +489,16 @@ public function isInSection() */ protected function setNewStyle($styleObject, $styleValue = null, $returnObject = false) { - if (null !== $styleValue && is_array($styleValue)) { + if ($styleValue instanceof AbstractStyle && get_class($styleValue) === get_class($styleObject)) { + return $styleValue; + } + if (is_array($styleValue)) { $styleObject->setStyleByArray($styleValue); - $style = $styleObject; - } else { - $style = $returnObject ? $styleObject : $styleValue; + + return $styleObject; } - return $style; + return $returnObject ? $styleObject : $styleValue; } /** diff --git a/tests/PhpWordTests/Element/CellTest.php b/tests/PhpWordTests/Element/CellTest.php index 8ef1f49c08..5417e0b457 100644 --- a/tests/PhpWordTests/Element/CellTest.php +++ b/tests/PhpWordTests/Element/CellTest.php @@ -48,6 +48,17 @@ public function testConstructWithStyleArray(): void self::assertNull($oCell->getWidth()); } + /** + * Test if the style object passed to the constructor is actually used. + */ + public function testConstructWithStyleObject(): void + { + $style = new \PhpOffice\PhpWord\Style\Cell(); + $oCell = new Cell(null, $style); + + self::assertSame($style, $oCell->getStyle()); + } + /** * Add text. */ diff --git a/tests/PhpWordTests/Writer/EPub3/Element/ImageTest.php b/tests/PhpWordTests/Writer/EPub3/Element/ImageTest.php index 53685f72dc..205d617c8e 100644 --- a/tests/PhpWordTests/Writer/EPub3/Element/ImageTest.php +++ b/tests/PhpWordTests/Writer/EPub3/Element/ImageTest.php @@ -10,60 +10,43 @@ class ImageTest extends TestCase { - /** - * @var XMLWriter - */ - private $xmlWriter; - - /** - * @var Image - */ - private $element; - - /** - * @var ImageWriter - */ - private $writer; - - protected function setUp(): void + public function testWrite(): void { - $this->xmlWriter = new XMLWriter(); + $xmlWriter = new XMLWriter(); $style = new ImageStyle(); $style->setWidth(100); $style->setHeight(100); - $this->element = new Image('tests/PhpWordTests/_files/images/earth.jpg', $style); - $this->writer = new ImageWriter($this->xmlWriter, $this->element); - } - - public function testWrite(): void - { - $this->writer->write(); + $element = new Image('tests/PhpWordTests/_files/images/earth.jpg', $style); + $writer = new ImageWriter($xmlWriter, $element); + $writer->write(); - $expected = '

      '; - self::assertEquals($expected, $this->xmlWriter->getData()); + $expected = '

      '; + self::assertSame($expected, $xmlWriter->getData()); } public function testWriteWithoutP(): void { + $xmlWriter = new XMLWriter(); $style = new ImageStyle(); $style->setWidth(100); $style->setHeight(100); - $this->element = new Image('tests/PhpWordTests/_files/images/earth.jpg', $style); - $this->writer = new ImageWriter($this->xmlWriter, $this->element, true); + $element = new Image('tests/PhpWordTests/_files/images/earth.jpg', $style); + $writer = new ImageWriter($xmlWriter, $element, true); - $this->writer->write(); + $writer->write(); - $expected = ''; - self::assertEquals($expected, $this->xmlWriter->getData()); + $expected = ''; + self::assertSame($expected, $xmlWriter->getData()); } public function testWriteWithInvalidElement(): void { + $xmlWriter = new XMLWriter(); $invalidElement = $this->createMock(\PhpOffice\PhpWord\Element\AbstractElement::class); - $writer = new ImageWriter($this->xmlWriter, $invalidElement); + $writer = new ImageWriter($xmlWriter, $invalidElement); $writer->write(); - self::assertEquals('', $this->xmlWriter->getData()); + self::assertSame('', $xmlWriter->getData()); } } From f924e995dad0a8eebb093ad0e484594ea3d4f431 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 29 Nov 2025 14:40:49 -0800 Subject: [PATCH 149/153] Less Mockery --- .../EPub3/{ => Element}/ElementTest.php | 12 ++-- .../Writer/EPub3/Element/FakeElement.php | 9 +++ .../Writer/EPub3/Element/ImageTest.php | 2 +- .../Writer/EPub3/Element/TextTest.php | 60 ++++++++----------- 4 files changed, 41 insertions(+), 42 deletions(-) rename tests/PhpWordTests/Writer/EPub3/{ => Element}/ElementTest.php (55%) create mode 100644 tests/PhpWordTests/Writer/EPub3/Element/FakeElement.php diff --git a/tests/PhpWordTests/Writer/EPub3/ElementTest.php b/tests/PhpWordTests/Writer/EPub3/Element/ElementTest.php similarity index 55% rename from tests/PhpWordTests/Writer/EPub3/ElementTest.php rename to tests/PhpWordTests/Writer/EPub3/Element/ElementTest.php index af745d9176..77ba57e069 100644 --- a/tests/PhpWordTests/Writer/EPub3/ElementTest.php +++ b/tests/PhpWordTests/Writer/EPub3/Element/ElementTest.php @@ -1,10 +1,11 @@ expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectException(WordException::class); + $this->expectExceptionMessage('Writer element class'); - $element = $this->createMock(AbstractElement::class); + $element = new FakeElement(); WriterElement::getElementClass($element); } } diff --git a/tests/PhpWordTests/Writer/EPub3/Element/FakeElement.php b/tests/PhpWordTests/Writer/EPub3/Element/FakeElement.php new file mode 100644 index 0000000000..f96ccb96e1 --- /dev/null +++ b/tests/PhpWordTests/Writer/EPub3/Element/FakeElement.php @@ -0,0 +1,9 @@ +createMock(\PhpOffice\PhpWord\Element\AbstractElement::class); + $invalidElement = new FakeElement(); $writer = new ImageWriter($xmlWriter, $invalidElement); $writer->write(); diff --git a/tests/PhpWordTests/Writer/EPub3/Element/TextTest.php b/tests/PhpWordTests/Writer/EPub3/Element/TextTest.php index 38490691d5..99b7350f7f 100644 --- a/tests/PhpWordTests/Writer/EPub3/Element/TextTest.php +++ b/tests/PhpWordTests/Writer/EPub3/Element/TextTest.php @@ -9,75 +9,63 @@ class TextTest extends TestCase { - /** - * @var XMLWriter - */ - private $xmlWriter; - - /** - * @var Text - */ - private $element; - - /** - * @var TextWriter - */ - private $writer; - - protected function setUp(): void - { - $this->xmlWriter = new XMLWriter(); - $this->element = new Text('Sample Text'); - $this->writer = new TextWriter($this->xmlWriter, $this->element); - } - public function testWrite(): void { - $this->writer->write(); + $xmlWriter = new XMLWriter(); + $element = new Text('Sample Text'); + $writer = new TextWriter($xmlWriter, $element); + $writer->write(); $expected = "

      \n Sample Text\n

      \n"; - self::assertEquals($expected, $this->xmlWriter->getData()); + self::assertSame($expected, $xmlWriter->getData()); } public function testWriteWithFontStyle(): void { - $this->element->setFontStyle('customStyle'); + $xmlWriter = new XMLWriter(); + $element = new Text('Sample Text'); + $writer = new TextWriter($xmlWriter, $element); + $element->setFontStyle('customStyle'); - $this->writer->write(); + $writer->write(); $expected = "

      \n Sample Text\n

      \n"; - self::assertEquals($expected, $this->xmlWriter->getData()); + self::assertSame($expected, $xmlWriter->getData()); } public function testWriteWithParagraphStyle(): void { - $this->element->setParagraphStyle('paragraphStyle'); + $xmlWriter = new XMLWriter(); + $element = new Text('Sample Text'); + $writer = new TextWriter($xmlWriter, $element); + $element->setParagraphStyle('paragraphStyle'); - $this->writer->write(); + $writer->write(); $expected = "

      \n Sample Text\n

      \n"; - self::assertEquals($expected, $this->xmlWriter->getData()); + self::assertSame($expected, $xmlWriter->getData()); } public function testWriteWithoutP(): void { $text = new Text('Sample Text'); $xmlWriter = new XMLWriter(); - $this->writer = new TextWriter($xmlWriter, $text, true); + $writer = new TextWriter($xmlWriter, $text, true); - $this->writer->write(); + $writer->write(); $expected = "Sample Text\n"; - self::assertEquals($expected, $xmlWriter->getData()); + self::assertSame($expected, $xmlWriter->getData()); } public function testWriteWithInvalidElement(): void { - $invalidElement = $this->createMock(\PhpOffice\PhpWord\Element\AbstractElement::class); - $writer = new TextWriter($this->xmlWriter, $invalidElement); + $xmlWriter = new XMLWriter(); + $invalidElement = new FakeElement(); + $writer = new TextWriter($xmlWriter, $invalidElement); $writer->write(); - self::assertEquals('', $this->xmlWriter->getData()); + self::assertSame('', $xmlWriter->getData()); } } From 5a03a034ffb7c2fd770f582d01859b87161a164f Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 30 Nov 2025 01:07:51 -0800 Subject: [PATCH 150/153] Word2007 Writer Support for SVG Based on PR #2790 by @prog-klk1 --- samples/Sample_47_SVG.php | 41 ++++++ samples/resources/sample.svg | 96 ++++++++++++ src/PhpWord/Element/Image.php | 49 +++++++ src/PhpWord/Writer/Word2007/Element/Image.php | 138 ++++++++++++++++++ tests/PhpWordTests/Element/SvgImageTest.php | 70 +++++++++ tests/PhpWordTests/Element/invalid.svg | 1 + tests/PhpWordTests/Element/nowidth.svg | 96 ++++++++++++ 7 files changed, 491 insertions(+) create mode 100644 samples/Sample_47_SVG.php create mode 100644 samples/resources/sample.svg create mode 100644 tests/PhpWordTests/Element/SvgImageTest.php create mode 100644 tests/PhpWordTests/Element/invalid.svg create mode 100644 tests/PhpWordTests/Element/nowidth.svg diff --git a/samples/Sample_47_SVG.php b/samples/Sample_47_SVG.php new file mode 100644 index 0000000000..878e5468ca --- /dev/null +++ b/samples/Sample_47_SVG.php @@ -0,0 +1,41 @@ +addSection(); +$section->addText('SVG image without any styles:'); +$svg = $section->addImage(__DIR__ . '/resources/sample.svg'); + +printSeparator($section); + +$section->addText('SVG image with styles:'); +$svg = $section->addImage( + __DIR__ . '/resources/sample.svg', + [ + 'width' => 200, + 'height' => 200, + 'align' => 'center', + 'wrappingStyle' => PhpOffice\PhpWord\Style\Image::WRAPPING_STYLE_BEHIND, + ] +); + +function printSeparator(Section $section): void +{ + $section->addTextBreak(); + $lineStyle = ['weight' => 0.2, 'width' => 150, 'height' => 0, 'align' => 'center']; + $section->addLine($lineStyle); + $section->addTextBreak(2); +} + +// Save file +echo write($phpWord, basename(__FILE__, '.php'), $writers); +if (!CLI) { + include_once 'Sample_Footer.php'; +} diff --git a/samples/resources/sample.svg b/samples/resources/sample.svg new file mode 100644 index 0000000000..8a800c4a2c --- /dev/null +++ b/samples/resources/sample.svg @@ -0,0 +1,96 @@ + + + Official PHP Logo + + + + image/svg+xml + + Official PHP Logo + + + Colin Viebrock + + + + + + + + + + + + Copyright Colin Viebrock 1997 - All rights reserved. + + + 1997 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/PhpWord/Element/Image.php b/src/PhpWord/Element/Image.php index 88259067af..bc9039c437 100644 --- a/src/PhpWord/Element/Image.php +++ b/src/PhpWord/Element/Image.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Element; +use DOMDocument; use PhpOffice\PhpWord\Exception\CreateTemporaryFileException; use PhpOffice\PhpWord\Exception\InvalidImageException; use PhpOffice\PhpWord\Exception\UnsupportedImageTypeException; @@ -434,6 +435,20 @@ private function checkImage(): void { $this->setSourceType(); + $ext = strtolower(pathinfo($this->source, PATHINFO_EXTENSION)); + if ($ext === 'svg') { + [$actualWidth, $actualHeight] = $this->getSvgDimensions($this->source); + $this->imageType = 'image/svg+xml'; + $this->imageExtension = 'svg'; + $this->imageFunc = null; + $this->imageQuality = null; + $this->memoryImage = false; + $this->sourceType = self::SOURCE_LOCAL; + $this->setProportionalSize($actualWidth, $actualHeight); + + return; + } + // Check image data if ($this->sourceType == self::SOURCE_ARCHIVE) { $imageData = $this->getArchiveImageSize($this->source); @@ -600,4 +615,38 @@ private function setProportionalSize($actualWidth, $actualHeight): void } } } + + public function getSvgDimensions(string $file): array + { + $xml = @file_get_contents($file); + if ($xml === false) { + throw new InvalidImageException("Impossible de lire le fichier SVG: $file"); + } + libxml_use_internal_errors(true); + $dom = new DOMDocument(); + if (!$dom->loadXML($xml)) { + throw new InvalidImageException('SVG invalide ou mal formé'); + } + $svg = $dom->documentElement; + + $wAttr = round((float) $svg->getAttribute('width')); + $hAttr = round((float) $svg->getAttribute('height')); + + $w = (int) filter_var($wAttr, FILTER_SANITIZE_NUMBER_INT); + $h = (int) filter_var($hAttr, FILTER_SANITIZE_NUMBER_INT); + + if ($w <= 0 || $h <= 0) { + $vb = $svg->getAttribute('viewBox'); + if (preg_match('/^\s*[\d.+-]+[\s,]+[\d.+-]+[\s,]+([\d.+-]+)[\s,]+([\d.+-]+)\s*$/', $vb, $m)) { + $w = (int) round((float) $m[1]); + $h = (int) round((float) $m[2]); + } + } + + if ($w <= 0 || $h <= 0) { + throw new InvalidImageException('Impossible de déterminer width/height ou viewBox valides pour le SVG'); + } + + return [$w, $h]; + } } diff --git a/src/PhpWord/Writer/Word2007/Element/Image.php b/src/PhpWord/Writer/Word2007/Element/Image.php index 7835f32ad5..813f1e7582 100644 --- a/src/PhpWord/Writer/Word2007/Element/Image.php +++ b/src/PhpWord/Writer/Word2007/Element/Image.php @@ -42,6 +42,12 @@ public function write(): void if (!$element instanceof ImageElement) { return; } + $ext = strtolower(pathinfo($element->getSource(), PATHINFO_EXTENSION)); + if ($ext === 'svg') { + $this->writeSvgDrawing($xmlWriter, $element); + + return; + } if ($element->isWatermark()) { $this->writeWatermark($xmlWriter, $element); @@ -127,4 +133,136 @@ private function writeWatermark(XMLWriter $xmlWriter, ImageElement $element): vo $xmlWriter->endElement(); // w:p } } + + private function writeSvgDrawing(XMLWriter $xmlWriter, ImageElement $element): void + { + $rId = $element->getRelationId() + ($element->isInSection() ? 6 : 0); + + $style = $element->getStyle(); + // dimensions px, fallback sur getSvgDimensions() + $pxW = $style->getWidth() ?: 0; + $pxH = $style->getHeight() ?: 0; + if ($pxW <= 0 || $pxH <= 0) { + [$pxW, $pxH] = $element->getSvgDimensions($element->getSource()); + } + $cx = \PhpOffice\PhpWord\Shared\Drawing::pixelsToEmu($pxW); + $cy = \PhpOffice\PhpWord\Shared\Drawing::pixelsToEmu($pxH); + + // + align + if (!$this->withoutP) { + $xmlWriter->startElement('w:p'); + (new ImageStyleWriter($xmlWriter, $style))->writeAlignment(); + } + // + $xmlWriter->startElement('w:r'); + // + $xmlWriter->startElement('w:drawing'); + + // avec déclarations xmlns comme python-docx-oss + $xmlWriter->startElement('wp:inline'); + $xmlWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $xmlWriter->writeAttribute('xmlns:pic', 'http://schemas.openxmlformats.org/drawingml/2006/picture'); + $xmlWriter->writeAttribute('xmlns:asvg', 'http://schemas.microsoft.com/office/drawing/2016/SVG/main'); + + // + $xmlWriter->startElement('wp:extent'); + $xmlWriter->writeAttribute('cx', (string) $cx); + $xmlWriter->writeAttribute('cy', (string) $cy); + $xmlWriter->endElement(); + + // + $xmlWriter->startElement('wp:docPr'); + $xmlWriter->writeAttribute('id', '1'); + $xmlWriter->writeAttribute('name', 'Picture 1'); + $xmlWriter->endElement(); + + // + $xmlWriter->startElement('wp:cNvGraphicFramePr'); + $xmlWriter->startElement('a:graphicFrameLocks'); + $xmlWriter->writeAttribute('noChangeAspect', '1'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + // + $xmlWriter->startElement('a:graphic'); + // + $xmlWriter->startElement('a:graphicData'); + $xmlWriter->writeAttribute( + 'uri', + 'http://schemas.openxmlformats.org/drawingml/2006/picture' + ); + + // + $xmlWriter->startElement('pic:pic'); + + // + $xmlWriter->startElement('pic:nvPicPr'); + $xmlWriter->startElement('pic:cNvPr'); + $xmlWriter->writeAttribute('id', '0'); + $xmlWriter->writeAttribute('name', basename($element->getSource())); + $xmlWriter->endElement(); + $xmlWriter->startElement('pic:cNvPicPr'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + // + $xmlWriter->startElement('pic:blipFill'); + $xmlWriter->startElement('a:blip'); + // uniquement extLst avec svgBlip + $xmlWriter->startElement('a:extLst'); + $xmlWriter->startElement('a:ext'); + $xmlWriter->writeAttribute( + 'uri', + '{96DAC541-7B7A-43D3-8B79-37D633B846F1}' + ); + $xmlWriter->startElement('asvg:svgBlip'); + $xmlWriter->writeAttribute( + 'r:embed', + 'rId' . $rId + ); + $xmlWriter->endElement(); // asvg:svgBlip + $xmlWriter->endElement(); // a:ext + $xmlWriter->endElement(); // a:extLst + $xmlWriter->endElement(); // a:blip + + // + $xmlWriter->startElement('a:stretch'); + $xmlWriter->startElement('a:fillRect'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->endElement(); // pic:blipFill + + // + $xmlWriter->startElement('pic:spPr'); + $xmlWriter->startElement('a:xfrm'); + $xmlWriter->startElement('a:off'); + $xmlWriter->writeAttribute('x', '0'); + $xmlWriter->writeAttribute('y', '0'); + $xmlWriter->endElement(); + $xmlWriter->startElement('a:ext'); + $xmlWriter->writeAttribute('cx', (string) $cx); + $xmlWriter->writeAttribute('cy', (string) $cy); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + $xmlWriter->startElement('a:prstGeom'); + $xmlWriter->writeAttribute('prst', 'rect'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); // pic:spPr + + $xmlWriter->endElement(); // pic:pic + + $xmlWriter->endElement(); // a:graphicData + $xmlWriter->endElement(); // a:graphic + + $xmlWriter->endElement(); // wp:inline + + $xmlWriter->endElement(); // w:drawing + $xmlWriter->endElement(); // w:r + + // + if (!$this->withoutP) { + $xmlWriter->endElement(); + } + } } diff --git a/tests/PhpWordTests/Element/SvgImageTest.php b/tests/PhpWordTests/Element/SvgImageTest.php new file mode 100644 index 0000000000..0e8bb4d32f --- /dev/null +++ b/tests/PhpWordTests/Element/SvgImageTest.php @@ -0,0 +1,70 @@ +addSection(); + $image = $section->addImage($svgPath); + + self::assertSame($svgPath, $image->getSource()); + self::assertSame('image/svg+xml', $image->getImageType()); + } + + public function testAddSvgImageWithStyles(): void + { + $svgPath = self::SVG_PATH; + if (!file_exists($svgPath)) { + self::markTestSkipped('SVG file not found: ' . $svgPath); + } + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $options = [ + 'width' => 200, + 'height' => 200, + 'wrappingStyle' => Image::WRAPPING_STYLE_BEHIND, + ]; + + $image = $section->addImage($svgPath, $options); + + self::assertSame(200, $image->getStyle()->getWidth()); + self::assertSame(200, $image->getStyle()->getHeight()); + self::assertSame(Image::WRAPPING_STYLE_BEHIND, $image->getStyle()->getWrappingStyle()); + } + + public function testNonExistentSvg(): void + { + $this->expectException(InvalidImageException::class); + $this->expectExceptionMessage('Impossible de lire'); + $element = new ImageElement('xyz.svg'); + } + + public function testInvalidSvg(): void + { + $this->expectException(InvalidImageException::class); + $this->expectExceptionMessage('SVG invalide'); + $element = new ImageElement(__DIR__ . '/invalid.svg'); + } + + public function testNoWidthSvg(): void + { + $this->expectException(InvalidImageException::class); + $this->expectExceptionMessage('width/height ou viewBox'); + $element = new ImageElement(__DIR__ . '/nowidth.svg'); + } +} diff --git a/tests/PhpWordTests/Element/invalid.svg b/tests/PhpWordTests/Element/invalid.svg new file mode 100644 index 0000000000..8e3dbcc520 --- /dev/null +++ b/tests/PhpWordTests/Element/invalid.svg @@ -0,0 +1 @@ +Deliberately invalid. diff --git a/tests/PhpWordTests/Element/nowidth.svg b/tests/PhpWordTests/Element/nowidth.svg new file mode 100644 index 0000000000..27c7814ae5 --- /dev/null +++ b/tests/PhpWordTests/Element/nowidth.svg @@ -0,0 +1,96 @@ + + + Official PHP Logo + + + + image/svg+xml + + Official PHP Logo + + + Colin Viebrock + + + + + + + + + + + + Copyright Colin Viebrock 1997 - All rights reserved. + + + 1997 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From fc77d44280cceccc6a3b5c50b1c8d3bbe70e7e4b Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 30 Nov 2025 02:28:22 -0800 Subject: [PATCH 151/153] Php8.5 Deprecation in Dompdf Handling of SVG --- samples/Sample_47_SVG.php | 6 ++ .../Writer/Word2007/Element/ImageSvgTest.php | 77 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 tests/PhpWordTests/Writer/Word2007/Element/ImageSvgTest.php diff --git a/samples/Sample_47_SVG.php b/samples/Sample_47_SVG.php index 878e5468ca..30d6d4c3fa 100644 --- a/samples/Sample_47_SVG.php +++ b/samples/Sample_47_SVG.php @@ -2,6 +2,7 @@ use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; include_once 'Sample_Header.php'; @@ -9,6 +10,11 @@ echo date('H:i:s'), ' Create new PhpWord object', EOL; $phpWord = new PhpWord(); +// dompdf has deprecation problem for svg with Php8.5 +$rendererName = Settings::PDF_RENDERER_MPDF; +$rendererLibraryPath = $vendorDirPath . '/mpdf/mpdf'; +Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + $section = $phpWord->addSection(); $section->addText('SVG image without any styles:'); $svg = $section->addImage(__DIR__ . '/resources/sample.svg'); diff --git a/tests/PhpWordTests/Writer/Word2007/Element/ImageSvgTest.php b/tests/PhpWordTests/Writer/Word2007/Element/ImageSvgTest.php new file mode 100644 index 0000000000..12edba8d5d --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/ImageSvgTest.php @@ -0,0 +1,77 @@ +addSection(); + $svg = $section->addImage( + 'samples/resources/sample.svg' + ); + $svg->getStyle()->setWidth(0); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + $path = '/w:document/w:body/w:p[1]/w:r[1]/w:drawing/wp:inline/wp:extent'; + self::assertTrue($doc->elementExists($path)); + self::assertSame( + self::CX, + $doc->getElementAttribute($path, 'cx') + ); + self::assertSame( + self::CY, + $doc->getElementAttribute($path, 'cy') + ); + } + + public function testNoStyle(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $svg = $section->addImage( + 'samples/resources/sample.svg' + ); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + $path = '/w:document/w:body/w:p[1]/w:r[1]/w:drawing/wp:inline/wp:extent'; + self::assertTrue($doc->elementExists($path)); + self::assertSame( + self::CX, + $doc->getElementAttribute($path, 'cx') + ); + self::assertSame( + self::CY, + $doc->getElementAttribute($path, 'cy') + ); + } +} From 524b263c2093486ca5f186a6433cd2ac09f3267a Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:46:56 -0800 Subject: [PATCH 152/153] Template Processor Support for SVG Based on PR #2806 by @geo-fret. Fix #2795 (which also requests ico, but geo-fret believes that isn't supported in docx). --- src/PhpWord/Shared/Converter.php | 6 +- src/PhpWord/TemplateProcessor.php | 125 ++++++++++++++++++- tests/PhpWordTests/TemplateProcessorTest.php | 31 +++-- tests/PhpWordTests/_files/images/phpword.svg | 50 ++++++++ 4 files changed, 194 insertions(+), 18 deletions(-) create mode 100644 tests/PhpWordTests/_files/images/phpword.svg diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php index 861cfa2d28..5a53885c40 100644 --- a/src/PhpWord/Shared/Converter.php +++ b/src/PhpWord/Shared/Converter.php @@ -464,10 +464,12 @@ public static function cssToCm($value) * * @param string $value * - * @return float + * @return ?float */ public static function cssToEmu($value) { - return self::pointToEmu(self::cssToPoint($value)); + $point = self::cssToPoint($value); + + return ($point === null) ? null : self::pointToEmu($point); } } diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 75e60baa75..52621af034 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -24,6 +24,7 @@ use PhpOffice\PhpWord\Exception\CopyFileException; use PhpOffice\PhpWord\Exception\CreateTemporaryFileException; use PhpOffice\PhpWord\Exception\Exception; +use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\Shared\Text; use PhpOffice\PhpWord\Shared\XMLWriter; use PhpOffice\PhpWord\Shared\ZipArchive; @@ -570,11 +571,28 @@ private function prepareImageAttrs($replaceImage, $varInlineArgs) $width = $this->chooseImageDimension($width, $varInlineArgs['width'] ?? null, 115); $height = $this->chooseImageDimension($height, $varInlineArgs['height'] ?? null, 70); - $imageData = @getimagesize($imgPath); - if (!is_array($imageData)) { - throw new Exception(sprintf('Invalid image: %s', $imgPath)); + $mime = mime_content_type($imgPath); + if ($mime !== 'image/svg+xml') { + $imageData = @getimagesize($imgPath); + if (!is_array($imageData)) { + throw new Exception(sprintf('Invalid image: %s', $imgPath)); + } + [$actualWidth, $actualHeight, $imageType] = $imageData; + } else { + $content = file_get_contents($imgPath); + if (!$content) { + throw new Exception(sprintf('Invalid image: %s', $imgPath)); + } + $svgXml = simplexml_load_string($content); + if (!$svgXml) { + throw new Exception(sprintf('Invalid image: %s', $imgPath)); + } + $svgAttributes = $svgXml->attributes(); + $actualWidth = $svgAttributes->width; + $actualHeight = $svgAttributes->height; + $actualWidth = is_numeric($actualWidth) ? $actualWidth . 'px' : $actualWidth; + $actualHeight = is_numeric($actualHeight) ? $actualHeight . 'px' : $actualHeight; } - [$actualWidth, $actualHeight, $imageType] = $imageData; // fix aspect ratio (by default) if (null === $ratio && isset($varInlineArgs['ratio'])) { @@ -586,9 +604,11 @@ private function prepareImageAttrs($replaceImage, $varInlineArgs) $imageAttrs = [ 'src' => $imgPath, - 'mime' => image_type_to_mime_type($imageType), + 'mime' => $mime, 'width' => $width, 'height' => $height, + 'originalWidth' => $actualWidth, + 'originalHeight' => $actualHeight, ]; return $imageAttrs; @@ -606,6 +626,7 @@ private function addImageToRelations($partFileName, $rid, $imgPath, $imageMimeTy 'image/png' => 'png', 'image/bmp' => 'bmp', 'image/gif' => 'gif', + 'image/svg+xml' => 'svg', ]; // get image embed name @@ -681,6 +702,48 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM // define templates // result can be verified via "Open XML SDK 2.5 Productivity Tool" (http://www.microsoft.com/en-us/download/details.aspx?id=30425) $imgTpl = ''; + // use drawing for svg, see https://www.datypic.com/sc/ooxml/e-w_drawing-1.html + $svgTpl = ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '; $i = 0; foreach ($searchParts as $partFileName => &$partContent) { @@ -702,7 +765,57 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM // replace preparations $this->addImageToRelations($partFileName, $rid, $imgPath, $preparedImageAttrs['mime']); - $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']], $imgTpl); + if ($preparedImageAttrs['mime'] !== 'image/svg+xml') { + $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']], $imgTpl); + } else { + $width = Converter::cssToEmu($preparedImageAttrs['width']); + $height = Converter::cssToEmu($preparedImageAttrs['height']); + if ($width === null) { + if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $preparedImageAttrs['width'], $matches)) { + $size = (float) ($matches[1]); + $unit = $matches[2]; + switch ($unit) { + case 'ex': + $size = $size * 2; + + // no break + case 'em': + $width = $size * 152400; + + break; + case '%': + $width = Converter::cssToEmu($preparedImageAttrs['originalWidth']) * $size; + + break; + } + } else { + $width = Converter::cssToEmu($preparedImageAttrs['originalWidth']); + } + } + if ($height === null) { + if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $preparedImageAttrs['height'], $matches)) { + $size = (float) ($matches[1]); + $unit = $matches[2]; + switch ($unit) { + case 'ex': + $size *= 2; + + // no break + case 'em': + $height = $size * 152400; + + break; + case '%': + $height = Converter::cssToEmu($preparedImageAttrs['originalHeight']) * $size; + + break; + } + } else { + $height = Converter::cssToEmu($preparedImageAttrs['originalHeight']); + } + } + $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}', '{ID}', '{NAME}'], [$rid, (string) $width, (string) $height, $imgIndex, 'graphic'], $svgTpl); + } // replace variable $varNameWithArgsFixed = static::ensureMacroCompleted($varNameWithArgs); diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index a06f8782b5..1bc734c568 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -884,14 +884,17 @@ public function testSetCheckboxWithCustomMacro(): void public function testSetImageValue(): void { $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx'); - $imagePath = __DIR__ . '/_files/images/earth.jpg'; + $imageJpg = __DIR__ . '/_files/images/earth.jpg'; + $imageGif = __DIR__ . '/_files/images/mario.gif'; + $imagePng = __DIR__ . '/_files/images/firefox.png'; + $imageSvg = __DIR__ . '/_files/images/phpword.svg'; $variablesReplace = [ - 'headerValue' => function () use ($imagePath) { - return $imagePath; + 'headerValue' => function () use ($imageJpg) { + return $imageJpg; }, - 'documentContent' => ['path' => $imagePath, 'width' => 500, 'height' => 500], - 'footerValue' => ['path' => $imagePath, 'width' => 100, 'height' => 50, 'ratio' => false], + 'documentContent' => ['path' => $imageJpg, 'width' => 500, 'height' => 500], + 'footerValue' => ['path' => $imageJpg, 'width' => 100, 'height' => 50, 'ratio' => false], ]; $templateProcessor->setImageValue(array_keys($variablesReplace), $variablesReplace); @@ -931,7 +934,16 @@ public function testSetImageValue(): void $testFileName = 'images-test-sample.docx'; $phpWord = new PhpWord(); $section = $phpWord->addSection(); - $section->addText('${Test:width=100:ratio=true}'); + $section->addText('${Test0:width=100:ratio=true}'); + $section->addText('${Test1::50:true}'); + $section->addText('${Test2}'); + $section->addText('${Test3:size=10cmx7cm:ratio=false}'); + $section->addText('${Test4:size=100mmx70mm:ratio=true}'); + $section->addText('${Test5:4in::true}'); + $section->addText('${Test6:300pt:200pt}'); + $section->addText('${Test7:25pc:}'); + $section->addText('${Test8:50%:50%}'); + $section->addText('${Test9::5ex}'); $objWriter = IOFactory::createWriter($phpWord, 'Word2007'); $objWriter->save($testFileName); self::assertFileExists($testFileName, "Generated file '{$testFileName}' not found!"); @@ -939,9 +951,8 @@ public function testSetImageValue(): void $resultFileName = 'images-test-result.docx'; $templateProcessor = new TemplateProcessor($testFileName); unlink($testFileName); - $templateProcessor->setImageValue('Test', $imagePath); - $templateProcessor->setImageValue('Test1', $imagePath); - $templateProcessor->setImageValue('Test2', $imagePath); + $templateProcessor->setImageValue('Test0', $imageJpg); + $templateProcessor->setImageValue(['Test1', 'Test2', 'Test3', 'Test4', 'Test5', 'Test6', 'Test7', 'Test8', 'Test9'], [$imageGif, $imagePng, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg, $imageSvg]); $templateProcessor->saveAs($resultFileName); self::assertFileExists($resultFileName, "Generated file '{$resultFileName}' not found!"); @@ -953,7 +964,7 @@ public function testSetImageValue(): void } unlink($resultFileName); - self::assertStringNotContainsString('${Test}', $expectedMainPartXml, 'word/document.xml has no image.'); + self::assertStringNotContainsString('${Test', $expectedMainPartXml, 'word/document.xml has not inserted all images.'); } /** diff --git a/tests/PhpWordTests/_files/images/phpword.svg b/tests/PhpWordTests/_files/images/phpword.svg new file mode 100644 index 0000000000..2fbeeb4af0 --- /dev/null +++ b/tests/PhpWordTests/_files/images/phpword.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 095baa950df33f81284fad89e1ad99744c1f0186 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:38:30 -0800 Subject: [PATCH 153/153] A Bit More Coverage --- src/PhpWord/TemplateProcessor.php | 48 +++++++------------------------ 1 file changed, 10 insertions(+), 38 deletions(-) diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 52621af034..cbe54a18ce 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -575,17 +575,17 @@ private function prepareImageAttrs($replaceImage, $varInlineArgs) if ($mime !== 'image/svg+xml') { $imageData = @getimagesize($imgPath); if (!is_array($imageData)) { - throw new Exception(sprintf('Invalid image: %s', $imgPath)); + throw new Exception(sprintf('Invalid image: %s', $imgPath)); // @codeCoverageIgnore } [$actualWidth, $actualHeight, $imageType] = $imageData; } else { $content = file_get_contents($imgPath); if (!$content) { - throw new Exception(sprintf('Invalid image: %s', $imgPath)); + throw new Exception(sprintf('Invalid image: %s', $imgPath)); // @codeCoverageIgnore } $svgXml = simplexml_load_string($content); if (!$svgXml) { - throw new Exception(sprintf('Invalid image: %s', $imgPath)); + throw new Exception(sprintf('Invalid image: %s', $imgPath)); // @codeCoverageIgnore } $svgAttributes = $svgXml->attributes(); $actualWidth = $svgAttributes->width; @@ -637,7 +637,7 @@ private function addImageToRelations($partFileName, $rid, $imgPath, $imageMimeTy if (isset($extTransform[$imageMimeType])) { $imgExt = $extTransform[$imageMimeType]; } else { - throw new Exception("Unsupported image type $imageMimeType"); + throw new Exception("Unsupported image type $imageMimeType"); // @codeCoverageIgnore } // add image to document @@ -771,47 +771,19 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM $width = Converter::cssToEmu($preparedImageAttrs['width']); $height = Converter::cssToEmu($preparedImageAttrs['height']); if ($width === null) { + $width = Converter::cssToEmu($preparedImageAttrs['originalWidth']); if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $preparedImageAttrs['width'], $matches)) { - $size = (float) ($matches[1]); $unit = $matches[2]; - switch ($unit) { - case 'ex': - $size = $size * 2; - - // no break - case 'em': - $width = $size * 152400; - - break; - case '%': - $width = Converter::cssToEmu($preparedImageAttrs['originalWidth']) * $size; - - break; - } - } else { - $width = Converter::cssToEmu($preparedImageAttrs['originalWidth']); + $size = (float) ($matches[1]) * (($unit === 'ex') ? 2 : 1); + $width = ($unit === '%') ? (Converter::cssToEmu($preparedImageAttrs['originalWidth']) * $size) : ($size * 152400); } } if ($height === null) { + $height = Converter::cssToEmu($preparedImageAttrs['originalHeight']); if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(em|ex|%)$/i', $preparedImageAttrs['height'], $matches)) { - $size = (float) ($matches[1]); $unit = $matches[2]; - switch ($unit) { - case 'ex': - $size *= 2; - - // no break - case 'em': - $height = $size * 152400; - - break; - case '%': - $height = Converter::cssToEmu($preparedImageAttrs['originalHeight']) * $size; - - break; - } - } else { - $height = Converter::cssToEmu($preparedImageAttrs['originalHeight']); + $size = (float) ($matches[1]) * (($unit === 'ex') ? 2 : 1); + $height = ($unit === '%') ? (Converter::cssToEmu($preparedImageAttrs['originalHeight']) * $size) : ($size * 152400); } } $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}', '{ID}', '{NAME}'], [$rid, (string) $width, (string) $height, $imgIndex, 'graphic'], $svgTpl);