From bcfb3e868c0ee086b8b746bd40984b212a294c8b Mon Sep 17 00:00:00 2001 From: Gerald Buttinger Date: Fri, 27 Nov 2015 14:30:22 +0100 Subject: [PATCH 01/53] Add methods setValuesFromArray and cloneRowFromArray to the TemplateProcessor-class and update samples and docs accordingly --- docs/templates-processing.rst | 16 ++++ .../Sample_37_TemplateCloneRowFromArray.php | 91 +++++++++++++++++++ src/PhpWord/TemplateProcessor.php | 34 +++++++ 3 files changed, 141 insertions(+) mode change 100644 => 100755 docs/templates-processing.rst create mode 100755 samples/Sample_37_TemplateCloneRowFromArray.php mode change 100644 => 100755 src/PhpWord/TemplateProcessor.php diff --git a/docs/templates-processing.rst b/docs/templates-processing.rst old mode 100644 new mode 100755 index 6a65ea0d..12b65f80 --- a/docs/templates-processing.rst +++ b/docs/templates-processing.rst @@ -15,11 +15,27 @@ Example: $templateProcessor->setValue('Name', 'Somebody someone'); $templateProcessor->setValue('Street', 'Coming-Undone-Street 32'); +You can also use ``TemplateProcessor::setValuesFromArray`` method to perform replacements from an array of "variable => value"-pairs. + +Example: + +.. code-block:: php + + $replacements = [ + 'Name' => 'Somebody someone', + 'Street' => 'Coming-Undone-Street 32' + ]; + $templateProcessor = new TemplateProcessor('Template.docx'); + $templateProcessor->setValuesFromArray($replacements); + It is not possible to directly add new OOXML elements to the template file being processed, but it is possible to transform main document part of the template using XSLT (see ``TemplateProcessor::applyXslStyleSheet``). See ``Sample_07_TemplateCloneRow.php`` for example on how to create multirow from a single row in a template by using ``TemplateProcessor::cloneRow``. +See ``Sample_37_TemplateCloneRowFromArray.php`` for example on how to create +multirow from a single row with a two-dimensional array as data-source in a template by using ``TemplateProcessor::cloneRowFromArray``. + See ``Sample_23_TemplateBlock.php`` for example on how to clone a block of text using ``TemplateProcessor::cloneBlock`` and delete a block of text using ``TemplateProcessor::deleteBlock``. diff --git a/samples/Sample_37_TemplateCloneRowFromArray.php b/samples/Sample_37_TemplateCloneRowFromArray.php new file mode 100755 index 00000000..5abf1103 --- /dev/null +++ b/samples/Sample_37_TemplateCloneRowFromArray.php @@ -0,0 +1,91 @@ + htmlspecialchars(date('l')), // On section/content + 'time' => htmlspecialchars(date('H:i')), // On footer + 'serverName' => htmlspecialchars(realpath(__DIR__)), // On header +]; +$templateProcessor->setValuesFromArray($replacements); + +// Simple table +$rows = [ + [ + 'rowNumber' => 1, + 'rowValue' => 'Sun' + ], + [ + 'rowNumber' => 2, + 'rowValue' => 'Mercury' + ], + [ + 'rowNumber' => 3, + 'rowValue' => 'Venus' + ], + [ + 'rowNumber' => 4, + 'rowValue' => 'Earth' + ], + [ + 'rowNumber' => 5, + 'rowValue' => 'Mars' + ], + [ + 'rowNumber' => 6, + 'rowValue' => 'Jupiter' + ], + [ + 'rowNumber' => 7, + 'rowValue' => 'Saturn' + ], + [ + 'rowNumber' => 8, + 'rowValue' => 'Uranus' + ], + [ + 'rowNumber' => 9, + 'rowValue' => 'Neptun' + ], + [ + 'rowNumber' => 10, + 'rowValue' => 'Pluto' + ] +]; +$templateProcessor->cloneRowFromArray('rowValue', $rows); + +// Table with a spanned cell +$rows = [ + [ + 'userId' => 1, + 'userFirstName' => 'James', + 'userName' => 'Taylor', + 'userPhone' => '+1 428 889 773' + ], + [ + 'userId' => 2, + 'userFirstName' => 'Robert', + 'userName' => 'Bell', + 'userPhone' => '+1 428 889 774' + ], + [ + 'userId' => 3, + 'userFirstName' => 'Michael', + 'userName' => 'Ray', + 'userPhone' => '+1 428 889 775' + ] +]; +$templateProcessor->cloneRowFromArray('userId', $rows); + + +echo date('H:i:s'), ' Saving the result document...', EOL; +$templateProcessor->saveAs('results/Sample_07_TemplateCloneRow.docx'); + +echo getEndingNotes(array('Word2007' => 'docx')); +if (!CLI) { + include_once 'Sample_Footer.php'; +} diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php old mode 100644 new mode 100755 index ce92bacf..654bcf8c --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -155,6 +155,20 @@ class TemplateProcessor } } + /** + * Set values from a one-dimensional array of "variable => value"-pairs. + * + * @param array $values + * + * @return void + */ + public function setValuesFromArray($values) + { + foreach ($values as $macro => $replace) { + $this->setValue($macro, $replace); + } + } + /** * Returns array of all variables in template. * @@ -234,6 +248,26 @@ class TemplateProcessor $this->tempDocumentMainPart = $result; } + /** + * Clone a table row and populates it's values from a two-dimensional array in a template document. + * + * @param string $search + * @param array $rows + * + * @return void + */ + public function cloneRowFromArray($search, $rows) + { + $this->cloneRow($search, count($rows)); + + foreach ($rows as $rowKey => $rowData) { + $rowNumber = $rowKey+1; + foreach ($rowData as $macro => $replace) { + $this->setValue($macro.'#'.$rowNumber,$replace); + } + } + } + /** * Clone a block. * From 71618f704d092ac32b73f5dc8ca2862946fba930 Mon Sep 17 00:00:00 2001 From: eweso <6918714+eweso@users.noreply.github.com> Date: Fri, 30 Mar 2018 19:24:45 +0200 Subject: [PATCH 02/53] Adding setNumId method for ListItem style By allowing to set the numId in the ListItem style manually, you can separate lists. Every ListItem with the same numId belongs to one list. This allows you to restart list counting. --- src/PhpWord/Style/ListItem.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index 306ecff3..c8bd2101 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * @copyright 2010-2017 PHPWord contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -139,6 +139,16 @@ class ListItem extends AbstractStyle return $this->numId; } + /** + * Set numbering Id, to force list to restart counting. Same num id means same list + * @param int + */ + public function setNumId($numInt) + { + $this->numId = $numInt; + $this->getListTypeStyle(); + } + /** * Get legacy numbering definition * @@ -148,7 +158,12 @@ class ListItem extends AbstractStyle private function getListTypeStyle() { // Check if legacy style already registered in global Style collection - $numStyle = "PHPWordList{$this->listType}"; + $numStyle = "PHPWordList_" . $this->listType; + + if ($this->numId) { + $numStyle .= '_' . $this->numId; + } + if (Style::getStyle($numStyle) !== null) { $this->setNumStyle($numStyle); From a09e7151acd71f1681f4bf70824b12a92099c359 Mon Sep 17 00:00:00 2001 From: eweso <6918714+eweso@users.noreply.github.com> Date: Fri, 30 Mar 2018 22:58:03 +0200 Subject: [PATCH 03/53] Update ListItem.php --- src/PhpWord/Style/ListItem.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index c8bd2101..4a844e48 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -140,8 +140,8 @@ class ListItem extends AbstractStyle } /** - * Set numbering Id, to force list to restart counting. Same num id means same list - * @param int + * Set numbering Id. Same numId means same list + * @param mixed */ public function setNumId($numInt) { From 5741e47129b6c4c4c82af443bc120c8273c123db Mon Sep 17 00:00:00 2001 From: eweso <6918714+eweso@users.noreply.github.com> Date: Sat, 31 Mar 2018 00:37:50 +0200 Subject: [PATCH 04/53] Update ListItem.php --- src/PhpWord/Style/ListItem.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index 4a844e48..74bf3c97 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -141,7 +141,7 @@ class ListItem extends AbstractStyle /** * Set numbering Id. Same numId means same list - * @param mixed + * @param mixed $numInt */ public function setNumId($numInt) { @@ -158,12 +158,12 @@ class ListItem extends AbstractStyle private function getListTypeStyle() { // Check if legacy style already registered in global Style collection - $numStyle = "PHPWordList_" . $this->listType; - + $numStyle = 'PHPWordListType' . $this->listType; + if ($this->numId) { - $numStyle .= '_' . $this->numId; + $numStyle .= 'NumId' . $this->numId; } - + if (Style::getStyle($numStyle) !== null) { $this->setNumStyle($numStyle); From 91ada213c525cb5a2712111e6c73d9ecd587b36a Mon Sep 17 00:00:00 2001 From: eweso <6918714+eweso@users.noreply.github.com> Date: Sat, 31 Mar 2018 00:38:43 +0200 Subject: [PATCH 05/53] Update ListItem.php --- src/PhpWord/Style/ListItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index 74bf3c97..4293940f 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2017 PHPWord contributors + * @copyright 2010-2018 PHPWord contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ From 13fc647d01915891abee0ce106f910223772cf9c Mon Sep 17 00:00:00 2001 From: Maxim Bulygin Date: Thu, 24 May 2018 17:03:35 +0300 Subject: [PATCH 06/53] html writes / setup table cell color --- src/PhpWord/Writer/HTML/Element/Table.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/HTML/Element/Table.php b/src/PhpWord/Writer/HTML/Element/Table.php index 844066f4..866ce1bf 100644 --- a/src/PhpWord/Writer/HTML/Element/Table.php +++ b/src/PhpWord/Writer/HTML/Element/Table.php @@ -50,6 +50,14 @@ class Table extends AbstractElement $rowCellCount = count($rowCells); for ($j = 0; $j < $rowCellCount; $j++) { $cellStyle = $rowCells[$j]->getStyle(); + $cellBgColor = $cellStyle->getBgColor(); + $cellFgColor = null; + if ($cellBgColor) { + $r = hexdec(substr($cellBgColor, 0, 2)); + $g = hexdec(substr($cellBgColor, 2, 2)); + $b = hexdec(substr($cellBgColor, 4, 2)); + $cellFgColor = (($r * 0.299 + $g * 0.587 + $b * 0.114) > 186) ? null : 'ffffff'; + } $cellColSpan = $cellStyle->getGridSpan(); $cellRowSpan = 1; $cellVMerge = $cellStyle->getVMerge(); @@ -73,7 +81,9 @@ class Table extends AbstractElement $cellTag = $tblHeader ? 'th' : 'td'; $cellColSpanAttr = (is_numeric($cellColSpan) && ($cellColSpan > 1) ? " colspan=\"{$cellColSpan}\"" : ''); $cellRowSpanAttr = ($cellRowSpan > 1 ? " rowspan=\"{$cellRowSpan}\"" : ''); - $content .= "<{$cellTag}{$cellColSpanAttr}{$cellRowSpanAttr}>" . PHP_EOL; + $cellBgColorAttr = (is_null($cellBgColor) ? '' : " bgcolor=\"#{$cellBgColor}\""); + $cellFgColorAttr = (is_null($cellFgColor) ? '' : " color=\"#{$cellFgColor}\""); + $content .= "<{$cellTag}{$cellColSpanAttr}{$cellRowSpanAttr}{$cellBgColorAttr}{$cellFgColorAttr}>" . PHP_EOL; $writer = new Container($this->parentWriter, $rowCells[$j]); $content .= $writer->write(); if ($cellRowSpan > 1) { From e40449e7c8691af4dbfefd43e7937acc46e8ce34 Mon Sep 17 00:00:00 2001 From: Maxim Bulygin Date: Thu, 24 May 2018 17:40:21 +0300 Subject: [PATCH 07/53] fix variable names --- src/PhpWord/Writer/HTML/Element/Table.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PhpWord/Writer/HTML/Element/Table.php b/src/PhpWord/Writer/HTML/Element/Table.php index 866ce1bf..2f10df41 100644 --- a/src/PhpWord/Writer/HTML/Element/Table.php +++ b/src/PhpWord/Writer/HTML/Element/Table.php @@ -53,10 +53,10 @@ class Table extends AbstractElement $cellBgColor = $cellStyle->getBgColor(); $cellFgColor = null; if ($cellBgColor) { - $r = hexdec(substr($cellBgColor, 0, 2)); - $g = hexdec(substr($cellBgColor, 2, 2)); - $b = hexdec(substr($cellBgColor, 4, 2)); - $cellFgColor = (($r * 0.299 + $g * 0.587 + $b * 0.114) > 186) ? null : 'ffffff'; + $red = hexdec(substr($cellBgColor, 0, 2)); + $green = hexdec(substr($cellBgColor, 2, 2)); + $blue = hexdec(substr($cellBgColor, 4, 2)); + $cellFgColor = (($red * 0.299 + $green * 0.587 + $blue * 0.114) > 186) ? null : 'ffffff'; } $cellColSpan = $cellStyle->getGridSpan(); $cellRowSpan = 1; From c408ac5d504599f38e19111108a6719b3a5c4306 Mon Sep 17 00:00:00 2001 From: troosan Date: Sun, 30 Dec 2018 14:14:27 +0100 Subject: [PATCH 08/53] Use embedded http server to test loading of remote images --- CHANGELOG.md | 9 ++ tests/PhpWord/Element/CellTest.php | 6 +- tests/PhpWord/Element/FooterTest.php | 6 +- tests/PhpWord/Element/HeaderTest.php | 6 +- tests/PhpWord/Element/ImageTest.php | 9 +- tests/PhpWord/MediaTest.php | 8 +- tests/PhpWord/Shared/HtmlTest.php | 5 +- tests/PhpWord/Writer/HTMLTest.php | 5 +- tests/PhpWord/Writer/Word2007Test.php | 5 +- tests/PhpWord/_files/images/new-php-logo.png | Bin 0 -> 10298 bytes .../AbstractWebServerEmbeddedTest.php | 80 ++++++++++++++++++ 11 files changed, 119 insertions(+), 20 deletions(-) create mode 100644 tests/PhpWord/_files/images/new-php-logo.png create mode 100644 tests/PhpWord/_includes/AbstractWebServerEmbeddedTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d07c7bd..735264f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +v0.17.0 (?? ??? 2019) +---------------------- +### Added + +### Fixed + +### Miscelaneous +- Use embedded http server to test loading of remote images @troosan # + v0.16.0 (30 dec 2018) ---------------------- ### Added diff --git a/tests/PhpWord/Element/CellTest.php b/tests/PhpWord/Element/CellTest.php index d4aaa488..7e63967a 100644 --- a/tests/PhpWord/Element/CellTest.php +++ b/tests/PhpWord/Element/CellTest.php @@ -17,12 +17,14 @@ namespace PhpOffice\PhpWord\Element; +use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest; + /** * Test class for PhpOffice\PhpWord\Element\Cell * * @runTestsInSeparateProcesses */ -class CellTest extends \PHPUnit\Framework\TestCase +class CellTest extends AbstractWebServerEmbeddedTest { /** * New instance @@ -165,7 +167,7 @@ class CellTest extends \PHPUnit\Framework\TestCase public function testAddImageSectionByUrl() { $oCell = new Cell(); - $element = $oCell->addImage('http://php.net/images/logos/php-med-trans-light.gif'); + $element = $oCell->addImage(self::getRemoteGifImageUrl()); $this->assertCount(1, $oCell->getElements()); $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); diff --git a/tests/PhpWord/Element/FooterTest.php b/tests/PhpWord/Element/FooterTest.php index 9de2487a..b1ef4677 100644 --- a/tests/PhpWord/Element/FooterTest.php +++ b/tests/PhpWord/Element/FooterTest.php @@ -17,12 +17,14 @@ namespace PhpOffice\PhpWord\Element; +use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest; + /** * Test class for PhpOffice\PhpWord\Element\Footer * * @runTestsInSeparateProcesses */ -class FooterTest extends \PHPUnit\Framework\TestCase +class FooterTest extends AbstractWebServerEmbeddedTest { /** * New instance @@ -116,7 +118,7 @@ class FooterTest extends \PHPUnit\Framework\TestCase public function testAddImageByUrl() { $oFooter = new Footer(1); - $element = $oFooter->addImage('http://php.net/images/logos/php-med-trans-light.gif'); + $element = $oFooter->addImage(self::getRemoteGifImageUrl()); $this->assertCount(1, $oFooter->getElements()); $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); diff --git a/tests/PhpWord/Element/HeaderTest.php b/tests/PhpWord/Element/HeaderTest.php index e61175f1..4bbf7b74 100644 --- a/tests/PhpWord/Element/HeaderTest.php +++ b/tests/PhpWord/Element/HeaderTest.php @@ -17,12 +17,14 @@ namespace PhpOffice\PhpWord\Element; +use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest; + /** * Test class for PhpOffice\PhpWord\Element\Header * * @runTestsInSeparateProcesses */ -class HeaderTest extends \PHPUnit\Framework\TestCase +class HeaderTest extends AbstractWebServerEmbeddedTest { /** * New instance @@ -125,7 +127,7 @@ class HeaderTest extends \PHPUnit\Framework\TestCase public function testAddImageByUrl() { $oHeader = new Header(1); - $element = $oHeader->addImage('http://php.net/images/logos/php-med-trans-light.gif'); + $element = $oHeader->addImage(self::getRemoteGifImageUrl()); $this->assertCount(1, $oHeader->getElements()); $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $element); diff --git a/tests/PhpWord/Element/ImageTest.php b/tests/PhpWord/Element/ImageTest.php index 747a77ac..f56d0794 100644 --- a/tests/PhpWord/Element/ImageTest.php +++ b/tests/PhpWord/Element/ImageTest.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWord\Element; +use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest; use PhpOffice\PhpWord\SimpleType\Jc; /** @@ -24,7 +25,7 @@ use PhpOffice\PhpWord\SimpleType\Jc; * * @runTestsInSeparateProcesses */ -class ImageTest extends \PHPUnit\Framework\TestCase +class ImageTest extends AbstractWebServerEmbeddedTest { /** * New instance @@ -131,7 +132,7 @@ class ImageTest extends \PHPUnit\Framework\TestCase */ public function testUnsupportedImage() { - //disable ssl verification, never do this in real application, you should pass the certiciate instead!!! + //disable ssl verification, never do this in real application, you should pass the certificiate instead!!! $arrContextOptions = array( 'ssl' => array( 'verify_peer' => false, @@ -139,7 +140,7 @@ class ImageTest extends \PHPUnit\Framework\TestCase ), ); stream_context_set_default($arrContextOptions); - $object = new Image('https://samples.libav.org/image-samples/RACECAR.BMP'); + $object = new Image(self::getRemoteBmpImageUrl()); $object->getSource(); } @@ -215,7 +216,7 @@ class ImageTest extends \PHPUnit\Framework\TestCase */ public function testConstructFromGd() { - $source = 'http://php.net/images/logos/php-icon.png'; + $source = self::getRemoteImageUrl(); $image = new Image($source); $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $image); diff --git a/tests/PhpWord/MediaTest.php b/tests/PhpWord/MediaTest.php index 02492016..3cf62b59 100644 --- a/tests/PhpWord/MediaTest.php +++ b/tests/PhpWord/MediaTest.php @@ -24,7 +24,7 @@ use PhpOffice\PhpWord\Element\Image; * * @runTestsInSeparateProcesses */ -class MediaTest extends \PHPUnit\Framework\TestCase +class MediaTest extends AbstractWebServerEmbeddedTest { /** * Get section media elements @@ -49,7 +49,7 @@ class MediaTest extends \PHPUnit\Framework\TestCase { $local = __DIR__ . '/_files/images/mars.jpg'; $object = __DIR__ . '/_files/documents/sheet.xls'; - $remote = 'http://php.net/images/logos/php-med-trans-light.gif'; + $remote = self::getRemoteImageUrl(); Media::addElement('section', 'image', $local, new Image($local)); Media::addElement('section', 'image', $local, new Image($local)); Media::addElement('section', 'image', $remote, new Image($local)); @@ -77,7 +77,7 @@ class MediaTest extends \PHPUnit\Framework\TestCase public function testAddHeaderMediaElement() { $local = __DIR__ . '/_files/images/mars.jpg'; - $remote = 'http://php.net/images/logos/php-med-trans-light.gif'; + $remote = self::getRemoteImageUrl(); Media::addElement('header1', 'image', $local, new Image($local)); Media::addElement('header1', 'image', $local, new Image($local)); Media::addElement('header1', 'image', $remote, new Image($remote)); @@ -92,7 +92,7 @@ class MediaTest extends \PHPUnit\Framework\TestCase public function testAddFooterMediaElement() { $local = __DIR__ . '/_files/images/mars.jpg'; - $remote = 'http://php.net/images/logos/php-med-trans-light.gif'; + $remote = self::getRemoteImageUrl(); Media::addElement('footer1', 'image', $local, new Image($local)); Media::addElement('footer1', 'image', $local, new Image($local)); Media::addElement('footer1', 'image', $remote, new Image($remote)); diff --git a/tests/PhpWord/Shared/HtmlTest.php b/tests/PhpWord/Shared/HtmlTest.php index 89292a20..2f9a4be4 100644 --- a/tests/PhpWord/Shared/HtmlTest.php +++ b/tests/PhpWord/Shared/HtmlTest.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWord\Shared; +use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest; use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\LineSpacingRule; @@ -27,7 +28,7 @@ use PhpOffice\PhpWord\TestHelperDOCX; * Test class for PhpOffice\PhpWord\Shared\Html * @coversDefaultClass \PhpOffice\PhpWord\Shared\Html */ -class HtmlTest extends \PHPUnit\Framework\TestCase +class HtmlTest extends AbstractWebServerEmbeddedTest { /** * Test unit conversion functions with various numbers @@ -487,7 +488,7 @@ class HtmlTest extends \PHPUnit\Framework\TestCase */ public function testParseRemoteImage() { - $src = 'https://phpword.readthedocs.io/en/latest/_images/phpword.png'; + $src = self::getRemoteImageUrl(); $phpWord = new \PhpOffice\PhpWord\PhpWord(); $section = $phpWord->addSection(); diff --git a/tests/PhpWord/Writer/HTMLTest.php b/tests/PhpWord/Writer/HTMLTest.php index 8868db5a..24a8bca3 100644 --- a/tests/PhpWord/Writer/HTMLTest.php +++ b/tests/PhpWord/Writer/HTMLTest.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWord\Writer; +use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Jc; @@ -26,7 +27,7 @@ use PhpOffice\PhpWord\SimpleType\Jc; * * @runTestsInSeparateProcesses */ -class HTMLTest extends \PHPUnit\Framework\TestCase +class HTMLTest extends AbstractWebServerEmbeddedTest { /** * Construct @@ -57,7 +58,7 @@ class HTMLTest extends \PHPUnit\Framework\TestCase { $localImage = __DIR__ . '/../_files/images/PhpWord.png'; $archiveImage = 'zip://' . __DIR__ . '/../_files/documents/reader.docx#word/media/image1.jpeg'; - $gdImage = 'http://php.net/images/logos/php-med-trans-light.gif'; + $gdImage = self::getRemoteGifImageUrl(); $objectSrc = __DIR__ . '/../_files/documents/sheet.xls'; $file = __DIR__ . '/../_files/temp.html'; diff --git a/tests/PhpWord/Writer/Word2007Test.php b/tests/PhpWord/Writer/Word2007Test.php index 0db36fc1..563475b4 100644 --- a/tests/PhpWord/Writer/Word2007Test.php +++ b/tests/PhpWord/Writer/Word2007Test.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWord\Writer; +use PhpOffice\PhpWord\AbstractWebServerEmbeddedTest; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\TestHelperDOCX; @@ -26,7 +27,7 @@ use PhpOffice\PhpWord\TestHelperDOCX; * * @runTestsInSeparateProcesses */ -class Word2007Test extends \PHPUnit\Framework\TestCase +class Word2007Test extends AbstractWebServerEmbeddedTest { /** * Tear down after each test @@ -75,7 +76,7 @@ class Word2007Test extends \PHPUnit\Framework\TestCase public function testSave() { $localImage = __DIR__ . '/../_files/images/earth.jpg'; - $remoteImage = 'http://php.net/images/logos/new-php-logo.png'; + $remoteImage = self::getRemoteGifImageUrl(); $phpWord = new PhpWord(); $phpWord->addFontStyle('Font', array('size' => 11)); $phpWord->addParagraphStyle('Paragraph', array('alignment' => Jc::CENTER)); diff --git a/tests/PhpWord/_files/images/new-php-logo.png b/tests/PhpWord/_files/images/new-php-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6649079930716c425de2d675a0feebbe7d0a0d70 GIT binary patch literal 10298 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 M07*qoM6N<$f)aut$N&HU literal 0 HcmV?d00001 diff --git a/tests/PhpWord/_includes/AbstractWebServerEmbeddedTest.php b/tests/PhpWord/_includes/AbstractWebServerEmbeddedTest.php new file mode 100644 index 00000000..9316a9fe --- /dev/null +++ b/tests/PhpWord/_includes/AbstractWebServerEmbeddedTest.php @@ -0,0 +1,80 @@ +start(); + while (!self::$httpServer->isRunning()) { + usleep(1000); + } + } + } + + public static function tearDownAfterClass() + { + if (self::isBuiltinServerSupported()) { + self::$httpServer->stop(); + } + } + + protected static function getBaseUrl() + { + return 'http://localhost:8080'; + } + + protected static function getRemoteImageUrl() + { + if (self::$httpServer) { + return self::getBaseUrl() . '/images/new-php-logo.png'; + } + + return 'http://php.net/images/logos/new-php-logo.png'; + } + + protected static function getRemoteGifImageUrl() + { + if (self::$httpServer) { + return self::getBaseUrl() . '/images/mario.gif'; + } + + return 'http://php.net/images/logos/php-med-trans-light.gif'; + } + + protected static function getRemoteBmpImageUrl() + { + if (self::$httpServer) { + return self::getBaseUrl() . '/images/duke_nukem.bmp'; + } + + return 'https://samples.libav.org/image-samples/RACECAR.BMP'; + } + + private static function isBuiltinServerSupported() + { + return version_compare(PHP_VERSION, '5.4.0', '>='); + } +} From f91863ed6431cb5e7eb6920385f51696951e4f9e Mon Sep 17 00:00:00 2001 From: troosan Date: Thu, 3 Jan 2019 11:33:56 +0100 Subject: [PATCH 09/53] Add RTL aligning of tables --- samples/Sample_36_RTL.php | 23 +++++++++++++ samples/index.php | 2 +- src/PhpWord/Reader/Word2007/AbstractPart.php | 1 + src/PhpWord/Style/Table.php | 32 +++++++++++++++++++ src/PhpWord/Writer/ODText/Style/Paragraph.php | 4 +++ src/PhpWord/Writer/ODText/Style/Table.php | 1 + src/PhpWord/Writer/Word2007/Style/Table.php | 3 ++ tests/PhpWord/Reader/Word2007/StyleTest.php | 18 +++++++++++ .../Writer/Word2007/Style/TableTest.php | 17 ++++++++++ 9 files changed, 100 insertions(+), 1 deletion(-) diff --git a/samples/Sample_36_RTL.php b/samples/Sample_36_RTL.php index 615557d7..ca93b14d 100644 --- a/samples/Sample_36_RTL.php +++ b/samples/Sample_36_RTL.php @@ -14,6 +14,29 @@ $textrun->addText('This is a Left to Right paragraph.'); $textrun = $section->addTextRun(array('alignment' => \PhpOffice\PhpWord\SimpleType\Jc::END)); $textrun->addText('سلام این یک پاراگراف راست به چپ است', array('rtl' => true)); +$section->addText('Table visually presented as RTL'); +$style = array('rtl' => true, 'size' => 12); +$tableStyle = array('borderSize' => 6, 'borderColor' => '000000', 'width' => 5000, 'unit' => \PhpOffice\PhpWord\SimpleType\TblWidth::PERCENT, 'bidiVisual' => true); + +$table = $section->addTable($tableStyle); +$cellHCentered = array('alignment' => \PhpOffice\PhpWord\SimpleType\Jc::CENTER); +$cellHEnd = array('alignment' => \PhpOffice\PhpWord\SimpleType\Jc::END); +$cellVCentered = array('valign' => \PhpOffice\PhpWord\Style\Cell::VALIGN_CENTER); + +//Vidually bidirectinal table +$table->addRow(); +$cell = $table->addCell(500, $cellVCentered); +$textrun = $cell->addTextRun($cellHCentered); +$textrun->addText('ردیف', $style); + +$cell = $table->addCell(11000); +$textrun = $cell->addTextRun($cellHEnd); +$textrun->addText('سوالات', $style); + +$cell = $table->addCell(500, $cellVCentered); +$textrun = $cell->addTextRun($cellHCentered); +$textrun->addText('بارم', $style); + // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); if (!CLI) { diff --git a/samples/index.php b/samples/index.php index 3dbc09ff..20b56b83 100644 --- a/samples/index.php +++ b/samples/index.php @@ -22,7 +22,7 @@ if (!CLI) { Read the Docs

-Requirement check:'; diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index a7816b19..bb4a3a49 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -483,6 +483,7 @@ abstract class AbstractPart $styleDefs["border{$ucfSide}Style"] = array(self::READ_VALUE, "w:tblBorders/w:$side", 'w:val'); } $styleDefs['layout'] = array(self::READ_VALUE, 'w:tblLayout', 'w:type'); + $styleDefs['bidiVisual'] = array(self::READ_TRUE, 'w:bidiVisual'); $styleDefs['cellSpacing'] = array(self::READ_VALUE, 'w:tblCellSpacing', 'w:w'); $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); diff --git a/src/PhpWord/Style/Table.php b/src/PhpWord/Style/Table.php index caf2c580..f777ac67 100644 --- a/src/PhpWord/Style/Table.php +++ b/src/PhpWord/Style/Table.php @@ -170,6 +170,14 @@ class Table extends Border */ private $columnWidths; + /** + * Visually Right to Left Table + * + * @see http://www.datypic.com/sc/ooxml/e-w_bidiVisual-1.html + * @var bool + */ + private $bidiVisual = false; + /** * Create new table style * @@ -775,4 +783,28 @@ class Table extends Border { $this->columnWidths = $value; } + + /** + * Get bidiVisual + * + * @return bool + */ + public function isBidiVisual() + { + return $this->bidiVisual; + } + + /** + * Set bidiVisual + * + * @param bool $bidi + * Set to true to visually present table as Right to Left + * @return self + */ + public function setBidiVisual($bidi) + { + $this->bidiVisual = $bidi; + + return $this; + } } diff --git a/src/PhpWord/Writer/ODText/Style/Paragraph.php b/src/PhpWord/Writer/ODText/Style/Paragraph.php index 223d02f0..f247dcc1 100644 --- a/src/PhpWord/Writer/ODText/Style/Paragraph.php +++ b/src/PhpWord/Writer/ODText/Style/Paragraph.php @@ -54,6 +54,10 @@ class Paragraph extends AbstractStyle $xmlWriter->writeAttribute('fo:margin-bottom', $marginBottom . 'cm'); $xmlWriter->writeAttribute('fo:text-align', $style->getAlignment()); } + + //Right to left + $xmlWriter->writeAttributeIf($style->isBidi(), 'style:writing-mode', 'rl-tb'); + $xmlWriter->endElement(); //style:paragraph-properties $xmlWriter->endElement(); //style:style diff --git a/src/PhpWord/Writer/ODText/Style/Table.php b/src/PhpWord/Writer/ODText/Style/Table.php index c64dee4f..646f2e44 100644 --- a/src/PhpWord/Writer/ODText/Style/Table.php +++ b/src/PhpWord/Writer/ODText/Style/Table.php @@ -43,6 +43,7 @@ class Table extends AbstractStyle //$xmlWriter->writeAttribute('style:width', 'table'); $xmlWriter->writeAttribute('style:rel-width', 100); $xmlWriter->writeAttribute('table:align', 'center'); + $xmlWriter->writeAttributeIf($style->isBidiVisual(), 'style:writing-mode', 'rl-tb'); $xmlWriter->endElement(); // style:table-properties $xmlWriter->endElement(); // style:style diff --git a/src/PhpWord/Writer/Word2007/Style/Table.php b/src/PhpWord/Writer/Word2007/Style/Table.php index 7f49be7c..443d6705 100644 --- a/src/PhpWord/Writer/Word2007/Style/Table.php +++ b/src/PhpWord/Writer/Word2007/Style/Table.php @@ -86,6 +86,9 @@ class Table extends AbstractStyle $styleWriter = new TablePosition($xmlWriter, $style->getPosition()); $styleWriter->write(); + //Right to left + $xmlWriter->writeElementIf($style->isBidiVisual() !== null, 'w:bidiVisual', 'w:val', $this->writeOnOf($style->isBidiVisual())); + $this->writeMargin($xmlWriter, $style); $this->writeBorder($xmlWriter, $style); diff --git a/tests/PhpWord/Reader/Word2007/StyleTest.php b/tests/PhpWord/Reader/Word2007/StyleTest.php index 4a7add16..ad48dcba 100644 --- a/tests/PhpWord/Reader/Word2007/StyleTest.php +++ b/tests/PhpWord/Reader/Word2007/StyleTest.php @@ -147,6 +147,24 @@ class StyleTest extends AbstractTestReader $this->assertSame(2160, $tableStyle->getIndent()->getValue()); } + public function testReadTableRTL() + { + $documentXml = ' + + + + '; + + $phpWord = $this->getDocumentFromString(array('document' => $documentXml)); + + $elements = $phpWord->getSection(0)->getElements(); + $this->assertInstanceOf('PhpOffice\PhpWord\Element\Table', $elements[0]); + $this->assertInstanceOf('PhpOffice\PhpWord\Style\Table', $elements[0]->getStyle()); + /** @var \PhpOffice\PhpWord\Style\Table $tableStyle */ + $tableStyle = $elements[0]->getStyle(); + $this->assertTrue($tableStyle->isBidiVisual()); + } + public function testReadHidden() { $documentXml = ' diff --git a/tests/PhpWord/Writer/Word2007/Style/TableTest.php b/tests/PhpWord/Writer/Word2007/Style/TableTest.php index ec3b2483..8e5cb634 100644 --- a/tests/PhpWord/Writer/Word2007/Style/TableTest.php +++ b/tests/PhpWord/Writer/Word2007/Style/TableTest.php @@ -141,4 +141,21 @@ class TableTest extends \PHPUnit\Framework\TestCase $this->assertSame($value, (int) $doc->getElementAttribute($path, 'w:w')); $this->assertSame($type, $doc->getElementAttribute($path, 'w:type')); } + + public function testRigthToLeft() + { + $tableStyle = new Table(); + $tableStyle->setBidiVisual(true); + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $table = $section->addTable($tableStyle); + $table->addRow(); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $path = '/w:document/w:body/w:tbl/w:tblPr/w:bidiVisual'; + $this->assertTrue($doc->elementExists($path)); + $this->assertEquals('1', $doc->getElementAttribute($path, 'w:val')); + } } From 6aae8bdccb9aca8ba3b40aa0e5ab942fe7eddc10 Mon Sep 17 00:00:00 2001 From: troosan Date: Thu, 3 Jan 2019 11:36:28 +0100 Subject: [PATCH 10/53] update doc and release note --- CHANGELOG.md | 1 + docs/styles.rst | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 735264f1..c7030a5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). v0.17.0 (?? ??? 2019) ---------------------- ### Added +- Add RightToLeft table presentation. @troosan #1550 ### Fixed diff --git a/docs/styles.rst b/docs/styles.rst index 31d04a3b..2be6eb94 100644 --- a/docs/styles.rst +++ b/docs/styles.rst @@ -108,11 +108,12 @@ Available Table style options: - ``border(Top|Right|Bottom|Left)Size``. Border size in *twip*. - ``cellMargin(Top|Right|Bottom|Left)``. Cell margin in *twip*. - ``indent``. Table indent from leading margin. Must be an instance of ``\PhpOffice\PhpWord\ComplexType\TblWidth``. -- ``width``. Table width in percent. +- ``width``. Table width in Fiftieths of a Percent or Twentieths of a Point. - ``unit``. The unit to use for the width. One of ``\PhpOffice\PhpWord\SimpleType\TblWidth``. Defaults to *auto*. - ``layout``. Table layout, either *fixed* or *autofit* See ``\PhpOffice\PhpWord\Style\Table`` for constants. - ``cellSpacing`` Cell spacing in *twip* - ``position`` Floating Table Positioning, see below for options +- ``bidiVisual`` Present table as Right-To-Left Floating Table Positioning options: From 3c9fa2df13517c3ffa29aaaea7da6c3455a909e6 Mon Sep 17 00:00:00 2001 From: Stathis Papadopoulos Date: Mon, 28 Jan 2019 10:50:28 +0100 Subject: [PATCH 11/53] Language::validateLocale should pass with locale 'zxx'. --- src/PhpWord/Style/Language.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Style/Language.php b/src/PhpWord/Style/Language.php index 8174f6ee..dd3ed819 100644 --- a/src/PhpWord/Style/Language.php +++ b/src/PhpWord/Style/Language.php @@ -229,7 +229,7 @@ final class Language extends AbstractStyle return strtolower($locale) . '-' . strtoupper($locale); } - if ($locale !== null && strstr($locale, '-') === false) { + if ($locale !== null && $locale !== 'zxx' && strstr($locale, '-') === false) { throw new \InvalidArgumentException($locale . ' is not a valid language code'); } From 67f3bd369cb42cc547d832035d25e3912447071e Mon Sep 17 00:00:00 2001 From: troosan Date: Thu, 31 Jan 2019 01:26:19 +0100 Subject: [PATCH 12/53] Add methods to replace macro with ComplexType --- .gitignore | 1 - samples/Sample_40_TemplateSetComplexValue.php | 45 ++++ .../Sample_40_TemplateSetComplexValue.docx | Bin 0 -> 14735 bytes src/PhpWord/TemplateProcessor.php | 212 ++++++++++++++++++ tests/PhpWord/TemplateProcessorTest.php | 128 +++++++++++ .../_includes/TestableTemplateProcesor.php | 28 +++ 6 files changed, 413 insertions(+), 1 deletion(-) create mode 100644 samples/Sample_40_TemplateSetComplexValue.php create mode 100644 samples/resources/Sample_40_TemplateSetComplexValue.docx diff --git a/.gitignore b/.gitignore index b2ec7e23..dd858cea 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ composer.phar vendor /report /build -/samples/resources /samples/results /.settings phpword.ini diff --git a/samples/Sample_40_TemplateSetComplexValue.php b/samples/Sample_40_TemplateSetComplexValue.php new file mode 100644 index 00000000..094823f7 --- /dev/null +++ b/samples/Sample_40_TemplateSetComplexValue.php @@ -0,0 +1,45 @@ +addText('This title has been set ', array('bold' => true, 'italic' => true, 'color' => 'blue')); +$title->addText('dynamically', array('bold' => true, 'italic' => true, 'color' => 'red', 'underline' => 'single')); +$templateProcessor->setComplexBlock('title', $title); + +$inline = new TextRun(); +$inline->addText('by a red italic text', array('italic' => true, 'color' => 'red')); +$templateProcessor->setComplexValue('inline', $inline); + +$table = new Table(array('borderSize' => 12, 'borderColor' => 'green', 'width' => 6000, 'unit' => TblWidth::TWIP)); +$table->addRow(); +$table->addCell(150)->addText('Cell A1'); +$table->addCell(150)->addText('Cell A2'); +$table->addCell(150)->addText('Cell A3'); +$table->addRow(); +$table->addCell(150)->addText('Cell B1'); +$table->addCell(150)->addText('Cell B2'); +$table->addCell(150)->addText('Cell B3'); +$templateProcessor->setComplexBlock('table', $table); + +$field = new Field('DATE', array('dateformat' => 'dddd d MMMM yyyy H:mm:ss'), array('PreserveFormat')); +$templateProcessor->setComplexValue('field', $field); + +// $link = new Link('https://github.com/PHPOffice/PHPWord'); +// $templateProcessor->setComplexValue('link', $link); + +echo date('H:i:s'), ' Saving the result document...', EOL; +$templateProcessor->saveAs('results/Sample_40_TemplateSetComplexValue.docx'); + +echo getEndingNotes(array('Word2007' => 'docx'), 'results/Sample_40_TemplateSetComplexValue.docx'); +if (!CLI) { + include_once 'Sample_Footer.php'; +} diff --git a/samples/resources/Sample_40_TemplateSetComplexValue.docx b/samples/resources/Sample_40_TemplateSetComplexValue.docx new file mode 100644 index 0000000000000000000000000000000000000000..7265908e8c5c54842b11079507f3fea33af4b8e1 GIT binary patch literal 14735 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 DlaFJ( literal 0 HcmV?d00001 diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index fbfdd9dc..498cf701 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord; use PhpOffice\Common\Text; +use PhpOffice\Common\XMLWriter; use PhpOffice\PhpWord\Escaper\RegExp; use PhpOffice\PhpWord\Escaper\Xml; use PhpOffice\PhpWord\Exception\CopyFileException; @@ -249,6 +250,46 @@ class TemplateProcessor return $subject; } + /** + * @param string $search + * @param \PhpOffice\PhpWord\Element\AbstractElement $complexType + */ + public function setComplexValue($search, \PhpOffice\PhpWord\Element\AbstractElement $complexType) + { + $elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1); + $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; + + $xmlWriter = new XMLWriter(); + /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $elementWriter */ + $elementWriter = new $objectClass($xmlWriter, $complexType, true); + $elementWriter->write(); + + $where = $this->findContainingXmlBlockForMacro($search, 'w:r'); + $block = $this->getSlice($where['start'], $where['end']); + $textParts = $this->splitTextIntoTexts($block); + $this->replaceXmlBlock($search, $textParts, 'w:r'); + + $search = static::ensureMacroCompleted($search); + $this->replaceXmlBlock($search, $xmlWriter->getData(), 'w:r'); + } + + /** + * @param string $search + * @param \PhpOffice\PhpWord\Element\AbstractElement $complexType + */ + public function setComplexBlock($search, \PhpOffice\PhpWord\Element\AbstractElement $complexType) + { + $elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1); + $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; + + $xmlWriter = new XMLWriter(); + /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $elementWriter */ + $elementWriter = new $objectClass($xmlWriter, $complexType, false); + $elementWriter->write(); + + $this->replaceXmlBlock($search, $xmlWriter->getData(), 'w:p'); + } + /** * @param mixed $search * @param mixed $replace @@ -685,6 +726,7 @@ class TemplateProcessor public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVariables = false, $variableReplacements = null) { $xmlBlock = null; + $matches = array(); preg_match( '/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', $this->tempDocumentMainPart, @@ -724,6 +766,7 @@ class TemplateProcessor */ public function replaceBlock($blockname, $replacement) { + $matches = array(); preg_match( '/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', $this->tempDocumentMainPart, @@ -865,6 +908,7 @@ class TemplateProcessor */ protected function getVariablesForPart($documentPartXML) { + $matches = array(); preg_match_all('/\$\{(.*?)}/i', $documentPartXML, $matches); return $matches[1]; @@ -893,6 +937,7 @@ class TemplateProcessor $pattern = '~PartName="\/(word\/document.*?\.xml)" ContentType="application\/vnd\.openxmlformats-officedocument\.wordprocessingml\.document\.main\+xml"~'; + $matches = array(); preg_match($pattern, $contentTypes, $matches); return array_key_exists(1, $matches) ? $matches[1] : 'word/document.xml'; @@ -1031,4 +1076,171 @@ class TemplateProcessor return $results; } + + /** + * Replace an XML block surrounding a macro with a new block + * + * @param string $macro Name of macro + * @param string $block New block content + * @param string $blockType XML tag type of block + * @return \PhpOffice\PhpWord\TemplateProcessor Fluent interface + */ + protected function replaceXmlBlock($macro, $block, $blockType = 'w:p') + { + $where = $this->findContainingXmlBlockForMacro($macro, $blockType); + if (false !== $where) { + $this->tempDocumentMainPart = $this->getSlice(0, $where['start']) . $block . $this->getSlice($where['end']); + } + + return $this; + } + + /** + * Find start and end of XML block containing the given macro + * e.g. ...${macro}... + * + * Note that only the first instance of the macro will be found + * + * @param string $macro Name of macro + * @param string $blockType XML tag for block + * @return bool|int[] FALSE if not found, otherwise array with start and end + */ + protected function findContainingXmlBlockForMacro($macro, $blockType = 'w:p') + { + $macroPos = $this->findMacro($macro); + if (false === $macroPos) { + return false; + } + $start = $this->findXmlBlockStart($macroPos, $blockType); + if (0 > $start) { + return false; + } + $end = $this->findXmlBlockEnd($start, $blockType); + if (0 > $end) { + return false; + } + + return array('start' => $start, 'end' => $end); + } + + /** + * Find start and end of XML block containing the given block macro + * e.g. ...${macro}...${/macro}... + * + * Note that only the first instance of the macro will be found + * + * @param string $macro Name of macro + * @param string $blockType XML tag for block + * @return bool|int[] FALSE if not found, otherwise array with start and end + */ + protected function findContainingXmlBlockForBlockMacro($macro, $blockType = 'w:p') + { + $macroStartPos = $this->findMacro($macro); + if (0 > $macroStartPos) { + return false; + } + $macroEndPos = $this->findMacro('/' . $macro, $macroStartPos); + if (0 > $macroEndPos) { + return false; + } + $start = $this->findXmlBlockStart($macroStartPos, $blockType); + if (0 > $start) { + return false; + } + $end = $this->findXmlBlockEnd($macroEndPos, $blockType); + if (0 > $end) { + return false; + } + + return array('start' => $start, 'end' => $end); + } + + /** + * Find the position of (the start of) a macro + * + * Returns -1 if not found, otherwise position of opening $ + * + * Note that only the first instance of the macro will be found + * + * @param string $search Macro name + * @param string $offset Offset from which to start searching + * @return int -1 if macro not found + */ + protected function findMacro($search, $offset = 0) + { + $search = static::ensureMacroCompleted($search); + $pos = strpos($this->tempDocumentMainPart, $search, $offset); + + return ($pos === false) ? -1 : $pos; + } + + /** + * Find the start position of the nearest XML block start before $offset + * + * @param int $offset Search position + * @param string $blockType XML Block tag + * @return int -1 if block start not found + */ + protected function findXmlBlockStart($offset, $blockType) + { + // first try XML tag with attributes + $blockStart = strrpos($this->tempDocumentMainPart, '<' . $blockType . ' ', ((strlen($this->tempDocumentMainPart) - $offset) * -1)); + // if not found, or if found but contains the XML tag without attribute + if (false === $blockStart || strrpos($this->getSlice($blockStart, $offset), '<' . $blockType . '>')) { + // also try XML tag without attributes + $blockStart = strrpos($this->tempDocumentMainPart, '<' . $blockType . '>', ((strlen($this->tempDocumentMainPart) - $offset) * -1)); + } + + return ($blockStart === false) ? -1 : $blockStart; + } + + /** + * Find the nearest block end position after $offset + * + * @param int $offset Search position + * @param string $blockType XML Block tag + * @return int -1 if block end not found + */ + protected function findXmlBlockEnd($offset, $blockType) + { + $blockEndStart = strpos($this->tempDocumentMainPart, '', $offset); + // return position of end of tag if found, otherwise -1 + + return ($blockEndStart === false) ? -1 : $blockEndStart + 3 + strlen($blockType); + } + + /** + * Splits a w:r/w:t into a list of w:r where each ${macro} is in a separate w:r + * + * @param string $text + * @return string + */ + protected function splitTextIntoTexts($text) + { + if (!$this->textNeedsSplitting($text)) { + return $text; + } + $matches = array(); + if (preg_match('/()/i', $text, $matches)) { + $extractedStyle = $matches[0]; + } else { + $extractedStyle = ''; + } + + $unformattedText = preg_replace('/>\s+<', $text); + $result = str_replace(array('${', '}'), array('' . $extractedStyle . '${', '}' . $extractedStyle . ''), $unformattedText); + + return str_replace(array('' . $extractedStyle . '', '', ''), array('', '', ''), $result); + } + + /** + * Returns true if string contains a macro that is not in it's own w:r + * + * @param string $text + * @return bool + */ + protected function textNeedsSplitting($text) + { + return preg_match('/[^>]\${|}[^<]/i', $text) == 1; + } } diff --git a/tests/PhpWord/TemplateProcessorTest.php b/tests/PhpWord/TemplateProcessorTest.php index 286ffe97..4c9f2358 100644 --- a/tests/PhpWord/TemplateProcessorTest.php +++ b/tests/PhpWord/TemplateProcessorTest.php @@ -17,6 +17,9 @@ namespace PhpOffice\PhpWord; +use PhpOffice\PhpWord\Element\Text; +use PhpOffice\PhpWord\Element\TextRun; + /** * @covers \PhpOffice\PhpWord\TemplateProcessor * @coversDefaultClass \PhpOffice\PhpWord\TemplateProcessor @@ -307,6 +310,59 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase ); } + public function testSetComplexValue() + { + $title = new TextRun(); + $title->addText('This is my title'); + + $firstname = new Text('Donald'); + $lastname = new Text('Duck'); + + $mainPart = ' + + + Hello ${document-title} + + + + + Hello ${firstname} ${lastname} + + '; + + $result = ' + + + + + This is my title + + + + + Hello + + + + Donald + + + + + + + Duck + + '; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setComplexBlock('document-title', $title); + $templateProcessor->setComplexValue('firstname', $firstname); + $templateProcessor->setComplexValue('lastname', $lastname); + + $this->assertEquals(preg_replace('/>\s+<', $result), preg_replace('/>\s+<', $templateProcessor->getMainPart())); + } + /** * @covers ::setValues * @test @@ -675,4 +731,76 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase $variables = $templateProcessor->getVariablesForPart('$15,000.00. ${variable_name}'); $this->assertEquals(array('variable_name'), $variables); } + + /** + * @covers ::textNeedsSplitting + */ + public function testTextNeedsSplitting() + { + $templateProcessor = new TestableTemplateProcesor(); + + $this->assertFalse($templateProcessor->textNeedsSplitting('${nothing-to-replace}')); + + $text = 'Hello ${firstname} ${lastname}'; + $this->assertTrue($templateProcessor->textNeedsSplitting($text)); + $splitText = $templateProcessor->splitTextIntoTexts($text); + $this->assertFalse($templateProcessor->textNeedsSplitting($splitText)); + } + + /** + * @covers ::splitTextIntoTexts + */ + public function testSplitTextIntoTexts() + { + $templateProcessor = new TestableTemplateProcesor(); + + $splitText = $templateProcessor->splitTextIntoTexts('${nothing-to-replace}'); + $this->assertEquals('${nothing-to-replace}', $splitText); + + $splitText = $templateProcessor->splitTextIntoTexts('Hello ${firstname} ${lastname}'); + $this->assertEquals('Hello ${firstname} ${lastname}', $splitText); + } + + public function testFindXmlBlockStart() + { + $toFind = ' + + + + + This whole paragraph will be replaced with my ${title} + '; + $mainPart = ' + + + + + + + ${value1} ${value2} + + + + + + + . + + + + + + + + + + ' . $toFind . ' + + '; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $position = $templateProcessor->findContainingXmlBlockForMacro('${title}', 'w:r'); + + $this->assertEquals($toFind, $templateProcessor->getSlice($position['start'], $position['end'])); + } } diff --git a/tests/PhpWord/_includes/TestableTemplateProcesor.php b/tests/PhpWord/_includes/TestableTemplateProcesor.php index 3b6f5b56..44c0bb55 100644 --- a/tests/PhpWord/_includes/TestableTemplateProcesor.php +++ b/tests/PhpWord/_includes/TestableTemplateProcesor.php @@ -35,6 +35,16 @@ class TestableTemplateProcesor extends TemplateProcessor return parent::fixBrokenMacros($documentPart); } + public function splitTextIntoTexts($text) + { + return parent::splitTextIntoTexts($text); + } + + public function textNeedsSplitting($text) + { + return parent::textNeedsSplitting($text); + } + public function getVariablesForPart($documentPartXML) { $documentPartXML = parent::fixBrokenMacros($documentPartXML); @@ -42,6 +52,24 @@ class TestableTemplateProcesor extends TemplateProcessor return parent::getVariablesForPart($documentPartXML); } + public function findXmlBlockStart($offset, $blockType) + { + return parent::findXmlBlockStart($offset, $blockType); + } + + public function findContainingXmlBlockForMacro($macro, $blockType = 'w:p') + { + return parent::findContainingXmlBlockForMacro($macro, $blockType); + } + + public function getSlice($startPosition, $endPosition = 0) + { + return parent::getSlice($startPosition, $endPosition); + } + + /** + * @return string + */ public function getMainPart() { return $this->tempDocumentMainPart; From d862b1f267ca86635593b3af3c637889248602ed Mon Sep 17 00:00:00 2001 From: troosan Date: Thu, 31 Jan 2019 01:32:00 +0100 Subject: [PATCH 13/53] update documentation --- docs/templates-processing.rst | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/templates-processing.rst b/docs/templates-processing.rst index 325de8de..5b32aa18 100644 --- a/docs/templates-processing.rst +++ b/docs/templates-processing.rst @@ -215,3 +215,32 @@ Applies the XSL stylesheet passed to header part, footer part and main part $xslDomDocument = new \DOMDocument(); $xslDomDocument->load('/path/to/my/stylesheet.xsl'); $templateProcessor->applyXslStyleSheet($xslDomDocument); + +setComplexValue +""""""""""""""" +Raplaces a ${macro} with the ComplexType passed. +See ``Sample_40_TemplateSetComplexValue.php`` for examples. + +.. code-block:: php + + $inline = new TextRun(); + $inline->addText('by a red italic text', array('italic' => true, 'color' => 'red')); + $templateProcessor->setComplexValue('inline', $inline); + +setComplexBlock +""""""""""""""" +Raplaces a ${macro} with the ComplexType passed. +See ``Sample_40_TemplateSetComplexValue.php`` for examples. + +.. code-block:: php + + $table = new Table(array('borderSize' => 12, 'borderColor' => 'green', 'width' => 6000, 'unit' => TblWidth::TWIP)); + $table->addRow(); + $table->addCell(150)->addText('Cell A1'); + $table->addCell(150)->addText('Cell A2'); + $table->addCell(150)->addText('Cell A3'); + $table->addRow(); + $table->addCell(150)->addText('Cell B1'); + $table->addCell(150)->addText('Cell B2'); + $table->addCell(150)->addText('Cell B3'); + $templateProcessor->setComplexBlock('table', $table); From bc448aed6c7df7782d9655d5df99e2789090daa6 Mon Sep 17 00:00:00 2001 From: troosan Date: Mon, 4 Feb 2019 21:53:19 +0100 Subject: [PATCH 14/53] improve code coverage --- src/PhpWord/TemplateProcessor.php | 42 ++++--------------------- tests/PhpWord/TemplateProcessorTest.php | 27 ++++++++++++++++ 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 498cf701..9e12028d 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -1108,7 +1108,7 @@ class TemplateProcessor protected function findContainingXmlBlockForMacro($macro, $blockType = 'w:p') { $macroPos = $this->findMacro($macro); - if (false === $macroPos) { + if (0 > $macroPos) { return false; } $start = $this->findXmlBlockStart($macroPos, $blockType); @@ -1116,39 +1116,8 @@ class TemplateProcessor return false; } $end = $this->findXmlBlockEnd($start, $blockType); - if (0 > $end) { - return false; - } - - return array('start' => $start, 'end' => $end); - } - - /** - * Find start and end of XML block containing the given block macro - * e.g. ...${macro}...${/macro}... - * - * Note that only the first instance of the macro will be found - * - * @param string $macro Name of macro - * @param string $blockType XML tag for block - * @return bool|int[] FALSE if not found, otherwise array with start and end - */ - protected function findContainingXmlBlockForBlockMacro($macro, $blockType = 'w:p') - { - $macroStartPos = $this->findMacro($macro); - if (0 > $macroStartPos) { - return false; - } - $macroEndPos = $this->findMacro('/' . $macro, $macroStartPos); - if (0 > $macroEndPos) { - return false; - } - $start = $this->findXmlBlockStart($macroStartPos, $blockType); - if (0 > $start) { - return false; - } - $end = $this->findXmlBlockEnd($macroEndPos, $blockType); - if (0 > $end) { + //if not found or if resulting string does not contain the macro we are searching for + if (0 > $end || strstr($this->getSlice($start, $end), $macro) === false) { return false; } @@ -1183,12 +1152,13 @@ class TemplateProcessor */ protected function findXmlBlockStart($offset, $blockType) { + $reverseOffset = (strlen($this->tempDocumentMainPart) - $offset) * -1; // first try XML tag with attributes - $blockStart = strrpos($this->tempDocumentMainPart, '<' . $blockType . ' ', ((strlen($this->tempDocumentMainPart) - $offset) * -1)); + $blockStart = strrpos($this->tempDocumentMainPart, '<' . $blockType . ' ', $reverseOffset); // if not found, or if found but contains the XML tag without attribute if (false === $blockStart || strrpos($this->getSlice($blockStart, $offset), '<' . $blockType . '>')) { // also try XML tag without attributes - $blockStart = strrpos($this->tempDocumentMainPart, '<' . $blockType . '>', ((strlen($this->tempDocumentMainPart) - $offset) * -1)); + $blockStart = strrpos($this->tempDocumentMainPart, '<' . $blockType . '>', $reverseOffset); } return ($blockStart === false) ? -1 : $blockStart; diff --git a/tests/PhpWord/TemplateProcessorTest.php b/tests/PhpWord/TemplateProcessorTest.php index 4c9f2358..043ad1ff 100644 --- a/tests/PhpWord/TemplateProcessorTest.php +++ b/tests/PhpWord/TemplateProcessorTest.php @@ -803,4 +803,31 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase $this->assertEquals($toFind, $templateProcessor->getSlice($position['start'], $position['end'])); } + + public function testShouldReturnFalseIfXmlBlockNotFound() + { + $mainPart = ' + + + + + + this is my text containing a ${macro} + + + '; + $templateProcessor = new TestableTemplateProcesor($mainPart); + + //non-existing macro + $result = $templateProcessor->findContainingXmlBlockForMacro('${fake-macro}', 'w:p'); + $this->assertFalse($result); + + //existing macro but not inside node looked for + $result = $templateProcessor->findContainingXmlBlockForMacro('${macro}', 'w:fake-node'); + $this->assertFalse($result); + + //existing macro but end tag not found after macro + $result = $templateProcessor->findContainingXmlBlockForMacro('${macro}', 'w:rPr'); + $this->assertFalse($result); + } } From d2b0b317e025d6e42bd8389258b3e6ca9d4dfed1 Mon Sep 17 00:00:00 2001 From: troosan Date: Mon, 4 Feb 2019 22:57:33 +0100 Subject: [PATCH 15/53] fix scrutinizer warnings --- src/PhpWord/TemplateProcessor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 9e12028d..0a366617 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -1088,7 +1088,7 @@ class TemplateProcessor protected function replaceXmlBlock($macro, $block, $blockType = 'w:p') { $where = $this->findContainingXmlBlockForMacro($macro, $blockType); - if (false !== $where) { + if (is_array($where)) { $this->tempDocumentMainPart = $this->getSlice(0, $where['start']) . $block . $this->getSlice($where['end']); } @@ -1132,7 +1132,7 @@ class TemplateProcessor * Note that only the first instance of the macro will be found * * @param string $search Macro name - * @param string $offset Offset from which to start searching + * @param int $offset Offset from which to start searching * @return int -1 if macro not found */ protected function findMacro($search, $offset = 0) From 58a2849e38b566ea3bf267d0f8b5bb2729be0d6b Mon Sep 17 00:00:00 2001 From: troosan Date: Mon, 4 Feb 2019 23:59:37 +0100 Subject: [PATCH 16/53] Add reading of the settings part --- src/PhpWord/TemplateProcessor.php | 35 +++++++++++++++++++ tests/PhpWord/TemplateProcessorTest.php | 14 ++++++++ .../_includes/TestableTemplateProcesor.php | 11 +++++- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 0a366617..0f685bc4 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -49,6 +49,13 @@ class TemplateProcessor */ protected $tempDocumentMainPart; + /** + * Content of settings part (in XML format) of the temporary document + * + * @var string + */ + protected $tempDocumentSettingsPart; + /** * Content of headers (in XML format) of the temporary document * @@ -120,6 +127,7 @@ class TemplateProcessor } $this->tempDocumentMainPart = $this->readPartWithRels($this->getMainPartName()); + $this->tempDocumentSettingsPart = $this->readPartWithRels($this->getSettingsPartName()); $this->tempDocumentContentTypes = $this->zipClass->getFromName($this->getDocumentContentTypesName()); } @@ -792,6 +800,22 @@ class TemplateProcessor $this->replaceBlock($blockname, ''); } + /** + * Automatically Recalculate Fields on Open + * + * @param bool $update + */ + public function setUpdateFields($update = true) + { + $string = $update ? 'true' : 'false'; + $matches = array(); + if (preg_match('//', $this->tempDocumentSettingsPart, $matches)) { + $this->tempDocumentSettingsPart = str_replace($matches[0], '', $this->tempDocumentSettingsPart); + } else { + $this->tempDocumentSettingsPart = str_replace('', '', $this->tempDocumentSettingsPart); + } + } + /** * Saves the result document. * @@ -806,6 +830,7 @@ class TemplateProcessor } $this->savePartWithRels($this->getMainPartName(), $this->tempDocumentMainPart); + $this->savePartWithRels($this->getSettingsPartName(), $this->tempDocumentSettingsPart); foreach ($this->tempDocumentFooters as $index => $xml) { $this->savePartWithRels($this->getFooterName($index), $xml); @@ -943,6 +968,16 @@ class TemplateProcessor return array_key_exists(1, $matches) ? $matches[1] : 'word/document.xml'; } + /** + * The name of the file containing the Settings part + * + * @return string + */ + protected function getSettingsPartName() + { + return 'word/settings.xml'; + } + /** * Get the name of the footer file for $index. * diff --git a/tests/PhpWord/TemplateProcessorTest.php b/tests/PhpWord/TemplateProcessorTest.php index 043ad1ff..4caca77a 100644 --- a/tests/PhpWord/TemplateProcessorTest.php +++ b/tests/PhpWord/TemplateProcessorTest.php @@ -830,4 +830,18 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase $result = $templateProcessor->findContainingXmlBlockForMacro('${macro}', 'w:rPr'); $this->assertFalse($result); } + + public function testShouldMakeFieldsUpdateOnOpen() + { + $settingsPart = ' + + '; + $templateProcessor = new TestableTemplateProcesor(null, $settingsPart); + + $templateProcessor->setUpdateFields(true); + $this->assertContains('', $templateProcessor->getSettingsPart()); + + $templateProcessor->setUpdateFields(false); + $this->assertContains('', $templateProcessor->getSettingsPart()); + } } diff --git a/tests/PhpWord/_includes/TestableTemplateProcesor.php b/tests/PhpWord/_includes/TestableTemplateProcesor.php index 44c0bb55..80cc748f 100644 --- a/tests/PhpWord/_includes/TestableTemplateProcesor.php +++ b/tests/PhpWord/_includes/TestableTemplateProcesor.php @@ -25,9 +25,10 @@ namespace PhpOffice\PhpWord; */ class TestableTemplateProcesor extends TemplateProcessor { - public function __construct($mainPart = null) + public function __construct($mainPart = null, $settingsPart = null) { $this->tempDocumentMainPart = $mainPart; + $this->tempDocumentSettingsPart = $settingsPart; } public function fixBrokenMacros($documentPart) @@ -74,4 +75,12 @@ class TestableTemplateProcesor extends TemplateProcessor { return $this->tempDocumentMainPart; } + + /** + * @return string + */ + public function getSettingsPart() + { + return $this->tempDocumentSettingsPart; + } } From 235cc1205c199a0cace54f858982ddbae7acdacd Mon Sep 17 00:00:00 2001 From: troosan Date: Tue, 5 Feb 2019 21:42:14 +0100 Subject: [PATCH 17/53] implement support for section vAlign --- CHANGELOG.md | 1 + docs/styles.rst | 2 ++ samples/Sample_03_Sections.php | 8 +++++ src/PhpWord/Reader/Word2007/Document.php | 1 + src/PhpWord/SimpleType/VerticalJc.php | 36 +++++++++++++++++++ src/PhpWord/Style/Cell.php | 15 ++++++-- src/PhpWord/Style/Section.php | 34 ++++++++++++++++++ src/PhpWord/Writer/Word2007/Style/Section.php | 4 +++ tests/PhpWord/Reader/Word2007/StyleTest.php | 13 +++++++ tests/PhpWord/Style/CellTest.php | 4 ++- tests/PhpWord/Style/SectionTest.php | 16 +++++++++ 11 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 src/PhpWord/SimpleType/VerticalJc.php diff --git a/CHANGELOG.md b/CHANGELOG.md index c7030a5b..e5ce3c15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ v0.17.0 (?? ??? 2019) ---------------------- ### Added - Add RightToLeft table presentation. @troosan #1550 +- Add support for page vertical alignment. @troosan #672 #1569 ### Fixed diff --git a/docs/styles.rst b/docs/styles.rst index 2be6eb94..27f8ee66 100644 --- a/docs/styles.rst +++ b/docs/styles.rst @@ -32,6 +32,8 @@ Available Section style options: See ``\PhpOffice\PhpWord\Style\Section::ORIENTATION_...`` class constants for possible values - ``pageSizeH``. Page height in *twip*. Implicitly defined by ``orientation`` option. Any changes are discouraged. - ``pageSizeW``. Page width in *twip*. Implicitly defined by ``orientation`` option. Any changes are discouraged. +- ``vAlign``. Vertical Page Alignment + See ``\PhpOffice\PhpWord\SimpleType\VerticalJc`` for possible values .. _font-style: diff --git a/samples/Sample_03_Sections.php b/samples/Sample_03_Sections.php index a7b5b13d..5bb9ecc2 100644 --- a/samples/Sample_03_Sections.php +++ b/samples/Sample_03_Sections.php @@ -1,4 +1,6 @@ addSection( ); $section->addText('This section uses other margins with folio papersize.'); +// The text of this section is vertically centered +$section = $phpWord->addSection( + array('vAlign' => VerticalJc::CENTER) +); +$section->addText('This section is vertically centered.'); + // New portrait section with Header & Footer $section = $phpWord->addSection( array( diff --git a/src/PhpWord/Reader/Word2007/Document.php b/src/PhpWord/Reader/Word2007/Document.php index 4e37541b..f0d1194a 100644 --- a/src/PhpWord/Reader/Word2007/Document.php +++ b/src/PhpWord/Reader/Word2007/Document.php @@ -106,6 +106,7 @@ class Document extends AbstractPart { $styleDefs = array( 'breakType' => array(self::READ_VALUE, 'w:type'), + 'vAlign' => array(self::READ_VALUE, 'w:vAlign'), 'pageSizeW' => array(self::READ_VALUE, 'w:pgSz', 'w:w'), 'pageSizeH' => array(self::READ_VALUE, 'w:pgSz', 'w:h'), 'orientation' => array(self::READ_VALUE, 'w:pgSz', 'w:orient'), diff --git a/src/PhpWord/SimpleType/VerticalJc.php b/src/PhpWord/SimpleType/VerticalJc.php new file mode 100644 index 00000000..2a37de41 --- /dev/null +++ b/src/PhpWord/SimpleType/VerticalJc.php @@ -0,0 +1,36 @@ +vAlign = $this->setEnumVal($value, $enum, $this->vAlign); + VerticalJc::validate($value); + $this->vAlign = $this->setEnumVal($value, VerticalJc::values(), $this->vAlign); return $this; } diff --git a/src/PhpWord/Style/Section.php b/src/PhpWord/Style/Section.php index 162e08e0..3989a31e 100644 --- a/src/PhpWord/Style/Section.php +++ b/src/PhpWord/Style/Section.php @@ -17,6 +17,8 @@ namespace PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\SimpleType\VerticalJc; + /** * Section settings */ @@ -166,6 +168,14 @@ class Section extends Border */ private $lineNumbering; + /** + * Vertical Text Alignment on Page + * One of \PhpOffice\PhpWord\SimpleType\VerticalJc + * + * @var string + */ + private $vAlign; + /** * Create new instance */ @@ -599,4 +609,28 @@ class Section extends Border return $this; } + + /** + * Get vertical alignment + * + * @return \PhpOffice\PhpWord\SimpleType\VerticalJc + */ + public function getVAlign() + { + return $this->vAlign; + } + + /** + * Set vertical alignment + * + * @param string $value + * @return self + */ + public function setVAlign($value = null) + { + VerticalJc::validate($value); + $this->vAlign = $value; + + return $this; + } } diff --git a/src/PhpWord/Writer/Word2007/Style/Section.php b/src/PhpWord/Writer/Word2007/Style/Section.php index af77396d..1122b6ff 100644 --- a/src/PhpWord/Writer/Word2007/Style/Section.php +++ b/src/PhpWord/Writer/Word2007/Style/Section.php @@ -48,6 +48,10 @@ class Section extends AbstractStyle $xmlWriter->writeAttribute('w:h', $style->getPageSizeH()); $xmlWriter->endElement(); // w:pgSz + // Vertical alignment + $vAlign = $style->getVAlign(); + $xmlWriter->writeElementIf(!is_null($vAlign), 'w:vAlign', 'w:val', $vAlign); + // Margins $margins = array( 'w:top' => array('getMarginTop', SectionStyle::DEFAULT_MARGIN), diff --git a/tests/PhpWord/Reader/Word2007/StyleTest.php b/tests/PhpWord/Reader/Word2007/StyleTest.php index ad48dcba..91e96c4a 100644 --- a/tests/PhpWord/Reader/Word2007/StyleTest.php +++ b/tests/PhpWord/Reader/Word2007/StyleTest.php @@ -22,6 +22,7 @@ use PhpOffice\PhpWord\SimpleType\TblWidth; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Table; use PhpOffice\PhpWord\Style\TablePosition; +use PhpOffice\PhpWord\SimpleType\VerticalJc; /** * Test class for PhpOffice\PhpWord\Reader\Word2007\Styles @@ -213,4 +214,16 @@ class StyleTest extends AbstractTestReader $this->getDocumentFromString(array('styles' => $documentXml)); $this->assertInstanceOf('PhpOffice\\PhpWord\\Style\\Font', Style::getStyle($name)); } + + public function testPageVerticalAlign() + { + $documentXml = ' + + '; + + $phpWord = $this->getDocumentFromString(array('document' => $documentXml)); + + $sectionStyle = $phpWord->getSection(0)->getStyle(); + $this->assertEquals(VerticalJc::CENTER, $sectionStyle->getVAlign()); + } } diff --git a/tests/PhpWord/Style/CellTest.php b/tests/PhpWord/Style/CellTest.php index db789fdc..3c31a457 100644 --- a/tests/PhpWord/Style/CellTest.php +++ b/tests/PhpWord/Style/CellTest.php @@ -17,6 +17,8 @@ namespace PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\SimpleType\VerticalJc; + /** * Test class for PhpOffice\PhpWord\Style\Cell * @@ -33,7 +35,7 @@ class CellTest extends \PHPUnit\Framework\TestCase $object = new Cell(); $attributes = array( - 'valign' => Cell::VALIGN_TOP, + 'valign' => VerticalJc::TOP, 'textDirection' => Cell::TEXT_DIR_BTLR, 'bgColor' => 'FFFF00', 'borderTopSize' => 120, diff --git a/tests/PhpWord/Style/SectionTest.php b/tests/PhpWord/Style/SectionTest.php index b26d1d94..59d18167 100644 --- a/tests/PhpWord/Style/SectionTest.php +++ b/tests/PhpWord/Style/SectionTest.php @@ -17,6 +17,8 @@ namespace PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\SimpleType\VerticalJc; + /** * Test class for PhpOffice\PhpWord\Style\Section * @@ -328,4 +330,18 @@ class SectionTest extends \PHPUnit\Framework\TestCase $oSettings->setBreakType(); $this->assertNull($oSettings->getBreakType()); } + + /** + * Vertical page alignment + */ + public function testVerticalAlign() + { + // Section Settings + $oSettings = new Section(); + + $this->assertNull($oSettings->getVAlign()); + + $oSettings->setVAlign(VerticalJc::BOTH); + $this->assertEquals('both', $oSettings->getVAlign()); + } } From e3020c0db3cafc74371d088ee26e91bae5ce3c45 Mon Sep 17 00:00:00 2001 From: troosan Date: Tue, 5 Feb 2019 23:05:18 +0100 Subject: [PATCH 18/53] fix warnings --- src/PhpWord/Style/Section.php | 2 +- tests/PhpWord/Reader/Word2007/StyleTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpWord/Style/Section.php b/src/PhpWord/Style/Section.php index 3989a31e..ff9b0be0 100644 --- a/src/PhpWord/Style/Section.php +++ b/src/PhpWord/Style/Section.php @@ -613,7 +613,7 @@ class Section extends Border /** * Get vertical alignment * - * @return \PhpOffice\PhpWord\SimpleType\VerticalJc + * @return string */ public function getVAlign() { diff --git a/tests/PhpWord/Reader/Word2007/StyleTest.php b/tests/PhpWord/Reader/Word2007/StyleTest.php index 91e96c4a..a7308f1a 100644 --- a/tests/PhpWord/Reader/Word2007/StyleTest.php +++ b/tests/PhpWord/Reader/Word2007/StyleTest.php @@ -19,10 +19,10 @@ namespace PhpOffice\PhpWord\Reader\Word2007; use PhpOffice\PhpWord\AbstractTestReader; use PhpOffice\PhpWord\SimpleType\TblWidth; +use PhpOffice\PhpWord\SimpleType\VerticalJc; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Table; use PhpOffice\PhpWord\Style\TablePosition; -use PhpOffice\PhpWord\SimpleType\VerticalJc; /** * Test class for PhpOffice\PhpWord\Reader\Word2007\Styles From 5206c7f6905e965b76ceb18d72b51e525223a51e Mon Sep 17 00:00:00 2001 From: troosan Date: Wed, 6 Feb 2019 18:19:01 +0100 Subject: [PATCH 19/53] fix parsing of border-color and add test --- CHANGELOG.md | 2 ++ samples/Sample_26_Html.php | 2 +- src/PhpWord/Shared/Html.php | 16 +++++++++++++++- tests/PhpWord/Shared/HtmlTest.php | 7 ++++++- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5ce3c15..9ec1deef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,11 @@ v0.17.0 (?? ??? 2019) ---------------------- ### Added - Add RightToLeft table presentation. @troosan #1550 +- Set complex type in template @troosan #1565 - Add support for page vertical alignment. @troosan #672 #1569 ### Fixed +- Fix HTML border-color parsing. @troosan #1551 #1570 ### Miscelaneous - Use embedded http server to test loading of remote images @troosan # diff --git a/samples/Sample_26_Html.php b/samples/Sample_26_Html.php index 82a5cf6e..6bd926fe 100644 --- a/samples/Sample_26_Html.php +++ b/samples/Sample_26_Html.php @@ -74,7 +74,7 @@ $html .= ' - +
12
12
This is bold text6
'; diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 7f4bf825..66ddc9f5 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -581,7 +581,7 @@ class Html $styles['spaceAfter'] = Converter::cssToPoint($cValue); break; case 'border-color': - $styles['color'] = trim($cValue, '#'); + self::mapBorderColor($styles, $cValue); break; case 'border-width': $styles['borderSize'] = Converter::cssToPoint($cValue); @@ -738,6 +738,20 @@ class Html } } + private static function mapBorderColor(&$styles, $cssBorderColor) + { + $numColors = substr_count($cssBorderColor, '#'); + if ($numColors === 1) { + $styles['borderColor'] = trim($cssBorderColor, '#'); + } elseif ($numColors > 1) { + $colors = explode(' ', $cssBorderColor); + $borders = array('borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor'); + for ($i = 0; $i < min(4, $numColors, count($colors)); $i++) { + $styles[$borders[$i]] = $colors[$i]; + } + } + } + /** * Transforms a HTML/CSS alignment into a \PhpOffice\PhpWord\SimpleType\Jc * diff --git a/tests/PhpWord/Shared/HtmlTest.php b/tests/PhpWord/Shared/HtmlTest.php index 2f9a4be4..43472324 100644 --- a/tests/PhpWord/Shared/HtmlTest.php +++ b/tests/PhpWord/Shared/HtmlTest.php @@ -298,7 +298,7 @@ class HtmlTest extends AbstractWebServerEmbeddedTest header a header b - header c + header c @@ -313,6 +313,11 @@ class HtmlTest extends AbstractWebServerEmbeddedTest $this->assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr/w:tc')); $this->assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tblPr/w:jc')); $this->assertEquals(Jc::START, $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tblPr/w:jc', 'w:val')); + //check border colors + $this->assertEquals('#00AA00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:top', 'w:color')); + $this->assertEquals('#00BB00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:right', 'w:color')); + $this->assertEquals('#00CC00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:bottom', 'w:color')); + $this->assertEquals('#00DD00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:left', 'w:color')); } /** From 3219950d5988fbc50c628a3930fff4bca294ad06 Mon Sep 17 00:00:00 2001 From: troosan Date: Wed, 6 Feb 2019 22:07:42 +0100 Subject: [PATCH 20/53] trim color codes and add tests --- src/PhpWord/Shared/Html.php | 4 +-- tests/PhpWord/Shared/HtmlTest.php | 47 +++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 66ddc9f5..89881822 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -516,7 +516,7 @@ class Html $styles['alignment'] = self::mapAlign($cValue); break; case 'display': - $styles['hidden'] = $cValue === 'none'; + $styles['hidden'] = $cValue === 'none' || $cValue === 'hidden'; break; case 'direction': $styles['rtl'] = $cValue === 'rtl'; @@ -747,7 +747,7 @@ class Html $colors = explode(' ', $cssBorderColor); $borders = array('borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor'); for ($i = 0; $i < min(4, $numColors, count($colors)); $i++) { - $styles[$borders[$i]] = $colors[$i]; + $styles[$borders[$i]] = trim($colors[$i], '#'); } } } diff --git a/tests/PhpWord/Shared/HtmlTest.php b/tests/PhpWord/Shared/HtmlTest.php index 43472324..5bc9e241 100644 --- a/tests/PhpWord/Shared/HtmlTest.php +++ b/tests/PhpWord/Shared/HtmlTest.php @@ -297,7 +297,7 @@ class HtmlTest extends AbstractWebServerEmbeddedTest header a - header b + header b header c @@ -313,11 +313,17 @@ class HtmlTest extends AbstractWebServerEmbeddedTest $this->assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr/w:tc')); $this->assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tblPr/w:jc')); $this->assertEquals(Jc::START, $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tblPr/w:jc', 'w:val')); + //check border colors - $this->assertEquals('#00AA00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:top', 'w:color')); - $this->assertEquals('#00BB00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:right', 'w:color')); - $this->assertEquals('#00CC00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:bottom', 'w:color')); - $this->assertEquals('#00DD00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:left', 'w:color')); + $this->assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:top', 'w:color')); + $this->assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:right', 'w:color')); + $this->assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:bottom', 'w:color')); + $this->assertEquals('00EE00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]/w:tcPr/w:tcBorders/w:left', 'w:color')); + + $this->assertEquals('00AA00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:top', 'w:color')); + $this->assertEquals('00BB00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:right', 'w:color')); + $this->assertEquals('00CC00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:bottom', 'w:color')); + $this->assertEquals('00DD00', $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]/w:tcPr/w:tcBorders/w:left', 'w:color')); } /** @@ -595,4 +601,35 @@ class HtmlTest extends AbstractWebServerEmbeddedTest $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); $this->assertFalse($doc->elementExists('/w:document/w:body/w:p[1]/w:pPr/w:jc')); } + + /** + * Tests parsing hidden text + */ + public function testParseHiddenText() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $html = '

This is some hidden text.

'; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:vanish')); + } + + /** + * Tests parsing letter spacing + */ + public function testParseLetterSpacing() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $html = '

This is some text with letter spacing.

'; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:spacing')); + $this->assertEquals(150 * 15, $doc->getElement('/w:document/w:body/w:p/w:r/w:rPr/w:spacing')->getAttribute('w:val')); + } } From b3982ebb70d694080525adc860891094dddf14ac Mon Sep 17 00:00:00 2001 From: troosan Date: Fri, 22 Feb 2019 22:06:30 +0100 Subject: [PATCH 21/53] fix documentation --- src/PhpWord/Element/AbstractContainer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Element/AbstractContainer.php b/src/PhpWord/Element/AbstractContainer.php index 204d4a73..5e058667 100644 --- a/src/PhpWord/Element/AbstractContainer.php +++ b/src/PhpWord/Element/AbstractContainer.php @@ -31,7 +31,7 @@ namespace PhpOffice\PhpWord\Element; * @method Footnote addFootnote(mixed $pStyle = null) * @method Endnote addEndnote(mixed $pStyle = null) * @method CheckBox addCheckBox(string $name, $text, mixed $fStyle = null, mixed $pStyle = null) - * @method Title addTitle(string $text, int $depth = 1) + * @method Title addTitle(mixed $text, int $depth = 1) * @method TOC addTOC(mixed $fontStyle = null, mixed $tocStyle = null, int $minDepth = 1, int $maxDepth = 9) * @method PageBreak addPageBreak() * @method Table addTable(mixed $style = null) From 9958a4825fbe623ebf0dc16e831dc9fe9d7e8ff9 Mon Sep 17 00:00:00 2001 From: troosan Date: Fri, 22 Feb 2019 22:06:54 +0100 Subject: [PATCH 22/53] allow other streams --- src/PhpWord/Writer/AbstractWriter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Writer/AbstractWriter.php b/src/PhpWord/Writer/AbstractWriter.php index 7e0d511a..2c1ad294 100644 --- a/src/PhpWord/Writer/AbstractWriter.php +++ b/src/PhpWord/Writer/AbstractWriter.php @@ -220,7 +220,7 @@ abstract class AbstractWriter implements WriterInterface // Temporary file $this->originalFilename = $filename; - if (strtolower($filename) == 'php://output' || strtolower($filename) == 'php://stdout') { + if (strpos(strtolower($filename), 'php://') === 0) { $filename = tempnam(Settings::getTempDir(), 'PhpWord'); if (false === $filename) { $filename = $this->originalFilename; // @codeCoverageIgnore From 81a1b2acffd55a47d94b372963404c425adc1eee Mon Sep 17 00:00:00 2001 From: Nick Winfield Date: Sat, 23 Feb 2019 23:24:49 +0000 Subject: [PATCH 23/53] TrackChange doesn't handle all return types of \DateTime::createFromFormat(...) (#1584) * Added boolean check before setting the date --- src/PhpWord/Element/TrackChange.php | 4 ++-- tests/PhpWord/Element/TrackChangeTest.php | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/PhpWord/Element/TrackChange.php b/src/PhpWord/Element/TrackChange.php index 410ffb7c..91c221f2 100644 --- a/src/PhpWord/Element/TrackChange.php +++ b/src/PhpWord/Element/TrackChange.php @@ -58,13 +58,13 @@ class TrackChange extends AbstractContainer * * @param string $changeType * @param string $author - * @param null|int|\DateTime $date + * @param null|int|bool|\DateTime $date */ public function __construct($changeType = null, $author = null, $date = null) { $this->changeType = $changeType; $this->author = $author; - if ($date !== null) { + if ($date !== null && $date !== false) { $this->date = ($date instanceof \DateTime) ? $date : new \DateTime('@' . $date); } } diff --git a/tests/PhpWord/Element/TrackChangeTest.php b/tests/PhpWord/Element/TrackChangeTest.php index df86feb2..b6cea924 100644 --- a/tests/PhpWord/Element/TrackChangeTest.php +++ b/tests/PhpWord/Element/TrackChangeTest.php @@ -41,4 +41,22 @@ class TrackChangeTest extends \PHPUnit\Framework\TestCase $this->assertEquals($date, $oTrackChange->getDate()); $this->assertEquals(TrackChange::INSERTED, $oTrackChange->getChangeType()); } + + /** + * New instance with invalid \DateTime (produced by \DateTime::createFromFormat(...)) + */ + public function testConstructDefaultWithInvalidDate() + { + $author = 'Test User'; + $date = false; + $oTrackChange = new TrackChange(TrackChange::INSERTED, $author, $date); + + $oText = new Text('dummy text'); + $oText->setTrackChange($oTrackChange); + + $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\TrackChange', $oTrackChange); + $this->assertEquals($author, $oTrackChange->getAuthor()); + $this->assertEquals($date, null); + $this->assertEquals(TrackChange::INSERTED, $oTrackChange->getChangeType()); + } } From 81af913a66d7c508ad2337b2763cbd0c05e106de Mon Sep 17 00:00:00 2001 From: troosan Date: Sun, 24 Feb 2019 21:23:59 +0100 Subject: [PATCH 24/53] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e62f2e6f..43ee0636 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ We want to create a high quality document writer and reader library that people - **Be brief, but be bold**. State your issues briefly. But speak out your ideas loudly, even if you can't or don't know how to implement it right away. The world will be better with limitless innovations. - **Follow PHP-FIG standards**. We follow PHP Standards Recommendations (PSRs) by [PHP Framework Interoperability Group](http://www.php-fig.org/). If you're not familiar with these standards, please, [familiarize yourself now](https://github.com/php-fig/fig-standards). Also, please, use [PHPCodeSniffer](http://pear.php.net/package/PHP_CodeSniffer/) to validate your code against PSRs. -- **Test your code**. Nobody else knows your code better than you. So, it's completely your mission to test the changes you made before pull request submission. We use [PHPUnit](https://phpunit.de/) for our testing purposes and recommend you using this tool too. [Here](https://phpunit.de/presentations.html) you can find PHPUnit best practices and additional information on effective unit testing, which helps us making PHPWord better day to day. Do not hesitate to smoke it carefully. It's a great investment in quality of your work, and it saves you years of life. +- **Test your code**. Nobody else knows your code better than you. So, it's completely your mission to test the changes you made before pull request submission. We use [PHPUnit](https://phpunit.de/) for our testing purposes and recommend you using this tool too. [Here](https://phpunit.readthedocs.io) you can find documentation on how to write tests with PHPUnit, which helps us making PHPWord better day to day. Do not hesitate to smoke it carefully. It's a great investment in quality of your work, and it saves you years of life. - **Request pull in separate branch**. Do not submit your request to the master branch. But create a separate branch named specifically for the issue that you addressed. Read [GitHub manual](https://help.github.com/articles/using-pull-requests) to find out more about this. If you are new to GitHub, read [this short manual](https://help.github.com/articles/fork-a-repo) to get yourself familiar with forks and how git works in general. [This video](http://www.youtube.com/watch?v=-zvHQXnBO6c) explains how to synchronize your Github Fork with the Branch of PHPWord. That's it. Thank you for your interest in PHPWord, and welcome! From 57ae7008b2fcd5941f2ddf487a13e9e2e616231d Mon Sep 17 00:00:00 2001 From: Maxim Bulygin Date: Fri, 1 Mar 2019 16:31:47 +0200 Subject: [PATCH 25/53] add test to auto invert text color --- tests/PhpWord/Writer/HTML/ElementTest.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/PhpWord/Writer/HTML/ElementTest.php b/tests/PhpWord/Writer/HTML/ElementTest.php index 90044b92..6ef02754 100644 --- a/tests/PhpWord/Writer/HTML/ElementTest.php +++ b/tests/PhpWord/Writer/HTML/ElementTest.php @@ -85,10 +85,10 @@ class ElementTest extends \PHPUnit\Framework\TestCase $section = $phpWord->addSection(); $table = $section->addTable(); $row1 = $table->addRow(); - $cell11 = $row1->addCell(1000, array('gridSpan' => 2)); + $cell11 = $row1->addCell(1000, array('gridSpan' => 2, 'bgColor' => '6086B8')); $cell11->addText('cell spanning 2 bellow'); $row2 = $table->addRow(); - $cell21 = $row2->addCell(500); + $cell21 = $row2->addCell(500, array('bgColor' => 'ffffff')); $cell21->addText('first cell'); $cell22 = $row2->addCell(500); $cell22->addText('second cell'); @@ -99,6 +99,11 @@ class ElementTest extends \PHPUnit\Framework\TestCase $this->assertTrue($xpath->query('/html/body/table/tr[1]/td')->length == 1); $this->assertEquals('2', $xpath->query('/html/body/table/tr/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); $this->assertTrue($xpath->query('/html/body/table/tr[2]/td')->length == 2); + + $this->assertEquals('#6086B8', $xpath->query('/html/body/table/tr[1]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent); + $this->assertEquals('#ffffff', $xpath->query('/html/body/table/tr[1]/td')->item(0)->attributes->getNamedItem('color')->textContent); + $this->assertEquals('#ffffff', $xpath->query('/html/body/table/tr[2]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent); + $this->assertNull($xpath->query('/html/body/table/tr[2]/td')->item(0)->attributes->getNamedItem('color')); } /** From 014ff7d261ab1f5f1ca4cec396e24cec8d672bde Mon Sep 17 00:00:00 2001 From: Mykola Nicholas Date: Wed, 13 Mar 2019 16:39:32 +0300 Subject: [PATCH 26/53] Added new constant to russian language --- src/PhpWord/Style/Language.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PhpWord/Style/Language.php b/src/PhpWord/Style/Language.php index dd3ed819..98593394 100644 --- a/src/PhpWord/Style/Language.php +++ b/src/PhpWord/Style/Language.php @@ -70,6 +70,9 @@ final class Language extends AbstractStyle const UK_UA = 'uk-UA'; const UK_UA_ID = 1058; + + const RU_RU = 'ru-RU'; + const RU_RU_ID = 1049; /** * Language ID, used for RTF document generation From 607378b8fb694eca29a2560e653abad79ee5a76a Mon Sep 17 00:00:00 2001 From: Seamus Lee Date: Sun, 24 Feb 2019 09:06:51 +1100 Subject: [PATCH 27/53] Ensure that entity_loader disable variable is re-set back to the original setting Simplify the setting of libxml_disable_entity_loader --- src/PhpWord/Shared/Html.php | 3 ++- src/PhpWord/TemplateProcessor.php | 3 ++- tests/PhpWord/_includes/XmlDocument.php | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 89881822..f2710ea1 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -72,7 +72,7 @@ class Html } // Load DOM - libxml_disable_entity_loader(true); + $orignalLibEntityLoader = libxml_disable_entity_loader(true); $dom = new \DOMDocument(); $dom->preserveWhiteSpace = $preserveWhiteSpace; $dom->loadXML($html); @@ -80,6 +80,7 @@ class Html $node = $dom->getElementsByTagName('body'); self::parseNode($node->item(0), $element); + libxml_disable_entity_loader($orignalLibEntityLoader); } /** diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 0f685bc4..7efc0f1a 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -170,7 +170,7 @@ class TemplateProcessor */ protected function transformSingleXml($xml, $xsltProcessor) { - libxml_disable_entity_loader(true); + $orignalLibEntityLoader = libxml_disable_entity_loader(true); $domDocument = new \DOMDocument(); if (false === $domDocument->loadXML($xml)) { throw new Exception('Could not load the given XML document.'); @@ -180,6 +180,7 @@ class TemplateProcessor if (false === $transformedXml) { throw new Exception('Could not transform the given XML document.'); } + libxml_disable_entity_loader($orignalLibEntityLoader); return $transformedXml; } diff --git a/tests/PhpWord/_includes/XmlDocument.php b/tests/PhpWord/_includes/XmlDocument.php index f51eaad8..3a7869bc 100644 --- a/tests/PhpWord/_includes/XmlDocument.php +++ b/tests/PhpWord/_includes/XmlDocument.php @@ -76,10 +76,10 @@ class XmlDocument $this->file = $file; $file = $this->path . '/' . $file; - libxml_disable_entity_loader(false); + $orignalLibEntityLoader = libxml_disable_entity_loader(false); $this->dom = new \DOMDocument(); $this->dom->load($file); - libxml_disable_entity_loader(true); + libxml_disable_entity_loader($orignalLibEntityLoader); return $this->dom; } From 8cea3221dcca66a16420eee6966a8f6fd237f70c Mon Sep 17 00:00:00 2001 From: troosan Date: Sun, 31 Mar 2019 13:20:51 +0200 Subject: [PATCH 28/53] remove trailing spaces --- src/PhpWord/Style/Language.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Style/Language.php b/src/PhpWord/Style/Language.php index 98593394..18ef8897 100644 --- a/src/PhpWord/Style/Language.php +++ b/src/PhpWord/Style/Language.php @@ -70,7 +70,7 @@ final class Language extends AbstractStyle const UK_UA = 'uk-UA'; const UK_UA_ID = 1058; - + const RU_RU = 'ru-RU'; const RU_RU_ID = 1049; From 2045e52db71c7e954c334dc28947a8756654e29f Mon Sep 17 00:00:00 2001 From: arthur Date: Tue, 9 Apr 2019 10:55:43 +0200 Subject: [PATCH 29/53] call static instead of self on protected method --- 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 f2710ea1..c484e08f 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -192,7 +192,7 @@ class Html $newElement = $element; } - self::parseChildNodes($node, $newElement, $styles, $data); + static::parseChildNodes($node, $newElement, $styles, $data); } /** From c00c77c4c1e3f1c1fc9e2d4bc2ecd7fdb8fa4083 Mon Sep 17 00:00:00 2001 From: Nicolas Dermine Date: Wed, 17 Apr 2019 18:27:33 +0200 Subject: [PATCH 30/53] fix typo in changelog --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ec1deef..66271bae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ v0.17.0 (?? ??? 2019) ### Fixed - Fix HTML border-color parsing. @troosan #1551 #1570 -### Miscelaneous +### Miscellaneous - Use embedded http server to test loading of remote images @troosan # v0.16.0 (30 dec 2018) @@ -40,7 +40,7 @@ v0.16.0 (30 dec 2018) - For RTF writers, sizes should should never have decimals @Samuel-BF #1536 - Style Name Parsing fails if document generated by a non-english word version @begnini #1434 -### Miscelaneous +### Miscellaneous - Get rid of duplicated code in TemplateProcessor @abcdmitry #1161 v0.15.0 (14 Jul 2018) @@ -86,7 +86,7 @@ v0.15.0 (14 Jul 2018) - Remove zend-stdlib dependency @Trainmaster #1284 - The default unit for `\PhpOffice\PhpWord\Style\Image` changed from `px` to `pt`. -### Miscelaneous +### Miscellaneous - Drop GitHub pages, switch to coveralls for code coverage analysis @czosel #1360 v0.14.0 (29 Dec 2017) From 18b3c754ef32f5a2c0fc21666ffe481da370cf97 Mon Sep 17 00:00:00 2001 From: Walter Tamboer Date: Thu, 9 May 2019 15:29:25 +0200 Subject: [PATCH 31/53] No nested w:pPr elements in ListItemRun. This commit fixes issue #1529 This commit prevents nested w:pPr elements when using a ListItemRun with a paragraph style. The different between a ListItem and a ListItem run is that the setWithoutPPR method is called on the ParagraphStyleWriter (PhpOffice\PhpWord\Writer\Word2007\Style\Paragraph). According to the specs it's not allowed to have nested w:pPr elements. See http://www.datypic.com/sc/ooxml/e-w_pPr-2.html --- .../Writer/Word2007/Element/ListItemRun.php | 59 +++++++++++++------ 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/src/PhpWord/Writer/Word2007/Element/ListItemRun.php b/src/PhpWord/Writer/Word2007/Element/ListItemRun.php index 765d2ee0..fda2b078 100644 --- a/src/PhpWord/Writer/Word2007/Element/ListItemRun.php +++ b/src/PhpWord/Writer/Word2007/Element/ListItemRun.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWord\Writer\Word2007\Element; +use PhpOffice\PhpWord\Element\ListItemRun as ListItemRunElement; use PhpOffice\PhpWord\Writer\Word2007\Style\Paragraph as ParagraphStyleWriter; /** @@ -31,34 +32,56 @@ class ListItemRun extends AbstractElement */ public function write() { - $xmlWriter = $this->getXmlWriter(); $element = $this->getElement(); - if (!$element instanceof \PhpOffice\PhpWord\Element\ListItemRun) { + + if (!$element instanceof ListItemRunElement) { return; } + $this->writeParagraph($element); + } + + private function writeParagraph(ListItemRunElement $element) + { + $xmlWriter = $this->getXmlWriter(); $xmlWriter->startElement('w:p'); - $xmlWriter->startElement('w:pPr'); - $paragraphStyle = $element->getParagraphStyle(); - $styleWriter = new ParagraphStyleWriter($xmlWriter, $paragraphStyle); - $styleWriter->setIsInline(true); - $styleWriter->write(); - - $xmlWriter->startElement('w:numPr'); - $xmlWriter->startElement('w:ilvl'); - $xmlWriter->writeAttribute('w:val', $element->getDepth()); - $xmlWriter->endElement(); // w:ilvl - $xmlWriter->startElement('w:numId'); - $xmlWriter->writeAttribute('w:val', $element->getStyle()->getNumId()); - $xmlWriter->endElement(); // w:numId - $xmlWriter->endElement(); // w:numPr - - $xmlWriter->endElement(); // w:pPr + $this->writeParagraphProperties($element); $containerWriter = new Container($xmlWriter, $element); $containerWriter->write(); $xmlWriter->endElement(); // w:p } + + private function writeParagraphProperties(ListItemRunElement $element) + { + $xmlWriter = $this->getXmlWriter(); + $xmlWriter->startElement('w:pPr'); + + $styleWriter = new ParagraphStyleWriter($xmlWriter, $element->getParagraphStyle()); + $styleWriter->setIsInline(true); + $styleWriter->setWithoutPPR(true); + $styleWriter->write(); + + $this->writeParagraphPropertiesNumbering($element); + + $xmlWriter->endElement(); // w:pPr + } + + private function writeParagraphPropertiesNumbering(ListItemRunElement $element) + { + $xmlWriter = $this->getXmlWriter(); + $xmlWriter->startElement('w:numPr'); + + $xmlWriter->writeElementBlock('w:ilvl', array( + 'w:val' => $element->getDepth(), + )); + + $xmlWriter->writeElementBlock('w:numId', array( + 'w:val' => $element->getStyle()->getNumId(), + )); + + $xmlWriter->endElement(); // w:numPr + } } From b209fec72bd277c0452d4f5891c31f320612dc2a Mon Sep 17 00:00:00 2001 From: Nishant Bhatt Date: Mon, 3 Jun 2019 15:44:10 +0200 Subject: [PATCH 32/53] To suport preseve text inside sub container if we use preseve text inside table, issue fix https://stackoverflow.com/questions/33070424/phpword-cannot-add-preservetext-in-section --- src/PhpWord/Element/AbstractContainer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Element/AbstractContainer.php b/src/PhpWord/Element/AbstractContainer.php index 204d4a73..74aabb19 100644 --- a/src/PhpWord/Element/AbstractContainer.php +++ b/src/PhpWord/Element/AbstractContainer.php @@ -254,7 +254,7 @@ abstract class AbstractContainer extends AbstractElement // Special condition, e.g. preservetext can only exists in cell when // the cell is located in header or footer $validSubcontainers = array( - 'PreserveText' => array(array('Cell'), array('Header', 'Footer')), + 'PreserveText' => array(array('Cell'), array('Header', 'Footer', 'Section')), 'Footnote' => array(array('Cell', 'TextRun'), array('Section')), 'Endnote' => array(array('Cell', 'TextRun'), array('Section')), ); From 3c6a1a1568602b90589c108ac748d98f6f022a23 Mon Sep 17 00:00:00 2001 From: troosan Date: Thu, 13 Jun 2019 23:03:09 +0200 Subject: [PATCH 33/53] add unit test --- tests/PhpWord/Writer/Word2007/ElementTest.php | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/PhpWord/Writer/Word2007/ElementTest.php b/tests/PhpWord/Writer/Word2007/ElementTest.php index 703f4590..6a295965 100644 --- a/tests/PhpWord/Writer/Word2007/ElementTest.php +++ b/tests/PhpWord/Writer/Word2007/ElementTest.php @@ -510,4 +510,25 @@ class ElementTest extends \PHPUnit\Framework\TestCase $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t')); $this->assertEquals('this text contains an & (ampersant)', $doc->getElement('/w:document/w:body/w:p/w:r/w:t')->nodeValue); } + + /** + * Test ListItemRun paragraph style writing + */ + public function testListItemRunStyleWriting() + { + $phpWord = new PhpWord(); + $phpWord->addParagraphStyle('MyParagraphStyle', array('spaceBefore' => 400)); + + $section = $phpWord->addSection(); + $listItemRun = $section->addListItemRun(0, null, 'MyParagraphStyle'); + $listItemRun->addText('List item'); + $listItemRun->addText(' in bold', array('bold' => true)); + + $doc = TestHelperDOCX::getDocument($phpWord); + $this->assertFalse($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:pPr')); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:pStyle')); + $this->assertEquals('List item', $doc->getElement('/w:document/w:body/w:p/w:r[1]/w:t')->nodeValue); + $this->assertEquals(' in bold', $doc->getElement('/w:document/w:body/w:p/w:r[2]/w:t')->nodeValue); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r[2]/w:rPr/w:b')); + } } From d7ed18c39db077aeb212598c1216a0068867296e Mon Sep 17 00:00:00 2001 From: troosan Date: Fri, 14 Jun 2019 00:19:44 +0200 Subject: [PATCH 34/53] fix test --- tests/PhpWord/Element/CellTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PhpWord/Element/CellTest.php b/tests/PhpWord/Element/CellTest.php index d4aaa488..db0bae5c 100644 --- a/tests/PhpWord/Element/CellTest.php +++ b/tests/PhpWord/Element/CellTest.php @@ -231,7 +231,7 @@ class CellTest extends \PHPUnit\Framework\TestCase public function testAddPreserveTextException() { $oCell = new Cell(); - $oCell->setDocPart('Section', 1); + $oCell->setDocPart('TextRun', 1); $oCell->addPreserveText('text'); } From d6b0977afe8922bb720cdcee5d3b91f97a1e537e Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Mon, 1 Jul 2019 13:09:36 -0400 Subject: [PATCH 35/53] Add issue templates for bug reports, feature requests, and usage questions --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 22 +++++++++++++ .github/ISSUE_TEMPLATE/how-to-use.md | 14 +++++++++ 3 files changed, 74 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/how-to-use.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..fcb3a65d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help improve PHPWord +labels: Bug Report + +--- + +### Describe the Bug + +A clear and concise description of what the bug is. + +### Steps to Reproduce + +Please provide a code sample that reproduces the issue. + +```php +addSection(); +$section->... +``` + +### Expected Behavior + +A clear and concise description of what you expected to happen. + +### Current Behavior + +What is the current behavior? + +### Context + +Please fill in your environment information: + +- PHP Version: +- PHPWord Version: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..171e8378 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest an idea for this project +labels: Change Request + +--- + +### Is your feature request related to a problem? Please describe. + +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +### Describe the solution you'd like + +A clear and concise description of what you want to happen. + +### Describe alternatives you've considered + +A clear and concise description of any alternative solutions or features you've considered. + +### Additional context + +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/how-to-use.md b/.github/ISSUE_TEMPLATE/how-to-use.md new file mode 100644 index 00000000..0fef996b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/how-to-use.md @@ -0,0 +1,14 @@ +--- +name: How to Use PHPWord +about: Find out how to use PHPWord +labels: WontFix + +--- + +***Please do not use the issue tracker to ask how to use PHPWord.*** + +Documentation is available on [Read the Docs](https://phpword.readthedocs.io/en/latest/). + +Sample code is in the [`/samples/` directory](https://github.com/PHPOffice/PHPWord/tree/develop/samples). + +Usage questions belong on [Stack Overflow](https://stackoverflow.com/questions/tagged/phpword). From e401adeb7e2f8e6efbbd6bd5b50807ee6863d5b8 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Mon, 1 Jul 2019 13:10:55 -0400 Subject: [PATCH 36/53] Update CONTRIBUTING to match reality and account for new issue templates --- CONTRIBUTING.md | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 43ee0636..dbef13a6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,13 +1,30 @@ # Contributing to PHPWord -PHPWord is built by the crowd and for the crowd. Every contribution is welcome; either by [submitting](https://github.com/PHPOffice/PHPWord/issues) bug issues or suggesting improvements, or in a more active form like [requesting](https://github.com/PHPOffice/PHPWord/pulls) a pull. +PHPWord is built by the crowd and for the crowd. Every contribution is welcome; either by [reporting a bug](https://github.com/PHPOffice/PHPWord/issues/new?labels=Bug+Report&template=bug_report.md) or [suggesting improvements](https://github.com/PHPOffice/PHPWord/issues/new?labels=Change+Request&template=feature_request.md), or in a more active form like [requesting a pull](https://github.com/PHPOffice/PHPWord/pulls). -We want to create a high quality document writer and reader library that people can use with more confidence and less bugs. We want to collaborate happily, code joyfully, and get alive merrily. Thus, below are some guidelines, that we expect to be followed by each contributor. +We want to create a high quality document writer and reader library that people can use with more confidence and fewer bugs. We want to collaborate happily, code joyfully, and live merrily. Thus, below are some guidelines that we expect to be followed by each contributor: -- **Be brief, but be bold**. State your issues briefly. But speak out your ideas loudly, even if you can't or don't know how to implement it right away. The world will be better with limitless innovations. -- **Follow PHP-FIG standards**. We follow PHP Standards Recommendations (PSRs) by [PHP Framework Interoperability Group](http://www.php-fig.org/). If you're not familiar with these standards, please, [familiarize yourself now](https://github.com/php-fig/fig-standards). Also, please, use [PHPCodeSniffer](http://pear.php.net/package/PHP_CodeSniffer/) to validate your code against PSRs. -- **Test your code**. Nobody else knows your code better than you. So, it's completely your mission to test the changes you made before pull request submission. We use [PHPUnit](https://phpunit.de/) for our testing purposes and recommend you using this tool too. [Here](https://phpunit.readthedocs.io) you can find documentation on how to write tests with PHPUnit, which helps us making PHPWord better day to day. Do not hesitate to smoke it carefully. It's a great investment in quality of your work, and it saves you years of life. -- **Request pull in separate branch**. Do not submit your request to the master branch. But create a separate branch named specifically for the issue that you addressed. Read [GitHub manual](https://help.github.com/articles/using-pull-requests) to find out more about this. If you are new to GitHub, read [this short manual](https://help.github.com/articles/fork-a-repo) to get yourself familiar with forks and how git works in general. [This video](http://www.youtube.com/watch?v=-zvHQXnBO6c) explains how to synchronize your Github Fork with the Branch of PHPWord. +- **Be brief, but be bold**. State your issues briefly. But speak out your ideas loudly, even if you can't or don't know how to implement them right away. The world will be better with limitless innovations. +- **Follow PHP-FIG standards**. We follow PHP Standards Recommendations (PSRs) by [PHP Framework Interoperability Group](http://www.php-fig.org/). If you're not familiar with these standards, [familiarize yourself now](https://github.com/php-fig/fig-standards). Also, please run `composer fix` to automatically fix your code to match these recommendations. +- **Test your code**. No one knows your code better than you, so we depend on you to test the changes you make before pull request submission. We use [PHPUnit](https://phpunit.de/) for our testing purposes and request that you use this tool too. Tests can be ran with `composer test`. [Documentation for writing tests with PHPUnit is available on Read the Docs.](https://phpunit.readthedocs.io) +- **Use best practices when submitting pull requests**. Create a separate branch named specifically for the issue that you are addressing. Read the [GitHub manual](https://help.github.com/articles/about-pull-requests) to learn more about pull requests and GitHub. If you are new to GitHub, read [this short manual](https://help.github.com/articles/fork-a-repo) to get yourself familiar with forks and how git works in general. [This video](http://www.youtube.com/watch?v=-zvHQXnBO6c) explains how to synchronize your fork on GitHub with the upstream branch from PHPWord. + +## Getting Started + +1. [Clone](https://help.github.com/en/articles/cloning-a-repository) [PHPWord](https://github.com/PHPOffice/PHPWord/) +2. [Install Composer](https://getcomposer.org/download/) if you don't already have it +3. Open your terminal and: + 1. Switch to the directory PHPWord was cloned to (e.g., `cd ~/Projects/PHPWord/`) + 2. Run `composer install` to install the dependencies + +You're ready to start working on PHPWord! Tests belong in the `/tests/PhpWord/` directory, the source code is in `/src/PhpWord/`, and any documentation should go in `/docs/`. Familiarize yourself with the codebase and try your hand at fixing [one of our outstanding issues](https://github.com/PHPOffice/PHPWord/issues). Before you get started, check the [existing pull requests](https://github.com/PHPOffice/PHPWord/pulls) to make sure no one else is already working on it. + +Once you have an issue you want to start working on, you'll need to write tests for it, and then you can start implementing the changes necessary to pass the new tests. To run the tests, you can run one of the following commands in your terminal: + +- `composer test-no-coverage` to run all of the tests +- `composer test` to run all of the tests and generate test coverage reports + +When you're ready to submit your new (and fully tested) feature, [submit a pull request to PHPWord](https://github.com/PHPOffice/PHPWord/issues/new). That's it. Thank you for your interest in PHPWord, and welcome! From 74e52ce71be3ab1fa1703c28cb2989a4df44dbbb Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Mon, 1 Jul 2019 13:33:48 -0400 Subject: [PATCH 37/53] Remove the existing issue template --- docs/ISSUE_TEMPLATE.md | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 docs/ISSUE_TEMPLATE.md diff --git a/docs/ISSUE_TEMPLATE.md b/docs/ISSUE_TEMPLATE.md deleted file mode 100644 index c7ed27d7..00000000 --- a/docs/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,35 +0,0 @@ -This is: - -- [ ] a bug report -- [ ] a feature request -- [ ] **not** a usage question (ask them on https://stackoverflow.com/questions/tagged/phpword) - -### Expected Behavior - -Please describe the behavior you are expecting. - -### Current Behavior - -What is the current behavior? - -### Failure Information - -Please help provide information about the failure. - -### How to Reproduce - -Please provide a code sample that reproduces the issue. - -```php -addSection(); -$section->... -``` - -### Context - -* PHP version: -* PHPWord version: 0.14 From 71ac081cfa3cfc38bb8007dd195bba5fda12e80f Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Mon, 1 Jul 2019 13:34:05 -0400 Subject: [PATCH 38/53] Add note about using `composer check` before submitting pull requests --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dbef13a6..ac262a0f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ Once you have an issue you want to start working on, you'll need to write tests - `composer test-no-coverage` to run all of the tests - `composer test` to run all of the tests and generate test coverage reports -When you're ready to submit your new (and fully tested) feature, [submit a pull request to PHPWord](https://github.com/PHPOffice/PHPWord/issues/new). +When you're ready to submit your new (and fully tested) feature, ensure `composer check` passes and [submit a pull request to PHPWord](https://github.com/PHPOffice/PHPWord/issues/new). That's it. Thank you for your interest in PHPWord, and welcome! From b13aa70ae9d451b8e6e6b049cbc11367fd90c0b6 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Mon, 1 Jul 2019 13:36:41 -0400 Subject: [PATCH 39/53] Move pull request template to avoid confusion --- {docs => .github}/PULL_REQUEST_TEMPLATE.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {docs => .github}/PULL_REQUEST_TEMPLATE.md (100%) diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from docs/PULL_REQUEST_TEMPLATE.md rename to .github/PULL_REQUEST_TEMPLATE.md From 9abf4473b0e0411c443f885d2f8fe6b99a553537 Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 2 Jul 2019 16:02:52 +0200 Subject: [PATCH 40/53] Update Wrong definition --- src/PhpWord/Element/AbstractContainer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Element/AbstractContainer.php b/src/PhpWord/Element/AbstractContainer.php index 5e058667..abf23c6e 100644 --- a/src/PhpWord/Element/AbstractContainer.php +++ b/src/PhpWord/Element/AbstractContainer.php @@ -41,7 +41,7 @@ namespace PhpOffice\PhpWord\Element; * @method Field addField(string $type = null, array $properties = array(), array $options = array(), mixed $text = null) * @method Line addLine(mixed $lineStyle = null) * @method Shape addShape(string $type, mixed $style = null) - * @method Chart addChart(string $type, array $categories, array $values, array $style = null) + * @method Chart addChart(string $type, array $categories, array $values, array $style = null, $seriesName = null) * @method FormField addFormField(string $type, mixed $fStyle = null, mixed $pStyle = null) * @method SDT addSDT(string $type) * From 9e93d5eae5e544e5c280a471eb959808d1eac0db Mon Sep 17 00:00:00 2001 From: Andrew Busel Date: Wed, 3 Jul 2019 19:24:15 +0300 Subject: [PATCH 41/53] Update Html.php --- src/PhpWord/Shared/Html.php | 50 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index c484e08f..1dd13072 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -28,13 +28,13 @@ use PhpOffice\PhpWord\Style\Paragraph; /** * Common Html functions * - * @SuppressWarnings(PHPMD.UnusedPrivateMethod) For readWPNode + * @SuppressWarnings(PHPMD.UnusedprotectedMethod) For readWPNode */ class Html { - private static $listIndex = 0; - private static $xpath; - private static $options; + protected static $listIndex = 0; + protected static $xpath; + protected static $options; /** * Add HTML parts. @@ -203,7 +203,7 @@ class Html * @param array $styles * @param array $data */ - private static function parseChildNodes($node, $element, $styles, $data) + protected static function parseChildNodes($node, $element, $styles, $data) { if ('li' != $node->nodeName) { $cNodes = $node->childNodes; @@ -225,7 +225,7 @@ class Html * @param array &$styles * @return \PhpOffice\PhpWord\Element\TextRun */ - private static function parseParagraph($node, $element, &$styles) + protected static function parseParagraph($node, $element, &$styles) { $styles['paragraph'] = self::recursiveParseStylesInHierarchy($node, $styles['paragraph']); $newElement = $element->addTextRun($styles['paragraph']); @@ -244,7 +244,7 @@ class Html * @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 */ - private static function parseHeading($element, &$styles, $argument1) + protected static function parseHeading($element, &$styles, $argument1) { $styles['paragraph'] = $argument1; $newElement = $element->addTextRun($styles['paragraph']); @@ -259,7 +259,7 @@ class Html * @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param array &$styles */ - private static function parseText($node, $element, &$styles) + protected static function parseText($node, $element, &$styles) { $styles['font'] = self::recursiveParseStylesInHierarchy($node, $styles['font']); @@ -280,7 +280,7 @@ class Html * @param string $argument1 Style name * @param string $argument2 Style value */ - private static function parseProperty(&$styles, $argument1, $argument2) + protected static function parseProperty(&$styles, $argument1, $argument2) { $styles['font'][$argument1] = $argument2; } @@ -291,7 +291,7 @@ class Html * @param \DOMNode $node * @param array &$styles */ - private static function parseSpan($node, &$styles) + protected static function parseSpan($node, &$styles) { self::parseInlineStyle($node, $styles['font']); } @@ -306,7 +306,7 @@ class Html * * @todo As soon as TableItem, RowItem and CellItem support relative width and height */ - private static function parseTable($node, $element, &$styles) + protected static function parseTable($node, $element, &$styles) { $elementStyles = self::parseInlineStyle($node, $styles['table']); @@ -335,7 +335,7 @@ class Html * @param array &$styles * @return Row $element */ - private static function parseRow($node, $element, &$styles) + protected static function parseRow($node, $element, &$styles) { $rowStyles = self::parseInlineStyle($node, $styles['row']); if ($node->parentNode->nodeName == 'thead') { @@ -353,7 +353,7 @@ class Html * @param array &$styles * @return \PhpOffice\PhpWord\Element\Cell|\PhpOffice\PhpWord\Element\TextRun $element */ - private static function parseCell($node, $element, &$styles) + protected static function parseCell($node, $element, &$styles) { $cellStyles = self::recursiveParseStylesInHierarchy($node, $styles['cell']); @@ -376,7 +376,7 @@ class Html * @param \DOMNode $node * @return bool Returns true if the node contains an HTML element that cannot be added to TextRun */ - private static function shouldAddTextRun(\DOMNode $node) + protected static function shouldAddTextRun(\DOMNode $node) { $containsBlockElement = self::$xpath->query('.//table|./p|./ul|./ol', $node)->length > 0; if ($containsBlockElement) { @@ -393,7 +393,7 @@ class Html * @param \DOMNode $node * @param array &$styles */ - private static function recursiveParseStylesInHierarchy(\DOMNode $node, array $style) + protected static function recursiveParseStylesInHierarchy(\DOMNode $node, array $style) { $parentStyle = self::parseInlineStyle($node, array()); $style = array_merge($parentStyle, $style); @@ -412,7 +412,7 @@ class Html * @param array &$styles * @param array &$data */ - private static function parseList($node, $element, &$styles, &$data) + protected static function parseList($node, $element, &$styles, &$data) { $isOrderedList = $node->nodeName === 'ol'; if (isset($data['listdepth'])) { @@ -431,7 +431,7 @@ class Html * @param bool $isOrderedList * @return array */ - private static function getListStyle($isOrderedList) + protected static function getListStyle($isOrderedList) { if ($isOrderedList) { return array( @@ -477,7 +477,7 @@ class Html * @todo This function is almost the same like `parseChildNodes`. Merged? * @todo As soon as ListItem inherits from AbstractContainer or TextRun delete parsing part of childNodes */ - private static function parseListItem($node, $element, &$styles, $data) + protected static function parseListItem($node, $element, &$styles, $data) { $cNodes = $node->childNodes; if (!empty($cNodes)) { @@ -495,7 +495,7 @@ class Html * @param array $styles * @return array */ - private static function parseStyle($attribute, $styles) + protected static function parseStyle($attribute, $styles) { $properties = explode(';', trim($attribute->value, " \t\n\r\0\x0B;")); @@ -623,7 +623,7 @@ class Html * * @return \PhpOffice\PhpWord\Element\Image **/ - private static function parseImage($node, $element) + protected static function parseImage($node, $element) { $style = array(); $src = null; @@ -726,7 +726,7 @@ class Html * @param string $cssBorderStyle * @return null|string */ - private static function mapBorderStyle($cssBorderStyle) + protected static function mapBorderStyle($cssBorderStyle) { switch ($cssBorderStyle) { case 'none': @@ -739,7 +739,7 @@ class Html } } - private static function mapBorderColor(&$styles, $cssBorderColor) + protected static function mapBorderColor(&$styles, $cssBorderColor) { $numColors = substr_count($cssBorderColor, '#'); if ($numColors === 1) { @@ -759,7 +759,7 @@ class Html * @param string $cssAlignment * @return string|null */ - private static function mapAlign($cssAlignment) + protected static function mapAlign($cssAlignment) { switch ($cssAlignment) { case 'right': @@ -778,7 +778,7 @@ class Html * * @param \PhpOffice\PhpWord\Element\AbstractContainer $element */ - private static function parseLineBreak($element) + protected static function parseLineBreak($element) { $element->addTextBreak(); } @@ -790,7 +790,7 @@ class Html * @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param array $styles */ - private static function parseLink($node, $element, &$styles) + protected static function parseLink($node, $element, &$styles) { $target = null; foreach ($node->attributes as $attribute) { From 415bdb378dbfbd50e12338b0ae898e5e8fe55fb1 Mon Sep 17 00:00:00 2001 From: Andrew Busel Date: Wed, 3 Jul 2019 19:25:29 +0300 Subject: [PATCH 42/53] 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 1dd13072..54e9509e 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -28,7 +28,7 @@ use PhpOffice\PhpWord\Style\Paragraph; /** * Common Html functions * - * @SuppressWarnings(PHPMD.UnusedprotectedMethod) For readWPNode + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) For readWPNode */ class Html { From 4f7790baab33bf70dc48b02ca838a2dcac21c849 Mon Sep 17 00:00:00 2001 From: Manuel Transfeld Date: Tue, 9 Jul 2019 17:22:30 +0200 Subject: [PATCH 43/53] Fix link anchor Fix a typo in a link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1d1d6ee..68092a48 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ PHPWord requires the following: ## Installation PHPWord is installed via [Composer](https://getcomposer.org/). -To [add a dependency](https://getcomposer.org/doc/04-schema.md#package-links>) to PHPWord in your project, either +To [add a dependency](https://getcomposer.org/doc/04-schema.md#package-links) to PHPWord in your project, either Run the following to use the latest stable version ```sh From 5f8fad39858be572cca0a3ff16cd582d4e5938e9 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Thu, 11 Jul 2019 17:14:35 -0400 Subject: [PATCH 44/53] Use relative links in README to ensure they go to the correct branch --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 68092a48..b15f83d7 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Develop: PHPWord is a library written in pure PHP that provides a set of classes to write to and read from different document file formats. The current version of PHPWord supports Microsoft [Office Open XML](http://en.wikipedia.org/wiki/Office_Open_XML) (OOXML or OpenXML), OASIS [Open Document Format for Office Applications](http://en.wikipedia.org/wiki/OpenDocument) (OpenDocument or ODF), [Rich Text Format](http://en.wikipedia.org/wiki/Rich_Text_Format) (RTF), HTML, and PDF. -PHPWord is an open source project licensed under the terms of [LGPL version 3](https://github.com/PHPOffice/PHPWord/blob/develop/COPYING.LESSER). PHPWord is aimed to be a high quality software product by incorporating [continuous integration](https://travis-ci.org/PHPOffice/PHPWord) and [unit testing](http://phpoffice.github.io/PHPWord/coverage/develop/). You can learn more about PHPWord by reading the [Developers' Documentation](http://phpword.readthedocs.org/). +PHPWord is an open source project licensed under the terms of [LGPL version 3](COPYING.LESSER). PHPWord is aimed to be a high quality software product by incorporating [continuous integration](https://travis-ci.org/PHPOffice/PHPWord) and [unit testing](http://phpoffice.github.io/PHPWord/coverage/develop/). You can learn more about PHPWord by reading the [Developers' Documentation](http://phpword.readthedocs.org/). If you have any questions, please ask on [StackOverFlow](https://stackoverflow.com/questions/tagged/phpword) @@ -174,7 +174,7 @@ You can also read the [Developers' Documentation](http://phpword.readthedocs.org We welcome everyone to contribute to PHPWord. Below are some of the things that you can do to contribute. -- Read [our contributing guide](https://github.com/PHPOffice/PHPWord/blob/master/CONTRIBUTING.md). +- Read [our contributing guide](CONTRIBUTING.md). - [Fork us](https://github.com/PHPOffice/PHPWord/fork) and [request a pull](https://github.com/PHPOffice/PHPWord/pulls) to the [develop](https://github.com/PHPOffice/PHPWord/tree/develop) branch. - Submit [bug reports or feature requests](https://github.com/PHPOffice/PHPWord/issues) to GitHub. - Follow [@PHPWord](https://twitter.com/PHPWord) and [@PHPOffice](https://twitter.com/PHPOffice) on Twitter. From 5fe485adac3c48c031377451ea94814b7cc95800 Mon Sep 17 00:00:00 2001 From: Andrey Bolonin Date: Sun, 14 Jul 2019 15:26:23 +0300 Subject: [PATCH 45/53] add php 7.4snapshot --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1d32cfda..806a5f57 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ php: - 7.1 - 7.2 - 7.3 + - 7.4snapshot matrix: include: From 41227e8e08f6bb8d56f201a8ab250d9ed1eb6317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Dupont?= Date: Wed, 28 Aug 2019 10:59:06 +0200 Subject: [PATCH 46/53] Fix apt-get crash in Travis CI --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1d32cfda..802f3229 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,6 +36,7 @@ env: before_install: ## Packages + - sudo rm -f /etc/apt/sources.list.d/mongodb.list # Makes apt crash on Precise, and we don't need MongoDB - sudo apt-get update -qq - sudo apt-get install -y graphviz From 72311767c5c6ac184c7a5bbb210d76dd88ccb3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Dupont?= Date: Wed, 28 Aug 2019 11:11:19 +0200 Subject: [PATCH 47/53] Fix Travis crash with Composer memory usage --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 802f3229..d3b1f907 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ matrix: - php: 7.0 env: COVERAGE=1 - php: 5.3 - env: COMPOSER_MEMORY_LIMIT=2G + env: COMPOSER_MEMORY_LIMIT=3G - php: 7.3 env: DEPENDENCIES="--ignore-platform-reqs" exclude: From 18ec5d63f379ff1b020901c8eb2e0ee0eab22690 Mon Sep 17 00:00:00 2001 From: troosan Date: Sun, 1 Sep 2019 21:03:22 +0200 Subject: [PATCH 48/53] fix phpmd config --- phpmd.xml.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpmd.xml.dist b/phpmd.xml.dist index 44b3efdf..2077e02b 100644 --- a/phpmd.xml.dist +++ b/phpmd.xml.dist @@ -19,7 +19,7 @@ - + From e9a4251c7e5b6b15bd4f0008f0a59b5bc8d44ace Mon Sep 17 00:00:00 2001 From: troosan Date: Sun, 1 Sep 2019 21:54:28 +0200 Subject: [PATCH 49/53] Use precise only for php 5.3 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 806a5f57..b689506c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: php -dist: precise +dist: xenial php: - 5.3 @@ -18,6 +18,7 @@ matrix: - php: 7.0 env: COVERAGE=1 - php: 5.3 + dist: precise env: COMPOSER_MEMORY_LIMIT=2G - php: 7.3 env: DEPENDENCIES="--ignore-platform-reqs" From aec9582d835d3dc30a0dbdaf95cb11d9ccf2cb4e Mon Sep 17 00:00:00 2001 From: troosan Date: Sun, 1 Sep 2019 22:12:34 +0200 Subject: [PATCH 50/53] allow php 7.4 build to fail --- .travis.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8c3cd86a..acdf95cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,17 +15,25 @@ php: matrix: include: - - php: 7.0 - env: COVERAGE=1 - php: 5.3 dist: precise env: COMPOSER_MEMORY_LIMIT=3G + - php: 5.4 + dist: trusty + - php: 5.5 + dist: trusty + - php: 7.0 + env: COVERAGE=1 - php: 7.3 env: DEPENDENCIES="--ignore-platform-reqs" exclude: - php: 5.3 + - php: 5.4 + - php: 5.5 - php: 7.0 - php: 7.3 + allow_failures: + - php: 7.4snapshot cache: directories: From 8f4f4dcd4840f4a1bfee768bc26a998c532cbb3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Ma=C5=A1a?= Date: Mon, 2 Sep 2019 18:13:10 +0200 Subject: [PATCH 51/53] Added return type --- src/PhpWord/Element/AbstractElement.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/PhpWord/Element/AbstractElement.php b/src/PhpWord/Element/AbstractElement.php index e3e54ed4..46372b71 100644 --- a/src/PhpWord/Element/AbstractElement.php +++ b/src/PhpWord/Element/AbstractElement.php @@ -96,7 +96,7 @@ abstract class AbstractElement /** * A reference to the parent * - * @var \PhpOffice\PhpWord\Element\AbstractElement + * @var AbstractElement|null */ private $parent; @@ -335,6 +335,11 @@ abstract class AbstractElement $this->commentRangeEnd->setEndElement($this); } + /** + * Get parent element + * + * @return AbstractElement|null + */ public function getParent() { return $this->parent; From 7628b41fdfa03a0fc499936ba41d280ebdad9146 Mon Sep 17 00:00:00 2001 From: Samuel BF <36996277+Samuel-BF@users.noreply.github.com> Date: Sat, 7 Sep 2019 21:55:33 +0200 Subject: [PATCH 52/53] Add support for basic fields in RTF writer. --- src/PhpWord/Writer/RTF/Element/Field.php | 80 ++++++++++++++++++++++++ tests/PhpWord/Writer/RTF/ElementTest.php | 38 ++++++++++- 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/PhpWord/Writer/RTF/Element/Field.php diff --git a/src/PhpWord/Writer/RTF/Element/Field.php b/src/PhpWord/Writer/RTF/Element/Field.php new file mode 100644 index 00000000..e958e9de --- /dev/null +++ b/src/PhpWord/Writer/RTF/Element/Field.php @@ -0,0 +1,80 @@ +element; + if (!$element instanceof \PhpOffice\PhpWord\Element\Field) { + return; + } + + $this->getStyles(); + + $content = ''; + $content .= $this->writeOpening(); + $content .= '{'; + $content .= $this->writeFontStyle(); + + $methodName = 'write' . ucfirst(strtolower($element->getType())); + if (!method_exists($this, $methodName)) { + // Unsupported field + $content .= ''; + } else { + $content .= '\\field{\\*\\fldinst '; + $content .= $this->$methodName($element); + $content .= '}{\\fldrslt}'; + } + $content .= '}'; + $content .= $this->writeClosing(); + + return $content; + } + + protected function writePage() + { + return 'PAGE'; + } + + protected function writeNumpages() + { + return 'NUMPAGES'; + } + + protected function writeDate(\PhpOffice\PhpWord\Element\Field $element) + { + $content = ''; + $content .= 'DATE'; + $properties = $element->getProperties(); + if (isset($properties['dateformat'])) { + $content .= ' \\\\@ "' . $properties['dateformat'] . '"'; + } + + return $content; + } +} diff --git a/tests/PhpWord/Writer/RTF/ElementTest.php b/tests/PhpWord/Writer/RTF/ElementTest.php index 47630335..4b01bacf 100644 --- a/tests/PhpWord/Writer/RTF/ElementTest.php +++ b/tests/PhpWord/Writer/RTF/ElementTest.php @@ -29,7 +29,7 @@ class ElementTest extends \PHPUnit\Framework\TestCase */ public function testUnmatchedElements() { - $elements = array('Container', 'Text', 'Title', 'Link', 'Image', 'Table'); + $elements = array('Container', 'Text', 'Title', 'Link', 'Image', 'Table', 'Field'); foreach ($elements as $element) { $objectClass = 'PhpOffice\\PhpWord\\Writer\\RTF\\Element\\' . $element; $parentWriter = new RTF(); @@ -39,4 +39,40 @@ class ElementTest extends \PHPUnit\Framework\TestCase $this->assertEquals('', $object->write()); } } + + public function testPageField() + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Field('PAGE'); + $field = new \PhpOffice\PhpWord\Writer\RTF\Element\Field($parentWriter, $element); + + $this->assertEquals("{\\field{\\*\\fldinst PAGE}{\\fldrslt}}\\par\n", $field->write()); + } + + public function testNumpageField() + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Field('NUMPAGES'); + $field = new \PhpOffice\PhpWord\Writer\RTF\Element\Field($parentWriter, $element); + + $this->assertEquals("{\\field{\\*\\fldinst NUMPAGES}{\\fldrslt}}\\par\n", $field->write()); + } + + public function testDateField() + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Field('DATE', array('dateformat' => 'd MM yyyy H:mm:ss')); + $field = new \PhpOffice\PhpWord\Writer\RTF\Element\Field($parentWriter, $element); + + $this->assertEquals("{\\field{\\*\\fldinst DATE \\\\@ \"d MM yyyy H:mm:ss\"}{\\fldrslt}}\\par\n", $field->write()); + } + + public function testIndexField() + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Field('INDEX'); + $field = new \PhpOffice\PhpWord\Writer\RTF\Element\Field($parentWriter, $element); + + $this->assertEquals("{}\\par\n", $field->write()); + } } From b8346af548d399acd9e30fc76ab0c55c2fec03a5 Mon Sep 17 00:00:00 2001 From: troosan Date: Tue, 1 Oct 2019 22:43:33 +0200 Subject: [PATCH 53/53] update changelog for version 0.17 --- CHANGELOG.md | 21 ++++++++++++++++++--- composer.json | 2 +- docs/conf.py | 2 +- docs/installing.rst | 2 +- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66271bae..5d55fa2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,18 +3,33 @@ Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -v0.17.0 (?? ??? 2019) +v0.17.0 (01 oct 2019) ---------------------- ### Added -- Add RightToLeft table presentation. @troosan #1550 +- Add methods setValuesFromArray and cloneRowFromArray to the TemplateProcessor @geraldb-nicat #670 - Set complex type in template @troosan #1565 +- implement support for section vAlign @troosan #1569 +- ParseStyle for border-color @Gllrm0 #1551 +- Html writer auto invert text color @SailorMax #1387 +- Add RightToLeft table presentation. @troosan #1550 - Add support for page vertical alignment. @troosan #672 #1569 +- Adding setNumId method for ListItem style @eweso #1329 +- Add support for basic fields in RTF writer. @Samuel-BF #1717 ### Fixed - Fix HTML border-color parsing. @troosan #1551 #1570 +- Language::validateLocale should pass with locale 'zxx'. @efpapado #1558 +- can't align center vertically with the text @ter987 #672 +- fix parsing of border-color and add test @troosan #1570 +- TrackChange doesn't handle all return types of \DateTime::createFromFormat(...) @superhaggis #1584 +- To support PreserveText inside sub container @bhattnishant #1637 +- No nested w:pPr elements in ListItemRun. @waltertamboer #1628 +- Ensure that entity_loader disable variable is re-set back to the original setting @seamuslee001 #1585 ### Miscellaneous -- Use embedded http server to test loading of remote images @troosan # +- Use embedded http server to test loading of remote images @troosan #1544 +- Change private to protected to be able extending class Html @SpinyMan #1646 +- Fix apt-get crash in Travis CI for PHP 5.3 @mdupont #1707 v0.16.0 (30 dec 2018) ---------------------- diff --git a/composer.json b/composer.json index bd57d6e3..f5f751ec 100644 --- a/composer.json +++ b/composer.json @@ -90,7 +90,7 @@ }, "extra": { "branch-alias": { - "dev-develop": "0.17-dev" + "dev-develop": "0.18-dev" } } } diff --git a/docs/conf.py b/docs/conf.py index d83c43f5..fdfe14ca 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,7 +48,7 @@ copyright = u'2014-2017, PHPWord Contributors' # built documents. # # The short X.Y version. -version = '0.16.0' +version = '0.17.0' # The full version, including alpha/beta/rc tags. release = version diff --git a/docs/installing.rst b/docs/installing.rst index 34353be8..2a9582d0 100644 --- a/docs/installing.rst +++ b/docs/installing.rst @@ -34,7 +34,7 @@ Example: { "require": { - "phpoffice/phpword": "v0.14.*" + "phpoffice/phpword": "v0.17.*" } }