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). 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 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/.travis.yml b/.travis.yml index 6ab138bb..b6b42a1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,12 @@ language: php -dist: precise +dist: xenial php: - - 5.6 - 7.1 - 7.2 - 7.3 - - nightly + - 7.4snapshot matrix: include: @@ -15,14 +14,14 @@ matrix: env: COVERAGE=1 - php: 7.3 env: DEPENDENCIES="--ignore-platform-reqs" - - php: nightly + - php: 7.4snapshot env: DEPENDENCIES="--ignore-platform-reqs" exclude: - php: 7.2 - php: 7.3 - - php: nightly + - php: 7.4snapshot allow_failures: - - php: nightly + - php: 7.4snapshot cache: directories: @@ -35,6 +34,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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d07c7bd..5d55fa2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,34 @@ 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 (01 oct 2019) +---------------------- +### Added +- 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 #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) ---------------------- ### Added @@ -27,7 +55,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) @@ -73,7 +101,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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e62f2e6f..ac262a0f 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.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. -- **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, 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! diff --git a/README.md b/README.md index f1d1d6ee..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) @@ -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 @@ -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. diff --git a/composer.json b/composer.json index c4ccb4cd..8f59195b 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,7 @@ "fix": "Fixes issues found by PHP-CS" }, "require": { - "php": "^5.6 || ^7.0", + "php": "^7.0", "ext-xml": "*", "zendframework/zend-escaper": "^2.2", "phpoffice/common": "^0.2.9" @@ -66,9 +66,9 @@ "require-dev": { "ext-zip": "*", "ext-gd": "*", - "phpunit/phpunit": "^5.7 || ^7.0", + "phpunit/phpunit": "^7.0", "squizlabs/php_codesniffer": "^3.0", - "friendsofphp/php-cs-fixer": "^2.3", + "friendsofphp/php-cs-fixer": "^2.14", "phpmd/phpmd": "2.*", "phploc/phploc": "^4.0", "dompdf/dompdf":"0.8.*", @@ -90,7 +90,7 @@ }, "extra": { "branch-alias": { - "dev-develop": "0.17-dev", + "dev-develop": "0.18-dev", "develop_v1.0": "1.0-dev" } } 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 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.*" } } diff --git a/docs/styles.rst b/docs/styles.rst index 31d04a3b..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: @@ -108,11 +110,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: diff --git a/docs/templates-processing.rst b/docs/templates-processing.rst index 095093b2..5b32aa18 100644 --- a/docs/templates-processing.rst +++ b/docs/templates-processing.rst @@ -17,13 +17,23 @@ Given a template containing .. code-block:: clean - Hello ${name}! + Hello ${firstname} ${lastname}! -The following will replace ``${name}`` with ``World``. The resulting document will now contain ``Hello World!`` +The following will replace ``${firstname}`` with ``John``, and ``${lastname}`` with ``Doe`` . +The resulting document will now contain ``Hello John Doe!`` .. code-block:: php - $templateProcessor->setValue('name', 'World'); + $templateProcessor->setValue('firstname', 'John'); + $templateProcessor->setValue('lastname', 'Doe'); + +setValues +""""""""" +You can also set multiple values by passing all of them in an array. + +.. code-block:: php + + $templateProcessor->setValues(array('firstname' => 'John', 'lastname' => 'Doe')); setImageValue """"""""""""" @@ -138,11 +148,11 @@ See ``Sample_07_TemplateCloneRow.php`` for an example. .. code-block:: clean - ------------------------------ + +-----------+----------------+ | ${userId} | ${userName} | - | |----------------| + | |----------------+ | | ${userAddress} | - ------------------------------ + +-----------+----------------+ .. code-block:: php @@ -152,15 +162,49 @@ Will result in .. code-block:: clean - ---------------------------------- + +-------------+------------------+ | ${userId#1} | ${userName#1} | - | |------------------| + | |------------------+ | | ${userAddress#1} | - ---------------------------------| + +-------------+------------------+ | ${userId#2} | ${userName#2} | - | |------------------| + | |------------------+ | | ${userAddress#2} | - ---------------------------------- + +-------------+------------------+ + +cloneRowAndSetValues +"""""""""""""""""""" +Finds a row in a table row identified by `$search` param and clones it as many times as there are entries in `$values`. + +.. code-block:: clean + + +-----------+----------------+ + | ${userId} | ${userName} | + | |----------------+ + | | ${userAddress} | + +-----------+----------------+ + +.. code-block:: php + + $values = [ + ['userId' => 1, 'userName' => 'Batman', 'userAddress' => 'Gotham City'], + ['userId' => 2, 'userName' => 'Superman', 'userAddress' => 'Metropolis'], + ]; + $templateProcessor->cloneRowAndSetValues('userId', ); + +Will result in + +.. code-block:: clean + + +---+-------------+ + | 1 | Batman | + | |-------------+ + | | Gotham City | + +---+-------------+ + | 2 | Superman | + | |-------------+ + | | Metropolis | + +---+-------------+ applyXslStyleSheet """""""""""""""""" @@ -171,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); diff --git a/phpmd.xml.dist b/phpmd.xml.dist index 44b3efdf..6246893c 100644 --- a/phpmd.xml.dist +++ b/phpmd.xml.dist @@ -19,7 +19,7 @@ - + @@ -28,6 +28,6 @@ - + \ No newline at end of file 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/samples/Sample_07_TemplateCloneRow.php b/samples/Sample_07_TemplateCloneRow.php index 81253d0a..42c53269 100644 --- a/samples/Sample_07_TemplateCloneRow.php +++ b/samples/Sample_07_TemplateCloneRow.php @@ -36,22 +36,46 @@ $templateProcessor->setValue('rowNumber#9', '9'); $templateProcessor->setValue('rowNumber#10', '10'); // Table with a spanned cell -$templateProcessor->cloneRow('userId', 3); +$values = array( + array( + 'userId' => 1, + 'userFirstName' => 'James', + 'userName' => 'Taylor', + 'userPhone' => '+1 428 889 773', + ), + array( + 'userId' => 2, + 'userFirstName' => 'Robert', + 'userName' => 'Bell', + 'userPhone' => '+1 428 889 774', + ), + array( + 'userId' => 3, + 'userFirstName' => 'Michael', + 'userName' => 'Ray', + 'userPhone' => '+1 428 889 775', + ), +); -$templateProcessor->setValue('userId#1', '1'); -$templateProcessor->setValue('userFirstName#1', 'James'); -$templateProcessor->setValue('userName#1', 'Taylor'); -$templateProcessor->setValue('userPhone#1', '+1 428 889 773'); +$templateProcessor->cloneRowAndSetValues('userId', $values); -$templateProcessor->setValue('userId#2', '2'); -$templateProcessor->setValue('userFirstName#2', 'Robert'); -$templateProcessor->setValue('userName#2', 'Bell'); -$templateProcessor->setValue('userPhone#2', '+1 428 889 774'); +//this is equivalent to cloning and settings values with cloneRowAndSetValues +// $templateProcessor->cloneRow('userId', 3); -$templateProcessor->setValue('userId#3', '3'); -$templateProcessor->setValue('userFirstName#3', 'Michael'); -$templateProcessor->setValue('userName#3', 'Ray'); -$templateProcessor->setValue('userPhone#3', '+1 428 889 775'); +// $templateProcessor->setValue('userId#1', '1'); +// $templateProcessor->setValue('userFirstName#1', 'James'); +// $templateProcessor->setValue('userName#1', 'Taylor'); +// $templateProcessor->setValue('userPhone#1', '+1 428 889 773'); + +// $templateProcessor->setValue('userId#2', '2'); +// $templateProcessor->setValue('userFirstName#2', 'Robert'); +// $templateProcessor->setValue('userName#2', 'Bell'); +// $templateProcessor->setValue('userPhone#2', '+1 428 889 774'); + +// $templateProcessor->setValue('userId#3', '3'); +// $templateProcessor->setValue('userFirstName#3', 'Michael'); +// $templateProcessor->setValue('userName#3', 'Ray'); +// $templateProcessor->setValue('userPhone#3', '+1 428 889 775'); echo date('H:i:s'), ' Saving the result document...', EOL; $templateProcessor->saveAs('results/Sample_07_TemplateCloneRow.docx'); 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/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/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/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/samples/resources/Sample_40_TemplateSetComplexValue.docx b/samples/resources/Sample_40_TemplateSetComplexValue.docx new file mode 100644 index 00000000..7265908e Binary files /dev/null and b/samples/resources/Sample_40_TemplateSetComplexValue.docx differ diff --git a/src/PhpWord/Element/AbstractContainer.php b/src/PhpWord/Element/AbstractContainer.php index 204d4a73..0c773dbe 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) @@ -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) * @@ -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')), ); 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; 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/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/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/Shared/Html.php b/src/PhpWord/Shared/Html.php index 2c3f5e42..cdee23f8 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -32,9 +32,9 @@ use PhpOffice\PhpWord\Style\Paragraph; */ 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. @@ -75,7 +75,7 @@ class Html $html = preg_replace('/(\>)\s*(\<)/m', '$1$2', $html); // Load DOM - libxml_disable_entity_loader(true); + $orignalLibEntityLoader = libxml_disable_entity_loader(true); $dom = new \DOMDocument(); $dom->preserveWhiteSpace = $preserveWhiteSpace; $dom->loadHTML($html, LIBXML_NOWARNING); @@ -83,6 +83,7 @@ class Html $node = $dom->getElementsByTagName('body'); self::parseNode($node->item(0), $element); + libxml_disable_entity_loader($orignalLibEntityLoader); } /** @@ -194,7 +195,7 @@ class Html $newElement = $element; } - self::parseChildNodes($node, $newElement, $styles, $data); + static::parseChildNodes($node, $newElement, $styles, $data); } /** @@ -205,7 +206,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; @@ -227,7 +228,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']); @@ -246,7 +247,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']); @@ -261,7 +262,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']); @@ -282,7 +283,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; } @@ -293,7 +294,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']); } @@ -308,7 +309,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']); @@ -337,7 +338,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') { @@ -355,7 +356,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']); @@ -378,7 +379,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) { @@ -395,7 +396,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); @@ -414,7 +415,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'])) { @@ -433,7 +434,7 @@ class Html * @param bool $isOrderedList * @return array */ - private static function getListStyle($isOrderedList) + protected static function getListStyle($isOrderedList) { if ($isOrderedList) { return array( @@ -479,7 +480,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)) { @@ -497,7 +498,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;")); @@ -519,7 +520,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'; @@ -584,7 +585,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); @@ -625,7 +626,7 @@ class Html * * @return \PhpOffice\PhpWord\Element\Image **/ - private static function parseImage($node, $element) + protected static function parseImage($node, $element) { $style = array(); $src = null; @@ -728,7 +729,7 @@ class Html * @param string $cssBorderStyle * @return null|string */ - private static function mapBorderStyle($cssBorderStyle) + protected static function mapBorderStyle($cssBorderStyle) { switch ($cssBorderStyle) { case 'none': @@ -741,13 +742,27 @@ class Html } } + protected 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]] = trim($colors[$i], '#'); + } + } + } + /** * Transforms a HTML/CSS alignment into a \PhpOffice\PhpWord\SimpleType\Jc * * @param string $cssAlignment * @return string|null */ - private static function mapAlign($cssAlignment) + protected static function mapAlign($cssAlignment) { switch ($cssAlignment) { case 'right': @@ -766,7 +781,7 @@ class Html * * @param \PhpOffice\PhpWord\Element\AbstractContainer $element */ - private static function parseLineBreak($element) + protected static function parseLineBreak($element) { $element->addTextBreak(); } @@ -778,7 +793,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) { 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/Language.php b/src/PhpWord/Style/Language.php index 8174f6ee..18ef8897 100644 --- a/src/PhpWord/Style/Language.php +++ b/src/PhpWord/Style/Language.php @@ -71,6 +71,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 * @@ -229,7 +232,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'); } diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index 306ecff3..4293940f 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -139,6 +139,16 @@ class ListItem extends AbstractStyle return $this->numId; } + /** + * Set numbering Id. Same numId means same list + * @param mixed $numInt + */ + 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 = 'PHPWordListType' . $this->listType; + + if ($this->numId) { + $numStyle .= 'NumId' . $this->numId; + } + if (Style::getStyle($numStyle) !== null) { $this->setNumStyle($numStyle); diff --git a/src/PhpWord/Style/Section.php b/src/PhpWord/Style/Section.php index 162e08e0..ff9b0be0 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 string + */ + 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/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/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index daef625a..7efc0f1a 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; @@ -48,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 * @@ -119,6 +127,7 @@ class TemplateProcessor } $this->tempDocumentMainPart = $this->readPartWithRels($this->getMainPartName()); + $this->tempDocumentSettingsPart = $this->readPartWithRels($this->getSettingsPartName()); $this->tempDocumentContentTypes = $this->zipClass->getFromName($this->getDocumentContentTypesName()); } @@ -161,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.'); @@ -171,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; } @@ -249,6 +259,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 @@ -284,6 +334,18 @@ class TemplateProcessor $this->tempDocumentFooters = $this->setValueForPart($search, $replace, $this->tempDocumentFooters, $limit); } + /** + * Set values from a one-dimensional array of "variable => value"-pairs. + * + * @param array $values + */ + public function setValues(array $values) + { + foreach ($values as $macro => $replace) { + $this->setValue($macro, $replace); + } + } + private function getImageArgs($varNameWithArgs) { $varElements = explode(':', $varNameWithArgs); @@ -641,6 +703,24 @@ class TemplateProcessor $this->tempDocumentMainPart = $result; } + /** + * Clones a table row and populates it's values from a two-dimensional array in a template document. + * + * @param string $search + * @param array $values + */ + public function cloneRowAndSetValues($search, $values) + { + $this->cloneRow($search, count($values)); + + foreach ($values as $rowKey => $rowData) { + $rowNumber = $rowKey + 1; + foreach ($rowData as $macro => $replace) { + $this->setValue($macro . '#' . $rowNumber, $replace); + } + } + } + /** * Clone a block. * @@ -655,6 +735,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, @@ -694,6 +775,7 @@ class TemplateProcessor */ public function replaceBlock($blockname, $replacement) { + $matches = array(); preg_match( '/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', $this->tempDocumentMainPart, @@ -719,6 +801,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. * @@ -733,6 +831,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); @@ -835,6 +934,7 @@ class TemplateProcessor */ protected function getVariablesForPart($documentPartXML) { + $matches = array(); preg_match_all('/\$\{(.*?)}/i', $documentPartXML, $matches); return $matches[1]; @@ -863,11 +963,22 @@ 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'; } + /** + * 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. * @@ -1001,4 +1112,141 @@ 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 (is_array($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 (0 > $macroPos) { + return false; + } + $start = $this->findXmlBlockStart($macroPos, $blockType); + if (0 > $start) { + return false; + } + $end = $this->findXmlBlockEnd($start, $blockType); + //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; + } + + 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 int $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) + { + $reverseOffset = (strlen($this->tempDocumentMainPart) - $offset) * -1; + // first try XML tag with attributes + $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 . '>', $reverseOffset); + } + + 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/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 diff --git a/src/PhpWord/Writer/HTML/Element/Table.php b/src/PhpWord/Writer/HTML/Element/Table.php index 67b15d63..a6f14792 100644 --- a/src/PhpWord/Writer/HTML/Element/Table.php +++ b/src/PhpWord/Writer/HTML/Element/Table.php @@ -51,6 +51,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) { + $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; $cellVMerge = $cellStyle->getVMerge(); @@ -74,7 +82,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) { 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/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/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 + } } 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/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/Element/CellTest.php b/tests/PhpWord/Element/CellTest.php index d4aaa488..f0d639bc 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); @@ -231,7 +233,7 @@ class CellTest extends \PHPUnit\Framework\TestCase public function testAddPreserveTextException() { $oCell = new Cell(); - $oCell->setDocPart('Section', 1); + $oCell->setDocPart('TextRun', 1); $oCell->addPreserveText('text'); } 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/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()); + } } 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/Reader/Word2007/StyleTest.php b/tests/PhpWord/Reader/Word2007/StyleTest.php index 4a7add16..a7308f1a 100644 --- a/tests/PhpWord/Reader/Word2007/StyleTest.php +++ b/tests/PhpWord/Reader/Word2007/StyleTest.php @@ -19,6 +19,7 @@ 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; @@ -147,6 +148,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 = ' @@ -195,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/Shared/HtmlTest.php b/tests/PhpWord/Shared/HtmlTest.php index 89292a20..5bc9e241 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 @@ -296,8 +297,8 @@ class HtmlTest extends \PHPUnit\Framework\TestCase header a - header b - header c + header b + header c @@ -312,6 +313,17 @@ class HtmlTest extends \PHPUnit\Framework\TestCase $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('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')); } /** @@ -487,7 +499,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(); @@ -589,4 +601,35 @@ class HtmlTest extends \PHPUnit\Framework\TestCase $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')); + } } 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()); + } } diff --git a/tests/PhpWord/TemplateProcessorTest.php b/tests/PhpWord/TemplateProcessorTest.php index 370fcdf1..4caca77a 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 @@ -196,6 +199,67 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase $this->assertTrue($docFound); } + /** + * @covers ::setValue + * @covers ::cloneRow + * @covers ::saveAs + * @test + */ + public function testCloneRowAndSetValues() + { + $mainPart = ' + + + + + + + + ${userId} + + + + + + + ${userName} + + + + + + + + + + + + + + + ${userLocation} + + + + + '; + $templateProcessor = new TestableTemplateProcesor($mainPart); + + $this->assertEquals( + array('userId', 'userName', 'userLocation'), + $templateProcessor->getVariables() + ); + + $values = array( + array('userId' => 1, 'userName' => 'Batman', 'userLocation' => 'Gotham City'), + array('userId' => 2, 'userName' => 'Superman', 'userLocation' => 'Metropolis'), + ); + $templateProcessor->setValue('tableHeader', 'My clonable table'); + $templateProcessor->cloneRowAndSetValues('userId', $values); + $this->assertContains('Superman', $templateProcessor->getMainPart()); + $this->assertContains('Metropolis', $templateProcessor->getMainPart()); + } + /** * @expectedException \Exception * @test @@ -246,6 +310,78 @@ 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 + */ + public function testSetValues() + { + $mainPart = ' + + + Hello ${firstname} ${lastname} + + '; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setValues(array('firstname' => 'John', 'lastname' => 'Doe')); + + $this->assertContains('Hello John Doe', $templateProcessor->getMainPart()); + } + /** * @covers ::setImageValue * @test @@ -595,4 +731,117 @@ 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'])); + } + + 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); + } + + 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/Writer/HTML/ElementTest.php b/tests/PhpWord/Writer/HTML/ElementTest.php index 5b7176c7..101e226f 100644 --- a/tests/PhpWord/Writer/HTML/ElementTest.php +++ b/tests/PhpWord/Writer/HTML/ElementTest.php @@ -86,10 +86,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'); @@ -100,6 +100,11 @@ class ElementTest extends \PHPUnit\Framework\TestCase $this->assertEquals(1, $xpath->query('/html/body/table/tr[1]/td')->length); $this->assertEquals('2', $xpath->query('/html/body/table/tr/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); $this->assertEquals(2, $xpath->query('/html/body/table/tr[2]/td')->length); + + $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')); } /** 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/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()); + } } 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')); + } } 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')); + } } 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 00000000..66490799 Binary files /dev/null and b/tests/PhpWord/_files/images/new-php-logo.png differ 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', '>='); + } +} diff --git a/tests/PhpWord/_includes/TestableTemplateProcesor.php b/tests/PhpWord/_includes/TestableTemplateProcesor.php index 3b6f5b56..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) @@ -35,6 +36,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,8 +53,34 @@ 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; } + + /** + * @return string + */ + public function getSettingsPart() + { + return $this->tempDocumentSettingsPart; + } } 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; }