diff --git a/.gitattributes b/.gitattributes index f3d033a781..48edc26187 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,6 @@ # build config /.scrutinizer.yml export-ignore -/.travis.yml export-ignore +/.github export-ignore /php_cs.dist export-ignore /phpmd.xml.dist export-ignore /phpstan.neon export-ignore @@ -18,4 +18,4 @@ # tests /phpunit.xml.dist export-ignore -/tests export-ignore \ No newline at end of file +/tests export-ignore diff --git a/.github/ISSUE_TEMPLATE/1_bug_report.yml b/.github/ISSUE_TEMPLATE/1_bug_report.yml new file mode 100644 index 0000000000..ea335468a5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1_bug_report.yml @@ -0,0 +1,65 @@ +name: 🐛 Bug Report +description: Create a report to help improve PHPWord +labels: [ "Bug Report" ] +body: + - type: markdown + attributes: + value: | + ### ❗️ Read this before submitting your bug report: + - **Write in English/French.** Reports in all other languages will be closed. + - **Provide as much detail as possible** + - Attachments : Error logs, Screenshots, Document files (generated and expected). + - If the issue cannot be reproduced, it cannot be fixed. + - type: textarea + id: what-happened + attributes: + label: Describe the bug and add attachments + description: What went wrong? If possible, add screenshots, error logs, document files (generated and expected) or screen recordings to help explain your problem. + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen. + validations: + required: true + - type: textarea + id: steps-reproduce + attributes: + label: Steps to reproduce + description: Please provide a code sample that reproduces the issue. + placeholder: | + ```php + addSection(); + $section->... + ``` + validations: + required: true + - type: input + id: phpword-version + attributes: + label: PHPWord version(s) where the bug happened + placeholder: "e.g., 1.2.0 or master" + validations: + required: true + - type: input + id: php-version + attributes: + label: PHP version(s) where the bug happened + placeholder: "e.g., 7.1 or 8.2" + validations: + required: true + - type: checkboxes + attributes: + label: Priority + description: Funded tickets have a higher priority. + options: + - label: I want to crowdfund the bug fix (with [@algora-io](https://docs.algora.io/bounties/overview)) and fund a community developer. + required: false + - label: I want to pay the bug fix and fund a maintainer for that. (Contact @Progi1984) + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/2_feature_request.yml b/.github/ISSUE_TEMPLATE/2_feature_request.yml new file mode 100644 index 0000000000..bf3539c372 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2_feature_request.yml @@ -0,0 +1,35 @@ +name: 💡 Feature request +description: Suggest an idea for this project +labels: [ "Change Request" ] +body: + - type: markdown + attributes: + value: | + ### ❗️ Read this before submitting your bug report: + - **Write in English/French.** Reports in all other languages will be closed. + - **Provide as much detail as possible** + - Attachments : Error logs, Screenshots, Document files (generated and expected). + - If the issue cannot be reproduced, it cannot be fixed. + - type: textarea + id: problem + attributes: + label: Describe the problem + description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: Describe the expected behavior + description: A clear and concise description of what you expected to happen. If possible, add screenshots, document files (expected). + validations: + required: true + - type: checkboxes + attributes: + label: Priority + description: Funded tickets have a higher priority. + options: + - label: I want to crowdfund the feature (with [@algora-io](https://docs.algora.io/bounties/overview)) and fund a community developer. + required: false + - label: I want to pay the feature and fund a maintainer for that. (Contact @Progi1984) + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index fcb3a65db1..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a report to help improve PHPWord -labels: Bug Report - ---- - -### Describe the Bug - -A clear and concise description of what the bug is. - -### Steps to Reproduce - -Please provide a code sample that reproduces the issue. - -```php -addSection(); -$section->... -``` - -### Expected Behavior - -A clear and concise description of what you expected to happen. - -### Current Behavior - -What is the current behavior? - -### Context - -Please fill in your environment information: - -- PHP Version: -- PHPWord Version: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 171e8378e1..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -labels: Change Request - ---- - -### Is your feature request related to a problem? Please describe. - -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -### Describe the solution you'd like - -A clear and concise description of what you want to happen. - -### Describe alternatives you've considered - -A clear and concise description of any alternative solutions or features you've considered. - -### Additional context - -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/how-to-use.md b/.github/ISSUE_TEMPLATE/how-to-use.md deleted file mode 100644 index 0fef996b19..0000000000 --- a/.github/ISSUE_TEMPLATE/how-to-use.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: How to Use PHPWord -about: Find out how to use PHPWord -labels: WontFix - ---- - -***Please do not use the issue tracker to ask how to use PHPWord.*** - -Documentation is available on [Read the Docs](https://phpword.readthedocs.io/en/latest/). - -Sample code is in the [`/samples/` directory](https://github.com/PHPOffice/PHPWord/tree/develop/samples). - -Usage questions belong on [Stack Overflow](https://stackoverflow.com/questions/tagged/phpword). diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5430a996ec..7b74162561 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,6 +6,7 @@ Fixes # (issue) ### Checklist: -- [ ] I have run `composer run-script check --timeout=0` and no errors were reported -- [ ] The new code is covered by unit tests (check build/coverage for coverage report) -- [ ] I have updated the documentation to describe the changes +- [ ] My CI is :green_circle: +- [ ] I have covered by unit tests my new code (check build/coverage for coverage report) +- [ ] I have updated the [documentation](https://github.com/PHPOffice/PHPWord/tree/master/docs) to describe the changes +- [ ] I have updated the [changelog](https://github.com/PHPOffice/PHPWord/blob/master/docs/changes/1.x/1.4.0.md) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000000..7a1c2a6ea7 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,51 @@ +name: Deploy + +on: + push: + branches: + - master + pull_request: + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + ### MkDocs + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.x + - name: Install Python Dependencies + run: pip install mkdocs-material autolink-references-mkdocs-plugin + - name: Build documentation + run: mkdocs build --site-dir public + ### PHPUnit + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: xdebug + - name: Create directory public/coverage + run: mkdir ./public/coverage + - name: Install PHP Dependencies + run: composer install --ansi --prefer-dist --no-interaction --no-progress + - name: Build Coverage Report + run: XDEBUG_MODE=coverage ./vendor/bin/phpunit -c ./ --coverage-text --coverage-html ./public/coverage + ### PHPDoc + - name: Create directory public/docs + run: mkdir ./public/docs + - name: Install PhpDocumentor + run: wget https://phpdoc.org/phpDocumentor.phar && chmod +x phpDocumentor.phar + - name: Build Documentation + run: ./phpDocumentor.phar run -d ./src -t ./public/docs + + ### Deploy + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + if: github.ref == 'refs/heads/master' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml deleted file mode 100644 index a7850833a3..0000000000 --- a/.github/workflows/github-pages.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: GithHub Pages -on: - push: - tags: - - '*' - -jobs: - github-pages: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP, with composer and extensions - uses: shivammathur/setup-php@v2 - with: - php-version: 7.4 - coverage: none # remove xdebug - - - name: Build API documentation - run: | - curl -LO https://github.com/phpDocumentor/phpDocumentor/releases/download/v3.0.0/phpDocumentor.phar - php phpDocumentor.phar --directory src/ --target docs/api - - - name: Deploy to GithHub Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs/api diff --git a/.github/workflows/ci.yml b/.github/workflows/php.yml similarity index 56% rename from .github/workflows/ci.yml rename to .github/workflows/php.yml index d9b2596624..ed772171e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/php.yml @@ -1,25 +1,29 @@ name: CI on: - - push - - pull_request + pull_request: + push: + branches: + - master jobs: test: runs-on: ubuntu-latest strategy: matrix: php-version: - - "7.1" + - "7.1.3" - "7.2" - "7.3" - "7.4" - "8.0" - "8.1" + - "8.2" + - "8.3" name: PHP ${{ matrix.php-version }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 @@ -33,12 +37,16 @@ jobs: run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- + - name: Remove lock for old EOL PHP versions + if: matrix.php-version == '7.1.3' || matrix.php-version == '7.2' || matrix.php-version == '7.3' || matrix.php-version == '7.4' + run: rm composer.lock && composer config platform.php ${{ matrix.php-version }} + - name: Install dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader @@ -58,12 +66,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib coverage: none tools: cs2pr @@ -73,31 +81,61 @@ jobs: run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - - name: Composer Install - run: composer global require friendsofphp/php-cs-fixer - - - name: Add environment path - run: export PATH="$PATH:$HOME/.composer/vendor/bin" + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Code style with PHP-CS-Fixer - run: php-cs-fixer fix --dry-run --diff + run: ./vendor/bin/php-cs-fixer fix --dry-run --diff + + phpstan: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 8.0 + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: none + tools: cs2pr + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Static analysis with PHPStan + run: ./vendor/bin/phpstan analyse coverage: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.1 extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib coverage: xdebug @@ -106,7 +144,7 @@ jobs: run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -115,8 +153,13 @@ jobs: - name: Install dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - - name: Coverage + - name: Run phpunit + run: ./vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover build/clover.xml + + - name: Upload coverage results to Coveralls + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - ./vendor/bin/phpunit --coverage-clover coverage-clover.xml - curl -LO https://scrutinizer-ci.com/ocular.phar - php ocular.phar code-coverage:upload --format=php-clover coverage-clover.xml + wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar + chmod +x php-coveralls.phar + php php-coveralls.phar --coverage_clover=build/clover.xml --json_path=build/coveralls-upload.json -vvv diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 548bf23d0c..0000000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: 'Close stale issues and PRs' -on: - schedule: - - cron: '30 1 * * *' - -permissions: - issues: write - pull-requests: write - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v5 - with: - days-before-stale: 90 - days-before-close: 60 - exempt-issue-labels: 'pinned,security' - exempt-pr-labels: 'pinned,security' - stale-issue-message: 'This issue has been automatically marked as stale because it has not had -recent activity. It will be closed if no further activity occurs. - -If this is still an issue for you, please try to help by debugging it -further and sharing your results. - -Thank you for your contributions.' - stale-pr-message: 'This PR has been automatically marked as stale because it has not had -recent activity. It will be closed if no further activity occurs. - -If this is still an issue for you, please try to complete the PR by adding tests and making sure that the CI is green. - -Thank you for your contributions.' diff --git a/.gitignore b/.gitignore index f5f6f7eb81..3695438057 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ Desktop.ini _build /build phpunit.xml -composer.lock composer.phar vendor /report @@ -22,3 +21,4 @@ phpword.ini /nbproject /.php_cs.cache /.phpunit.result.cache +/public \ No newline at end of file diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index d1fe59618d..0000000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,35 +0,0 @@ -build: - nodes: - analysis: - tests: - override: [php-scrutinizer-run] - environment: - php: - version: '7.4' - pecl_extensions: - - zip - -filter: - excluded_paths: [ 'vendor/*', 'tests/*', 'samples/*', 'src/PhpWord/Shared/PCLZip/*' ] - -before_commands: - - "composer install --prefer-source --dev" - -tools: - php_code_sniffer: - enabled: true - config: - standard: PSR2 - php_mess_detector: - enabled: true - config: - ruleset: phpmd.xml.dist - external_code_coverage: - enabled: false - timeout: 1200 - php_cpd: true - # php_sim: # Temporarily disabled to allow focus on things other than duplicates - # min_mass: 40 - php_pdepend: true - php_analyzer: true - sensiolabs_security_checker: true diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 6e140f555b..0000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,630 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. -This project adheres to [Semantic Versioning](http://semver.org/). - -## [0.18.3](https://github.com/PHPOffice/PHPWord/tree/0.18.3) (2022-02-17) - -[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/0.18.2...0.18.3) - -### Bug fixes -- PHP 8.1 compatibility - -## [0.18.2](https://github.com/PHPOffice/PHPWord/tree/0.18.2) (2021-06-04) - -[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/0.18.1...0.18.2) - -### Bug fixes -- when adding image to relationship first check that the generated RID is actually unique [\#2063](https://github.com/PHPOffice/PHPWord/pull/2063) ([tpv-ebben](https://github.com/tpv-ebben)) -- Update chart, don't write 'c:overlap' if grouping is 'clustered' [\#2052](https://github.com/PHPOffice/PHPWord/pull/2052) ([dfsd534](https://github.com/dfsd534)) -- Update Html parser to accept line-height:normal [\#2041](https://github.com/PHPOffice/PHPWord/pull/2041) ([joelgo](https://github.com/joelgo)) -- Fix image border in Word2007 Writer for LibreOffice 7 [\#2021](https://github.com/PHPOffice/PHPWord/pull/2021) ([kamilmmach](https://github.com/kamilmmach)) - -### Miscellaneous -- Corrected namespace for Language class in docs. [\#2087](https://github.com/PHPOffice/PHPWord/pull/2087) ([MegaChriz](https://github.com/MegaChriz)) -- Added support for Garamond font [\#2078](https://github.com/PHPOffice/PHPWord/pull/2078) ([artemkolotilkin](https://github.com/artemkolotilkin)) -- Add BorderStyle for Cell Style to documentation [\#2090](https://github.com/PHPOffice/PHPWord/pull/2090) ([DShkrabak](https://github.com/DShkrabak)) - -## [0.18.1](https://github.com/PHPOffice/PHPWord/tree/0.18.1) (2021-03-08) - -[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/0.18.0...0.18.1) - -### Bug fixes -- Fix BC break in #1946. This package does not replace laminas/laminas-zendframework-bridge [\#2032](https://github.com/PHPOffice/PHPWord/pull/2032) ([mussbach](https://github.com/mussbach)) - -## [0.18.0](https://github.com/PHPOffice/PHPWord/tree/0.18.0) (2021-02-12) - -[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/0.17.0...0.18.0) - -### Enhancements -- Add support for charts in template processor [\#2012](https://github.com/PHPOffice/PHPWord/pull/2012) ([dbarzin](https://github.com/dbarzin)) -- add/setting page element border style. [\#1986](https://github.com/PHPOffice/PHPWord/pull/1986) ([emnabs](https://github.com/emnabs)) -- allow to use customized pdf library [\#1983](https://github.com/PHPOffice/PHPWord/pull/1983) ([SailorMax](https://github.com/SailorMax)) -- feat: Update addHtml to handle style inheritance [\#1965](https://github.com/PHPOffice/PHPWord/pull/1965) ([Julien1138](https://github.com/Julien1138)) -- Add parsing of Shape node values [\#1924](https://github.com/PHPOffice/PHPWord/pull/1924) ([sven-ahrens](https://github.com/sven-ahrens)) -- Allow to redefine TCPDF object [\#1907](https://github.com/PHPOffice/PHPWord/pull/1907) ([SailorMax](https://github.com/SailorMax)) -- Enhancements to addHTML parser [\#1902](https://github.com/PHPOffice/PHPWord/pull/1902) ([lubosdz](https://github.com/lubosdz)) -- Make Default Paper Configurable [\#1851](https://github.com/PHPOffice/PHPWord/pull/1851) ([oleibman](https://github.com/oleibman)) -- Implement various missing features for the ODT writer [\#1796](https://github.com/PHPOffice/PHPWord/pull/1796) ([oleibman](https://github.com/oleibman)) -- Added support for "cloudConvert" images [\#1794](https://github.com/PHPOffice/PHPWord/pull/1794) ([ErnestStaug](https://github.com/ErnestStaug)) -- Add support for several features for the RTF writer [\#1775](https://github.com/PHPOffice/PHPWord/pull/1775) ([oleibman](https://github.com/oleibman)) -- Add font style for Field elements [\#1774](https://github.com/PHPOffice/PHPWord/pull/1774) ([oleibman](https://github.com/oleibman)) -- Add support for ListItemRun in HTML writer [\#1766](https://github.com/PHPOffice/PHPWord/pull/1766) ([stefan-91](https://github.com/stefan-91)) -- Improvements in RTF writer [\#1755](https://github.com/PHPOffice/PHPWord/pull/1755) ([oleibman](https://github.com/oleibman)) -- Allow a closure to be passed with image replacement tags [\#1716](https://github.com/PHPOffice/PHPWord/pull/1716) ([mbardelmeijer](https://github.com/mbardelmeijer)) -- Add Option for Dynamic Chart Legend Position [\#1699](https://github.com/PHPOffice/PHPWord/pull/1699) ([Stephan212](https://github.com/Stephan212)) -- Add parsing of HTML checkbox input field [\#1832](https://github.com/PHPOffice/PHPWord/pull/1832) ([Matze2010](https://github.com/Matze2010)) - -### Bug fixes -- Fix image stroke in libreoffice 7.x [\#1992](https://github.com/PHPOffice/PHPWord/pull/1992) ([Adizbek](https://github.com/Adizbek)) -- Fix deprecated warning for non-hexadecimal number [\#1988](https://github.com/PHPOffice/PHPWord/pull/1988) ([Ciki](https://github.com/Ciki)) -- Fix limit not taken into account when adding image in template [\#1967](https://github.com/PHPOffice/PHPWord/pull/1967) ([jsochor](https://github.com/jsochor)) -- Add null check when setComplexValue is not found [\#1936](https://github.com/PHPOffice/PHPWord/pull/1936) ([YannikFirre](https://github.com/YannikFirre)) -- Some document have non-standard locale code [\#1824](https://github.com/PHPOffice/PHPWord/pull/1824) ([ErnestStaug](https://github.com/ErnestStaug)) -- Fixes PHPDoc @param and @return types for several Converter methods [\#1818](https://github.com/PHPOffice/PHPWord/pull/1818) ([caugner](https://github.com/caugner)) -- Update the regexp to avoid catastrophic backtracking [\#1809](https://github.com/PHPOffice/PHPWord/pull/1809) ([juzser](https://github.com/juzser)) -- Fix PHPUnit tests on develop branch [\#1771](https://github.com/PHPOffice/PHPWord/pull/1771) ([mdupont](https://github.com/mdupont)) -- TemplateProcessor cloneBlock wrongly clones images [\#1763](https://github.com/PHPOffice/PHPWord/pull/1763) ([alarai](https://github.com/alarai)) - -### Miscellaneous -- Compatibility with PHP 7.4, PHP 8.0 and migrate to Laminas Escaper [\#1946](https://github.com/PHPOffice/PHPWord/pull/1946) ([liborm85](https://github.com/liborm85)) -- Remove legacy PHPOffice/Common package, fix PHP 8.0 compatibility [\#1996](https://github.com/PHPOffice/PHPWord/pull/1996) ([liborm85](https://github.com/liborm85)) -- Improve Word2007 Test Coverage [\#1858](https://github.com/PHPOffice/PHPWord/pull/1858) ([oleibman](https://github.com/oleibman)) -- Fix typo in docs. Update templates-processing.rst [\#1952](https://github.com/PHPOffice/PHPWord/pull/1952) ([mnvx](https://github.com/mnvx)) -- Fix documentation and method name for FootnoteProperties [\#1776](https://github.com/PHPOffice/PHPWord/pull/1776) ([mdupont](https://github.com/mdupont)) -- fix: documentation about paragraph indentation [\#1764](https://github.com/PHPOffice/PHPWord/pull/1764) ([mdupont](https://github.com/mdupont)) -- Update templates-processing.rst [\#1745](https://github.com/PHPOffice/PHPWord/pull/1745) ([igronus](https://github.com/igronus)) -- Unused variables $rows, $cols in sample [\#1877](https://github.com/PHPOffice/PHPWord/pull/1877) ([ThanasisMpalatsoukas](https://github.com/ThanasisMpalatsoukas)) -- Add unit test for NumberingStyle [\#1744](https://github.com/PHPOffice/PHPWord/pull/1744) ([Manunchik](https://github.com/Manunchik)) -- Add unit test for PhpWord Settings [\#1743](https://github.com/PHPOffice/PHPWord/pull/1743) ([Manunchik](https://github.com/Manunchik)) -- Add unit test for Media elements [\#1742](https://github.com/PHPOffice/PHPWord/pull/1742) ([Manunchik](https://github.com/Manunchik)) -- Update templates processing docs [\#1729](https://github.com/PHPOffice/PHPWord/pull/1729) ([hcdias](https://github.com/hcdias)) - -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 -- Add getVariableCount method in TemplateProcessor. @nicoder #1272 -- Add setting Chart Title and Legend visibility @Tom-Magill #1433 -- Add ability to pass a Style object in Section constructor @ndench #1416 -- Add support for hidden text @Alexmg86 #1527 -- Add support for setting images in TemplateProcessor @SailorMax #1170 -- Add "Plain Text" type to SDT (Structured Document Tags) @morrisdj #1541 -- Added possibility to index variables inside cloned block in TemplateProcessor @JPBetley #817 -- Added possibility to replace variables inside cloned block with values in TemplateProcessor @DIDoS #1392 - -### Fixed -- Fix regex in `cloneBlock` function @nicoder #1269 -- HTML Title Writer loses text when Title contains a TextRun instead a string. @begnini #1436 -- Fix regex in fixBrokenMacros, make it less greedy @MuriloSo @brainwood @yurii-sio2 #1502 #1345 -- 240 twips are being added to line spacing, should not happen when using lineRule fixed @troosan #1509 #1505 -- Adding table layout to the generated HTML @aarangara #1441 -- Fix loading of Sharepoint document @Garrcomm #1498 -- RTF writer: Round getPageSizeW and getPageSizeH to avoid decimals @Patrick64 #1493 -- Fix parsing of Office 365 documents @Timanx #1485 -- 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 - -### Miscellaneous -- Get rid of duplicated code in TemplateProcessor @abcdmitry #1161 - -v0.15.0 (14 Jul 2018) ----------------------- -### Added -- Parsing of `align` HTML attribute - @troosan #1231 -- Parse formatting inside HTML lists - @troosan @samimussbach #1239 #945 #1215 #508 -- Parsing of CSS `direction` instruction, HTML `lang` attribute, formatting inside table cell - @troosan #1273 #1252 #1254 -- Add support for Track changes @Cip @troosan #354 #1262 -- Add support for fixed Table Layout @aoloe @ekopach @troosan #841 #1276 -- Add support for Cell Spacing @dox07 @troosan #1040 -- Add parsing of formatting inside lists @atomicalnet @troosan #594 -- Added support for Vertically Raised or Lowered Text (w:position) @anrikun @troosan #640 -- Add support for MACROBUTTON field @phryneas @troosan #1021 -- Add support for Hyphenation @Trainmaster #1282 (Document: `autoHyphenation`, `consecutiveHyphenLimit`, `hyphenationZone`, `doNotHyphenateCaps`, Paragraph: `suppressAutoHyphens`) -- Added support for Floating Table Positioning (tblpPr) @anrikun #639 -- Added support for Image text wrapping distance @troosan #1310 -- Added parsing of CSS line-height and text-indent in HTML reader @troosan #1316 -- Added the ability to enable gridlines and axislabels on charts @FrankMeyer #576 -- Add support for table indent (tblInd) @Trainmaster #1343 -- Added parsing of internal links in HTML reader @lalop #1336 -- Several improvements to charts @JAEK-S #1332 -- Add parsing of html image in base64 format @jgpATs2w #1382 -- Added Support for Indentation & Tabs on RTF Writer. @smaug1985 #1405 -- Allows decimal numbers in HTML line-height style @jgpATs2w #1413 - -### Fixed -- Fix reading of docx default style - @troosan #1238 -- Fix the size unit of when parsing html images - @troosan #1254 -- Fixed HTML parsing of nested lists - @troosan #1265 -- Save PNG alpha information when using remote images. @samsullivan #779 -- Fix parsing of `` tag. @troosan #1274 -- Bookmark are not writton as internal link in html writer @troosan #1263 -- It should be possible to add a Footnote in a ListItemRun @troosan #1287 #1287 -- Fix colspan and rowspan for tables in HTML Writer @mattbolt #1292 -- Fix parsing of Heading and Title formating @troosan @gthomas2 #465 -- Fix Dateformat typo, fix hours casing, add Month-Day-Year formats @ComputerTinker #591 -- Support reading of w:drawing for documents produced by word 2011+ @gthomas2 #464 #1324 -- Fix missing column width in ODText writer @potofcoffee #413 -- Disable entity loader before parsing XML to avoid XXE injection @Tom4t0 #1427 - -### Changed -- Remove zend-stdlib dependency @Trainmaster #1284 -- The default unit for `\PhpOffice\PhpWord\Style\Image` changed from `px` to `pt`. - -### Miscellaneous -- Drop GitHub pages, switch to coveralls for code coverage analysis @czosel #1360 - -v0.14.0 (29 Dec 2017) ----------------------- -This release fixes several bugs and adds some new features. -This version brings compatibility with PHP 7.0 & 7.1 - -### Added -- Possibility to control the footnote numbering - @troosan #1068 -- Image creation from string - @troosan #937 -- Introduced the `\PhpOffice\PhpWord\SimpleType\NumberFormat` simple type. - @troosan -- Support for ContextualSpacing - @postHawk #1088 -- Possiblity to hide spelling and/or grammatical errors - @troosan #542 -- Possiblity to set default document language as well as changing the language for each text element - @troosan #1108 -- Support for Comments - @troosan #1067 -- Support for paragraph textAlignment - @troosan #1165 -- Add support for HTML underline tag in addHtml - @zNightFalLz #1186 -- Add support for HTML
in addHtml - @anrikun @troosan #659 -- Allow to change cell width unit - guillaume-ro-fr #986 -- Allow to change the line height rule @troosan -- Implement PageBreak for odt writer @cookiekiller #863 #824 -- Allow to force an update of all fields on opening a document - @troosan #951 -- Allow adding a CheckBox in a TextRun - @irond #727 -- Add support for HTML img tag - @srggroup #934 -- Add support for password protection for docx - @mariahaubner #1019 - -### Fixed -- Loosen dependency to Zend -- Images are not being printed when generating PDF - @hubertinio #1074 #431 -- Fixed some PHP 7 warnings - @ likeuntomurphy #927 -- Fixed PHP 7.2 compatibility (renamed `Object` class names to `ObjectElement`) - @SailorMax #1185 -- Fixed Word 97 reader - @alsofronie @Benpxpx @mario-rivera #912 #920 #892 -- Fixed image loading over https - @troosan #988 -- Impossibility to set different even and odd page headers - @troosan #981 -- Fixed Word2007 reader where unnecessary paragraphs were being created - @donghaobo #1043 #620 -- Fixed Word2007 reader where margins were not being read correctly - @slowprog #885 #1008 -- Impossible to add element PreserveText in Section - @rvanlaak #452 -- Added missing options for numbering format - @troosan #1041 -- Fixed impossibility to set a different footer for first page - @ctrlaltca #1116, @aoloe #875 -- Fixed styles not being applied by HTML writer, better pdf output - @sarke #1047 #500 #1139 -- Fixed read docx error when document contains image from remote url - @FBnil #1173 #1176 -- Padded the $args array to remove error - @kaigoh #1150, @reformed #870 -- Fix incorrect image size between windows and mac - @bskrtich #874 -- Fix adding HTML table to document - @mogilvie @arivanbastos #324 -- Fix parsing on/off values (w:val="true|false|1|0|on|off") - @troosan #1221 #1219 -- Fix error on Empty Dropdown Entry - @ComputerTinker #592 - -### Deprecated -- PhpWord->getProtection(), get it from the settings instead PhpWord->getSettings()->getDocumentProtection(); - - - -v0.13.0 (31 July 2016) -------------------- -This release brings several improvements in `TemplateProcessor`, automatic output escaping feature for OOXML, ODF, HTML, and RTF (turned off, by default). -It also introduces constants for horizontal alignment options, and resolves some issues with PHP 7. -Manual installation feature has been dropped since the release. Please, use [Composer](https://getcomposer.org/) to install PHPWord. - -### Added -- Introduced the `\PhpOffice\PhpWord\SimpleType\Jc` simple type. - @RomanSyroeshko -- Introduced the `\PhpOffice\PhpWord\SimpleType\JcTable` simple type. - @RomanSyroeshko -- Introduced writer for the "Paragraph Alignment" element (see `\PhpOffice\PhpWord\Writer\Word2007\Element\ParagraphAlignment`). - @RomanSyroeshko -- Introduced writer for the "Table Alignment" element (see `\PhpOffice\PhpWord\Writer\Word2007\Element\TableAlignment`). - @RomanSyroeshko -- Supported indexed arrays in arguments of `TemplateProcessor::setValue()`. - @RomanSyroeshko #618 -- Introduced automatic output escaping for OOXML, ODF, HTML, and RTF. To turn the feature on use `phpword.ini` or `\PhpOffice\PhpWord\Settings`. - @RomanSyroeshko #483 -- Supported processing of headers and footers in `TemplateProcessor::applyXslStyleSheet()`. - @RomanSyroeshko #335 - -### Changed -- Improved error message for the case when `autoload.php` is not found. - @RomanSyroeshko #371 -- Renamed the `align` option of `NumberingLevel`, `Frame`, `Table`, and `Paragraph` styles into `alignment`. - @RomanSyroeshko -- Improved performance of `TemplateProcessor::setValue()`. - @kazitanvirahsan #614, #617 -- Fixed some HTML tags not rendering any output (p, header & table) - #257, #324 - @twmobius and @garethellis - -### Deprecated -- `getAlign` and `setAlign` methods of `NumberingLevel`, `Frame`, `Table`, and `Paragraph` styles. -Use the correspondent `getAlignment` and `setAlignment` methods instead. - @RomanSyroeshko -- `left`, `right`, and `justify` alignment options for paragraphs (now are mapped to `Jc::START`, `Jc::END`, and `Jc::BOTH`). - @RomanSyroeshko -- `left`, `right`, and `justify` alignment options for tables (now are mapped to `Jc::START`, `Jc::END`, and `Jc::CENTER`). - @RomanSyroeshko -- `TCPDF` due to its limited HTML support. Use `DomPDF` or `MPDF` writer instead. - @RomanSyroeshko #399 - -### Removed -- `\PhpOffice\PhpWord\Style\Alignment`. Style properties, which previously stored instances of this class, now deal with strings. -In each case set of available string values is defined by the correspondent simple type. - @RomanSyroeshko -- Manual installation support. Since the release we have dependencies on third party libraries, -so installation via ZIP-archive download is not an option anymore. To install PHPWord use [Composer](https://getcomposer.org/). - We also removed `\PhpOffice\PhpWord\Autoloader`, because the latter change made it completely useless. - Autoloaders provided by Composer are in use now (see `bootstrap.php`). - @RomanSyroeshko -- `\PhpOffice\PhpWord\Shared\Drawing` replaced by `\PhpOffice\Common\Drawing`. - @Progi1984 #658 -- `\PhpOffice\PhpWord\Shared\Font`. - @Progi1984 #658 -- `\PhpOffice\PhpWord\Shared\String` replaced by `\PhpOffice\Common\Text`. - @Progi1984 @RomanSyroeshko #658 -- `\PhpOffice\PhpWord\Shared\XMLReader` replaced by `\PhpOffice\Common\XMLReader`. - @Progi1984 #658 -- `\PhpOffice\PhpWord\Shared\XMLWriter` replaced by `\PhpOffice\Common\XMLWriter`. - @Progi1984 @RomanSyroeshko #658 -- `AbstractContainer::addMemoryImage()`. Use `AbstractContainer::addImage()` instead. - -### Fixed -- `Undefined property` error while reading MS-DOC documents. - @jaberu #610 -- Corrupted OOXML template issue in case when its names is broken immediately after `$` sign. -That case wasn't taken into account in implementation of `TemplateProcessor::fixBrokenMacros()`. - @RomanSyroeshko @d-damien #548 - - - -v0.12.1 (30 August 2015) ------------------------ -Maintenance release. This release is focused primarily on `TemplateProcessor`. - -### Changes -- Changed visibility of all private properties and methods of `TemplateProcessor` to `protected`. - @RomanSyroeshko #498 -- Improved performance of `TemplateProcessor::setValue()`. - @RomanSyroeshko @nicoSWD #513 - -### Bugfixes -- Fixed issue with "Access denied" message while opening `Sample_07_TemplateCloneRow.docx` and `Sample_23_TemplateBlock.docx` result files on Windows platform. - @RomanSyroeshko @AshSat #532 -- Fixed `PreserveText` element alignment in footer (see `Sample_12_HeaderFooter.php`). - @RomanSyroeshko @SSchwaiger #495 - - - -v0.12.0 (3 January 2015) ------------------------ -This release added form fields (textinput, checkbox, and dropdown), drawing shapes (arc, curve, line, polyline, rect, oval), and basic 2D chart (pie, doughnut, bar, line, area, scatter, radar) elements along with some new styles. Basic MsDoc reader is introduced. - -### Features -- Element: Ability to add drawing shapes (arc, curve, line, polyline, rect, oval) using new `Shape` element - @ivanlanin #123 -- Font: New `scale`, `spacing`, and `kerning` property of font style - @ivanlanin -- Paragraph: Added shading to the paragraph style for full width shading - @lrobert #264 -- RTF Writer: Support for sections, margins, and borders - @ivanlanin #249 -- Section: Ability to set paper size, e.g. A4, A3, and Legal - @ivanlanin #249 -- General: New `PhpWord::save()` method to encapsulate `IOFactory` - @ivanlanin -- General: New `Shared\Converter` static class - @ivanlanin -- Chart: Basic 2D chart (pie, doughnut, bar, line, area, scatter, radar) - @ivanlanin #278 -- Chart: 3D charts and ability to set width and height - @ivanlanin -- FormField: Ability to add textinput, checkbox, and dropdown form elements - @ivanlanin #266 -- Setting: Ability to define document protection (readOnly, comments, trackedChanges, forms) - @ivanlanin -- Setting: Ability to remove [Compatibility Mode] text in the MS Word title bar - @ivanlanin -- SDT: Ability to add structured document tag elements (comboBox, dropDownList, date) - @ivanlanin -- Paragraph: Support for paragraph with borders - @ivanlanin #294 -- Word2007 Writer : Support for RTL - @Progi1984 #331 -- MsDOC Reader: Basic MsDOC Reader - @Progi1984 #23, #287 -- "absolute" horizontal and vertical positioning of Frame - @basjan #302 -- Add new-page function for PDF generation. For multiple PDF-backends - @chc88 #426 -- Report style options enumerated when style unknown - @h6w - -### Bugfixes -- Fix rare PclZip/realpath/PHP version problem - @andrew-kzoo #261 -- `addHTML` encoding and ampersand fixes for PHP 5.3 - @bskrtich #270 -- Page breaks on titles and tables - @ivanlanin #274 -- Table inside vertical border does not rendered properly - @ivanlanin #280 -- `add` of container should be case insensitive, e.g. `addToc` should be accepted, not only `addTOC` - @ivanlanin #294 -- Fix specific borders (and margins) were not written correctly in word2007 writer - @pscheit #327 -- "HTML is not a valid writer" exception while running "Sample_36_RTL.php" - @RomanSyroeshko #340 -- "addShape()" magic method in AbstractContainer is mistakenly named as "addObject()" - @GMTA #356 -- `Element\Section::setPageSizeW()` and `Element\Section::setPageSizeH()` were mentioned in the docs but not implemented. -- Special Characters (ampersand) in Title break docx output - @RomanSyroeshko #401 -- `` tag is closed with `` tag: - @franzholz #438 - -### Deprecated -- `Element\Link::getTarget()` replaced by `Element\Link::getSource()` -- `Element\Section::getSettings()` and `Element\Section::setSettings()` replaced by `Element\Section::getStyle()` and `Element\Section::setStyle()` -- `Shared\Drawing` and `Shared\Font` merged into `Shared\Converter` -- `DocumentProperties` replaced by `Metadata\DocInfo` -- `Template` replaced by `TemplateProcessor` -- `PhpWord->loadTemplate($filename)` - -### Miscellaneous -- Docs: Add known issue on `README` about requirement for temporary folder to be writable and update `samples/index.php` for this requirement check - @ivanlanin #238 -- Docs: Correct elements.rst about Line - @chrissharkman #292 -- PclZip: Remove temporary file after used - @andrew-kzoo #265 -- Autoloader: Add the ability to set the autoloader options - @bskrtich #267 -- Element: Refactor elements to move set relation Id from container to element - @ivanlanin -- Introduced CreateTemporaryFileException, CopyFileException - @RomanSyroeshko -- Settings: added method to set user defined temporary directory - @RomanSyroeshko #310 -- Renamed `Template` into `TemplateProcessor` - @RomanSyroeshko #216 -- Reverted #51. All text escaping must be performed out of the library - @RomanSyroeshko #51 - - - -v0.11.1 (2 June 2014) --------------------- -This is an immediate bugfix release for HTML reader. - -- HTML Reader: `

` and header tags puts no output - @canyildiz @ivanlanin #257 - - - -v0.11.0 (1 June 2014) --------------------- -This release marked the change of PHPWord license from LGPL 2.1 to LGPL 3. Four new elements were added: TextBox, ListItemRun, Field, and Line. Relative and absolute positioning for images and textboxes were added. Writer classes were refactored into parts, elements, and styles. ODT and RTF features were enhanced. Ability to add elements to PHPWord object via HTML were implemented. RTF and HTML reader were initiated. - -### Features -- Image: Ability to define relative and absolute positioning - @basjan #217 -- Footer: Conform footer with header by adding firstPage, evenPage and by inheritance - @basjan @ivanlanin #219 -- Element: New `TextBox` element - @basjan @ivanlanin #228, #229, #231 -- HTML: Ability to add elements to PHPWord object via html - @basjan #231 -- Element: New `ListItemRun` element that can add a list item with inline formatting like a textrun - @basjan #235 -- Table: Ability to add table inside a cell (nested table) - @ivanlanin #149 -- RTF Writer: UTF8 support for RTF: Internal UTF8 text is converted to Unicode before writing - @ivanlanin #158 -- Table: Ability to define table width (in percent and twip) and position - @ivanlanin #237 -- RTF Writer: Ability to add links and page breaks in RTF - @ivanlanin #196 -- ListItemRun: Remove fontStyle parameter because ListItemRun is inherited from TextRun and TextRun doesn't have fontStyle - @ivanlanin -- Config: Ability to use a config file to store various common settings - @ivanlanin #200 -- ODT Writer: Enable inline font style in TextRun - @ivanlanin -- ODT Writer: Enable underline, strike/doublestrike, smallcaps/allcaps, superscript/subscript font style - @ivanlanin -- ODT Writer: Enable section and column - @ivanlanin -- PDF Writer: Add TCPDF and mPDF as optional PDF renderer library - @ivanlanin -- ODT Writer: Enable title element and custom document properties - @ivanlanin -- ODT Reader: Ability to read standard and custom document properties - @ivanlanin -- Word2007 Writer: Enable the missing custom document properties writer - @ivanlanin -- Image: Enable "image float left" - @ivanlanin #244 -- RTF Writer: Ability to write document properties - @ivanlanin -- RTF Writer: Ability to write image - @ivanlanin -- Element: New `Field` element - @basjan #251 -- RTF Reader: Basic RTF reader - @ivanlanin #72, #252 -- Element: New `Line` element - @basjan #253 -- Title: Ability to apply numbering in heading - @ivanlanin #193 -- HTML Reader: Basic HTML reader - @ivanlanin #80, #254 -- RTF Writer: Basic table writing - @ivanlanin #245 - -### Bugfixes -- Header: All images added to the second header were assigned to the first header - @basjan #222 -- Conversion: Fix conversion from cm to pixel, pixel to cm, and pixel to point - @basjan #233, #234 -- PageBreak: Page break adds new line in the beginning of the new page - @ivanlanin #150 -- Image: `marginLeft` and `marginTop` cannot accept float value - @ivanlanin #248 -- Title: Orphan `w:fldChar` caused OpenOffice to crash when opening DOCX - @ivanlanin #236 - -### Deprecated -- Static classes `Footnotes`, `Endnotes`, and `TOC` -- `Writer\Word2007\Part`: `Numbering::writeNumbering()`, `Settings::writeSettings()`, `WebSettings::writeWebSettings()`, `ContentTypes::writeContentTypes()`, `Styles::writeStyles()`, `Document::writeDocument()` all changed into `write()` -- `Writer\Word2007\Part\DocProps`: Split into `Writer\Word2007\Part\DocPropsCore` and `Writer\Word2007\Part\DocPropsApp` -- `Element\Title::getBookmarkId()` replaced by `Element\Title::getRelationId()` -- `Writer\HTML::writeDocument`: Replaced by `Writer\HTML::getContent` - -### Miscellaneous -- License: Change the project license from LGPL 2.1 into LGPL 3.0 - #211 -- Word2007 Writer: New `Style\Image` class - @ivanlanin -- Refactor: Replace static classes `Footnotes`, `Endnotes`, and `TOC` with `Collections` - @ivanlanin #206 -- QA: Reactivate `phpcpd` and `phpmd` on Travis - @ivanlanin -- Refactor: PHPMD recommendation: Change all `get...` method that returns `boolean` into `is...` or `has...` - @ivanlanin -- Docs: Create gh-pages branch for API documentation - @Progi1984 #154 -- QA: Add `.scrutinizer.yml` and include `composer.lock` for preparation to Scrutinizer - @ivanlanin #186 -- Writer: Refactor writer parts using composite pattern - @ivanlanin -- Docs: Show code quality and test code coverage badge on README -- Style: Change behaviour of `set...` function of boolean properties; when none is defined, assumed true - @ivanlanin -- Shared: Unify PHP ZipArchive and PCLZip features into PhpWord ZipArchive - @ivanlanin -- Docs: Create VERSION file - @ivanlanin -- QA: Improve dan update requirement check in `samples` folder - @ivanlanin - - - -v0.10.1 (21 May 2014) --------------------- -This is a bugfix release for `php-zip` requirement in Composer. - -- Change Composer requirements for php-zip from `require` to `suggest` - @bskrtich #246 - - - -v0.10.0 (4 May 2014) -------------------- -This release marked heavy refactorings on internal code structure with the creation of some abstract classes to reduce code duplication. `Element` subnamespace is introduced in this release to replace `Section`. Word2007 reader capability is greatly enhanced. Endnote is introduced. List numbering is now customizable. Basic HTML and PDF writing support is enabled. Basic ODText reader is introduced. - -### Features -- Image: Get image dimensions without EXIF extension - @andrew-kzoo #184 -- Table: Add `tblGrid` element for Libre/Open Office table sizing - @gianis6 #183 -- Footnote: Ability to insert textbreak in footnote `$footnote->addTextBreak()` - @ivanlanin -- Footnote: Ability to style footnote reference mark by using `FootnoteReference` style - @ivanlanin -- Font: Add `bgColor` to font style to define background using HEX color - @jcarignan #168 -- Table: Add `exactHeight` to row style to define whether row height should be exact or atLeast - @jcarignan #168 -- Element: New `CheckBox` element for sections and table cells - @ozilion #156 -- Settings: Ability to use PCLZip as alternative to ZipArchive - @bskrtich @ivanlanin #106, #140, #185 -- Template: Ability to find & replace variables in headers & footers - @dgudgeon #190 -- Template: Ability to clone & delete block of text using `cloneBlock` and `deleteBlock` - @diego-vieira #191 -- TOC: Ability to have two or more TOC in one document and to set min and max depth for TOC - @Pyreweb #189 -- Table: Ability to add footnote in table cell - @ivanlanin #187 -- Footnote: Ability to add image in footnote - @ivanlanin #187 -- ListItem: Ability to add list item in header/footer - @ivanlanin #187 -- CheckBox: Ability to add checkbox in header/footer - @ivanlanin #187 -- Link: Ability to add link in header/footer - @ivanlanin #187 -- Object: Ability to add object in header, footer, textrun, and footnote - @ivanlanin #187 -- Media: Add `Media::resetElements()` to reset all media data - @juzi #19 -- General: Add `Style::resetStyles()` - @ivanlanin #187 -- DOCX Reader: Ability to read header, footer, footnotes, link, preservetext, textbreak, pagebreak, table, list, image, and title - @ivanlanin -- Endnote: Ability to add endnotes - @ivanlanin -- ListItem: Ability to create custom list and reset list number - @ivanlanin #10, #198 -- ODT Writer: Basic table writing support - @ivanlanin -- Image: Keep image aspect ratio if only 1 dimension styled - @japonicus #194 -- HTML Writer: Basic HTML writer: text, textrun, link, title, textbreak, table, image (as Base64), footnote, endnote - @ivanlanin #203, #67, #147 -- PDF Writer: Basic PDF writer using DomPDF: All HTML element except image - @ivanlanin #68 -- DOCX Writer: Change `docProps/app.xml` `Application` to `PHPWord` - @ivanlanin -- DOCX Writer: Create `word/settings.xml` and `word/webSettings.xml` dynamically - @ivanlanin -- ODT Writer: Basic image writing - @ivanlanin -- ODT Writer: Link writing - @ivanlanin -- ODT Reader: Basic ODText Reader - @ivanlanin #71 -- Section: Ability to define gutter and line numbering - @ivanlanin -- Font: Small caps, all caps, and double strikethrough - @ivanlanin #151 -- Settings: Ability to use measurement unit other than twips with `setMeasurementUnit` - @ivanlanin #199 -- Style: Remove `bgColor` from `Font`, `Table`, and `Cell` and put it into the new `Shading` style - @ivanlanin -- Style: New `Indentation` and `Spacing` style - @ivanlanin -- Paragraph: Ability to define first line and right indentation - @ivanlanin - -### Bugfixes -- Footnote: Footnote content doesn't show footnote reference number - @ivanlanin #170 -- Documentation: Error in a function - @theBeerNut #195 - -### Deprecated -- `createTextRun` replaced by `addTextRun` -- `createFootnote` replaced by `addFootnote` -- `createHeader` replaced by `addHeader` -- `createFooter` replaced by `addFooter` -- `createSection` replaced by `addSection` -- `Element\Footnote::getReferenceId` replaced by `Element\AbstractElement::getRelationId` -- `Element\Footnote::setReferenceId` replaced by `Element\AbstractElement::setRelationId` -- `Footnote::addFootnoteLinkElement` replaced by `Media::addElement` -- `Footnote::getFootnoteLinkElements` replaced by `Media::getElements` -- All current methods on `Media` -- `Element\Link::getLinkSrc` replaced by `Element\Link::getTarget` -- `Element\Link::getLinkName` replaced by `Element\Link::getText` -- `Style\Cell::getDefaultBorderColor` - -### Miscellaneous -- Documentation: Simplify page level docblock - @ivanlanin #179 -- Writer: Refactor writer classes and create a new `Write\AbstractWriter` abstract class - @ivanlanin #160 -- General: Refactor folders: `Element` and `Exception` - @ivanlanin #187 -- General: Remove legacy `HashTable` and `Shared\ZipStreamWrapper` and all related properties/methods - @ivanlanin #187 -- Element: New `AbstractElement` abstract class - @ivanlanin #187 -- Media: Refactor media class to use one method for all docPart (section, header, footer, footnote) - @ivanlanin #187 -- General: Remove underscore prefix from all private properties name - @ivanlanin #187 -- General: Move Section `Settings` to `Style\Section` - @ivanlanin #187 -- General: Give `Abstract` prefix and `Interface` suffix for all abstract classes and interfaces as per [PHP-FIG recommendation](https://github.com/php-fig/fig-standards/blob/master/bylaws/002-psr-naming-conventions.md) - @ivanlanin #187 -- Style: New `Style\AbstractStyle` abstract class - @ivanlanin #187 -- Writer: New 'ODText\Base` class - @ivanlanin #187 -- General: Rename `Footnote` to `Footnotes` to reflect the nature of collection - @ivanlanin -- General: Add some unit tests for Shared & Element (100%!) - @Progi1984 -- Test: Add some samples and tests for image wrapping style - @brunocasado #59 -- Refactor: Remove Style\Tabs - @ivanlanin -- Refactor: Apply composite pattern for writers - @ivanlanin -- Refactor: Split `AbstractContainer` from `AbstractElement` - @ivanlanin -- Refactor: Apply composite pattern for Word2007 reader - @ivanlanin - - - -v0.9.1 (27 Mar 2014) -------------------- -This is a bugfix release for PSR-4 compatibility. - -- Fixed PSR-4 composer autoloader - @AntonTyutin - - - -v0.9.0 (26 Mar 2014) -------------------- -This release marked the transformation to namespaces (PHP 5.3+). - -### Features -- Image: Ability to use remote or GD images using `addImage()` on sections, headers, footer, cells, and textruns - @ivanlanin -- Header: Ability to use remote or GD images using `addWatermark()` - @ivanlanin - -### Bugfixes -- Preserve text doesn't render correctly when the text is not the first word, e.g. 'Page {PAGE}' - @ivanlanin - -### Miscellaneous -- Move documentation to [Read The Docs](http://phpword.readthedocs.org/en/develop/) - @Progi1984 @ivanlanin #82 -- Reorganize and redesign samples folder - @ivanlanin #137 -- Use `PhpOffice\PhpWord` namespace for PSR compliance - @RomanSyroeshko @gabrielbull #159, #58 -- Restructure folders and change folder name `Classes` to `src` and `Tests` to `test` for PSR compliance - @RomanSyroeshko @gabrielbull -- Compliance to phpDocumentor - @ivanlanin -- Merge Style\TableFull into Style\Table. Style\TableFull is deprecated - @ivanlanin #160 -- Merge Section\MemoryImage into Section\Image. Section\Image is deprecated - @ivanlanin #160 - - - -v0.8.1 (17 Mar 2014) -------------------- -This is a bugfix release for image detection functionality. - -- Added fallback for computers that do not have exif_imagetype - @bskrtich, @gabrielbull - - - -v0.8.0 (15 Mar 2014) -------------------- -This release merged a lot of improvements from the community. Unit tests introduced in this release and has reached 90% code coverage. - -### Features -- Template: Permit to save a template generated as a file (PHPWord_Template::saveAs()) - @RomanSyroeshko #56, #57 -- Word2007: Support sections page numbering - @gabrielbull -- Word2007: Added line height methods to mirror the line height settings in Word in the paragraph styling - @gabrielbull -- Word2007: Added support for page header & page footer height - @JillElaine #5 -- General: Add ability to manage line breaks after image insertion - @bskrtich #6, #66, #84 -- Template: Ability to limit number of replacements performed by setValue() method of Template class - @RomanSyroeshko #52, #53, #85 -- Table row: Repeat as header row & allow row to break across pages - @ivanlanin #48, #86 -- Table: Table width in percentage - @ivanlanin #48, #86 -- Font: Superscript and subscript - @ivanlanin #48, #86 -- Paragraph: Hanging paragraph - @ivanlanin #48, #86 -- Section: Multicolumn and section break - @ivanlanin #48, #86 -- Template: Ability to apply XSL style sheet to Template - @RomanSyroeshko #46, #47, #83 -- General: PHPWord_Shared_Font::pointSizeToTwips() converter - @ivanlanin #87 -- Paragraph: Ability to define normal paragraph style with PHPWord::setNormalStyle() - @ivanlanin #87 -- Paragraph: Ability to define parent style (basedOn) and style for following paragraph (next) - @ivanlanin #87 -- Clone table rows on the fly when using a template document - @jeroenmoors #44, #88 -- Initial addition of basic footnote support - @deds #16 -- Paragraph: Ability to define paragraph pagination: widow control, keep next, keep lines, and page break before - @ivanlanin #92 -- General: PHPWord_Style_Font refactoring - @ivanlanin #93 -- Font: Use points instead of halfpoints internally. Conversion to halfpoints done during XML Writing. - @ivanlanin #93 -- Paragraph: setTabs() function - @ivanlanin #92 -- General: Basic support for TextRun on ODT and RTF - @ivanlanin #99 -- Reader: Basic Reader for Word2007 - @ivanlanin #104 -- TextRun: Allow Text Break in Text Run - @bskrtich #109 -- General: Support for East Asian fontstyle - @jhfangying #111, #118 -- Image: Use exif_imagetype to check image format instead of extension name - @gabrielbull #114 -- General: Setting for XMLWriter Compatibility option - @bskrtich #103 -- MemoryImage: Allow remote image when allow_url_open = on - @ivanlanin #122 -- TextBreak: Allow font and paragraph style for text break - @ivanlanin #18 - -### Bugfixes -- Fixed bug with cell styling - @gabrielbull -- Fixed bug list items inside of cells - @gabrielbull -- Adding a value that contains "&" in a template breaks it - @SiebelsTim #51 -- Example in README.md is broken - @Progi1984 #89 -- General: PHPWord_Shared_Drawing::centimetersToPixels() conversion - @ivanlanin #94 -- Footnote: Corrupt DOCX reported by MS Word when sections > 1 and not every sections have footnote - @ivanlanin #125 - -### Miscellaneous -- UnitTests - @Progi1984 - - - -v0.7.0 (28 Jan 2014) -------------------- -This is the first release after a long development hiatus in [CodePlex](https://phpword.codeplex.com/). This release initialized ODT and RTF Writer, along with some other new features for the existing Word2007 Writer, e.g. tab, multiple header, rowspan and colspan. [Composer](https://packagist.org/packages/phpoffice/phpword) and [Travis](https://travis-ci.org/PHPOffice/PHPWord) were added. - -### Features -- Implement RTF Writer - @Progi1984 #1 -- Implement ODT Writer - @Progi1984 #2 -- Word2007: Add rowspan and colspan to cells - @kaystrobach -- Word2007: Support for tab stops - @RLovelett -- Word2007: Support Multiple headers - @RLovelett -- Word2007: Wrapping Styles to Images - @gabrielbull -- Added support for image wrapping style - @gabrielbull - -### Bugfixes -- "Warning: Invalid error type specified in ...\PHPWord.php on line 226" is thrown when the specified template file is not found - @RomanSyroeshko #32 -- PHPWord_Shared_String.IsUTF8 returns FALSE for Cyrillic UTF-8 input - @RomanSyroeshko #34 -- Temporary files naming logic in PHPWord_Template can lead to a collision - @RomanSyroeshko #38 - -### Miscellaneous -- Add superscript/subscript styling in Excel2007 Writer - @MarkBaker -- add indentation support to paragraphs - @deds -- Support for Composer - @Progi1984 #27 -- Basic CI with Travis - @Progi1984 -- Added PHPWord_Exception and exception when could not copy the template - @Progi1984 -- IMPROVED: Moved examples out of Classes directory - @Progi1984 -- IMPROVED: Advanced string replace in setValue for Template - @Esmeraldo [#49](http://phpword.codeplex.com/workitem/49) diff --git a/README.md b/README.md index 3d84ad74f7..96dd989970 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,15 @@ # ![PHPWord](https://rawgit.com/PHPOffice/PHPWord/develop/docs/images/phpword.svg "PHPWord") -Master: -[![Latest Stable Version](https://poser.pugx.org/phpoffice/phpword/v/stable.png)](https://packagist.org/packages/phpoffice/phpword) -[![Build Status](https://travis-ci.org/PHPOffice/PHPWord.svg?branch=master)](https://travis-ci.org/PHPOffice/PHPWord) -[![Code Quality](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/) +[![Latest Stable Version](https://poser.pugx.org/phpoffice/phpword/v)](https://packagist.org/packages/phpoffice/phpword) [![Coverage Status](https://coveralls.io/repos/github/PHPOffice/PHPWord/badge.svg?branch=master)](https://coveralls.io/github/PHPOffice/PHPWord?branch=master) -[![Total Downloads](https://poser.pugx.org/phpoffice/phpword/downloads.png)](https://packagist.org/packages/phpoffice/phpword) -[![License](https://poser.pugx.org/phpoffice/phpword/license.png)](https://packagist.org/packages/phpoffice/phpword) -[![Join the chat at https://gitter.im/PHPOffice/PHPWord](https://img.shields.io/badge/GITTER-join%20chat-green.svg)](https://gitter.im/PHPOffice/PHPWord) +[![Total Downloads](https://poser.pugx.org/phpoffice/phpword/downloads)](https://packagist.org/packages/phpoffice/phpword) +[![License](https://poser.pugx.org/phpoffice/phpword/license)](https://packagist.org/packages/phpoffice/phpword) -Develop: -[![Latest Development Version](https://img.shields.io/badge/unstable-dev--develop-orange.svg)](https://packagist.org/packages/phpoffice/phpword#dev-develop) -[![Build Status](https://travis-ci.org/PHPOffice/PHPWord.svg?branch=develop)](https://travis-ci.org/PHPOffice/PHPWord/branches) -[![Code Quality](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/PHPOffice/PHPWord/?branch=develop) -[![Coverage Status](https://coveralls.io/repos/github/PHPOffice/PHPWord/badge.svg?branch=develop)](https://coveralls.io/github/PHPOffice/PHPWord?branch=develop) +Branch Master : [![PHPWord](https://github.com/PHPOffice/PHPWord/actions/workflows/php.yml/badge.svg?branch=master)](https://github.com/PHPOffice/PHPWord/actions/workflows/php.yml) 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](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://github.com/PHPOffice/PHPWord/actions) and unit testing. You can learn more about PHPWord by reading the [Developers' Documentation](https://phpoffice.github.io/PHPWord/). If you have any questions, please ask on [StackOverFlow](https://stackoverflow.com/questions/tagged/phpword) @@ -28,7 +20,7 @@ Read more about PHPWord: - [Installation](#installation) - [Getting started](#getting-started) - [Contributing](#contributing) -- [Developers' Documentation](http://phpword.readthedocs.org/) +- [Developers' Documentation](https://phpoffice.github.io/PHPWord/) ## Features @@ -60,7 +52,7 @@ With PHPWord, you can create OOXML, ODF, or RTF documents dynamically using your PHPWord requires the following: -- PHP 7.4+ +- PHP 7.1+ - [XML Parser extension](http://www.php.net/manual/en/xml.installation.php) - [Laminas Escaper component](https://docs.laminas.dev/laminas-escaper/intro/) - [Zip extension](http://php.net/manual/en/book.zip.php) (optional, used to write OOXML and ODF) @@ -78,9 +70,9 @@ Run the following to use the latest stable version ```sh composer require phpoffice/phpword ``` -or if you want the latest develop version +or if you want the latest unreleased version ```sh -composer require phpoffice/phpword:dev-develop +composer require phpoffice/phpword:dev-master ``` ## Getting started @@ -158,13 +150,13 @@ $objWriter->save('helloWorld.html'); ``` More examples are provided in the [samples folder](samples/). For an easy access to those samples launch `php -S localhost:8000` in the samples directory then browse to [http://localhost:8000](http://localhost:8000) to view the samples. -You can also read the [Developers' Documentation](http://phpword.readthedocs.org/) for more detail. +You can also read the [Developers' Documentation](https://phpoffice.github.io/PHPWord/) for more detail. ## Contributing We welcome everyone to contribute to PHPWord. Below are some of the things that you can do to contribute. - 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. +- [Fork us](https://github.com/PHPOffice/PHPWord/fork) and [request a pull](https://github.com/PHPOffice/PHPWord/pulls) to the [master](https://github.com/PHPOffice/PHPWord/tree/master) 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. +- Follow [@PHPOffice](https://twitter.com/PHPOffice) on Twitter. diff --git a/composer.json b/composer.json index f95fbe739e..9633fe01da 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "docx", "OOXML", "OpenXML", "Office Open XML", "ISO IEC 29500", "WordprocessingML", "RTF", "Rich Text Format", "doc", "odt", "ODF", "OpenDocument", "PDF", "HTML" ], - "homepage": "https://phpword.readthedocs.io/", + "homepage": "https://phpoffice.github.io/PHPWord/", "type": "library", "license": "LGPL-3.0", "authors": [ @@ -36,19 +36,19 @@ ], "scripts": { "test": [ - "phpunit --color=always" + "@php vendor/bin/phpunit --color=always" ], "test-no-coverage": [ - "phpunit --color=always --no-coverage" + "@php vendor/bin/phpunit --color=always --no-coverage" ], "check": [ - "php-cs-fixer fix --ansi --dry-run --diff", - "phpcs --report-width=200 --report-summary --report-full samples/ src/ tests/ --ignore=src/PhpWord/Shared/PCLZip --standard=PSR2 -n", - "phpmd src/,tests/ text ./phpmd.xml.dist --exclude pclzip.lib.php", - "@test-no-coverage" + "@php vendor/bin/php-cs-fixer fix --ansi --dry-run --diff", + "@php vendor/bin/phpmd src/,tests/ text ./phpmd.xml.dist --exclude pclzip.lib.php", + "@test-no-coverage", + "@php vendor/bin/phpstan analyse --ansi" ], "fix": [ - "php-cs-fixer fix --ansi" + "@php vendor/bin/php-cs-fixer fix --ansi" ] }, "scripts-descriptions": { @@ -57,24 +57,30 @@ "check": "Runs PHP CheckStyle and PHP Mess detector", "fix": "Fixes issues found by PHP-CS" }, + "config": { + "platform": { + "php": "8.0" + } + }, "require": { "php": "^7.1|^8.0", "ext-dom": "*", "ext-json": "*", "ext-xml": "*", - "laminas/laminas-escaper": ">=2.6" + "phpoffice/math": "^0.2" }, "require-dev": { "ext-zip": "*", "ext-gd": "*", "ext-libxml": "*", - "dompdf/dompdf": "^2.0", + "dompdf/dompdf": "^2.0 || ^3.0", "mpdf/mpdf": "^8.1", - "php-coveralls/php-coveralls": "^2.5", "phpmd/phpmd": "^2.13", "phpunit/phpunit": ">=7.0", "tecnickcom/tcpdf": "^6.5", - "symfony/process": "^4.4" + "symfony/process": "^4.4 || ^5.0", + "friendsofphp/php-cs-fixer": "^3.3", + "phpstan/phpstan-phpunit": "@stable" }, "suggest": { "ext-zip": "Allows writing OOXML and ODF", @@ -92,10 +98,5 @@ "psr-4": { "PhpOffice\\PhpWordTests\\": "tests/PhpWordTests" } - }, - "extra": { - "branch-alias": { - "dev-develop": "0.19-dev" - } } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000000..4b3473cf0c --- /dev/null +++ b/composer.lock @@ -0,0 +1,5093 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "d9579858cde1a3c80e4da1ced85b72fd", + "packages": [ + { + "name": "phpoffice/math", + "version": "0.2.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/Math.git", + "reference": "fc2eb6d1a61b058d5dac77197059db30ee3c8329" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/Math/zipball/fc2eb6d1a61b058d5dac77197059db30ee3c8329", + "reference": "fc2eb6d1a61b058d5dac77197059db30ee3c8329", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xml": "*", + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpunit/phpunit": "^7.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\Math\\": "src/Math/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Progi1984", + "homepage": "https://lefevre.dev" + } + ], + "description": "Math - Manipulate Math Formula", + "homepage": "https://phpoffice.github.io/Math/", + "keywords": [ + "MathML", + "officemathml", + "php" + ], + "support": { + "issues": "https://github.com/PHPOffice/Math/issues", + "source": "https://github.com/PHPOffice/Math/tree/0.2.0" + }, + "time": "2024-08-12T07:30:45+00:00" + } + ], + "packages-dev": [ + { + "name": "composer/pcre", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "67a32d7d6f9f560b726ab25a061b38ff3a80c560" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/67a32d7d6f9f560b726ab25a061b38ff3a80c560", + "reference": "67a32d7d6f9f560b726ab25a061b38ff3a80c560", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/1.0.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-01-21T20:24:37+00:00" + }, + { + "name": "composer/semver", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-04-01T19:23:25+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "9e36aeed4616366d2b690bdce11f71e9178c579a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/9e36aeed4616366d2b690bdce11f71e9178c579a", + "reference": "9e36aeed4616366d2b690bdce11f71e9178c579a", + "shasum": "" + }, + "require": { + "composer/pcre": "^1", + "php": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/2.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-24T20:20:32+00:00" + }, + { + "name": "doctrine/annotations", + "version": "1.14.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", + "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^1 || ^2", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "~1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6", + "vimeo/psalm": "^4.10" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.14.3" + }, + "time": "2023-02-01T09:20:38+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "8cffffb2218e01f3b370bf763e00e81697725259" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/8cffffb2218e01f3b370bf763e00e81697725259", + "reference": "8cffffb2218e01f3b370bf763e00e81697725259", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v1.1.0" + }, + "time": "2023-05-29T18:55:17+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:15:36+00:00" + }, + { + "name": "doctrine/lexer", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/2.1.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2022-12-14T08:49:07+00:00" + }, + { + "name": "dompdf/dompdf", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "fbc7c5ee5d94f7a910b78b43feb7931b7f971b59" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/fbc7c5ee5d94f7a910b78b43feb7931b7f971b59", + "reference": "fbc7c5ee5d94f7a910b78b43feb7931b7f971b59", + "shasum": "" + }, + "require": { + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v3.0.0" + }, + "time": "2024-04-29T14:01:28+00:00" + }, + { + "name": "dompdf/php-font-lib", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "991d6a954f6bbd7e41022198f00586b230731441" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/991d6a954f6bbd7e41022198f00586b230731441", + "reference": "991d6a954f6bbd7e41022198f00586b230731441", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.0" + }, + "time": "2024-04-29T13:40:38+00:00" + }, + { + "name": "dompdf/php-svg-lib", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/eb045e518185298eb6ff8d80d0d0c6b17aecd9af", + "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.0" + }, + "time": "2024-04-29T13:26:35+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", + "reference": "47177af1cfb9dab5d1cc4daf91b7179c2efe7fad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/47177af1cfb9dab5d1cc4daf91b7179c2efe7fad", + "reference": "47177af1cfb9dab5d1cc4daf91b7179c2efe7fad", + "shasum": "" + }, + "require": { + "composer/semver": "^3.2", + "composer/xdebug-handler": "^2.0", + "doctrine/annotations": "^1.12", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.2.5 || ^8.0", + "php-cs-fixer/diff": "^2.0", + "symfony/console": "^4.4.20 || ^5.1.3 || ^6.0", + "symfony/event-dispatcher": "^4.4.20 || ^5.0 || ^6.0", + "symfony/filesystem": "^4.4.20 || ^5.0 || ^6.0", + "symfony/finder": "^4.4.20 || ^5.0 || ^6.0", + "symfony/options-resolver": "^4.4.20 || ^5.0 || ^6.0", + "symfony/polyfill-mbstring": "^1.23", + "symfony/polyfill-php80": "^1.23", + "symfony/polyfill-php81": "^1.23", + "symfony/process": "^4.4.20 || ^5.0 || ^6.0", + "symfony/stopwatch": "^4.4.20 || ^5.0 || ^6.0" + }, + "require-dev": { + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^1.5", + "mikey179/vfsstream": "^1.6.8", + "php-coveralls/php-coveralls": "^2.5.2", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", + "phpspec/prophecy": "^1.15", + "phpspec/prophecy-phpunit": "^1.1 || ^2.0", + "phpunit/phpunit": "^8.5.21 || ^9.5", + "phpunitgoodpractices/polyfill": "^1.5", + "phpunitgoodpractices/traits": "^1.9.1", + "symfony/phpunit-bridge": "^5.2.4 || ^6.0", + "symfony/yaml": "^4.4.20 || ^5.0 || ^6.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2021-12-11T16:25:08+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + }, + "time": "2024-03-31T07:05:07+00:00" + }, + { + "name": "mpdf/mpdf", + "version": "v8.2.4", + "source": { + "type": "git", + "url": "https://github.com/mpdf/mpdf.git", + "reference": "9e3ff91606fed11cd58a130eabaaf60e56fdda88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/mpdf/zipball/9e3ff91606fed11cd58a130eabaaf60e56fdda88", + "reference": "9e3ff91606fed11cd58a130eabaaf60e56fdda88", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-mbstring": "*", + "mpdf/psr-http-message-shim": "^1.0 || ^2.0", + "mpdf/psr-log-aware-trait": "^2.0 || ^3.0", + "myclabs/deep-copy": "^1.7", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": "^5.6 || ^7.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "setasign/fpdi": "^2.1" + }, + "require-dev": { + "mockery/mockery": "^1.3.0", + "mpdf/qrcode": "^1.1.0", + "squizlabs/php_codesniffer": "^3.5.0", + "tracy/tracy": "~2.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-bcmath": "Needed for generation of some types of barcodes", + "ext-xml": "Needed mainly for SVG manipulation", + "ext-zlib": "Needed for compression of embedded resources, such as fonts" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Mpdf\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-only" + ], + "authors": [ + { + "name": "Matěj Humpál", + "role": "Developer, maintainer" + }, + { + "name": "Ian Back", + "role": "Developer (retired)" + } + ], + "description": "PHP library generating PDF files from UTF-8 encoded HTML", + "homepage": "https://mpdf.github.io", + "keywords": [ + "pdf", + "php", + "utf-8" + ], + "support": { + "docs": "http://mpdf.github.io", + "issues": "https://github.com/mpdf/mpdf/issues", + "source": "https://github.com/mpdf/mpdf" + }, + "funding": [ + { + "url": "https://www.paypal.me/mpdf", + "type": "custom" + } + ], + "time": "2024-06-14T16:06:41+00:00" + }, + { + "name": "mpdf/psr-http-message-shim", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/mpdf/psr-http-message-shim.git", + "reference": "f25a0153d645e234f9db42e5433b16d9b113920f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/psr-http-message-shim/zipball/f25a0153d645e234f9db42e5433b16d9b113920f", + "reference": "f25a0153d645e234f9db42e5433b16d9b113920f", + "shasum": "" + }, + "require": { + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mpdf\\PsrHttpMessageShim\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Dorison", + "email": "mark@chromatichq.com" + }, + { + "name": "Kristofer Widholm", + "email": "kristofer@chromatichq.com" + }, + { + "name": "Nigel Cunningham", + "email": "nigel.cunningham@technocrat.com.au" + } + ], + "description": "Shim to allow support of different psr/message versions.", + "support": { + "issues": "https://github.com/mpdf/psr-http-message-shim/issues", + "source": "https://github.com/mpdf/psr-http-message-shim/tree/v2.0.1" + }, + "time": "2023-10-02T14:34:03+00:00" + }, + { + "name": "mpdf/psr-log-aware-trait", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/mpdf/psr-log-aware-trait.git", + "reference": "a633da6065e946cc491e1c962850344bb0bf3e78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/psr-log-aware-trait/zipball/a633da6065e946cc491e1c962850344bb0bf3e78", + "reference": "a633da6065e946cc491e1c962850344bb0bf3e78", + "shasum": "" + }, + "require": { + "psr/log": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mpdf\\PsrLogAwareTrait\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Dorison", + "email": "mark@chromatichq.com" + }, + { + "name": "Kristofer Widholm", + "email": "kristofer@chromatichq.com" + } + ], + "description": "Trait to allow support of different psr/log versions.", + "support": { + "issues": "https://github.com/mpdf/psr-log-aware-trait/issues", + "source": "https://github.com/mpdf/psr-log-aware-trait/tree/v3.0.0" + }, + "time": "2023-05-03T06:19:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2024-06-12T14:39:25+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.18.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + }, + "time": "2023-12-10T21:03:43+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, + { + "name": "pdepend/pdepend", + "version": "2.16.2", + "source": { + "type": "git", + "url": "https://github.com/pdepend/pdepend.git", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "shasum": "" + }, + "require": { + "php": ">=5.3.7", + "symfony/config": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/polyfill-mbstring": "^1.19" + }, + "require-dev": { + "easy-doc/easy-doc": "0.0.0|^1.2.3", + "gregwar/rst": "^1.0", + "squizlabs/php_codesniffer": "^2.0.0" + }, + "bin": [ + "src/bin/pdepend" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "PDepend\\": "src/main/php/PDepend" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Official version of pdepend to be handled with Composer", + "keywords": [ + "PHP Depend", + "PHP_Depend", + "dev", + "pdepend" + ], + "support": { + "issues": "https://github.com/pdepend/pdepend/issues", + "source": "https://github.com/pdepend/pdepend/tree/2.16.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend", + "type": "tidelift" + } + ], + "time": "2023-12-17T18:09:59+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "php-cs-fixer/diff", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/diff.git", + "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/29dc0d507e838c4580d018bd8b5cb412474f7ec3", + "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", + "symfony/process": "^3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "sebastian/diff v3 backport support for PHP 5.6+", + "homepage": "https://github.com/PHP-CS-Fixer", + "keywords": [ + "diff" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/diff/issues", + "source": "https://github.com/PHP-CS-Fixer/diff/tree/v2.0.2" + }, + "abandoned": true, + "time": "2020-10-14T08:32:19+00:00" + }, + { + "name": "phpmd/phpmd", + "version": "2.15.0", + "source": { + "type": "git", + "url": "https://github.com/phpmd/phpmd.git", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0", + "shasum": "" + }, + "require": { + "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", + "ext-xml": "*", + "pdepend/pdepend": "^2.16.1", + "php": ">=5.3.9" + }, + "require-dev": { + "easy-doc/easy-doc": "0.0.0 || ^1.3.2", + "ext-json": "*", + "ext-simplexml": "*", + "gregwar/rst": "^1.0", + "mikey179/vfsstream": "^1.6.8", + "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2" + }, + "bin": [ + "src/bin/phpmd" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHPMD\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Manuel Pichler", + "email": "github@manuel-pichler.de", + "homepage": "https://github.com/manuelpichler", + "role": "Project Founder" + }, + { + "name": "Marc Würth", + "email": "ravage@bluewin.ch", + "homepage": "https://github.com/ravage84", + "role": "Project Maintainer" + }, + { + "name": "Other contributors", + "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", + "role": "Contributors" + } + ], + "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", + "homepage": "https://phpmd.org/", + "keywords": [ + "dev", + "mess detection", + "mess detector", + "pdepend", + "phpmd", + "pmd" + ], + "support": { + "irc": "irc://irc.freenode.org/phpmd", + "issues": "https://github.com/phpmd/phpmd/issues", + "source": "https://github.com/phpmd/phpmd/tree/2.15.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd", + "type": "tidelift" + } + ], + "time": "2023-12-11T08:22:20+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.11.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "640410b32995914bde3eed26fa89552f9c2c082f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/640410b32995914bde3eed26fa89552f9c2c082f", + "reference": "640410b32995914bde3eed26fa89552f9c2c082f", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2024-08-08T09:02:50+00:00" + }, + { + "name": "phpstan/phpstan-phpunit", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phpunit.git", + "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11", + "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.11" + }, + "conflict": { + "phpunit/phpunit": "<7.0" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPUnit extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-phpunit/issues", + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0" + }, + "time": "2024-04-20T06:39:00+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.30", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:47:57+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.15", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1", + "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.28", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.15" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2023-12-01T16:55:19+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "sabberworm/php-css-parser", + "version": "v8.6.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d2fb94a9641be84d79c7548c6d39bbebba6e9a70", + "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=5.6.20" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.27" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.6.0" + }, + "time": "2024-07-01T07:33:21+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-05-07T05:35:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T06:03:37+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bde739e7565280bda77be70044ac1047bc007e34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-02T09:26:13+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "setasign/fpdi", + "version": "v2.6.0", + "source": { + "type": "git", + "url": "https://github.com/Setasign/FPDI.git", + "reference": "a6db878129ec6c7e141316ee71872923e7f1b7ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/a6db878129ec6c7e141316ee71872923e7f1b7ad", + "reference": "a6db878129ec6c7e141316ee71872923e7f1b7ad", + "shasum": "" + }, + "require": { + "ext-zlib": "*", + "php": "^5.6 || ^7.0 || ^8.0" + }, + "conflict": { + "setasign/tfpdf": "<1.31" + }, + "require-dev": { + "phpunit/phpunit": "~5.7", + "setasign/fpdf": "~1.8.6", + "setasign/tfpdf": "~1.33", + "squizlabs/php_codesniffer": "^3.5", + "tecnickcom/tcpdf": "~6.2" + }, + "suggest": { + "setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured." + }, + "type": "library", + "autoload": { + "psr-4": { + "setasign\\Fpdi\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Slabon", + "email": "jan.slabon@setasign.com", + "homepage": "https://www.setasign.com" + }, + { + "name": "Maximilian Kresse", + "email": "maximilian.kresse@setasign.com", + "homepage": "https://www.setasign.com" + } + ], + "description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.", + "homepage": "https://www.setasign.com/fpdi", + "keywords": [ + "fpdf", + "fpdi", + "pdf" + ], + "support": { + "issues": "https://github.com/Setasign/FPDI/issues", + "source": "https://github.com/Setasign/FPDI/tree/v2.6.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/setasign/fpdi", + "type": "tidelift" + } + ], + "time": "2023-12-11T16:03:32+00:00" + }, + { + "name": "symfony/config", + "version": "v5.4.31", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "dd5ea39de228813aba0c23c3a4153da2a4cf3cd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/dd5ea39de228813aba0c23c3a4153da2a4cf3cd9", + "reference": "dd5ea39de228813aba0c23c3a4153da2a4cf3cd9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22" + }, + "conflict": { + "symfony/finder": "<4.4" + }, + "require-dev": { + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/messenger": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/yaml": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v5.4.31" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-11-09T08:22:43+00:00" + }, + { + "name": "symfony/console", + "version": "v5.3.16", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "2e322c76cdccb302af6b275ea2207169c8355328" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/2e322c76cdccb302af6b275ea2207169c8355328", + "reference": "2e322c76cdccb302af6b275ea2207169c8355328", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" + }, + "conflict": { + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.3.16" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-01T08:24:05+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v5.4.34", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "75d568165a65fa7d8124869ec7c3a90424352e6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/75d568165a65fa7d8124869ec7c3a90424352e6c", + "reference": "75d568165a65fa7d8124869ec7c3a90424352e6c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22", + "symfony/service-contracts": "^1.1.6|^2" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<5.3", + "symfony/finder": "<4.4", + "symfony/proxy-manager-bridge": "<4.4", + "symfony/yaml": "<4.4.26" + }, + "provide": { + "psr/container-implementation": "1.0", + "symfony/service-implementation": "1.0|2.0" + }, + "require-dev": { + "symfony/config": "^5.3|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/yaml": "^4.4.26|^5.0|^6.0" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v5.4.34" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-12-28T09:31:38+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.4.22", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "1df20e45d56da29a4b1d8259dd6e950acbf1b13f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1df20e45d56da29a4b1d8259dd6e950acbf1b13f", + "reference": "1df20e45d56da29a4b1d8259dd6e950acbf1b13f", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.22" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-17T11:31:58+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1", + "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.4.25", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/0ce3a62c9579a53358d3a7eb6b3dfb79789a6364", + "reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.4.25" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-31T13:04:02+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "078e9a5e1871fcfe6a5ce421b539344c21afef19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/078e9a5e1871fcfe6a5ce421b539344c21afef19", + "reference": "078e9a5e1871fcfe6a5ce421b539344c21afef19", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-02-16T09:33:00+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v5.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9", + "reference": "4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v5.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-02-14T08:03:56+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "42292d99c55abe617799667f454222c54c60e229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-28T09:04:16+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/process", + "version": "v5.4.34", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "8fa22178dfc368911dbd513b431cd9b06f9afe7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/8fa22178dfc368911dbd513b431cd9b06f9afe7a", + "reference": "8fa22178dfc368911dbd513b431cd9b06f9afe7a", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.34" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-12-02T08:41:43+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-30T19:17:29+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v5.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "f83692cd869a6f2391691d40a01e8acb89e76fee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/f83692cd869a6f2391691d40a01e8acb89e76fee", + "reference": "f83692cd869a6f2391691d40a01e8acb89e76fee", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/service-contracts": "^1|^2|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v5.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-02-14T08:03:56+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.22", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", + "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.22" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-14T06:11:53+00:00" + }, + { + "name": "tecnickcom/tcpdf", + "version": "6.7.5", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/TCPDF.git", + "reference": "951eabf0338ec2522bd0d5d9c79b08a3a3d36b36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/951eabf0338ec2522bd0d5d9c79b08a3a3d36b36", + "reference": "951eabf0338ec2522bd0d5d9c79b08a3a3d36b36", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "config", + "include", + "tcpdf.php", + "tcpdf_parser.php", + "tcpdf_import.php", + "tcpdf_barcodes_1d.php", + "tcpdf_barcodes_2d.php", + "include/tcpdf_colors.php", + "include/tcpdf_filters.php", + "include/tcpdf_font_data.php", + "include/tcpdf_fonts.php", + "include/tcpdf_images.php", + "include/tcpdf_static.php", + "include/barcodes/datamatrix.php", + "include/barcodes/pdf417.php", + "include/barcodes/qrcode.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "TCPDF is a PHP class for generating PDF documents and barcodes.", + "homepage": "http://www.tcpdf.org/", + "keywords": [ + "PDFD32000-2008", + "TCPDF", + "barcodes", + "datamatrix", + "pdf", + "pdf417", + "qrcode" + ], + "support": { + "issues": "https://github.com/tecnickcom/TCPDF/issues", + "source": "https://github.com/tecnickcom/TCPDF/tree/6.7.5" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_donations¤cy_code=GBP&business=paypal@tecnick.com&item_name=donation%20for%20tcpdf%20project", + "type": "custom" + } + ], + "time": "2024-04-20T17:25:10+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2023-11-20T00:12:19+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "phpstan/phpstan-phpunit": 0 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.1|^8.0", + "ext-dom": "*", + "ext-json": "*", + "ext-xml": "*" + }, + "platform-dev": { + "ext-zip": "*", + "ext-gd": "*", + "ext-libxml": "*" + }, + "platform-overrides": { + "php": "8.0" + }, + "plugin-api-version": "2.6.0" +} diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index bd38cd5d9d..0000000000 --- a/docs/Makefile +++ /dev/null @@ -1,153 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PHPWord.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PHPWord.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/PHPWord" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PHPWord" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/changes/0.x/0.10.0.md b/docs/changes/0.x/0.10.0.md new file mode 100644 index 0000000000..3c31d772c2 --- /dev/null +++ b/docs/changes/0.x/0.10.0.md @@ -0,0 +1,84 @@ + +# 0.10.0 (4 May 2014) + +This release marked heavy refactorings on internal code structure with the creation of some abstract classes to reduce code duplication. `Element` subnamespace is introduced in this release to replace `Section`. Word2007 reader capability is greatly enhanced. Endnote is introduced. List numbering is now customizable. Basic HTML and PDF writing support is enabled. Basic ODText reader is introduced. + +### Features +- Image: Get image dimensions without EXIF extension - @andrew-kzoo #184 +- Table: Add `tblGrid` element for Libre/Open Office table sizing - @gianis6 #183 +- Footnote: Ability to insert textbreak in footnote `$footnote->addTextBreak()` - @ivanlanin +- Footnote: Ability to style footnote reference mark by using `FootnoteReference` style - @ivanlanin +- Font: Add `bgColor` to font style to define background using HEX color - @jcarignan #168 +- Table: Add `exactHeight` to row style to define whether row height should be exact or atLeast - @jcarignan #168 +- Element: New `CheckBox` element for sections and table cells - @ozilion #156 +- Settings: Ability to use PCLZip as alternative to ZipArchive - @bskrtich @ivanlanin #106, #140, #185 +- Template: Ability to find & replace variables in headers & footers - @dgudgeon #190 +- Template: Ability to clone & delete block of text using `cloneBlock` and `deleteBlock` - @diego-vieira #191 +- TOC: Ability to have two or more TOC in one document and to set min and max depth for TOC - @Pyreweb #189 +- Table: Ability to add footnote in table cell - @ivanlanin #187 +- Footnote: Ability to add image in footnote - @ivanlanin #187 +- ListItem: Ability to add list item in header/footer - @ivanlanin #187 +- CheckBox: Ability to add checkbox in header/footer - @ivanlanin #187 +- Link: Ability to add link in header/footer - @ivanlanin #187 +- Object: Ability to add object in header, footer, textrun, and footnote - @ivanlanin #187 +- Media: Add `Media::resetElements()` to reset all media data - @juzi #19 +- General: Add `Style::resetStyles()` - @ivanlanin #187 +- DOCX Reader: Ability to read header, footer, footnotes, link, preservetext, textbreak, pagebreak, table, list, image, and title - @ivanlanin +- Endnote: Ability to add endnotes - @ivanlanin +- ListItem: Ability to create custom list and reset list number - @ivanlanin #10, #198 +- ODT Writer: Basic table writing support - @ivanlanin +- Image: Keep image aspect ratio if only 1 dimension styled - @japonicus #194 +- HTML Writer: Basic HTML writer: text, textrun, link, title, textbreak, table, image (as Base64), footnote, endnote - @ivanlanin #203, #67, #147 +- PDF Writer: Basic PDF writer using DomPDF: All HTML element except image - @ivanlanin #68 +- DOCX Writer: Change `docProps/app.xml` `Application` to `PHPWord` - @ivanlanin +- DOCX Writer: Create `word/settings.xml` and `word/webSettings.xml` dynamically - @ivanlanin +- ODT Writer: Basic image writing - @ivanlanin +- ODT Writer: Link writing - @ivanlanin +- ODT Reader: Basic ODText Reader - @ivanlanin #71 +- Section: Ability to define gutter and line numbering - @ivanlanin +- Font: Small caps, all caps, and double strikethrough - @ivanlanin #151 +- Settings: Ability to use measurement unit other than twips with `setMeasurementUnit` - @ivanlanin #199 +- Style: Remove `bgColor` from `Font`, `Table`, and `Cell` and put it into the new `Shading` style - @ivanlanin +- Style: New `Indentation` and `Spacing` style - @ivanlanin +- Paragraph: Ability to define first line and right indentation - @ivanlanin + +### Bugfixes +- Footnote: Footnote content doesn't show footnote reference number - @ivanlanin #170 +- Documentation: Error in a function - @theBeerNut #195 + +### Deprecated +- `createTextRun` replaced by `addTextRun` +- `createFootnote` replaced by `addFootnote` +- `createHeader` replaced by `addHeader` +- `createFooter` replaced by `addFooter` +- `createSection` replaced by `addSection` +- `Element\Footnote::getReferenceId` replaced by `Element\AbstractElement::getRelationId` +- `Element\Footnote::setReferenceId` replaced by `Element\AbstractElement::setRelationId` +- `Footnote::addFootnoteLinkElement` replaced by `Media::addElement` +- `Footnote::getFootnoteLinkElements` replaced by `Media::getElements` +- All current methods on `Media` +- `Element\Link::getLinkSrc` replaced by `Element\Link::getTarget` +- `Element\Link::getLinkName` replaced by `Element\Link::getText` +- `Style\Cell::getDefaultBorderColor` + +### Miscellaneous +- Documentation: Simplify page level docblock - @ivanlanin #179 +- Writer: Refactor writer classes and create a new `Write\AbstractWriter` abstract class - @ivanlanin #160 +- General: Refactor folders: `Element` and `Exception` - @ivanlanin #187 +- General: Remove legacy `HashTable` and `Shared\ZipStreamWrapper` and all related properties/methods - @ivanlanin #187 +- Element: New `AbstractElement` abstract class - @ivanlanin #187 +- Media: Refactor media class to use one method for all docPart (section, header, footer, footnote) - @ivanlanin #187 +- General: Remove underscore prefix from all private properties name - @ivanlanin #187 +- General: Move Section `Settings` to `Style\Section` - @ivanlanin #187 +- General: Give `Abstract` prefix and `Interface` suffix for all abstract classes and interfaces as per [PHP-FIG recommendation](https://github.com/php-fig/fig-standards/blob/master/bylaws/002-psr-naming-conventions.md) - @ivanlanin #187 +- Style: New `Style\AbstractStyle` abstract class - @ivanlanin #187 +- Writer: New 'ODText\Base` class - @ivanlanin #187 +- General: Rename `Footnote` to `Footnotes` to reflect the nature of collection - @ivanlanin +- General: Add some unit tests for Shared & Element (100%!) - @Progi1984 +- Test: Add some samples and tests for image wrapping style - @brunocasado #59 +- Refactor: Remove Style\Tabs - @ivanlanin +- Refactor: Apply composite pattern for writers - @ivanlanin +- Refactor: Split `AbstractContainer` from `AbstractElement` - @ivanlanin +- Refactor: Apply composite pattern for Word2007 reader - @ivanlanin + + diff --git a/docs/changes/0.x/0.10.1.md b/docs/changes/0.x/0.10.1.md new file mode 100644 index 0000000000..3fd3f620c9 --- /dev/null +++ b/docs/changes/0.x/0.10.1.md @@ -0,0 +1,9 @@ + + +# 0.10.1 (21 May 2014) + +This is a bugfix release for `php-zip` requirement in Composer. + +- Change Composer requirements for php-zip from `require` to `suggest` - @bskrtich #246 + + diff --git a/docs/changes/0.x/0.11.0.md b/docs/changes/0.x/0.11.0.md new file mode 100644 index 0000000000..303b4fe66d --- /dev/null +++ b/docs/changes/0.x/0.11.0.md @@ -0,0 +1,62 @@ +# 0.11.0 (1 June 2014) + +This release marked the change of PHPWord license from LGPL 2.1 to LGPL 3. Four new elements were added: TextBox, ListItemRun, Field, and Line. Relative and absolute positioning for images and textboxes were added. Writer classes were refactored into parts, elements, and styles. ODT and RTF features were enhanced. Ability to add elements to PHPWord object via HTML were implemented. RTF and HTML reader were initiated. + +### Features +- Image: Ability to define relative and absolute positioning - @basjan #217 +- Footer: Conform footer with header by adding firstPage, evenPage and by inheritance - @basjan @ivanlanin #219 +- Element: New `TextBox` element - @basjan @ivanlanin #228, #229, #231 +- HTML: Ability to add elements to PHPWord object via html - @basjan #231 +- Element: New `ListItemRun` element that can add a list item with inline formatting like a textrun - @basjan #235 +- Table: Ability to add table inside a cell (nested table) - @ivanlanin #149 +- RTF Writer: UTF8 support for RTF: Internal UTF8 text is converted to Unicode before writing - @ivanlanin #158 +- Table: Ability to define table width (in percent and twip) and position - @ivanlanin #237 +- RTF Writer: Ability to add links and page breaks in RTF - @ivanlanin #196 +- ListItemRun: Remove fontStyle parameter because ListItemRun is inherited from TextRun and TextRun doesn't have fontStyle - @ivanlanin +- Config: Ability to use a config file to store various common settings - @ivanlanin #200 +- ODT Writer: Enable inline font style in TextRun - @ivanlanin +- ODT Writer: Enable underline, strike/doublestrike, smallcaps/allcaps, superscript/subscript font style - @ivanlanin +- ODT Writer: Enable section and column - @ivanlanin +- PDF Writer: Add TCPDF and mPDF as optional PDF renderer library - @ivanlanin +- ODT Writer: Enable title element and custom document properties - @ivanlanin +- ODT Reader: Ability to read standard and custom document properties - @ivanlanin +- Word2007 Writer: Enable the missing custom document properties writer - @ivanlanin +- Image: Enable "image float left" - @ivanlanin #244 +- RTF Writer: Ability to write document properties - @ivanlanin +- RTF Writer: Ability to write image - @ivanlanin +- Element: New `Field` element - @basjan #251 +- RTF Reader: Basic RTF reader - @ivanlanin #72, #252 +- Element: New `Line` element - @basjan #253 +- Title: Ability to apply numbering in heading - @ivanlanin #193 +- HTML Reader: Basic HTML reader - @ivanlanin #80, #254 +- RTF Writer: Basic table writing - @ivanlanin #245 + +### Bugfixes +- Header: All images added to the second header were assigned to the first header - @basjan #222 +- Conversion: Fix conversion from cm to pixel, pixel to cm, and pixel to point - @basjan #233, #234 +- PageBreak: Page break adds new line in the beginning of the new page - @ivanlanin #150 +- Image: `marginLeft` and `marginTop` cannot accept float value - @ivanlanin #248 +- Title: Orphan `w:fldChar` caused OpenOffice to crash when opening DOCX - @ivanlanin #236 + +### Deprecated +- Static classes `Footnotes`, `Endnotes`, and `TOC` +- `Writer\Word2007\Part`: `Numbering::writeNumbering()`, `Settings::writeSettings()`, `WebSettings::writeWebSettings()`, `ContentTypes::writeContentTypes()`, `Styles::writeStyles()`, `Document::writeDocument()` all changed into `write()` +- `Writer\Word2007\Part\DocProps`: Split into `Writer\Word2007\Part\DocPropsCore` and `Writer\Word2007\Part\DocPropsApp` +- `Element\Title::getBookmarkId()` replaced by `Element\Title::getRelationId()` +- `Writer\HTML::writeDocument`: Replaced by `Writer\HTML::getContent` + +### Miscellaneous +- License: Change the project license from LGPL 2.1 into LGPL 3.0 - #211 +- Word2007 Writer: New `Style\Image` class - @ivanlanin +- Refactor: Replace static classes `Footnotes`, `Endnotes`, and `TOC` with `Collections` - @ivanlanin #206 +- QA: Reactivate `phpcpd` and `phpmd` on Travis - @ivanlanin +- Refactor: PHPMD recommendation: Change all `get...` method that returns `boolean` into `is...` or `has...` - @ivanlanin +- Docs: Create gh-pages branch for API documentation - @Progi1984 #154 +- QA: Add `.scrutinizer.yml` and include `composer.lock` for preparation to Scrutinizer - @ivanlanin #186 +- Writer: Refactor writer parts using composite pattern - @ivanlanin +- Docs: Show code quality and test code coverage badge on README +- Style: Change behaviour of `set...` function of boolean properties; when none is defined, assumed true - @ivanlanin +- Shared: Unify PHP ZipArchive and PCLZip features into PhpWord ZipArchive - @ivanlanin +- Docs: Create VERSION file - @ivanlanin +- QA: Improve dan update requirement check in `samples` folder - @ivanlanin + diff --git a/docs/changes/0.x/0.11.1.md b/docs/changes/0.x/0.11.1.md new file mode 100644 index 0000000000..19be54c67c --- /dev/null +++ b/docs/changes/0.x/0.11.1.md @@ -0,0 +1,5 @@ +# 0.11.1 (2 June 2014) + +This is an immediate bugfix release for HTML reader. + +- HTML Reader: `

` and header tags puts no output - @canyildiz @ivanlanin #257 diff --git a/docs/changes/0.x/0.12.0.md b/docs/changes/0.x/0.12.0.md new file mode 100644 index 0000000000..28a1c3ec2f --- /dev/null +++ b/docs/changes/0.x/0.12.0.md @@ -0,0 +1,60 @@ +# 0.12.0 (3 January 2015) + +This release added form fields (textinput, checkbox, and dropdown), drawing shapes (arc, curve, line, polyline, rect, oval), and basic 2D chart (pie, doughnut, bar, line, area, scatter, radar) elements along with some new styles. Basic MsDoc reader is introduced. + +### Features +- Element: Ability to add drawing shapes (arc, curve, line, polyline, rect, oval) using new `Shape` element - @ivanlanin #123 +- Font: New `scale`, `spacing`, and `kerning` property of font style - @ivanlanin +- Paragraph: Added shading to the paragraph style for full width shading - @lrobert #264 +- RTF Writer: Support for sections, margins, and borders - @ivanlanin #249 +- Section: Ability to set paper size, e.g. A4, A3, and Legal - @ivanlanin #249 +- General: New `PhpWord::save()` method to encapsulate `IOFactory` - @ivanlanin +- General: New `Shared\Converter` static class - @ivanlanin +- Chart: Basic 2D chart (pie, doughnut, bar, line, area, scatter, radar) - @ivanlanin #278 +- Chart: 3D charts and ability to set width and height - @ivanlanin +- FormField: Ability to add textinput, checkbox, and dropdown form elements - @ivanlanin #266 +- Setting: Ability to define document protection (readOnly, comments, trackedChanges, forms) - @ivanlanin +- Setting: Ability to remove [Compatibility Mode] text in the MS Word title bar - @ivanlanin +- SDT: Ability to add structured document tag elements (comboBox, dropDownList, date) - @ivanlanin +- Paragraph: Support for paragraph with borders - @ivanlanin #294 +- Word2007 Writer : Support for RTL - @Progi1984 #331 +- MsDOC Reader: Basic MsDOC Reader - @Progi1984 #23, #287 +- "absolute" horizontal and vertical positioning of Frame - @basjan #302 +- Add new-page function for PDF generation. For multiple PDF-backends - @chc88 #426 +- Report style options enumerated when style unknown - @h6w + +### Bugfixes +- Fix rare PclZip/realpath/PHP version problem - @andrew-kzoo #261 +- `addHTML` encoding and ampersand fixes for PHP 5.3 - @bskrtich #270 +- Page breaks on titles and tables - @ivanlanin #274 +- Table inside vertical border does not rendered properly - @ivanlanin #280 +- `add` of container should be case insensitive, e.g. `addToc` should be accepted, not only `addTOC` - @ivanlanin #294 +- Fix specific borders (and margins) were not written correctly in word2007 writer - @pscheit #327 +- "HTML is not a valid writer" exception while running "Sample_36_RTL.php" - @RomanSyroeshko #340 +- "addShape()" magic method in AbstractContainer is mistakenly named as "addObject()" - @GMTA #356 +- `Element\Section::setPageSizeW()` and `Element\Section::setPageSizeH()` were mentioned in the docs but not implemented. +- Special Characters (ampersand) in Title break docx output - @RomanSyroeshko #401 +- `` tag is closed with `` tag: - @franzholz #438 + +### Deprecated +- `Element\Link::getTarget()` replaced by `Element\Link::getSource()` +- `Element\Section::getSettings()` and `Element\Section::setSettings()` replaced by `Element\Section::getStyle()` and `Element\Section::setStyle()` +- `Shared\Drawing` and `Shared\Font` merged into `Shared\Converter` +- `DocumentProperties` replaced by `Metadata\DocInfo` +- `Template` replaced by `TemplateProcessor` +- `PhpWord->loadTemplate($filename)` + +### Miscellaneous +- Docs: Add known issue on `README` about requirement for temporary folder to be writable and update `samples/index.php` for this requirement check - @ivanlanin #238 +- Docs: Correct elements.rst about Line - @chrissharkman #292 +- PclZip: Remove temporary file after used - @andrew-kzoo #265 +- Autoloader: Add the ability to set the autoloader options - @bskrtich #267 +- Element: Refactor elements to move set relation Id from container to element - @ivanlanin +- Introduced CreateTemporaryFileException, CopyFileException - @RomanSyroeshko +- Settings: added method to set user defined temporary directory - @RomanSyroeshko #310 +- Renamed `Template` into `TemplateProcessor` - @RomanSyroeshko #216 +- Reverted #51. All text escaping must be performed out of the library - @RomanSyroeshko #51 + + + +v \ No newline at end of file diff --git a/docs/changes/0.x/0.12.1.md b/docs/changes/0.x/0.12.1.md new file mode 100644 index 0000000000..db133ac462 --- /dev/null +++ b/docs/changes/0.x/0.12.1.md @@ -0,0 +1,11 @@ +# 0.12.1 (30 August 2015) + +Maintenance release. This release is focused primarily on `TemplateProcessor`. + +### Changes +- Changed visibility of all private properties and methods of `TemplateProcessor` to `protected`. - @RomanSyroeshko #498 +- Improved performance of `TemplateProcessor::setValue()`. - @RomanSyroeshko @nicoSWD #513 + +### Bugfixes +- Fixed issue with "Access denied" message while opening `Sample_07_TemplateCloneRow.docx` and `Sample_23_TemplateBlock.docx` result files on Windows platform. - @RomanSyroeshko @AshSat #532 +- Fixed `PreserveText` element alignment in footer (see `Sample_12_HeaderFooter.php`). - @RomanSyroeshko @SSchwaiger #495 diff --git a/docs/changes/0.x/0.13.0.md b/docs/changes/0.x/0.13.0.md new file mode 100644 index 0000000000..ece6d18990 --- /dev/null +++ b/docs/changes/0.x/0.13.0.md @@ -0,0 +1,47 @@ +# 0.13.0 (31 July 2016) + +This release brings several improvements in `TemplateProcessor`, automatic output escaping feature for OOXML, ODF, HTML, and RTF (turned off, by default). +It also introduces constants for horizontal alignment options, and resolves some issues with PHP 7. +Manual installation feature has been dropped since the release. Please, use [Composer](https://getcomposer.org/) to install PHPWord. + +### Added +- Introduced the `\PhpOffice\PhpWord\SimpleType\Jc` simple type. - @RomanSyroeshko +- Introduced the `\PhpOffice\PhpWord\SimpleType\JcTable` simple type. - @RomanSyroeshko +- Introduced writer for the "Paragraph Alignment" element (see `\PhpOffice\PhpWord\Writer\Word2007\Element\ParagraphAlignment`). - @RomanSyroeshko +- Introduced writer for the "Table Alignment" element (see `\PhpOffice\PhpWord\Writer\Word2007\Element\TableAlignment`). - @RomanSyroeshko +- Supported indexed arrays in arguments of `TemplateProcessor::setValue()`. - @RomanSyroeshko #618 +- Introduced automatic output escaping for OOXML, ODF, HTML, and RTF. To turn the feature on use `phpword.ini` or `\PhpOffice\PhpWord\Settings`. - @RomanSyroeshko #483 +- Supported processing of headers and footers in `TemplateProcessor::applyXslStyleSheet()`. - @RomanSyroeshko #335 + +### Changed +- Improved error message for the case when `autoload.php` is not found. - @RomanSyroeshko #371 +- Renamed the `align` option of `NumberingLevel`, `Frame`, `Table`, and `Paragraph` styles into `alignment`. - @RomanSyroeshko +- Improved performance of `TemplateProcessor::setValue()`. - @kazitanvirahsan #614, #617 +- Fixed some HTML tags not rendering any output (p, header & table) - #257, #324 - @twmobius and @garethellis + +### Deprecated +- `getAlign` and `setAlign` methods of `NumberingLevel`, `Frame`, `Table`, and `Paragraph` styles. +Use the correspondent `getAlignment` and `setAlignment` methods instead. - @RomanSyroeshko +- `left`, `right`, and `justify` alignment options for paragraphs (now are mapped to `Jc::START`, `Jc::END`, and `Jc::BOTH`). - @RomanSyroeshko +- `left`, `right`, and `justify` alignment options for tables (now are mapped to `Jc::START`, `Jc::END`, and `Jc::CENTER`). - @RomanSyroeshko +- `TCPDF` due to its limited HTML support. Use `DomPDF` or `MPDF` writer instead. - @RomanSyroeshko #399 + +### Removed +- `\PhpOffice\PhpWord\Style\Alignment`. Style properties, which previously stored instances of this class, now deal with strings. +In each case set of available string values is defined by the correspondent simple type. - @RomanSyroeshko +- Manual installation support. Since the release we have dependencies on third party libraries, +so installation via ZIP-archive download is not an option anymore. To install PHPWord use [Composer](https://getcomposer.org/). + We also removed `\PhpOffice\PhpWord\Autoloader`, because the latter change made it completely useless. + Autoloaders provided by Composer are in use now (see `bootstrap.php`). - @RomanSyroeshko +- `\PhpOffice\PhpWord\Shared\Drawing` replaced by `\PhpOffice\Common\Drawing`. - @Progi1984 #658 +- `\PhpOffice\PhpWord\Shared\Font`. - @Progi1984 #658 +- `\PhpOffice\PhpWord\Shared\String` replaced by `\PhpOffice\Common\Text`. - @Progi1984 @RomanSyroeshko #658 +- `\PhpOffice\PhpWord\Shared\XMLReader` replaced by `\PhpOffice\Common\XMLReader`. - @Progi1984 #658 +- `\PhpOffice\PhpWord\Shared\XMLWriter` replaced by `\PhpOffice\Common\XMLWriter`. - @Progi1984 @RomanSyroeshko #658 +- `AbstractContainer::addMemoryImage()`. Use `AbstractContainer::addImage()` instead. + +### Fixed +- `Undefined property` error while reading MS-DOC documents. - @jaberu #610 +- Corrupted OOXML template issue in case when its names is broken immediately after `$` sign. +That case wasn't taken into account in implementation of `TemplateProcessor::fixBrokenMacros()`. - @RomanSyroeshko @d-damien #548 + diff --git a/docs/changes/0.x/0.14.0.md b/docs/changes/0.x/0.14.0.md new file mode 100644 index 0000000000..c6a71267d5 --- /dev/null +++ b/docs/changes/0.x/0.14.0.md @@ -0,0 +1,47 @@ +# 0.14.0 (29 Dec 2017) + +This release fixes several bugs and adds some new features. +This version brings compatibility with PHP 7.0 & 7.1 + +### Added +- Possibility to control the footnote numbering -by [@troosan](https://github.com/troosan) in [#1068](https://github.com/PHPOffice/PHPWord/pull/1068) +- Image creation from string -by [@troosan](https://github.com/troosan) in [#937](https://github.com/PHPOffice/PHPWord/pull/937) +- Introduced the `\PhpOffice\PhpWord\SimpleType\NumberFormat` simple type. - @troosan +- Support for ContextualSpacing -by [@postHawk](https://github.com/postHawk) in [#1088](https://github.com/PHPOffice/PHPWord/pull/1088) +- Possiblity to hide spelling and/or grammatical errors -by [@troosan](https://github.com/troosan) in [#542](https://github.com/PHPOffice/PHPWord/pull/542) +- Possiblity to set default document language as well as changing the language for each text element -by [@troosan](https://github.com/troosan) in [#1108](https://github.com/PHPOffice/PHPWord/pull/1108) +- Support for Comments -by [@troosan](https://github.com/troosan) in [#1067](https://github.com/PHPOffice/PHPWord/pull/1067) +- Support for paragraph textAlignment -by [@troosan](https://github.com/troosan) in [#1165](https://github.com/PHPOffice/PHPWord/pull/1165) +- Add support for HTML underline tag `` in addHtml -by [@zNightFalLz](https://github.com/zNightFalLz) in [#1186](https://github.com/PHPOffice/PHPWord/pull/1186) +- Add support for HTML `
` in addHtml - @anrikunby [@troosan](https://github.com/troosan) in [#659](https://github.com/PHPOffice/PHPWord/pull/659) +- Allow to change cell width unit - guillaume-ro-fr #986 +- Allow to change the line height rule @troosan +- Implement PageBreak for odt writerby [@cookiekiller](https://github.com/cookiekiller) in [#863](https://github.com/PHPOffice/PHPWord/pull/863) #824 +- Allow to force an update of all fields on opening a document -by [@troosan](https://github.com/troosan) in [#951](https://github.com/PHPOffice/PHPWord/pull/951) +- Allow adding a CheckBox in a TextRun -by [@irond](https://github.com/irond) in [#727](https://github.com/PHPOffice/PHPWord/pull/727) +- Add support for HTML img tag -by [@srggroup](https://github.com/srggroup) in [#934](https://github.com/PHPOffice/PHPWord/pull/934) +- Add support for password protection for docx -by [@mariahaubner](https://github.com/mariahaubner) in [#1019](https://github.com/PHPOffice/PHPWord/pull/1019) + +### Fixed +- Loosen dependency to Zend +- Images are not being printed when generating PDF -by [@hubertinio](https://github.com/hubertinio) in [#1074](https://github.com/PHPOffice/PHPWord/pull/1074) #431 +- Fixed some PHP 7 warnings - @ likeuntomurphy #927 +- Fixed PHP 7.2 compatibility (renamed `Object` class names to `ObjectElement`) -by [@SailorMax](https://github.com/SailorMax) in [#1185](https://github.com/PHPOffice/PHPWord/pull/1185) +- Fixed Word 97 reader - @alsofronie @Benpxpxby [@mario-rivera](https://github.com/mario-rivera) in [#912](https://github.com/PHPOffice/PHPWord/pull/912) #920 #892 +- Fixed image loading over https -by [@troosan](https://github.com/troosan) in [#988](https://github.com/PHPOffice/PHPWord/pull/988) +- Impossibility to set different even and odd page headers -by [@troosan](https://github.com/troosan) in [#981](https://github.com/PHPOffice/PHPWord/pull/981) +- Fixed Word2007 reader where unnecessary paragraphs were being created -by [@donghaobo](https://github.com/donghaobo) in [#1043](https://github.com/PHPOffice/PHPWord/pull/1043) #620 +- Fixed Word2007 reader where margins were not being read correctly -by [@slowprog](https://github.com/slowprog) in [#885](https://github.com/PHPOffice/PHPWord/pull/885) #1008 +- Impossible to add element PreserveText in Section -by [@rvanlaak](https://github.com/rvanlaak) in [#452](https://github.com/PHPOffice/PHPWord/pull/452) +- Added missing options for numbering format -by [@troosan](https://github.com/troosan) in [#1041](https://github.com/PHPOffice/PHPWord/pull/1041) +- Fixed impossibility to set a different footer for first page -by [@ctrlaltca](https://github.com/ctrlaltca) in [#1116](https://github.com/PHPOffice/PHPWord/pull/1116),by [@aoloe](https://github.com/aoloe) in [#875](https://github.com/PHPOffice/PHPWord/pull/875) +- Fixed styles not being applied by HTML writer, better pdf output -by [@sarke](https://github.com/sarke) in [#1047](https://github.com/PHPOffice/PHPWord/pull/1047) #500 #1139 +- Fixed read docx error when document contains image from remote url -by [@FBnil](https://github.com/FBnil) in [#1173](https://github.com/PHPOffice/PHPWord/pull/1173) #1176 +- Padded the $args array to remove error -by [@kaigoh](https://github.com/kaigoh) in [#1150](https://github.com/PHPOffice/PHPWord/pull/1150),by [@reformed](https://github.com/reformed) in [#870](https://github.com/PHPOffice/PHPWord/pull/870) +- Fix incorrect image size between windows and mac -by [@bskrtich](https://github.com/bskrtich) in [#874](https://github.com/PHPOffice/PHPWord/pull/874) +- Fix adding HTML table to document - @mogilvieby [@arivanbastos](https://github.com/arivanbastos) in [#324](https://github.com/PHPOffice/PHPWord/pull/324) +- Fix parsing on/off values (w:val="true|false|1|0|on|off") -by [@troosan](https://github.com/troosan) in [#1221](https://github.com/PHPOffice/PHPWord/pull/1221) #1219 +- Fix error on Empty Dropdown Entry -by [@ComputerTinker](https://github.com/ComputerTinker) in [#592](https://github.com/PHPOffice/PHPWord/pull/592) + +### Deprecated +- PhpWord->getProtection(), get it from the settings instead PhpWord->getSettings()->getDocumentProtection(); diff --git a/docs/changes/0.x/0.15.0.md b/docs/changes/0.x/0.15.0.md new file mode 100644 index 0000000000..4fa0b8811d --- /dev/null +++ b/docs/changes/0.x/0.15.0.md @@ -0,0 +1,45 @@ +# 0.15.0 (14 Jul 2018) + +### Added +- Parsing of `align` HTML attribute -by [@troosan](https://github.com/troosan) in [#1231](https://github.com/PHPOffice/PHPWord/pull/1231) +- Parse formatting inside HTML lists - @troosanby [@samimussbach](https://github.com/samimussbach) in [#1239](https://github.com/PHPOffice/PHPWord/pull/1239) / [#945](https://github.com/PHPOffice/PHPWord/pull/945) / [#1215](https://github.com/PHPOffice/PHPWord/pull/1215) / [#508](https://github.com/PHPOffice/PHPWord/pull/508) +- Parsing of CSS `direction` instruction, HTML `lang` attribute, formatting inside table cell -by [@troosan](https://github.com/troosan) in [#1273](https://github.com/PHPOffice/PHPWord/pull/1273) / [#1252](https://github.com/PHPOffice/PHPWord/pull/1252) / [#1254](https://github.com/PHPOffice/PHPWord/pull/1254) +- Add support for Track changes @Cipby [@troosan](https://github.com/troosan) in [#354](https://github.com/PHPOffice/PHPWord/pull/354) / [#1262](https://github.com/PHPOffice/PHPWord/pull/1262) +- Add support for fixed Table Layout @aoloe @ekopachby [@troosan](https://github.com/troosan) in [#841](https://github.com/PHPOffice/PHPWord/pull/841) / [#1276](https://github.com/PHPOffice/PHPWord/pull/1276) +- Add support for Cell Spacing @dox07by [@troosan](https://github.com/troosan) in [#1040](https://github.com/PHPOffice/PHPWord/pull/1040) +- Add parsing of formatting inside lists @atomicalnetby [@troosan](https://github.com/troosan) in [#594](https://github.com/PHPOffice/PHPWord/pull/594) +- Added support for Vertically Raised or Lowered Text (w:position) @anrikunby [@troosan](https://github.com/troosan) in [#640](https://github.com/PHPOffice/PHPWord/pull/640) +- Add support for MACROBUTTON field @phryneasby [@troosan](https://github.com/troosan) in [#1021](https://github.com/PHPOffice/PHPWord/pull/1021) +- Add support for Hyphenationby [@Trainmaster](https://github.com/Trainmaster) in [#1282](https://github.com/PHPOffice/PHPWord/pull/1282) (Document: `autoHyphenation`, `consecutiveHyphenLimit`, `hyphenationZone`, `doNotHyphenateCaps`, Paragraph: `suppressAutoHyphens`) +- Added support for Floating Table Positioning (tblpPr)by [@anrikun](https://github.com/anrikun) in [#639](https://github.com/PHPOffice/PHPWord/pull/639) +- Added support for Image text wrapping distanceby [@troosan](https://github.com/troosan) in [#1310](https://github.com/PHPOffice/PHPWord/pull/1310) +- Added parsing of CSS line-height and text-indent in HTML readerby [@troosan](https://github.com/troosan) in [#1316](https://github.com/PHPOffice/PHPWord/pull/1316) +- Added the ability to enable gridlines and axislabels on chartsby [@FrankMeyer](https://github.com/FrankMeyer) in [#576](https://github.com/PHPOffice/PHPWord/pull/576) +- Add support for table indent (tblInd)by [@Trainmaster](https://github.com/Trainmaster) in [#1343](https://github.com/PHPOffice/PHPWord/pull/1343) +- Added parsing of internal links in HTML readerby [@lalop](https://github.com/lalop) in [#1336](https://github.com/PHPOffice/PHPWord/pull/1336) +- Several improvements to chartsby [@JAEK-S](https://github.com/JAEK-S) in [#1332](https://github.com/PHPOffice/PHPWord/pull/1332) +- Add parsing of html image in base64 formatby [@jgpATs2w](https://github.com/jgpATs2w) in [#1382](https://github.com/PHPOffice/PHPWord/pull/1382) +- Added Support for Indentation & Tabs on RTF Writer.by [@smaug1985](https://github.com/smaug1985) in [#1405](https://github.com/PHPOffice/PHPWord/pull/1405) +- Allows decimal numbers in HTML line-height styleby [@jgpATs2w](https://github.com/jgpATs2w) in [#1413](https://github.com/PHPOffice/PHPWord/pull/1413) + +### Fixed +- Fix reading of docx default style -by [@troosan](https://github.com/troosan) in [#1238](https://github.com/PHPOffice/PHPWord/pull/1238) +- Fix the size unit of when parsing html images -by [@troosan](https://github.com/troosan) in [#1254](https://github.com/PHPOffice/PHPWord/pull/1254) +- Fixed HTML parsing of nested lists -by [@troosan](https://github.com/troosan) in [#1265](https://github.com/PHPOffice/PHPWord/pull/1265) +- Save PNG alpha information when using remote images.by [@samsullivan](https://github.com/samsullivan) in [#779](https://github.com/PHPOffice/PHPWord/pull/779) +- Fix parsing of `` tag.by [@troosan](https://github.com/troosan) in [#1274](https://github.com/PHPOffice/PHPWord/pull/1274) +- Bookmark are not writton as internal link in html writerby [@troosan](https://github.com/troosan) in [#1263](https://github.com/PHPOffice/PHPWord/pull/1263) +- It should be possible to add a Footnote in a ListItemRunby [@troosan](https://github.com/troosan) in [#1287](https://github.com/PHPOffice/PHPWord/pull/1287) #1287 +- Fix colspan and rowspan for tables in HTML Writerby [@mattbolt](https://github.com/mattbolt) in [#1292](https://github.com/PHPOffice/PHPWord/pull/1292) +- Fix parsing of Heading and Title formating @troosanby [@gthomas2](https://github.com/gthomas2) in [#465](https://github.com/PHPOffice/PHPWord/pull/465) +- Fix Dateformat typo, fix hours casing, add Month-Day-Year formatsby [@ComputerTinker](https://github.com/ComputerTinker) in [#591](https://github.com/PHPOffice/PHPWord/pull/591) +- Support reading of w:drawing for documents produced by word 2011+by [@gthomas2](https://github.com/gthomas2) in [#464](https://github.com/PHPOffice/PHPWord/pull/464) #1324 +- Fix missing column width in ODText writerby [@potofcoffee](https://github.com/potofcoffee) in [#413](https://github.com/PHPOffice/PHPWord/pull/413) +- Disable entity loader before parsing XML to avoid XXE injectionby [@Tom4t0](https://github.com/Tom4t0) in [#1427](https://github.com/PHPOffice/PHPWord/pull/1427) + +### Changed +- Remove zend-stdlib dependencyby [@Trainmaster](https://github.com/Trainmaster) in [#1284](https://github.com/PHPOffice/PHPWord/pull/1284) +- The default unit for `\PhpOffice\PhpWord\Style\Image` changed from `px` to `pt`. + +### Miscellaneous +- Drop GitHub pages, switch to coveralls for code coverage analysisby [@czosel](https://github.com/czosel) in [#1360](https://github.com/PHPOffice/PHPWord/pull/1360) diff --git a/docs/changes/0.x/0.16.0.md b/docs/changes/0.x/0.16.0.md new file mode 100644 index 0000000000..3eccc34d03 --- /dev/null +++ b/docs/changes/0.x/0.16.0.md @@ -0,0 +1,27 @@ +# 0.16.0 (30 dec 2018) + +### Added +- Add getVariableCount method in TemplateProcessor.by [@nicoder](https://github.com/nicoder) in [#1272](https://github.com/PHPOffice/PHPWord/pull/1272) +- Add setting Chart Title and Legend visibilityby [@Tom-Magill](https://github.com/Tom-Magill) in [#1433](https://github.com/PHPOffice/PHPWord/pull/1433) +- Add ability to pass a Style object in Section constructorby [@ndench](https://github.com/ndench) in [#1416](https://github.com/PHPOffice/PHPWord/pull/1416) +- Add support for hidden textby [@Alexmg86](https://github.com/Alexmg86) in [#1527](https://github.com/PHPOffice/PHPWord/pull/1527) +- Add support for setting images in TemplateProcessorby [@SailorMax](https://github.com/SailorMax) in [#1170](https://github.com/PHPOffice/PHPWord/pull/1170) +- Add "Plain Text" type to SDT (Structured Document Tags)by [@morrisdj](https://github.com/morrisdj) in [#1541](https://github.com/PHPOffice/PHPWord/pull/1541) +- Added possibility to index variables inside cloned block in TemplateProcessorby [@JPBetley](https://github.com/JPBetley) in [#817](https://github.com/PHPOffice/PHPWord/pull/817) +- Added possibility to replace variables inside cloned block with values in TemplateProcessorby [@DIDoS](https://github.com/DIDoS) in [#1392](https://github.com/PHPOffice/PHPWord/pull/1392) + +### Fixed +- Fix regex in `cloneBlock` functionby [@nicoder](https://github.com/nicoder) in [#1269](https://github.com/PHPOffice/PHPWord/pull/1269) +- HTML Title Writer loses text when Title contains a TextRun instead a string.by [@begnini](https://github.com/begnini) in [#1436](https://github.com/PHPOffice/PHPWord/pull/1436) +- Fix regex in fixBrokenMacros, make it less greedy @MuriloSo @brainwoodby [@yurii-sio2](https://github.com/yurii-sio2) in [#1502](https://github.com/PHPOffice/PHPWord/pull/1502) / [#1345](https://github.com/PHPOffice/PHPWord/pull/1345) +- 240 twips are being added to line spacing, should not happen when using lineRule fixedby [@troosan](https://github.com/troosan) in [#1509](https://github.com/PHPOffice/PHPWord/pull/1509) / [#1505](https://github.com/PHPOffice/PHPWord/pull/1505) +- Adding table layout to the generated HTMLby [@aarangara](https://github.com/aarangara) in [#1441](https://github.com/PHPOffice/PHPWord/pull/1441) +- Fix loading of Sharepoint documentby [@Garrcomm](https://github.com/Garrcomm) in [#1498](https://github.com/PHPOffice/PHPWord/pull/1498) +- RTF writer: Round getPageSizeW and getPageSizeH to avoid decimalsby [@Patrick64](https://github.com/Patrick64) in [#1493](https://github.com/PHPOffice/PHPWord/pull/1493) +- Fix parsing of Office 365 documentsby [@Timanx](https://github.com/Timanx) in [#1485](https://github.com/PHPOffice/PHPWord/pull/1485) +- For RTF writers, sizes should should never have decimalsby [@Samuel-BF](https://github.com/Samuel-BF) in [#1536](https://github.com/PHPOffice/PHPWord/pull/1536) +- Style Name Parsing fails if document generated by a non-english word versionby [@begnini](https://github.com/begnini) in [#1434](https://github.com/PHPOffice/PHPWord/pull/1434) + +### Miscellaneous +- Get rid of duplicated code in TemplateProcessorby [@abcdmitry](https://github.com/abcdmitry) in [#1161](https://github.com/PHPOffice/PHPWord/pull/1161) + diff --git a/docs/changes/0.x/0.17.0.md b/docs/changes/0.x/0.17.0.md new file mode 100644 index 0000000000..ffd2d91dd4 --- /dev/null +++ b/docs/changes/0.x/0.17.0.md @@ -0,0 +1,27 @@ +# 0.17.0 (01 oct 2019) + +### Added +- Add methods setValuesFromArray and cloneRowFromArray to the TemplateProcessor [@geraldb-nicat](https://github.com/geraldb-nicat) GH-670 +- Set complex type in template [@troosan](https://github.com/troosan) GH-1565 +- implement support for section vAlign [@troosan](https://github.com/troosan) GH-1569 +- ParseStyle for border-color [@Gllrm0](https://github.com/Gllrm0) GH-1551 +- Html writer auto invert text color [@SailorMax](https://github.com/SailorMax) GH-1387 +- Add RightToLeft table presentation. [@troosan](https://github.com/troosan) GH-1550 +- Add support for page vertical alignment. [@troosan](https://github.com/troosan) GH-672 GH-1569 +- Adding setNumId method for ListItem style [@eweso](https://github.com/eweso) GH-1329 +- Add support for basic fields in RTF writer. [@Samuel-BF](https://github.com/Samuel-BF) GH-1717 + +### Fixed +- Fix HTML border-color parsing. [@troosan](https://github.com/troosan) GH-1551 / GH-1570 +- Language::validateLocale should pass with locale 'zxx'. [@efpapado](https://github.com/efpapado) GH-1558 +- can't align center vertically with the text [@ter987](https://github.com/ter987) GH-672 +- fix parsing of border-color and add test [@troosan](https://github.com/troosan) GH-1570 +- TrackChange doesn't handle all return types of \DateTime::createFromFormat(...) [@superhaggis](https://github.com/superhaggis) GH-1584 +- To support PreserveText inside sub container [@bhattnishant](https://github.com/bhattnishant) GH-1637 +- No nested w:pPr elements in ListItemRun. [@waltertamboer](https://github.com/waltertamboer) GH-1628 +- Ensure that entity_loader disable variable is re-set back to the original setting [@seamuslee001](https://github.com/seamuslee001) GH-1585 + +### Miscellaneous +- Use embedded http server to test loading of remote images [@troosan](https://github.com/troosan) GH-1544 +- Change private to protected to be able extending class Html [@SpinyMan](https://github.com/SpinyMan) GH-1646 +- Fix apt-get crash in Travis CI for PHP 5.3 [@mdupont](https://github.com/mdupont) GH-1707 \ No newline at end of file diff --git a/docs/changes/0.x/0.18.0.md b/docs/changes/0.x/0.18.0.md new file mode 100644 index 0000000000..5f9d6a4d2f --- /dev/null +++ b/docs/changes/0.x/0.18.0.md @@ -0,0 +1,47 @@ +# [0.18.0](https://github.com/PHPOffice/PHPWord/tree/0.18.0) (2021-02-12) + +[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/0.17.0...0.18.0) + +### Enhancements +- Add support for charts in template processor [#2012](https://github.com/PHPOffice/PHPWord/pull/2012) ([@dbarzin](https://github.com/dbarzin)) +- add/setting page element border style. [#1986](https://github.com/PHPOffice/PHPWord/pull/1986) ([@emnabs](https://github.com/emnabs)) +- allow to use customized pdf library [#1983](https://github.com/PHPOffice/PHPWord/pull/1983) ([@SailorMax](https://github.com/SailorMax)) +- feat: Update addHtml to handle style inheritance [#1965](https://github.com/PHPOffice/PHPWord/pull/1965) ([@Julien1138](https://github.com/Julien1138)) +- Add parsing of Shape node values [#1924](https://github.com/PHPOffice/PHPWord/pull/1924) ([@sven-ahrens](https://github.com/sven-ahrens)) +- Allow to redefine TCPDF object [#1907](https://github.com/PHPOffice/PHPWord/pull/1907) ([@SailorMax](https://github.com/SailorMax)) +- Enhancements to addHTML parser [#1902](https://github.com/PHPOffice/PHPWord/pull/1902) ([@lubosdz](https://github.com/lubosdz)) +- Make Default Paper Configurable [#1851](https://github.com/PHPOffice/PHPWord/pull/1851) ([@oleibman](https://github.com/oleibman)) +- Implement various missing features for the ODT writer [#1796](https://github.com/PHPOffice/PHPWord/pull/1796) ([@oleibman](https://github.com/oleibman)) +- Added support for "cloudConvert" images [#1794](https://github.com/PHPOffice/PHPWord/pull/1794) ([@ErnestStaug](https://github.com/ErnestStaug)) +- Add support for several features for the RTF writer [#1775](https://github.com/PHPOffice/PHPWord/pull/1775) ([@oleibman](https://github.com/oleibman)) +- Add font style for Field elements [#1774](https://github.com/PHPOffice/PHPWord/pull/1774) ([@oleibman](https://github.com/oleibman)) +- Add support for ListItemRun in HTML writer [#1766](https://github.com/PHPOffice/PHPWord/pull/1766) ([@stefan-91](https://github.com/stefan-91)) +- Improvements in RTF writer [#1755](https://github.com/PHPOffice/PHPWord/pull/1755) ([@oleibman](https://github.com/oleibman)) +- Allow a closure to be passed with image replacement tags [#1716](https://github.com/PHPOffice/PHPWord/pull/1716) ([@mbardelmeijer](https://github.com/mbardelmeijer)) +- Add Option for Dynamic Chart Legend Position [#1699](https://github.com/PHPOffice/PHPWord/pull/1699) ([@Stephan212](https://github.com/Stephan212)) +- Add parsing of HTML checkbox input field [#1832](https://github.com/PHPOffice/PHPWord/pull/1832) ([@Matze2010](https://github.com/Matze2010)) + +### Bug fixes +- Fix image stroke in libreoffice 7.x [#1992](https://github.com/PHPOffice/PHPWord/pull/1992) ([@Adizbek](https://github.com/Adizbek)) +- Fix deprecated warning for non-hexadecimal number [#1988](https://github.com/PHPOffice/PHPWord/pull/1988) ([@Ciki](https://github.com/Ciki)) +- Fix limit not taken into account when adding image in template [#1967](https://github.com/PHPOffice/PHPWord/pull/1967) ([@jsochor](https://github.com/jsochor)) +- Add null check when setComplexValue is not found [#1936](https://github.com/PHPOffice/PHPWord/pull/1936) ([@YannikFirre](https://github.com/YannikFirre)) +- Some document have non-standard locale code [#1824](https://github.com/PHPOffice/PHPWord/pull/1824) ([@ErnestStaug](https://github.com/ErnestStaug)) +- Fixes PHPDoc @param and @return types for several Converter methods [#1818](https://github.com/PHPOffice/PHPWord/pull/1818) ([@caugner](https://github.com/caugner)) +- Update the regexp to avoid catastrophic backtracking [#1809](https://github.com/PHPOffice/PHPWord/pull/1809) ([@juzser](https://github.com/juzser)) +- Fix PHPUnit tests on develop branch [#1771](https://github.com/PHPOffice/PHPWord/pull/1771) ([@mdupont](https://github.com/mdupont)) +- TemplateProcessor cloneBlock wrongly clones images [#1763](https://github.com/PHPOffice/PHPWord/pull/1763) ([@alarai](https://github.com/alarai)) + +### Miscellaneous +- Compatibility with PHP 7.4, PHP 8.0 and migrate to Laminas Escaper [#1946](https://github.com/PHPOffice/PHPWord/pull/1946) ([@liborm85](https://github.com/liborm85)) +- Remove legacy PHPOffice/Common package, fix PHP 8.0 compatibility [#1996](https://github.com/PHPOffice/PHPWord/pull/1996) ([@liborm85](https://github.com/liborm85)) +- Improve Word2007 Test Coverage [#1858](https://github.com/PHPOffice/PHPWord/pull/1858) ([@oleibman](https://github.com/oleibman)) +- Fix typo in docs. Update templates-processing.rst [#1952](https://github.com/PHPOffice/PHPWord/pull/1952) ([@mnvx](https://github.com/mnvx)) +- Fix documentation and method name for FootnoteProperties [#1776](https://github.com/PHPOffice/PHPWord/pull/1776) ([@mdupont](https://github.com/mdupont)) +- fix: documentation about paragraph indentation [#1764](https://github.com/PHPOffice/PHPWord/pull/1764) ([@mdupont](https://github.com/mdupont)) +- Update templates-processing.rst [#1745](https://github.com/PHPOffice/PHPWord/pull/1745) ([@igronus](https://github.com/igronus)) +- Unused variables $rows, $cols in sample [#1877](https://github.com/PHPOffice/PHPWord/pull/1877) ([@ThanasisMpalatsoukas](https://github.com/ThanasisMpalatsoukas)) +- Add unit test for NumberingStyle [#1744](https://github.com/PHPOffice/PHPWord/pull/1744) ([@Manunchik](https://github.com/Manunchik)) +- Add unit test for PhpWord Settings [#1743](https://github.com/PHPOffice/PHPWord/pull/1743) (@[Manunchik](https://github.com/Manunchik)) +- Add unit test for Media elements [#1742](https://github.com/PHPOffice/PHPWord/pull/1742) ([@Manunchik](https://github.com/Manunchik)) +- Update templates processing docs [#1729](https://github.com/PHPOffice/PHPWord/pull/1729) ([@hcdias](https://github.com/hcdias)) diff --git a/docs/changes/0.x/0.18.1.md b/docs/changes/0.x/0.18.1.md new file mode 100644 index 0000000000..f4e1547a26 --- /dev/null +++ b/docs/changes/0.x/0.18.1.md @@ -0,0 +1,7 @@ +# [0.18.1](https://github.com/PHPOffice/PHPWord/tree/0.18.1) (2021-03-08) + +[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/0.18.0...0.18.1) + +### Bug fixes +- Fix BC break in GH-1946. + This package does not replace laminas/laminas-zendframework-bridge by [@mussbach](https://github.com/mussbach) in [#2032](https://github.com/PHPOffice/PHPWord/pull/2032) \ No newline at end of file diff --git a/docs/changes/0.x/0.18.2.md b/docs/changes/0.x/0.18.2.md new file mode 100644 index 0000000000..fe40b7eb3a --- /dev/null +++ b/docs/changes/0.x/0.18.2.md @@ -0,0 +1,14 @@ +# [0.18.2](https://github.com/PHPOffice/PHPWord/tree/0.18.2) (2021-06-04) + +[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/0.18.1...0.18.2) + +### Bug fixes +- when adding image to relationship first check that the generated RID is actually unique by [@tpv-ebben](https://github.com/tpv-ebben) in [#2063](https://github.com/PHPOffice/PHPWord/pull/2063) +- Update chart, don't write 'c:overlap' if grouping is 'clustered' by [@dfsd534](https://github.com/dfsd534) in [#2052](https://github.com/PHPOffice/PHPWord/pull/2052) +- Update Html parser to accept line-height:normal by [@joelgo](https://github.com/joelgo) in [#2041](https://github.com/PHPOffice/PHPWord/pull/2041) +- Fix image border in Word2007 Writer for LibreOffice 7 by [k@amilmmach](https://github.com/kamilmmach) in [#2021](https://github.com/PHPOffice/PHPWord/pull/2021) + +### Miscellaneous +- Corrected namespace for Language class in docs by [@MegaChriz](https://github.com/MegaChriz) in [#2087](https://github.com/PHPOffice/PHPWord/pull/2087) +- Added support for Garamond font by [@artemkolotilkin](https://github.com/artemkolotilkin) in [#2078](https://github.com/PHPOffice/PHPWord/pull/2078) +- Add BorderStyle for Cell Style to documentation by [@DShkrabak](https://github.com/DShkrabak) in [#2090](https://github.com/PHPOffice/PHPWord/pull/2090) diff --git a/docs/changes/0.x/0.18.3.md b/docs/changes/0.x/0.18.3.md new file mode 100644 index 0000000000..123fe1f2b0 --- /dev/null +++ b/docs/changes/0.x/0.18.3.md @@ -0,0 +1,6 @@ +# [0.18.3](https://github.com/PHPOffice/PHPWord/tree/0.18.3) (2022-02-17) + +[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/0.18.2...0.18.3) + +### Bug fixes +- PHP 8.1 compatibility diff --git a/docs/changes/0.x/0.7.0.md b/docs/changes/0.x/0.7.0.md new file mode 100644 index 0000000000..11c3784bc3 --- /dev/null +++ b/docs/changes/0.x/0.7.0.md @@ -0,0 +1,26 @@ +# 0.7.0 (28 Jan 2014) + +This is the first release after a long development hiatus in [CodePlex](https://phpword.codeplex.com/). This release initialized ODT and RTF Writer, along with some other new features for the existing Word2007 Writer, e.g. tab, multiple header, rowspan and colspan. [Composer](https://packagist.org/packages/phpoffice/phpword) and [Travis](https://travis-ci.org/PHPOffice/PHPWord) were added. + +### Features +- Implement RTF Writer - @Progi1984 #1 +- Implement ODT Writer - @Progi1984 #2 +- Word2007: Add rowspan and colspan to cells - @kaystrobach +- Word2007: Support for tab stops - @RLovelett +- Word2007: Support Multiple headers - @RLovelett +- Word2007: Wrapping Styles to Images - @gabrielbull +- Added support for image wrapping style - @gabrielbull + +### Bugfixes +- "Warning: Invalid error type specified in ...\PHPWord.php on line 226" is thrown when the specified template file is not found - @RomanSyroeshko #32 +- PHPWord_Shared_String.IsUTF8 returns FALSE for Cyrillic UTF-8 input - @RomanSyroeshko #34 +- Temporary files naming logic in PHPWord_Template can lead to a collision - @RomanSyroeshko #38 + +### Miscellaneous +- Add superscript/subscript styling in Excel2007 Writer - @MarkBaker +- add indentation support to paragraphs - @deds +- Support for Composer - @Progi1984 #27 +- Basic CI with Travis - @Progi1984 +- Added PHPWord_Exception and exception when could not copy the template - @Progi1984 +- IMPROVED: Moved examples out of Classes directory - @Progi1984 +- IMPROVED: Advanced string replace in setValue for Template - @Esmeraldo [#49](http://phpword.codeplex.com/workitem/49) \ No newline at end of file diff --git a/docs/changes/0.x/0.8.0.md b/docs/changes/0.x/0.8.0.md new file mode 100644 index 0000000000..6d5ae7c37c --- /dev/null +++ b/docs/changes/0.x/0.8.0.md @@ -0,0 +1,45 @@ +# 0.8.0 (15 Mar 2014) + +This release merged a lot of improvements from the community. Unit tests introduced in this release and has reached 90% code coverage. + +### Features +- Template: Permit to save a template generated as a file (PHPWord_Template::saveAs()) - @RomanSyroeshko #56, #57 +- Word2007: Support sections page numbering - @gabrielbull +- Word2007: Added line height methods to mirror the line height settings in Word in the paragraph styling - @gabrielbull +- Word2007: Added support for page header & page footer height - @JillElaine #5 +- General: Add ability to manage line breaks after image insertion - @bskrtich #6, #66, #84 +- Template: Ability to limit number of replacements performed by setValue() method of Template class - @RomanSyroeshko #52, #53, #85 +- Table row: Repeat as header row & allow row to break across pages - @ivanlanin #48, #86 +- Table: Table width in percentage - @ivanlanin #48, #86 +- Font: Superscript and subscript - @ivanlanin #48, #86 +- Paragraph: Hanging paragraph - @ivanlanin #48, #86 +- Section: Multicolumn and section break - @ivanlanin #48, #86 +- Template: Ability to apply XSL style sheet to Template - @RomanSyroeshko #46, #47, #83 +- General: PHPWord_Shared_Font::pointSizeToTwips() converter - @ivanlanin #87 +- Paragraph: Ability to define normal paragraph style with PHPWord::setNormalStyle() - @ivanlanin #87 +- Paragraph: Ability to define parent style (basedOn) and style for following paragraph (next) - @ivanlanin #87 +- Clone table rows on the fly when using a template document - @jeroenmoors #44, #88 +- Initial addition of basic footnote support - @deds #16 +- Paragraph: Ability to define paragraph pagination: widow control, keep next, keep lines, and page break before - @ivanlanin #92 +- General: PHPWord_Style_Font refactoring - @ivanlanin #93 +- Font: Use points instead of halfpoints internally. Conversion to halfpoints done during XML Writing. - @ivanlanin #93 +- Paragraph: setTabs() function - @ivanlanin #92 +- General: Basic support for TextRun on ODT and RTF - @ivanlanin #99 +- Reader: Basic Reader for Word2007 - @ivanlanin #104 +- TextRun: Allow Text Break in Text Run - @bskrtich #109 +- General: Support for East Asian fontstyle - @jhfangying #111, #118 +- Image: Use exif_imagetype to check image format instead of extension name - @gabrielbull #114 +- General: Setting for XMLWriter Compatibility option - @bskrtich #103 +- MemoryImage: Allow remote image when allow_url_open = on - @ivanlanin #122 +- TextBreak: Allow font and paragraph style for text break - @ivanlanin #18 + +### Bugfixes +- Fixed bug with cell styling - @gabrielbull +- Fixed bug list items inside of cells - @gabrielbull +- Adding a value that contains "&" in a template breaks it - @SiebelsTim #51 +- Example in README.md is broken - @Progi1984 #89 +- General: PHPWord_Shared_Drawing::centimetersToPixels() conversion - @ivanlanin #94 +- Footnote: Corrupt DOCX reported by MS Word when sections > 1 and not every sections have footnote - @ivanlanin #125 + +### Miscellaneous +- UnitTests - @Progi1984 \ No newline at end of file diff --git a/docs/changes/0.x/0.8.1.md b/docs/changes/0.x/0.8.1.md new file mode 100644 index 0000000000..340e6e369a --- /dev/null +++ b/docs/changes/0.x/0.8.1.md @@ -0,0 +1,9 @@ + +# 0.8.1 (17 Mar 2014) + +This is a bugfix release for image detection functionality. + +- Added fallback for computers that do not have exif_imagetype - @bskrtich, @gabrielbull + + + diff --git a/docs/changes/0.x/0.9.0.md b/docs/changes/0.x/0.9.0.md new file mode 100644 index 0000000000..8b96f24a77 --- /dev/null +++ b/docs/changes/0.x/0.9.0.md @@ -0,0 +1,19 @@ +# 0.9.0 (26 Mar 2014) + +This release marked the transformation to namespaces (PHP 5.3+). + +### Features +- Image: Ability to use remote or GD images using `addImage()` on sections, headers, footer, cells, and textruns - @ivanlanin +- Header: Ability to use remote or GD images using `addWatermark()` - @ivanlanin + +### Bugfixes +- Preserve text doesn't render correctly when the text is not the first word, e.g. 'Page {PAGE}' - @ivanlanin + +### Miscellaneous +- Move documentation to [Read The Docs](http://phpword.readthedocs.org/en/develop/) - by [@Progi1984](https://github.com/Progi1984) & [@ivanlanin](https://github.com/ivanlanin) in [#82](https://github.com/PHPOffice/PHPWord/pull/82) +- Reorganize and redesign samples folder - @ivanlanin #137 +- Use `PhpOffice\PhpWord` namespace for PSR compliance - @RomanSyroeshko @gabrielbull #159, #58 +- Restructure folders and change folder name `Classes` to `src` and `Tests` to `test` for PSR compliance - @RomanSyroeshko @gabrielbull +- Compliance to phpDocumentor - @ivanlanin +- Merge Style\TableFull into Style\Table. Style\TableFull is deprecated - @ivanlanin #160 +- Merge Section\MemoryImage into Section\Image. Section\Image is deprecated - @ivanlanin #160 diff --git a/docs/changes/0.x/0.9.1.md b/docs/changes/0.x/0.9.1.md new file mode 100644 index 0000000000..9278f916cf --- /dev/null +++ b/docs/changes/0.x/0.9.1.md @@ -0,0 +1,8 @@ + +# 0.9.1 (27 Mar 2014) + +This is a bugfix release for PSR-4 compatibility. + +- Fixed PSR-4 composer autoloader - @AntonTyutin + + diff --git a/docs/changes/1.x/1.0.0.md b/docs/changes/1.x/1.0.0.md new file mode 100644 index 0000000000..4240a0bd18 --- /dev/null +++ b/docs/changes/1.x/1.0.0.md @@ -0,0 +1,110 @@ +# [1.0.0](https://github.com/PHPOffice/PHPWord/tree/1.0.0) (2022-11-15) + +[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/0.18.3...1.0.0) + +### BREAKING CHANGE + +Most deprecated things were dropped. See details in +https://github.com/PHPOffice/PHPWord/commit/b9f1151bc6f90c276153c3c9dca10a5fc7f355fb. + +#### Dropped classes: + +- `PhpOffice\PhpWord\Template` + +#### Dropped constants: + +- `PhpOffice\PhpWord\Style\Font::UNDERLINE_DOTHASH` +- `PhpOffice\PhpWord\Style\Font::UNDERLINE_DOTHASHHEAVY` +- `PhpOffice\PhpWord\Style\Cell::VALIGN_TOP` +- `PhpOffice\PhpWord\Style\Cell::VALIGN_CENTER` +- `PhpOffice\PhpWord\Style\Cell::VALIGN_BOTTOM` +- `PhpOffice\PhpWord\Style\Cell::VALIGN_BOTH` +- `PhpOffice\PhpWord\Style\TOC::TABLEADER_DOT` +- `PhpOffice\PhpWord\Style\TOC::TABLEADER_UNDERSCORE` +- `PhpOffice\PhpWord\Style\TOC::TABLEADER_LINE` +- `PhpOffice\PhpWord\Style\TOC::TABLEADER_NONE` +- `PhpOffice\PhpWord\Style\Table::WIDTH_AUTO` +- `PhpOffice\PhpWord\Style\Table::WIDTH_PERCENT` +- `PhpOffice\PhpWord\Style\Table::WIDTH_TWIP` +- `PhpOffice\PhpWord\PhpWord::DEFAULT_FONT_NAME` +- `PhpOffice\PhpWord\PhpWord::DEFAULT_FONT_SIZE` +- `PhpOffice\PhpWord\PhpWord::DEFAULT_FONT_COLOR` +- `PhpOffice\PhpWord\PhpWord::DEFAULT_FONT_CONTENT_TYPE` +- +#### Dropped methods: + +- `PhpOffice\PhpWord\Ekement\AbstractContainer::createTextRun()` +- `PhpOffice\PhpWord\Ekement\AbstractContainer::createFootnote()` +- `PhpOffice\PhpWord\Ekement\Footnote::getReferenceId()` +- `PhpOffice\PhpWord\Ekement\Footnote::setReferenceId()` +- `PhpOffice\PhpWord\Ekement\Image::getIsWatermark()` +- `PhpOffice\PhpWord\Ekement\Image::getIsMemImage()` +- `PhpOffice\PhpWord\Ekement\Link::getTarget()` +- `PhpOffice\PhpWord\Ekement\Link::getLinkSrc()` +- `PhpOffice\PhpWord\Ekement\Link::getLinkName()` +- `PhpOffice\PhpWord\Ekement\OLEObject::getObjectId()` +- `PhpOffice\PhpWord\Ekement\OLEObject::setObjectId()` +- `PhpOffice\PhpWord\Ekement\Section::getFootnotePropoperties()` +- `PhpOffice\PhpWord\Ekement\Section::setSettings()` +- `PhpOffice\PhpWord\Ekement\Section::getSettings()` +- `PhpOffice\PhpWord\Ekement\Section::createHeader()` +- `PhpOffice\PhpWord\Ekement\Section::createFooter()` +- `PhpOffice\PhpWord\Ekement\Section::getFooter()` +- `PhpOffice\PhpWord\Media::addSectionMediaElement()` +- `PhpOffice\PhpWord\Media::addSectionLinkElement()` +- `PhpOffice\PhpWord\Media::getSectionMediaElements()` +- `PhpOffice\PhpWord\Media::countSectionMediaElements()` +- `PhpOffice\PhpWord\Media::addHeaderMediaElement()` +- `PhpOffice\PhpWord\Media::countHeaderMediaElements()` +- `PhpOffice\PhpWord\Media::getHeaderMediaElements()` +- `PhpOffice\PhpWord\Media::addFooterMediaElement()` +- `PhpOffice\PhpWord\Media::countFooterMediaElements()` +- `PhpOffice\PhpWord\Media::getFooterMediaElements()` +- `PhpOffice\PhpWord\PhpWord::getProtection()` +- `PhpOffice\PhpWord\PhpWord::loadTemplate()` +- `PhpOffice\PhpWord\PhpWord::createSection()` +- `PhpOffice\PhpWord\PhpWord::getDocumentProperties()` +- `PhpOffice\PhpWord\PhpWord::setDocumentProperties()` +- `PhpOffice\PhpWord\Reader\AbstractReader::getReadDataOnly()` +- `PhpOffice\PhpWord\Settings::getCompatibility()` +- `PhpOffice\PhpWord\Style\AbstractStyle::setArrayStyle()` +- `PhpOffice\PhpWord\Style\Cell::getDefaultBorderColor()` +- `PhpOffice\PhpWord\Style\Font::getBold()` +- `PhpOffice\PhpWord\Style\Font::getItalic()` +- `PhpOffice\PhpWord\Style\Font::getSuperScript()` +- `PhpOffice\PhpWord\Style\Font::getSubScript()` +- `PhpOffice\PhpWord\Style\Font::getStrikethrough()` +- `PhpOffice\PhpWord\Style\Font::getParagraphStyle()` +- `PhpOffice\PhpWord\Style\Frame::getAlign()` +- `PhpOffice\PhpWord\Style\Frame::setAlign()` +- `PhpOffice\PhpWord\Style\NumberingLevel::getAlign()` +- `PhpOffice\PhpWord\Style\NumberingLevel::setAlign()` +- `PhpOffice\PhpWord\Style\Paragraph::getAlign()` +- `PhpOffice\PhpWord\Style\Paragraph::setAlign()` +- `PhpOffice\PhpWord\Style\Paragraph::getWidowControl()` +- `PhpOffice\PhpWord\Style\Paragraph::getKeepNext()` +- `PhpOffice\PhpWord\Style\Paragraph::getKeepLines()` +- `PhpOffice\PhpWord\Style\Paragraph::getPageBreakBefore()` +- `PhpOffice\PhpWord\Style\Row::getTblHeader()` +- `PhpOffice\PhpWord\Style\Row::isTblHeader()` +- `PhpOffice\PhpWord\Style\Row::getCantSplit()` +- `PhpOffice\PhpWord\Style\Row::getExactHeight()` +- `PhpOffice\PhpWord\Style\Spacing::getRule()` +- `PhpOffice\PhpWord\Style\Spacing::setRule()` +- `PhpOffice\PhpWord\Style\Table::getAlign()` +- `PhpOffice\PhpWord\Style\Table::setAlign()` +- `PhpOffice\PhpWord\Writer\AbstractWriter::getUseDiskCaching()` +- `PhpOffice\PhpWord\Writer\HTML::writeDocument()` + +### Bug fixes + +- Multiple PHP 8.1 fixes +- `loadConfig` returns config that was actually applied +- HTML Reader : Override inline style on HTML attribute for table +- HTML Reader : Use `border` attribute for tables +- HTML Reader : Style page-break-after in paragraph +- HTML Reader : Heading in Text Run is not allowed + +### Miscellaneous + +- Drop support for PHP 7.0 and older \ No newline at end of file diff --git a/docs/changes/1.x/1.1.0.md b/docs/changes/1.x/1.1.0.md new file mode 100644 index 0000000000..1fee96fb87 --- /dev/null +++ b/docs/changes/1.x/1.1.0.md @@ -0,0 +1,24 @@ +# [1.1.0](https://github.com/PHPOffice/PHPWord/tree/1.1.0) (2023-05-30) + +[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/1.0.0...1.1.0) + +### Enhancements + +- Introduce deleteRow() method for TemplateProcessor +- HTML Reader: Add basic support for CSS Style Tag +- Allow customizing macro syntax in TemplateProcessor +- Add background color support for textboxes +- Add softhyphen support to word reader +- Add support table row height when importing HTML +- Add support for fractional font sizes +- Added image quality support, with the maximum quality as default +- Support for reading nested tables + +### Bug fixes + +- DOCX reader: Read empty vmerge +- Fixed setting width of Cell Style + +### Miscellaneous + +- `master` is the new default branch \ No newline at end of file diff --git a/docs/changes/1.x/1.2.0.md b/docs/changes/1.x/1.2.0.md new file mode 100644 index 0000000000..7a4b09ea2d --- /dev/null +++ b/docs/changes/1.x/1.2.0.md @@ -0,0 +1,68 @@ +# [1.2.0](https://github.com/PHPOffice/PHPWord/tree/1.2.0) + +[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/1.1.0...1.2.0) + +## Enhancements + +- Word2007 Reader/Writer : Added noWrap table cell property by [@kernusr](https://github.com/kernusr) in GH-2359 +- HTML Reader : Support for `font-variant: small-caps` by [@cambraca](https://github.com/cambraca) in GH-2117 +- Improved TextDirection for styling a cell by [@terryzwt](https://github.com/terryzwt) in GH-2429 +- Word2007 Reader : Added option to disable loading images by [@aelliott1485](https://github.com/aelliott1485) in GH-2450 +- HTML Writer : Added border-spacing to default styles for table by [@kernusr](https://github.com/kernusr) in GH-2451 +- Word2007 Reader : Support for table cell borders and margins by [@kernusr](https://github.com/kernusr) in GH-2454 +- PDF Writer : Add config for defining the default font by [@MikeMaldini](https://github.com/MikeMaldini) in [#2262](https://github.com/PHPOffice/PHPWord/pull/2262) & [#2468](https://github.com/PHPOffice/PHPWord/pull/2468) +- Word2007 Reader : Added support for Comments by [@shaedrich](https://github.com/shaedrich) in [#2161](https://github.com/PHPOffice/PHPWord/pull/2161) & [#2469](https://github.com/PHPOffice/PHPWord/pull/2469) +- Word2007 Reader/Writer: Permit book-fold printing by [@potofcoffee](https://github.com/potofcoffee) in [#2225](https://github.com/PHPOffice/PHPWord/pull/2225) & [#2470](https://github.com/PHPOffice/PHPWord/pull/2470) +- Word2007 Writer : Add PageNumber to TOC by [@jet-desk](https://github.com/jet-desk) in [#1652](https://github.com/PHPOffice/PHPWord/pull/1652) & [#2471](https://github.com/PHPOffice/PHPWord/pull/2471) +- Word2007 Reader/Writer + ODText Reader/Writer : Add Element Formula in by [@Progi1984](https://github.com/Progi1984) in [#2477](https://github.com/PHPOffice/PHPWord/pull/2477) +- Add Support for Various Missing Features in HTML Writer by [@oleibman](https://github.com/oleibman) in [#2475](https://github.com/PHPOffice/PHPWord/pull/2475) + - Fixed addHTML (text-align:right in html is not handled correctly) in [#2467](https://github.com/PHPOffice/PHPWord/pull/2467) + - HTML Writer : Added ability to specify generic fallback font + - HTML Writer : Added ability to specify handling of whitespace + - HTML Writer : Added support for Table Border style, color, and size + - HTML Writer : Added support for empty paragraphs (Word writer permits, browsers generally suppress) + - HTML Writer : Paragraph style should support indentation, line-height, page-break-before + - HTML Writer : Removed margin-top/bottom when spacing is null in Paragraph style + - HTML Writer : Added default paragraph style to all paragraphs, as well as class Normal + - HTML Writer : Use css @page and page declarations for sections + - HTML Writer : Wrap sections in div, with page break before each (except first) + - PDF Writer : Added support for PageBreak + - PDF Writer : Added callback for modifying the HTML + - Added Support for Language, both for document overall and individual text elements +- Template : Set a checkbox by [@nxtpge](https://github.com/nxtpge) in [#2509](https://github.com/PHPOffice/PHPWord/pull/2509) +- ODText / RTF / Word2007 Writer : Add field FILENAME by [@milkyway-git](https://github.com/milkyway-git) in [#2510](https://github.com/PHPOffice/PHPWord/pull/2510) +- ODText Reader : Improve Section Reader by [@oleibman](https://github.com/oleibman) in [#2507](https://github.com/PHPOffice/PHPWord/pull/2507) + +### Bug fixes + +- Fixed wrong mimetype for docx files by [@gamerlv](https://github.com/gamerlv) in GH-2416 +- Word2007 Reader : Read hyperlingks in headings by [@hannesdorn](https://github.com/hannesdorn) in GH-2433 +- PclZip : strtr using empty string by [@spl1nes](https://github.com/spl1nes) in GH-2432 +- Fixed PHP 8.2 deprecated about Allow access to an undefined property by [@DAdq26](https://github.com/DAdq26) in GH-2440 +- Template Processor : Fixed choose dimention for Float Value by [@gdevilbat](https://github.com/gdevilbat) in GH-2449 +- HTML Parser : Fix image parsing from url without extension by [@JokubasR](https://github.com/JokubasR) in GH-2459 +- Word2007 Reader : Fixed reading of Office365 DocX file by [@filippotoso](https://github.com/filippotoso) & [@lfglopes](https://github.com/lfglopes) in [#2506](https://github.com/PHPOffice/PHPWord/pull/2506) +- Word2007 Reader : Check for null on $fontDefaultStyle by [@spatialfree](https://github.com/spatialfree) in [#2513](https://github.com/PHPOffice/PHPWord/pull/2513) + +### Miscellaneous + +- Added PHPStan by [@PowerKiKi](https://github.com/PowerKiKi) in GH-2405 +- Bump symfony/process from 4.4.44 to 5.4.26 by [@dependabot](https://github.com/dependabot) in GH-2431 +- Bump phpunit/phpunit from 9.6.8 to 9.6.10 by [@dependabot](https://github.com/dependabot) in GH-2430 +- Added Coveralls.io by [@Progi1984](https://github.com/Progi1984) in GH-2452 +- Added support for PHP 8.2 & PHP 8.3 by [@Progi1984](https://github.com/Progi1984) in GH-2453 +- Moved documention from ReadTheDocs to MkDocs & Github Pages by [@Progi1984](https://github.com/Progi1984) in GH-2465 +- Bump phpstan/phpstan-phpunit from 1.3.13 to 1.3.14 by [@dependabot](https://github.com/dependabot) in [#2457](https://github.com/PHPOffice/PHPWord/pull/2457) +- Bump symfony/process from 5.4.26 to 5.4.28 by [@dependabot](https://github.com/dependabot) in [#2456](https://github.com/PHPOffice/PHPWord/pull/2456) +- Bump phpunit/phpunit from 9.6.10 to 9.6.11 by [@dependabot](https://github.com/dependabot) in [#2455](https://github.com/PHPOffice/PHPWord/pull/2455) +- Remove deprecated utf8_encode in PHP 8.2 by [@mhcwebdesign](https://github.com/mhcwebdesign) in [#2447](https://github.com/PHPOffice/PHPWord/pull/2447) & [#2472](https://github.com/PHPOffice/PHPWord/pull/2472) +- Bump mpdf/mpdf from 8.1.6 to 8.2.0 by [@dependabot](https://github.com/dependabot) in [#2480](https://github.com/PHPOffice/PHPWord/pull/2480) +- Bump phpunit/phpunit from 9.6.11 to 9.6.13 by [@dependabot](https://github.com/dependabot) in [#2481](https://github.com/PHPOffice/PHPWord/pull/2481) +- Bump tecnickcom/tcpdf from 6.6.2 to 6.6.5 by [@dependabot](https://github.com/dependabot) in [#2482](https://github.com/PHPOffice/PHPWord/pull/2482) +- Bump phpmd/phpmd from 2.13.0 to 2.14.1 by [@dependabot](https://github.com/dependabot) in [#2483](https://github.com/PHPOffice/PHPWord/pull/2483) +- Bump phpstan/phpstan-phpunit from 1.3.14 to 1.3.15 by [@dependabot](https://github.com/dependabot) in [#2494](https://github.com/PHPOffice/PHPWord/pull/2494) + + +### BC Breaks +- Removed dependency `laminas/laminas-escaper` +- *Unintended Break* TemplateProcessor Does Not Persist File After Destruct. [#2539](https://github.com/PHPOffice/PHPWord/issues/2539) To be fixed by [#2545](https://github.com/PHPOffice/PHPWord/pull/2545 diff --git a/docs/changes/1.x/1.3.0.md b/docs/changes/1.x/1.3.0.md new file mode 100644 index 0000000000..4b2e1b25df --- /dev/null +++ b/docs/changes/1.x/1.3.0.md @@ -0,0 +1,53 @@ +# [1.3.0](https://github.com/PHPOffice/PHPWord/tree/1.3.0) + +[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/1.2.0...1.3.0) + +## Enhancements + +- IOFactory : Added extractVariables method to extract variables from a document [@sibalonat](https://github.com/sibalonat) in [#2515](https://github.com/PHPOffice/PHPWord/pull/2515) +- PDF Writer : Documented how to specify a PDF renderer, when working with the PDF writer, as well as the three available choices by [@settermjd](https://github.com/settermjd) in [#2642](https://github.com/PHPOffice/PHPWord/pull/2642) +- Word2007 Reader: Support for Paragraph Border Style by [@damienfa](https://github.com/damienfa) in [#2651](https://github.com/PHPOffice/PHPWord/pull/2651) +- Word2007 Writer: Support for field REF by [@crystoline](https://github.com/crystoline) in [#2652](https://github.com/PHPOffice/PHPWord/pull/2652) +- Word2007 Reader : Support for FormFields by [@vincentKool](https://github.com/vincentKool) in [#2653](https://github.com/PHPOffice/PHPWord/pull/2653) +- RTF Writer : Support for Table Border Style fixing [#345](https://github.com/PHPOffice/PHPWord/issues/345) by [@Progi1984](https://github.com/Progi1984) in [#2656](https://github.com/PHPOffice/PHPWord/pull/2656) +- Word2007 Reader: Support the page break () by [@stanolacko](https://github.com/stanolacko) in [#2662](https://github.com/PHPOffice/PHPWord/pull/2662) +- MsDoc Reader: Support for UTF-8 characters by [@Progi1984] fixing [#881](https://github.com/PHPOffice/PHPWord/issues/881), [#1454](https://github.com/PHPOffice/PHPWord/issues/1454), [#1817](https://github.com/PHPOffice/PHPWord/issues/1817), [#1927](https://github.com/PHPOffice/PHPWord/issues/1927), [#2383](https://github.com/PHPOffice/PHPWord/issues/2383), [#2565](https://github.com/PHPOffice/PHPWord/issues/2565) in [#2664](https://github.com/PHPOffice/PHPWord/pull/2664) +- Word2007 Writer: Added support for multiples comment for the same text by [@rodrigoq](https://github.com/rodrigoq) fixing [#2109](https://github.com/PHPOffice/PHPWord/issues/2109) in [#2665](https://github.com/PHPOffice/PHPWord/pull/2665) + +### Bug fixes + +- MsDoc Reader : Correct Font Size Calculation by [@oleibman](https://github.com/oleibman) fixing [#2526](https://github.com/PHPOffice/PHPWord/issues/2526) in [#2531](https://github.com/PHPOffice/PHPWord/pull/2531) +- Html Reader : Process Titles as Headings not Paragraphs [@0b10011](https://github.com/0b10011) and [@oleibman](https://github.com/oleibman) Issue [#1692](https://github.com/PHPOffice/PHPWord/issues/1692) PR [#2533](https://github.com/PHPOffice/PHPWord/pull/2533) +- Generate Table Cell if Row Doesn't Have Any [@oleibman](https://github.com/oleibman) fixing [#2505](https://github.com/PHPOffice/PHPWord/issues/2505) in [#2516](https://github.com/PHPOffice/PHPWord/pull/2516) +- TemplateProcessor Persist File After Destruct [@oleibman](https://github.com/oleibman) fixing [#2539](https://github.com/PHPOffice/PHPWord/issues/2539) in [#2545](https://github.com/PHPOffice/PHPWord/pull/2545) +- TemplateProcessor Destructor Problem with Php7 [@oleibman](https://github.com/oleibman) fixing [#2548](https://github.com/PHPOffice/PHPWord/issues/2548) in [#2554](https://github.com/PHPOffice/PHPWord/pull/2554) +- bug: TemplateProcessor fix multiline values [@gimler](https://github.com/gimler) fixing [#268](https://github.com/PHPOffice/PHPWord/issues/268), [#2323](https://github.com/PHPOffice/PHPWord/issues/2323) and [#2486](https://github.com/PHPOffice/PHPWord/issues/2486) in [#2522](https://github.com/PHPOffice/PHPWord/pull/2522) +- 32-bit Problem in PasswordEncoder [@oleibman](https://github.com/oleibman) fixing [#2550](https://github.com/PHPOffice/PHPWord/issues/2550) in [#2551](https://github.com/PHPOffice/PHPWord/pull/2551) +- Typo : Fix hardcoded macro chars in TemplateProcessor method [@glafarge](https://github.com/glafarge) in [#2618](https://github.com/PHPOffice/PHPWord/pull/2618) +- XML Reader : Prevent fatal errors when opening corrupt files or "doc" files [@mmcev106](https://github.com/mmcev106) in [#2626](https://github.com/PHPOffice/PHPWord/pull/2626) +- Documentation : Updated Comment element by [@laminga](https://github.com/laminga) in [#2650](https://github.com/PHPOffice/PHPWord/pull/2650) +- HTML Reader : Read width & height attributes in points fixing [#2589](https://github.com/PHPOffice/PHPWord/issues/2589) by [@Progi1984](https://github.com/Progi1984) in [#2654](https://github.com/PHPOffice/PHPWord/pull/2654) +- Template Processor : Fixed bad naming of variables fixing [#2586](https://github.com/PHPOffice/PHPWord/issues/2586) by [@Progi1984](https://github.com/Progi1984) in [#2655](https://github.com/PHPOffice/PHPWord/pull/2655) +- Word2007 Writer : Fix first footnote appearing as separator [#2634](https://github.com/PHPOffice/PHPWord/issues/2634) by [@jacksleight](https://github.com/jacksleight) in [#2635](https://github.com/PHPOffice/PHPWord/pull/2635) +- Template Processor : Fixed images with transparent backgrounds displaying a white background by [@ElwynVdb](https://github.com/ElwynVdb) in [#2638](https://github.com/PHPOffice/PHPWord/pull/2638) +- HTML Writer : Fixed rowspan for tables by [@andomiell](https://github.com/andomiell) in [#2659](https://github.com/PHPOffice/PHPWord/pull/2659) +- Word2007 Writer : Fixed StrikeThrough property by [@noec764](https://github.com/noec764) fixing [#1722](https://github.com/PHPOffice/PHPWord/issues/1722) & [#1693](https://github.com/PHPOffice/PHPWord/issues/1693) in [#2661](https://github.com/PHPOffice/PHPWord/pull/2661) +- HTML Reader : Fixed link without href by [@asmundstavdahl](https://github.com/asmundstavdahl) fixing [#1562](https://github.com/PHPOffice/PHPWord/issues/1562) in [#2663](https://github.com/PHPOffice/PHPWord/pull/2663) + +### Miscellaneous + +- Bump dompdf/dompdf from 2.0.3 to 2.0.4 by [@dependabot](https://github.com/dependabot) in [#2530](https://github.com/PHPOffice/PHPWord/pull/2530) +- Bump phpunit/phpunit from 9.6.13 to 9.6.14 by [@dependabot](https://github.com/dependabot) in [#2519](https://github.com/PHPOffice/PHPWord/pull/2519) +- Bump mpdf/mpdf from 8.2.0 to 8.2.2 by [@dependabot](https://github.com/dependabot) in [#2518](https://github.com/PHPOffice/PHPWord/pull/2518) +- Bump phpmd/phpmd from 2.14.1 to 2.15.0 by [@dependabot](https://github.com/dependabot) in [#2538](https://github.com/PHPOffice/PHPWord/pull/2538) +- Bump phpunit/phpunit from 9.6.14 to 9.6.15 by [@dependabot](https://github.com/dependabot) in [#2537](https://github.com/PHPOffice/PHPWord/pull/2537) +- Bump symfony/process from 5.4.28 to 5.4.34 by [@dependabot](https://github.com/dependabot) in [#2536](https://github.com/PHPOffice/PHPWord/pull/2536) +- Allow rgb() when converting Html by [@oleibman](https://github.com/oleibman) fixing [#2508](https://github.com/PHPOffice/PHPWord/issues/2508) in [#2512](https://github.com/PHPOffice/PHPWord/pull/2512) +- Improved Issue Template by [@Progi1984](https://github.com/Progi1984) in [#2609](https://github.com/PHPOffice/PHPWord/pull/2609) +- Bump phpoffice/math from 0.1.0 to 0.2.0 by [@Progi1984](https://github.com/Progi1984) fixing [#2559](https://github.com/PHPOffice/PHPWord/issues/2559) in [#2645](https://github.com/PHPOffice/PHPWord/pull/2645) +- Bump tecnickcom/tcpdf from 6.6.5 to 6.7.5 by [@dependabot](https://github.com/dependabot) in [#2646](https://github.com/PHPOffice/PHPWord/pull/2646) +- Bump mpdf/mpdf from 8.2.2 to 8.2.4 by [@dependabot](https://github.com/dependabot) in [#2647](https://github.com/PHPOffice/PHPWord/pull/2647) +- Bump phenx/php-svg-lib from 0.5.1 to 0.5.4 by [@dependabot](https://github.com/dependabot) in [#2649](https://github.com/PHPOffice/PHPWord/pull/2649) +- Bump phpstan/phpstan-phpunit from 1.3.15 to 1.4.0 by [@dependabot](https://github.com/dependabot) in [#2648](https://github.com/PHPOffice/PHPWord/pull/2648) + +### BC Breaks diff --git a/docs/changes/1.x/1.4.0.md b/docs/changes/1.x/1.4.0.md new file mode 100644 index 0000000000..8db845337d --- /dev/null +++ b/docs/changes/1.x/1.4.0.md @@ -0,0 +1,20 @@ +# [1.4.0](https://github.com/PHPOffice/PHPWord/tree/1.4.0) (WIP) + +[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/1.3.0...1.4.0) + +## Enhancements + +- Writer ODText: Support for ListItemRun by [@Progi1984](https://github.com/Progi1984) fixing [#2159](https://github.com/PHPOffice/PHPWord/issues/2159), [#2620](https://github.com/PHPOffice/PHPWord/issues/2620) in [#2669](https://github.com/PHPOffice/PHPWord/pull/2669) +- Writer HTML: Support for vAlign in Tables by [@SpraxDev](https://github.com/SpraxDev) in [#2675](https://github.com/PHPOffice/PHPWord/pull/2675) + +### Bug fixes + +- Writer ODText: Support for images inside a textRun by [@Progi1984](https://github.com/Progi1984) fixing [#2240](https://github.com/PHPOffice/PHPWord/issues/2240) in [#2668](https://github.com/PHPOffice/PHPWord/pull/2668) +- Allow vAlign and vMerge on Style\Cell to be set to null by [@SpraxDev](https://github.com/SpraxDev) fixing [#2673](https://github.com/PHPOffice/PHPWord/issues/2673) in [#2676](https://github.com/PHPOffice/PHPWord/pull/2676) + +### Miscellaneous + +- Bump dompdf/dompdf from 2.0.4 to 3.0.0 by [@dependabot](https://github.com/dependabot) fixing [#2621](https://github.com/PHPOffice/PHPWord/issues/2621) in [#2666](https://github.com/PHPOffice/PHPWord/pull/2666) +- Add test case to make sure vMerge defaults to 'continue' by [@SpraxDev](https://github.com/SpraxDev) in [#2677](https://github.com/PHPOffice/PHPWord/pull/2677) + +### BC Breaks diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index ef7b2dcf6c..0000000000 --- a/docs/conf.py +++ /dev/null @@ -1,290 +0,0 @@ -# -*- coding: utf-8 -*- -# -# PhpWord documentation build configuration file, created by -# sphinx-quickstart on Fri Mar 14 23:09:26 2014. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'PHPWord' -copyright = u'2014-2021, PHPWord Contributors' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.18.2' -# The full version, including alpha/beta/rc tags. -release = version - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'PHPWorddoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'PHPWord.tex', u'PHPWord Documentation', - u'The PHPWord Team', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'PHPWord', u'PHPWord Documentation', - [u'The PHPWord Team'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'PHPWord', u'PHPWord Documentation', - u'The PHPWord Team', 'PHPWord', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# -- Options for Epub output --------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = u'PHPWord' -epub_author = u'The PHPWord Team' -epub_publisher = u'The PHPWord Team' -epub_copyright = copyright - -# The language of the text. It defaults to the language option -# or en if the language is not set. -#epub_language = '' - -# The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -#epub_identifier = '' - -# A unique identification for the text. -#epub_uid = '' - -# A tuple containing the cover image and cover page html template filenames. -#epub_cover = () - -# HTML files that should be inserted before the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_pre_files = [] - -# HTML files shat should be inserted after the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_post_files = [] - -# A list of files that should not be packed into the epub file. -#epub_exclude_files = [] - -# The depth of the table of contents in toc.ncx. -#epub_tocdepth = 3 - -# Allow duplicate toc entries. -#epub_tocdup = True - -# Highlight PHP without starting addText($text, [$fontStyle], [$paragraphStyle]); - $textrun = $section->addTextRun([$paragraphStyle]); - -- ``$text``. Text to be displayed in the document. -- ``$fontStyle``. See :ref:`font-style`. -- ``$paragraphStyle``. See :ref:`paragraph-style`. - -For available styling options see :ref:`font-style` and :ref:`paragraph-style`. - -If you want to enable track changes on added text you can mark it as INSERTED or DELETED by a specific user at a given time: - -.. code-block:: php - - $text = $section->addText('Hello World!'); - $text->setChanged(\PhpOffice\PhpWord\Element\ChangedElement::TYPE_INSERTED, 'Fred', (new \DateTime())); - -Titles -~~~~~~ - -If you want to structure your document or build table of contents, you need titles or headings. -To add a title to the document, use the ``addTitleStyle`` and ``addTitle`` method. -If `depth` is 0, a Title will be inserted, otherwise a Heading1, Heading2, ... - -.. code-block:: php - - $phpWord->addTitleStyle($depth, [$fontStyle], [$paragraphStyle]); - $section->addTitle($text, [$depth]); - -- ``depth``. -- ``$fontStyle``. See :ref:`font-style`. -- ``$paragraphStyle``. See :ref:`paragraph-style`. -- ``$text``. Text to be displayed in the document. This can be `string` or a `\PhpOffice\PhpWord\Element\TextRun` - -It's necessary to add a title style to your document because otherwise the title won't be detected as a real title. - -Links -~~~~~ - -You can add Hyperlinks to the document by using the function addLink: - -.. code-block:: php - - $section->addLink($linkSrc, [$linkName], [$fontStyle], [$paragraphStyle]); - -- ``$linkSrc``. The URL of the link. -- ``$linkName``. Placeholder of the URL that appears in the document. -- ``$fontStyle``. See :ref:`font-style`. -- ``$paragraphStyle``. See :ref:`paragraph-style`. - -Preserve texts -~~~~~~~~~~~~~~ - -The ``addPreserveText`` method is used to add a page number or page count to headers or footers. - -.. code-block:: php - - $footer->addPreserveText('Page {PAGE} of {NUMPAGES}.'); - -Breaks ------- - -Text breaks -~~~~~~~~~~~ - -Text breaks are empty new lines. To add text breaks, use the following syntax. All parameters are optional. - -.. code-block:: php - - $section->addTextBreak([$breakCount], [$fontStyle], [$paragraphStyle]); - -- ``$breakCount``. How many lines. -- ``$fontStyle``. See :ref:`font-style`. -- ``$paragraphStyle``. See :ref:`paragraph-style`. - -Page breaks -~~~~~~~~~~~ - -There are two ways to insert a page break, using the ``addPageBreak`` -method or using the ``pageBreakBefore`` style of paragraph. - -.. code-block:: php - - $section->addPageBreak(); - -Lists ------ - -Lists can be added by using ``addListItem`` and ``addListItemRun`` methods. -``addListItem`` is used for creating lists that only contain plain text. -``addListItemRun`` is used for creating complex list items that contains texts -with different style (some bold, other italics, etc) or other elements, e.g. -images or links. The syntaxes are as follow: - -Basic usage: - -.. code-block:: php - - $section->addListItem($text, [$depth], [$fontStyle], [$listStyle], [$paragraphStyle]); - $listItemRun = $section->addListItemRun([$depth], [$listStyle], [$paragraphStyle]) - -Parameters: - -- ``$text``. Text that appears in the document. -- ``$depth``. Depth of list item. -- ``$fontStyle``. See :ref:`font-style`. -- ``$listStyle``. List style of the current element TYPE\_NUMBER, - TYPE\_ALPHANUM, TYPE\_BULLET\_FILLED, etc. See list of constants in PHPWord\\Style\\ListItem. -- ``$paragraphStyle``. See :ref:`paragraph-style`. - -See ``Sample_09_Tables.php`` for more code sample. - -Advanced usage: - -You can also create your own numbering style by changing the ``$listStyle`` parameter with the name of your numbering style. - -.. code-block:: php - - $phpWord->addNumberingStyle( - 'multilevel', - array( - 'type' => 'multilevel', - 'levels' => array( - array('format' => 'decimal', 'text' => '%1.', 'left' => 360, 'hanging' => 360, 'tabPos' => 360), - array('format' => 'upperLetter', 'text' => '%2.', 'left' => 720, 'hanging' => 360, 'tabPos' => 720), - ) - ) - ); - $section->addListItem('List Item I', 0, null, 'multilevel'); - $section->addListItem('List Item I.a', 1, null, 'multilevel'); - $section->addListItem('List Item I.b', 1, null, 'multilevel'); - $section->addListItem('List Item II', 0, null, 'multilevel'); - -For available styling options see :ref:`numbering-level-style`. - -Tables ------- - -To add tables, rows, and cells, use the ``addTable``, ``addRow``, and ``addCell`` methods: - -.. code-block:: php - - $table = $section->addTable([$tableStyle]); - $table->addRow([$height], [$rowStyle]); - $cell = $table->addCell($width, [$cellStyle]); - -Table style can be defined with ``addTableStyle``: - -.. code-block:: php - - $tableStyle = array( - 'borderColor' => '006699', - 'borderSize' => 6, - 'cellMargin' => 50 - ); - $firstRowStyle = array('bgColor' => '66BBFF'); - $phpWord->addTableStyle('myTable', $tableStyle, $firstRowStyle); - $table = $section->addTable('myTable'); - -For available styling options see :ref:`table-style`. - -Cell span -~~~~~~~~~ - -You can span a cell on multiple columns by using ``gridSpan`` or multiple rows by using ``vMerge``. - -.. code-block:: php - - $cell = $table->addCell(200); - $cell->getStyle()->setGridSpan(5); - -See ``Sample_09_Tables.php`` for more code sample. - -Images ------- - -To add an image, use the ``addImage`` method to sections, headers, footers, textruns, or table cells. - -.. code-block:: php - - $section->addImage($src, [$style]); - -- ``$src``. String path to a local image, URL of a remote image or the image data, as a string. Warning: Do not pass user-generated strings here, as that would allow an attacker to read arbitrary files or perform server-side request forgery by passing file paths or URLs instead of image data. -- ``$style``. See :ref:`image-style`. - -Examples: - -.. code-block:: php - - $section = $phpWord->addSection(); - $section->addImage( - 'mars.jpg', - array( - 'width' => 100, - 'height' => 100, - 'marginTop' => -1, - 'marginLeft' => -1, - 'wrappingStyle' => 'behind' - ) - ); - $footer = $section->addFooter(); - $footer->addImage('http://example.com/image.php'); - $textrun = $section->addTextRun(); - $textrun->addImage('http://php.net/logo.jpg'); - $source = file_get_contents('/path/to/my/images/earth.jpg'); - $textrun->addImage($source); - -Watermarks -~~~~~~~~~~ - -To add a watermark (or page background image), your section needs a -header reference. After creating a header, you can use the -``addWatermark`` method to add a watermark. - -.. code-block:: php - - $section = $phpWord->addSection(); - $header = $section->addHeader(); - $header->addWatermark('resources/_earth.jpg', array('marginTop' => 200, 'marginLeft' => 55)); - -Objects -------- - -You can add OLE embeddings, such as Excel spreadsheets or PowerPoint -presentations to the document by using ``addOLEObject`` method. - -.. code-block:: php - - $section->addOLEObject($src, [$style]); - -Table of contents ------------------ - -To add a table of contents (TOC), you can use the ``addTOC`` method. -Your TOC can only be generated if you have add at least one title (See "Titles"). - -.. code-block:: php - - $section->addTOC([$fontStyle], [$tocStyle], [$minDepth], [$maxDepth]); - -- ``$fontStyle``. See font style section. -- ``$tocStyle``. See available options below. -- ``$minDepth``. Minimum depth of header to be shown. Default 1. -- ``$maxDepth``. Maximum depth of header to be shown. Default 9. - -Options for ``$tocStyle``: - -- ``tabLeader``. Fill type between the title text and the page number. Use the defined constants in ``\PhpOffice\PhpWord\Style\TOC``. -- ``tabPos``. The position of the tab where the page number appears in *twip*. -- ``indent``. The indent factor of the titles in *twip*. - -Footnotes & endnotes --------------------- - -You can create footnotes with ``addFootnote`` and endnotes with -``addEndnote`` in texts or textruns, but it's recommended to use textrun -to have better layout. You can use ``addText``, ``addLink``, -``addTextBreak``, ``addImage``, ``addOLEObject`` on footnotes and endnotes. - -On textrun: - -.. code-block:: php - - $textrun = $section->addTextRun(); - $textrun->addText('Lead text.'); - $footnote = $textrun->addFootnote(); - $footnote->addText('Footnote text can have '); - $footnote->addLink('http://test.com', 'links'); - $footnote->addText('.'); - $footnote->addTextBreak(); - $footnote->addText('And text break.'); - $textrun->addText('Trailing text.'); - $endnote = $textrun->addEndnote(); - $endnote->addText('Endnote put at the end'); - -On text: - -.. code-block:: php - - $section->addText('Lead text.'); - $footnote = $section->addFootnote(); - $footnote->addText('Footnote text.'); - -By default the footnote reference number will be displayed with decimal number -starting from 1. This number uses the ``FooterReference`` style which you can -redefine with the ``addFontStyle`` method. Default value for this style is -``array('superScript' => true)``; - -The footnote numbering can be controlled by setting the FootnoteProperties on the Section. - -.. code-block:: php - - $fp = new \PhpOffice\PhpWord\ComplexType\FootnoteProperties(); - //sets the position of the footnote (pageBottom (default), beneathText, sectEnd, docEnd) - $fp->setPos(\PhpOffice\PhpWord\ComplexType\FootnoteProperties::POSITION_BENEATH_TEXT); - //set the number format to use (decimal (default), upperRoman, upperLetter, ...) - $fp->setNumFmt(\PhpOffice\PhpWord\SimpleType\NumberFormat::LOWER_ROMAN); - //force starting at other than 1 - $fp->setNumStart(2); - //when to restart counting (continuous (default), eachSect, eachPage) - $fp->setNumRestart(\PhpOffice\PhpWord\ComplexType\FootnoteProperties::RESTART_NUMBER_EACH_PAGE); - //And finaly, set it on the Section - $section->setFootnoteProperties($fp); - -Checkboxes ----------- - -Checkbox elements can be added to sections or table cells by using ``addCheckBox``. - -.. code-block:: php - - $section->addCheckBox($name, $text, [$fontStyle], [$paragraphStyle]) - -- ``$name``. Name of the check box. -- ``$text``. Text to be displayed in the document. -- ``$fontStyle``. See :ref:`font-style`. -- ``$paragraphStyle``. See :ref:`paragraph-style`. - -Textboxes ---------- - -To be completed - -Fields ------- - -Currently the following fields are supported: - -- PAGE -- NUMPAGES -- DATE -- XE -- INDEX - -.. code-block:: php - - $section->addField($fieldType, [$properties], [$options], [$fieldText], [$fontStyle]) - -- ``$fontStyle``. See :ref:`font-style`. - -See ``\PhpOffice\PhpWord\Element\Field`` for list of properties and options available for each field type. -Options which are not specifically defined can be added. Those must start with a ``\``. - -For instance for the INDEX field, you can do the following (See `Index Field for list of available options `_ ): - -.. code-block:: php - - //the $fieldText can be either a simple string - $fieldText = 'The index value'; - - //or a 'TextRun', to be able to format the text you want in the index - $fieldText = new TextRun(); - $fieldText->addText('My '); - $fieldText->addText('bold index', ['bold' => true]); - $fieldText->addText(' entry'); - $section->addField('XE', array(), array(), $fieldText); - - //this actually adds the index - $section->addField('INDEX', array(), array('\\e " " \\h "A" \\c "3"'), 'right click to update index'); - -Line ----- - -Line elements can be added to sections by using ``addLine``. - -.. code-block:: php - - $lineStyle = array('weight' => 1, 'width' => 100, 'height' => 0, 'color' => 635552); - $section->addLine($lineStyle); - -Available line style attributes: - -- ``weight``. Line width in *twip*. -- ``color``. Defines the color of stroke. -- ``dash``. Line types: dash, rounddot, squaredot, dashdot, longdash, longdashdot, longdashdotdot. -- ``beginArrow``. Start type of arrow: block, open, classic, diamond, oval. -- ``endArrow``. End type of arrow: block, open, classic, diamond, oval. -- ``width``. Line-object width in *pt*. -- ``height``. Line-object height in *pt*. -- ``flip``. Flip the line element: true, false. - -Chart ------ - -Charts can be added using - -.. code-block:: php - - $categories = array('A', 'B', 'C', 'D', 'E'); - $series = array(1, 3, 2, 5, 4); - $chart = $section->addChart('line', $categories, $series, $style); - -For available styling options see :ref:`chart-style`. - -check out the Sample_32_Chart.php for more options and styling. - -Comments --------- - -Comments can be added to a document by using ``addComment``. -The comment can contain formatted text. Once the comment has been added, it can be linked to any element with ``setCommentStart``. - -.. code-block:: php - - // first create a comment - $comment= new \PhpOffice\PhpWord\Element\Comment('Authors name', new \DateTime(), 'my_initials'); - $comment->addText('Test', array('bold' => true)); - - // add it to the document - $phpWord->addComment($comment); - - $textrun = $section->addTextRun(); - $textrun->addText('This '); - $text = $textrun->addText('is'); - // link the comment to the text you just created - $text->setCommentStart($comment); - -If no end is set for a comment using the ``setCommentEnd``, the comment will be ended automatically at the end of the element it is started on. - -Track Changes -------------- - -Track changes can be set on text elements. There are 2 ways to set the change information on an element. -Either by calling the `setChangeInfo()`, or by setting the `TrackChange` instance on the element with `setTrackChange()`. - -.. code-block:: php - - $phpWord = new \PhpOffice\PhpWord\PhpWord(); - - // New portrait section - $section = $phpWord->addSection(); - $textRun = $section->addTextRun(); - - $text = $textRun->addText('Hello World! Time to '); - - $text = $textRun->addText('wake ', array('bold' => true)); - $text->setChangeInfo(TrackChange::INSERTED, 'Fred', time() - 1800); - - $text = $textRun->addText('up'); - $text->setTrackChange(new TrackChange(TrackChange::INSERTED, 'Fred')); - - $text = $textRun->addText('go to sleep'); - $text->setChangeInfo(TrackChange::DELETED, 'Barney', new \DateTime('@' . (time() - 3600))); diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000000..81341fbc18 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,5 @@ +# Frequently asked questions + +## How contribute to PHPWord? +- Improve the documentation + diff --git a/docs/faq.rst b/docs/faq.rst deleted file mode 100644 index 451e1d7305..0000000000 --- a/docs/faq.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. _faq: - -Frequently asked questions -========================== - -How contribute to PHPWord? --------------------------- -- Improve the documentation (`Sphinx Format `__) diff --git a/docs/general.rst b/docs/general.rst deleted file mode 100644 index 0d08739729..0000000000 --- a/docs/general.rst +++ /dev/null @@ -1,344 +0,0 @@ -.. _general: - -General usage -============= - -Basic example -------------- - -The following is a basic example of the PHPWord library. More examples -are provided in the `samples -folder `__. - -.. code-block:: php - - addSection(); - // Adding Text element to the Section having font styled by default... - $section->addText( - '"Learn from yesterday, live for today, hope for tomorrow. ' - . 'The important thing is not to stop questioning." ' - . '(Albert Einstein)' - ); - - /* - * Note: it's possible to customize font style of the Text element you add in three ways: - * - inline; - * - using named font style (new font style object will be implicitly created); - * - using explicitly created font style object. - */ - - // Adding Text element with font customized inline... - $section->addText( - '"Great achievement is usually born of great sacrifice, ' - . 'and is never the result of selfishness." ' - . '(Napoleon Hill)', - array('name' => 'Tahoma', 'size' => 10) - ); - - // Adding Text element with font customized using named font style... - $fontStyleName = 'oneUserDefinedStyle'; - $phpWord->addFontStyle( - $fontStyleName, - array('name' => 'Tahoma', 'size' => 10, 'color' => '1B2232', 'bold' => true) - ); - $section->addText( - '"The greatest accomplishment is not in never falling, ' - . 'but in rising again after you fall." ' - . '(Vince Lombardi)', - $fontStyleName - ); - - // Adding Text element with font customized using explicitly created font style object... - $fontStyle = new \PhpOffice\PhpWord\Style\Font(); - $fontStyle->setBold(true); - $fontStyle->setName('Tahoma'); - $fontStyle->setSize(13); - $myTextElement = $section->addText('"Believe you can and you\'re halfway there." (Theodor Roosevelt)'); - $myTextElement->setFontStyle($fontStyle); - - // Saving the document as OOXML file... - $objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007'); - $objWriter->save('helloWorld.docx'); - - // Saving the document as ODF file... - $objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'ODText'); - $objWriter->save('helloWorld.odt'); - - // Saving the document as HTML file... - $objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'HTML'); - $objWriter->save('helloWorld.html'); - - /* Note: we skip RTF, because it's not XML-based and requires a different example. */ - /* Note: we skip PDF, because "HTML-to-PDF" approach is used to create PDF documents. */ - -PHPWord Settings ----------------- - -The ``PhpOffice\PhpWord\Settings`` class provides some options that will -affect the behavior of PHPWord. Below are the options. - -XML Writer compatibility -~~~~~~~~~~~~~~~~~~~~~~~~ - -This option sets -`XMLWriter::setIndent `__ -and -`XMLWriter::setIndentString `__. -The default value of this option is ``true`` (compatible), which is -`required for -OpenOffice `__ to -render OOXML document correctly. You can set this option to ``false`` -during development to make the resulting XML file easier to read. - -.. code-block:: php - - \PhpOffice\PhpWord\Settings::setCompatibility(false); - -Zip class -~~~~~~~~~ - -By default, PHPWord uses `Zip extension `__ -to deal with ZIP compressed archives and files inside them. If you can't have -Zip extension installed on your server, you can use pure PHP library -alternative, `PclZip `__, which is -included in PHPWord. - -.. code-block:: php - - \PhpOffice\PhpWord\Settings::setZipClass(\PhpOffice\PhpWord\Settings::PCLZIP); - -Output escaping -~~~~~~~~~~~~~~~ - -Writing documents of some formats, especially XML-based, requires correct output escaping. -Without it your document may become broken when you put special characters like ampersand, quotes, and others in it. - -Escaping can be performed in two ways: outside of the library by a software developer and inside of the library by built-in mechanism. -By default, the built-in mechanism is disabled for backward compatibility with versions prior to v0.13.0. -To turn it on set ``outputEscapingEnabled`` option to ``true`` in your PHPWord configuration file or use the following instruction at runtime: - -.. code-block:: php - - \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); - -Default Paper -~~~~~~~~~~~~~ - -By default, all sections of the document will print on A4 paper. -You can alter the default paper by using the following function: - -.. code-block:: php - - \PhpOffice\PhpWord\Settings::setDefaultPaper('Letter'); - -Default font -~~~~~~~~~~~~ - -By default, every text appears in Arial 10 point. You can alter the -default font by using the following two functions: - -.. code-block:: php - - $phpWord->setDefaultFontName('Times New Roman'); - $phpWord->setDefaultFontSize(12); - -Document settings ------------------ -Settings for the generated document can be set using ``$phpWord->getSettings()`` - -Magnification Setting -~~~~~~~~~~~~~~~~~~~~~ -The default zoom value is 100 percent. This can be changed either to another percentage - -.. code-block:: php - - $phpWord->getSettings()->setZoom(75); - -Or to predefined values ``fullPage``, ``bestFit``, ``textFit`` - -.. code-block:: php - - $phpWord->getSettings()->setZoom(Zoom::BEST_FIT); - -Mirroring the Page Margins -~~~~~~~~~~~~~~~~~~~~~~~~~~ -Use mirror margins to set up facing pages for double-sided documents, such as books or magazines. - -.. code-block:: php - - $phpWord->getSettings()->setMirrorMargins(true); - -Spelling and grammatical checks -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -By default spelling and grammatical errors are shown as soon as you open a word document. -For big documents this can slow down the opening of the document. You can hide the spelling and/or grammatical errors with: - -.. code-block:: php - - $phpWord->getSettings()->setHideGrammaticalErrors(true); - $phpWord->getSettings()->setHideSpellingErrors(true); - -You can also specify the status of the spell and grammar checks, marking spelling or grammar as dirty will force a re-check when opening the document. - -.. code-block:: php - - $proofState = new ProofState(); - $proofState->setGrammar(ProofState::CLEAN); - $proofState->setSpelling(ProofState::DIRTY); - - $phpWord->getSettings()->setProofState(proofState); - -Track Revisions -~~~~~~~~~~~~~~~ -Track changes can be activated using ``setTrackRevisions``, you can furture specify - -- Not to use move syntax, instead moved items will be seen as deleted in one place and added in another -- Not track formatting revisions - -.. code-block:: php - - $phpWord->getSettings()->setTrackRevisions(true); - $phpWord->getSettings()->setDoNotTrackMoves(true); - $phpWord->getSettings()->setDoNotTrackFormatting(true); - -Decimal Symbol -~~~~~~~~~~~~~~ -The default symbol to represent a decimal figure is the ``.`` in english. In french you might want to change it to ``,`` for instance. - -.. code-block:: php - - $phpWord->getSettings()->setDecimalSymbol(','); - -Document Language -~~~~~~~~~~~~~~~~~ -The default language of the document can be change with the following. - -.. code-block:: php - - $phpWord->getSettings()->setThemeFontLang(new Language(Language::FR_BE)); - -``Language`` has 3 parameters, one for Latin languages, one for East Asian languages and one for Complex (Bi-Directional) languages. -A couple of language codes are provided in the ``PhpOffice\PhpWord\Style\Language`` class but any valid code/ID can be used. - -In case you are generating an RTF document the language need to be set differently. - -.. code-block:: php - - $lang = new Language(); - $lang->setLangId(Language::EN_GB_ID); - $phpWord->getSettings()->setThemeFontLang($lang); - -Document information --------------------- - -You can set the document information such as title, creator, and company -name. Use the following functions: - -.. code-block:: php - - $properties = $phpWord->getDocInfo(); - $properties->setCreator('My name'); - $properties->setCompany('My factory'); - $properties->setTitle('My title'); - $properties->setDescription('My description'); - $properties->setCategory('My category'); - $properties->setLastModifiedBy('My name'); - $properties->setCreated(mktime(0, 0, 0, 3, 12, 2014)); - $properties->setModified(mktime(0, 0, 0, 3, 14, 2014)); - $properties->setSubject('My subject'); - $properties->setKeywords('my, key, word'); - -Measurement units ------------------ - -The base length unit in Open Office XML is twip. Twip means "TWentieth -of an Inch Point", i.e. 1 twip = 1/1440 inch. - -You can use PHPWord helper functions to convert inches, centimeters, or -points to twip. - -.. code-block:: php - - // Paragraph with 6 points space after - $phpWord->addParagraphStyle('My Style', array( - 'spaceAfter' => \PhpOffice\PhpWord\Shared\Converter::pointToTwip(6)) - ); - - $section = $phpWord->addSection(); - $sectionStyle = $section->getStyle(); - // half inch left margin - $sectionStyle->setMarginLeft(\PhpOffice\PhpWord\Shared\Converter::inchToTwip(.5)); - // 2 cm right margin - $sectionStyle->setMarginRight(\PhpOffice\PhpWord\Shared\Converter::cmToTwip(2)); - -Document protection -------------------- - -The document (or parts of it) can be password protected. - -.. code-block:: php - - $documentProtection = $phpWord->getSettings()->getDocumentProtection(); - $documentProtection->setEditing(DocProtect::READ_ONLY); - $documentProtection->setPassword('myPassword'); - -Automatically Recalculate Fields on Open ----------------------------------------- - -To force an update of the fields present in the document, set updateFields to true - -.. code-block:: php - - $phpWord->getSettings()->setUpdateFields(true); - -Hyphenation ------------ -Hyphenation describes the process of breaking words with hyphens. There are several options to control hyphenation. - -Auto hyphenation -~~~~~~~~~~~~~~~~ - -To automatically hyphenate text set ``autoHyphenation`` to ``true``. - -.. code-block:: php - - $phpWord->getSettings()->setAutoHyphenation(true); - -Consecutive Hyphen Limit -~~~~~~~~~~~~~~~~~~~~~~~~ - -The maximum number of consecutive lines of text ending with a hyphen can be controlled by the ``consecutiveHyphenLimit`` option. -There is no limit if the option is not set or the provided value is ``0``. - -.. code-block:: php - - $phpWord->getSettings()->setConsecutiveHyphenLimit(2); - -Hyphenation Zone -~~~~~~~~~~~~~~~~ - -The hyphenation zone (in *twip*) is the allowed amount of whitespace before hyphenation is applied. -The smaller the hyphenation zone the more words are hyphenated. Or in other words, the wider the hyphenation zone the less words are hyphenated. - -.. code-block:: php - - $phpWord->getSettings()->setHyphenationZone(\PhpOffice\PhpWord\Shared\Converter::cmToTwip(1)); - -Hyphenate Caps -~~~~~~~~~~~~~~ - -To control whether or not words in all capital letters shall be hyphenated use the `doNotHyphenateCaps` option. - -.. code-block:: php - - $phpWord->getSettings()->setDoNotHyphenateCaps(true); diff --git a/docs/howto.md b/docs/howto.md new file mode 100644 index 0000000000..f53217f869 --- /dev/null +++ b/docs/howto.md @@ -0,0 +1,101 @@ +# How to + +## Create float left image + +Use absolute positioning relative to margin horizontally and to line vertically. + +``` php + 40, + 'height' => 40, + 'wrappingStyle' => 'square', + 'positioning' => 'absolute', + 'posHorizontalRel' => 'margin', + 'posVerticalRel' => 'line', +); +$textrun->addImage('resources/_earth.jpg', $imageStyle); +``` + +## Download the produced file automatically + +Use ``php://output`` as the filename. + +``` php +addSection(); +$section->addText('Hello World!'); +$file = 'HelloWorld.docx'; +header("Content-Description: File Transfer"); +header('Content-Disposition: attachment; filename="' . $file . '"'); +header('Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document'); +header('Content-Transfer-Encoding: binary'); +header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); +header('Expires: 0'); +$xmlWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007'); +$xmlWriter->save("php://output"); +``` + +## Create numbered headings + +Define a numbering style and title styles, and match the two styles (with ``pStyle`` and ``numStyle``) like below. + +``` php +addNumberingStyle( + 'hNum', + array('type' => 'multilevel', 'levels' => array( + array('pStyle' => 'Heading1', 'format' => 'decimal', 'text' => '%1'), + array('pStyle' => 'Heading2', 'format' => 'decimal', 'text' => '%1.%2'), + array('pStyle' => 'Heading3', 'format' => 'decimal', 'text' => '%1.%2.%3'), + ) + ) +); +$phpWord->addTitleStyle(1, array('size' => 16), array('numStyle' => 'hNum', 'numLevel' => 0)); +$phpWord->addTitleStyle(2, array('size' => 14), array('numStyle' => 'hNum', 'numLevel' => 1)); +$phpWord->addTitleStyle(3, array('size' => 12), array('numStyle' => 'hNum', 'numLevel' => 2)); + +$section->addTitle('Heading 1', 1); +$section->addTitle('Heading 2', 2); +$section->addTitle('Heading 3', 3); +``` + +## Add a link within a title + +Apply 'HeadingN' paragraph style to TextRun or Link. Sample code: + +``` php +addTitleStyle(1, array('size' => 16, 'bold' => true)); +$phpWord->addTitleStyle(2, array('size' => 14, 'bold' => true)); +$phpWord->addFontStyle('Link', array('color' => '0000FF', 'underline' => 'single')); + +$section = $phpWord->addSection(); + +// Textrun +$textrun = $section->addTextRun('Heading1'); +$textrun->addText('The '); +$textrun->addLink('https://github.com/PHPOffice/PHPWord', 'PHPWord', 'Link'); + +// Link +$section->addLink('https://github.com/', 'GitHub', 'Link', 'Heading2'); +``` + +## Remove [Compatibility Mode] text in the MS Word title bar + +Use the ``Metadata\Compatibility\setOoxmlVersion(n)`` method with ``n`` is the version of Office: + +* 14 = Office 2010 +* 15 = Office 2013 + +``` php +getCompatibility()->setOoxmlVersion(15); +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000000..211fc31a79 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,115 @@ +# + +![PHPWord](images/phpword.svg) + +PHPWord is a library written in pure PHP that provides a set ofclasses to write to different document file formats, i.e. [Microsoft Office Open XML](http://en.wikipedia.org/wiki/Office_Open_XML)(`.docx`), OASIS [Open Document Format for Office Applications](http://en.wikipedia.org/wiki/OpenDocument) (`.odt`), [Rich Text Format](http://en.wikipedia.org/wiki/Rich_Text_Format) (`.rtf`), [Microsoft Word Binary File](https://en.wikipedia.org/wiki/Doc_(computing)) (`.doc`), HTML (`.html`), and PDF (`.pdf`). + +PHPWord is an open source project licensed under the terms of [LGPL version 3](https://github.com/PHPOffice/PHPWord/blob/master/COPYING.LESSER). PHPWord is aimed to be a high quality software product by incorporating [continuous integration and unit testing](https://github.com/PHPOffice/PHPWord/actions/workflows/php.yml). You can learn more about PHPWord by reading this Developers'Documentation. + + +## Features + +- Set document properties, e.g. title, subject, and creator. +- Create document sections with different settings, e.g. portrait/landscape, page size, and page numbering +- Create header and footer for each sections +- Set default font type, font size, and paragraph style +- Use UTF-8 and East Asia fonts/characters +- Define custom font styles (e.g. bold, italic, color) and paragraph styles (e.g. centered, multicolumns, spacing) either as named style or inline in text +- Insert paragraphs, either as a simple text or complex one (a text run) that contains other elements +- Insert titles (headers) and table of contents +- Insert text breaks and page breaks +- Insert and format images, either local, remote, or as page watermarks +- Insert binary OLE Objects such as Excel or Visio +- Insert and format table with customized properties for each rows (e.g. repeat as header row) and cells (e.g. background color, rowspan, colspan) +- Insert list items as bulleted, numbered, or multilevel +- Insert hyperlinks +- Insert footnotes and endnotes +- Insert drawing shapes (arc, curve, line, polyline, rect, oval) +- Insert charts (pie, doughnut, bar, line, area, scatter, radar) +- Insert form fields (textinput, checkbox, and dropdown) +- Create document from templates +- Use XSL 1.0 style sheets to transform headers, main document part, and footers of an OOXML template +- ... and many more features on progress + +## File formats + +Below are the supported features for each file formats. + + +### Writers + + +| Features | | OOXML | ODF | RTF | HTML | PDF | +|---------------------------|----------------------|--------|-------|-------|--------|--------| +| **Document Properties** | Standard | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | +| | Custom | :material-check: | :material-check: | | | | +| **Element Type** | Text | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | +| | Text Run | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | +| | Title | :material-check: | :material-check: | | :material-check: | :material-check: | +| | Link | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | +| | Preserve Text | :material-check: | | | | | +| | Text Break | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | +| | Page Break | :material-check: | | :material-check: | | | +| | List | :material-check: | :material-check: | | | | +| | Table | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | +| | Image | :material-check: | :material-check: | :material-check: | :material-check: | | +| | Object | :material-check: | | | | | +| | Watermark | :material-check: | | | | | +| | Table of Contents | :material-check: | | | | | +| | Header | :material-check: | | | | | +| | Footer | :material-check: | | | | | +| | Footnote | :material-check: | | | :material-check: | | +| | Endnote | :material-check: | | | :material-check: | | +| | Comments | :material-check: | | | | | +| **Graphs** | 2D basic graphs | :material-check: | | | | | +| | 2D advanced graphs | | | | | | +| | 3D graphs | :material-check: | | | | | +| **Math** | OMML support | :material-check: | | | | | +| | MathML support | | :material-check: | | | | +| **Bonus** | Encryption | | | | | | +| | Protection | | | | | | + +### Readers + + +| Features | | OOXML | DOC | ODF | RTF | HTML | +|---------------------------|----------------------|--------|-------|-------|-------|-------| +| **Document Properties** | Standard | :material-check: | | | | | +| | Custom | :material-check: | | | | | +| **Element Type** | Text | :material-check: | :material-check: | :material-check: | :material-check: | :material-check: | +| | Text Run | :material-check: | | | | | +| | Title | :material-check: | | :material-check: | | | +| | Link | :material-check: | :material-check: | | | | +| | Preserve Text | :material-check: | | | | | +| | Text Break | :material-check: | :material-check: | | | | +| | Page Break | :material-check: | | | | | +| | List | :material-check: | | :material-check: | | :material-check: | +| | Table | :material-check: | | | | :material-check: | +| | Image | :material-check: | :material-check: | | | | +| | Object | | | | | | +| | Watermark | | | | | | +| | Table of Contents | | | | | | +| | Header | :material-check: | | | | | +| | Footer | :material-check: | | | | | +| | Footnote | :material-check: | | | | | +| | Endnote | :material-check: | | | | | +| | Comments | :material-check: | | | | | +| **Graphs** | 2D basic graphs | | | | | | +| | 2D advanced graphs | | | | | | +| | 3D graphs | | | | | | +| **Math** | OMML support | :material-check: | | | | | +| | MathML support | | :material-check: | | | | +| **Bonus** | Encryption | | | | | | +| | Protection | | | | | | + + +## Contributing + +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) +- [Fork us](https://github.com/PHPOffice/PHPWord/fork) and [request a pull](https://github.com/PHPOffice/PHPWord/pulls) to the [master](https://github.com/PHPOffice/PHPWord/tree/master) branch +- Submit [bug reports or feature requests](https://github.com/PHPOffice/PHPWord/issues) to GitHub +- Follow [@PHPOffice](https://twitter.com/PHPOffice) on Twitter diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 671c32a64c..0000000000 --- a/docs/index.rst +++ /dev/null @@ -1,40 +0,0 @@ -.. PHPWord documentation master file, created by - sphinx-quickstart on Fri Mar 14 23:09:26 2014. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to PHPWord's documentation -================================== - -|PHPWord| - -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 (OOXML or OpenXML), OASIS Open -Document Format for Office Applications (OpenDocument or ODF), and Rich Text -Format (RTF). - -.. toctree:: - :maxdepth: 2 - - intro - installing - general - containers - elements - styles - templates-processing - writersreaders - recipes - faq - credits - references - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - -.. |PHPWord| image:: images/phpword.png diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 0000000000..1b54a3173f --- /dev/null +++ b/docs/install.md @@ -0,0 +1,52 @@ +# Installation + +## Requirements + +Mandatory: + +- PHP 7.1+ +- PHP [DOM extension](http://php.net/manual/en/book.dom.php) +- PHP [JSON extension](http://php.net/manual/en/book.json.php) +- PHP [XML Parser extension](http://www.php.net/manual/en/xml.installation.php) +- PHP [XMLWriter extension](http://php.net/manual/en/book.xmlwriter.php) + + +## Installation + +There are two ways to install PHPWord, i.e. via [Composer](http://getcomposer.org) or manually by downloading the library. + +### Using Composer + +To install via Composer, add the following lines to your `composer.json`: + +``` json +{ + "require": { + "phpoffice/phpword": "dev-master" + } +} +``` + + +### Using manual install +To install manually: + +* [download PHPOffice\PHPWord package from GitHub](https://github.com/PHPOffice/PHPWord/archive/master.zip) +* [download PHPOffice\Common package from GitHub](https://github.com/PHPOffice/Common/archive/master.zip) +* extract the package and put the contents to your machine. + + +``` php +`__ extension - -Optional: - -- `Zip `__ extension -- `GD `__ extension -- `XMLWriter `__ extension -- `XSL `__ extension -- `dompdf `__ library - -Installation ------------- - -PHPWord is installed via `Composer `__. -You just need to `add dependency `__ on PHPWord into your package. - -Example: - -.. code-block:: bash - - composer require phpoffice/phpword - -If you are a developer or if you want to help us with testing then fetch the latest branch for developers. -Notice: all contributions must be done against the developer branch. - -Example: - -.. code-block:: bash - - composer require phpoffice/phpword:dev-develop - -Using samples -------------- - -More examples are provided in the ``samples`` directory. -For an easy access to those samples launch ``php -S localhost:8000`` in the samples directory then browse to http://localhost:8000 to view the samples. diff --git a/docs/intro.rst b/docs/intro.rst deleted file mode 100644 index d88cd626dd..0000000000 --- a/docs/intro.rst +++ /dev/null @@ -1,199 +0,0 @@ -.. _intro: - -Introduction -============ - -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 `__ (OOXML or -OpenXML), OASIS `Open Document Format for Office -Applications `__ -(OpenDocument or ODF), and `Rich Text -Format `__ (RTF). - -PHPWord is an open source project licensed under the terms of `LGPL -version 3 `__. -PHPWord is aimed to be a high quality software product by incorporating -`continuous integration `__ and -`unit testing `__. -You can learn more about PHPWord by reading this Developers' -Documentation and the `API -Documentation `__. - -Features --------- - -- Set document properties, e.g. title, subject, and creator. -- Create document sections with different settings, e.g. - portrait/landscape, page size, and page numbering -- Create header and footer for each sections -- Set default font type, font size, and paragraph style -- Use UTF-8 and East Asia fonts/characters -- Define custom font styles (e.g. bold, italic, color) and paragraph - styles (e.g. centered, multicolumns, spacing) either as named style - or inline in text -- Insert paragraphs, either as a simple text or complex one (a text - run) that contains other elements -- Insert titles (headers) and table of contents -- Insert text breaks and page breaks -- Insert right-to-left text -- Insert and format images, either local, remote, or as page watermarks -- Insert binary OLE Objects such as Excel or Visio -- Insert and format table with customized properties for each rows - (e.g. repeat as header row) and cells (e.g. background color, - rowspan, colspan) -- Insert list items as bulleted, numbered, or multilevel -- Insert hyperlinks -- Insert footnotes and endnotes -- Insert drawing shapes (arc, curve, line, polyline, rect, oval) -- Insert charts (pie, doughnut, bar, line, area, scatter, radar) -- Insert form fields (textinput, checkbox, and dropdown) -- Insert comments -- Create document from templates -- Use XSL 1.0 style sheets to transform headers, main document part, and footers of an OOXML template -- ... and many more features on progress - -File formats ------------- - -Below are the supported features for each file formats. - -Writers -~~~~~~~ - -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| Features | | OOXML | ODF | RTF | HTML | PDF | -+===========================+======================+========+=======+=======+========+=======+ -| **Document Properties** | Standard | ✓ | ✓ | ✓ | ✓ | ✓ | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Custom | ✓ | ✓ | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| **Element Type** | Text | ✓ | ✓ | ✓ | ✓ | ✓ | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Text Run | ✓ | ✓ | ✓ | ✓ | ✓ | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Title | ✓ | ✓ | | ✓ | ✓ | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Link | ✓ | ✓ | ✓ | ✓ | ✓ | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Preserve Text | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Text Break | ✓ | ✓ | ✓ | ✓ | ✓ | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Page Break | ✓ | | ✓ | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | List | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Table | ✓ | ✓ | ✓ | ✓ | ✓ | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Image | ✓ | ✓ | ✓ | ✓ | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Object | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Watermark | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Table of Contents | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Header | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Footer | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Footnote | ✓ | | | ✓ | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Endnote | ✓ | | | ✓ | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Comments | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| **Graphs** | 2D basic graphs | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | 2D advanced graphs | | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | 3D graphs | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| **Math** | OMML support | | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | MathML support | | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| **Bonus** | Encryption | | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ -| | Protection | | | | | | -+---------------------------+----------------------+--------+-------+-------+--------+-------+ - -Readers -~~~~~~~ - -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| Features | | OOXML | DOC | ODF | RTF | HTML | -+===========================+======================+========+=======+=======+=======+=======+ -| **Document Properties** | Standard | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Custom | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| **Element Type** | Text | ✓ | ✓ | ✓ | ✓ | ✓ | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Text Run | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Title | ✓ | | ✓ | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Link | ✓ | ✓ | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Preserve Text | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Text Break | ✓ | ✓ | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Page Break | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | List | ✓ | | ✓ | | ✓ | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Table | ✓ | | | | ✓ | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Image | ✓ | ✓ | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Object | | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Watermark | | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Table of Contents | | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Header | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Footer | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Footnote | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Endnote | ✓ | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Comments | | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| **Graphs** | 2D basic graphs | | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | 2D advanced graphs | | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | 3D graphs | | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| **Math** | OMML support | | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | MathML support | | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| **Bonus** | Encryption | | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ -| | Protection | | | | | | -+---------------------------+----------------------+--------+-------+-------+-------+-------+ - -Contributing ------------- - -We welcome everyone to contribute to PHPWord. Below are some of the -things that you can do to contribute. - -- Read `our contributing - guide `__. -- `Fork us `__ and `request - a pull `__ to the - `develop `__ - branch. -- Submit `bug reports or feature - requests `__ to GitHub. -- Follow `@PHPWord `__ and - `@PHPOffice `__ on Twitter. diff --git a/docs/recipes.rst b/docs/recipes.rst deleted file mode 100644 index 5042cdedc6..0000000000 --- a/docs/recipes.rst +++ /dev/null @@ -1,97 +0,0 @@ -.. _recipes: - -Recipes -======= - -Create float left image ------------------------ - -Use absolute positioning relative to margin horizontally and to line vertically. - -.. code-block:: php - - $imageStyle = array( - 'width' => 40, - 'height' => 40, - 'wrappingStyle' => 'square', - 'positioning' => 'absolute', - 'posHorizontalRel' => 'margin', - 'posVerticalRel' => 'line', - ); - $textrun->addImage('resources/_earth.jpg', $imageStyle); - $textrun->addText($lipsumText); - -Download the produced file automatically ----------------------------------------- - -Use ``php://output`` as the filename. - -.. code-block:: php - - $phpWord = new \PhpOffice\PhpWord\PhpWord(); - $section = $phpWord->createSection(); - $section->addText('Hello World!'); - $file = 'HelloWorld.docx'; - header("Content-Description: File Transfer"); - header('Content-Disposition: attachment; filename="' . $file . '"'); - header('Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document'); - header('Content-Transfer-Encoding: binary'); - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Expires: 0'); - $xmlWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007'); - $xmlWriter->save("php://output"); - -Create numbered headings ------------------------- - -Define a numbering style and title styles, and match the two styles (with ``pStyle`` and ``numStyle``) like below. - -.. code-block:: php - - $phpWord->addNumberingStyle( - 'hNum', - array('type' => 'multilevel', 'levels' => array( - array('pStyle' => 'Heading1', 'format' => 'decimal', 'text' => '%1'), - array('pStyle' => 'Heading2', 'format' => 'decimal', 'text' => '%1.%2'), - array('pStyle' => 'Heading3', 'format' => 'decimal', 'text' => '%1.%2.%3'), - ) - ) - ); - $phpWord->addTitleStyle(1, array('size' => 16), array('numStyle' => 'hNum', 'numLevel' => 0)); - $phpWord->addTitleStyle(2, array('size' => 14), array('numStyle' => 'hNum', 'numLevel' => 1)); - $phpWord->addTitleStyle(3, array('size' => 12), array('numStyle' => 'hNum', 'numLevel' => 2)); - - $section->addTitle('Heading 1', 1); - $section->addTitle('Heading 2', 2); - $section->addTitle('Heading 3', 3); - -Add a link within a title -------------------------- - -Apply 'HeadingN' paragraph style to TextRun or Link. Sample code: - -.. code-block:: php - - $phpWord = new \PhpOffice\PhpWord\PhpWord(); - $phpWord->addTitleStyle(1, array('size' => 16, 'bold' => true)); - $phpWord->addTitleStyle(2, array('size' => 14, 'bold' => true)); - $phpWord->addFontStyle('Link', array('color' => '0000FF', 'underline' => 'single')); - - $section = $phpWord->addSection(); - - // Textrun - $textrun = $section->addTextRun('Heading1'); - $textrun->addText('The '); - $textrun->addLink('https://github.com/PHPOffice/PHPWord', 'PHPWord', 'Link'); - - // Link - $section->addLink('https://github.com/', 'GitHub', 'Link', 'Heading2'); - -Remove [Compatibility Mode] text in the MS Word title bar ---------------------------------------------------------- - -Use the ``Metadata\Compatibility\setOoxmlVersion(n)`` method with ``n`` is the version of Office (14 = Office 2010, 15 = Office 2013). - -.. code-block:: php - - $phpWord->getCompatibility()->setOoxmlVersion(15); diff --git a/docs/references.rst b/docs/references.rst deleted file mode 100644 index a17f558db6..0000000000 --- a/docs/references.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. _references: - -References -========== - -ISO/IEC 29500, Third edition, 2012-09-01 ----------------------------------------- - -- `Part 1: Fundamentals and Markup Language Reference - `__ -- `Part 2: Open Packaging Conventions - `__ -- `Part 3: Markup Compatibility and Extensibility - `__ -- `Part 4: Transitional Migration Features - `__ - -Formal specifications ---------------------- - -- `Oasis OpenDocument Standard Version 1.2 `__ -- `Rich Text Format (RTF) Specification, version 1.9.1 `__ - -Other resources ---------------- - -- `DocumentFormat.OpenXml.Wordprocessing Namespace on - MSDN `__ diff --git a/docs/styles.rst b/docs/styles.rst deleted file mode 100644 index 075dd3967d..0000000000 --- a/docs/styles.rst +++ /dev/null @@ -1,213 +0,0 @@ -.. _styles: - -Styles -====== - -.. _section-style: - -Section -------- - -Available Section style options: - -- ``borderBottomColor``. Border bottom color. -- ``borderBottomSize``. Border bottom size in *twip*. -- ``borderLeftColor``. Border left color. -- ``borderLeftSize``. Border left size in *twip*. -- ``borderRightColor``. Border right color. -- ``borderRightSize``. Border right size in *twip*. -- ``borderTopColor``. Border top color. -- ``borderTopSize``. Border top size in *twip*. -- ``breakType``. Section break type (nextPage, nextColumn, continuous, evenPage, oddPage). -- ``colsNum``. Number of columns. -- ``colsSpace``. Spacing between columns. -- ``footerHeight``. Spacing to bottom of footer. -- ``gutter``. Page gutter spacing. -- ``headerHeight``. Spacing to top of header. -- ``marginTop``. Page margin top in *twip*. -- ``marginLeft``. Page margin left in *twip*. -- ``marginRight``. Page margin right in *twip*. -- ``marginBottom``. Page margin bottom in *twip*. -- ``orientation``. Page orientation (``portrait``, which is default, or ``landscape``). - 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: - -Font ----- - -Available Font style options: - -- ``allCaps``. All caps, *true* or *false*. -- ``bgColor``. Font background color, e.g. *FF0000*. -- ``bold``. Bold, *true* or *false*. -- ``color``. Font color, e.g. *FF0000*. -- ``doubleStrikethrough``. Double strikethrough, *true* or *false*. -- ``fgColor``. Font highlight color, e.g. *yellow*, *green*, *blue*. - See ``\PhpOffice\PhpWord\Style\Font::FGCOLOR_...`` class constants for possible values -- ``hint``. Font content type, *default*, *eastAsia*, or *cs*. -- ``italic``. Italic, *true* or *false*. -- ``name``. Font name, e.g. *Arial*. -- ``rtl``. Right to Left language, *true* or *false*. -- ``size``. Font size, e.g. *20*, *22*. -- ``smallCaps``. Small caps, *true* or *false*. -- ``strikethrough``. Strikethrough, *true* or *false*. -- ``subScript``. Subscript, *true* or *false*. -- ``superScript``. Superscript, *true* or *false*. -- ``underline``. Underline, *single*, *dash*, *dotted*, etc. - See ``\PhpOffice\PhpWord\Style\Font::UNDERLINE_...`` class constants for possible values -- ``lang``. Language, either a language code like *en-US*, *fr-BE*, etc. or an object (or as an array) if you need to set eastAsian or bidirectional languages - See ``\PhpOffice\PhpWord\Style\Language`` class for some language codes. -- ``position``. The text position, raised or lowered, in half points -- ``hidden``. Hidden text, *true* or *false*. - -.. _paragraph-style: - -Paragraph ---------- - -Available Paragraph style options: - -- ``alignment``. Supports all alignment modes since 1st Edition of ECMA-376 standard up till ISO/IEC 29500:2012. - See ``\PhpOffice\PhpWord\SimpleType\Jc`` class constants for possible values. -- ``basedOn``. Parent style. -- ``hanging``. Hanging indentation in *half inches*. -- ``indent``. Indent (left indentation) in *half inches*. -- ``indentation``. An array of indentation key => value pairs in *twip*. Supports *left*, *right*, *firstLine* and *hanging* indentation. - See ``\PhpOffice\PhpWord\Style\Indentation`` for possible identation types. -- ``keepLines``. Keep all lines on one page, *true* or *false*. -- ``keepNext``. Keep paragraph with next paragraph, *true* or *false*. -- ``lineHeight``. Text line height, e.g. *1.0*, *1.5*, etc. -- ``next``. Style for next paragraph. -- ``pageBreakBefore``. Start paragraph on next page, *true* or *false*. -- ``spaceBefore``. Space before paragraph in *twip*. -- ``spaceAfter``. Space after paragraph in *twip*. -- ``spacing``. Space between lines in *twip*. If spacingLineRule is auto, 240 (height of 1 line) will be added, so if you want a double line height, set this to 240. -- ``spacingLineRule``. Line Spacing Rule. *auto*, *exact*, *atLeast* - See ``\PhpOffice\PhpWord\SimpleType\LineSpacingRule`` class constants for possible values. -- ``suppressAutoHyphens``. Hyphenation for paragraph, *true* or *false*. -- ``tabs``. Set of custom tab stops. -- ``widowControl``. Allow first/last line to display on a separate page, *true* or *false*. -- ``contextualSpacing``. Ignore Spacing Above and Below When Using Identical Styles, *true* or *false*. -- ``bidi``. Right to Left Paragraph Layout, *true* or *false*. -- ``shading``. Paragraph Shading. -- ``textAlignment``. Vertical Character Alignment on Line. - See ``\PhpOffice\PhpWord\SimpleType\TextAlignment`` class constants for possible values. - -.. _table-style: - -Table ------ - -Available Table style options: - -- ``alignment``. Supports all alignment modes since 1st Edition of ECMA-376 standard up till ISO/IEC 29500:2012. - See ``\PhpOffice\PhpWord\SimpleType\JcTable`` and ``\PhpOffice\PhpWord\SimpleType\Jc`` class constants for possible values. -- ``bgColor``. Background color, e.g. '9966CC'. -- ``border(Top|Right|Bottom|Left)Color``. Border color, e.g. '9966CC'. -- ``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 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: - -- ``leftFromText`` Distance From Left of Table to Text in *twip* -- ``rightFromText`` Distance From Right of Table to Text in *twip* -- ``topFromText`` Distance From Top of Table to Text in *twip* -- ``bottomFromText`` Distance From Top of Table to Text in *twip* -- ``vertAnchor`` Table Vertical Anchor, one of ``\PhpOffice\PhpWord\Style\TablePosition::VANCHOR_*`` -- ``horzAnchor`` Table Horizontal Anchor, one of ``\PhpOffice\PhpWord\Style\TablePosition::HANCHOR_*`` -- ``tblpXSpec`` Relative Horizontal Alignment From Anchor, one of ``\PhpOffice\PhpWord\Style\TablePosition::XALIGN_*`` -- ``tblpX`` Absolute Horizontal Distance From Anchorin *twip* -- ``tblpYSpec`` Relative Vertical Alignment From Anchor, one of ``\PhpOffice\PhpWord\Style\TablePosition::YALIGN_*`` -- ``tblpY`` Absolute Vertical Distance From Anchorin *twip* - -Available Row style options: - -- ``cantSplit``. Table row cannot break across pages, *true* or *false*. -- ``exactHeight``. Row height is exact or at least. -- ``tblHeader``. Repeat table row on every new page, *true* or *false*. - -Available Cell style options: - -- ``bgColor``. Background color, e.g. '9966CC'. -- ``border(Top|Right|Bottom|Left)Color``. Border color, e.g. '9966CC'. -- ``border(Top|Right|Bottom|Left)Size``. Border size in *twip*. -- ``border(Top|Right|Bottom|Left)Style``. Border style. You can use constants from ``\PhpOffice\PhpWord\SimpleType\Border`` -- ``gridSpan``. Number of columns spanned. -- ``textDirection(btLr|tbRl)``. Direction of text. - You can use constants ``\PhpOffice\PhpWord\Style\Cell::TEXT_DIR_BTLR`` and ``\PhpOffice\PhpWord\Style\Cell::TEXT_DIR_TBRL`` -- ``valign``. Vertical alignment, *top*, *center*, *both*, *bottom*. -- ``vMerge``. *restart* or *continue*. -- ``width``. Cell width in *twip*. - -.. _image-style: - -Image ------ - -Available Image style options: - -- ``alignment``. See ``\PhpOffice\PhpWord\SimpleType\Jc`` class for the details. -- ``height``. Height in *pt*. -- ``marginLeft``. Left margin in inches, can be negative. -- ``marginTop``. Top margin in inches, can be negative. -- ``width``. Width in *pt*. -- ``wrappingStyle``. Wrapping style, *inline*, *square*, *tight*, *behind*, or *infront*. -- ``wrapDistanceTop``. Top text wrapping in pixels. -- ``wrapDistanceBottom``. Bottom text wrapping in pixels. -- ``wrapDistanceLeft``. Left text wrapping in pixels. -- ``wrapDistanceRight``. Right text wrapping in pixels. - -.. _numbering-level-style: - -Numbering level ---------------- - -Available NumberingLevel style options: - -- ``alignment``. Supports all alignment modes since 1st Edition of ECMA-376 standard up till ISO/IEC 29500:2012. - See ``\PhpOffice\PhpWord\SimpleType\Jc`` class constants for possible values. -- ``font``. Font name. -- ``format``. Numbering format bullet\|decimal\|upperRoman\|lowerRoman\|upperLetter\|lowerLetter. -- ``hanging``. See paragraph style. -- ``hint``. See font style. -- ``left``. See paragraph style. -- ``restart``. Restart numbering level symbol. -- ``start``. Starting value. -- ``suffix``. Content between numbering symbol and paragraph text tab\|space\|nothing. -- ``tabPos``. See paragraph style. -- ``text``. Numbering level text e.g. %1 for nonbullet or bullet character. - -.. _chart-style: - -Chart ------ - -Available Chart style options: - -- ``width``. Width (in EMU). -- ``height``. Height (in EMU). -- ``3d``. Is 3D; applies to pie, bar, line, area, *true* or *false*. -- ``colors``. A list of colors to use in the chart. -- ``title``. The title for the chart. -- ``showLegend``. Show legend, *true* or *false*. -- ``LegendPosition``. Legend position, *r* (default), *b*, *t*, *l* or *tr*. -- ``categoryLabelPosition``. Label position for categories, *nextTo* (default), *low* or *high*. -- ``valueLabelPosition``. Label position for values, *nextTo* (default), *low* or *high*. -- ``categoryAxisTitle``. The title for the category axis. -- ``valueAxisTitle``. The title for the values axis. -- ``majorTickMarkPos``. The position for major tick marks, *in*, *out*, *cross*, *none* (default). -- ``showAxisLabels``. Show labels for axis, *true* or *false*. -- ``gridX``. Show Gridlines for X-Axis, *true* or *false*. -- ``gridY``. Show Gridlines for Y-Axis, *true* or *false*. diff --git a/docs/templates-processing.rst b/docs/templates-processing.rst deleted file mode 100644 index 7d0ef2e654..0000000000 --- a/docs/templates-processing.rst +++ /dev/null @@ -1,279 +0,0 @@ -.. _templates-processing: - -Templates processing -==================== - -You can create an OOXML document template with included search-patterns (macros) which can be replaced by any value you wish. Only single-line values can be replaced. -Macros are defined like this: ``${search-pattern}``. -To load a template file, create a new instance of the TemplateProcessor. - -.. code-block:: php - - $templateProcessor = new TemplateProcessor('Template.docx'); - -setValue -"""""""" -Given a template containing - -.. code-block:: clean - - Hello ${firstname} ${lastname}! - -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('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 -""""""""""""" -The search-pattern model for images can be like: - - ``${search-image-pattern}`` - - ``${search-image-pattern:[width]:[height]:[ratio]}`` - - ``${search-image-pattern:[width]x[height]}`` - - ``${search-image-pattern:size=[width]x[height]}`` - - ``${search-image-pattern:width=[width]:height=[height]:ratio=false}`` - -Where: - - [width] and [height] can be just numbers or numbers with measure, which supported by Word (cm, mm, in, pt, pc, px, %, em, ex) - - [ratio] uses only for ``false``, ``-`` or ``f`` to turn off respect aspect ration of image. By default template image size uses as 'container' size. - -Example: - -.. code-block:: clean - - ${CompanyLogo} - ${UserLogo:50:50} ${Name} - ${City} - ${Street} - -.. code-block:: php - - $templateProcessor = new TemplateProcessor('Template.docx'); - $templateProcessor->setValue('Name', 'John Doe'); - $templateProcessor->setValue(array('City', 'Street'), array('Detroit', '12th Street')); - - $templateProcessor->setImageValue('CompanyLogo', 'path/to/company/logo.png'); - $templateProcessor->setImageValue('UserLogo', array('path' => 'path/to/logo.png', 'width' => 100, 'height' => 100, 'ratio' => false)); - $templateProcessor->setImageValue('FeatureImage', function () { - // Closure will only be executed if the replacement tag is found in the template - - return array('path' => SlowFeatureImageGenerator::make(), 'width' => 100, 'height' => 100, 'ratio' => false); - }); - -cloneBlock -"""""""""" -Given a template containing -See ``Sample_23_TemplateBlock.php`` for an example. - -.. code-block:: clean - - ${block_name} - Customer: ${customer_name} - Address: ${customer_address} - ${/block_name} - -The following will duplicate everything between ``${block_name}`` and ``${/block_name}`` 3 times. - -.. code-block:: php - - $templateProcessor->cloneBlock('block_name', 3, true, true); - -The last parameter will rename any macro defined inside the block and add #1, #2, #3 ... to the macro name. -The result will be - -.. code-block:: clean - - Customer: ${customer_name#1} - Address: ${customer_address#1} - - Customer: ${customer_name#2} - Address: ${customer_address#2} - - Customer: ${customer_name#3} - Address: ${customer_address#3} - -It is also possible to pass an array with the values to replace the marcros with. -If an array with replacements is passed, the ``count`` argument is ignored, it is the size of the array that counts. - -.. code-block:: php - - $replacements = array( - array('customer_name' => 'Batman', 'customer_address' => 'Gotham City'), - array('customer_name' => 'Superman', 'customer_address' => 'Metropolis'), - ); - $templateProcessor->cloneBlock('block_name', 0, true, false, $replacements); - -The result will then be - -.. code-block:: clean - - Customer: Batman - Address: Gotham City - - Customer: Superman - Address: Metropolis - -replaceBlock -"""""""""""" -Given a template containing - -.. code-block:: clean - - ${block_name} - This block content will be replaced - ${/block_name} - -The following will replace everything between ``${block_name}`` and ``${/block_name}`` with the value passed. - -.. code-block:: php - - $templateProcessor->replaceBlock('block_name', 'This is the replacement text.'); - -deleteBlock -""""""""""" -Same as previous, but it deletes the block - -.. code-block:: php - - $templateProcessor->deleteBlock('block_name'); - -cloneRow -"""""""" -Clones a table row in a template document. -See ``Sample_07_TemplateCloneRow.php`` for an example. - -.. code-block:: clean - - +-----------+----------------+ - | ${userId} | ${userName} | - | |----------------+ - | | ${userAddress} | - +-----------+----------------+ - -.. code-block:: php - - $templateProcessor->cloneRow('userId', 2); - -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', $values); - -Will result in - -.. code-block:: clean - - +---+-------------+ - | 1 | Batman | - | |-------------+ - | | Gotham City | - +---+-------------+ - | 2 | Superman | - | |-------------+ - | | Metropolis | - +---+-------------+ - -applyXslStyleSheet -"""""""""""""""""" -Applies the XSL stylesheet passed to header part, footer part and main part - -.. code-block:: php - - $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); - -setChartValue -""""""""""""" -Replace a variable by a chart. - -.. code-block:: php - - $categories = array('A', 'B', 'C', 'D', 'E'); - $series1 = array(1, 3, 2, 5, 4); - $chart = new Chart('doughnut', $categories, $series1); - $templateProcessor->setChartValue('myChart', $chart); - -save -"""" -Saves the loaded template within the current directory. Returns the file path. - -.. code-block:: php - - $filepath = $templateProcessor->save(); - -saveAs -"""""" -Saves a copy of the loaded template in the indicated path. - -.. code-block:: php - - $pathToSave = 'path/to/save/file.ext'; - $templateProcessor->saveAs($pathToSave); diff --git a/docs/containers.rst b/docs/usage/containers.md similarity index 59% rename from docs/containers.rst rename to docs/usage/containers.md index 9ee58efcd4..e85a700f48 100644 --- a/docs/containers.rst +++ b/docs/usage/containers.md @@ -1,79 +1,77 @@ -.. _containers: +# Containers -Containers -========== +Containers are objects where you can put elements (texts, lists, tables, etc). There are 3 main containers, i.e. sections, headers, and footers.There are 3 elements that can also act as containers, i.e. textruns, table cells, and footnotes. -Containers are objects where you can put elements (texts, lists, tables, -etc). There are 3 main containers, i.e. sections, headers, and footers. -There are 3 elements that can also act as containers, i.e. textruns, -table cells, and footnotes. +## Sections -Sections --------- +Every visible element in word is placed inside of a section. To create a section, use the following code: -Every visible element in word is placed inside of a section. To create a -section, use the following code: +``` php +addSection($sectionStyle); +``` - $section = $phpWord->addSection($sectionStyle); +The ``$sectionStyle`` is an optional associative array that sets the section. Example: -The ``$sectionStyle`` is an optional associative array that sets the -section. Example: +``` php + 'landscape', + 'marginTop' => 600, + 'colsNum' => 2, +); +``` - $sectionStyle = array( - 'orientation' => 'landscape', - 'marginTop' => 600, - 'colsNum' => 2, - ); - -Page number -~~~~~~~~~~~ +### Page number You can change a section page number by using the ``pageNumberingStart`` style of the section. -.. code-block:: php +``` php +addSection(array('pageNumberingStart' => 1)); +// Method 1 +$section = $phpWord->addSection(array('pageNumberingStart' => 1)); - // Method 2 - $section = $phpWord->addSection(); - $section->getStyle()->setPageNumberingStart(1); +// Method 2 +$section = $phpWord->addSection(); +$section->getStyle()->setPageNumberingStart(1); +``` -Multicolumn -~~~~~~~~~~~ +### Multicolumn You can change a section layout to multicolumn (like in a newspaper) by using the ``breakType`` and ``colsNum`` style of the section. -.. code-block:: php +``` php +addSection(array('breakType' => 'continuous', 'colsNum' => 2)); +// Method 1 +$section = $phpWord->addSection(array('breakType' => 'continuous', 'colsNum' => 2)); - // Method 2 - $section = $phpWord->addSection(); - $section->getStyle()->setBreakType('continuous'); - $section->getStyle()->setColsNum(2); +// Method 2 +$section = $phpWord->addSection(); +$section->getStyle()->setBreakType('continuous'); +$section->getStyle()->setColsNum(2); +``` -Line numbering -~~~~~~~~~~~~~~ +### Line numbering You can apply line numbering to a section by using the ``lineNumbering`` style of the section. -.. code-block:: php +``` php +addSection(array('lineNumbering' => array())); +// Method 1 +$section = $phpWord->addSection(array('lineNumbering' => array())); - // Method 2 - $section = $phpWord->addSection(); - $section->getStyle()->setLineNumbering(array()); +// Method 2 +$section = $phpWord->addSection(); +$section->getStyle()->setLineNumbering(array()); +``` Below are the properties of the line numbering style. @@ -83,15 +81,16 @@ Below are the properties of the line numbering style. - ``restart`` Line numbering restart setting continuous\|newPage\|newSection -Headers -------- +## Headers Each section can have its own header reference. To create a header use the ``addHeader`` method: -.. code-block:: php +``` php +addHeader(); +$header = $section->addHeader(); +``` Be sure to save the result in a local object. You can use all elements that are available for the footer. See "Footer" section for detail. @@ -106,19 +105,22 @@ You can pass an optional parameter to specify where the header/footer should be To change the evenAndOddHeaders use the ``getSettings`` method to return the Settings object, and then call the ``setEvenAndOddHeaders`` method: -.. code-block:: php +``` php +getSettings()->setEvenAndOddHeaders(true); +$phpWord->getSettings()->setEvenAndOddHeaders(true); +``` -Footers -------- +## Footers Each section can have its own footer reference. To create a footer, use the ``addFooter`` method: -.. code-block:: php +``` php +addFooter(); +$footer = $section->addFooter(); +``` Be sure to save the result in a local object to add elements to a footer. You can add the following elements to footers: @@ -131,8 +133,7 @@ footer. You can add the following elements to footers: See the "Elements" section for the detail of each elements. -Other containers ----------------- +### Other containers Textruns, table cells, and footnotes are elements that can also act as containers. See the corresponding "Elements" section for the detail of diff --git a/docs/usage/elements/chart.md b/docs/usage/elements/chart.md new file mode 100644 index 0000000000..d204425b18 --- /dev/null +++ b/docs/usage/elements/chart.md @@ -0,0 +1,15 @@ +# Chart + +Charts can be added using + +``` php +addChart('line', $categories, $series, $style); +``` + +For available styling options, see [`Styles > Chart`](../styles/chart.md). + +Check out the Sample_32_Chart.php for more options and styling. \ No newline at end of file diff --git a/docs/usage/elements/checkbox.md b/docs/usage/elements/checkbox.md new file mode 100644 index 0000000000..67c58163d1 --- /dev/null +++ b/docs/usage/elements/checkbox.md @@ -0,0 +1,14 @@ +# Checkbox + +Checkbox elements can be added to sections or table cells by using ``addCheckBox``. + +``` php +addCheckBox($name, $text, [$fontStyle], [$paragraphStyle]); +``` + +- ``$name``. Name of the check box. +- ``$text``. Text to be displayed in the document. +- ``$fontStyle``. See [`Styles > Font`](../styles/font.md). +- ``$paragraphStyle``. See [`Styles > Paragraph`](../styles/paragraph.md). \ No newline at end of file diff --git a/docs/usage/elements/comment.md b/docs/usage/elements/comment.md new file mode 100644 index 0000000000..50813fa2a1 --- /dev/null +++ b/docs/usage/elements/comment.md @@ -0,0 +1,24 @@ +# Comment + +Comments can be added to a document by using ``addComment``. +The comment can contain formatted text. Once the comment has been added, it can be linked to any element with ``setCommentRangeStart``. + +``` php +addText('Test', array('bold' => true)); + +// add it to the document +$phpWord->addComment($comment); + +$textrun = $section->addTextRun(); +$textrun->addText('This '); +$text = $textrun->addText('is'); +// link the comment to the text you just created +$text->setCommentRangeStart($comment); +$textrun->addText(' a test'); +``` + +If no end is set for a comment using the ``setCommentRangeEnd``, the comment will be ended automatically at the end of the element it is started on. \ No newline at end of file diff --git a/docs/usage/elements/field.md b/docs/usage/elements/field.md new file mode 100644 index 0000000000..1cafd18ef8 --- /dev/null +++ b/docs/usage/elements/field.md @@ -0,0 +1,49 @@ +# Field + +Currently the following fields are supported: + +- PAGE +- NUMPAGES +- DATE +- XE +- INDEX +- FILENAME +- REF + +``` php +addField($fieldType, [$properties], [$options], [$fieldText], [$fontStyle]) +``` + +- ``$fontStyle``. See [`Styles > Font`](../styles/font.md). + +See ``\PhpOffice\PhpWord\Element\Field`` for list of properties and options available for each field type. +Options which are not specifically defined can be added. Those must start with a ``\``. + +For instance for the INDEX field, you can do the following (See `Index Field for list of available options `_ ): + +``` php +addText('My '); +$fieldText->addText('bold index', ['bold' => true]); +$fieldText->addText(' entry'); +$section->addField('XE', array(), array(), $fieldText); + +// this actually adds the index +$section->addField('INDEX', array(), array('\\e " " \\h "A" \\c "3"'), 'right click to update index'); + +// Adding reference to a bookmark +$fieldText->addField('REF', [ + 'name' => 'bookmark' +], [ + 'InsertParagraphNumberRelativeContext', + 'CreateHyperLink', +]); +``` diff --git a/docs/usage/elements/formula.md b/docs/usage/elements/formula.md new file mode 100644 index 0000000000..a114b73e38 --- /dev/null +++ b/docs/usage/elements/formula.md @@ -0,0 +1,21 @@ +# Formula + +Formula can be added using + +``` php +setDenominator(new Element\Numeric(2)) + ->setNumerator(new Element\Identifier('π')) +; + +$math = new Math(); +$math->add($fraction); + +$formula = $section->addFormula($math); +``` \ No newline at end of file diff --git a/docs/usage/elements/image.md b/docs/usage/elements/image.md new file mode 100644 index 0000000000..cb288bbf3d --- /dev/null +++ b/docs/usage/elements/image.md @@ -0,0 +1,36 @@ +# Image + +To add an image, use the ``addImage`` method to sections, headers, footers, textruns, or table cells. + +``` php +addImage($src, [$style]); +``` + +- ``$src``. String path to a local image, URL of a remote image or the image data, as a string. Warning: Do not pass user-generated strings here, as that would allow an attacker to read arbitrary files or perform server-side request forgery by passing file paths or URLs instead of image data. +- ``$style``. See [`Styles > Image`](../styles/image.md). + +Examples: + +``` php +addSection(); +$section->addImage( + 'mars.jpg', + array( + 'width' => 100, + 'height' => 100, + 'marginTop' => -1, + 'marginLeft' => -1, + 'wrappingStyle' => 'behind' + ) +); +$footer = $section->addFooter(); +$footer->addImage('http://example.com/image.php'); +$textrun = $section->addTextRun(); +$textrun->addImage('http://php.net/logo.jpg'); +$source = file_get_contents('/path/to/my/images/earth.jpg'); +$textrun->addImage($source); +``` \ No newline at end of file diff --git a/docs/usage/elements/index.md b/docs/usage/elements/index.md new file mode 100644 index 0000000000..6106c8914b --- /dev/null +++ b/docs/usage/elements/index.md @@ -0,0 +1,34 @@ +# Elements + +Below are the matrix of element availability in each container. The column shows the containers while the rows lists the elements. + +| Num | Element | Section | Header | Footer | Cell | Text Run | Footnote | +|-------|-----------------|-----------|----------|----------|---------|------------|------------| +| 1 | [Text](text.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| 2 | Text Run | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :red_circle: | :red_circle: | +| 3 | [Link](link.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| 4 | [Title](title.md) | :white_check_mark: | :question: | :question: | :question: | :question: | :question: | +| 5 | [Preserve Text](preservetext.md) | :question: | :white_check_mark: | :white_check_mark: | :material-check-decagram-outline: | :red_circle: | :red_circle: | +| 6 | [Text Break](textbreak.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| 7 | [Page Break](pagebreak.md) | :white_check_mark: | :red_circle: | :red_circle: | :red_circle: | :red_circle: | :red_circle: | +| 8 | [List](list.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :red_circle: | :red_circle: | +| 9 | [Table](table.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :red_circle: | :red_circle: | +| 10 | [Image](image.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| 11 | [Watermark](watermark.md) | :red_circle: | :white_check_mark: | :red_circle: | :red_circle: | :red_circle: | :red_circle: | +| 12 | [OLEObject](oleobject.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| 13 | [TOC](toc.md) | :white_check_mark: | :red_circle: | :red_circle: | :red_circle: | :red_circle: | :red_circle: | +| 14 | [Footnote](note.md) | :white_check_mark: | :red_circle: | :red_circle: | :material-check-decagram: | :material-check-decagram: | :red_circle: | +| 15 | [Endnote](note.md) | :white_check_mark: | :red_circle: | :red_circle: | :material-check-decagram: | :material-check-decagram: | :red_circle: | +| 16 | [CheckBox](checkbox.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :red_circle: | +| 17 | [TextBox](textbox.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :red_circle: | :red_circle: | +| 18 | [Field](field.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| 19 | [Line](line.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| 20 | [Chart](chart.md) | :white_check_mark: | | | :white_check_mark: | | | + +Legend: + +- :white_check_mark: : Available. +- :material-check-decagram-outline: : Available only when inside header/footer. +- :material-check-decagram: : Available only when inside section. +- :red_circle: : Not available. +- :question: : Should be available. \ No newline at end of file diff --git a/docs/usage/elements/line.md b/docs/usage/elements/line.md new file mode 100644 index 0000000000..7062f884da --- /dev/null +++ b/docs/usage/elements/line.md @@ -0,0 +1,21 @@ +# Line + +Line elements can be added to sections by using ``addLine``. + +``` php + 1, 'width' => 100, 'height' => 0, 'color' => 635552); +$section->addLine($lineStyle); +``` + +Available line style attributes: + +- ``weight``. Line width in *twip*. +- ``color``. Defines the color of stroke. +- ``dash``. Line types: dash, rounddot, squaredot, dashdot, longdash, longdashdot, longdashdotdot. +- ``beginArrow``. Start type of arrow: block, open, classic, diamond, oval. +- ``endArrow``. End type of arrow: block, open, classic, diamond, oval. +- ``width``. Line-object width in *pt*. +- ``height``. Line-object height in *pt*. +- ``flip``. Flip the line element: true, false. \ No newline at end of file diff --git a/docs/usage/elements/link.md b/docs/usage/elements/link.md new file mode 100644 index 0000000000..0719c1c018 --- /dev/null +++ b/docs/usage/elements/link.md @@ -0,0 +1,14 @@ +# Link + +You can add Hyperlinks to the document by using the function addLink: + +``` php +addLink($linkSrc, [$linkName], [$fontStyle], [$paragraphStyle]); +``` + +- ``$linkSrc``. The URL of the link. +- ``$linkName``. Placeholder of the URL that appears in the document. +- ``$fontStyle``. See [`Styles > Font`](../styles/font.md). +- ``$paragraphStyle``. See [`Styles > Paragraph`](../styles/paragraph.md). \ No newline at end of file diff --git a/docs/usage/elements/list.md b/docs/usage/elements/list.md new file mode 100644 index 0000000000..4b51cd8634 --- /dev/null +++ b/docs/usage/elements/list.md @@ -0,0 +1,48 @@ +# List + +Lists can be added by using ``addListItem`` and ``addListItemRun`` methods. ``addListItem`` is used for creating lists that only contain plain text. ``addListItemRun`` is used for creating complex list items that contains texts with different style (some bold, other italics, etc) or other elements, e.g. images or links. The syntaxes are as follow: + +Basic usage: + +``` php +addListItem($text, [$depth], [$fontStyle], [$listStyle], [$paragraphStyle]); +$listItemRun = $section->addListItemRun([$depth], [$listStyle], [$paragraphStyle]) +``` + +Parameters: + +- ``$text``. Text that appears in the document. +- ``$depth``. Depth of list item. +- ``$fontStyle``. See [`Styles > Font`](../styles/font.md).. +- ``$listStyle``. List style of the current element TYPE\_NUMBER, + TYPE\_ALPHANUM, TYPE\_BULLET\_FILLED, etc. See list of constants in PHPWord\\Style\\ListItem. +- ``$paragraphStyle``. See [`Styles > Paragraph`](../styles/paragraph.md).. + +See ``Sample_14_ListItem.php`` for more code sample. + +Advanced usage: + +You can also create your own numbering style by changing the ``$listStyle`` parameter with the name of your numbering style. + +``` php +addNumberingStyle( + 'multilevel', + array( + 'type' => 'multilevel', + 'levels' => array( + array('format' => 'decimal', 'text' => '%1.', 'left' => 360, 'hanging' => 360, 'tabPos' => 360), + array('format' => 'upperLetter', 'text' => '%2.', 'left' => 720, 'hanging' => 360, 'tabPos' => 720), + ) + ) +); +$section->addListItem('List Item I', 0, null, 'multilevel'); +$section->addListItem('List Item I.a', 1, null, 'multilevel'); +$section->addListItem('List Item I.b', 1, null, 'multilevel'); +$section->addListItem('List Item II', 0, null, 'multilevel'); +``` + +For available styling options see [`Styles > Numbering Level`](../styles/numberinglevel.md). \ No newline at end of file diff --git a/docs/usage/elements/note.md b/docs/usage/elements/note.md new file mode 100644 index 0000000000..69a1947357 --- /dev/null +++ b/docs/usage/elements/note.md @@ -0,0 +1,54 @@ +# Footnote & Endnote + +You can create footnotes with ``addFootnote`` and endnotes with``addEndnote`` in texts or textruns, but it's recommended to use textrun to have better layout. You can use ``addText``, ``addLink``,``addTextBreak``, ``addImage``, ``addOLEObject`` on footnotes and endnotes. + +On textrun: + +``` php +addTextRun(); +$textrun->addText('Lead text.'); +$footnote = $textrun->addFootnote(); +$footnote->addText('Footnote text can have '); +$footnote->addLink('http://test.com', 'links'); +$footnote->addText('.'); +$footnote->addTextBreak(); +$footnote->addText('And text break.'); +$textrun->addText('Trailing text.'); +$endnote = $textrun->addEndnote(); +$endnote->addText('Endnote put at the end'); +``` + +On text: + +``` php +addText('Lead text.'); +$footnote = $section->addFootnote(); +$footnote->addText('Footnote text.'); +``` + +By default the footnote reference number will be displayed with decimal number +starting from 1. This number uses the ``FooterReference`` style which you can +redefine with the ``addFontStyle`` method. Default value for this style is +``array('superScript' => true)``; + +The footnote numbering can be controlled by setting the FootnoteProperties on the Section. + +``` php +setPos(\PhpOffice\PhpWord\ComplexType\FootnoteProperties::POSITION_BENEATH_TEXT); +//set the number format to use (decimal (default), upperRoman, upperLetter, ...) +$fp->setNumFmt(\PhpOffice\PhpWord\SimpleType\NumberFormat::LOWER_ROMAN); +//force starting at other than 1 +$fp->setNumStart(2); +//when to restart counting (continuous (default), eachSect, eachPage) +$fp->setNumRestart(\PhpOffice\PhpWord\ComplexType\FootnoteProperties::RESTART_NUMBER_EACH_PAGE); +//And finaly, set it on the Section +$section->setFootnoteProperties($fp); +``` \ No newline at end of file diff --git a/docs/usage/elements/oleobject.md b/docs/usage/elements/oleobject.md new file mode 100644 index 0000000000..ebee5fbafa --- /dev/null +++ b/docs/usage/elements/oleobject.md @@ -0,0 +1,9 @@ +# Object + +You can add OLE embeddings, such as Excel spreadsheets or PowerPoint presentations to the document by using ``addOLEObject`` method. + +``` php +addOLEObject($src, [$style]); +``` \ No newline at end of file diff --git a/docs/usage/elements/pagebreak.md b/docs/usage/elements/pagebreak.md new file mode 100644 index 0000000000..12f0fad57b --- /dev/null +++ b/docs/usage/elements/pagebreak.md @@ -0,0 +1,9 @@ +# Page breaks + +There are two ways to insert a page break, using the ``addPageBreak`` method or using the ``pageBreakBefore`` style of paragraph. + +``` php +addPageBreak(); +``` \ No newline at end of file diff --git a/docs/usage/elements/preservetext.md b/docs/usage/elements/preservetext.md new file mode 100644 index 0000000000..67a9cb0920 --- /dev/null +++ b/docs/usage/elements/preservetext.md @@ -0,0 +1,9 @@ +# Preserve text + +The ``addPreserveText`` method is used to add a page number or page count to headers or footers. + +``` php +addPreserveText('Page {PAGE} of {NUMPAGES}.'); +``` \ No newline at end of file diff --git a/docs/usage/elements/table.md b/docs/usage/elements/table.md new file mode 100644 index 0000000000..04b06429e7 --- /dev/null +++ b/docs/usage/elements/table.md @@ -0,0 +1,41 @@ +# Table + +To add tables, rows, and cells, use the ``addTable``, ``addRow``, and ``addCell`` methods: + +``` php +addTable([$tableStyle]); +$table->addRow([$height], [$rowStyle]); +$cell = $table->addCell($width, [$cellStyle]); +``` + +Table style can be defined with ``addTableStyle``: + +``` php + '006699', + 'borderSize' => 6, + 'cellMargin' => 50 +); +$firstRowStyle = array('bgColor' => '66BBFF'); +$phpWord->addTableStyle('myTable', $tableStyle, $firstRowStyle); +$table = $section->addTable('myTable'); +``` + +For available styling options see [`Styles > Table`](../styles/table.md). + +## Cell span + +You can span a cell on multiple columns by using ``gridSpan`` or multiple rows by using ``vMerge``. + +``` php +addCell(200); +$cell->getStyle()->setGridSpan(5); +``` + +See ``Sample_09_Tables.php`` for more code sample. \ No newline at end of file diff --git a/docs/usage/elements/text.md b/docs/usage/elements/text.md new file mode 100644 index 0000000000..41984be095 --- /dev/null +++ b/docs/usage/elements/text.md @@ -0,0 +1,26 @@ +# Text + + +Text can be added by using ``addText`` and ``addTextRun`` methods. ``addText`` is used for creating simple paragraphs that only contain texts with the same style. ``addTextRun`` is used for creating complex paragraphs that contain text with different style (some bold, other italics, etc) or other elements, e.g. images or links. The syntaxes are as follow: + +``` php +addText($text, [$fontStyle], [$paragraphStyle]); +$textrun = $section->addTextRun([$paragraphStyle]); +``` + +- ``$text``. Text to be displayed in the document. +- ``$fontStyle``. See [`Styles > Font`](../styles/font.md). +- ``$paragraphStyle``. See [`Styles > Paragraph`](../styles/paragraph.md). + +For available styling options, see [`Styles > Font`](../styles/font.md) and [`Styles > Paragraph`](../styles/paragraph.md). + +If you want to enable track changes on added text you can mark it as INSERTED or DELETED by a specific user at a given time: + +``` php +addText('Hello World!'); +$text->setChanged(\PhpOffice\PhpWord\Element\ChangedElement::TYPE_INSERTED, 'Fred', (new \DateTime())); +``` \ No newline at end of file diff --git a/docs/usage/elements/textbox.md b/docs/usage/elements/textbox.md new file mode 100644 index 0000000000..9341cdcbd9 --- /dev/null +++ b/docs/usage/elements/textbox.md @@ -0,0 +1,3 @@ +# TextBox + +To Be Completed... \ No newline at end of file diff --git a/docs/usage/elements/textbreak.md b/docs/usage/elements/textbreak.md new file mode 100644 index 0000000000..1937101cc8 --- /dev/null +++ b/docs/usage/elements/textbreak.md @@ -0,0 +1,13 @@ +# Text breaks + +Text breaks are empty new lines. To add text breaks, use the following syntax. All parameters are optional. + +``` php +addTextBreak([$breakCount], [$fontStyle], [$paragraphStyle]); +``` + +- ``$breakCount``. How many lines. +- ``$fontStyle``. See [`Styles > Font`](../styles/font.md). +- ``$paragraphStyle``. See [`Styles > Paragraph`](../styles/paragraph.md). \ No newline at end of file diff --git a/docs/usage/elements/title.md b/docs/usage/elements/title.md new file mode 100644 index 0000000000..fba78ef645 --- /dev/null +++ b/docs/usage/elements/title.md @@ -0,0 +1,24 @@ +# Title + +If you want to structure your document or build table of contents, you need titles or headings. +To add a title to the document, use the ``addTitleStyle`` and ``addTitle`` method. +If `depth` is 0, a Title will be inserted, otherwise a Heading1, Heading2, ... + +``` php +addTitleStyle($depth, [$fontStyle], [$paragraphStyle]); +$section->addTitle($text, $depth, $pageNumber); +``` + +`addTitleStyle` : +- ``$depth`` +- ``$fontStyle``: See [`Styles > Font`](../styles/font.md). +- ``$paragraphStyle``: See [`Styles > Paragraph`](../styles/paragraph.md). + +`addTitle` : +- ``$text``. Text to be displayed in the document. This can be `string` or a `\PhpOffice\PhpWord\Element\TextRun` +- ``$depth`` +- ``$pageNumber`` : Number of the page + +It's necessary to add a title style to your document because otherwise the title won't be detected as a real title. \ No newline at end of file diff --git a/docs/usage/elements/toc.md b/docs/usage/elements/toc.md new file mode 100644 index 0000000000..d7b05c2e96 --- /dev/null +++ b/docs/usage/elements/toc.md @@ -0,0 +1,21 @@ +# Table of contents + +To add a table of contents (TOC), you can use the ``addTOC`` method. +Your TOC can only be generated if you have add at least one title (See "[Title](title.md)"). + +``` php +addTOC([$fontStyle], [$tocStyle], [$minDepth], [$maxDepth]); +``` + +- ``$fontStyle``. See font style section. +- ``$tocStyle``. See available options below. +- ``$minDepth``. Minimum depth of header to be shown. Default 1. +- ``$maxDepth``. Maximum depth of header to be shown. Default 9. + +Options for ``$tocStyle``: + +- ``tabLeader``. Fill type between the title text and the page number. Use the defined constants in ``\PhpOffice\PhpWord\Style\TOC``. +- ``tabPos``. The position of the tab where the page number appears in *twip*. +- ``indent``. The indent factor of the titles in *twip*. \ No newline at end of file diff --git a/docs/usage/elements/trackchanges.md b/docs/usage/elements/trackchanges.md new file mode 100644 index 0000000000..70cd312543 --- /dev/null +++ b/docs/usage/elements/trackchanges.md @@ -0,0 +1,25 @@ +# Track Changes + +Track changes can be set on text elements. There are 2 ways to set the change information on an element. +Either by calling the `setChangeInfo()`, or by setting the `TrackChange` instance on the element with `setTrackChange()`. + +``` php +addSection(); +$textRun = $section->addTextRun(); + +$text = $textRun->addText('Hello World! Time to '); + +$text = $textRun->addText('wake ', array('bold' => true)); +$text->setChangeInfo(TrackChange::INSERTED, 'Fred', time() - 1800); + +$text = $textRun->addText('up'); +$text->setTrackChange(new TrackChange(TrackChange::INSERTED, 'Fred')); + +$text = $textRun->addText('go to sleep'); +$text->setChangeInfo(TrackChange::DELETED, 'Barney', new \DateTime('@' . (time() - 3600))); +``` \ No newline at end of file diff --git a/docs/usage/elements/watermark.md b/docs/usage/elements/watermark.md new file mode 100644 index 0000000000..0b2bae6bf0 --- /dev/null +++ b/docs/usage/elements/watermark.md @@ -0,0 +1,13 @@ +# Watermark + +To add a watermark (or page background image), your section needs a +header reference. After creating a header, you can use the +``addWatermark`` method to add a watermark. + +``` php +addSection(); +$header = $section->addHeader(); +$header->addWatermark('resources/_earth.jpg', array('marginTop' => 200, 'marginLeft' => 55)); +``` \ No newline at end of file diff --git a/docs/usage/introduction.md b/docs/usage/introduction.md new file mode 100644 index 0000000000..b3a101ab0d --- /dev/null +++ b/docs/usage/introduction.md @@ -0,0 +1,384 @@ +# Introduction + +## Basic example + +The following is a basic example of the PHPWord library. More examples +are provided in the [samples folder](https://github.com/PHPOffice/PHPWord/tree/master/samples/). + +``` php +addSection(); +// Adding Text element to the Section having font styled by default... +$section->addText( + '"Learn from yesterday, live for today, hope for tomorrow. ' + . 'The important thing is not to stop questioning." ' + . '(Albert Einstein)' +); + +/* + * Note: it's possible to customize font style of the Text element you add in three ways: + * - inline; + * - using named font style (new font style object will be implicitly created); + * - using explicitly created font style object. + */ + +// Adding Text element with font customized inline... +$section->addText( + '"Great achievement is usually born of great sacrifice, ' + . 'and is never the result of selfishness." ' + . '(Napoleon Hill)', + array('name' => 'Tahoma', 'size' => 10) +); + +// Adding Text element with font customized using named font style... +$fontStyleName = 'oneUserDefinedStyle'; +$phpWord->addFontStyle( + $fontStyleName, + array('name' => 'Tahoma', 'size' => 10, 'color' => '1B2232', 'bold' => true) +); +$section->addText( + '"The greatest accomplishment is not in never falling, ' + . 'but in rising again after you fall." ' + . '(Vince Lombardi)', + $fontStyleName +); + +// Adding Text element with font customized using explicitly created font style object... +$fontStyle = new \PhpOffice\PhpWord\Style\Font(); +$fontStyle->setBold(true); +$fontStyle->setName('Tahoma'); +$fontStyle->setSize(13); +$myTextElement = $section->addText('"Believe you can and you\'re halfway there." (Theodor Roosevelt)'); +$myTextElement->setFontStyle($fontStyle); + +// Saving the document as OOXML file... +$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007'); +$objWriter->save('helloWorld.docx'); + +// Saving the document as ODF file... +$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'ODText'); +$objWriter->save('helloWorld.odt'); + +// Saving the document as HTML file... +$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'HTML'); +$objWriter->save('helloWorld.html'); + +/* Note: we skip RTF, because it's not XML-based and requires a different example. */ +/* Note: we skip PDF, because "HTML-to-PDF" approach is used to create PDF documents. */ +``` + +## PHPWord Settings + +The ``PhpOffice\PhpWord\Settings`` class provides some options that will +affect the behavior of PHPWord. Below are the options. + +### XML Writer compatibility + +This option sets [XMLWriter::setIndent](http://www.php.net/manual/en/function.xmlwriter-set-indent.php) and [XMLWriter::setIndentString](http://www.php.net/manual/en/function.xmlwriter-set-indent-string.php>). The default value of this option is ``true`` (compatible), which is [required for OpenOffice](https://github.com/PHPOffice/PHPWord/issues/103) to render OOXML document correctly. You can set this option to ``false`` during development to make the resulting XML file easier to read. + +``` php +setDefaultFontName('Times New Roman'); +$phpWord->setDefaultFontSize(12); +``` + +## Document settings + +Settings for the generated document can be set using ``$phpWord->getSettings()`` + +### Magnification Setting + +The default zoom value is 100 percent. This can be changed either to another percentage + +``` php +getSettings()->setZoom(75); +``` + +Or to predefined values ``fullPage``, ``bestFit``, ``textFit`` + +``` php +getSettings()->setZoom(Zoom::BEST_FIT); +``` + +### Mirroring the Page Margins + +Use mirror margins to set up facing pages for double-sided documents, such as books or magazines. + +``` php +getSettings()->setMirrorMargins(true); +``` + +!!! note annotate "Don't forget to set both paper size and page size" + + For example, to print a document on A4 paper (landscape) and fold it into A5 pages (portrait), use this section style: + + ``` php + getSettings()->setMirrorMargins(true); + $phpWord->addSection([ + 'paperSize' => 'A4', + 'orientation' => 'landscape', + 'pageSizeW' => Converter::cmToTwip(14.85), + 'pageSizeH' => Converter::cmToTwip(21), + ]); + ``` + +### Printing as folded booklet + +Use book-fold printing to set up documents to be printed as foldable pages. + +``` php +getSettings()->setBookFoldPrinting(true); +``` + +### Spelling and grammatical checks + +By default spelling and grammatical errors are shown as soon as you open a word document. +For big documents this can slow down the opening of the document. You can hide the spelling and/or grammatical errors with: + +``` php +getSettings()->setHideGrammaticalErrors(true); +$phpWord->getSettings()->setHideSpellingErrors(true); +``` + +You can also specify the status of the spell and grammar checks, marking spelling or grammar as dirty will force a re-check when opening the document. + +``` php +setGrammar(\PhpOffice\PhpWord\ComplexType\ProofState::CLEAN); +$proofState->setSpelling(\PhpOffice\PhpWord\ComplexType\ProofState::DIRTY); + +$phpWord->getSettings()->setProofState($proofState); +``` + +### Track Revisions + +Track changes can be activated using ``setTrackRevisions``, you can furture specify + +- Not to use move syntax, instead moved items will be seen as deleted in one place and added in another +- Not track formatting revisions + +``` php +getSettings()->setTrackRevisions(true); +$phpWord->getSettings()->setDoNotTrackMoves(true); +$phpWord->getSettings()->setDoNotTrackFormatting(true); +``` + +### Decimal Symbol + +The default symbol to represent a decimal figure is the ``.`` in english. In french you might want to change it to ``,`` for instance. + +``` php +getSettings()->setDecimalSymbol(','); +``` + +### Document Language + +The default language of the document can be change with the following. + +``` php +getSettings()->setThemeFontLang(new Language(Language::FR_BE)); +``` + +``Language`` has 3 parameters, one for Latin languages, one for East Asian languages and one for Complex (Bi-Directional) languages. +A couple of language codes are provided in the ``PhpOffice\PhpWord\Style\Language`` class but any valid code/ID can be used. + +In case you are generating an RTF document the language need to be set differently. + +``` php +setLangId(Language::EN_GB_ID); +$phpWord->getSettings()->setThemeFontLang($lang); +``` + +## Document information + +You can set the document information such as title, creator, and company +name. Use the following functions: + +``` php +getDocInfo(); +$properties->setCreator('My name'); +$properties->setCompany('My factory'); +$properties->setTitle('My title'); +$properties->setDescription('My description'); +$properties->setCategory('My category'); +$properties->setLastModifiedBy('My name'); +$properties->setCreated(mktime(0, 0, 0, 3, 12, 2014)); +$properties->setModified(mktime(0, 0, 0, 3, 14, 2014)); +$properties->setSubject('My subject'); +$properties->setKeywords('my, key, word'); +``` + +## Measurement units + +The base length unit in Open Office XML is twip. Twip means "TWentieth +of an Inch Point", i.e. 1 twip = 1/1440 inch. + +You can use PHPWord helper functions to convert inches, centimeters, or +points to twip. + +``` php +addParagraphStyle('My Style', array( + 'spaceAfter' => \PhpOffice\PhpWord\Shared\Converter::pointToTwip(6)) +); + +$section = $phpWord->addSection(); +$sectionStyle = $section->getStyle(); +// half inch left margin +$sectionStyle->setMarginLeft(\PhpOffice\PhpWord\Shared\Converter::inchToTwip(.5)); +// 2 cm right margin +$sectionStyle->setMarginRight(\PhpOffice\PhpWord\Shared\Converter::cmToTwip(2)); +``` + +## Document protection + +The document (or parts of it) can be password protected. + +``` php +getSettings()->getDocumentProtection(); +$documentProtection->setEditing(DocProtect::READ_ONLY); +$documentProtection->setPassword('myPassword'); +``` + +## Automatically Recalculate Fields on Open + +To force an update of the fields present in the document, set updateFields to true + +``` php +getSettings()->setUpdateFields(true); +``` + +## Hyphenation + +Hyphenation describes the process of breaking words with hyphens. There are several options to control hyphenation. + +### Auto hyphenation + +To automatically hyphenate text set ``autoHyphenation`` to ``true``. + +``` php +getSettings()->setAutoHyphenation(true); +``` + +### Consecutive Hyphen Limit + +The maximum number of consecutive lines of text ending with a hyphen can be controlled by the ``consecutiveHyphenLimit`` option. +There is no limit if the option is not set or the provided value is ``0``. + +``` php +getSettings()->setConsecutiveHyphenLimit(2); +``` + +### Hyphenation Zone + +The hyphenation zone (in *twip*) is the allowed amount of whitespace before hyphenation is applied. +The smaller the hyphenation zone the more words are hyphenated. Or in other words, the wider the hyphenation zone the less words are hyphenated. + +``` php +getSettings()->setHyphenationZone(\PhpOffice\PhpWord\Shared\Converter::cmToTwip(1)); +``` + +### Hyphenate Caps + +To control whether or not words in all capital letters shall be hyphenated use the `doNotHyphenateCaps` option. + +``` php +getSettings()->setDoNotHyphenateCaps(true); +``` \ No newline at end of file diff --git a/docs/usage/readers.md b/docs/usage/readers.md new file mode 100644 index 0000000000..9cd9c1c4d6 --- /dev/null +++ b/docs/usage/readers.md @@ -0,0 +1,51 @@ +# Readers + +## HTML +The name of the reader is `HTML`. + +``` php +load(__DIR__ . '/sample.html'); +``` + +## MsDoc +The name of the reader is `MsDoc`. + +``` php +load(__DIR__ . '/sample.doc'); +``` + +## ODText +The name of the reader is `ODText`. + +``` php +load(__DIR__ . '/sample.odt'); +``` + +## RTF +The name of the reader is `RTF`. + +``` php +load(__DIR__ . '/sample.rtf'); +``` + +## Word2007 +The name of the reader is `Word2007`. + +``` php +load(__DIR__ . '/sample.docx'); +``` \ No newline at end of file diff --git a/docs/usage/styles/chart.md b/docs/usage/styles/chart.md new file mode 100644 index 0000000000..f297718d24 --- /dev/null +++ b/docs/usage/styles/chart.md @@ -0,0 +1,19 @@ +# Chart + +Available Chart style options: + +- ``width``. Width (in EMU). +- ``height``. Height (in EMU). +- ``3d``. Is 3D; applies to pie, bar, line, area, *true* or *false*. +- ``colors``. A list of colors to use in the chart. +- ``title``. The title for the chart. +- ``showLegend``. Show legend, *true* or *false*. +- ``LegendPosition``. Legend position, *r* (default), *b*, *t*, *l* or *tr*. +- ``categoryLabelPosition``. Label position for categories, *nextTo* (default), *low* or *high*. +- ``valueLabelPosition``. Label position for values, *nextTo* (default), *low* or *high*. +- ``categoryAxisTitle``. The title for the category axis. +- ``valueAxisTitle``. The title for the values axis. +- ``majorTickMarkPos``. The position for major tick marks, *in*, *out*, *cross*, *none* (default). +- ``showAxisLabels``. Show labels for axis, *true* or *false*. +- ``gridX``. Show Gridlines for X-Axis, *true* or *false*. +- ``gridY``. Show Gridlines for Y-Axis, *true* or *false*. \ No newline at end of file diff --git a/docs/usage/styles/font.md b/docs/usage/styles/font.md new file mode 100644 index 0000000000..921dc7fd85 --- /dev/null +++ b/docs/usage/styles/font.md @@ -0,0 +1,28 @@ +# Font + +Available Font style options: + +- ``allCaps``. All caps, *true* or *false*. +- ``bgColor``. Font background color, e.g. *FF0000*. +- ``bold``. Bold, *true* or *false*. +- ``color``. Font color, e.g. *FF0000*. +- ``doubleStrikethrough``. Double strikethrough, *true* or *false*. +- ``fgColor``. Font highlight color, e.g. *yellow*, *green*, *blue*. + See ``\PhpOffice\PhpWord\Style\Font::FGCOLOR_...`` class constants for possible values +- ``hint``. Font content type, *default*, *eastAsia*, or *cs*. +- ``italic``. Italic, *true* or *false*. +- ``name``. Font name, e.g. *Arial*. +- ``rtl``. Right to Left language, *true* or *false*. +- ``size``. Font size, e.g. *20*, *22*. +- ``smallCaps``. Small caps, *true* or *false*. +- ``strikethrough``. Strikethrough, *true* or *false*. +- ``subScript``. Subscript, *true* or *false*. +- ``superScript``. Superscript, *true* or *false*. +- ``underline``. Underline, *single*, *dash*, *dotted*, etc. + See ``\PhpOffice\PhpWord\Style\Font::UNDERLINE_...`` class constants for possible values +- ``lang``. Language, either a language code like *en-US*, *fr-BE*, etc. or an object (or as an array) if you need to set eastAsian or bidirectional languages + See ``\PhpOffice\PhpWord\Style\Language`` class for some language codes. +- ``position``. The text position, raised or lowered, in half points +- ``hidden``. Hidden text, *true* or *false*. +- ``whiteSpace``. How white space is handled when generating html/pdf. Possible values are *pre-wrap* and *normal* (other css values for white space are accepted, but are not expected to be useful). +- ``fallbackFont``. Fallback generic font for html/pdf. Possible values are *sans-serif*, *serif*, and *monospace* (other css values for generic fonts are accepted). diff --git a/docs/usage/styles/image.md b/docs/usage/styles/image.md new file mode 100644 index 0000000000..b6c4ddf936 --- /dev/null +++ b/docs/usage/styles/image.md @@ -0,0 +1,14 @@ +# Image + +Available Image style options: + +- ``alignment``. See ``\PhpOffice\PhpWord\SimpleType\Jc`` class for the details. +- ``height``. Height in *pt*. +- ``marginLeft``. Left margin in inches, can be negative. +- ``marginTop``. Top margin in inches, can be negative. +- ``width``. Width in *pt*. +- ``wrappingStyle``. Wrapping style, *inline*, *square*, *tight*, *behind*, or *infront*. +- ``wrapDistanceTop``. Top text wrapping in pixels. +- ``wrapDistanceBottom``. Bottom text wrapping in pixels. +- ``wrapDistanceLeft``. Left text wrapping in pixels. +- ``wrapDistanceRight``. Right text wrapping in pixels. \ No newline at end of file diff --git a/docs/usage/styles/numberinglevel.md b/docs/usage/styles/numberinglevel.md new file mode 100644 index 0000000000..392e820048 --- /dev/null +++ b/docs/usage/styles/numberinglevel.md @@ -0,0 +1,16 @@ +# Numbering level + +Available NumberingLevel style options: + +- ``alignment``. Supports all alignment modes since 1st Edition of ECMA-376 standard up till ISO/IEC 29500:2012. + See ``\PhpOffice\PhpWord\SimpleType\Jc`` class constants for possible values. +- ``font``. Font name. +- ``format``. Numbering format bullet\|decimal\|upperRoman\|lowerRoman\|upperLetter\|lowerLetter. +- ``hanging``. See paragraph style. +- ``hint``. See font style. +- ``left``. See paragraph style. +- ``restart``. Restart numbering level symbol. +- ``start``. Starting value. +- ``suffix``. Content between numbering symbol and paragraph text tab\|space\|nothing. +- ``tabPos``. See paragraph style. +- ``text``. Numbering level text e.g. %1 for nonbullet or bullet character. \ No newline at end of file diff --git a/docs/usage/styles/paragraph.md b/docs/usage/styles/paragraph.md new file mode 100644 index 0000000000..e9ca155005 --- /dev/null +++ b/docs/usage/styles/paragraph.md @@ -0,0 +1,29 @@ +# Paragraph + +Available Paragraph style options: + +- ``alignment``. Supports all alignment modes since 1st Edition of ECMA-376 standard up till ISO/IEC 29500:2012. + See ``\PhpOffice\PhpWord\SimpleType\Jc`` class constants for possible values. +- ``basedOn``. Parent style. +- ``hanging``. Hanging indentation in *half inches*. +- ``indent``. Indent (left indentation) in *half inches*. +- ``indentation``. An array of indentation key => value pairs in *twip*. Supports *left*, *right*, *firstLine* and *hanging* indentation. + See ``\PhpOffice\PhpWord\Style\Indentation`` for possible identation types. +- ``keepLines``. Keep all lines on one page, *true* or *false*. +- ``keepNext``. Keep paragraph with next paragraph, *true* or *false*. +- ``lineHeight``. Text line height, e.g. *1.0*, *1.5*, etc. +- ``next``. Style for next paragraph. +- ``pageBreakBefore``. Start paragraph on next page, *true* or *false*. +- ``spaceBefore``. Space before paragraph in *twip*. +- ``spaceAfter``. Space after paragraph in *twip*. +- ``spacing``. Space between lines in *twip*. If spacingLineRule is auto, 240 (height of 1 line) will be added, so if you want a double line height, set this to 240. +- ``spacingLineRule``. Line Spacing Rule. *auto*, *exact*, *atLeast* + See ``\PhpOffice\PhpWord\SimpleType\LineSpacingRule`` class constants for possible values. +- ``suppressAutoHyphens``. Hyphenation for paragraph, *true* or *false*. +- ``tabs``. Set of custom tab stops. +- ``widowControl``. Allow first/last line to display on a separate page, *true* or *false*. +- ``contextualSpacing``. Ignore Spacing Above and Below When Using Identical Styles, *true* or *false*. +- ``bidi``. Right to Left Paragraph Layout, *true* or *false*. +- ``shading``. Paragraph Shading. +- ``textAlignment``. Vertical Character Alignment on Line. + See ``\PhpOffice\PhpWord\SimpleType\TextAlignment`` class constants for possible values. \ No newline at end of file diff --git a/docs/usage/styles/section.md b/docs/usage/styles/section.md new file mode 100644 index 0000000000..2be22e8077 --- /dev/null +++ b/docs/usage/styles/section.md @@ -0,0 +1,28 @@ +# Section + +Available Section style options: + +- ``borderBottomColor``. Border bottom color. +- ``borderBottomSize``. Border bottom size in *twip*. +- ``borderLeftColor``. Border left color. +- ``borderLeftSize``. Border left size in *twip*. +- ``borderRightColor``. Border right color. +- ``borderRightSize``. Border right size in *twip*. +- ``borderTopColor``. Border top color. +- ``borderTopSize``. Border top size in *twip*. +- ``breakType``. Section break type (nextPage, nextColumn, continuous, evenPage, oddPage). +- ``colsNum``. Number of columns. +- ``colsSpace``. Spacing between columns. +- ``footerHeight``. Spacing to bottom of footer. +- ``gutter``. Page gutter spacing. +- ``headerHeight``. Spacing to top of header. +- ``marginTop``. Page margin top in *twip*. +- ``marginLeft``. Page margin left in *twip*. +- ``marginRight``. Page margin right in *twip*. +- ``marginBottom``. Page margin bottom in *twip*. +- ``orientation``. Page orientation (``portrait``, which is default, or ``landscape``). + 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 \ No newline at end of file diff --git a/docs/usage/styles/table.md b/docs/usage/styles/table.md new file mode 100644 index 0000000000..1f82f3c638 --- /dev/null +++ b/docs/usage/styles/table.md @@ -0,0 +1,49 @@ +# Table + +Available Table style options: + +- ``alignment``. Supports all alignment modes since 1st Edition of ECMA-376 standard up till ISO/IEC 29500:2012. + See ``\PhpOffice\PhpWord\SimpleType\JcTable`` and ``\PhpOffice\PhpWord\SimpleType\Jc`` class constants for possible values. +- ``bgColor``. Background color, e.g. '9966CC'. +- ``border(Top|Right|Bottom|Left)Color``. Border color, e.g. '9966CC'. +- ``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 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: + +- ``leftFromText`` Distance From Left of Table to Text in *twip* +- ``rightFromText`` Distance From Right of Table to Text in *twip* +- ``topFromText`` Distance From Top of Table to Text in *twip* +- ``bottomFromText`` Distance From Top of Table to Text in *twip* +- ``vertAnchor`` Table Vertical Anchor, one of ``\PhpOffice\PhpWord\Style\TablePosition::VANCHOR_*`` +- ``horzAnchor`` Table Horizontal Anchor, one of ``\PhpOffice\PhpWord\Style\TablePosition::HANCHOR_*`` +- ``tblpXSpec`` Relative Horizontal Alignment From Anchor, one of ``\PhpOffice\PhpWord\Style\TablePosition::XALIGN_*`` +- ``tblpX`` Absolute Horizontal Distance From Anchorin *twip* +- ``tblpYSpec`` Relative Vertical Alignment From Anchor, one of ``\PhpOffice\PhpWord\Style\TablePosition::YALIGN_*`` +- ``tblpY`` Absolute Vertical Distance From Anchorin *twip* + +Available Row style options: + +- ``cantSplit``. Table row cannot break across pages, *true* or *false*. +- ``exactHeight``. Row height is exact or at least. +- ``tblHeader``. Repeat table row on every new page, *true* or *false*. + +Available Cell style options: + +- ``bgColor``. Background color, e.g. '9966CC'. +- ``border(Top|Right|Bottom|Left)Color``. Border color, e.g. '9966CC'. +- ``border(Top|Right|Bottom|Left)Size``. Border size in *twip*. +- ``border(Top|Right|Bottom|Left)Style``. Border style. You can use constants from ``\PhpOffice\PhpWord\SimpleType\Border`` +- ``gridSpan``. Number of columns spanned. +- ``textDirection(btLr|tbRl)``. Direction of text. + You can use constants ``\PhpOffice\PhpWord\Style\Cell::TEXT_DIR_BTLR`` and ``\PhpOffice\PhpWord\Style\Cell::TEXT_DIR_TBRL`` +- ``valign``. Vertical alignment, *top*, *center*, *both*, *bottom*. +- ``vMerge``. *restart* or *continue*. +- ``width``. Cell width in *twip*. \ No newline at end of file diff --git a/docs/usage/template.md b/docs/usage/template.md new file mode 100644 index 0000000000..a0c885e75e --- /dev/null +++ b/docs/usage/template.md @@ -0,0 +1,372 @@ +# Template processing + +You can create an OOXML document template with included search-patterns (macros) which can be replaced by any value you wish. Only single-line values can be replaced. +By default Macros are defined like this: ``${search-pattern}`` but you can define custom macros. +To load a template file, create a new instance of the TemplateProcessor. + +``` php +setValue('firstname', 'John'); +$templateProcessor->setValue('lastname', 'Doe'); +``` + +## setValues + +You can also set multiple values by passing all of them in an array. + +``` php +setValues(array('firstname' => 'John', 'lastname' => 'Doe')); +``` + +## setCheckbox + +Given a template containing a checkbox control with the title or tag named: + +``` clean +${checkbox} +``` +The following will check the checkbox: + +``` php +setCheckbox('checkbox', true); +``` + +!!! note annotate "To add a checkbox and set its title or tag in a template" + + 1. Go to **Developer** tab > **Controls** group + 2. Select the **Check Box Content Control** + 3. Right-click on your checkbox + 4. Click on **Properties** + 5. Set the title or the tag + + These steps may change regarding the version of Microsoft Word used. + +## setMacroOpeningChars + +You can define a custom opening macro. The following will set ``{#`` as the opening search pattern. + +``` php +setMacroOpeningChars('{#'); +``` + +## setMacroClosingChars + +You can define a custom closing macro. The following will set ``#}`` as the closing search pattern. + +``` php +setMacroClosingChars('#}'); +``` + +## setMacroChars + +You can define a custom opening and closing macro at the same time . The following will set the search-pattern like this: ``{#search-pattern#}`` . + +``` php +setMacroChars('{#', '#}'); +``` + +## setImageValue + +The search-pattern model for images can be like: + - ``${search-image-pattern}`` + - ``${search-image-pattern:[width]:[height]:[ratio]}`` + - ``${search-image-pattern:[width]x[height]}`` + - ``${search-image-pattern:size=[width]x[height]}`` + - ``${search-image-pattern:width=[width]:height=[height]:ratio=false}`` + +Where: + - [width] and [height] can be just numbers or numbers with measure, which supported by Word (cm, mm, in, pt, pc, px, %, em, ex) + - [ratio] uses only for ``false``, ``-`` or ``f`` to turn off respect aspect ration of image. By default template image size uses as 'container' size. + +Example: + +``` clean + +${CompanyLogo} +${UserLogo:50:50} ${Name} - ${City} - ${Street} +``` + +``` php +setValue('Name', 'John Doe'); +$templateProcessor->setValue(array('City', 'Street'), array('Detroit', '12th Street')); + +$templateProcessor->setImageValue('CompanyLogo', 'path/to/company/logo.png'); +$templateProcessor->setImageValue('UserLogo', array('path' => 'path/to/logo.png', 'width' => 100, 'height' => 100, 'ratio' => false)); +$templateProcessor->setImageValue('FeatureImage', function () { + // Closure will only be executed if the replacement tag is found in the template + + return array('path' => SlowFeatureImageGenerator::make(), 'width' => 100, 'height' => 100, 'ratio' => false); +}); +``` + +## cloneBlock + +Given a template containing +See ``Sample_23_TemplateBlock.php`` for an example. + +``` clean + +${block_name} +Customer: ${customer_name} +Address: ${customer_address} +${/block_name} +``` + +The following will duplicate everything between ``${block_name}`` and ``${/block_name}`` 3 times. + +``` php +cloneBlock('block_name', 3, true, true); +``` + +The last parameter will rename any macro defined inside the block and add #1, #2, #3 ... to the macro name. +The result will be + +``` clean + +Customer: ${customer_name#1} +Address: ${customer_address#1} + +Customer: ${customer_name#2} +Address: ${customer_address#2} + +Customer: ${customer_name#3} +Address: ${customer_address#3} +``` + +It is also possible to pass an array with the values to replace the marcros with. +If an array with replacements is passed, the ``count`` argument is ignored, it is the size of the array that counts. + +``` php + 'Batman', 'customer_address' => 'Gotham City'), + array('customer_name' => 'Superman', 'customer_address' => 'Metropolis'), +); +$templateProcessor->cloneBlock('block_name', 0, true, false, $replacements); +``` + +The result will then be + +``` clean + +Customer: Batman +Address: Gotham City + +Customer: Superman +Address: Metropolis +``` + +## replaceBlock + +Given a template containing + +``` clean + +${block_name} +This block content will be replaced +${/block_name} +``` + +The following will replace everything between ``${block_name}`` and ``${/block_name}`` with the value passed. + +``` php +replaceBlock('block_name', 'This is the replacement text.'); +``` + +## deleteBlock + +Same as previous, but it deletes the block + +``` php +deleteBlock('block_name'); +``` + +## cloneRow + +Clones a table row in a template document. +See ``Sample_07_TemplateCloneRow.php`` for an example. + +``` clean + ++-----------+----------------+ +| ${userId} | ${userName} | +| |----------------+ +| | ${userAddress} | ++-----------+----------------+ +``` + +``` php +cloneRow('userId', 2); +``` + +Will result in + +``` 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`. + +``` clean + ++-----------+----------------+ +| ${userId} | ${userName} | +| |----------------+ +| | ${userAddress} | ++-----------+----------------+ +``` + +``` php + 1, 'userName' => 'Batman', 'userAddress' => 'Gotham City'], + ['userId' => 2, 'userName' => 'Superman', 'userAddress' => 'Metropolis'], +]; +$templateProcessor->cloneRowAndSetValues('userId', $values); +``` + +Will result in + +``` clean + ++---+-------------+ +| 1 | Batman | +| |-------------+ +| | Gotham City | ++---+-------------+ +| 2 | Superman | +| |-------------+ +| | Metropolis | ++---+-------------+ +``` + +## applyXslStyleSheet + +Applies the XSL stylesheet passed to header part, footer part and main part + +``` php +load('/path/to/my/stylesheet.xsl'); +$templateProcessor->applyXslStyleSheet($xslDomDocument); +``` + +## setComplexValue + +Replaces a ${macro} with the ComplexType passed. +See ``Sample_40_TemplateSetComplexValue.php`` for examples. + +``` php +addText('by a red italic text', array('italic' => true, 'color' => 'red')); +$templateProcessor->setComplexValue('inline', $inline); +``` + +## setComplexBlock + +Replaces a ${macro} with the ComplexType passed. +See ``Sample_40_TemplateSetComplexValue.php`` for examples. + +``` php + 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); +``` + +## setChartValue + +Replace a variable by a chart. + +``` php +setChartValue('myChart', $chart); +``` + +## save + +Saves the loaded template within the current directory. Returns the file path. + +``` php +save(); +``` + +## saveAs + +Saves a copy of the loaded template in the indicated path. + +``` php +saveAs($pathToSave); +``` diff --git a/docs/usage/writers.md b/docs/usage/writers.md new file mode 100644 index 0000000000..f561345a95 --- /dev/null +++ b/docs/usage/writers.md @@ -0,0 +1,145 @@ +# Writers + +## HTML +The name of the writer is `HTML`. + +``` php +save(__DIR__ . '/sample.html'); +``` + + +When generating html/pdf, you can alter the default handling of white space (normal), and/or supply a fallback generic font as follows: + +```php +$writer = IOFactory::createWriter($oPhpWord, 'HTML'); +$writer->setDefaultGenericFont('serif'); +$writer->setDefaultWhiteSpace('pre-wrap'); +$writer->save(__DIR__ . '/sample.html'); +``` + +## ODText +The name of the writer is `ODText`. + +``` php +save(__DIR__ . '/sample.docx'); +``` + +## PDF +The name of the writer is `PDF`. + +``` php +save(__DIR__ . '/sample.pdf'); +``` + +To generate a PDF, the PhpWord object passes through HTML before generating the PDF. +This HTML can be modified using a callback. + +``` php +setEditCallback('cbEditHTML'); +$writer->save(__DIR__ . '/sample.pdf'); + +/** + * Add a meta tag generator + */ +function cbEditHTML(string $inputHTML): string +{ + $beforeBody = ''; + $needle = ''; + + $pos = strpos($inputHTML, $needle); + if ($pos !== false) { + $inputHTML = (string) substr_replace($inputHTML, "$beforeBody\n$needle", $pos, strlen($needle)); + } + + return $inputHTML; +} +``` + +### Options + +You can define options like : +* `font`: default font + +Options must be defined before creating the writer. + +``` php + 'Arial' +]); + +$writer = IOFactory::createWriter($oPhpWord, 'PDF'); +$writer->save(__DIR__ . '/sample.pdf'); +``` + +#### Specify the PDF Renderer + +Before PHPWord can write a PDF, you **must** specify the renderer to use and the path to it. +Currently, three renderers are supported: + +- [DomPDF](https://github.com/dompdf/dompdf) +- [MPDF](https://mpdf.github.io/) +- [TCPDF](https://tcpdf.org/) + +To specify the renderer you use two static `Settings` functions: + +- `setPdfRendererName`: This sets the name of the renderer library to use. + Provide one of [`Settings`' three `PDF_` constants](https://github.com/PHPOffice/PHPWord/blob/master/src/PhpWord/Settings.php#L39-L41) to the function call. +- `setPdfRendererPath`: This sets the path to the renderer library. + This directory is the renderer's package directory within Composer's _vendor_ directory. + +In the code below, you can see an example of setting MPDF as the desired PDF renderer. + +```php +Settings::setPdfRendererName(Settings::PDF_RENDERER_MPDF); +Settings::setPdfRendererPath(__DIR__ . '/../vendor/mpdf/mpdf'); +``` + +## RTF +The name of the writer is `RTF`. + +``` php +save(__DIR__ . '/sample.rtf'); +``` + +## Word2007 +The name of the writer is `Word2007`. + +``` php +save(__DIR__ . '/sample.docx'); +``` + +### ZIP Adapter +You can change the ZIP Adapter for the writer. By default, the ZIP Adapter is `ZipArchiveAdapter`. + +``` php +setZipAdapter(new PclZipAdapter()); +$writer->save(__DIR__ . '/sample.docx'); +``` diff --git a/docs/writersreaders.rst b/docs/writersreaders.rst deleted file mode 100644 index 33aacc72af..0000000000 --- a/docs/writersreaders.rst +++ /dev/null @@ -1,110 +0,0 @@ -.. _writersreaders: - -Writers & readers -================= - -OOXML ------ - -The package of OOXML document consists of the following files. - -- \_rels/ - - - .rels - -- docProps/ - - - app.xml - - core.xml - - custom.xml - -- word/ - - - rels/ - - - document.rels.xml - - - media/ - - theme/ - - - theme1.xml - - - document.xml - - fontTable.xml - - numbering.xml - - settings.xml - - styles.xml - - webSettings.xml - -- [Content\_Types].xml - -OpenDocument ------------- - -Package -~~~~~~~ - -The package of OpenDocument document consists of the following files. - -- META-INF/ - - - manifest.xml - -- Pictures/ -- content.xml -- meta.xml -- styles.xml - -content.xml -~~~~~~~~~~~ - -The structure of ``content.xml`` is described below. - -- office:document-content - - - office:font-facedecls - - office:automatic-styles - - office:body - - - office:text - - - draw:\* - - office:forms - - table:table - - text:list - - text:numbered-paragraph - - text:p - - text:table-of-contents - - text:section - - - office:chart - - office:image - - office:drawing - -styles.xml -~~~~~~~~~~ - -The structure of ``styles.xml`` is described below. - -- office:document-styles - - - office:styles - - office:automatic-styles - - office:master-styles - - - office:master-page - -RTF ---- - -To be completed. - -HTML ----- - -To be completed. - -PDF ---- - -To be completed. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000000..6b2c1b7fa6 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,119 @@ +site_name: PHPWord +site_url: https://phpoffice.github.io/PHPWord +repo_url: https://github.com/PHPOffice/PHPWord +repo_name: PHPOffice/PHPWord +edit_uri: edit/develop/docs/ + +## Theme +theme: + name: material + palette: + primary: indigo + features: + - search.highlight + - search.suggest + +## Plugins +plugins: + - search + - autolink_references: + autolinks: + - reference_prefix: GP- + target_url: https://github.com/ + - reference_prefix: GH- + target_url: https://github.com/PHPOffice/PHPWord/issues/ + - reference_prefix: CP- + target_url: https://archive.codeplex.com/?p=phpword& + +## Config +extra: + generator: false +markdown_extensions: + ## Syntax highlighting + - pymdownx.highlight + - pymdownx.superfences + ## Support for emojis + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + ## Support for call-outs + - admonition + - pymdownx.details +use_directory_urls: false + +## Navigation +nav: + - Introduction: 'index.md' + - Install: 'install.md' + - Usage: + - Introduction: 'usage/introduction.md' + - Containers: 'usage/containers.md' + - Elements: + - Introduction: 'usage/elements/index.md' + - Chart: 'usage/elements/chart.md' + - Checkbox: 'usage/elements/checkbox.md' + - Comment: 'usage/elements/comment.md' + - Field: 'usage/elements/field.md' + - Footnote & Endnote: 'usage/elements/note.md' + - Formula: 'usage/elements/formula.md' + - Image: 'usage/elements/image.md' + - Line: 'usage/elements/line.md' + - Link: 'usage/elements/link.md' + - List: 'usage/elements/list.md' + - OLE Object: 'usage/elements/oleobject.md' + - Page Break: 'usage/elements/pagebreak.md' + - Preserve Text: 'usage/elements/preservetext.md' + - Text: 'usage/elements/text.md' + - TextBox: 'usage/elements/textbox.md' + - Text Break: 'usage/elements/textbreak.md' + - Table: 'usage/elements/table.md' + - Table of contents: 'usage/elements/toc.md' + - Title: 'usage/elements/title.md' + - Track Changes: 'usage/elements/trackchanges.md' + - Watermark: 'usage/elements/watermark.md' + - Styles: + - Chart: 'usage/styles/chart.md' + - Font: 'usage/styles/font.md' + - Image: 'usage/styles/image.md' + - Numbering Level: 'usage/styles/numberinglevel.md' + - Paragraph: 'usage/styles/paragraph.md' + - Section: 'usage/styles/section.md' + - Table: 'usage/styles/table.md' + - Template Processing: 'usage/template.md' + - Readers: 'usage/readers.md' + - Writers: 'usage/writers.md' + - FAQ: 'faq.md' + - How to: 'howto.md' + - Credits: 'credits.md' + - Releases: + - '1.x': + - '1.4.0 (WIP)': 'changes/1.x/1.4.0.md' + - '1.3.0': 'changes/1.x/1.3.0.md' + - '1.2.0': 'changes/1.x/1.2.0.md' + - '1.1.0': 'changes/1.x/1.1.0.md' + - '1.0.0': 'changes/1.x/1.0.0.md' + - '0.x': + - '0.18.3': 'changes/0.x/0.18.3.md' + - '0.18.2': 'changes/0.x/0.18.2.md' + - '0.18.1': 'changes/0.x/0.18.1.md' + - '0.18.0': 'changes/0.x/0.18.0.md' + - '0.17.0': 'changes/0.x/0.17.0.md' + - '0.16.0': 'changes/0.x/0.16.0.md' + - '0.15.0': 'changes/0.x/0.15.0.md' + - '0.14.0': 'changes/0.x/0.14.0.md' + - '0.13.0': 'changes/0.x/0.13.0.md' + - '0.12.1': 'changes/0.x/0.12.1.md' + - '0.12.0': 'changes/0.x/0.12.0.md' + - '0.11.1': 'changes/0.x/0.11.1.md' + - '0.11.0': 'changes/0.x/0.11.0.md' + - '0.10.1': 'changes/0.x/0.10.1.md' + - '0.10.0': 'changes/0.x/0.10.0.md' + - '0.9.1': 'changes/0.x/0.9.1.md' + - '0.9.0': 'changes/0.x/0.9.0.md' + - '0.8.1': 'changes/0.x/0.8.1.md' + - '0.8.0': 'changes/0.x/0.8.0.md' + - '0.7.0': 'changes/0.x/0.7.0.md' + - Developers: + - 'Coveralls': 'https://coveralls.io/github/PHPOffice/PHPWord' + - 'Code Coverage': 'coverage/index.html' + - 'PHPDoc': 'docs/index.html' diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000000..909718b83b --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,1931 @@ +parameters: + ignoreErrors: + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:__call\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement but returns null\\.$#" + count: 1 + path: src/PhpWord/Element/AbstractContainer.php + + - + message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|T of object, string given\\.$#" + count: 1 + path: src/PhpWord/Element/AbstractContainer.php + + - + message: "#^Parameter \\#1 \\$string of function md5 expects string, int\\<0, max\\> given\\.$#" + count: 1 + path: src/PhpWord/Element/AbstractElement.php + + - + message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\|null given\\.$#" + count: 1 + path: src/PhpWord/Element/Cell.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:setOptions\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Field but returns array\\.$#" + count: 1 + path: src/PhpWord/Element/Field.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:setProperties\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Field but returns array\\.$#" + count: 1 + path: src/PhpWord/Element/Field.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\) does not accept null\\.$#" + count: 1 + path: src/PhpWord/Element/Field.php + + - + message: "#^Result of \\|\\| is always true\\.$#" + count: 1 + path: src/PhpWord/Element/Field.php + + - + message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Element/Footnote.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:getArchiveImageSize\\(\\) should return array\\|null but returns array\\|false\\|null\\.$#" + count: 1 + path: src/PhpWord/Element/Image.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:getImageString\\(\\) should return string\\|null but returns string\\|false\\|null\\.$#" + count: 1 + path: src/PhpWord/Element/Image.php + + - + message: "#^Parameter \\#1 \\$callback of function call_user_func expects callable\\(\\)\\: mixed, string given\\.$#" + count: 1 + path: src/PhpWord/Element/Image.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\Image\\:\\:\\$source \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: src/PhpWord/Element/Image.php + + - + message: "#^Offset 'extension' does not exist on array\\{dirname\\?\\: string, basename\\: string, extension\\?\\: string, filename\\: string\\}\\.$#" + count: 2 + path: src/PhpWord/Element/OLEObject.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Element\\\\OLEObject\\:\\:\\$icon \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: src/PhpWord/Element/OLEObject.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Section\\:\\:addHeader\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Header but returns PhpOffice\\\\PhpWord\\\\Element\\\\Footer\\.$#" + count: 1 + path: src/PhpWord/Element/Section.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Element\\\\Section\\:\\:addHeaderFooter\\(\\) should return PhpOffice\\\\PhpWord\\\\Element\\\\Footer but returns PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\.$#" + count: 1 + path: src/PhpWord/Element/Section.php + + - + message: "#^Parameter \\#2 \\$styleValue of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setNewStyle\\(\\) expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, array\\|PhpOffice\\\\PhpWord\\\\Style\\|PhpOffice\\\\PhpWord\\\\Style\\\\Section\\|string given\\.$#" + count: 1 + path: src/PhpWord/Element/Section.php + + - + message: "#^Parameter \\#3 \\$length of function substr expects int\\|null, int\\|false given\\.$#" + count: 1 + path: src/PhpWord/Element/Section.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeAsciiCharacter\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Escaper/Rtf.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeAsciiCharacter\\(\\) has parameter \\$code with no type specified\\.$#" + count: 1 + path: src/PhpWord/Escaper/Rtf.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeMultibyteCharacter\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Escaper/Rtf.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Escaper\\\\Rtf\\:\\:escapeMultibyteCharacter\\(\\) has parameter \\$code with no type specified\\.$#" + count: 1 + path: src/PhpWord/Escaper/Rtf.php + + - + message: "#^Cannot instantiate interface PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface\\.$#" + count: 1 + path: src/PhpWord/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createObject\\(\\) should return PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface but returns object\\.$#" + count: 1 + path: src/PhpWord/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createReader\\(\\) should return PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface but returns PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface\\.$#" + count: 1 + path: src/PhpWord/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\IOFactory\\:\\:createWriter\\(\\) should return PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface but returns PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\|PhpOffice\\\\PhpWord\\\\Writer\\\\WriterInterface\\.$#" + count: 1 + path: src/PhpWord/IOFactory.php + + - + message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|T of object, string given\\.$#" + count: 1 + path: src/PhpWord/IOFactory.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:getDefaultFontSize\\(\\) should return int but returns float\\|int\\.$#" + count: 1 + path: src/PhpWord/PhpWord.php + + - + message: "#^Parameter \\#1 \\$callback of function forward_static_call_array expects callable\\(\\)\\: mixed, array\\{'PhpOffice\\\\\\\\PhpWord…', string\\} given\\.$#" + count: 1 + path: src/PhpWord/PhpWord.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\AbstractReader\\:\\:openFile\\(\\) should return resource but return statement is missing\\.$#" + count: 1 + path: src/PhpWord/Reader/AbstractReader.php + + - + message: "#^Parameter \\#2 \\$html of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:addHtml\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpWord/Reader/HTML.php + + - + message: "#^Offset 'textNodes' on array\\{changed\\: PhpOffice\\\\PhpWord\\\\Element\\\\TrackChange, textNodes\\: DOMNodeList\\\\} in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: src/PhpWord/Reader/ODText/Content.php + + - + message: "#^Parameter \\#2 \\$contextNode of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLReader\\:\\:getElements\\(\\) expects DOMElement\\|null, DOMNode\\|null given\\.$#" + count: 2 + path: src/PhpWord/Reader/ODText/Content.php + + - + message: "#^Parameter \\#2 \\$depth of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addTitle\\(\\) expects int, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/ODText/Content.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$rtf \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: src/PhpWord/Reader/RTF.php + + - + message: "#^Cannot call method setStyleByArray\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" + count: 1 + path: src/PhpWord/Reader/RTF/Document.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Reader\\\\RTF\\\\Document\\:\\:\\$phpWord is never read, only written\\.$#" + count: 1 + path: src/PhpWord/Reader/RTF/Document.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\ReaderInterface\\:\\:load\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Reader/ReaderInterface.php + + - + message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|false given\\.$#" + count: 2 + path: src/PhpWord/Reader/Word2007.php + + - + message: "#^Parameter \\#2 \\$xmlFile of method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\:\\:getRels\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007.php + + - + message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, string\\|false given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007.php + + - + message: "#^Binary operation \"/\" between string\\|null and 2 results in an error\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Call to an undefined method DOMNode\\:\\:getAttribute\\(\\)\\.$#" + count: 2 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:getHeadingDepth\\(\\) never returns float so it can be removed from the return type\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:getHeadingDepth\\(\\) should return float\\|int\\|null but returns string\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:read\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#1 \\$depth of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addListItemRun\\(\\) expects int, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#1 \\$height of method PhpOffice\\\\PhpWord\\\\Element\\\\Table\\:\\:addRow\\(\\) expects int\\|null, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:setRelationId\\(\\) expects int, string\\|null given\\.$#" + count: 2 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#1 \\$width of method PhpOffice\\\\PhpWord\\\\Element\\\\Row\\:\\:addCell\\(\\) expects int\\|null, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#2 \\$contextNode of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLReader\\:\\:getAttribute\\(\\) expects DOMElement\\|null, DOMNode\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#2 \\$depth of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addTitle\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and DOMElement will always evaluate to false\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/AbstractPart.php + + - + message: "#^Parameter \\#2 \\$relationId of method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\Footnotes\\:\\:getElement\\(\\) expects int, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Footnotes.php + + - + message: "#^Parameter \\#3 \\$levelId of method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\Numbering\\:\\:readLevel\\(\\) expects int, string\\|null given\\.$#" + count: 2 + path: src/PhpWord/Reader/Word2007/Numbering.php + + - + message: "#^Parameter \\#1 \\$comments of method PhpOffice\\\\PhpWord\\\\ComplexType\\\\TrackChangesView\\:\\:setComments\\(\\) expects bool\\|null, string\\|null given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Settings.php + + - + message: "#^Parameter \\#1 \\$consecutiveHyphenLimit of method PhpOffice\\\\PhpWord\\\\Metadata\\\\Settings\\:\\:setConsecutiveHyphenLimit\\(\\) expects int, string given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Settings.php + + - + message: "#^Parameter \\#1 \\$hyphenationZone of method PhpOffice\\\\PhpWord\\\\Metadata\\\\Settings\\:\\:setHyphenationZone\\(\\) expects float\\|int\\|null, string given\\.$#" + count: 1 + path: src/PhpWord/Reader/Word2007/Settings.php + + - + message: "#^Parameter \\#1 \\$filename of function parse_ini_file expects string, string\\|false given\\.$#" + count: 1 + path: src/PhpWord/Settings.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\AbstractEnum\\:\\:getConstants\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/AbstractEnum.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\AbstractEnum\\:\\:\\$constCacheArray has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/AbstractEnum.php + + - + message: "#^Binary operation \"/\" between string and 10 results in an error\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:angleToDegree\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:cssToPoint\\(\\) should return float\\|null but returns string\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:htmlToRgb\\(\\) should return array but returns false\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:inchToEmu\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:pixelToEmu\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$centimeter of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:cmToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$inch of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:inchToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$pica of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:picaToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Parameter \\#1 \\$pixel of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Converter\\:\\:pixelToPoint\\(\\) expects float, string given\\.$#" + count: 1 + path: src/PhpWord/Shared/Converter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:angleToDegrees\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Drawing.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:emuToPixels\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Drawing.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Drawing\\:\\:pixelsToEmu\\(\\) should return int but returns float\\.$#" + count: 1 + path: src/PhpWord/Shared/Drawing.php + + - + message: "#^Binary operation \"\\*\" between string and 50 results in an error\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Call to an undefined method DOMNode\\:\\:getAttribute\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Cannot call method setBorderSize\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Cannot call method setStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^If condition is always true\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:addHtml\\(\\) has parameter \\$options with no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:filterOutNonInheritedStyles\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapBorderColor\\(\\) has parameter \\$cssBorderColor with no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapBorderColor\\(\\) has parameter \\$styles with no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:mapListType\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseLink\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseList\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseStyleDeclarations\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:recursiveParseStylesInHierarchy\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Parameter \\#1 \\$attribute of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseStyle\\(\\) expects DOMAttr, DOMNode given\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Parameter \\#2 \\$element of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:parseNode\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\|PhpOffice\\\\PhpWord\\\\Element\\\\Row\\|PhpOffice\\\\PhpWord\\\\Element\\\\Table given\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$listIndex has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$options has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Html\\:\\:\\$xpath has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Result of \\|\\| is always true\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Right side of && is always true\\.$#" + count: 1 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Variable \\$cNodes in empty\\(\\) always exists and is not falsy\\.$#" + count: 2 + path: src/PhpWord/Shared/Html.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$encryptionMatrix has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$initialCodeArray has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\Microsoft\\\\PasswordEncoder\\:\\:\\$passwordMaxLength has no type specified\\.$#" + count: 1 + path: src/PhpWord/Shared/Microsoft/PasswordEncoder.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:getData\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/PhpWord/Shared/XMLWriter.php + + - + message: "#^Call to method add\\(\\) on an unknown class PclZip\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method addFromString\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method close\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method extract\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method extractByIndex\\(\\) on an unknown class PclZip\\.$#" + count: 3 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method extractTo\\(\\) on an unknown class PclZip\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method getFromName\\(\\) on an unknown class PclZip\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Call to method listContent\\(\\) on an unknown class PclZip\\.$#" + count: 3 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Comparison operation \"\\!\\=\" between array and 0 results in an error\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_ADD_PATH not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_EXTRACT_AS_STRING not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_PATH not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Constant PCLZIP_OPT_REMOVE_PATH not found\\.$#" + count: 2 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Instantiated class PclZip not found\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:getFromName\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:open\\(\\) should return bool but returns int\\|true\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Offset 'dirname' does not exist on array\\{dirname\\?\\: string, basename\\: string, extension\\?\\: string, filename\\: string\\}\\.$#" + count: 3 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^PHPDoc tag @var for variable \\$zip contains unknown class PclZip\\.$#" + count: 6 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Parameter \\#1 \\$callback of function call_user_func_array expects callable\\(\\)\\: mixed, array\\{\\$this\\(PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\)\\|PclZip\\|ZipArchive, mixed\\} given\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Parameter \\#1 \\$stream of function fclose expects resource, resource\\|false given\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Parameter \\#1 \\$stream of function fwrite expects resource, resource\\|false given\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:\\$zip has unknown class PclZip as its type\\.$#" + count: 1 + path: src/PhpWord/Shared/ZipArchive.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addFontStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addLinkStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addNumberingStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Numbering but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addParagraphStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addTableStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Table but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\:\\:addTitleStyle\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style.php + + - + message: "#^Call to an undefined method object\\:\\:setStyleByArray\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Style/AbstractStyle.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setFloatVal\\(\\) should return float\\|null but returns float\\|int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/AbstractStyle.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setNumericVal\\(\\) should return float\\|int\\|null but returns float\\|int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/AbstractStyle.php + + - + message: "#^Parameter \\#3 \\$length of function substr expects int\\|null, int\\|false given\\.$#" + count: 1 + path: src/PhpWord/Style/AbstractStyle.php + + - + message: "#^Unreachable statement \\- code above always terminates\\.$#" + count: 1 + path: src/PhpWord/Style/AbstractStyle.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Border\\:\\:getBorderSize\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: src/PhpWord/Style/Border.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\:\\:getBgColor\\(\\) should return string but returns null\\.$#" + count: 1 + path: src/PhpWord/Style/Cell.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\:\\:setUnit\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Cell.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\:\\:\\$shading is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Cell.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:getMajorTickPosition\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setCategoryAxisTitle\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setValueAxisTitle\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:setValueLabelPosition\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:showAxisLabels\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Chart\\:\\:showGridY\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^PHPDoc tag @param has invalid value \\(string\\)\\: Unexpected token \"\\\\n \\* \", expected variable at offset 250$#" + count: 1 + path: src/PhpWord/Style/Chart.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setAllCaps\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setBgColor\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Table but return statement is missing\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setDoubleStrikethrough\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSmallCaps\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setStrikethrough\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSubScript\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:setSuperScript\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Font but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:\\$allCaps is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:\\$doubleStrikethrough is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:\\$lang is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:\\$paragraph is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:\\$shading is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:\\$smallCaps is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Font\\:\\:\\$strikethrough is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Font.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Line\\:\\:\\$weight \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Line.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\ListItem\\:\\:getListTypeStyle\\(\\) should return array but empty return statement found\\.$#" + count: 1 + path: src/PhpWord/Style/ListItem.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\ListItem\\:\\:getListTypeStyle\\(\\) should return array but return statement is missing\\.$#" + count: 1 + path: src/PhpWord/Style/ListItem.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\:\\:setStyleValue\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Paragraph.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\:\\:\\$indentation is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Paragraph.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\:\\:\\$shading is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Paragraph.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\:\\:\\$spacing is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Paragraph.php + + - + message: "#^Result of && is always false\\.$#" + count: 1 + path: src/PhpWord/Style/Paragraph.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Section\\:\\:setSettingValue\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\Section but returns PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\.$#" + count: 1 + path: src/PhpWord/Style/Section.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Section\\:\\:\\$lineNumbering is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Section.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Shape\\:\\:\\$extrusion is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Shape.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Shape\\:\\:\\$fill is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Shape.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Shape\\:\\:\\$frame is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Shape.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Shape\\:\\:\\$outline is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Shape.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\Shape\\:\\:\\$shadow is never written, only read\\.$#" + count: 1 + path: src/PhpWord/Style/Shape.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\TOC\\:\\:setTabLeader\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\TOC but returns PhpOffice\\\\PhpWord\\\\Style\\\\Tab\\.$#" + count: 1 + path: src/PhpWord/Style/TOC.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\TOC\\:\\:setTabPos\\(\\) should return PhpOffice\\\\PhpWord\\\\Style\\\\TOC but returns PhpOffice\\\\PhpWord\\\\Style\\\\Tab\\.$#" + count: 1 + path: src/PhpWord/Style/TOC.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideHColor\\(\\) should return string but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideHSize\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideVColor\\(\\) should return string but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderInsideVSize\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getBorderSize\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginBottom\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginLeft\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginRight\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Style\\\\Table\\:\\:getCellMarginTop\\(\\) should return int but returns int\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/Table.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$bottomFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$leftFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$rightFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$tblpX \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$tblpY \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Style\\\\TablePosition\\:\\:\\$topFromText \\(int\\) does not accept float\\|int\\|null\\.$#" + count: 1 + path: src/PhpWord/Style/TablePosition.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Cannot access offset 'end' on array\\\\|true\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Cannot access offset 'start' on array\\\\|true\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$imageMimeType with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$imgPath with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$partFileName with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:addImageToRelations\\(\\) has parameter \\$rid with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$baseValue with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$defaultValue with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:chooseImageDimension\\(\\) has parameter \\$inlineValue with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$actualHeight with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$actualWidth with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$height with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:fixImageWidthHeightRatio\\(\\) has parameter \\$width with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getImageArgs\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getImageArgs\\(\\) has parameter \\$varNameWithArgs with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getNextRelationsIndex\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:getNextRelationsIndex\\(\\) has parameter \\$documentPartName with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:indexClonedVariables\\(\\) should return string but returns array\\, string\\|null\\>\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has parameter \\$replaceImage with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:prepareImageAttrs\\(\\) has parameter \\$varInlineArgs with no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:setValueForPart\\(\\) should return string but returns array\\\\|string\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:setValueForPart\\(\\) should return string but returns array\\\\|string\\|null\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Parameter \\#1 \\$element of method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\Chart\\:\\:setElement\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\Chart, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement given\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Parameter \\#2 \\$array of function implode expects array\\|null, array\\\\|string given\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Parameter \\#2 \\$array of function implode expects array\\|null, string given\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$macroClosingChars has no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$macroOpeningChars has no type specified\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$tempDocumentFooters \\(array\\\\) does not accept string\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\TemplateProcessor\\:\\:\\$tempDocumentHeaders \\(array\\\\) does not accept string\\.$#" + count: 1 + path: src/PhpWord/TemplateProcessor.php + + - + message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: src/PhpWord/Writer/AbstractWriter.php + + - + message: "#^PHPDoc tag @param has invalid value \\(\\\\PhpOffice\\\\PhpWord\\\\PhpWord\\)\\: Unexpected token \"\\\\n \\*\", expected variable at offset 78$#" + count: 1 + path: src/PhpWord/Writer/AbstractWriter.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\HTML\\\\Element\\\\AbstractElement\\:\\:write\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/HTML/Element/AbstractElement.php + + - + message: "#^Variable \\$row in PHPDoc tag @var does not match assigned variable \\$rowStyle\\.$#" + count: 1 + path: src/PhpWord/Writer/HTML/Element/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Field\\:\\:writeDefault\\(\\) has parameter \\$type with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Field.php + + - + message: "#^Variable \\$row in PHPDoc tag @var does not match any variable in the foreach loop\\: \\$cell$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Table.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Text\\:\\:replacetabs\\(\\) has parameter \\$text with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Text.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Text\\:\\:replacetabs\\(\\) has parameter \\$xmlWriter with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Text.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Element\\\\Text\\:\\:writeChangeInsertion\\(\\) has parameter \\$start with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/Text.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getParagraphStyle\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Element/TextRun.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setColumnWidths\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Parameter \\#1 \\$container of method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Content\\:\\:collectTrackedChanges\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer, PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement given\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Content\\:\\:\\$imageParagraphStyles has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Content.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 2 + path: src/PhpWord/Writer/ODText/Part/Styles.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\ODText\\\\Part\\\\Styles\\:\\:cvttwiptostr\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/ODText/Part/Styles.php + + - + message: "#^Parameter \\#1 \\$callback of function call_user_func_array expects callable\\(\\)\\: mixed, array\\{PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\AbstractRenderer, string\\} given\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:\\$renderer \\(PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\AbstractRenderer\\) does not accept object\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:loadHtml\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:output\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:render\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:setPaper\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Class PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF referenced with incorrect case\\: PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\Dompdf\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF\\:\\:createExternalWriterInstance\\(\\) should return PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\\\DomPDF but returns Dompdf\\\\Dompdf\\.$#" + count: 1 + path: src/PhpWord/Writer/PDF/DomPDF.php + + - + message: "#^Binary operation \"\\+\" between int\\|string and 1 results in an error\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Parameter \\#1 \\$value of method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Style\\\\Font\\:\\:setNameIndex\\(\\) expects int, int\\|string given\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|null\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$fontStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Font\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|null\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\AbstractElement\\:\\:\\$paragraphStyle \\(PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\) does not accept null\\.$#" + count: 2 + path: src/PhpWord/Writer/RTF/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:write\\(\\) should return string but empty return statement found\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writeDate\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writeNumpages\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\RTF\\\\Element\\\\Field\\:\\:writePage\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Field.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getImageStringData\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Image.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getStyle\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Image.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getSource\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Link.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Element/Link.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 2 + path: src/PhpWord/Writer/RTF/Part/Document.php + + - + message: "#^Binary operation \"\\+\" between int\\|string and 1 results in an error\\.$#" + count: 1 + path: src/PhpWord/Writer/RTF/Style/Border.php + + - + message: "#^PHPDoc tag @param has invalid value \\(\\\\PhpOffice\\\\PhpWord\\\\PhpWord\\)\\: Unexpected token \"\\\\n \", expected variable at offset 86$#" + count: 1 + path: src/PhpWord/Writer/Word2007.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\AbstractElement\\:\\:write\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/AbstractElement.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\Field\\:\\:buildPropertiesAndOptions\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/Field.php + + - + message: "#^Parameter \\#1 \\$content of method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\AbstractElement\\:\\:writeText\\(\\) expects string, bool\\|int\\|string given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/FormField.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, bool\\|int\\|string given\\.$#" + count: 3 + path: src/PhpWord/Writer/Word2007/Element/FormField.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int given\\.$#" + count: 4 + path: src/PhpWord/Writer/Word2007/Element/FormField.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\ParagraphAlignment\\:\\:\\$attributes has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\ParagraphAlignment\\:\\:\\$name has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php + + - + message: "#^Parameter \\#2 \\$content of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, bool\\|int\\|string given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/SDT.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int\\<100000000, 999999999\\> given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/SDT.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\TableAlignment\\:\\:\\$attributes has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/TableAlignment.php + + - + message: "#^Property PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Element\\\\TableAlignment\\:\\:\\$name has no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/TableAlignment.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:addBookmark\\(\\) invoked with 0 parameters, 1 required\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Element/Title.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Part\\\\Chart\\:\\:writeAxisTitle\\(\\) has parameter \\$title with no type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Part/Chart.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int given\\.$#" + count: 9 + path: src/PhpWord/Writer/Word2007/Part/Chart.php + + - + message: "#^Parameter \\#3 \\$value of method PhpOffice\\\\PhpWord\\\\Shared\\\\XMLWriter\\:\\:writeElementBlock\\(\\) expects string\\|null, int\\<0, max\\> given\\.$#" + count: 4 + path: src/PhpWord/Writer/Word2007/Part/Chart.php + + - + message: "#^Parameter \\#1 \\$string of function md5 expects string, int\\<0, max\\> given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Part/Numbering.php + + - + message: "#^Parameter \\#1 \\$haystack of function strpos expects string, int given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Part/Rels.php + + - + message: "#^Method PhpOffice\\\\PhpWord\\\\Writer\\\\Word2007\\\\Style\\\\AbstractStyle\\:\\:write\\(\\) has no return type specified\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Style/AbstractStyle.php + + - + message: "#^Parameter \\#1 \\$styleName of static method PhpOffice\\\\PhpWord\\\\Style\\:\\:getStyle\\(\\) expects string, PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\|string given\\.$#" + count: 1 + path: src/PhpWord/Writer/Word2007/Style/Font.php + + - + message: "#^Call to an undefined method object\\:\\:read\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractTestReader.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\AbstractTestReader\\:\\:\\$parts has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractTestReader.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbeddedTest\\:\\:getBaseUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbeddedTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbeddedTest\\:\\:getRemoteBmpImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbeddedTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbeddedTest\\:\\:getRemoteGifImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbeddedTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbeddedTest\\:\\:getRemoteImageUrl\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbeddedTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\AbstractWebServerEmbeddedTest\\:\\:\\$httpServer has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/AbstractWebServerEmbeddedTest.php + + - + message: "#^Parameter \\#1 \\$width of class PhpOffice\\\\PhpWord\\\\Element\\\\Cell constructor expects int\\|null, string given\\.$#" + count: 2 + path: tests/PhpWordTests/Element/CellTest.php + + - + message: "#^Parameter \\#2 \\$style of class PhpOffice\\\\PhpWord\\\\Element\\\\Cell constructor expects array\\|PhpOffice\\\\PhpWord\\\\Style\\\\Cell\\|null, int given\\.$#" + count: 2 + path: tests/PhpWordTests/Element/CellTest.php + + - + message: "#^Parameter \\#1 \\$text of method PhpOffice\\\\PhpWord\\\\Element\\\\Field\\:\\:setText\\(\\) expects PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string\\|null, array given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/FieldTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$createFunction with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$extension with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$imageFunction with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$imageQuality with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$source with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Element\\\\ImageTest\\:\\:testImages\\(\\) has parameter \\$type with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Parameter \\#1 \\$source of class PhpOffice\\\\PhpWord\\\\Element\\\\Image constructor expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Parameter \\#1 \\$string of function md5 expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Parameter \\#1 \\$string of function ucfirst expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Parameter \\#3 \\$watermark of class PhpOffice\\\\PhpWord\\\\Element\\\\Image constructor expects bool, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/ImageTest.php + + - + message: "#^Parameter \\#2 \\$style of class PhpOffice\\\\PhpWord\\\\Element\\\\Section constructor expects array\\|PhpOffice\\\\PhpWord\\\\Style\\|string\\|null, PhpOffice\\\\PhpWord\\\\Style\\\\Section given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/SectionTest.php + + - + message: "#^Parameter \\#1 \\$text of class PhpOffice\\\\PhpWord\\\\Element\\\\Title constructor expects PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string, PhpOffice\\\\PhpWord\\\\Element\\\\PageBreak given\\.$#" + count: 1 + path: tests/PhpWordTests/Element/TitleTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:escapestring\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:escapestring\\(\\) has parameter \\$str with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:expect\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Escaper\\\\RtfEscaper2Test\\:\\:expect\\(\\) has parameter \\$str with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Escaper/RtfEscaper2Test.php + + - + message: "#^Parameter \\#1 \\$expected of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) expects class\\-string\\, string given\\.$#" + count: 1 + path: tests/PhpWordTests/IOFactoryTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/IOFactoryTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\PhpWord\\:\\:undefinedMethod\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/PhpWordTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getRows\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/ElementTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/ElementTest.php + + - + message: "#^Cannot access offset 0 on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/ElementTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getElement\\(\\)\\.$#" + count: 2 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Cannot call method getElement\\(\\) on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\|string\\.$#" + count: 3 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Cannot call method isBold\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Variable \\$endnote in PHPDoc tag @var does not match assigned variable \\$documentEndnote\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Variable \\$footnote in PHPDoc tag @var does not match assigned variable \\$documentFootnote\\.$#" + count: 1 + path: tests/PhpWordTests/Reader/Word2007/PartTest.php + + - + message: "#^Cannot access offset 0 on PhpOffice\\\\PhpWord\\\\Element\\\\TextRun\\.$#" + count: 2 + path: tests/PhpWordTests/Reader/Word2007/StyleTest.php + + - + message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$compatibility has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultFontName has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultFontSize has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$defaultPaper has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$measurementUnit has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$pdfRendererName has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$pdfRendererPath has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$tempDir has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\SettingsTest\\:\\:\\$zipClass has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/SettingsTest.php + + - + message: "#^Cannot call method getStyleName\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Table\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Shared/HtmlTest.php + + - + message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpWord\\\\Shared\\\\Text\\:\\:numberFormat\\(\\) expects float, string given\\.$#" + count: 2 + path: tests/PhpWordTests/Shared/TextTest.php + + - + message: "#^Parameter \\#2 \\$locale of function setlocale expects array\\|string\\|null, int given\\.$#" + count: 1 + path: tests/PhpWordTests/Shared/XMLWriterTest.php + + - + message: "#^Parameter \\#2 \\$locale of function setlocale expects string\\|null, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Shared/XMLWriterTest.php + + - + message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: tests/PhpWordTests/Shared/ZipArchiveTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Style\\\\AbstractStyleTest\\:\\:callProtectedMethod\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Style/AbstractStyleTest.php + + - + message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|object, class\\-string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/AbstractStyleTest.php + + - + message: "#^Cannot call method setLineHeight\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Font\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Style/FontTest.php + + - + message: "#^Parameter \\#1 \\$type of class PhpOffice\\\\PhpWord\\\\Style\\\\Font constructor expects string, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/FontTest.php + + - + message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setStyleValue\\(\\) expects array\\|int\\|string, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/FontTest.php + + - + message: "#^Cannot call method setLineHeight\\(\\) on PhpOffice\\\\PhpWord\\\\Style\\\\Paragraph\\|string\\.$#" + count: 1 + path: tests/PhpWordTests/Style/ParagraphTest.php + + - + message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\AbstractStyle\\:\\:setStyleValue\\(\\) expects array\\|int\\|string, bool given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/RowTest.php + + - + message: "#^Parameter \\#2 \\$value of method PhpOffice\\\\PhpWord\\\\Style\\\\Section\\:\\:setSettingValue\\(\\) expects array\\|int\\|string, null given\\.$#" + count: 1 + path: tests/PhpWordTests/Style/SectionTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractElement\\:\\:getText\\(\\)\\.$#" + count: 2 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Shared\\\\ZipArchive\\:\\:AddFromString\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Cannot access offset 'end' on array\\\\|bool\\.$#" + count: 2 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Cannot access offset 'start' on array\\\\|bool\\.$#" + count: 2 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\) expects string, string\\|false given\\.$#" + count: 6 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringNotContainsString\\(\\) expects string, string\\|false given\\.$#" + count: 4 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Part \\$documentZip \\(ZipArchive\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Part \\$templateZip \\(ZipArchive\\) of encapsed string cannot be cast to string\\.$#" + count: 1 + path: tests/PhpWordTests/TemplateProcessorTest.php + + - + message: "#^Argument of an invalid type array\\\\|false supplied for foreach, only iterables are supported\\.$#" + count: 1 + path: tests/PhpWordTests/TestHelperDOCX.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\TestableTemplateProcesor\\:\\:__construct\\(\\) has parameter \\$mainPart with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/TestableTemplateProcesor.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\TestableTemplateProcesor\\:\\:__construct\\(\\) has parameter \\$settingsPart with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/TestableTemplateProcesor.php + + - + message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" + count: 9 + path: tests/PhpWordTests/Writer/HTML/ElementTest.php + + - + message: "#^Cannot call method item\\(\\) on DOMNodeList\\\\|false\\.$#" + count: 11 + path: tests/PhpWordTests/Writer/HTML/ElementTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 3 + path: tests/PhpWordTests/Writer/HTML/Element/PageBreakTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\ODText\\\\Style\\\\FontTest\\:\\:providerAllNamedColors\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/ODText/Style/FontTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" + count: 2 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getOrientation\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getPaperSize\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getTempDir\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setFont\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setOrientation\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setPaperSize\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:setTempDir\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 3 + path: tests/PhpWordTests/Writer/PDF/DomPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/MPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/MPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 2 + path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php + + - + message: "#^Call to an undefined method PhpOffice\\\\PhpWord\\\\Writer\\\\PDF\\:\\:getFont\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDF/TCPDFTest.php + + - + message: "#^Parameter \\#2 \\$libraryBaseDir of static method PhpOffice\\\\PhpWord\\\\Settings\\:\\:setPdfRenderer\\(\\) expects string, string\\|false given\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/PDFTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/ElementTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\ElementTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/ElementTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has no return type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/StyleTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\RTF\\\\StyleTest\\:\\:removeCr\\(\\) has parameter \\$field with no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/RTF/StyleTest.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\Writer\\\\Word2007\\\\Element\\\\ChartTest\\:\\:\\$outputEscapingEnabled has no type specified\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/Element/ChartTest.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/ElementTest.php + + - + message: "#^Call to an undefined method object\\:\\:write\\(\\)\\.$#" + count: 1 + path: tests/PhpWordTests/Writer/Word2007/StyleTest.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getElement\\(\\) should return DOMElement\\|null but returns DOMNode\\|null\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + - + message: "#^Method PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:getNodeList\\(\\) should return DOMNodeList\\ but returns DOMNodeList\\\\|false\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$path \\(string\\) does not accept string\\|false\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php + + - + message: "#^Property PhpOffice\\\\PhpWordTests\\\\XmlDocument\\:\\:\\$xpath \\(DOMXPath\\) does not accept null\\.$#" + count: 1 + path: tests/PhpWordTests/XmlDocument.php diff --git a/phpstan.neon b/phpstan.neon index 666c63b9c1..aac94077bd 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,13 +1,19 @@ includes: - - vendor/phpstan/phpstan/conf/config.level1.neon + - phpstan-baseline.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon parameters: - memory-limit: 20000000 - autoload_directories: - - tests - autoload_files: - - tests/bootstrap.php - excludes_analyse: - - */pclzip.lib.php - - src/PhpWord/Shared/OLERead.php - - src/PhpWord/Reader/MsDoc.php - - src/PhpWord/Writer/PDF/MPDF.php \ No newline at end of file + level: 7 + paths: + - src/ + - tests/ + excludePaths: + - */pclzip.lib.php + - src/PhpWord/Shared/OLERead.php + - src/PhpWord/Reader/MsDoc.php + - src/PhpWord/Writer/PDF/MPDF.php + bootstrapFiles: + - tests/bootstrap.php + ignoreErrors: + - + identifier: missingType.iterableValue diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e3f02121bb..6f1f5445ab 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,23 +1,24 @@ - - - - ./tests/PhpWordTests - - - - - ./src - - ./src/PhpWordTests/Shared/PCLZip - - - + + + + + ./src + + + ./src/PhpWord/Shared/PCLZip + + + + + + + + + + + + ./tests/PhpWordTests + + + diff --git a/samples/Sample_09_Tables.php b/samples/Sample_09_Tables.php index f1b67c9ecc..877072d898 100644 --- a/samples/Sample_09_Tables.php +++ b/samples/Sample_09_Tables.php @@ -56,11 +56,13 @@ /* * 3. colspan (gridSpan) and rowspan (vMerge) - * --------------------- - * | | B | | - * | A |--------| E | - * | | C | D | | - * --------------------- + * ------------------------- + * | A | B | C | + * |-----|-----------| | + * | D | | + * ------|-----------| | + * | E | F | G | | + * ------------------------- */ $section->addPageBreak(); @@ -77,25 +79,20 @@ $phpWord->addTableStyle($spanTableStyleName, $fancyTableStyle); $table = $section->addTable($spanTableStyleName); -$table->addRow(); - -$cell1 = $table->addCell(2000, $cellRowSpan); -$textrun1 = $cell1->addTextRun($cellHCentered); -$textrun1->addText('A'); -$textrun1->addFootnote()->addText('Row span'); - -$cell2 = $table->addCell(4000, $cellColSpan); -$textrun2 = $cell2->addTextRun($cellHCentered); -$textrun2->addText('B'); -$textrun2->addFootnote()->addText('Column span'); +$row1 = $table->addRow(); +$row1->addCell(500)->addText('A'); +$row1->addCell(1000, ['gridSpan' => 2])->addText('B'); +$row1->addCell(500, ['vMerge' => 'restart'])->addText('C'); -$table->addCell(2000, $cellRowSpan)->addText('E', null, $cellHCentered); +$row2 = $table->addRow(); +$row2->addCell(1500, ['gridSpan' => 3])->addText('D'); +$row2->addCell(null, ['vMerge' => 'continue']); -$table->addRow(); -$table->addCell(null, $cellRowContinue); -$table->addCell(2000, $cellVCentered)->addText('C', null, $cellHCentered); -$table->addCell(2000, $cellVCentered)->addText('D', null, $cellHCentered); -$table->addCell(null, $cellRowContinue); +$row3 = $table->addRow(); +$row3->addCell(500)->addText('E'); +$row3->addCell(500)->addText('F'); +$row3->addCell(500)->addText('G'); +$row3->addCell(null, ['vMerge' => 'continue']); /* * 4. colspan (gridSpan) and rowspan (vMerge) diff --git a/samples/Sample_13_Images.php b/samples/Sample_13_Images.php index 30b9cae949..f351bd91eb 100644 --- a/samples/Sample_13_Images.php +++ b/samples/Sample_13_Images.php @@ -1,22 +1,23 @@ addSection(); $section->addText('Local image without any styles:'); -$section->addImage('resources/_mars.jpg'); +$section->addImage(__DIR__ . '/resources/_mars.jpg'); printSeparator($section); $section->addText('Local image with styles:'); -$section->addImage('resources/_earth.jpg', ['width' => 210, 'height' => 210, 'alignment' => \PhpOffice\PhpWord\SimpleType\Jc::CENTER]); +$section->addImage(__DIR__ . '/resources/_earth.jpg', ['width' => 210, 'height' => 210, 'alignment' => \PhpOffice\PhpWord\SimpleType\Jc::CENTER]); // Remote image printSeparator($section); @@ -26,7 +27,7 @@ // Image from string printSeparator($section); -$source = 'resources/_mars.jpg'; +$source = __DIR__ . '/resources/_mars.jpg'; $fileContent = file_get_contents($source); $section->addText('Image from string'); $section->addImage($fileContent); @@ -38,7 +39,7 @@ foreach ($wrappingStyles as $wrappingStyle) { $section->addText("Wrapping style {$wrappingStyle}"); $section->addImage( - 'resources/_earth.jpg', + __DIR__ . '/resources/_earth.jpg', [ 'positioning' => 'relative', 'marginTop' => -1, @@ -57,7 +58,7 @@ //Absolute positioning $section->addText('Absolute positioning: see top right corner of page'); $section->addImage( - 'resources/_mars.jpg', + __DIR__ . '/resources/_mars.jpg', [ 'width' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(3), 'height' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(3), @@ -75,7 +76,7 @@ $section->addText('Relative positioning: Horizontal position center relative to column,'); $section->addText('Vertical position top relative to line'); $section->addImage( - 'resources/_mars.jpg', + __DIR__ . '/resources/_mars.jpg', [ 'width' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(3), 'height' => \PhpOffice\PhpWord\Shared\Converter::cmToPixel(3), diff --git a/samples/Sample_26_Html.php b/samples/Sample_26_Html.php index 84468faaa5..daf109966c 100644 --- a/samples/Sample_26_Html.php +++ b/samples/Sample_26_Html.php @@ -12,7 +12,7 @@ $html .= '

Some well-formed HTML snippet needs to be used

'; $html .= '

With for example some1 inline formatting1

'; -$html .= '

A link to Read the docs

'; +$html .= '

A link to Read the docs

'; $html .= '

היי, זה פסקה מימין לשמאל

'; diff --git a/samples/Sample_27_Field.php b/samples/Sample_27_Field.php index d250946169..fd441ba022 100644 --- a/samples/Sample_27_Field.php +++ b/samples/Sample_27_Field.php @@ -26,6 +26,9 @@ $section->addText('Number of pages field:'); $section->addField('NUMPAGES', ['numformat' => '0,00', 'format' => 'Arabic'], ['PreserveFormat']); + +$section->addText('Filename field:'); +$section->addField('FILENAME', ['format' => 'Upper'], ['Path', 'PreserveFormat']); $section->addTextBreak(); $textrun = $section->addTextRun(); diff --git a/samples/Sample_36_RTL.php b/samples/Sample_36_RTL.php index 4b3e760465..b3baca90cb 100644 --- a/samples/Sample_36_RTL.php +++ b/samples/Sample_36_RTL.php @@ -2,9 +2,15 @@ include_once 'Sample_Header.php'; +use PhpOffice\PhpWord\Settings; + // New Word document echo date('H:i:s'), ' Create new PhpWord object', EOL; $phpWord = new \PhpOffice\PhpWord\PhpWord(); +$phpWord->setDefaultFontName('DejaVu Sans'); // for good rendition of PDF +$rendererName = Settings::PDF_RENDERER_MPDF; +$rendererLibraryPath = $vendorDirPath . '/mpdf/mpdf'; +Settings::setPdfRenderer($rendererName, $rendererLibraryPath); // New section $section = $phpWord->addSection(); @@ -26,15 +32,15 @@ //Vidually bidirectinal table $table->addRow(); -$cell = $table->addCell(500, $cellVCentered); +$cell = $table->addCell(1500, $cellVCentered); $textrun = $cell->addTextRun($cellHCentered); $textrun->addText('ردیف', $style); -$cell = $table->addCell(11000); +$cell = $table->addCell(2000); $textrun = $cell->addTextRun($cellHEnd); $textrun->addText('سوالات', $style); -$cell = $table->addCell(500, $cellVCentered); +$cell = $table->addCell(1000, $cellVCentered); $textrun = $cell->addTextRun($cellHCentered); $textrun->addText('بارم', $style); diff --git a/samples/Sample_37_Comments.php b/samples/Sample_37_Comments.php index 79647478e8..8254f35c6d 100644 --- a/samples/Sample_37_Comments.php +++ b/samples/Sample_37_Comments.php @@ -42,7 +42,7 @@ $imageComment->addText('Hey, Mars does look '); $imageComment->addText('red', ['color' => 'FF0000']); $phpWord->addComment($commentOnImage); -$image = $section->addImage('resources/_mars.jpg'); +$image = $section->addImage(__DIR__ . '/resources/_mars.jpg'); $image->setCommentRangeStart($commentOnImage); $section->addTextBreak(2); @@ -56,6 +56,21 @@ $comment1->setEndElement($anotherText); $phpWord->addComment($comment1); +// We can also do things the other way round, link the comment to the element +$lastText = $section->addText('with a last text and two comments'); + +$comment1 = new \PhpOffice\PhpWord\Element\Comment('Authors name', new \DateTime(), 'my_initials'); +$comment1->addText('Comment 1', ['bold' => true]); +$comment1->setStartElement($lastText); +$comment1->setEndElement($lastText); +$phpWord->addComment($comment1); + +$comment2 = new \PhpOffice\PhpWord\Element\Comment('Authors name', new \DateTime(), 'my_initials'); +$comment2->addText('Comment 2', ['bold' => true]); +$comment2->setStartElement($lastText); +$comment2->setEndElement($lastText); +$phpWord->addComment($comment2); + // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); if (!CLI) { diff --git a/samples/Sample_42_TemplateSetCheckbox.php b/samples/Sample_42_TemplateSetCheckbox.php new file mode 100644 index 0000000000..9386a191b0 --- /dev/null +++ b/samples/Sample_42_TemplateSetCheckbox.php @@ -0,0 +1,21 @@ +setCheckbox('checkbox', true); +$templateProcessor->setCheckbox('checkbox2', false); + +echo date('H:i:s'), ' Saving the result document...', EOL; +$templateProcessor->saveAs(__DIR__ . "/results/{$filename}"); + +echo getEndingNotes(['Word2007' => 'docx'], "results/{$filename}"); +if (!CLI) { + include_once 'Sample_Footer.php'; +} diff --git a/samples/Sample_43_RTLDefault.php b/samples/Sample_43_RTLDefault.php new file mode 100644 index 0000000000..164ddd4379 --- /dev/null +++ b/samples/Sample_43_RTLDefault.php @@ -0,0 +1,33 @@ +setDefaultFontName('DejaVu Sans'); // for good rendition of PDF +$rendererName = Settings::PDF_RENDERER_MPDF; +$rendererLibraryPath = $vendorDirPath . '/mpdf/mpdf'; +Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + +// New section +$section = $phpWord->addSection(); +$arabic = '

الألم الذي ربما تنجم عنه بعض ا.

'; +$english = '

LTR in RTL document.

'; +SharedHtml::addHtml($section, $arabic, false, false); +SharedHtml::addHtml($section, $english, false, false); +SharedHtml::addHtml($section, $english, false, false); +SharedHtml::addHtml($section, $arabic, false, false); +SharedHtml::addHtml($section, $arabic, false, false); + +// Save file +echo write($phpWord, basename(__FILE__, '.php'), $writers); +if (!CLI) { + include_once 'Sample_Footer.php'; +} +Settings::setDefaultRtl(false); diff --git a/samples/Sample_44_ExtractVariablesFromReaderWord2007.php b/samples/Sample_44_ExtractVariablesFromReaderWord2007.php new file mode 100644 index 0000000000..24574e5fb7 --- /dev/null +++ b/samples/Sample_44_ExtractVariablesFromReaderWord2007.php @@ -0,0 +1,14 @@ + diff --git a/samples/index.php b/samples/index.php index c47deebe46..a9733d2c7c 100644 --- a/samples/index.php +++ b/samples/index.php @@ -4,7 +4,7 @@ use PhpOffice\PhpWord\Settings; $requirements = [ - 'php' => ['PHP 7.4', version_compare(PHP_VERSION, '7.4', '>=')], + 'php' => ['PHP 7.1', version_compare(PHP_VERSION, '7.1', '>=')], 'xml' => ['PHP extension XML', extension_loaded('xml')], 'temp' => ['Temp folder "' . Settings::getTempDir() . '" is writable', is_writable(Settings::getTempDir())], 'zip' => ['PHP extension ZipArchive (optional)', extension_loaded('zip')], @@ -19,7 +19,7 @@

 

Fork us on Github! - Read the Docs + Read the Docs

Adding element via HTML

Double height

Includes images

- + diff --git a/samples/resources/Sample_42_TemplateSetCheckbox.docx b/samples/resources/Sample_42_TemplateSetCheckbox.docx new file mode 100644 index 0000000000..9abc486b69 Binary files /dev/null and b/samples/resources/Sample_42_TemplateSetCheckbox.docx differ diff --git a/samples/resources/Sample_44_ExtractVariablesFromReaderWord2007.docx b/samples/resources/Sample_44_ExtractVariablesFromReaderWord2007.docx new file mode 100644 index 0000000000..a9385e126c Binary files /dev/null and b/samples/resources/Sample_44_ExtractVariablesFromReaderWord2007.docx differ diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 7741cfb475..0000000000 --- a/sonar-project.properties +++ /dev/null @@ -1,17 +0,0 @@ -# must be unique in a given SonarQube instance -sonar.projectKey=phpoffice:phpword -# this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1. -sonar.projectName=PHPWord -sonar.projectVersion=0.16 - -# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. -# This property is optional if sonar.modules is set. -sonar.sources=src -sonar.tests=tests -sonar.php.coverage.reportPaths=build/logs/clover.xml -sonar.php.tests.reportPath=build/logs/logfile.xml - -# Encoding of the source code. Default is default system encoding -#sonar.sourceEncoding=UTF-8 - -sonar.host.url=http://localhost:9000 \ No newline at end of file diff --git a/src/PhpWord/Collection/AbstractCollection.php b/src/PhpWord/Collection/AbstractCollection.php index 225b361566..646489d886 100644 --- a/src/PhpWord/Collection/AbstractCollection.php +++ b/src/PhpWord/Collection/AbstractCollection.php @@ -21,22 +21,23 @@ * Collection abstract class. * * @since 0.10.0 + * @template T */ abstract class AbstractCollection { /** * Items. * - * @var \PhpOffice\PhpWord\Element\AbstractContainer[] + * @var T[] */ private $items = []; /** * Get items. * - * @return \PhpOffice\PhpWord\Element\AbstractContainer[] + * @return T[] */ - public function getItems() + public function getItems(): array { return $this->items; } @@ -44,11 +45,9 @@ public function getItems() /** * Get item by index. * - * @param int $index - * - * @return \PhpOffice\PhpWord\Element\AbstractContainer + * @return ?T */ - public function getItem($index) + public function getItem(int $index) { if (array_key_exists($index, $this->items)) { return $this->items[$index]; @@ -60,10 +59,9 @@ public function getItem($index) /** * Set item. * - * @param int $index - * @param \PhpOffice\PhpWord\Element\AbstractContainer $item + * @param ?T $item */ - public function setItem($index, $item): void + public function setItem(int $index, $item): void { if (array_key_exists($index, $this->items)) { $this->items[$index] = $item; @@ -73,13 +71,11 @@ public function setItem($index, $item): void /** * Add new item. * - * @param \PhpOffice\PhpWord\Element\AbstractContainer $item - * - * @return int + * @param T $item */ - public function addItem($item) + public function addItem($item): int { - $index = $this->countItems() + 1; + $index = $this->countItems(); $this->items[$index] = $item; return $index; @@ -87,10 +83,8 @@ public function addItem($item) /** * Get item count. - * - * @return int */ - public function countItems() + public function countItems(): int { return count($this->items); } diff --git a/src/PhpWord/Collection/Bookmarks.php b/src/PhpWord/Collection/Bookmarks.php index e7d9b4a384..71544c9469 100644 --- a/src/PhpWord/Collection/Bookmarks.php +++ b/src/PhpWord/Collection/Bookmarks.php @@ -17,10 +17,13 @@ namespace PhpOffice\PhpWord\Collection; +use PhpOffice\PhpWord\Element\Bookmark; + /** * Bookmarks collection. * * @since 0.12.0 + * @extends AbstractCollection */ class Bookmarks extends AbstractCollection { diff --git a/src/PhpWord/Collection/Charts.php b/src/PhpWord/Collection/Charts.php index bb63a13962..7c2dfbab94 100644 --- a/src/PhpWord/Collection/Charts.php +++ b/src/PhpWord/Collection/Charts.php @@ -17,10 +17,13 @@ namespace PhpOffice\PhpWord\Collection; +use PhpOffice\PhpWord\Element\Chart; + /** * Charts collection. * * @since 0.12.0 + * @extends AbstractCollection */ class Charts extends AbstractCollection { diff --git a/src/PhpWord/Collection/Comments.php b/src/PhpWord/Collection/Comments.php index 8c6b577d7e..5fa4020a5a 100644 --- a/src/PhpWord/Collection/Comments.php +++ b/src/PhpWord/Collection/Comments.php @@ -17,10 +17,13 @@ namespace PhpOffice\PhpWord\Collection; +use PhpOffice\PhpWord\Element\Comment; + /** * Comments collection. * * @since 0.12.0 + * @extends AbstractCollection */ class Comments extends AbstractCollection { diff --git a/src/PhpWord/Collection/Endnotes.php b/src/PhpWord/Collection/Endnotes.php index 362b25881d..09903b1bf6 100644 --- a/src/PhpWord/Collection/Endnotes.php +++ b/src/PhpWord/Collection/Endnotes.php @@ -17,10 +17,13 @@ namespace PhpOffice\PhpWord\Collection; +use PhpOffice\PhpWord\Element\Endnote; + /** * Endnotes collection. * * @since 0.10.0 + * @extends AbstractCollection */ class Endnotes extends AbstractCollection { diff --git a/src/PhpWord/Collection/Footnotes.php b/src/PhpWord/Collection/Footnotes.php index 76eae3eab3..0387fce3c7 100644 --- a/src/PhpWord/Collection/Footnotes.php +++ b/src/PhpWord/Collection/Footnotes.php @@ -17,10 +17,13 @@ namespace PhpOffice\PhpWord\Collection; +use PhpOffice\PhpWord\Element\Footnote; + /** * Footnotes collection. * * @since 0.10.0 + * @extends AbstractCollection */ class Footnotes extends AbstractCollection { diff --git a/src/PhpWord/Collection/Titles.php b/src/PhpWord/Collection/Titles.php index 7b795771e8..543aabda1d 100644 --- a/src/PhpWord/Collection/Titles.php +++ b/src/PhpWord/Collection/Titles.php @@ -17,10 +17,13 @@ namespace PhpOffice\PhpWord\Collection; +use PhpOffice\PhpWord\Element\Title; + /** * Titles collection. * * @since 0.10.0 + * @extends AbstractCollection */ class Titles extends AbstractCollection { diff --git a/src/PhpWord/ComplexType/TrackChangesView.php b/src/PhpWord/ComplexType/TrackChangesView.php index 170c56033c..4331047414 100644 --- a/src/PhpWord/ComplexType/TrackChangesView.php +++ b/src/PhpWord/ComplexType/TrackChangesView.php @@ -72,7 +72,7 @@ public function hasMarkup() /** * Set Display Visual Indicator Of Markup Area. * - * @param bool $markup + * @param ?bool $markup * Set to true to show markup */ public function setMarkup($markup): void @@ -93,7 +93,7 @@ public function hasComments() /** * Set Display Comments. * - * @param bool $comments + * @param ?bool $comments * Set to true to show comments */ public function setComments($comments): void @@ -114,7 +114,7 @@ public function hasInsDel() /** * Set Display Content Revisions. * - * @param bool $insDel + * @param ?bool $insDel * Set to true to show content revisions */ public function setInsDel($insDel): void @@ -156,7 +156,7 @@ public function hasInkAnnotations() /** * Set Display Ink Annotations. * - * @param bool $inkAnnotations + * @param ?bool $inkAnnotations * Set to true to show ink annotations */ public function setInkAnnotations($inkAnnotations): void diff --git a/src/PhpWord/Element/AbstractContainer.php b/src/PhpWord/Element/AbstractContainer.php index 1e8bc7f71b..927e102613 100644 --- a/src/PhpWord/Element/AbstractContainer.php +++ b/src/PhpWord/Element/AbstractContainer.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Element; use BadMethodCallException; +use PhpOffice\Math\Math; use ReflectionClass; /** @@ -34,7 +35,7 @@ * @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(mixed $text, int $depth = 1) + * @method Title addTitle(mixed $text, int $depth = 1, int $pageNumber = null) * @method TOC addTOC(mixed $fontStyle = null, mixed $tocStyle = null, int $minDepth = 1, int $maxDepth = 9) * @method PageBreak addPageBreak() * @method Table addTable(mixed $style = null) @@ -47,6 +48,7 @@ * @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) + * @method Formula addFormula(Math $math) * @method \PhpOffice\PhpWord\Element\OLEObject addObject(string $source, mixed $style = null) deprecated, use addOLEObject instead * * @since 0.10.0 @@ -88,6 +90,7 @@ public function __call($function, $args) 'Footnote', 'Endnote', 'CheckBox', 'TextBox', 'Field', 'Line', 'Shape', 'Title', 'TOC', 'PageBreak', 'Chart', 'FormField', 'SDT', 'Comment', 'Cell', + 'Formula', ]; $functions = []; foreach ($elements as $element) { diff --git a/src/PhpWord/Element/AbstractElement.php b/src/PhpWord/Element/AbstractElement.php index 3b59d06cd4..385e4d3140 100644 --- a/src/PhpWord/Element/AbstractElement.php +++ b/src/PhpWord/Element/AbstractElement.php @@ -19,8 +19,10 @@ use DateTime; use InvalidArgumentException; +use PhpOffice\PhpWord\Collection\Comments; use PhpOffice\PhpWord\Media; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Style; /** * Element abstract class. @@ -32,7 +34,7 @@ abstract class AbstractElement /** * PhpWord object. * - * @var \PhpOffice\PhpWord\PhpWord + * @var ?PhpWord */ protected $phpWord; @@ -131,33 +133,31 @@ abstract class AbstractElement protected $collectionRelation = false; /** - * The start position for the linked comment. + * The start position for the linked comments. * - * @var Comment + * @var Comments */ - protected $commentRangeStart; + protected $commentsRangeStart; /** - * The end position for the linked comment. + * The end position for the linked comments. * - * @var Comment + * @var Comments */ - protected $commentRangeEnd; + protected $commentsRangeEnd; /** * Get PhpWord. * - * @return \PhpOffice\PhpWord\PhpWord + * @return ?PhpWord */ - public function getPhpWord() + public function getPhpWord(): ?PhpWord { return $this->phpWord; } /** * Set PhpWord as reference. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord */ public function setPhpWord(?PhpWord $phpWord = null): void { @@ -289,14 +289,28 @@ public function getNestedLevel() return $this->nestedLevel; } + /** + * Get comments start. + * + * @return Comments + */ + public function getCommentsRangeStart(): ?Comments + { + return $this->commentsRangeStart; + } + /** * Get comment start. * * @return Comment */ - public function getCommentRangeStart() + public function getCommentRangeStart(): ?Comment { - return $this->commentRangeStart; + if ($this->commentsRangeStart != null) { + return $this->commentsRangeStart->getItem($this->commentsRangeStart->countItems()); + } + + return null; } /** @@ -307,8 +321,30 @@ public function setCommentRangeStart(Comment $value): void if ($this instanceof Comment) { throw new InvalidArgumentException('Cannot set a Comment on a Comment'); } - $this->commentRangeStart = $value; - $this->commentRangeStart->setStartElement($this); + if ($this->commentsRangeStart == null) { + $this->commentsRangeStart = new Comments(); + } + // Set ID early to avoid duplicates. + if ($value->getElementId() == null) { + $value->setElementId(); + } + foreach ($this->commentsRangeStart->getItems() as $comment) { + if ($value->getElementId() == $comment->getElementId()) { + return; + } + } + $idxItem = $this->commentsRangeStart->addItem($value); + $this->commentsRangeStart->getItem($idxItem)->setStartElement($this); + } + + /** + * Get comments end. + * + * @return Comments + */ + public function getCommentsRangeEnd(): ?Comments + { + return $this->commentsRangeEnd; } /** @@ -316,9 +352,13 @@ public function setCommentRangeStart(Comment $value): void * * @return Comment */ - public function getCommentRangeEnd() + public function getCommentRangeEnd(): ?Comment { - return $this->commentRangeEnd; + if ($this->commentsRangeEnd != null) { + return $this->commentsRangeEnd->getItem($this->commentsRangeEnd->countItems()); + } + + return null; } /** @@ -329,8 +369,20 @@ public function setCommentRangeEnd(Comment $value): void if ($this instanceof Comment) { throw new InvalidArgumentException('Cannot set a Comment on a Comment'); } - $this->commentRangeEnd = $value; - $this->commentRangeEnd->setEndElement($this); + if ($this->commentsRangeEnd == null) { + $this->commentsRangeEnd = new Comments(); + } + // Set ID early to avoid duplicates. + if ($value->getElementId() == null) { + $value->setElementId(); + } + foreach ($this->commentsRangeEnd->getItems() as $comment) { + if ($value->getElementId() == $comment->getElementId()) { + return; + } + } + $idxItem = $this->commentsRangeEnd->addItem($value); + $this->commentsRangeEnd->getItem($idxItem)->setEndElement($this); } /** @@ -347,8 +399,6 @@ public function getParent() * Set parent container. * * Passed parameter should be a container, except for Table (contain Row) and Row (contain Cell) - * - * @param \PhpOffice\PhpWord\Element\AbstractElement $container */ public function setParentContainer(self $container): void { @@ -432,7 +482,7 @@ public function isInSection() * Set new style value. * * @param mixed $styleObject Style object - * @param mixed $styleValue Style value + * @param null|array|string|Style $styleValue Style value * @param bool $returnObject Always return object * * @return mixed diff --git a/src/PhpWord/Element/Cell.php b/src/PhpWord/Element/Cell.php index f07d246d5d..b0aa0c4ed4 100644 --- a/src/PhpWord/Element/Cell.php +++ b/src/PhpWord/Element/Cell.php @@ -32,21 +32,21 @@ class Cell extends AbstractContainer /** * Cell width. * - * @var int + * @var ?int */ private $width; /** * Cell style. * - * @var \PhpOffice\PhpWord\Style\Cell + * @var ?\PhpOffice\PhpWord\Style\Cell */ private $style; /** * Create new instance. * - * @param int $width + * @param null|int $width * @param array|\PhpOffice\PhpWord\Style\Cell $style */ public function __construct($width = null, $style = null) @@ -58,7 +58,7 @@ public function __construct($width = null, $style = null) /** * Get cell style. * - * @return \PhpOffice\PhpWord\Style\Cell + * @return ?\PhpOffice\PhpWord\Style\Cell */ public function getStyle() { @@ -68,7 +68,7 @@ public function getStyle() /** * Get cell width. * - * @return int + * @return ?int */ public function getWidth() { diff --git a/src/PhpWord/Element/Chart.php b/src/PhpWord/Element/Chart.php index f2277c4476..4f652f2512 100644 --- a/src/PhpWord/Element/Chart.php +++ b/src/PhpWord/Element/Chart.php @@ -50,7 +50,7 @@ class Chart extends AbstractElement /** * Chart style. * - * @var \PhpOffice\PhpWord\Style\Chart + * @var ?\PhpOffice\PhpWord\Style\Chart */ private $style; @@ -120,7 +120,7 @@ public function getSeries() /** * Get chart style. * - * @return \PhpOffice\PhpWord\Style\Chart + * @return ?\PhpOffice\PhpWord\Style\Chart */ public function getStyle() { diff --git a/src/PhpWord/Element/Comment.php b/src/PhpWord/Element/Comment.php index 6972f82379..9173c49148 100644 --- a/src/PhpWord/Element/Comment.php +++ b/src/PhpWord/Element/Comment.php @@ -79,15 +79,11 @@ public function getInitials() /** * Sets the element where this comment starts. - * - * @param \PhpOffice\PhpWord\Element\AbstractElement $value */ public function setStartElement(AbstractElement $value): void { $this->startElement = $value; - if ($value->getCommentRangeStart() == null) { - $value->setCommentRangeStart($this); - } + $value->setCommentRangeStart($this); } /** @@ -102,15 +98,11 @@ public function getStartElement() /** * Sets the element where this comment ends. - * - * @param \PhpOffice\PhpWord\Element\AbstractElement $value */ public function setEndElement(AbstractElement $value): void { $this->endElement = $value; - if ($value->getCommentRangeEnd() == null) { - $value->setCommentRangeEnd($this); - } + $value->setCommentRangeEnd($this); } /** diff --git a/src/PhpWord/Element/Field.php b/src/PhpWord/Element/Field.php index a912f3ca22..a828aaa02e 100644 --- a/src/PhpWord/Element/Field.php +++ b/src/PhpWord/Element/Field.php @@ -85,6 +85,16 @@ class Field extends AbstractElement 'properties' => ['StyleIdentifier' => ''], 'options' => ['PreserveFormat'], ], + 'FILENAME' => [ + 'properties' => [ + 'format' => ['Upper', 'Lower', 'FirstCap', 'Caps'], + ], + 'options' => ['Path', 'PreserveFormat'], + ], + 'REF' => [ + 'properties' => ['name' => ''], + 'options' => ['f', 'h', 'n', 'p', 'r', 't', 'w'], + ], ]; /** @@ -268,7 +278,7 @@ public function getOptions() /** * Set Field text. * - * @param string|TextRun $text + * @param null|string|TextRun $text * * @return null|string|TextRun */ diff --git a/src/PhpWord/Element/Footnote.php b/src/PhpWord/Element/Footnote.php index 2c1bd26f97..7a08091abc 100644 --- a/src/PhpWord/Element/Footnote.php +++ b/src/PhpWord/Element/Footnote.php @@ -29,7 +29,7 @@ class Footnote extends AbstractContainer /** * Paragraph style. * - * @var \PhpOffice\PhpWord\Style\Paragraph|string + * @var null|\PhpOffice\PhpWord\Style\Paragraph|string */ protected $paragraphStyle; @@ -54,7 +54,7 @@ public function __construct($paragraphStyle = null) /** * Get paragraph style. * - * @return \PhpOffice\PhpWord\Style\Paragraph|string + * @return null|\PhpOffice\PhpWord\Style\Paragraph|string */ public function getParagraphStyle() { diff --git a/src/PhpWord/Element/FormField.php b/src/PhpWord/Element/FormField.php index 23cded7229..ef8c22fe47 100644 --- a/src/PhpWord/Element/FormField.php +++ b/src/PhpWord/Element/FormField.php @@ -35,7 +35,7 @@ class FormField extends Text /** * Form field name. * - * @var bool|int|string + * @var ?string */ private $name; @@ -53,7 +53,7 @@ class FormField extends Text /** * Value. * - * @var bool|int|string + * @var null|bool|int|string */ private $value; @@ -105,7 +105,7 @@ public function setType($value) /** * Get name. * - * @return string + * @return ?string */ public function getName() { @@ -115,7 +115,7 @@ public function getName() /** * Set name. * - * @param bool|int|string $value + * @param ?string $value * * @return self */ @@ -153,7 +153,7 @@ public function setDefault($value) /** * Get value. * - * @return bool|int|string + * @return null|bool|int|string */ public function getValue() { @@ -163,7 +163,7 @@ public function getValue() /** * Set value. * - * @param bool|int|string $value + * @param null|bool|int|string $value * * @return self */ diff --git a/src/PhpWord/Element/Formula.php b/src/PhpWord/Element/Formula.php new file mode 100644 index 0000000000..ca9f5d6b4b --- /dev/null +++ b/src/PhpWord/Element/Formula.php @@ -0,0 +1,53 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +declare(strict_types=1); + +namespace PhpOffice\PhpWord\Element; + +use PhpOffice\Math\Math; + +/** + * Formula element. + */ +class Formula extends AbstractElement +{ + /** + * @var Math + */ + protected $math; + + /** + * Create a new Formula Element. + */ + public function __construct(Math $math) + { + $this->setMath($math); + } + + public function setMath(Math $math): self + { + $this->math = $math; + + return $this; + } + + public function getMath(): Math + { + return $this->math; + } +} diff --git a/src/PhpWord/Element/Image.php b/src/PhpWord/Element/Image.php index 15c6997d29..12d637d7d2 100644 --- a/src/PhpWord/Element/Image.php +++ b/src/PhpWord/Element/Image.php @@ -54,7 +54,7 @@ class Image extends AbstractElement /** * Image style. * - * @var ImageStyle + * @var ?ImageStyle */ private $style; @@ -89,7 +89,7 @@ class Image extends AbstractElement /** * Image function. * - * @var string + * @var null|callable(resource): void */ private $imageFunc; @@ -100,6 +100,16 @@ class Image extends AbstractElement */ private $imageExtension; + /** + * Image quality. + * + * Functions imagepng() and imagejpeg() have an optional parameter for + * quality. + * + * @var null|int + */ + private $imageQuality; + /** * Is memory image. * @@ -149,7 +159,7 @@ public function __construct($source, $style = null, $watermark = false, $name = /** * Get Image style. * - * @return ImageStyle + * @return ?ImageStyle */ public function getStyle() { @@ -249,13 +259,21 @@ public function getImageCreateFunction() /** * Get image function. * - * @return string + * @return null|callable(resource): void */ - public function getImageFunction() + public function getImageFunction(): ?callable { return $this->imageFunc; } + /** + * Get image quality. + */ + public function getImageQuality(): ?int + { + return $this->imageQuality; + } + /** * Get image extension. * @@ -317,20 +335,13 @@ public function setMediaIndex($value): void } /** - * Get image string data. - * - * @param bool $base64 - * - * @return null|string - * - * @since 0.11.0 + * Get image string. */ - public function getImageStringData($base64 = false) + public function getImageString(): ?string { $source = $this->source; $actualSource = null; $imageBinary = null; - $imageData = null; $isTemp = false; // Get actual source from archive image or other source @@ -367,32 +378,50 @@ public function getImageStringData($base64 = false) imagesavealpha($imageResource, true); } ob_start(); - call_user_func($this->imageFunc, $imageResource); + $callback = $this->imageFunc; + $callback($imageResource); $imageBinary = ob_get_contents(); ob_end_clean(); } elseif ($this->sourceType == self::SOURCE_STRING) { $imageBinary = $this->source; } else { $fileHandle = fopen($actualSource, 'rb', false); - if ($fileHandle !== false) { - $imageBinary = fread($fileHandle, filesize($actualSource)); + $fileSize = filesize($actualSource); + if ($fileHandle !== false && $fileSize > 0) { + $imageBinary = fread($fileHandle, $fileSize); fclose($fileHandle); } } - if ($imageBinary !== null) { - if ($base64) { - $imageData = chunk_split(base64_encode($imageBinary)); - } else { - $imageData = chunk_split(bin2hex($imageBinary)); - } - } // Delete temporary file if necessary if ($isTemp === true) { @unlink($actualSource); } - return $imageData; + return $imageBinary; + } + + /** + * Get image string data. + * + * @param bool $base64 + * + * @return null|string + * + * @since 0.11.0 + */ + public function getImageStringData($base64 = false) + { + $imageBinary = $this->getImageString(); + if ($imageBinary === null) { + return null; + } + + if ($base64) { + return base64_encode($imageBinary); + } + + return bin2hex($imageBinary); } /** @@ -503,31 +532,45 @@ private function setFunctions(): void switch ($this->imageType) { case 'image/png': $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefrompng'; - $this->imageFunc = 'imagepng'; + $this->imageFunc = function ($resource): void { + imagepng($resource, null, $this->imageQuality); + }; $this->imageExtension = 'png'; + $this->imageQuality = -1; break; case 'image/gif': $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefromgif'; - $this->imageFunc = 'imagegif'; + $this->imageFunc = function ($resource): void { + imagegif($resource); + }; $this->imageExtension = 'gif'; + $this->imageQuality = null; break; case 'image/jpeg': case 'image/jpg': $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefromjpeg'; - $this->imageFunc = 'imagejpeg'; + $this->imageFunc = function ($resource): void { + imagejpeg($resource, null, $this->imageQuality); + }; $this->imageExtension = 'jpg'; + $this->imageQuality = 100; break; case 'image/bmp': case 'image/x-ms-bmp': $this->imageType = 'image/bmp'; + $this->imageFunc = null; $this->imageExtension = 'bmp'; + $this->imageQuality = null; break; case 'image/tiff': + $this->imageType = 'image/tiff'; + $this->imageFunc = null; $this->imageExtension = 'tif'; + $this->imageQuality = null; break; } diff --git a/src/PhpWord/Element/Line.php b/src/PhpWord/Element/Line.php index 7659a11483..40ad75c799 100644 --- a/src/PhpWord/Element/Line.php +++ b/src/PhpWord/Element/Line.php @@ -27,7 +27,7 @@ class Line extends AbstractElement /** * Line style. * - * @var \PhpOffice\PhpWord\Style\Line + * @var ?\PhpOffice\PhpWord\Style\Line */ private $style; @@ -44,7 +44,7 @@ public function __construct($style = null) /** * Get line style. * - * @return \PhpOffice\PhpWord\Style\Line + * @return ?\PhpOffice\PhpWord\Style\Line */ public function getStyle() { diff --git a/src/PhpWord/Element/Link.php b/src/PhpWord/Element/Link.php index 0e6ada43c5..b8acba1d3b 100644 --- a/src/PhpWord/Element/Link.php +++ b/src/PhpWord/Element/Link.php @@ -43,14 +43,14 @@ class Link extends AbstractElement /** * Font style. * - * @var \PhpOffice\PhpWord\Style\Font|string + * @var null|\PhpOffice\PhpWord\Style\Font|string */ private $fontStyle; /** * Paragraph style. * - * @var \PhpOffice\PhpWord\Style\Paragraph|string + * @var null|\PhpOffice\PhpWord\Style\Paragraph|string */ private $paragraphStyle; @@ -109,7 +109,7 @@ public function getText() /** * Get Text style. * - * @return \PhpOffice\PhpWord\Style\Font|string + * @return null|\PhpOffice\PhpWord\Style\Font|string */ public function getFontStyle() { @@ -119,7 +119,7 @@ public function getFontStyle() /** * Get Paragraph style. * - * @return \PhpOffice\PhpWord\Style\Paragraph|string + * @return null|\PhpOffice\PhpWord\Style\Paragraph|string */ public function getParagraphStyle() { diff --git a/src/PhpWord/Element/ListItem.php b/src/PhpWord/Element/ListItem.php index 36484d90f9..d3f25c29b7 100644 --- a/src/PhpWord/Element/ListItem.php +++ b/src/PhpWord/Element/ListItem.php @@ -28,7 +28,7 @@ class ListItem extends AbstractElement /** * Element style. * - * @var \PhpOffice\PhpWord\Style\ListItem + * @var ?\PhpOffice\PhpWord\Style\ListItem */ private $style; @@ -71,7 +71,7 @@ public function __construct($text, $depth = 0, $fontStyle = null, $listStyle = n /** * Get style. * - * @return \PhpOffice\PhpWord\Style\ListItem + * @return ?\PhpOffice\PhpWord\Style\ListItem */ public function getStyle() { diff --git a/src/PhpWord/Element/ListItemRun.php b/src/PhpWord/Element/ListItemRun.php index 69274a520c..7a63337093 100644 --- a/src/PhpWord/Element/ListItemRun.php +++ b/src/PhpWord/Element/ListItemRun.php @@ -32,7 +32,7 @@ class ListItemRun extends TextRun /** * ListItem Style. * - * @var \PhpOffice\PhpWord\Style\ListItem + * @var ?\PhpOffice\PhpWord\Style\ListItem */ private $style; @@ -66,7 +66,7 @@ public function __construct($depth = 0, $listStyle = null, $paragraphStyle = nul /** * Get ListItem style. * - * @return \PhpOffice\PhpWord\Style\ListItem + * @return ?\PhpOffice\PhpWord\Style\ListItem */ public function getStyle() { diff --git a/src/PhpWord/Element/OLEObject.php b/src/PhpWord/Element/OLEObject.php index df7396cfe1..73645e07f2 100644 --- a/src/PhpWord/Element/OLEObject.php +++ b/src/PhpWord/Element/OLEObject.php @@ -35,7 +35,7 @@ class OLEObject extends AbstractElement /** * Image Style. * - * @var \PhpOffice\PhpWord\Style\Image + * @var ?\PhpOffice\PhpWord\Style\Image */ private $style; @@ -100,7 +100,7 @@ public function getSource() /** * Get object style. * - * @return \PhpOffice\PhpWord\Style\Image + * @return ?\PhpOffice\PhpWord\Style\Image */ public function getStyle() { diff --git a/src/PhpWord/Element/PreserveText.php b/src/PhpWord/Element/PreserveText.php index 19f468bb41..7370b63743 100644 --- a/src/PhpWord/Element/PreserveText.php +++ b/src/PhpWord/Element/PreserveText.php @@ -29,21 +29,21 @@ class PreserveText extends AbstractElement /** * Text content. * - * @var array|string + * @var null|array|string */ private $text; /** * Text style. * - * @var \PhpOffice\PhpWord\Style\Font|string + * @var null|\PhpOffice\PhpWord\Style\Font|string */ private $fontStyle; /** * Paragraph style. * - * @var \PhpOffice\PhpWord\Style\Paragraph|string + * @var null|\PhpOffice\PhpWord\Style\Paragraph|string */ private $paragraphStyle; @@ -69,7 +69,7 @@ public function __construct($text = null, $fontStyle = null, $paragraphStyle = n /** * Get Text style. * - * @return \PhpOffice\PhpWord\Style\Font|string + * @return null|\PhpOffice\PhpWord\Style\Font|string */ public function getFontStyle() { @@ -79,7 +79,7 @@ public function getFontStyle() /** * Get Paragraph style. * - * @return \PhpOffice\PhpWord\Style\Paragraph|string + * @return null|\PhpOffice\PhpWord\Style\Paragraph|string */ public function getParagraphStyle() { @@ -89,7 +89,7 @@ public function getParagraphStyle() /** * Get Text content. * - * @return array|string + * @return null|array|string */ public function getText() { diff --git a/src/PhpWord/Element/Row.php b/src/PhpWord/Element/Row.php index d2f0ecac89..f97df595f2 100644 --- a/src/PhpWord/Element/Row.php +++ b/src/PhpWord/Element/Row.php @@ -29,14 +29,14 @@ class Row extends AbstractElement /** * Row height. * - * @var int + * @var ?int */ private $height; /** * Row style. * - * @var \PhpOffice\PhpWord\Style\Row + * @var ?\PhpOffice\PhpWord\Style\Row */ private $style; @@ -89,7 +89,7 @@ public function getCells() /** * Get row style. * - * @return \PhpOffice\PhpWord\Style\Row + * @return ?\PhpOffice\PhpWord\Style\Row */ public function getStyle() { @@ -99,7 +99,7 @@ public function getStyle() /** * Get row height. * - * @return int + * @return ?int */ public function getHeight() { diff --git a/src/PhpWord/Element/SDT.php b/src/PhpWord/Element/SDT.php index b7ff31ec21..561cb9dd9e 100644 --- a/src/PhpWord/Element/SDT.php +++ b/src/PhpWord/Element/SDT.php @@ -34,7 +34,7 @@ class SDT extends Text /** * Value. * - * @var bool|int|string + * @var null|bool|int|string */ private $value; @@ -100,7 +100,7 @@ public function setType($value) /** * Get value. * - * @return bool|int|string + * @return null|bool|int|string */ public function getValue() { @@ -110,7 +110,7 @@ public function getValue() /** * Set value. * - * @param bool|int|string $value + * @param null|bool|int|string $value * * @return self */ diff --git a/src/PhpWord/Element/Section.php b/src/PhpWord/Element/Section.php index d77645ff62..dbba7f5ccb 100644 --- a/src/PhpWord/Element/Section.php +++ b/src/PhpWord/Element/Section.php @@ -31,7 +31,7 @@ class Section extends AbstractContainer /** * Section style. * - * @var \PhpOffice\PhpWord\Style\Section + * @var ?\PhpOffice\PhpWord\Style\Section */ private $style; @@ -60,7 +60,7 @@ class Section extends AbstractContainer * Create new instance. * * @param int $sectionCount - * @param null|array|\PhpOffice\PhpWord\Style $style + * @param null|array|\PhpOffice\PhpWord\Style|string $style */ public function __construct($sectionCount, $style = null) { @@ -87,7 +87,7 @@ public function setStyle($style = null): void /** * Get section style. * - * @return \PhpOffice\PhpWord\Style\Section + * @return ?\PhpOffice\PhpWord\Style\Section */ public function getStyle() { @@ -154,8 +154,6 @@ public function getFootnoteProperties() /** * Set the footnote properties. - * - * @param FootnoteProperties $footnoteProperties */ public function setFootnoteProperties(?FootnoteProperties $footnoteProperties = null): void { diff --git a/src/PhpWord/Element/Shape.php b/src/PhpWord/Element/Shape.php index 930ed8a95d..15161f892e 100644 --- a/src/PhpWord/Element/Shape.php +++ b/src/PhpWord/Element/Shape.php @@ -36,7 +36,7 @@ class Shape extends AbstractElement /** * Shape style. * - * @var \PhpOffice\PhpWord\Style\Shape + * @var ?\PhpOffice\PhpWord\Style\Shape */ private $style; @@ -80,7 +80,7 @@ public function setType($value = null) /** * Get shape style. * - * @return \PhpOffice\PhpWord\Style\Shape + * @return ?\PhpOffice\PhpWord\Style\Shape */ public function getStyle() { diff --git a/src/PhpWord/Element/Table.php b/src/PhpWord/Element/Table.php index 308e7bc63e..53a828a9ea 100644 --- a/src/PhpWord/Element/Table.php +++ b/src/PhpWord/Element/Table.php @@ -27,7 +27,7 @@ class Table extends AbstractElement /** * Table style. * - * @var \PhpOffice\PhpWord\Style\Table + * @var ?\PhpOffice\PhpWord\Style\Table */ private $style; @@ -41,7 +41,7 @@ class Table extends AbstractElement /** * Table width. * - * @var int + * @var ?int */ private $width; @@ -102,7 +102,7 @@ public function getRows() /** * Get table style. * - * @return \PhpOffice\PhpWord\Style\Table + * @return null|\PhpOffice\PhpWord\Style\Table|string */ public function getStyle() { @@ -112,7 +112,7 @@ public function getStyle() /** * Get table width. * - * @return int + * @return ?int */ public function getWidth() { diff --git a/src/PhpWord/Element/Text.php b/src/PhpWord/Element/Text.php index 6a11e82f75..9695395324 100644 --- a/src/PhpWord/Element/Text.php +++ b/src/PhpWord/Element/Text.php @@ -29,7 +29,7 @@ class Text extends AbstractElement /** * Text content. * - * @var string + * @var ?string */ protected $text; @@ -147,7 +147,7 @@ public function setText($text) /** * Get Text content. * - * @return string + * @return ?string */ public function getText() { diff --git a/src/PhpWord/Element/TextBox.php b/src/PhpWord/Element/TextBox.php index 7472f4bc34..af37f65772 100644 --- a/src/PhpWord/Element/TextBox.php +++ b/src/PhpWord/Element/TextBox.php @@ -34,7 +34,7 @@ class TextBox extends AbstractContainer /** * TextBox style. * - * @var \PhpOffice\PhpWord\Style\TextBox + * @var ?\PhpOffice\PhpWord\Style\TextBox */ private $style; @@ -51,7 +51,7 @@ public function __construct($style = null) /** * Get textbox style. * - * @return \PhpOffice\PhpWord\Style\TextBox + * @return ?\PhpOffice\PhpWord\Style\TextBox */ public function getStyle() { diff --git a/src/PhpWord/Element/TextBreak.php b/src/PhpWord/Element/TextBreak.php index 088503a40e..c3cd087d74 100644 --- a/src/PhpWord/Element/TextBreak.php +++ b/src/PhpWord/Element/TextBreak.php @@ -28,14 +28,14 @@ class TextBreak extends AbstractElement /** * Paragraph style. * - * @var \PhpOffice\PhpWord\Style\Paragraph|string + * @var null|\PhpOffice\PhpWord\Style\Paragraph|string */ private $paragraphStyle; /** * Text style. * - * @var \PhpOffice\PhpWord\Style\Font|string + * @var null|\PhpOffice\PhpWord\Style\Font|string */ private $fontStyle; @@ -82,7 +82,7 @@ public function setFontStyle($style = null, $paragraphStyle = null) /** * Get Text style. * - * @return \PhpOffice\PhpWord\Style\Font|string + * @return null|\PhpOffice\PhpWord\Style\Font|string */ public function getFontStyle() { @@ -113,7 +113,7 @@ public function setParagraphStyle($style = null) /** * Get Paragraph style. * - * @return \PhpOffice\PhpWord\Style\Paragraph|string + * @return null|\PhpOffice\PhpWord\Style\Paragraph|string */ public function getParagraphStyle() { diff --git a/src/PhpWord/Element/TextRun.php b/src/PhpWord/Element/TextRun.php index fc8727592a..8ddc9cd7ba 100644 --- a/src/PhpWord/Element/TextRun.php +++ b/src/PhpWord/Element/TextRun.php @@ -32,14 +32,14 @@ class TextRun extends AbstractContainer /** * Paragraph style. * - * @var \PhpOffice\PhpWord\Style\Paragraph|string + * @var Paragraph|string */ protected $paragraphStyle; /** * Create new instance. * - * @param array|\PhpOffice\PhpWord\Style\Paragraph|string $paragraphStyle + * @param array|Paragraph|string $paragraphStyle */ public function __construct($paragraphStyle = null) { @@ -49,7 +49,7 @@ public function __construct($paragraphStyle = null) /** * Get Paragraph style. * - * @return \PhpOffice\PhpWord\Style\Paragraph|string + * @return Paragraph|string */ public function getParagraphStyle() { @@ -59,9 +59,9 @@ public function getParagraphStyle() /** * Set Paragraph style. * - * @param array|\PhpOffice\PhpWord\Style\Paragraph|string $style + * @param array|Paragraph|string $style * - * @return \PhpOffice\PhpWord\Style\Paragraph|string + * @return Paragraph|string */ public function setParagraphStyle($style = null) { @@ -78,4 +78,16 @@ public function setParagraphStyle($style = null) return $this->paragraphStyle; } + + public function getText(): string + { + $outstr = ''; + foreach ($this->getElements() as $element) { + if ($element instanceof Text) { + $outstr .= $element->getText(); + } + } + + return $outstr; + } } diff --git a/src/PhpWord/Element/Title.php b/src/PhpWord/Element/Title.php index 947e941696..8fd5b21b1d 100644 --- a/src/PhpWord/Element/Title.php +++ b/src/PhpWord/Element/Title.php @@ -43,7 +43,7 @@ class Title extends AbstractElement /** * Name of the heading style, e.g. 'Heading1'. * - * @var string + * @var ?string */ private $style; @@ -54,13 +54,20 @@ class Title extends AbstractElement */ protected $collectionRelation = true; + /** + * Page number. + * + * @var int + */ + private $pageNumber; + /** * Create a new Title Element. * * @param string|TextRun $text * @param int $depth */ - public function __construct($text, $depth = 1) + public function __construct($text, $depth = 1, ?int $pageNumber = null) { if (is_string($text)) { $this->text = SharedText::toUTF8($text); @@ -75,12 +82,16 @@ public function __construct($text, $depth = 1) if (array_key_exists($styleName, Style::getStyles())) { $this->style = str_replace('_', '', $styleName); } + + if ($pageNumber !== null) { + $this->pageNumber = $pageNumber; + } } /** * Get Title Text content. * - * @return string + * @return string|TextRun */ public function getText() { @@ -100,10 +111,18 @@ public function getDepth() /** * Get Title style. * - * @return string + * @return ?string */ public function getStyle() { return $this->style; } + + /** + * Get page number. + */ + public function getPageNumber(): ?int + { + return $this->pageNumber; + } } diff --git a/src/PhpWord/Escaper/AbstractEscaper.php b/src/PhpWord/Escaper/AbstractEscaper.php index 571d5df92e..28e33818a4 100644 --- a/src/PhpWord/Escaper/AbstractEscaper.php +++ b/src/PhpWord/Escaper/AbstractEscaper.php @@ -25,7 +25,7 @@ abstract class AbstractEscaper implements EscaperInterface { /** - * @param string $input + * @param ?string $input * * @return string */ diff --git a/src/PhpWord/Escaper/Rtf.php b/src/PhpWord/Escaper/Rtf.php index b627bcd11e..842e3adc91 100644 --- a/src/PhpWord/Escaper/Rtf.php +++ b/src/PhpWord/Escaper/Rtf.php @@ -47,7 +47,7 @@ protected function escapeMultibyteCharacter($code) /** * @see http://www.randomchaos.com/documents/?source=php_and_unicode * - * @param string $input + * @param ?string $input */ protected function escapeSingleValue($input) { diff --git a/src/PhpWord/IOFactory.php b/src/PhpWord/IOFactory.php index de2656d8c6..9ceb7026a3 100644 --- a/src/PhpWord/IOFactory.php +++ b/src/PhpWord/IOFactory.php @@ -17,6 +17,8 @@ namespace PhpOffice\PhpWord; +use PhpOffice\PhpWord\Element\Text; +use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\Reader\ReaderInterface; use PhpOffice\PhpWord\Writer\WriterInterface; @@ -89,6 +91,43 @@ public static function load($filename, $readerName = 'Word2007') return $reader->load($filename); } + /** + * Loads PhpWord ${variable} from file. + * + * @param string $filename The name of the file + * + * @return array The extracted variables + */ + public static function extractVariables(string $filename, string $readerName = 'Word2007'): array + { + /** @var \PhpOffice\PhpWord\Reader\ReaderInterface $reader */ + $reader = self::createReader($readerName); + $document = $reader->load($filename); + $extractedVariables = []; + foreach ($document->getSections() as $section) { + $concatenatedText = ''; + foreach ($section->getElements() as $element) { + if ($element instanceof TextRun) { + foreach ($element->getElements() as $textElement) { + if ($textElement instanceof Text) { + $text = $textElement->getText(); + $concatenatedText .= $text; + } + } + } + } + preg_match_all('/\$\{([^}]+)\}/', $concatenatedText, $matches); + if (!empty($matches[1])) { + foreach ($matches[1] as $match) { + $trimmedMatch = trim($match); + $extractedVariables[] = $trimmedMatch; + } + } + } + + return $extractedVariables; + } + /** * Check if it's a concrete class (not abstract nor interface). * diff --git a/src/PhpWord/Media.php b/src/PhpWord/Media.php index 22b0b06b3a..31487a91fe 100644 --- a/src/PhpWord/Media.php +++ b/src/PhpWord/Media.php @@ -41,7 +41,6 @@ class Media * @param string $container section|headerx|footerx|footnote|endnote * @param string $mediaType image|object|link * @param string $source - * @param \PhpOffice\PhpWord\Element\Image $image * * @return int */ @@ -74,8 +73,7 @@ public static function addElement($container, $mediaType, $source, ?Image $image $mediaData['imageType'] = $image->getImageType(); if ($isMemImage) { $mediaData['isMemImage'] = true; - $mediaData['createFunction'] = $image->getImageCreateFunction(); - $mediaData['imageFunction'] = $image->getImageFunction(); + $mediaData['imageString'] = $image->getImageString(); } $target = "{$container}_image{$mediaTypeCount}.{$extension}"; $image->setTarget($target); diff --git a/src/PhpWord/Metadata/DocInfo.php b/src/PhpWord/Metadata/DocInfo.php index bdb2b7d37b..f7229cf0a5 100644 --- a/src/PhpWord/Metadata/DocInfo.php +++ b/src/PhpWord/Metadata/DocInfo.php @@ -441,7 +441,7 @@ public function getCustomPropertyValue($propertyName) * * @param string $propertyName * - * @return string + * @return ?string */ public function getCustomPropertyType($propertyName) { diff --git a/src/PhpWord/Metadata/Settings.php b/src/PhpWord/Metadata/Settings.php index e0e5ae0a66..2de9ef8959 100644 --- a/src/PhpWord/Metadata/Settings.php +++ b/src/PhpWord/Metadata/Settings.php @@ -114,7 +114,7 @@ class Settings /** * Theme Font Languages. * - * @var Language + * @var ?Language */ private $themeFontLang; @@ -149,7 +149,7 @@ class Settings /** * The allowed amount of whitespace before hyphenation is applied. * - * @var null|float + * @var null|float|int */ private $hyphenationZone; @@ -160,6 +160,13 @@ class Settings */ private $doNotHyphenateCaps; + /** + * Enable or disable book-folded printing. + * + * @var bool + */ + private $bookFoldPrinting = false; + /** * @return Protection */ @@ -213,7 +220,7 @@ public function hasHideSpellingErrors() /** * Hide spelling errors. * - * @param bool $hideSpellingErrors + * @param ?bool $hideSpellingErrors */ public function setHideSpellingErrors($hideSpellingErrors): void { @@ -233,7 +240,7 @@ public function hasHideGrammaticalErrors() /** * Hide grammatical errors. * - * @param bool $hideGrammaticalErrors + * @param ?bool $hideGrammaticalErrors */ public function setHideGrammaticalErrors($hideGrammaticalErrors): void { @@ -249,7 +256,7 @@ public function hasEvenAndOddHeaders() } /** - * @param bool $evenAndOddHeaders + * @param ?bool $evenAndOddHeaders */ public function setEvenAndOddHeaders($evenAndOddHeaders): void { @@ -268,8 +275,6 @@ public function getRevisionView() /** * Set the Visibility of Annotation Types. - * - * @param TrackChangesView $trackChangesView */ public function setRevisionView(?TrackChangesView $trackChangesView = null): void { @@ -285,7 +290,7 @@ public function hasTrackRevisions() } /** - * @param bool $trackRevisions + * @param ?bool $trackRevisions */ public function setTrackRevisions($trackRevisions): void { @@ -301,7 +306,7 @@ public function hasDoNotTrackMoves() } /** - * @param bool $doNotTrackMoves + * @param ?bool $doNotTrackMoves */ public function setDoNotTrackMoves($doNotTrackMoves): void { @@ -317,7 +322,7 @@ public function hasDoNotTrackFormatting() } /** - * @param bool $doNotTrackFormatting + * @param ?bool $doNotTrackFormatting */ public function setDoNotTrackFormatting($doNotTrackFormatting): void { @@ -364,22 +369,20 @@ public function setMirrorMargins($mirrorMargins): void /** * Returns the Language. - * - * @return Language */ - public function getThemeFontLang() + public function getThemeFontLang(): ?Language { return $this->themeFontLang; } /** - * sets the Language for this document. - * - * @param Language $themeFontLang + * Sets the Language for this document. */ - public function setThemeFontLang($themeFontLang): void + public function setThemeFontLang(Language $themeFontLang): self { $this->themeFontLang = $themeFontLang; + + return $this; } /** @@ -391,7 +394,7 @@ public function hasUpdateFields() } /** - * @param bool $updateFields + * @param ?bool $updateFields */ public function setUpdateFields($updateFields): void { @@ -451,7 +454,7 @@ public function setConsecutiveHyphenLimit($consecutiveHyphenLimit): void } /** - * @return null|float + * @return null|float|int */ public function getHyphenationZone() { @@ -459,7 +462,7 @@ public function getHyphenationZone() } /** - * @param float $hyphenationZone Measurement unit is twip + * @param null|float|int $hyphenationZone Measurement unit is twip */ public function setHyphenationZone($hyphenationZone): void { @@ -481,4 +484,16 @@ public function setDoNotHyphenateCaps($doNotHyphenateCaps): void { $this->doNotHyphenateCaps = (bool) $doNotHyphenateCaps; } + + public function hasBookFoldPrinting(): bool + { + return $this->bookFoldPrinting; + } + + public function setBookFoldPrinting(bool $bookFoldPrinting): self + { + $this->bookFoldPrinting = $bookFoldPrinting; + + return $this; + } } diff --git a/src/PhpWord/PhpWord.php b/src/PhpWord/PhpWord.php index 70ee652b29..cf6f16ae02 100644 --- a/src/PhpWord/PhpWord.php +++ b/src/PhpWord/PhpWord.php @@ -77,6 +77,7 @@ public function __construct() // Reset Media and styles Media::resetElements(); Style::resetStyles(); + Settings::setDefaultRtl(null); // Collection $collections = ['Bookmarks', 'Titles', 'Footnotes', 'Endnotes', 'Charts', 'Comments']; @@ -133,7 +134,6 @@ public function __call($function, $args) if (in_array($function, $addCollection)) { $key = ucfirst(str_replace('add', '', $function) . 's'); - /** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collectionObject */ $collectionObject = $this->collections[$key]; return $collectionObject->addItem($args[0] ?? null); @@ -211,7 +211,7 @@ public function getSection($index) /** * Create new section. * - * @param array $style + * @param null|array|string $style * * @return \PhpOffice\PhpWord\Element\Section */ @@ -325,4 +325,52 @@ public function save($filename, $format = 'Word2007', $download = false) return true; } + + /** + * Create new section. + * + * @deprecated 0.10.0 + * + * @param array $settings + * + * @return \PhpOffice\PhpWord\Element\Section + * + * @codeCoverageIgnore + */ + public function createSection($settings = null) + { + return $this->addSection($settings); + } + + /** + * Get document properties object. + * + * @deprecated 0.12.0 + * + * @return \PhpOffice\PhpWord\Metadata\DocInfo + * + * @codeCoverageIgnore + */ + public function getDocumentProperties() + { + return $this->getDocInfo(); + } + + /** + * Set document properties object. + * + * @deprecated 0.12.0 + * + * @param \PhpOffice\PhpWord\Metadata\DocInfo $documentProperties + * + * @return self + * + * @codeCoverageIgnore + */ + public function setDocumentProperties($documentProperties) + { + $this->metadata['Document'] = $documentProperties; + + return $this; + } } diff --git a/src/PhpWord/Reader/AbstractReader.php b/src/PhpWord/Reader/AbstractReader.php index ff768e3306..da5e6f6e19 100644 --- a/src/PhpWord/Reader/AbstractReader.php +++ b/src/PhpWord/Reader/AbstractReader.php @@ -42,6 +42,13 @@ abstract class AbstractReader implements ReaderInterface */ protected $fileHandle; + /** + * Load images. + * + * @var bool + */ + protected $imageLoading = true; + /** * Read data only? * @@ -67,6 +74,18 @@ public function setReadDataOnly($value = true) return $this; } + public function hasImageLoading(): bool + { + return $this->imageLoading; + } + + public function setImageLoading(bool $value): self + { + $this->imageLoading = $value; + + return $this; + } + /** * Open file for reading. * diff --git a/src/PhpWord/Reader/MsDoc.php b/src/PhpWord/Reader/MsDoc.php index ac084818a6..72b9af6c59 100644 --- a/src/PhpWord/Reader/MsDoc.php +++ b/src/PhpWord/Reader/MsDoc.php @@ -39,29 +39,21 @@ class MsDoc extends AbstractReader implements ReaderInterface /** * WordDocument Stream. - * - * @var */ private $dataWorkDocument; /** * 1Table Stream. - * - * @var */ private $data1Table; /** * Data Stream. - * - * @var */ private $dataData; /** * Object Pool Stream. - * - * @var */ private $dataObjectPool; @@ -90,6 +82,12 @@ class MsDoc extends AbstractReader implements ReaderInterface */ private $arraySections = []; + /** @var string */ + private $summaryInformation; + + /** @var string */ + private $documentSummaryInformation; + const VERSION_97 = '97'; const VERSION_2000 = '2000'; const VERSION_2002 = '2002'; @@ -158,9 +156,9 @@ private function loadOLE($filename): void // Get Data stream $this->dataObjectPool = $ole->getStream($ole->wrkObjectPool); // Get Summary Information data - $this->_SummaryInformation = $ole->getStream($ole->summaryInformation); + $this->summaryInformation = $ole->getStream($ole->summaryInformation); // Get Document Summary Information data - $this->_DocumentSummaryInformation = $ole->getStream($ole->docSummaryInfos); + $this->documentSummaryInformation = $ole->getStream($ole->docSummaryInfos); } private function getNumInLcb($lcb, $iSize) @@ -1139,7 +1137,7 @@ private function readFibContent(): void /** * Section and information about them. * - * @see : http://msdn.microsoft.com/en-us/library/dd924458%28v=office.12%29.aspx + * @see http://msdn.microsoft.com/en-us/library/dd924458%28v=office.12%29.aspx */ private function readRecordPlcfSed(): void { @@ -1153,7 +1151,7 @@ private function readRecordPlcfSed(): void $posMem += 4; // PlcfSed : aSed - //@see : http://msdn.microsoft.com/en-us/library/dd950194%28v=office.12%29.aspx + //@see http://msdn.microsoft.com/en-us/library/dd950194%28v=office.12%29.aspx $numSed = $this->getNumInLcb($this->arrayFib['lcbPlcfSed'], 12); $aSed = []; @@ -1185,7 +1183,7 @@ private function readRecordPlcfSed(): void /** * Specifies the fonts that are used in the document. * - * @see : http://msdn.microsoft.com/en-us/library/dd943880%28v=office.12%29.aspx + * @see http://msdn.microsoft.com/en-us/library/dd943880%28v=office.12%29.aspx */ private function readRecordSttbfFfn(): void { @@ -1281,10 +1279,12 @@ private function readRecordPlcfBtePapx(): void break; } $strLen = $arrayRGFC[$key + 1] - $arrayRGFC[$key] - 1; - for ($inc = 0; $inc < $strLen; ++$inc) { - $byte = self::getInt1d($this->dataWorkDocument, $arrayRGFC[$key] + $inc); + for ($inc = 0; $inc < ($strLen * 2); ++$inc) { + $byte = self::getInt2d($this->dataWorkDocument, $arrayRGFC[$key] + ($inc * 2)); if ($byte > 0) { - $string .= chr($byte); + $string .= mb_chr($byte, 'UTF-8'); + } else { + break; } } } @@ -1476,7 +1476,7 @@ private function readRecordPlcfBteChpx(): void $offset = $offsetBase; // ChpxFkp - // @see : http://msdn.microsoft.com/en-us/library/dd910989%28v=office.12%29.aspx + // @see http://msdn.microsoft.com/en-us/library/dd910989%28v=office.12%29.aspx $numRGFC = self::getInt1d($this->dataWorkDocument, $offset + 511); $arrayRGFC = []; for ($inc = 0; $inc <= $numRGFC; ++$inc) { @@ -1499,7 +1499,7 @@ private function readRecordPlcfBteChpx(): void if ($rgb > 0) { // Chp Structure - // @see : http://msdn.microsoft.com/en-us/library/dd772849%28v=office.12%29.aspx + // @see http://msdn.microsoft.com/en-us/library/dd772849%28v=office.12%29.aspx $posRGB = $offsetBase + $rgb * 2; $cb = self::getInt1d($this->dataWorkDocument, $posRGB); @@ -1513,8 +1513,6 @@ private function readRecordPlcfBteChpx(): void } /** - * @param $sprm - * * @return stdClass */ private function readSprm($sprm) @@ -1875,7 +1873,7 @@ private function readPrl($data, $pos, $cbNum) break; // sprmCHps case 0x43: - $oStylePrl->styleFont['size'] = dechex($operand / 2); + $oStylePrl->styleFont['size'] = $operand / 2; break; // sprmCIss @@ -1936,12 +1934,13 @@ private function readPrl($data, $pos, $cbNum) // $operand = self::getInt2d($data, $pos); $pos += 2; $cbNum -= 2; + // $ipat = ($operand >> 0) && bindec('111111'); // $icoBack = ($operand >> 6) && bindec('11111'); // $icoFore = ($operand >> 11) && bindec('11111'); break; // sprmCCv - //@see : http://msdn.microsoft.com/en-us/library/dd952824%28v=office.12%29.aspx + //@see http://msdn.microsoft.com/en-us/library/dd952824%28v=office.12%29.aspx case 0x70: $red = str_pad(dechex(self::getInt1d($this->dataWorkDocument, $pos)), 2, '0', STR_PAD_LEFT); ++$pos; @@ -2052,7 +2051,7 @@ private function readPrl($data, $pos, $cbNum) // HFD > clsid $sprmCPicLocation += 16; // HFD > hyperlink - //@see : http://msdn.microsoft.com/en-us/library/dd909835%28v=office.12%29.aspx + //@see http://msdn.microsoft.com/en-us/library/dd909835%28v=office.12%29.aspx $streamVersion = self::getInt4d($this->dataData, $sprmCPicLocation); $sprmCPicLocation += 4; $data = self::getInt4d($this->dataData, $sprmCPicLocation); @@ -2120,8 +2119,8 @@ private function readPrl($data, $pos, $cbNum) }*/ } else { // Pictures - //@see : http://msdn.microsoft.com/en-us/library/dd925458%28v=office.12%29.aspx - //@see : http://msdn.microsoft.com/en-us/library/dd926136%28v=office.12%29.aspx + //@see http://msdn.microsoft.com/en-us/library/dd925458%28v=office.12%29.aspx + //@see http://msdn.microsoft.com/en-us/library/dd926136%28v=office.12%29.aspx // PICF : lcb $sprmCPicLocation += 4; // PICF : cbHeader @@ -2208,13 +2207,13 @@ private function readPrl($data, $pos, $cbNum) $sprmCPicLocation += $shapeRH['recLen']; } // picture : rgfb - //@see : http://msdn.microsoft.com/en-us/library/dd950560%28v=office.12%29.aspx + //@see http://msdn.microsoft.com/en-us/library/dd950560%28v=office.12%29.aspx $fileBlockRH = $this->loadRecordHeader($this->dataData, $sprmCPicLocation); while ($fileBlockRH['recType'] == 0xF007 || ($fileBlockRH['recType'] >= 0xF018 && $fileBlockRH['recType'] <= 0xF117)) { $sprmCPicLocation += 8; switch ($fileBlockRH['recType']) { // OfficeArtFBSE - //@see : http://msdn.microsoft.com/en-us/library/dd944923%28v=office.12%29.aspx + //@see http://msdn.microsoft.com/en-us/library/dd944923%28v=office.12%29.aspx case 0xF007: // btWin32 ++$sprmCPicLocation; @@ -2249,7 +2248,7 @@ private function readPrl($data, $pos, $cbNum) } } // embeddedBlip - //@see : http://msdn.microsoft.com/en-us/library/dd910081%28v=office.12%29.aspx + //@see http://msdn.microsoft.com/en-us/library/dd910081%28v=office.12%29.aspx $embeddedBlipRH = $this->loadRecordHeader($this->dataData, $sprmCPicLocation); switch ($embeddedBlipRH['recType']) { case self::OFFICEARTBLIPJPG: @@ -2334,7 +2333,7 @@ private function generatePhpWord(): void foreach ($this->arrayParagraphs as $itmParagraph) { $textPara = $itmParagraph; foreach ($this->arrayCharacters as $oCharacters) { - $subText = substr($textPara, $oCharacters->pos_start, $oCharacters->pos_len); + $subText = mb_substr($textPara, $oCharacters->pos_start, $oCharacters->pos_len); $subText = str_replace(chr(13), PHP_EOL, $subText); $arrayText = explode(PHP_EOL, $subText); if (end($arrayText) == '') { diff --git a/src/PhpWord/Reader/ODText.php b/src/PhpWord/Reader/ODText.php index aba280db01..d7f8344443 100644 --- a/src/PhpWord/Reader/ODText.php +++ b/src/PhpWord/Reader/ODText.php @@ -53,13 +53,8 @@ public function load($docFile) /** * Read document part. - * - * @param array $relationships - * @param string $partName - * @param string $docFile - * @param string $xmlFile */ - private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, $xmlFile): void + private function readPart(PhpWord $phpWord, array $relationships, string $partName, string $docFile, string $xmlFile): void { $partClass = "PhpOffice\\PhpWord\\Reader\\ODText\\{$partName}"; if (class_exists($partClass)) { @@ -72,12 +67,8 @@ private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, /** * Read all relationship files. - * - * @param string $docFile - * - * @return array */ - private function readRelationships($docFile) + private function readRelationships(string $docFile): array { $rels = []; $xmlFile = 'META-INF/manifest.xml'; diff --git a/src/PhpWord/Reader/ODText/Content.php b/src/PhpWord/Reader/ODText/Content.php index ccbc5eec96..15c76c27b9 100644 --- a/src/PhpWord/Reader/ODText/Content.php +++ b/src/PhpWord/Reader/ODText/Content.php @@ -18,6 +18,10 @@ namespace PhpOffice\PhpWord\Reader\ODText; use DateTime; +use DOMElement; +use DOMNodeList; +use PhpOffice\Math\Reader\MathML; +use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Shared\XMLReader; @@ -29,6 +33,9 @@ */ class Content extends AbstractPart { + /** @var ?Section */ + private $section; + /** * Read content.xml. */ @@ -40,48 +47,112 @@ public function read(PhpWord $phpWord): void $trackedChanges = []; $nodes = $xmlReader->getElements('office:body/office:text/*'); + $this->section = null; + $this->processNodes($nodes, $xmlReader, $phpWord); + $this->section = null; + } + + /** @param DOMNodeList<DOMElement> $nodes */ + public function processNodes(DOMNodeList $nodes, XMLReader $xmlReader, PhpWord $phpWord): void + { if ($nodes->length > 0) { - $section = $phpWord->addSection(); foreach ($nodes as $node) { // $styleName = $xmlReader->getAttribute('text:style-name', $node); switch ($node->nodeName) { case 'text:h': // Heading $depth = $xmlReader->getAttribute('text:outline-level', $node); - $section->addTitle($node->nodeValue, $depth); + $this->getSection($phpWord)->addTitle($node->nodeValue, $depth); break; case 'text:p': // Paragraph - $children = $node->childNodes; - foreach ($children as $child) { - switch ($child->nodeName) { - case 'text:change-start': - $changeId = $child->getAttribute('text:change-id'); - if (isset($trackedChanges[$changeId])) { - $changed = $trackedChanges[$changeId]; - } + $styleName = $xmlReader->getAttribute('text:style-name', $node); + if (substr($styleName, 0, 2) === 'SB') { + break; + } + $element = $xmlReader->getElement('draw:frame/draw:object', $node); + if ($element) { + $mathFile = str_replace('./', '', $element->getAttribute('xlink:href')) . '/content.xml'; - break; - case 'text:change-end': - unset($changed); + $xmlReaderObject = new XMLReader(); + $mathElement = $xmlReaderObject->getDomFromZip($this->docFile, $mathFile); + if ($mathElement) { + $mathXML = $mathElement->saveXML($mathElement); - break; - case 'text:change': - $changeId = $child->getAttribute('text:change-id'); - if (isset($trackedChanges[$changeId])) { - $changed = $trackedChanges[$changeId]; - } + if (is_string($mathXML)) { + $reader = new MathML(); + $math = $reader->read($mathXML); - break; + $this->getSection($phpWord)->addFormula($math); + } + } + } else { + $children = $node->childNodes; + $spans = false; + /** @var DOMElement $child */ + foreach ($children as $child) { + switch ($child->nodeName) { + case 'text:change-start': + $changeId = $child->getAttribute('text:change-id'); + if (isset($trackedChanges[$changeId])) { + $changed = $trackedChanges[$changeId]; + } + + break; + case 'text:change-end': + unset($changed); + + break; + case 'text:change': + $changeId = $child->getAttribute('text:change-id'); + if (isset($trackedChanges[$changeId])) { + $changed = $trackedChanges[$changeId]; + } + + break; + case 'text:span': + $spans = true; + + break; + } } - } - $element = $section->addText($node->nodeValue); - if (isset($changed) && is_array($changed)) { - $element->setTrackChange($changed['changed']); - if (isset($changed['textNodes'])) { - foreach ($changed['textNodes'] as $changedNode) { - $element = $section->addText($changedNode->nodeValue); - $element->setTrackChange($changed['changed']); + if ($spans) { + $element = $this->getSection($phpWord)->addTextRun(); + foreach ($children as $child) { + switch ($child->nodeName) { + case 'text:span': + /** @var DOMElement $child2 */ + foreach ($child->childNodes as $child2) { + switch ($child2->nodeName) { + case '#text': + $element->addText($child2->nodeValue); + + break; + case 'text:tab': + $element->addText("\t"); + + break; + case 'text:s': + $spaces = (int) $child2->getAttribute('text:c') ?: 1; + $element->addText(str_repeat(' ', $spaces)); + + break; + } + } + + break; + } + } + } else { + $element = $this->getSection($phpWord)->addText($node->nodeValue); + } + if (isset($changed) && is_array($changed)) { + $element->setTrackChange($changed['changed']); + if (isset($changed['textNodes'])) { + foreach ($changed['textNodes'] as $changedNode) { + $element = $this->getSection($phpWord)->addText($changedNode->nodeValue); + $element->setTrackChange($changed['changed']); + } } } } @@ -91,7 +162,7 @@ public function read(PhpWord $phpWord): void $listItems = $xmlReader->getElements('text:list-item/text:p', $node); foreach ($listItems as $listItem) { // $listStyleName = $xmlReader->getAttribute('text:style-name', $listItem); - $section->addListItem($listItem->nodeValue, 0); + $this->getSection($phpWord)->addListItem($listItem->nodeValue, 0); } break; @@ -110,9 +181,26 @@ public function read(PhpWord $phpWord): void $trackedChanges[$changedRegion->getAttribute('text:id')] = ['changed' => $changed, 'textNodes' => $textNodes]; } + break; + case 'text:section': // Section + // $sectionStyleName = $xmlReader->getAttribute('text:style-name', $listItem); + $this->section = $phpWord->addSection(); + $children = $node->childNodes; + $this->processNodes($children, $xmlReader, $phpWord); + break; } } } } + + private function getSection(PhpWord $phpWord): Section + { + $section = $this->section; + if ($section === null) { + $section = $this->section = $phpWord->addSection(); + } + + return $section; + } } diff --git a/src/PhpWord/Reader/Word2007.php b/src/PhpWord/Reader/Word2007.php index 5f2a69c721..bb20a8fed2 100644 --- a/src/PhpWord/Reader/Word2007.php +++ b/src/PhpWord/Reader/Word2007.php @@ -17,7 +17,10 @@ namespace PhpOffice\PhpWord\Reader; +use Exception; +use PhpOffice\PhpWord\Element\AbstractElement; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Reader\Word2007\AbstractPart; use PhpOffice\PhpWord\Shared\XMLReader; use PhpOffice\PhpWord\Shared\ZipArchive; @@ -42,23 +45,34 @@ public function load($docFile) { $phpWord = new PhpWord(); $relationships = $this->readRelationships($docFile); + $commentRefs = []; $steps = [ - ['stepPart' => 'document', 'stepItems' => [ - 'styles' => 'Styles', - 'numbering' => 'Numbering', - ]], - ['stepPart' => 'main', 'stepItems' => [ - 'officeDocument' => 'Document', - 'core-properties' => 'DocPropsCore', - 'extended-properties' => 'DocPropsApp', - 'custom-properties' => 'DocPropsCustom', - ]], - ['stepPart' => 'document', 'stepItems' => [ - 'endnotes' => 'Endnotes', - 'footnotes' => 'Footnotes', - 'settings' => 'Settings', - ]], + [ + 'stepPart' => 'document', + 'stepItems' => [ + 'styles' => 'Styles', + 'numbering' => 'Numbering', + ], + ], + [ + 'stepPart' => 'main', + 'stepItems' => [ + 'officeDocument' => 'Document', + 'core-properties' => 'DocPropsCore', + 'extended-properties' => 'DocPropsApp', + 'custom-properties' => 'DocPropsCustom', + ], + ], + [ + 'stepPart' => 'document', + 'stepItems' => [ + 'endnotes' => 'Endnotes', + 'footnotes' => 'Footnotes', + 'settings' => 'Settings', + 'comments' => 'Comments', + ], + ], ]; foreach ($steps as $step) { @@ -72,7 +86,8 @@ public function load($docFile) if (isset($stepItems[$relType])) { $partName = $stepItems[$relType]; $xmlFile = $relItem['target']; - $this->readPart($phpWord, $relationships, $partName, $docFile, $xmlFile); + $part = $this->readPart($phpWord, $relationships, $commentRefs, $partName, $docFile, $xmlFile); + $commentRefs = $part->getCommentReferences(); } } } @@ -83,20 +98,23 @@ public function load($docFile) /** * Read document part. * - * @param array $relationships - * @param string $partName - * @param string $docFile - * @param string $xmlFile + * @param array<string, array<string, null|AbstractElement>> $commentRefs */ - private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, $xmlFile): void + private function readPart(PhpWord $phpWord, array $relationships, array $commentRefs, string $partName, string $docFile, string $xmlFile): AbstractPart { $partClass = "PhpOffice\\PhpWord\\Reader\\Word2007\\{$partName}"; - if (class_exists($partClass)) { - /** @var \PhpOffice\PhpWord\Reader\Word2007\AbstractPart $part Type hint */ - $part = new $partClass($docFile, $xmlFile); - $part->setRels($relationships); - $part->read($phpWord); + if (!class_exists($partClass)) { + throw new Exception(sprintf('The part "%s" doesn\'t exist', $partClass)); } + + /** @var AbstractPart $part Type hint */ + $part = new $partClass($docFile, $xmlFile); + $part->setImageLoading($this->hasImageLoading()); + $part->setRels($relationships); + $part->setCommentReferences($commentRefs); + $part->read($phpWord); + + return $part; } /** diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 7d4209953b..98a74772cd 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -19,8 +19,12 @@ use DateTime; use DOMElement; +use InvalidArgumentException; +use PhpOffice\Math\Reader\OfficeMathML; use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType; use PhpOffice\PhpWord\Element\AbstractContainer; +use PhpOffice\PhpWord\Element\AbstractElement; +use PhpOffice\PhpWord\Element\FormField; use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\PhpWord; @@ -67,6 +71,20 @@ abstract class AbstractPart */ protected $rels = []; + /** + * Comment references. + * + * @var array<string, array<string, AbstractElement>> + */ + protected $commentRefs = []; + + /** + * Image Loading. + * + * @var bool + */ + protected $imageLoading = true; + /** * Read part. */ @@ -94,6 +112,74 @@ public function setRels($value): void $this->rels = $value; } + public function setImageLoading(bool $value): self + { + $this->imageLoading = $value; + + return $this; + } + + public function hasImageLoading(): bool + { + return $this->imageLoading; + } + + /** + * Get comment references. + * + * @return array<string, array<string, null|AbstractElement>> + */ + public function getCommentReferences(): array + { + return $this->commentRefs; + } + + /** + * Set comment references. + * + * @param array<string, array<string, null|AbstractElement>> $commentRefs + */ + public function setCommentReferences(array $commentRefs): self + { + $this->commentRefs = $commentRefs; + + return $this; + } + + /** + * Set comment reference. + */ + private function setCommentReference(string $type, string $id, AbstractElement $element): self + { + if (!in_array($type, ['start', 'end'])) { + throw new InvalidArgumentException('Type must be "start" or "end"'); + } + + if (!array_key_exists($id, $this->commentRefs)) { + $this->commentRefs[$id] = [ + 'start' => null, + 'end' => null, + ]; + } + $this->commentRefs[$id][$type] = $element; + + return $this; + } + + /** + * Get comment reference. + * + * @return array<string, null|AbstractElement> + */ + protected function getCommentReference(string $id): array + { + if (!array_key_exists($id, $this->commentRefs)) { + throw new InvalidArgumentException(sprintf('Comment with id %s isn\'t referenced in document', $id)); + } + + return $this->commentRefs[$id]; + } + /** * Read w:p. * @@ -105,40 +191,93 @@ public function setRels($value): void protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart = 'document'): void { // Paragraph style - $paragraphStyle = null; - $headingDepth = null; - if ($xmlReader->elementExists('w:pPr', $domNode)) { - $paragraphStyle = $this->readParagraphStyle($xmlReader, $domNode); - $headingDepth = $this->getHeadingDepth($paragraphStyle); - } + $paragraphStyle = $xmlReader->elementExists('w:pPr', $domNode) ? $this->readParagraphStyle($xmlReader, $domNode) : null; - // PreserveText - if ($xmlReader->elementExists('w:r/w:instrText', $domNode)) { + if ($xmlReader->elementExists('w:r/w:fldChar/w:ffData', $domNode)) { + // FormField + $partOfFormField = false; + $formNodes = []; + $formType = null; + $textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag', $domNode); + if ($textRunContainers > 0) { + $nodes = $xmlReader->getElements('*', $domNode); + $paragraph = $parent->addTextRun($paragraphStyle); + foreach ($nodes as $node) { + if ($xmlReader->elementExists('w:fldChar/w:ffData', $node)) { + $partOfFormField = true; + $formNodes[] = $node; + if ($xmlReader->elementExists('w:fldChar/w:ffData/w:ddList', $node)) { + $formType = 'dropdown'; + } elseif ($xmlReader->elementExists('w:fldChar/w:ffData/w:textInput', $node)) { + $formType = 'textinput'; + } elseif ($xmlReader->elementExists('w:fldChar/w:ffData/w:checkBox', $node)) { + $formType = 'checkbox'; + } + } elseif ($partOfFormField && + $xmlReader->elementExists('w:fldChar', $node) && + 'end' == $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar') + ) { + $formNodes[] = $node; + $partOfFormField = false; + // Process the form fields + $this->readFormField($xmlReader, $formNodes, $paragraph, $paragraphStyle, $formType); + } elseif ($partOfFormField) { + $formNodes[] = $node; + } else { + // normal runs + $this->readRun($xmlReader, $node, $paragraph, $docPart, $paragraphStyle); + } + } + } + } elseif ($xmlReader->elementExists('w:r/w:instrText', $domNode)) { + // PreserveText $ignoreText = false; $textContent = ''; $fontStyle = $this->readFontStyle($xmlReader, $domNode); $nodes = $xmlReader->getElements('w:r', $domNode); foreach ($nodes as $node) { - $instrText = $xmlReader->getValue('w:instrText', $node); - if ($xmlReader->elementExists('w:fldChar', $node)) { - $fldCharType = $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar'); - if ('begin' == $fldCharType) { - $ignoreText = true; - } elseif ('end' == $fldCharType) { - $ignoreText = false; - } + if ($xmlReader->elementExists('w:lastRenderedPageBreak', $node)) { + $parent->addPageBreak(); } + $instrText = $xmlReader->getValue('w:instrText', $node); if (null !== $instrText) { $textContent .= '{' . $instrText . '}'; } else { + if ($xmlReader->elementExists('w:fldChar', $node)) { + $fldCharType = $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar'); + if ('begin' == $fldCharType) { + $ignoreText = true; + } elseif ('end' == $fldCharType) { + $ignoreText = false; + } + } if (false === $ignoreText) { $textContent .= $xmlReader->getValue('w:t', $node); } } } $parent->addPreserveText(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8'), $fontStyle, $paragraphStyle); - } elseif ($xmlReader->elementExists('w:pPr/w:numPr', $domNode)) { - // List item + + return; + } + + // Formula + $xmlReader->registerNamespace('m', 'http://schemas.openxmlformats.org/officeDocument/2006/math'); + if ($xmlReader->elementExists('m:oMath', $domNode)) { + $mathElement = $xmlReader->getElement('m:oMath', $domNode); + $mathXML = $mathElement->ownerDocument->saveXML($mathElement); + if (is_string($mathXML)) { + $reader = new OfficeMathML(); + $math = $reader->read($mathXML); + + $parent->addFormula($math); + } + + return; + } + + // List item + if ($xmlReader->elementExists('w:pPr/w:numPr', $domNode)) { $numId = $xmlReader->getAttribute('w:val', $domNode, 'w:pPr/w:numPr/w:numId'); $levelId = $xmlReader->getAttribute('w:val', $domNode, 'w:pPr/w:numPr/w:ilvl'); $nodes = $xmlReader->getElements('*', $domNode); @@ -148,10 +287,15 @@ protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $par foreach ($nodes as $node) { $this->readRun($xmlReader, $node, $listItemRun, $docPart, $paragraphStyle); } - } elseif ($headingDepth !== null) { - // Heading or Title + + return; + } + + // Heading or Title + $headingDepth = $xmlReader->elementExists('w:pPr', $domNode) ? $this->getHeadingDepth($paragraphStyle) : null; + if ($headingDepth !== null) { $textContent = null; - $nodes = $xmlReader->getElements('w:r', $domNode); + $nodes = $xmlReader->getElements('w:r|w:hyperlink', $domNode); if ($nodes->length === 1) { $textContent = htmlspecialchars($xmlReader->getValue('w:t', $nodes->item(0)), ENT_QUOTES, 'UTF-8'); } else { @@ -161,26 +305,129 @@ protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $par } } $parent->addTitle($textContent, $headingDepth); + + return; + } + + // Text and TextRun + $textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag|w:commentReference|w:commentRangeStart|w:commentRangeEnd', $domNode); + if (0 === $textRunContainers) { + $parent->addTextBreak(1, $paragraphStyle); } else { - // Text and TextRun - $textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag', $domNode); - if (0 === $textRunContainers) { - $parent->addTextBreak(null, $paragraphStyle); - } else { - $nodes = $xmlReader->getElements('*', $domNode); - $paragraph = $parent->addTextRun($paragraphStyle); - foreach ($nodes as $node) { - $this->readRun($xmlReader, $node, $paragraph, $docPart, $paragraphStyle); + $nodes = $xmlReader->getElements('*', $domNode); + $paragraph = $parent->addTextRun($paragraphStyle); + foreach ($nodes as $node) { + $this->readRun($xmlReader, $node, $paragraph, $docPart, $paragraphStyle); + } + } + } + + /** + * @param DOMElement[] $domNodes + * @param AbstractContainer $parent + * @param mixed $paragraphStyle + * @param string $formType + */ + private function readFormField(XMLReader $xmlReader, array $domNodes, $parent, $paragraphStyle, $formType): void + { + if (!in_array($formType, ['textinput', 'checkbox', 'dropdown'])) { + return; + } + + $formField = $parent->addFormField($formType, null, $paragraphStyle); + $ffData = $xmlReader->getElement('w:fldChar/w:ffData', $domNodes[0]); + + foreach ($xmlReader->getElements('*', $ffData) as $node) { + /** @var DOMElement $node */ + switch ($node->localName) { + case 'name': + $formField->setName($node->getAttribute('w:val')); + + break; + case 'ddList': + $listEntries = []; + foreach ($xmlReader->getElements('*', $node) as $ddListNode) { + switch ($ddListNode->localName) { + case 'result': + $formField->setValue($xmlReader->getAttribute('w:val', $ddListNode)); + + break; + case 'default': + $formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode)); + + break; + case 'listEntry': + $listEntries[] = $xmlReader->getAttribute('w:val', $ddListNode); + + break; + } + } + $formField->setEntries($listEntries); + if (null !== $formField->getValue()) { + $formField->setText($listEntries[$formField->getValue()]); + } + + break; + case 'textInput': + foreach ($xmlReader->getElements('*', $node) as $ddListNode) { + switch ($ddListNode->localName) { + case 'default': + $formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode)); + + break; + case 'format': + case 'maxLength': + break; + } + } + + break; + case 'checkBox': + foreach ($xmlReader->getElements('*', $node) as $ddListNode) { + switch ($ddListNode->localName) { + case 'default': + $formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode)); + + break; + case 'checked': + $formField->setValue($xmlReader->getAttribute('w:val', $ddListNode)); + + break; + case 'size': + case 'sizeAuto': + break; + } + } + + break; + } + } + + if ('textinput' == $formType) { + $ignoreText = true; + $textContent = ''; + foreach ($domNodes as $node) { + if ($xmlReader->elementExists('w:fldChar', $node)) { + $fldCharType = $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar'); + if ('separate' == $fldCharType) { + $ignoreText = false; + } elseif ('end' == $fldCharType) { + $ignoreText = true; + } + } + + if (false === $ignoreText) { + $textContent .= $xmlReader->getValue('w:t', $node); } } + $formField->setValue(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8')); + $formField->setText(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8')); } } /** * Returns the depth of the Heading, returns 0 for a Title. * - * @param array $paragraphStyle - * * @return null|number */ private function getHeadingDepth(?array $paragraphStyle = null) @@ -211,7 +458,7 @@ private function getHeadingDepth(?array $paragraphStyle = null) */ protected function readRun(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart, $paragraphStyle = null): void { - if (in_array($domNode->nodeName, ['w:ins', 'w:del', 'w:smartTag', 'w:hyperlink'])) { + if (in_array($domNode->nodeName, ['w:ins', 'w:del', 'w:smartTag', 'w:hyperlink', 'w:commentReference'])) { $nodes = $xmlReader->getElements('*', $domNode); foreach ($nodes as $node) { $this->readRun($xmlReader, $node, $parent, $docPart, $paragraphStyle); @@ -223,6 +470,17 @@ protected function readRun(XMLReader $xmlReader, DOMElement $domNode, $parent, $ $this->readRunChild($xmlReader, $node, $parent, $docPart, $paragraphStyle, $fontStyle); } } + + if ($xmlReader->elementExists('.//*["commentReference"=local-name()]', $domNode)) { + $node = iterator_to_array($xmlReader->getElements('.//*["commentReference"=local-name()]', $domNode))[0]; + $attributeIdentifier = $node->attributes->getNamedItem('id'); + if ($attributeIdentifier) { + $id = $attributeIdentifier->nodeValue; + + $this->setCommentReference('start', $id, $parent->getElement($parent->countElements() - 1)); + $this->setCommentReference('end', $id, $parent->getElement($parent->countElements() - 1)); + } + } } /** @@ -249,7 +507,7 @@ protected function readRunChild(XMLReader $xmlReader, DOMElement $node, Abstract // Image $rId = $xmlReader->getAttribute('r:id', $node, 'v:shape/v:imagedata'); $target = $this->getMediaTarget($docPart, $rId); - if (null !== $target) { + if ($this->hasImageLoading() && null !== $target) { if ('External' == $this->getTargetMode($docPart, $rId)) { $imageSource = $target; } else { @@ -271,7 +529,7 @@ protected function readRunChild(XMLReader $xmlReader, DOMElement $node, Abstract $embedId = $xmlReader->getAttribute('r:embed', $node, 'wp:anchor/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip'); } $target = $this->getMediaTarget($docPart, $embedId); - if (null !== $target) { + if ($this->hasImageLoading() && null !== $target) { $imageSource = "zip://{$this->docFile}#{$target}"; $parent->addImage($imageSource, null, false, $name); } @@ -320,9 +578,12 @@ protected function readRunChild(XMLReader $xmlReader, DOMElement $node, Abstract $type = ($runParent->nodeName == 'w:del') ? TrackChange::DELETED : TrackChange::INSERTED; $author = $runParent->getAttribute('w:author'); $date = DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $runParent->getAttribute('w:date')); + $date = $date instanceof DateTime ? $date : null; $element->setChangeInfo($type, $author, $date); } } + } elseif ($node->nodeName == 'w:softHyphen') { + $element = $parent->addText("\u{200c}", $fontStyle, $paragraphStyle); } } @@ -364,9 +625,8 @@ protected function readTable(XMLReader $xmlReader, DOMElement $domNode, $parent, } elseif ('w:tc' == $rowNode->nodeName) { // Cell $cellWidth = $xmlReader->getAttribute('w:w', $rowNode, 'w:tcPr/w:tcW'); $cellStyle = null; - $cellStyleNode = $xmlReader->getElement('w:tcPr', $rowNode); - if (null !== $cellStyleNode) { - $cellStyle = $this->readCellStyle($xmlReader, $cellStyleNode); + if ($xmlReader->elementExists('w:tcPr', $rowNode)) { + $cellStyle = $this->readCellStyle($xmlReader, $rowNode); } $cell = $row->addCell($cellWidth, $cellStyle); @@ -374,6 +634,8 @@ protected function readTable(XMLReader $xmlReader, DOMElement $domNode, $parent, foreach ($cellNodes as $cellNode) { if ('w:p' == $cellNode->nodeName) { // Paragraph $this->readParagraph($xmlReader, $cellNode, $cell, $docPart); + } elseif ($cellNode->nodeName == 'w:tbl') { // Table + $this->readTable($xmlReader, $cellNode, $cell, $docPart); } } } @@ -410,6 +672,18 @@ protected function readParagraphStyle(XMLReader $xmlReader, DOMElement $domNode) 'contextualSpacing' => [self::READ_TRUE, 'w:contextualSpacing'], 'bidi' => [self::READ_TRUE, 'w:bidi'], 'suppressAutoHyphens' => [self::READ_TRUE, 'w:suppressAutoHyphens'], + 'borderTopStyle' => [self::READ_VALUE, 'w:pBdr/w:top'], + 'borderTopColor' => [self::READ_VALUE, 'w:pBdr/w:top', 'w:color'], + 'borderTopSize' => [self::READ_VALUE, 'w:pBdr/w:top', 'w:sz'], + 'borderRightStyle' => [self::READ_VALUE, 'w:pBdr/w:right'], + 'borderRightColor' => [self::READ_VALUE, 'w:pBdr/w:right', 'w:color'], + 'borderRightSize' => [self::READ_VALUE, 'w:pBdr/w:right', 'w:sz'], + 'borderBottomStyle' => [self::READ_VALUE, 'w:pBdr/w:bottom'], + 'borderBottomColor' => [self::READ_VALUE, 'w:pBdr/w:bottom', 'w:color'], + 'borderBottomSize' => [self::READ_VALUE, 'w:pBdr/w:bottom', 'w:sz'], + 'borderLeftStyle' => [self::READ_VALUE, 'w:pBdr/w:left'], + 'borderLeftColor' => [self::READ_VALUE, 'w:pBdr/w:left', 'w:color'], + 'borderLeftSize' => [self::READ_VALUE, 'w:pBdr/w:left', 'w:sz'], ]; return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); @@ -550,7 +824,7 @@ private function readTableIndent(XMLReader $xmlReader, DOMElement $domNode) /** * Read w:tcPr. * - * @return array + * @return null|array */ private function readCellStyle(XMLReader $xmlReader, DOMElement $domNode) { @@ -558,11 +832,28 @@ private function readCellStyle(XMLReader $xmlReader, DOMElement $domNode) 'valign' => [self::READ_VALUE, 'w:vAlign'], 'textDirection' => [self::READ_VALUE, 'w:textDirection'], 'gridSpan' => [self::READ_VALUE, 'w:gridSpan'], - 'vMerge' => [self::READ_VALUE, 'w:vMerge'], + 'vMerge' => [self::READ_VALUE, 'w:vMerge', null, null, 'continue'], 'bgColor' => [self::READ_VALUE, 'w:shd', 'w:fill'], + 'noWrap' => [self::READ_VALUE, 'w:noWrap', null, null, true], ]; + $style = null; - return $this->readStyleDefs($xmlReader, $domNode, $styleDefs); + if ($xmlReader->elementExists('w:tcPr', $domNode)) { + $styleNode = $xmlReader->getElement('w:tcPr', $domNode); + + $borders = ['top', 'left', 'bottom', 'right']; + foreach ($borders as $side) { + $ucfSide = ucfirst($side); + + $styleDefs['border' . $ucfSide . 'Size'] = [self::READ_VALUE, 'w:tcBorders/w:' . $side, 'w:sz']; + $styleDefs['border' . $ucfSide . 'Color'] = [self::READ_VALUE, 'w:tcBorders/w:' . $side, 'w:color']; + $styleDefs['border' . $ucfSide . 'Style'] = [self::READ_VALUE, 'w:tcBorders/w:' . $side, 'w:val']; + } + + $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); + } + + return $style; } /** @@ -614,7 +905,6 @@ private function findPossibleAttribute(XMLReader $xmlReader, DOMElement $node, $ /** * Read style definition. * - * @param DOMElement $parentNode * @param array $styleDefs * * @ignoreScrutinizerPatch @@ -626,7 +916,7 @@ protected function readStyleDefs(XMLReader $xmlReader, ?DOMElement $parentNode = $styles = []; foreach ($styleDefs as $styleProp => $styleVal) { - [$method, $element, $attribute, $expected] = array_pad($styleVal, 4, null); + [$method, $element, $attribute, $expected, $default] = array_pad($styleVal, 5, null); $element = $this->findPossibleElement($xmlReader, $parentNode, $element); if ($element === null) { @@ -640,7 +930,7 @@ protected function readStyleDefs(XMLReader $xmlReader, ?DOMElement $parentNode = // Use w:val as default if no attribute assigned $attribute = ($attribute === null) ? 'w:val' : $attribute; - $attributeValue = $xmlReader->getAttribute($attribute, $node); + $attributeValue = $xmlReader->getAttribute($attribute, $node) ?? $default; $styleValue = $this->readStyleDef($method, $attributeValue, $expected); if ($styleValue !== null) { diff --git a/src/PhpWord/Reader/Word2007/Comments.php b/src/PhpWord/Reader/Word2007/Comments.php new file mode 100644 index 0000000000..61b31713b5 --- /dev/null +++ b/src/PhpWord/Reader/Word2007/Comments.php @@ -0,0 +1,56 @@ +<?php + +namespace PhpOffice\PhpWord\Reader\Word2007; + +use DateTime; +use PhpOffice\PhpWord\Element\Comment; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\XMLReader; + +class Comments extends AbstractPart +{ + /** + * Collection name comments. + * + * @var string + */ + protected $collection = 'comments'; + + /** + * Read settings.xml. + */ + public function read(PhpWord $phpWord): void + { + $xmlReader = new XMLReader(); + $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); + + $comments = $phpWord->getComments(); + + $nodes = $xmlReader->getElements('*'); + + foreach ($nodes as $node) { + $name = str_replace('w:', '', $node->nodeName); + + $author = $xmlReader->getAttribute('w:author', $node); + $date = $xmlReader->getAttribute('w:date', $node); + $initials = $xmlReader->getAttribute('w:initials', $node); + + $element = new Comment($author, new DateTime($date), $initials); + + $range = $this->getCommentReference($xmlReader->getAttribute('w:id', $node)); + if ($range['start']) { + $range['start']->setCommentRangeStart($element); + } + if ($range['end']) { + $range['end']->setCommentRangeEnd($element); + } + + $pNodes = $xmlReader->getElements('w:p/w:r', $node); + foreach ($pNodes as $pNode) { + $this->readRun($xmlReader, $pNode, $element, $this->collection); + } + + $phpWord->getComments()->addItem($element); + } + } +} diff --git a/src/PhpWord/Reader/Word2007/Document.php b/src/PhpWord/Reader/Word2007/Document.php index da42bddc9e..87a24f4198 100644 --- a/src/PhpWord/Reader/Word2007/Document.php +++ b/src/PhpWord/Reader/Word2007/Document.php @@ -138,8 +138,6 @@ private function readSectionStyle(XMLReader $xmlReader, DOMElement $domNode) /** * Read w:p node. - * - * @todo <w:lastRenderedPageBreak> */ private function readWPNode(XMLReader $xmlReader, DOMElement $node, Section &$section): void { diff --git a/src/PhpWord/Reader/Word2007/Settings.php b/src/PhpWord/Reader/Word2007/Settings.php index 63578595f5..7466008d49 100644 --- a/src/PhpWord/Reader/Word2007/Settings.php +++ b/src/PhpWord/Reader/Word2007/Settings.php @@ -30,7 +30,10 @@ */ class Settings extends AbstractPart { - private static $booleanProperties = [ + /** + * @var array<string> + */ + private $booleanProperties = [ 'mirrorMargins', 'hideSpellingErrors', 'hideGrammaticalErrors', @@ -41,6 +44,7 @@ class Settings extends AbstractPart 'updateFields', 'autoHyphenation', 'doNotHyphenateCaps', + 'bookFoldPrinting', ]; /** @@ -60,12 +64,8 @@ public function read(PhpWord $phpWord): void $value = $xmlReader->getAttribute('w:val', $node); $method = 'set' . $name; - if (in_array($name, $this::$booleanProperties)) { - if ($value == 'false') { - $docSettings->$method(false); - } else { - $docSettings->$method(true); - } + if (in_array($name, $this->booleanProperties)) { + $docSettings->$method($value !== 'false'); } elseif (method_exists($this, $method)) { $this->$method($xmlReader, $phpWord, $node); } elseif (method_exists($docSettings, $method)) { diff --git a/src/PhpWord/Reader/Word2007/Styles.php b/src/PhpWord/Reader/Word2007/Styles.php index 4566398ad2..760adf9493 100644 --- a/src/PhpWord/Reader/Word2007/Styles.php +++ b/src/PhpWord/Reader/Word2007/Styles.php @@ -39,14 +39,16 @@ public function read(PhpWord $phpWord): void $fontDefaults = $xmlReader->getElement('w:docDefaults/w:rPrDefault'); if ($fontDefaults !== null) { $fontDefaultStyle = $this->readFontStyle($xmlReader, $fontDefaults); - if (array_key_exists('name', $fontDefaultStyle)) { - $phpWord->setDefaultFontName($fontDefaultStyle['name']); - } - if (array_key_exists('size', $fontDefaultStyle)) { - $phpWord->setDefaultFontSize($fontDefaultStyle['size']); - } - if (array_key_exists('lang', $fontDefaultStyle)) { - $phpWord->getSettings()->setThemeFontLang(new Language($fontDefaultStyle['lang'])); + if ($fontDefaultStyle) { + if (array_key_exists('name', $fontDefaultStyle)) { + $phpWord->setDefaultFontName($fontDefaultStyle['name']); + } + if (array_key_exists('size', $fontDefaultStyle)) { + $phpWord->setDefaultFontSize($fontDefaultStyle['size']); + } + if (array_key_exists('lang', $fontDefaultStyle)) { + $phpWord->getSettings()->setThemeFontLang(new Language($fontDefaultStyle['lang'])); + } } } diff --git a/src/PhpWord/Settings.php b/src/PhpWord/Settings.php index 7b1f456eca..984486ccfe 100644 --- a/src/PhpWord/Settings.php +++ b/src/PhpWord/Settings.php @@ -2,10 +2,8 @@ /** * This file is part of PHPWord - A pure PHP library for reading and writing * word processing documents. - * * PHPWord is free software distributed under the terms of the GNU Lesser * General Public License version 3 as published by the Free Software Foundation. - * * For the full copyright and license information, please read the LICENSE * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. @@ -29,48 +27,46 @@ class Settings * * @const string */ - const ZIPARCHIVE = 'ZipArchive'; - const PCLZIP = 'PclZip'; - const OLD_LIB = \PhpOffice\PhpWord\Shared\ZipArchive::class; // @deprecated 0.11 + public const ZIPARCHIVE = 'ZipArchive'; + public const PCLZIP = 'PclZip'; + public const OLD_LIB = \PhpOffice\PhpWord\Shared\ZipArchive::class; // @deprecated 0.11 /** * PDF rendering libraries. * * @const string */ - const PDF_RENDERER_DOMPDF = 'DomPDF'; - const PDF_RENDERER_TCPDF = 'TCPDF'; - const PDF_RENDERER_MPDF = 'MPDF'; + public const PDF_RENDERER_DOMPDF = 'DomPDF'; + public const PDF_RENDERER_TCPDF = 'TCPDF'; + public const PDF_RENDERER_MPDF = 'MPDF'; /** * Measurement units multiplication factor. - * * Applied to: * - Section: margins, header/footer height, gutter, column spacing * - Tab: position * - Indentation: left, right, firstLine, hanging - * - Spacing: before, after + * - Spacing: before, after. * * @const string */ - const UNIT_TWIP = 'twip'; // = 1/20 point - const UNIT_CM = 'cm'; - const UNIT_MM = 'mm'; - const UNIT_INCH = 'inch'; - const UNIT_POINT = 'point'; // = 1/72 inch - const UNIT_PICA = 'pica'; // = 1/6 inch = 12 points + public const UNIT_TWIP = 'twip'; // = 1/20 point + public const UNIT_CM = 'cm'; + public const UNIT_MM = 'mm'; + public const UNIT_INCH = 'inch'; + public const UNIT_POINT = 'point'; // = 1/72 inch + public const UNIT_PICA = 'pica'; // = 1/6 inch = 12 points /** * Default font settings. - * * OOXML defined font size values in halfpoints, i.e. twice of what PhpWord * use, and the conversion will be conducted during XML writing. */ - const DEFAULT_FONT_NAME = 'Arial'; - const DEFAULT_FONT_SIZE = 10; - const DEFAULT_FONT_COLOR = '000000'; - const DEFAULT_FONT_CONTENT_TYPE = 'default'; // default|eastAsia|cs - const DEFAULT_PAPER = 'A4'; + public const DEFAULT_FONT_NAME = 'Arial'; + public const DEFAULT_FONT_SIZE = 10; + public const DEFAULT_FONT_COLOR = '000000'; + public const DEFAULT_FONT_CONTENT_TYPE = 'default'; // default|eastAsia|cs + public const DEFAULT_PAPER = 'A4'; /** * Compatibility option for XMLWriter. @@ -89,21 +85,28 @@ class Settings /** * Name of the external Library used for rendering PDF files. * - * @var string + * @var null|string */ private static $pdfRendererName; + /** + * Options used for rendering PDF files. + * + * @var array + */ + private static $pdfRendererOptions = []; + /** * Directory Path to the external Library used for rendering PDF files. * - * @var string + * @var null|string */ private static $pdfRendererPath; /** * Measurement unit. * - * @var float|int + * @var string */ private static $measurementUnit = self::UNIT_TWIP; @@ -117,7 +120,7 @@ class Settings /** * Default font size. * - * @var int + * @var float|int */ private static $defaultFontSize = self::DEFAULT_FONT_SIZE; @@ -128,6 +131,13 @@ class Settings */ private static $defaultPaper = self::DEFAULT_PAPER; + /** + * Is RTL by default ? + * + * @var ?bool + */ + private static $defaultRtl; + /** * The user defined temporary directory. * @@ -148,23 +158,17 @@ class Settings * * @return bool Compatibility */ - public static function hasCompatibility() + public static function hasCompatibility(): bool { return self::$xmlWriterCompatibility; } /** * Set the compatibility option used by the XMLWriter. - * - * This sets the setIndent and setIndentString for better compatibility - * - * @param bool $compatibility - * - * @return bool + * This sets the setIndent and setIndentString for better compatibility. */ - public static function setCompatibility($compatibility) + public static function setCompatibility(bool $compatibility): bool { - $compatibility = (bool) $compatibility; self::$xmlWriterCompatibility = $compatibility; return true; @@ -172,22 +176,16 @@ public static function setCompatibility($compatibility) /** * Get zip handler class. - * - * @return string */ - public static function getZipClass() + public static function getZipClass(): string { return self::$zipClass; } /** * Set zip handler class. - * - * @param string $zipClass - * - * @return bool */ - public static function setZipClass($zipClass) + public static function setZipClass(string $zipClass): bool { if (in_array($zipClass, [self::PCLZIP, self::ZIPARCHIVE, self::OLD_LIB])) { self::$zipClass = $zipClass; @@ -201,12 +199,9 @@ public static function setZipClass($zipClass) /** * Set details of the external library for rendering PDF files. * - * @param string $libraryName - * @param string $libraryBaseDir - * * @return bool Success or failure */ - public static function setPdfRenderer($libraryName, $libraryBaseDir) + public static function setPdfRenderer(string $libraryName, string $libraryBaseDir): bool { if (!self::setPdfRendererName($libraryName)) { return false; @@ -217,22 +212,16 @@ public static function setPdfRenderer($libraryName, $libraryBaseDir) /** * Return the PDF Rendering Library. - * - * @return string */ - public static function getPdfRendererName() + public static function getPdfRendererName(): ?string { return self::$pdfRendererName; } /** * Identify the external library to use for rendering PDF files. - * - * @param string $libraryName - * - * @return bool */ - public static function setPdfRendererName($libraryName) + public static function setPdfRendererName(?string $libraryName): bool { $pdfRenderers = [self::PDF_RENDERER_DOMPDF, self::PDF_RENDERER_TCPDF, self::PDF_RENDERER_MPDF]; if (!in_array($libraryName, $pdfRenderers)) { @@ -245,22 +234,36 @@ public static function setPdfRendererName($libraryName) /** * Return the directory path to the PDF Rendering Library. - * - * @return string */ - public static function getPdfRendererPath() + public static function getPdfRendererPath(): ?string { return self::$pdfRendererPath; } + /** + * Set options of the external library for rendering PDF files. + */ + public static function setPdfRendererOptions(array $options): void + { + self::$pdfRendererOptions = $options; + } + + /** + * Return the PDF Rendering Options. + */ + public static function getPdfRendererOptions(): array + { + return self::$pdfRendererOptions; + } + /** * Location of external library to use for rendering PDF files. * - * @param string $libraryBaseDir Directory path to the library's base folder + * @param null|string $libraryBaseDir Directory path to the library's base folder * * @return bool Success or failure */ - public static function setPdfRendererPath($libraryBaseDir) + public static function setPdfRendererPath(?string $libraryBaseDir): bool { if (!$libraryBaseDir || false === file_exists($libraryBaseDir) || false === is_readable($libraryBaseDir)) { return false; @@ -272,25 +275,25 @@ public static function setPdfRendererPath($libraryBaseDir) /** * Get measurement unit. - * - * @return string */ - public static function getMeasurementUnit() + public static function getMeasurementUnit(): string { return self::$measurementUnit; } /** * Set measurement unit. - * - * @param string $value - * - * @return bool */ - public static function setMeasurementUnit($value) + public static function setMeasurementUnit(string $value): bool { - $units = [self::UNIT_TWIP, self::UNIT_CM, self::UNIT_MM, self::UNIT_INCH, - self::UNIT_POINT, self::UNIT_PICA, ]; + $units = [ + self::UNIT_TWIP, + self::UNIT_CM, + self::UNIT_MM, + self::UNIT_INCH, + self::UNIT_POINT, + self::UNIT_PICA, + ]; if (!in_array($value, $units)) { return false; } @@ -302,11 +305,11 @@ public static function setMeasurementUnit($value) /** * Sets the user defined path to temporary directory. * - * @since 0.12.0 - * * @param string $tempDir The user defined path to temporary directory + * + * @since 0.12.0 */ - public static function setTempDir($tempDir): void + public static function setTempDir(string $tempDir): void { self::$tempDir = $tempDir; } @@ -315,10 +318,8 @@ public static function setTempDir($tempDir): void * Returns path to temporary directory. * * @since 0.12.0 - * - * @return string */ - public static function getTempDir() + public static function getTempDir(): string { if (!empty(self::$tempDir)) { $tempDir = self::$tempDir; @@ -331,44 +332,34 @@ public static function getTempDir() /** * @since 0.13.0 - * - * @return bool */ - public static function isOutputEscapingEnabled() + public static function isOutputEscapingEnabled(): bool { return self::$outputEscapingEnabled; } /** * @since 0.13.0 - * - * @param bool $outputEscapingEnabled */ - public static function setOutputEscapingEnabled($outputEscapingEnabled): void + public static function setOutputEscapingEnabled(bool $outputEscapingEnabled): void { self::$outputEscapingEnabled = $outputEscapingEnabled; } /** * Get default font name. - * - * @return string */ - public static function getDefaultFontName() + public static function getDefaultFontName(): string { return self::$defaultFontName; } /** * Set default font name. - * - * @param string $value - * - * @return bool */ - public static function setDefaultFontName($value) + public static function setDefaultFontName(string $value): bool { - if (is_string($value) && trim($value) !== '') { + if (trim($value) !== '') { self::$defaultFontName = $value; return true; @@ -380,7 +371,7 @@ public static function setDefaultFontName($value) /** * Get default font size. * - * @return int + * @return float|int */ public static function getDefaultFontSize() { @@ -390,14 +381,11 @@ public static function getDefaultFontSize() /** * Set default font size. * - * @param int $value - * - * @return bool + * @param null|float|int $value */ - public static function setDefaultFontSize($value) + public static function setDefaultFontSize($value): bool { - $value = (int) $value; - if ($value > 0) { + if ((is_int($value) || is_float($value)) && (int) $value > 0) { self::$defaultFontSize = $value; return true; @@ -406,14 +394,20 @@ public static function setDefaultFontSize($value) return false; } + public static function setDefaultRtl(?bool $defaultRtl): void + { + self::$defaultRtl = $defaultRtl; + } + + public static function isDefaultRtl(): ?bool + { + return self::$defaultRtl; + } + /** * Load setting from phpword.yml or phpword.yml.dist. - * - * @param string $filename - * - * @return array */ - public static function loadConfig($filename = null) + public static function loadConfig(?string $filename = null): array { // Get config file $configFile = null; @@ -455,24 +449,18 @@ public static function loadConfig($filename = null) /** * Get default paper. - * - * @return string */ - public static function getDefaultPaper() + public static function getDefaultPaper(): string { return self::$defaultPaper; } /** * Set default paper. - * - * @param string $value - * - * @return bool */ - public static function setDefaultPaper($value) + public static function setDefaultPaper(string $value): bool { - if (is_string($value) && trim($value) !== '') { + if (trim($value) !== '') { self::$defaultPaper = $value; return true; diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php index 53891f5292..365aa141b0 100644 --- a/src/PhpWord/Shared/Converter.php +++ b/src/PhpWord/Shared/Converter.php @@ -374,7 +374,7 @@ public static function htmlToRgb($value) * * @param string $value * - * @return float + * @return ?float */ public static function cssToPoint($value) { diff --git a/src/PhpWord/Shared/Css.php b/src/PhpWord/Shared/Css.php new file mode 100644 index 0000000000..fea3e3d5d6 --- /dev/null +++ b/src/PhpWord/Shared/Css.php @@ -0,0 +1,80 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ +declare(strict_types=1); + +namespace PhpOffice\PhpWord\Shared; + +class Css +{ + /** + * @var string + */ + private $cssContent; + + /** + * @var array<string, array<string, string>> + */ + private $styles = []; + + public function __construct(string $cssContent) + { + $this->cssContent = $cssContent; + } + + public function process(): void + { + $cssContent = str_replace(["\r", "\n"], '', $this->cssContent); + preg_match_all('/(.+?)\s?\{\s?(.+?)\s?\}/', $cssContent, $cssExtracted); + // Check the number of extracted + if (count($cssExtracted) != 3) { + return; + } + // Check if there are x selectors and x rules + if (count($cssExtracted[1]) != count($cssExtracted[2])) { + return; + } + + foreach ($cssExtracted[1] as $key => $selector) { + $rules = trim($cssExtracted[2][$key]); + $rules = explode(';', $rules); + foreach ($rules as $rule) { + if (empty($rule)) { + continue; + } + [$key, $value] = explode(':', trim($rule)); + $this->styles[$this->sanitize($selector)][$this->sanitize($key)] = $this->sanitize($value); + } + } + } + + public function getStyles(): array + { + return $this->styles; + } + + public function getStyle(string $selector): array + { + $selector = $this->sanitize($selector); + + return $this->styles[$selector] ?? []; + } + + private function sanitize(string $value): string + { + return addslashes(trim($value)); + } +} diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index ad5aec1181..334f5c269e 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -37,12 +37,19 @@ */ class Html { + private const RGB_REGEXP = '/^\s*rgb\s*[(]\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*[)]\s*$/'; + protected static $listIndex = 0; protected static $xpath; protected static $options; + /** + * @var Css + */ + protected static $css; + /** * Add HTML parts. * @@ -61,7 +68,7 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit * @todo parse $stylesheet for default styles. Should result in an array based on id, class and element, * which could be applied when such an element occurs in the parseNode function. */ - self::$options = $options; + static::$options = $options; // Preprocess: remove all line ends, decode HTML entity, // fix ampersand and angle brackets and add body tag for HTML fragments @@ -82,10 +89,10 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit $dom = new DOMDocument(); $dom->preserveWhiteSpace = $preserveWhiteSpace; $dom->loadXML($html); - self::$xpath = new DOMXPath($dom); + static::$xpath = new DOMXPath($dom); $node = $dom->getElementsByTagName('body'); - self::parseNode($node->item(0), $element); + static::parseNode($node->item(0), $element); if (\PHP_VERSION_ID < 80000) { libxml_disable_entity_loader($orignalLibEntityLoader); } @@ -95,7 +102,7 @@ public static function addHtml($element, $html, $fullHTML = false, $preserveWhit * parse Inline style of a node. * * @param DOMNode $node Node to check on attributes and to compile a style array - * @param array $styles is supplied, the inline style attributes are added to the already existing style + * @param array<string, mixed> $styles is supplied, the inline style attributes are added to the already existing style * * @return array */ @@ -104,11 +111,14 @@ protected static function parseInlineStyle($node, $styles = []) if (XML_ELEMENT_NODE == $node->nodeType) { $attributes = $node->attributes; // get all the attributes(eg: id, class) + $attributeDir = $attributes->getNamedItem('dir'); + $attributeDirValue = $attributeDir ? $attributeDir->nodeValue : ''; + $bidi = $attributeDirValue === 'rtl'; foreach ($attributes as $attribute) { $val = $attribute->value; switch (strtolower($attribute->name)) { case 'align': - $styles['alignment'] = self::mapAlign(trim($val)); + $styles['alignment'] = self::mapAlign(trim($val), $bidi); break; case 'lang': @@ -136,7 +146,7 @@ protected static function parseInlineStyle($node, $styles = []) break; case 'bgcolor': // tables, rows, cells e.g. <tr bgColor="#FF0000"> - $styles['bgColor'] = trim($val, '# '); + $styles['bgColor'] = self::convertRgb($val); break; case 'valign': @@ -149,6 +159,19 @@ protected static function parseInlineStyle($node, $styles = []) } } + $attributeIdentifier = $attributes->getNamedItem('id'); + if ($attributeIdentifier && self::$css) { + $styles = self::parseStyleDeclarations(self::$css->getStyle('#' . $attributeIdentifier->nodeValue), $styles); + } + + $attributeClass = $attributes->getNamedItem('class'); + if ($attributeClass) { + if (self::$css) { + $styles = self::parseStyleDeclarations(self::$css->getStyle('.' . $attributeClass->nodeValue), $styles); + } + $styles['className'] = $attributeClass->nodeValue; + } + $attributeStyle = $attributes->getNamedItem('style'); if ($attributeStyle) { $styles = self::parseStyle($attributeStyle, $styles); @@ -168,6 +191,13 @@ protected static function parseInlineStyle($node, $styles = []) */ protected static function parseNode($node, $element, $styles = [], $data = []): void { + if ($node->nodeName == 'style') { + self::$css = new Css($node->textContent); + self::$css->process(); + + return; + } + // Populate styles array $styleTypes = ['font', 'paragraph', 'list', 'table', 'row', 'cell']; foreach ($styleTypes as $styleType) { @@ -246,7 +276,7 @@ protected static function parseNode($node, $element, $styles = [], $data = []): * Parse child nodes. * * @param DOMNode $node - * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param \PhpOffice\PhpWord\Element\AbstractContainer|Row|Table $element * @param array $styles * @param array $data */ @@ -297,10 +327,10 @@ protected static function parseInput($node, $element, &$styles): void return; } - $inputType = $attributes->getNamedItem('type')->value; + $inputType = $attributes->getNamedItem('type')->nodeValue; switch ($inputType) { case 'checkbox': - $checked = ($checked = $attributes->getNamedItem('checked')) && $checked->value === 'true' ? true : false; + $checked = ($checked = $attributes->getNamedItem('checked')) && $checked->nodeValue === 'true' ? true : false; $textrun = $element->addTextRun($styles['paragraph']); $textrun->addFormField('checkbox')->setValue($checked); @@ -389,9 +419,14 @@ protected static function parseTable($node, $element, &$styles) $newElement = $element->addTable($elementStyles); + // Add style name from CSS Class + if (isset($elementStyles['className'])) { + $newElement->getStyle()->setStyleName($elementStyles['className']); + } + $attributes = $node->attributes; - if ($attributes->getNamedItem('border') !== null) { - $border = (int) $attributes->getNamedItem('border')->value; + if ($attributes->getNamedItem('border')) { + $border = (int) $attributes->getNamedItem('border')->nodeValue; $newElement->getStyle()->setBorderSize(Converter::pixelToTwip($border)); } @@ -414,7 +449,11 @@ protected static function parseRow($node, $element, &$styles) $rowStyles['tblHeader'] = true; } - return $element->addRow(null, $rowStyles); + // set cell height to control row heights + $height = $rowStyles['height'] ?? null; + unset($rowStyles['height']); // would not apply + + return $element->addRow($height, $rowStyles); } /** @@ -588,15 +627,15 @@ protected static function getListStyle($isOrderedList) return [ 'type' => 'hybridMultilevel', 'levels' => [ - ['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'], - ['format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'], - ['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'], - ['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'], - ['format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'], - ['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'], - ['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'], - ['format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'], - ['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => '•', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => '◦', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => '•', 'alignment' => 'left', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => '•', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => '◦', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => '•', 'alignment' => 'left', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => '•', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => '◦', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => '•', 'alignment' => 'left', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'], ], ]; } @@ -635,13 +674,22 @@ protected static function parseStyle($attribute, $styles) { $properties = explode(';', trim($attribute->value, " \t\n\r\0\x0B;")); + $selectors = []; foreach ($properties as $property) { [$cKey, $cValue] = array_pad(explode(':', $property, 2), 2, null); - $cValue = trim($cValue ?? ''); - $cKey = strtolower(trim($cKey)); - switch ($cKey) { + $selectors[strtolower(trim($cKey))] = trim($cValue ?? ''); + } + + return self::parseStyleDeclarations($selectors, $styles); + } + + protected static function parseStyleDeclarations(array $selectors, array $styles) + { + $bidi = ($selectors['direction'] ?? '') === 'rtl'; + foreach ($selectors as $property => $value) { + switch ($property) { case 'text-decoration': - switch ($cValue) { + switch ($value) { case 'underline': $styles['underline'] = 'single'; @@ -654,44 +702,45 @@ protected static function parseStyle($attribute, $styles) break; case 'text-align': - $styles['alignment'] = self::mapAlign($cValue); + $styles['alignment'] = self::mapAlign($value, $bidi); break; case 'display': - $styles['hidden'] = $cValue === 'none' || $cValue === 'hidden'; + $styles['hidden'] = $value === 'none' || $value === 'hidden'; break; case 'direction': - $styles['rtl'] = $cValue === 'rtl'; + $styles['rtl'] = $value === 'rtl'; + $styles['bidi'] = $value === 'rtl'; break; case 'font-size': - $styles['size'] = Converter::cssToPoint($cValue); + $styles['size'] = Converter::cssToPoint($value); break; case 'font-family': - $cValue = array_map('trim', explode(',', $cValue)); - $styles['name'] = ucwords($cValue[0]); + $value = array_map('trim', explode(',', $value)); + $styles['name'] = ucwords($value[0]); break; case 'color': - $styles['color'] = trim($cValue, '#'); + $styles['color'] = self::convertRgb($value); break; case 'background-color': - $styles['bgColor'] = trim($cValue, '#'); + $styles['bgColor'] = self::convertRgb($value); break; case 'line-height': $matches = []; - if ($cValue === 'normal') { + if ($value === 'normal') { $spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO; $spacing = 0; - } elseif (preg_match('/([0-9]+\.?[0-9]*[a-z]+)/', $cValue, $matches)) { + } elseif (preg_match('/([0-9]+\.?[0-9]*[a-z]+)/', $value, $matches)) { //matches number with a unit, e.g. 12px, 15pt, 20mm, ... $spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::EXACT; $spacing = Converter::cssToTwip($matches[1]); - } elseif (preg_match('/([0-9]+)%/', $cValue, $matches)) { + } elseif (preg_match('/([0-9]+)%/', $value, $matches)) { //matches percentages $spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO; //we are subtracting 1 line height because the Spacing writer is adding one line @@ -700,23 +749,23 @@ protected static function parseStyle($attribute, $styles) //any other, wich is a multiplier. E.g. 1.2 $spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO; //we are subtracting 1 line height because the Spacing writer is adding one line - $spacing = ($cValue * Paragraph::LINE_HEIGHT) - Paragraph::LINE_HEIGHT; + $spacing = ($value * Paragraph::LINE_HEIGHT) - Paragraph::LINE_HEIGHT; } $styles['spacingLineRule'] = $spacingLineRule; $styles['line-spacing'] = $spacing; break; case 'letter-spacing': - $styles['letter-spacing'] = Converter::cssToTwip($cValue); + $styles['letter-spacing'] = Converter::cssToTwip($value); break; case 'text-indent': - $styles['indentation']['firstLine'] = Converter::cssToTwip($cValue); + $styles['indentation']['firstLine'] = Converter::cssToTwip($value); break; case 'font-weight': $tValue = false; - if (preg_match('#bold#', $cValue)) { + if (preg_match('#bold#', $value)) { $tValue = true; // also match bolder } $styles['bold'] = $tValue; @@ -724,52 +773,65 @@ protected static function parseStyle($attribute, $styles) break; case 'font-style': $tValue = false; - if (preg_match('#(?:italic|oblique)#', $cValue)) { + if (preg_match('#(?:italic|oblique)#', $value)) { $tValue = true; } $styles['italic'] = $tValue; + break; + case 'font-variant': + $tValue = false; + if (preg_match('#small-caps#', $value)) { + $tValue = true; + } + $styles['smallCaps'] = $tValue; + break; case 'margin': - $cValue = Converter::cssToTwip($cValue); - $styles['spaceBefore'] = $cValue; - $styles['spaceAfter'] = $cValue; + $value = Converter::cssToTwip($value); + $styles['spaceBefore'] = $value; + $styles['spaceAfter'] = $value; break; case 'margin-top': - // BC change: up to ver. 0.17.0 incorrectly converted to points - Converter::cssToPoint($cValue) - $styles['spaceBefore'] = Converter::cssToTwip($cValue); + // BC change: up to ver. 0.17.0 incorrectly converted to points - Converter::cssToPoint($value) + $styles['spaceBefore'] = Converter::cssToTwip($value); break; case 'margin-bottom': - // BC change: up to ver. 0.17.0 incorrectly converted to points - Converter::cssToPoint($cValue) - $styles['spaceAfter'] = Converter::cssToTwip($cValue); + // BC change: up to ver. 0.17.0 incorrectly converted to points - Converter::cssToPoint($value) + $styles['spaceAfter'] = Converter::cssToTwip($value); break; case 'border-color': - self::mapBorderColor($styles, $cValue); + self::mapBorderColor($styles, $value); break; case 'border-width': - $styles['borderSize'] = Converter::cssToPoint($cValue); + $styles['borderSize'] = Converter::cssToPoint($value); break; case 'border-style': - $styles['borderStyle'] = self::mapBorderStyle($cValue); + $styles['borderStyle'] = self::mapBorderStyle($value); break; case 'width': - if (preg_match('/([0-9]+[a-z]+)/', $cValue, $matches)) { + if (preg_match('/([0-9]+[a-z]+)/', $value, $matches)) { $styles['width'] = Converter::cssToTwip($matches[1]); $styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::TWIP; - } elseif (preg_match('/([0-9]+)%/', $cValue, $matches)) { + } elseif (preg_match('/([0-9]+)%/', $value, $matches)) { $styles['width'] = $matches[1] * 50; $styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::PERCENT; - } elseif (preg_match('/([0-9]+)/', $cValue, $matches)) { + } elseif (preg_match('/([0-9]+)/', $value, $matches)) { $styles['width'] = $matches[1]; $styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::AUTO; } + break; + case 'height': + $styles['height'] = Converter::cssToTwip($value); + $styles['exactHeight'] = true; + break; case 'border': case 'border-top': @@ -778,9 +840,9 @@ protected static function parseStyle($attribute, $styles) case 'border-left': // must have exact order [width color style], e.g. "1px #0011CC solid" or "2pt green solid" // Word does not accept shortened hex colors e.g. #CCC, only full e.g. #CCCCCC - if (preg_match('/([0-9]+[^0-9]*)\s+(\#[a-fA-F0-9]+|[a-zA-Z]+)\s+([a-z]+)/', $cValue, $matches)) { - if (false !== strpos($cKey, '-')) { - $tmp = explode('-', $cKey); + if (preg_match('/([0-9]+[^0-9]*)\s+(\#[a-fA-F0-9]+|[a-zA-Z]+)\s+([a-z]+)/', $value, $matches)) { + if (false !== strpos($property, '-')) { + $tmp = explode('-', $property); $which = $tmp[1]; $which = ucfirst($which); // e.g. bottom -> Bottom } else { @@ -803,13 +865,13 @@ protected static function parseStyle($attribute, $styles) break; case 'vertical-align': // https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align - if (preg_match('#(?:top|bottom|middle|sub|baseline)#i', $cValue, $matches)) { + if (preg_match('#(?:top|bottom|middle|sub|baseline)#i', $value, $matches)) { $styles['valign'] = self::mapAlignVertical($matches[0]); } break; case 'page-break-after': - if ($cValue == 'always') { + if ($value == 'always') { $styles['isPageBreak'] = true; } @@ -840,12 +902,34 @@ protected static function parseImage($node, $element) break; case 'width': $width = $attribute->value; + + // pt + if (false !== strpos($width, 'pt')) { + $width = Converter::pointToPixel((float) str_replace('pt', '', $width)); + } + + // px + if (false !== strpos($width, 'px')) { + $width = str_replace('px', '', $width); + } + $style['width'] = $width; $style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX; break; case 'height': $height = $attribute->value; + + // pt + if (false !== strpos($height, 'pt')) { + $height = Converter::pointToPixel((float) str_replace('pt', '', $height)); + } + + // px + if (false !== strpos($height, 'px')) { + $height = str_replace('px', '', $height); + } + $style['height'] = $height; $style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX; @@ -910,7 +994,10 @@ protected static function parseImage($node, $element) $tmpDir = Settings::getTempDir() . '/'; $match = []; preg_match('/.+\.(\w+)$/', $src, $match); - $src = $tmpDir . uniqid() . '.' . $match[1]; + $src = $tmpDir . uniqid(); + if (isset($match[1])) { + $src .= '.' . $match[1]; + } $ifp = fopen($src, 'wb'); @@ -968,20 +1055,21 @@ protected static function mapBorderColor(&$styles, $cssBorderColor): void * Transforms a HTML/CSS alignment into a \PhpOffice\PhpWord\SimpleType\Jc. * * @param string $cssAlignment + * @param bool $bidi * * @return null|string */ - protected static function mapAlign($cssAlignment) + protected static function mapAlign($cssAlignment, $bidi) { switch ($cssAlignment) { case 'right': - return Jc::END; + return $bidi ? Jc::START : Jc::END; case 'center': return Jc::CENTER; case 'justify': return Jc::BOTH; default: - return Jc::START; + return $bidi ? Jc::END : Jc::START; } } @@ -1068,7 +1156,11 @@ protected static function parseLink($node, $element, &$styles) } $styles['font'] = self::parseInlineStyle($node, $styles['font']); - if (strpos($target, '#') === 0) { + if (empty($target)) { + $target = '#'; + } + + if (strpos($target, '#') === 0 && strlen($target) > 1) { return $element->addLink(substr($target, 1), $node->textContent, $styles['font'], $styles['paragraph'], true); } @@ -1108,4 +1200,13 @@ protected static function parseHorizRule($node, $element): void // - line - that is a shape, has different behaviour // - repeated text, e.g. underline "_", because of unpredictable line wrapping } + + private static function convertRgb(string $rgb): string + { + if (preg_match(self::RGB_REGEXP, $rgb, $matches) === 1) { + return sprintf('%02X%02X%02X', $matches[1], $matches[2], $matches[3]); + } + + return trim($rgb, '# '); + } } diff --git a/src/PhpWord/Shared/Microsoft/PasswordEncoder.php b/src/PhpWord/Shared/Microsoft/PasswordEncoder.php index 5ff42e49b9..d6cf69fc6d 100644 --- a/src/PhpWord/Shared/Microsoft/PasswordEncoder.php +++ b/src/PhpWord/Shared/Microsoft/PasswordEncoder.php @@ -34,6 +34,9 @@ class PasswordEncoder const ALGORITHM_MAC = 'MAC'; const ALGORITHM_HMAC = 'HMAC'; + private const ALL_ONE_BITS = (PHP_INT_SIZE > 4) ? 0xFFFFFFFF : -1; + private const HIGH_ORDER_BIT = (PHP_INT_SIZE > 4) ? 0x80000000 : PHP_INT_MIN; + /** * Mapping between algorithm name and algorithm ID. * @@ -128,7 +131,7 @@ public static function hashPassword($password, $algorithmName = self::ALGORITHM_ // build low-order word and hig-order word and combine them $combinedKey = self::buildCombinedKey($byteChars); // build reversed hexadecimal string - $hex = str_pad(strtoupper(dechex($combinedKey & 0xFFFFFFFF)), 8, '0', \STR_PAD_LEFT); + $hex = str_pad(strtoupper(dechex($combinedKey & self::ALL_ONE_BITS)), 8, '0', \STR_PAD_LEFT); $reversedHex = $hex[6] . $hex[7] . $hex[4] . $hex[5] . $hex[2] . $hex[3] . $hex[0] . $hex[1]; $generatedKey = mb_convert_encoding($reversedHex, 'UCS-2LE', 'UTF-8'); @@ -232,10 +235,10 @@ private static function buildCombinedKey($byteChars) */ private static function int32($value) { - $value = ($value & 0xFFFFFFFF); + $value = $value & self::ALL_ONE_BITS; - if ($value & 0x80000000) { - $value = -((~$value & 0xFFFFFFFF) + 1); + if ($value & self::HIGH_ORDER_BIT) { + $value = -((~$value & self::ALL_ONE_BITS) + 1); } return $value; diff --git a/src/PhpWord/Shared/OLERead.php b/src/PhpWord/Shared/OLERead.php index 316e7812e6..d4399d6fb7 100644 --- a/src/PhpWord/Shared/OLERead.php +++ b/src/PhpWord/Shared/OLERead.php @@ -61,6 +61,17 @@ class OLERead public $wrkObjectPool = null; public $summaryInformation = null; public $docSummaryInfos = null; + public $numBigBlockDepotBlocks = null; + public $rootStartBlock = null; + public $sbdStartBlock = null; + public $extensionBlock = null; + public $numExtensionBlocks = null; + public $bigBlockChain = null; + public $smallBlockChain = null; + public $entry = null; + public $rootentry = null; + public $wrkObjectPoolelseif = null; + public $props = array(); /** * Read the file diff --git a/src/PhpWord/Shared/PCLZip/pclzip.lib.php b/src/PhpWord/Shared/PCLZip/pclzip.lib.php index 62927497bb..5243b3c3a0 100644 --- a/src/PhpWord/Shared/PCLZip/pclzip.lib.php +++ b/src/PhpWord/Shared/PCLZip/pclzip.lib.php @@ -1597,7 +1597,7 @@ public function privParseOptions(&$p_options_list, $p_size, &$v_result_list, $v_ if (is_string($p_options_list[$i + 1])) { // ----- Remove spaces - $p_options_list[$i + 1] = strtr($p_options_list[$i + 1], ' ', ''); + $p_options_list[$i + 1] = str_replace(' ', '', $p_options_list[$i + 1]); // ----- Parse items $v_work_list = explode(",", $p_options_list[$i + 1]); diff --git a/src/PhpWord/Shared/Text.php b/src/PhpWord/Shared/Text.php index 667d67ab4a..b9a8831cdd 100644 --- a/src/PhpWord/Shared/Text.php +++ b/src/PhpWord/Shared/Text.php @@ -138,14 +138,15 @@ public static function isUTF8($value = '') /** * Return UTF8 encoded value. * - * @param string $value + * @param null|string $value * - * @return string + * @return ?string */ public static function toUTF8($value = '') { if (null !== $value && !self::isUTF8($value)) { - $value = utf8_encode($value); + // PHP8.2 : utf8_encode is deprecated, but mb_convert_encoding always usable + $value = (function_exists('mb_convert_encoding')) ? mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1') : utf8_encode($value); } return $value; diff --git a/src/PhpWord/Shared/Validate.php b/src/PhpWord/Shared/Validate.php new file mode 100644 index 0000000000..0967b5693c --- /dev/null +++ b/src/PhpWord/Shared/Validate.php @@ -0,0 +1,76 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ +declare(strict_types=1); + +namespace PhpOffice\PhpWord\Shared; + +class Validate +{ + public const CSS_WHITESPACE = [ + 'pre-wrap', + 'normal', + 'nowrap', + 'pre', + 'pre-line', + 'initial', + 'inherit', + ]; + + public const CSS_GENERICFONT = [ + 'serif', + 'sans-serif', + 'monospace', + 'cursive', + 'fantasy', + 'system-ui', + 'math', + 'emoji', + 'fangsong', + ]; + + /** + * Validate html css white-space value. It is expected that only pre-wrap and normal (default) are useful. + * + * @param string $value CSS White space + * + * @return string value if valid, empty string if not + */ + public static function validateCSSWhiteSpace(?string $value): string + { + if (in_array($value, self::CSS_WHITESPACE)) { + return $value; + } + + return ''; + } + + /** + * Validate generic font for fallback for html. + * + * @param string $value Generic font name + * + * @return string Value if legitimate, empty string if not + */ + public static function validateCSSGenericFont(?string $value): string + { + if (in_array($value, self::CSS_GENERICFONT)) { + return $value; + } + + return ''; + } +} diff --git a/src/PhpWord/Shared/XMLReader.php b/src/PhpWord/Shared/XMLReader.php index 73762db2ea..e836607d29 100644 --- a/src/PhpWord/Shared/XMLReader.php +++ b/src/PhpWord/Shared/XMLReader.php @@ -61,8 +61,16 @@ public function getDomFromZip($zipFile, $xmlFile) } $zip = new ZipArchive(); - $zip->open($zipFile); - $content = $zip->getFromName($xmlFile); + $openStatus = $zip->open($zipFile); + if ($openStatus !== true) { + /** + * Throw an exception since making further calls on the ZipArchive would cause a fatal error. + * This prevents fatal errors on corrupt archives and attempts to open old "doc" files. + */ + throw new Exception("The archive failed to load with the following error code: $openStatus"); + } + + $content = $zip->getFromName(ltrim($xmlFile, '/')); $zip->close(); if ($content === false) { @@ -97,24 +105,21 @@ public function getDomFromString($content) * Get elements. * * @param string $path - * @param DOMElement $contextNode * - * @return DOMNodeList + * @return DOMNodeList<DOMElement> */ public function getElements($path, ?DOMElement $contextNode = null) { if ($this->dom === null) { - return []; + return new DOMNodeList(); // @phpstan-ignore-line } if ($this->xpath === null) { $this->xpath = new DOMXpath($this->dom); } - if (null === $contextNode) { - return $this->xpath->query($path); - } + $result = @$this->xpath->query($path, $contextNode); - return $this->xpath->query($path, $contextNode); + return empty($result) ? new DOMNodeList() : $result; // @phpstan-ignore-line } /** @@ -141,7 +146,6 @@ public function registerNamespace($prefix, $namespaceURI) * Get element. * * @param string $path - * @param DOMElement $contextNode * * @return null|DOMElement */ @@ -159,7 +163,6 @@ public function getElement($path, ?DOMElement $contextNode = null) * Get element attribute. * * @param string $attribute - * @param DOMElement $contextNode * @param string $path * * @return null|string @@ -187,7 +190,6 @@ public function getAttribute($attribute, ?DOMElement $contextNode = null, $path * Get element value. * * @param string $path - * @param DOMElement $contextNode * * @return null|string */ @@ -205,7 +207,6 @@ public function getValue($path, ?DOMElement $contextNode = null) * Count elements. * * @param string $path - * @param DOMElement $contextNode * * @return int */ @@ -220,7 +221,6 @@ public function countElements($path, ?DOMElement $contextNode = null) * Element exists. * * @param string $path - * @param DOMElement $contextNode * * @return bool */ diff --git a/src/PhpWord/Shared/ZipArchive.php b/src/PhpWord/Shared/ZipArchive.php index 74922b2c2b..f120756d8b 100644 --- a/src/PhpWord/Shared/ZipArchive.php +++ b/src/PhpWord/Shared/ZipArchive.php @@ -20,6 +20,7 @@ use PclZip; use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\Settings; +use Throwable; /** * ZipArchive wrapper. @@ -30,8 +31,8 @@ * * @method bool addFile(string $filename, string $localname = null) * @method bool addFromString(string $localname, string $contents) - * @method string getNameIndex(int $index) - * @method int locateName(string $name) + * @method false|string getNameIndex(int $index) + * @method false|int locateName(string $name) * * @since 0.10.0 */ @@ -162,13 +163,16 @@ public function open($filename, $flags = null) * Close the active archive. * * @return bool - * - * @codeCoverageIgnore Can't find any test case. Uncomment when found. */ public function close() { if (!$this->usePclzip) { - if ($this->zip->close() === false) { + try { + $result = @$this->zip->close(); + } catch (Throwable $e) { + $result = false; + } + if ($result === false) { throw new Exception("Could not close zip file {$this->filename}: "); } } @@ -396,7 +400,7 @@ public function pclzipGetNameIndex($index) * * @param string $filename Filename for the file in zip archive * - * @return int + * @return false|int */ public function pclzipLocateName($filename) { diff --git a/src/PhpWord/Style.php b/src/PhpWord/Style.php index 23107ef535..ea039622df 100644 --- a/src/PhpWord/Style.php +++ b/src/PhpWord/Style.php @@ -39,7 +39,7 @@ class Style * Add paragraph style. * * @param string $styleName - * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $styles + * @param AbstractStyle|array $styles * * @return \PhpOffice\PhpWord\Style\Paragraph */ @@ -52,8 +52,8 @@ public static function addParagraphStyle($styleName, $styles) * Add font style. * * @param string $styleName - * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $fontStyle - * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $paragraphStyle + * @param AbstractStyle|array $fontStyle + * @param AbstractStyle|array $paragraphStyle * * @return \PhpOffice\PhpWord\Style\Font */ @@ -66,7 +66,7 @@ public static function addFontStyle($styleName, $fontStyle, $paragraphStyle = nu * Add link style. * * @param string $styleName - * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $styles + * @param AbstractStyle|array $styles * * @return \PhpOffice\PhpWord\Style\Font */ @@ -79,7 +79,7 @@ public static function addLinkStyle($styleName, $styles) * Add numbering style. * * @param string $styleName - * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $styleValues + * @param AbstractStyle|array $styleValues * * @return \PhpOffice\PhpWord\Style\Numbering * @@ -94,8 +94,8 @@ public static function addNumberingStyle($styleName, $styleValues) * Add title style. * * @param null|int $depth Provide null to set title font - * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $fontStyle - * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $paragraphStyle + * @param AbstractStyle|array $fontStyle + * @param AbstractStyle|array $paragraphStyle * * @return \PhpOffice\PhpWord\Style\Font */ @@ -149,7 +149,7 @@ public static function resetStyles(): void /** * Set default paragraph style. * - * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $styles Paragraph style definition + * @param AbstractStyle|array $styles Paragraph style definition * * @return \PhpOffice\PhpWord\Style\Paragraph */ @@ -161,7 +161,7 @@ public static function setDefaultParagraphStyle($styles) /** * Get all styles. * - * @return \PhpOffice\PhpWord\Style\AbstractStyle[] + * @return AbstractStyle[] */ public static function getStyles() { @@ -173,7 +173,7 @@ public static function getStyles() * * @param string $styleName * - * @return \PhpOffice\PhpWord\Style\AbstractStyle Paragraph|Font|Table|Numbering + * @return ?AbstractStyle Paragraph|Font|Table|Numbering */ public static function getStyle($styleName) { @@ -190,10 +190,10 @@ public static function getStyle($styleName) * The $styleValues could be an array or object * * @param string $name - * @param \PhpOffice\PhpWord\Style\AbstractStyle $style - * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $value + * @param AbstractStyle $style + * @param AbstractStyle|array $value * - * @return \PhpOffice\PhpWord\Style\AbstractStyle + * @return AbstractStyle */ private static function setStyleValues($name, $style, $value = null) { diff --git a/src/PhpWord/Style/AbstractStyle.php b/src/PhpWord/Style/AbstractStyle.php index a4be670dac..4e5def618d 100644 --- a/src/PhpWord/Style/AbstractStyle.php +++ b/src/PhpWord/Style/AbstractStyle.php @@ -30,7 +30,7 @@ abstract class AbstractStyle /** * Style name. * - * @var string + * @var ?string */ protected $styleName; @@ -62,7 +62,7 @@ abstract class AbstractStyle /** * Get style name. * - * @return string + * @return ?string */ public function getStyleName() { @@ -161,7 +161,7 @@ public function getChildStyleValue($substyleObject, $substyleProperty) * Check if the set method is exists. Throws an exception? * * @param string $key - * @param string $value + * @param array|int|string $value * * @return self */ @@ -202,10 +202,10 @@ public function setStyleByArray($values = []) /** * Set default for null and empty value. * - * @param string $value (was: mixed) - * @param string $default (was: mixed) + * @param ?string $value + * @param string $default * - * @return string (was: mixed) + * @return string */ protected function setNonEmptyVal($value, $default) { @@ -253,7 +253,7 @@ protected function setNumericVal($value, $default = null) /** * Set integer value: Convert string that contains only numeric into integer. * - * @param null|int $value + * @param null|float|int|string $value * @param null|int $default * * @return null|int diff --git a/src/PhpWord/Style/Border.php b/src/PhpWord/Style/Border.php index 2ee69be56f..e2a56c5d21 100644 --- a/src/PhpWord/Style/Border.php +++ b/src/PhpWord/Style/Border.php @@ -22,6 +22,8 @@ */ class Border extends AbstractStyle { + const DEFAULT_MARGIN = 1440; // In twips. + /** * Border Top Size. * @@ -32,7 +34,7 @@ class Border extends AbstractStyle /** * Border Top Color. * - * @var string + * @var null|string */ protected $borderTopColor; @@ -53,7 +55,7 @@ class Border extends AbstractStyle /** * Border Left Color. * - * @var string + * @var null|string */ protected $borderLeftColor; @@ -74,7 +76,7 @@ class Border extends AbstractStyle /** * Border Right Color. * - * @var string + * @var null|string */ protected $borderRightColor; @@ -95,7 +97,7 @@ class Border extends AbstractStyle /** * Border Bottom Color. * - * @var string + * @var null|string */ protected $borderBottomColor; @@ -106,6 +108,34 @@ class Border extends AbstractStyle */ protected $borderBottomStyle; + /** + * Top margin spacing. + * + * @var float|int + */ + protected $marginTop = self::DEFAULT_MARGIN; + + /** + * Left margin spacing. + * + * @var float|int + */ + protected $marginLeft = self::DEFAULT_MARGIN; + + /** + * Right margin spacing. + * + * @var float|int + */ + protected $marginRight = self::DEFAULT_MARGIN; + + /** + * Bottom margin spacing. + * + * @var float|int + */ + protected $marginBottom = self::DEFAULT_MARGIN; + /** * Get border size. * @@ -141,7 +171,7 @@ public function setBorderSize($value = null) /** * Get border color. * - * @return string[] + * @return array<null|string> */ public function getBorderColor() { @@ -156,7 +186,7 @@ public function getBorderColor() /** * Set border color. * - * @param string $value + * @param null|string $value * * @return self */ @@ -229,7 +259,7 @@ public function setBorderTopSize($value = null) /** * Get border top color. * - * @return string + * @return null|string */ public function getBorderTopColor() { @@ -239,7 +269,7 @@ public function getBorderTopColor() /** * Set border top color. * - * @param string $value + * @param null|string $value * * @return self */ @@ -301,7 +331,7 @@ public function setBorderLeftSize($value = null) /** * Get border left color. * - * @return string + * @return null|string */ public function getBorderLeftColor() { @@ -311,7 +341,7 @@ public function getBorderLeftColor() /** * Set border left color. * - * @param string $value + * @param null|string $value * * @return self */ @@ -373,7 +403,7 @@ public function setBorderRightSize($value = null) /** * Get border right color. * - * @return string + * @return null|string */ public function getBorderRightColor() { @@ -383,7 +413,7 @@ public function getBorderRightColor() /** * Set border right color. * - * @param string $value + * @param null|string $value * * @return self */ @@ -445,7 +475,7 @@ public function setBorderBottomSize($value = null) /** * Get border bottom color. * - * @return string + * @return null|string */ public function getBorderBottomColor() { @@ -455,7 +485,7 @@ public function getBorderBottomColor() /** * Set border bottom color. * - * @param string $value + * @param null|string $value * * @return self */ @@ -501,4 +531,100 @@ public function hasBorder() return $borders !== array_filter($borders, 'is_null'); } + + /** + * Get Margin Top. + * + * @return float|int + */ + public function getMarginTop() + { + return $this->marginTop; + } + + /** + * Set Margin Top. + * + * @param float|int $value + * + * @return self + */ + public function setMarginTop($value = null) + { + $this->marginTop = $this->setNumericVal($value, self::DEFAULT_MARGIN); + + return $this; + } + + /** + * Get Margin Left. + * + * @return float|int + */ + public function getMarginLeft() + { + return $this->marginLeft; + } + + /** + * Set Margin Left. + * + * @param float|int $value + * + * @return self + */ + public function setMarginLeft($value = null) + { + $this->marginLeft = $this->setNumericVal($value, self::DEFAULT_MARGIN); + + return $this; + } + + /** + * Get Margin Right. + * + * @return float|int + */ + public function getMarginRight() + { + return $this->marginRight; + } + + /** + * Set Margin Right. + * + * @param float|int $value + * + * @return self + */ + public function setMarginRight($value = null) + { + $this->marginRight = $this->setNumericVal($value, self::DEFAULT_MARGIN); + + return $this; + } + + /** + * Get Margin Bottom. + * + * @return float|int + */ + public function getMarginBottom() + { + return $this->marginBottom; + } + + /** + * Set Margin Bottom. + * + * @param float|int $value + * + * @return self + */ + public function setMarginBottom($value = null) + { + $this->marginBottom = $this->setNumericVal($value, self::DEFAULT_MARGIN); + + return $this; + } } diff --git a/src/PhpWord/Style/Cell.php b/src/PhpWord/Style/Cell.php index 0f05092b69..0888d52083 100644 --- a/src/PhpWord/Style/Cell.php +++ b/src/PhpWord/Style/Cell.php @@ -69,7 +69,7 @@ class Cell extends Border /** * Vertical align (top, center, both, bottom). * - * @var string + * @var null|string */ private $vAlign; @@ -93,7 +93,7 @@ class Cell extends Border * - restart: Start/restart merged region * - continue: Continue merged region * - * @var string + * @var null|string */ private $vMerge; @@ -107,7 +107,7 @@ class Cell extends Border /** * Width. * - * @var int + * @var ?int */ private $width; @@ -118,10 +118,17 @@ class Cell extends Border */ private $unit = TblWidth::TWIP; + /** + * Prevent text from wrapping in the cell. + * + * @var bool + */ + private $noWrap = true; + /** * Get vertical align. * - * @return string + * @return null|string */ public function getVAlign() { @@ -131,12 +138,18 @@ public function getVAlign() /** * Set vertical align. * - * @param string $value + * @param null|string $value * * @return self */ public function setVAlign($value = null) { + if ($value === null) { + $this->vAlign = null; + + return $this; + } + VerticalJc::validate($value); $this->vAlign = $this->setEnumVal($value, VerticalJc::values(), $this->vAlign); @@ -162,7 +175,14 @@ public function getTextDirection() */ public function setTextDirection($value = null) { - $enum = [self::TEXT_DIR_BTLR, self::TEXT_DIR_TBRL]; + $enum = [ + self::TEXT_DIR_BTLR, + self::TEXT_DIR_TBRL, + self::TEXT_DIR_LRTB, + self::TEXT_DIR_LRTBV, + self::TEXT_DIR_TBRLV, + self::TEXT_DIR_TBLRV, + ]; $this->textDirection = $this->setEnumVal($value, $enum, $this->textDirection); return $this; @@ -221,7 +241,7 @@ public function setGridSpan($value = null) /** * Get vertical merge (rowspan). * - * @return string + * @return null|string */ public function getVMerge() { @@ -231,12 +251,18 @@ public function getVMerge() /** * Set vertical merge (rowspan). * - * @param string $value + * @param null|string $value * * @return self */ public function setVMerge($value = null) { + if ($value === null) { + $this->vMerge = null; + + return $this; + } + $enum = [self::VMERGE_RESTART, self::VMERGE_CONTINUE]; $this->vMerge = $this->setEnumVal($value, $enum, $this->vMerge); @@ -270,7 +296,7 @@ public function setShading($value = null) /** * Get cell width. * - * @return int + * @return ?int */ public function getWidth() { @@ -286,7 +312,7 @@ public function getWidth() */ public function setWidth($value) { - $this->setIntVal($value); + $this->width = $this->setIntVal($value); return $this; } @@ -312,4 +338,22 @@ public function setUnit($value) return $this; } + + /** + * Set noWrap. + */ + public function setNoWrap(bool $value): self + { + $this->noWrap = $this->setBoolVal($value, true); + + return $this; + } + + /** + * Get noWrap. + */ + public function getNoWrap(): bool + { + return $this->noWrap; + } } diff --git a/src/PhpWord/Style/Font.php b/src/PhpWord/Style/Font.php index d0cc2503e1..a4580bc87b 100644 --- a/src/PhpWord/Style/Font.php +++ b/src/PhpWord/Style/Font.php @@ -17,6 +17,9 @@ namespace PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Shared\Validate; + /** * Font style. */ @@ -230,7 +233,7 @@ class Font extends AbstractStyle /** * Right to left languages. * - * @var bool + * @var ?bool */ private $rtl; @@ -245,7 +248,7 @@ class Font extends AbstractStyle /** * Languages. * - * @var \PhpOffice\PhpWord\Style\Language + * @var null|\PhpOffice\PhpWord\Style\Language */ private $lang; @@ -267,6 +270,20 @@ class Font extends AbstractStyle */ private $position; + /** + * Preservation of white space in html. + * + * @var string Value used for css white-space + */ + private $whiteSpace = ''; + + /** + * Generic font as fallback for html. + * + * @var string generic font name + */ + private $fallbackFont = ''; + /** * Create new font style. * @@ -288,7 +305,7 @@ public function __construct($type = 'text', $paragraph = null) */ public function getStyleValues() { - $styles = [ + return [ 'name' => $this->getStyleName(), 'basic' => [ 'name' => $this->getName(), @@ -319,9 +336,9 @@ public function getStyleValues() 'rtl' => $this->isRTL(), 'shading' => $this->getShading(), 'lang' => $this->getLang(), + 'whiteSpace' => $this->getWhiteSpace(), + 'fallbackFont' => $this->getFallbackFont(), ]; - - return $styles; } /** @@ -548,10 +565,8 @@ public function setSubScript($value = true) /** * Get strikethrough. - * - * @return bool */ - public function isStrikethrough() + public function isStrikethrough(): ?bool { return $this->strikethrough; } @@ -560,20 +575,16 @@ public function isStrikethrough() * Set strikethrough. * * @param bool $value - * - * @return self */ - public function setStrikethrough($value = true) + public function setStrikethrough($value = true): self { return $this->setPairedVal($this->strikethrough, $this->doubleStrikethrough, $value); } /** * Get double strikethrough. - * - * @return bool */ - public function isDoubleStrikethrough() + public function isDoubleStrikethrough(): ?bool { return $this->doubleStrikethrough; } @@ -582,10 +593,8 @@ public function isDoubleStrikethrough() * Set double strikethrough. * * @param bool $value - * - * @return self */ - public function setDoubleStrikethrough($value = true) + public function setDoubleStrikethrough($value = true): self { return $this->setPairedVal($this->doubleStrikethrough, $this->strikethrough, $value); } @@ -827,17 +836,17 @@ public function setParagraph($value = null) /** * Get rtl. * - * @return bool + * @return ?bool */ public function isRTL() { - return $this->rtl; + return $this->rtl ?? Settings::isDefaultRtl(); } /** * Set rtl. * - * @param bool $value + * @param ?bool $value * * @return self */ @@ -875,7 +884,7 @@ public function setShading($value = null) /** * Get language. * - * @return \PhpOffice\PhpWord\Style\Language + * @return null|\PhpOffice\PhpWord\Style\Language */ public function getLang() { @@ -946,4 +955,44 @@ public function setPosition($value = null) return $this; } + + /** + * Set html css white-space value. It is expected that only pre-wrap and normal (default) are useful. + * + * @param null|string $value Should be one of pre-wrap, normal, nowrap, pre, pre-line, initial, inherit + */ + public function setWhiteSpace(?string $value): self + { + $this->whiteSpace = Validate::validateCSSWhiteSpace($value); + + return $this; + } + + /** + * Get html css white-space value. + */ + public function getWhiteSpace(): string + { + return $this->whiteSpace; + } + + /** + * Set generic font for fallback for html. + * + * @param string $value generic font name + */ + public function setFallbackFont(?string $value): self + { + $this->fallbackFont = Validate::validateCSSGenericFont($value); + + return $this; + } + + /** + * Get html fallback generic font. + */ + public function getFallbackFont(): string + { + return $this->fallbackFont; + } } diff --git a/src/PhpWord/Style/Indentation.php b/src/PhpWord/Style/Indentation.php index 87277b4b81..42c6e11845 100644 --- a/src/PhpWord/Style/Indentation.php +++ b/src/PhpWord/Style/Indentation.php @@ -44,7 +44,7 @@ class Indentation extends AbstractStyle * * @var float|int */ - private $firstLine; + private $firstLine = 0; /** * Indentation removed from first line (twip). @@ -80,7 +80,7 @@ public function getLeft() * * @return self */ - public function setLeft($value = null) + public function setLeft($value) { $this->left = $this->setNumericVal($value, $this->left); @@ -104,7 +104,7 @@ public function getRight() * * @return self */ - public function setRight($value = null) + public function setRight($value) { $this->right = $this->setNumericVal($value, $this->right); @@ -128,7 +128,7 @@ public function getFirstLine() * * @return self */ - public function setFirstLine($value = null) + public function setFirstLine($value) { $this->firstLine = $this->setNumericVal($value, $this->firstLine); diff --git a/src/PhpWord/Style/Language.php b/src/PhpWord/Style/Language.php index fd63eb143e..18e7f76e77 100644 --- a/src/PhpWord/Style/Language.php +++ b/src/PhpWord/Style/Language.php @@ -40,18 +40,27 @@ final class Language extends AbstractStyle const FR_BE = 'fr-BE'; const FR_BE_ID = 2060; + const FR_CH = 'fr-CH'; + const FR_CH_ID = 4108; + const ES_ES = 'es-ES'; const ES_ES_ID = 3082; const DE_DE = 'de-DE'; const DE_DE_ID = 1031; + const DE_CH = 'de-CH'; + const DE_CH_ID = 2055; + const HE_IL = 'he-IL'; const HE_IL_ID = 1037; const IT_IT = 'it-IT'; const IT_IT_ID = 1040; + const IT_CH = 'it-CH'; + const IT_CH_ID = 2064; + const JA_JP = 'ja-JP'; const JA_JP_ID = 1041; @@ -70,6 +79,9 @@ final class Language extends AbstractStyle const NL_NL = 'nl-NL'; const NL_NL_ID = 1043; + const SV_SE = 'sv-SE'; + const SV_SE_ID = 1053; + const UK_UA = 'uk-UA'; const UK_UA_ID = 1058; @@ -244,7 +256,9 @@ private function validateLocale($locale) if ($locale !== null && strlen($locale) === 2) { return strtolower($locale) . '-' . strtoupper($locale); } - + if ($locale === 'und') { + return 'en-EN'; + } if ($locale !== null && $locale !== 'zxx' && strstr($locale, '-') === false) { throw new InvalidArgumentException($locale . ' is not a valid language code'); } diff --git a/src/PhpWord/Style/Numbering.php b/src/PhpWord/Style/Numbering.php index 0efb088dd4..c3af1fc352 100644 --- a/src/PhpWord/Style/Numbering.php +++ b/src/PhpWord/Style/Numbering.php @@ -54,22 +54,16 @@ class Numbering extends AbstractStyle /** * Get Id. - * - * @return int */ - public function getNumId() + public function getNumId(): ?int { return $this->numId; } /** * Set Id. - * - * @param int $value - * - * @return self */ - public function setNumId($value) + public function setNumId(int $value): self { $this->numId = $this->setIntVal($value, $this->numId); @@ -78,22 +72,16 @@ public function setNumId($value) /** * Get multilevel type. - * - * @return string */ - public function getType() + public function getType(): ?string { return $this->type; } /** * Set multilevel type. - * - * @param string $value - * - * @return self */ - public function setType($value) + public function setType(string $value): self { $enum = ['singleLevel', 'multilevel', 'hybridMultilevel']; $this->type = $this->setEnumVal($value, $enum, $this->type); @@ -106,19 +94,15 @@ public function setType($value) * * @return NumberingLevel[] */ - public function getLevels() + public function getLevels(): array { return $this->levels; } /** * Set multilevel type. - * - * @param array $values - * - * @return self */ - public function setLevels($values) + public function setLevels(array $values): self { if (is_array($values)) { foreach ($values as $key => $value) { diff --git a/src/PhpWord/Style/Paragraph.php b/src/PhpWord/Style/Paragraph.php index e22016517b..c77617403d 100644 --- a/src/PhpWord/Style/Paragraph.php +++ b/src/PhpWord/Style/Paragraph.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Exception\InvalidStyleException; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Text; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\TextAlignment; @@ -99,7 +100,7 @@ class Paragraph extends Border /** * Text line height. * - * @var int + * @var null|float|int */ private $lineHeight; @@ -169,9 +170,9 @@ class Paragraph extends Border /** * Right to Left Paragraph Layout. * - * @var bool + * @var ?bool */ - private $bidi = false; + private $bidi; /** * Vertical Character Alignment on Line. @@ -321,9 +322,9 @@ public function setNext($value = null) } /** - * Get shading. + * Get indentation. * - * @return \PhpOffice\PhpWord\Style\Indentation + * @return null|\PhpOffice\PhpWord\Style\Indentation */ public function getIndentation() { @@ -419,7 +420,7 @@ public function setSpace($value = null) /** * Get space before paragraph. * - * @return int + * @return null|float|int */ public function getSpaceBefore() { @@ -429,7 +430,7 @@ public function getSpaceBefore() /** * Set space before paragraph. * - * @param int $value + * @param null|float|int $value * * @return self */ @@ -441,7 +442,7 @@ public function setSpaceBefore($value = null) /** * Get space after paragraph. * - * @return int + * @return null|float|int */ public function getSpaceAfter() { @@ -451,7 +452,7 @@ public function getSpaceAfter() /** * Set space after paragraph. * - * @param int $value + * @param null|float|int $value * * @return self */ @@ -463,7 +464,7 @@ public function setSpaceAfter($value = null) /** * Get spacing between lines. * - * @return float|int + * @return null|float|int */ public function getSpacing() { @@ -473,7 +474,7 @@ public function getSpacing() /** * Set spacing between lines. * - * @param float|int $value + * @param null|float|int $value * * @return self */ @@ -507,7 +508,7 @@ public function setSpacingLineRule($value) /** * Get line height. * - * @return float|int + * @return null|float|int */ public function getLineHeight() { @@ -759,17 +760,17 @@ public function setContextualSpacing($contextualSpacing) /** * Get bidirectional. * - * @return bool + * @return ?bool */ public function isBidi() { - return $this->bidi; + return $this->bidi ?? Settings::isDefaultRtl(); } /** * Set bidi. * - * @param bool $bidi + * @param ?bool $bidi * Set to true to write from right to left * * @return self diff --git a/src/PhpWord/Style/Section.php b/src/PhpWord/Style/Section.php index 29e06bf3cf..3b08aa5f38 100644 --- a/src/PhpWord/Style/Section.php +++ b/src/PhpWord/Style/Section.php @@ -40,7 +40,6 @@ class Section extends Border */ const DEFAULT_WIDTH = 11905.511811024; // In twips. const DEFAULT_HEIGHT = 16837.79527559; // In twips. - const DEFAULT_MARGIN = 1440; // In twips. const DEFAULT_GUTTER = 0; // In twips. const DEFAULT_HEADER_HEIGHT = 720; // In twips. const DEFAULT_FOOTER_HEIGHT = 720; // In twips. @@ -77,34 +76,6 @@ class Section extends Border */ private $pageSizeH = self::DEFAULT_HEIGHT; - /** - * Top margin spacing. - * - * @var float|int - */ - private $marginTop = self::DEFAULT_MARGIN; - - /** - * Left margin spacing. - * - * @var float|int - */ - private $marginLeft = self::DEFAULT_MARGIN; - - /** - * Right margin spacing. - * - * @var float|int - */ - private $marginRight = self::DEFAULT_MARGIN; - - /** - * Bottom margin spacing. - * - * @var float|int - */ - private $marginBottom = self::DEFAULT_MARGIN; - /** * Page gutter spacing. * @@ -159,7 +130,7 @@ class Section extends Border * - evenPage: Even page section break * - oddPage: Odd page section break * - * @var string + * @var ?string */ private $breakType; @@ -176,7 +147,7 @@ class Section extends Border * Vertical Text Alignment on Page * One of \PhpOffice\PhpWord\SimpleType\VerticalJc. * - * @var string + * @var ?string */ private $vAlign; @@ -224,7 +195,7 @@ public function setPaperSize($value = '') * Set Setting Value. * * @param string $key - * @param string $value + * @param array|int|string $value * * @return self */ @@ -344,102 +315,6 @@ public function setPageSizeH($value = null) return $this; } - /** - * Get Margin Top. - * - * @return float|int - */ - public function getMarginTop() - { - return $this->marginTop; - } - - /** - * Set Margin Top. - * - * @param float|int $value - * - * @return self - */ - public function setMarginTop($value = null) - { - $this->marginTop = $this->setNumericVal($value, self::DEFAULT_MARGIN); - - return $this; - } - - /** - * Get Margin Left. - * - * @return float|int - */ - public function getMarginLeft() - { - return $this->marginLeft; - } - - /** - * Set Margin Left. - * - * @param float|int $value - * - * @return self - */ - public function setMarginLeft($value = null) - { - $this->marginLeft = $this->setNumericVal($value, self::DEFAULT_MARGIN); - - return $this; - } - - /** - * Get Margin Right. - * - * @return float|int - */ - public function getMarginRight() - { - return $this->marginRight; - } - - /** - * Set Margin Right. - * - * @param float|int $value - * - * @return self - */ - public function setMarginRight($value = null) - { - $this->marginRight = $this->setNumericVal($value, self::DEFAULT_MARGIN); - - return $this; - } - - /** - * Get Margin Bottom. - * - * @return float|int - */ - public function getMarginBottom() - { - return $this->marginBottom; - } - - /** - * Set Margin Bottom. - * - * @param float|int $value - * - * @return self - */ - public function setMarginBottom($value = null) - { - $this->marginBottom = $this->setNumericVal($value, self::DEFAULT_MARGIN); - - return $this; - } - /** * Get gutter. * @@ -587,7 +462,7 @@ public function setColsSpace($value = null) /** * Get Break Type. * - * @return string + * @return ?string */ public function getBreakType() { @@ -635,7 +510,7 @@ public function setLineNumbering($value = null) /** * Get vertical alignment. * - * @return string + * @return ?string */ public function getVAlign() { diff --git a/src/PhpWord/Style/Spacing.php b/src/PhpWord/Style/Spacing.php index 7807065dd4..196ad8daa1 100644 --- a/src/PhpWord/Style/Spacing.php +++ b/src/PhpWord/Style/Spacing.php @@ -30,21 +30,21 @@ class Spacing extends AbstractStyle /** * Spacing above paragraph (twip). * - * @var float|int + * @var null|float|int */ private $before; /** * Spacing below paragraph (twip). * - * @var float|int + * @var null|float|int */ private $after; /** * Spacing between lines in paragraph (twip). * - * @var float|int + * @var null|float|int */ private $line; @@ -68,7 +68,7 @@ public function __construct($style = []) /** * Get before. * - * @return float|int + * @return null|float|int */ public function getBefore() { @@ -78,7 +78,7 @@ public function getBefore() /** * Set before. * - * @param float|int $value + * @param null|float|int $value * * @return self */ @@ -92,7 +92,7 @@ public function setBefore($value = null) /** * Get after. * - * @return float|int + * @return null|float|int */ public function getAfter() { @@ -102,7 +102,7 @@ public function getAfter() /** * Set after. * - * @param float|int $value + * @param null|float|int $value * * @return self */ @@ -116,7 +116,7 @@ public function setAfter($value = null) /** * Get line. * - * @return float|int + * @return null|float|int */ public function getLine() { @@ -126,7 +126,7 @@ public function getLine() /** * Set distance. * - * @param float|int $value + * @param null|float|int $value * * @return self */ diff --git a/src/PhpWord/Style/Table.php b/src/PhpWord/Style/Table.php index b267c7568f..3adb1a38f5 100644 --- a/src/PhpWord/Style/Table.php +++ b/src/PhpWord/Style/Table.php @@ -18,9 +18,11 @@ namespace PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\JcTable; use PhpOffice\PhpWord\SimpleType\TblWidth; +use PhpOffice\PhpWord\Style; class Table extends Border { @@ -131,7 +133,7 @@ class Table extends Border private $unit = TblWidth::AUTO; /** - * @var float|int cell spacing value + * @var null|float|int cell spacing value */ protected $cellSpacing; @@ -143,7 +145,7 @@ class Table extends Border /** * Position. * - * @var \PhpOffice\PhpWord\Style\TablePosition + * @var ?\PhpOffice\PhpWord\Style\TablePosition */ private $position; @@ -162,9 +164,9 @@ class Table extends Border * * @see http://www.datypic.com/sc/ooxml/e-w_bidiVisual-1.html * - * @var bool + * @var ?bool */ - private $bidiVisual = false; + private $bidiVisual; /** * Create new table style. @@ -188,7 +190,7 @@ public function __construct($tableStyle = null, $firstRowStyle = null) } /** - * @param float|int $cellSpacing + * @param null|float|int $cellSpacing */ public function setCellSpacing($cellSpacing = null): void { @@ -196,7 +198,7 @@ public function setCellSpacing($cellSpacing = null): void } /** - * @return float|int + * @return null|float|int */ public function getCellSpacing() { @@ -216,7 +218,7 @@ public function getFirstRow() /** * Get background. * - * @return string + * @return ?string */ public function getBgColor() { @@ -704,7 +706,7 @@ private function setTableOnlyProperty($property, $value, $isNumeric = true) /** * Get position. * - * @return \PhpOffice\PhpWord\Style\TablePosition + * @return ?\PhpOffice\PhpWord\Style\TablePosition */ public function getPosition() { @@ -726,7 +728,7 @@ public function setPosition($value = null) } /** - * @return TblWidthComplexType + * @return ?TblWidthComplexType */ public function getIndent() { @@ -768,17 +770,17 @@ public function setColumnWidths(?array $value = null): void /** * Get bidiVisual. * - * @return bool + * @return ?bool */ public function isBidiVisual() { - return $this->bidiVisual; + return $this->bidiVisual ?? Settings::isDefaultRtl(); } /** * Set bidiVisual. * - * @param bool $bidi + * @param ?bool $bidi * Set to true to visually present table as Right to Left * * @return self diff --git a/src/PhpWord/Style/TextBox.php b/src/PhpWord/Style/TextBox.php index e43e6fa231..341d93068f 100644 --- a/src/PhpWord/Style/TextBox.php +++ b/src/PhpWord/Style/TextBox.php @@ -2,10 +2,8 @@ /** * This file is part of PHPWord - A pure PHP library for reading and writing * word processing documents. - * * PHPWord is free software distributed under the terms of the GNU Lesser * General Public License version 3 as published by the Free Software Foundation. - * * For the full copyright and license information, please read the LICENSE * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. @@ -27,121 +25,128 @@ class TextBox extends Image /** * margin top. * - * @var int + * @var null|int */ private $innerMarginTop; /** * margin left. * - * @var int + * @var null|int */ private $innerMarginLeft; /** * margin right. * - * @var int + * @var null|int */ private $innerMarginRight; /** * Cell margin bottom. * - * @var int + * @var null|int */ private $innerMarginBottom; /** * border size. * - * @var int + * @var null|int */ private $borderSize; /** * border color. * - * @var string + * @var null|string */ private $borderColor; /** - * Set margin top. + * background color. * - * @param int $value + * @var null|string + */ + private $bgColor; + + /** + * Set background color. + */ + public function setBgColor(?string $value = null): void + { + $this->bgColor = $value; + } + + /** + * Get background color. + */ + public function getBgColor(): ?string + { + return $this->bgColor; + } + + /** + * Set margin top. */ - public function setInnerMarginTop($value = null): void + public function setInnerMarginTop(?int $value = null): void { $this->innerMarginTop = $value; } /** * Get margin top. - * - * @return int */ - public function getInnerMarginTop() + public function getInnerMarginTop(): ?int { return $this->innerMarginTop; } /** * Set margin left. - * - * @param int $value */ - public function setInnerMarginLeft($value = null): void + public function setInnerMarginLeft(?int $value = null): void { $this->innerMarginLeft = $value; } /** * Get margin left. - * - * @return int */ - public function getInnerMarginLeft() + public function getInnerMarginLeft(): ?int { return $this->innerMarginLeft; } /** * Set margin right. - * - * @param int $value */ - public function setInnerMarginRight($value = null): void + public function setInnerMarginRight(?int $value = null): void { $this->innerMarginRight = $value; } /** * Get margin right. - * - * @return int */ - public function getInnerMarginRight() + public function getInnerMarginRight(): ?int { return $this->innerMarginRight; } /** * Set margin bottom. - * - * @param int $value */ - public function setInnerMarginBottom($value = null): void + public function setInnerMarginBottom(?int $value = null): void { $this->innerMarginBottom = $value; } /** * Get margin bottom. - * - * @return int */ - public function getInnerMarginBottom() + public function getInnerMarginBottom(): ?int { return $this->innerMarginBottom; } @@ -149,9 +154,9 @@ public function getInnerMarginBottom() /** * Set TLRB cell margin. * - * @param int $value Margin in twips + * @param null|int $value Margin in twips */ - public function setInnerMargin($value = null): void + public function setInnerMargin(?int $value = null): void { $this->setInnerMarginTop($value); $this->setInnerMarginLeft($value); @@ -164,17 +169,15 @@ public function setInnerMargin($value = null): void * * @return int[] */ - public function getInnerMargin() + public function getInnerMargin(): array { return [$this->innerMarginLeft, $this->innerMarginTop, $this->innerMarginRight, $this->innerMarginBottom]; } /** * Has inner margin? - * - * @return bool */ - public function hasInnerMargins() + public function hasInnerMargins(): bool { $hasInnerMargins = false; $margins = $this->getInnerMargin(); @@ -191,39 +194,33 @@ public function hasInnerMargins() /** * Set border size. * - * @param int $value Size in points + * @param null|int $value Size in points */ - public function setBorderSize($value = null): void + public function setBorderSize(?int $value = null): void { $this->borderSize = $value; } /** * Get border size. - * - * @return int */ - public function getBorderSize() + public function getBorderSize(): ?int { return $this->borderSize; } /** * Set border color. - * - * @param string $value */ - public function setBorderColor($value = null): void + public function setBorderColor(?string $value = null): void { $this->borderColor = $value; } /** * Get border color. - * - * @return string */ - public function getBorderColor() + public function getBorderColor(): ?string { return $this->borderColor; } diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 1cf3242984..f6fe1d8887 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -27,6 +27,7 @@ use PhpOffice\PhpWord\Shared\Text; use PhpOffice\PhpWord\Shared\XMLWriter; use PhpOffice\PhpWord\Shared\ZipArchive; +use Throwable; use XSLTProcessor; class TemplateProcessor @@ -94,6 +95,10 @@ class TemplateProcessor */ protected $tempDocumentNewImages = []; + protected static $macroOpeningChars = '${'; + + protected static $macroClosingChars = '}'; + /** * @since 0.12.0 Throws CreateTemporaryFileException and CopyFileException instead of Exception * @@ -131,6 +136,18 @@ public function __construct($documentTemplate) $this->tempDocumentContentTypes = $this->zipClass->getFromName($this->getDocumentContentTypesName()); } + public function __destruct() + { + // ZipClass + if ($this->zipClass) { + try { + $this->zipClass->close(); + } catch (Throwable $e) { + // Nothing to do here. + } + } + } + /** * Expose zip class. * @@ -238,30 +255,25 @@ public function applyXslStyleSheet($xslDomDocument, $xslOptions = [], $xslOption */ protected static function ensureMacroCompleted($macro) { - if (substr($macro, 0, 2) !== '${' && substr($macro, -1) !== '}') { - $macro = '${' . $macro . '}'; + if (substr($macro, 0, 2) !== self::$macroOpeningChars && substr($macro, -1) !== self::$macroClosingChars) { + $macro = self::$macroOpeningChars . $macro . self::$macroClosingChars; } return $macro; } /** - * @param string $subject + * @param ?string $subject * * @return string */ protected static function ensureUtf8Encoded($subject) { - if (!Text::isUTF8($subject) && null !== $subject) { - $subject = utf8_encode($subject); - } - - return (null !== $subject) ? $subject : ''; + return $subject ? Text::toUTF8($subject) : ''; } /** * @param string $search - * @param \PhpOffice\PhpWord\Element\AbstractElement $complexType */ public function setComplexValue($search, Element\AbstractElement $complexType): void { @@ -289,7 +301,6 @@ public function setComplexValue($search, Element\AbstractElement $complexType): /** * @param string $search - * @param \PhpOffice\PhpWord\Element\AbstractElement $complexType */ public function setComplexBlock($search, Element\AbstractElement $complexType): void { @@ -334,6 +345,15 @@ public function setValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_ $replace = $xmlEscaper->escape($replace); } + // convert carriage returns + if (is_array($replace)) { + foreach ($replace as &$item) { + $item = $this->replaceCarriageReturns($item); + } + } else { + $replace = $this->replaceCarriageReturns($replace); + } + $this->tempDocumentHeaders = $this->setValueForPart($search, $replace, $this->tempDocumentHeaders, $limit); $this->tempDocumentMainPart = $this->setValueForPart($search, $replace, $this->tempDocumentMainPart, $limit); $this->tempDocumentFooters = $this->setValueForPart($search, $replace, $this->tempDocumentFooters, $limit); @@ -349,6 +369,27 @@ public function setValues(array $values): void } } + public function setCheckbox(string $search, bool $checked): void + { + $search = static::ensureMacroCompleted($search); + $blockType = 'w:sdt'; + + $where = $this->findContainingXmlBlockForMacro($search, $blockType); + if (!is_array($where)) { + return; + } + + $block = $this->getSlice($where['start'], $where['end']); + + $val = $checked ? '1' : '0'; + $block = preg_replace('/(<w14:checked w14:val=)".*?"(\/>)/', '$1"' . $val . '"$2', $block); + + $text = $checked ? '☒' : '☐'; + $block = preg_replace('/(<w:t>).*?(<\/w:t>)/', '$1' . $text . '$2', $block); + + $this->replaceXmlBlock($search, $block, $blockType); + } + /** * @param string $search */ @@ -433,7 +474,7 @@ private function chooseImageDimension($baseValue, $inlineValue, $defaultValue) if (null === $value && isset($inlineValue)) { $value = $inlineValue; } - if (!preg_match('/^([0-9]*(cm|mm|in|pt|pc|px|%|em|ex|)|auto)$/i', $value ?? '')) { + if (!preg_match('/^([0-9\.]*(cm|mm|in|pt|pc|px|%|em|ex|)|auto)$/i', $value ?? '')) { $value = null; } if (null === $value) { @@ -621,13 +662,13 @@ public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEM foreach (array_keys($this->tempDocumentHeaders) as $headerIndex) { $searchParts[$this->getHeaderName($headerIndex)] = &$this->tempDocumentHeaders[$headerIndex]; } - foreach (array_keys($this->tempDocumentFooters) as $headerIndex) { - $searchParts[$this->getFooterName($headerIndex)] = &$this->tempDocumentFooters[$headerIndex]; + foreach (array_keys($this->tempDocumentFooters) as $footerIndex) { + $searchParts[$this->getFooterName($footerIndex)] = &$this->tempDocumentFooters[$footerIndex]; } // define templates // result can be verified via "Open XML SDK 2.5 Productivity Tool" (http://www.microsoft.com/en-us/download/details.aspx?id=30425) - $imgTpl = '<w:pict><v:shape type="#_x0000_t75" style="width:{WIDTH};height:{HEIGHT}" stroked="f"><v:imagedata r:id="{RID}" o:title=""/></v:shape></w:pict>'; + $imgTpl = '<w:pict><v:shape type="#_x0000_t75" style="width:{WIDTH};height:{HEIGHT}" stroked="f" filled="f"><v:imagedata r:id="{RID}" o:title=""/></v:shape></w:pict>'; $i = 0; foreach ($searchParts as $partFileName => &$partContent) { @@ -759,6 +800,70 @@ public function cloneRow($search, $numberOfClones): void $this->tempDocumentMainPart = $result; } + /** + * Delete a table row in a template document. + */ + public function deleteRow(string $search): void + { + if (self::$macroOpeningChars !== substr($search, 0, 2) && self::$macroClosingChars !== substr($search, -1)) { + $search = self::$macroOpeningChars . $search . self::$macroClosingChars; + } + + $tagPos = strpos($this->tempDocumentMainPart, $search); + if (!$tagPos) { + throw new Exception(sprintf('Can not delete row %s, template variable not found or variable contains markup.', $search)); + } + + $tableStart = $this->findTableStart($tagPos); + $tableEnd = $this->findTableEnd($tagPos); + $xmlTable = $this->getSlice($tableStart, $tableEnd); + + if (substr_count($xmlTable, '<w:tr') === 1) { + $this->tempDocumentMainPart = $this->getSlice(0, $tableStart) . $this->getSlice($tableEnd); + + return; + } + + $rowStart = $this->findRowStart($tagPos); + $rowEnd = $this->findRowEnd($tagPos); + $xmlRow = $this->getSlice($rowStart, $rowEnd); + + $this->tempDocumentMainPart = $this->getSlice(0, $rowStart) . $this->getSlice($rowEnd); + + // Check if there's a cell spanning multiple rows. + if (preg_match('#<w:vMerge w:val="restart"/>#', $xmlRow)) { + $extraRowStart = $rowStart; + while (true) { + $extraRowStart = $this->findRowStart($extraRowStart + 1); + $extraRowEnd = $this->findRowEnd($extraRowStart + 1); + + // If extraRowEnd is lower then 7, there was no next row found. + if ($extraRowEnd < 7) { + break; + } + + // If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row. + $tmpXmlRow = $this->getSlice($extraRowStart, $extraRowEnd); + if (!preg_match('#<w:vMerge/>#', $tmpXmlRow) && + !preg_match('#<w:vMerge w:val="continue" />#', $tmpXmlRow) + ) { + break; + } + + $tableStart = $this->findTableStart($extraRowEnd + 1); + $tableEnd = $this->findTableEnd($extraRowEnd + 1); + $xmlTable = $this->getSlice($tableStart, $tableEnd); + if (substr_count($xmlTable, '<w:tr') === 1) { + $this->tempDocumentMainPart = $this->getSlice(0, $tableStart) . $this->getSlice($tableEnd); + + return; + } + + $this->tempDocumentMainPart = $this->getSlice(0, $extraRowStart) . $this->getSlice($extraRowEnd); + } + } + } + /** * Clones a table row and populates it's values from a two-dimensional array in a template document. * @@ -792,8 +897,12 @@ public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVaria { $xmlBlock = null; $matches = []; + $escapedMacroOpeningChars = self::$macroOpeningChars; + $escapedMacroClosingChars = self::$macroClosingChars; preg_match( - '/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\${' . $blockname . '}<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\${\/' . $blockname . '}<\/w:.*?p>)/is', + //'/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\{{' . $blockname . '}<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\{{\/' . $blockname . '}<\/w:.*?p>)/is', + '/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\\' . $escapedMacroOpeningChars . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\\' . $escapedMacroOpeningChars . '\/' . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>)/is', + //'/(.*((?s)<w:p\b(?:(?!<w:p\b).)*?\\'. $escapedMacroOpeningChars . $blockname . '}<\/w:.*?p>))(.*)((?s)<w:p\b(?:(?!<w:p\b).)[^$]*?\\'.$escapedMacroOpeningChars.'\/' . $blockname . '}<\/w:.*?p>)/is', $this->tempDocumentMainPart, $matches ); @@ -832,8 +941,10 @@ public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVaria public function replaceBlock($blockname, $replacement): void { $matches = []; + $escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars); + $escapedMacroClosingChars = preg_quote(self::$macroClosingChars); preg_match( - '/(<\?xml.*)(<w:p.*>\${' . $blockname . '}<\/w:.*?p>)(.*)(<w:p.*\${\/' . $blockname . '}<\/w:.*?p>)/is', + '/(<\?xml.*)(<w:p.*>' . $escapedMacroOpeningChars . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>)(.*)(<w:p.*' . $escapedMacroOpeningChars . '\/' . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>)/is', $this->tempDocumentMainPart, $matches ); @@ -949,8 +1060,12 @@ public function saveAs($fileName): void */ protected function fixBrokenMacros($documentPart) { + $brokenMacroOpeningChars = substr(self::$macroOpeningChars, 0, 1); + $endMacroOpeningChars = substr(self::$macroOpeningChars, 1); + $macroClosingChars = self::$macroClosingChars; + return preg_replace_callback( - '/\$(?:\{|[^{$]*\>\{)[^}$]*\}/U', + '/\\' . $brokenMacroOpeningChars . '(?:\\' . $endMacroOpeningChars . '|[^{$]*\>\{)[^' . $macroClosingChars . '$]*\}/U', function ($match) { return strip_tags($match[0]); }, @@ -963,7 +1078,7 @@ function ($match) { * * @param mixed $search * @param mixed $replace - * @param string $documentPartXML + * @param array<int, string>|string $documentPartXML * @param int $limit * * @return string @@ -989,7 +1104,10 @@ protected function setValueForPart($search, $replace, $documentPartXML, $limit) protected function getVariablesForPart($documentPartXML) { $matches = []; - preg_match_all('/\$\{(.*?)}/i', $documentPartXML, $matches); + $escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars); + $escapedMacroClosingChars = preg_quote(self::$macroClosingChars); + + preg_match_all("/$escapedMacroOpeningChars(.*?)$escapedMacroClosingChars/i", $documentPartXML, $matches); return $matches[1]; } @@ -1079,6 +1197,39 @@ protected function getDocumentContentTypesName() return '[Content_Types].xml'; } + /** + * Find the start position of the nearest table before $offset. + */ + private function findTableStart(int $offset): int + { + $rowStart = strrpos( + $this->tempDocumentMainPart, + '<w:tbl ', + ((strlen($this->tempDocumentMainPart) - $offset) * -1) + ); + + if (!$rowStart) { + $rowStart = strrpos( + $this->tempDocumentMainPart, + '<w:tbl>', + ((strlen($this->tempDocumentMainPart) - $offset) * -1) + ); + } + if (!$rowStart) { + throw new Exception('Can not find the start position of the table.'); + } + + return $rowStart; + } + + /** + * Find the end position of the nearest table row after $offset. + */ + private function findTableEnd(int $offset): int + { + return strpos($this->tempDocumentMainPart, '</w:tbl>', $offset) + 7; + } + /** * Find the start position of the nearest table row before $offset. * @@ -1141,15 +1292,26 @@ protected function getSlice($startPosition, $endPosition = 0) protected function indexClonedVariables($count, $xmlBlock) { $results = []; + $escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars); + $escapedMacroClosingChars = preg_quote(self::$macroClosingChars); + for ($i = 1; $i <= $count; ++$i) { - $results[] = preg_replace('/\$\{([^:]*?)(:.*?)?\}/', '\${\1#' . $i . '\2}', $xmlBlock); + $results[] = preg_replace("/$escapedMacroOpeningChars([^:]*?)(:.*?)?$escapedMacroClosingChars/", self::$macroOpeningChars . '\1#' . $i . '\2' . self::$macroClosingChars, $xmlBlock); } return $results; } /** - * Raplaces variables with values from array, array keys are the variable names. + * Replace carriage returns with xml. + */ + public function replaceCarriageReturns(string $string): string + { + return str_replace(["\r\n", "\r", "\n"], '</w:t><w:br/><w:t>', $string); + } + + /** + * Replaces variables with values from array, array keys are the variable names. * * @param array $variableReplacements * @param string $xmlBlock @@ -1297,7 +1459,7 @@ protected function splitTextIntoTexts($text) } $unformattedText = preg_replace('/>\s+</', '><', $text); - $result = str_replace(['${', '}'], ['</w:t></w:r><w:r>' . $extractedStyle . '<w:t xml:space="preserve">${', '}</w:t></w:r><w:r>' . $extractedStyle . '<w:t xml:space="preserve">'], $unformattedText); + $result = str_replace([self::$macroOpeningChars, self::$macroClosingChars], ['</w:t></w:r><w:r>' . $extractedStyle . '<w:t xml:space="preserve">' . self::$macroOpeningChars, self::$macroClosingChars . '</w:t></w:r><w:r>' . $extractedStyle . '<w:t xml:space="preserve">'], $unformattedText); return str_replace(['<w:r>' . $extractedStyle . '<w:t xml:space="preserve"></w:t></w:r>', '<w:r><w:t xml:space="preserve"></w:t></w:r>', '<w:t>'], ['', '', '<w:t xml:space="preserve">'], $result); } @@ -1311,6 +1473,30 @@ protected function splitTextIntoTexts($text) */ protected function textNeedsSplitting($text) { - return preg_match('/[^>]\${|}[^<]/i', $text) == 1; + $escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars); + $escapedMacroClosingChars = preg_quote(self::$macroClosingChars); + + return 1 === preg_match('/[^>]' . $escapedMacroOpeningChars . '|' . $escapedMacroClosingChars . '[^<]/i', $text); + } + + public function setMacroOpeningChars(string $macroOpeningChars): void + { + self::$macroOpeningChars = $macroOpeningChars; + } + + public function setMacroClosingChars(string $macroClosingChars): void + { + self::$macroClosingChars = $macroClosingChars; + } + + public function setMacroChars(string $macroOpeningChars, string $macroClosingChars): void + { + self::$macroOpeningChars = $macroOpeningChars; + self::$macroClosingChars = $macroClosingChars; + } + + public function getTempDocumentFilename(): string + { + return $this->tempDocumentFilename; } } diff --git a/src/PhpWord/Writer/AbstractWriter.php b/src/PhpWord/Writer/AbstractWriter.php index 3131dbafbc..8ebf98c7b5 100644 --- a/src/PhpWord/Writer/AbstractWriter.php +++ b/src/PhpWord/Writer/AbstractWriter.php @@ -347,17 +347,8 @@ protected function addFilesToPackage(ZipArchive $zip, $elements): void // Retrive GD image content or get local media if (isset($element['isMemImage']) && $element['isMemImage']) { - $image = call_user_func($element['createFunction'], $element['source']); - if ($element['imageType'] === 'image/png') { - // PNG images need to preserve alpha channel information - imagesavealpha($image, true); - } - ob_start(); - call_user_func($element['imageFunction'], $image); - $imageContents = ob_get_contents(); - ob_end_clean(); + $imageContents = $element['imageString']; $zip->addFromString($target, $imageContents); - imagedestroy($image); } else { $this->addFileToPackage($zip, $element['source'], $target); } diff --git a/src/PhpWord/Writer/HTML.php b/src/PhpWord/Writer/HTML.php index ea0d654e5d..647890591d 100644 --- a/src/PhpWord/Writer/HTML.php +++ b/src/PhpWord/Writer/HTML.php @@ -18,6 +18,9 @@ namespace PhpOffice\PhpWord\Writer; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Shared\Validate; +use PhpOffice\PhpWord\Style\Font; /** * HTML writer. @@ -42,6 +45,27 @@ class HTML extends AbstractWriter implements WriterInterface */ protected $notes = []; + /** + * Callback for editing generated html. + * + * @var null|callable + */ + private $editCallback; + + /** + * Default generic name for default font for html. + * + * @var string + */ + private $defaultGenericFont = ''; + + /** + * Default white space style for html. + * + * @var string + */ + private $defaultWhiteSpace = ''; + /** * Create new instance. */ @@ -63,10 +87,8 @@ public function __construct(?PhpWord $phpWord = null) /** * Save PhpWord to file. - * - * @param string $filename */ - public function save($filename = null): void + public function save(string $filename): void { $this->writeFile($this->openFile($filename), $this->getContent()); } @@ -84,14 +106,56 @@ public function getContent() $content .= '<!DOCTYPE html>' . PHP_EOL; $content .= '<!-- Generated by PHPWord -->' . PHP_EOL; - $content .= '<html>' . PHP_EOL; + $langtext = ''; + $phpWord = $this->getPhpWord(); + $lang = $phpWord->getSettings()->getThemeFontLang(); + if (!empty($lang)) { + $lang2 = $lang->getLatin(); + if (!$lang2) { + $lang2 = $lang->getEastAsia(); + } + if (!$lang2) { + $lang2 = $lang->getBidirectional(); + } + if ($lang2) { + $langtext = " lang='" . $lang2 . "'"; + } + } + $content .= "<html$langtext>" . PHP_EOL; $content .= $this->getWriterPart('Head')->write(); $content .= $this->getWriterPart('Body')->write(); $content .= '</html>' . PHP_EOL; + // Trigger a callback for editing the entire HTML + $callback = $this->editCallback; + if ($callback !== null) { + $content = $callback($content); + } + return $content; } + /** + * Return the callback to edit the entire HTML. + */ + public function getEditCallback(): ?callable + { + return $this->editCallback; + } + + /** + * Set a callback to edit the entire HTML. + * + * The callback must accept the HTML as string as first parameter, + * and it must return the edited HTML as string. + */ + public function setEditCallback(?callable $callback): self + { + $this->editCallback = $callback; + + return $this; + } + /** * Get is PDF. * @@ -122,4 +186,52 @@ public function addNote($noteId, $noteMark): void { $this->notes[$noteId] = $noteMark; } + + /** + * Get generic name for default font for html. + */ + public function getDefaultGenericFont(): string + { + return $this->defaultGenericFont; + } + + /** + * Set generic name for default font for html. + */ + public function setDefaultGenericFont(string $value): self + { + $this->defaultGenericFont = Validate::validateCSSGenericFont($value); + + return $this; + } + + /** + * Get default white space style for html. + */ + public function getDefaultWhiteSpace(): string + { + return $this->defaultWhiteSpace; + } + + /** + * Set default white space style for html. + */ + public function setDefaultWhiteSpace(string $value): self + { + $this->defaultWhiteSpace = Validate::validateCSSWhiteSpace($value); + + return $this; + } + + /** + * Escape string or not depending on setting. + */ + public function escapeHTML(string $txt): string + { + if (Settings::isOutputEscapingEnabled()) { + return htmlspecialchars($txt, ENT_QUOTES | (defined('ENT_SUBSTITUTE') ? ENT_SUBSTITUTE : 0), 'UTF-8'); + } + + return $txt; + } } diff --git a/src/PhpWord/Writer/HTML/Element/AbstractElement.php b/src/PhpWord/Writer/HTML/Element/AbstractElement.php index f5b0e91719..7c7bde3189 100644 --- a/src/PhpWord/Writer/HTML/Element/AbstractElement.php +++ b/src/PhpWord/Writer/HTML/Element/AbstractElement.php @@ -17,9 +17,8 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; -use Laminas\Escaper\Escaper; use PhpOffice\PhpWord\Element\AbstractElement as Element; -use PhpOffice\PhpWord\Writer\AbstractWriter; +use PhpOffice\PhpWord\Writer\HTML; /** * Abstract HTML element writer. @@ -31,7 +30,7 @@ abstract class AbstractElement /** * Parent writer. * - * @var \PhpOffice\PhpWord\Writer\AbstractWriter + * @var HTML */ protected $parentWriter; @@ -49,11 +48,6 @@ abstract class AbstractElement */ protected $withoutP = false; - /** - * @var \Laminas\Escaper\Escaper|\PhpOffice\PhpWord\Escaper\AbstractEscaper - */ - protected $escaper; - /** * Write element. */ @@ -64,12 +58,11 @@ abstract public function write(); * * @param bool $withoutP */ - public function __construct(AbstractWriter $parentWriter, Element $element, $withoutP = false) + public function __construct(HTML $parentWriter, Element $element, $withoutP = false) { $this->parentWriter = $parentWriter; $this->element = $element; $this->withoutP = $withoutP; - $this->escaper = new Escaper(); } /** diff --git a/src/PhpWord/Writer/HTML/Element/Link.php b/src/PhpWord/Writer/HTML/Element/Link.php index 7d302c1f85..ac48c865bd 100644 --- a/src/PhpWord/Writer/HTML/Element/Link.php +++ b/src/PhpWord/Writer/HTML/Element/Link.php @@ -17,7 +17,7 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; -use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Writer\HTML; /** * Link element HTML writer. @@ -39,11 +39,11 @@ public function write() $prefix = $this->element->isInternal() ? '#' : ''; $content = $this->writeOpening(); - if (Settings::isOutputEscapingEnabled()) { - $content .= "<a href=\"{$prefix}{$this->escaper->escapeHtmlAttr($this->element->getSource())}\">{$this->escaper->escapeHtml($this->element->getText())}</a>"; - } else { - $content .= "<a href=\"{$prefix}{$this->element->getSource()}\">{$this->element->getText()}</a>"; - } + $content .= "<a href=\"{$prefix}" + . $this->parentWriter->escapeHTML($this->element->getSource()) + . '">' + . $this->parentWriter->escapeHTML($this->element->getText()) + . '</a>'; $content .= $this->writeClosing(); return $content; diff --git a/src/PhpWord/Writer/HTML/Element/ListItem.php b/src/PhpWord/Writer/HTML/Element/ListItem.php index d04798684f..ddc3ecf0fd 100644 --- a/src/PhpWord/Writer/HTML/Element/ListItem.php +++ b/src/PhpWord/Writer/HTML/Element/ListItem.php @@ -17,7 +17,7 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; -use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Writer\HTML; /** * ListItem element HTML writer. @@ -37,11 +37,7 @@ public function write() return ''; } - if (Settings::isOutputEscapingEnabled()) { - $content = '<p>' . $this->escaper->escapeHtml($this->element->getTextObject()->getText()) . '</p>' . PHP_EOL; - } else { - $content = '<p>' . $this->element->getTextObject()->getText() . '</p>' . PHP_EOL; - } + $content = '<p>' . $this->parentWriter->escapeHTML($this->element->getTextObject()->getText()) . '</p>' . PHP_EOL; return $content; } diff --git a/src/PhpWord/Writer/HTML/Element/PageBreak.php b/src/PhpWord/Writer/HTML/Element/PageBreak.php index 762426bf26..e5c48cc789 100644 --- a/src/PhpWord/Writer/HTML/Element/PageBreak.php +++ b/src/PhpWord/Writer/HTML/Element/PageBreak.php @@ -17,6 +17,8 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; +use PhpOffice\PhpWord\Writer\PDF\TCPDF; + /** * PageBreak element HTML writer. * @@ -35,10 +37,13 @@ public function write() { /** @var \PhpOffice\PhpWord\Writer\HTML $parentWriter Type hint */ $parentWriter = $this->parentWriter; + if ($parentWriter instanceof TCPDF) { + return '<br pagebreak="true"/>'; + } if ($parentWriter->isPdf()) { return '<pagebreak style="page-break-before: always;" pagebreak="true"></pagebreak>'; } - return ''; + return '<div style="page-break-before: always; height: 0; margin: 0; padding: 0; overflow: hidden;"> </div>' . PHP_EOL; } } diff --git a/src/PhpWord/Writer/HTML/Element/Table.php b/src/PhpWord/Writer/HTML/Element/Table.php index b1a2ee9667..7f8d0bcc3c 100644 --- a/src/PhpWord/Writer/HTML/Element/Table.php +++ b/src/PhpWord/Writer/HTML/Element/Table.php @@ -17,6 +17,8 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; +use PhpOffice\PhpWord\Writer\HTML\Style\Table as TableStyleWriter; + /** * Table element HTML writer. * @@ -39,7 +41,7 @@ public function write() $rows = $this->element->getRows(); $rowCount = count($rows); if ($rowCount > 0) { - $content .= '<table' . self::getTableStyle($this->element->getStyle()) . '>' . PHP_EOL; + $content .= '<table' . $this->getTableStyle($this->element->getStyle()) . '>' . PHP_EOL; for ($i = 0; $i < $rowCount; ++$i) { /** @var \PhpOffice\PhpWord\Element\Row $row Type hint */ @@ -51,10 +53,10 @@ public function write() $rowCellCount = count($rowCells); for ($j = 0; $j < $rowCellCount; ++$j) { $cellStyle = $rowCells[$j]->getStyle(); + $cellStyleCss = $this->getTableStyle($cellStyle); $cellBgColor = $cellStyle->getBgColor(); - $cellBgColor === 'auto' && $cellBgColor = null; // auto cannot be parsed to hexadecimal number $cellFgColor = null; - if ($cellBgColor) { + if ($cellBgColor && $cellBgColor !== 'auto') { $red = hexdec(substr($cellBgColor, 0, 2)); $green = hexdec(substr($cellBgColor, 2, 2)); $blue = hexdec(substr($cellBgColor, 4, 2)); @@ -63,42 +65,27 @@ public function write() $cellColSpan = $cellStyle->getGridSpan(); $cellRowSpan = 1; $cellVMerge = $cellStyle->getVMerge(); - // If this is the first cell of the vertical merge, find out how man rows it spans + // If this is the first cell of the vertical merge, find out how many rows it spans if ($cellVMerge === 'restart') { - for ($k = $i + 1; $k < $rowCount; ++$k) { - $kRowCells = $rows[$k]->getCells(); - if (isset($kRowCells[$j])) { - if ($kRowCells[$j]->getStyle()->getVMerge() === 'continue') { - ++$cellRowSpan; - } else { - break; - } - } else { - break; - } - } + $cellRowSpan = $this->calculateCellRowSpan($rows, $i, $j); } // Ignore cells that are merged vertically with previous rows if ($cellVMerge !== 'continue') { $cellTag = $tblHeader ? 'th' : 'td'; $cellColSpanAttr = (is_numeric($cellColSpan) && ($cellColSpan > 1) ? " colspan=\"{$cellColSpan}\"" : ''); $cellRowSpanAttr = ($cellRowSpan > 1 ? " rowspan=\"{$cellRowSpan}\"" : ''); - $cellBgColorAttr = (null === $cellBgColor ? '' : " bgcolor=\"#{$cellBgColor}\""); - $cellFgColorAttr = (null === $cellFgColor ? '' : " color=\"#{$cellFgColor}\""); - $content .= "<{$cellTag}{$cellColSpanAttr}{$cellRowSpanAttr}{$cellBgColorAttr}{$cellFgColorAttr}>" . PHP_EOL; + $cellBgColorAttr = (empty($cellBgColor) ? '' : " bgcolor=\"#{$cellBgColor}\""); + $cellFgColorAttr = (empty($cellFgColor) ? '' : " color=\"#{$cellFgColor}\""); + $content .= "<{$cellTag}{$cellStyleCss}{$cellColSpanAttr}{$cellRowSpanAttr}{$cellBgColorAttr}{$cellFgColorAttr}>" . PHP_EOL; $writer = new Container($this->parentWriter, $rowCells[$j]); $content .= $writer->write(); if ($cellRowSpan > 1) { // There shouldn't be any content in the subsequent merged cells, but lets check anyway for ($k = $i + 1; $k < $rowCount; ++$k) { $kRowCells = $rows[$k]->getCells(); - if (isset($kRowCells[$j])) { - if ($kRowCells[$j]->getStyle()->getVMerge() === 'continue') { - $writer = new Container($this->parentWriter, $kRowCells[$j]); - $content .= $writer->write(); - } else { - break; - } + if (isset($kRowCells[$j]) && $kRowCells[$j]->getStyle()->getVMerge() === 'continue') { + $writer = new Container($this->parentWriter, $kRowCells[$j]); + $content .= $writer->write(); } else { break; } @@ -118,26 +105,77 @@ public function write() /** * Translates Table style in CSS equivalent. * - * @param null|\PhpOffice\PhpWord\Style\Table|string $tableStyle - * - * @return string + * @param null|\PhpOffice\PhpWord\Style\Cell|\PhpOffice\PhpWord\Style\Table|string $tableStyle */ - private function getTableStyle($tableStyle = null) + private function getTableStyle($tableStyle = null): string { if ($tableStyle == null) { return ''; } if (is_string($tableStyle)) { - $style = ' class="' . $tableStyle; - } else { - $style = ' style="'; - if ($tableStyle->getLayout() == \PhpOffice\PhpWord\Style\Table::LAYOUT_FIXED) { - $style .= 'table-layout: fixed;'; - } elseif ($tableStyle->getLayout() == \PhpOffice\PhpWord\Style\Table::LAYOUT_AUTO) { - $style .= 'table-layout: auto;'; + return ' class="' . $tableStyle . '"'; + } + + $styleWriter = new TableStyleWriter($tableStyle); + $style = $styleWriter->write(); + if ($style === '') { + return ''; + } + + return ' style="' . $style . '"'; + } + + /** + * Calculates cell rowspan. + * + * @param \PhpOffice\PhpWord\Element\Row[] $rows + */ + private function calculateCellRowSpan(array $rows, int $rowIndex, int $colIndex): int + { + $currentRow = $rows[$rowIndex]; + $currentRowCells = $currentRow->getCells(); + $shiftedColIndex = 0; + + foreach ($currentRowCells as $cell) { + if ($cell === $currentRowCells[$colIndex]) { + break; + } + + $colSpan = 1; + + if ($cell->getStyle()->getGridSpan() !== null) { + $colSpan = $cell->getStyle()->getGridSpan(); + } + + $shiftedColIndex += $colSpan; + } + + $rowCount = count($rows); + $rowSpan = 1; + + for ($i = $rowIndex + 1; $i < $rowCount; ++$i) { + $rowCells = $rows[$i]->getCells(); + $colIndex = 0; + + foreach ($rowCells as $cell) { + if ($colIndex === $shiftedColIndex) { + if ($cell->getStyle()->getVMerge() === 'continue') { + ++$rowSpan; + } + + break; + } + + $colSpan = 1; + + if ($cell->getStyle()->getGridSpan() !== null) { + $colSpan = $cell->getStyle()->getGridSpan(); + } + + $colIndex += $colSpan; } } - return $style . '"'; + return $rowSpan; } } diff --git a/src/PhpWord/Writer/HTML/Element/Text.php b/src/PhpWord/Writer/HTML/Element/Text.php index a360f0922b..5af9f2ab63 100644 --- a/src/PhpWord/Writer/HTML/Element/Text.php +++ b/src/PhpWord/Writer/HTML/Element/Text.php @@ -18,9 +18,10 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; use PhpOffice\PhpWord\Element\TrackChange; -use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; +use PhpOffice\PhpWord\Writer\HTML; use PhpOffice\PhpWord\Writer\HTML\Style\Font as FontStyleWriter; use PhpOffice\PhpWord\Writer\HTML\Style\Paragraph as ParagraphStyleWriter; @@ -66,19 +67,21 @@ class Text extends AbstractElement */ public function write() { + $this->processFontStyle(); + /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ $element = $this->element; - $this->getFontStyle(); + + $text = $this->parentWriter->escapeHTML($element->getText()); + if (!$this->withoutP && !trim($text)) { + $text = ' '; + } $content = ''; $content .= $this->writeOpening(); $content .= $this->openingText; $content .= $this->openingTags; - if (Settings::isOutputEscapingEnabled()) { - $content .= $this->escaper->escapeHtml($element->getText()); - } else { - $content .= $element->getText(); - } + $content .= $text; $content .= $this->closingTags; $content .= $this->closingText; $content .= $this->writeClosing(); @@ -115,10 +118,7 @@ protected function writeOpening() { $content = ''; if (!$this->withoutP) { - $style = ''; - if (method_exists($this->element, 'getParagraphStyle')) { - $style = $this->getParagraphStyle(); - } + $style = $this->getParagraphStyle(); $content .= "<p{$style}>"; } @@ -141,12 +141,7 @@ protected function writeClosing() $content .= $this->writeTrackChangeClosing(); if (!$this->withoutP) { - if (Settings::isOutputEscapingEnabled()) { - $content .= $this->escaper->escapeHtml($this->closingText); - } else { - $content .= $this->closingText; - } - + $content .= $this->parentWriter->escapeHTML($this->closingText); $content .= '</p>' . PHP_EOL; } @@ -228,6 +223,7 @@ private function getParagraphStyle() $pStyleIsObject = ($paragraphStyle instanceof Paragraph); if ($pStyleIsObject) { $styleWriter = new ParagraphStyleWriter($paragraphStyle); + $styleWriter->setParentWriter($this->parentWriter); $style = $styleWriter->write(); } elseif (is_string($paragraphStyle)) { $style = $paragraphStyle; @@ -243,22 +239,50 @@ private function getParagraphStyle() /** * Get font style. */ - private function getFontStyle(): void + private function processFontStyle(): void { /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ $element = $this->element; - $style = ''; + + $attributeStyle = $attributeLang = ''; + $lang = null; + $fontStyle = $element->getFontStyle(); - $fStyleIsObject = ($fontStyle instanceof Font); - if ($fStyleIsObject) { + if ($fontStyle instanceof Font) { + // Attribute style $styleWriter = new FontStyleWriter($fontStyle); - $style = $styleWriter->write(); - } elseif (is_string($fontStyle)) { - $style = $fontStyle; + $fontCSS = $styleWriter->write(); + if ($fontCSS) { + $attributeStyle = ' style="' . $fontCSS . '"'; + } + // Attribute Lang + $lang = $fontStyle->getLang(); + } elseif (!empty($fontStyle)) { + // Attribute class + $attributeStyle = ' class="' . $fontStyle . '"'; + // Attribute Lang + /** @var Font $cssClassStyle */ + $cssClassStyle = Style::getStyle($fontStyle); + if ($cssClassStyle !== null && method_exists($cssClassStyle, 'getLang')) { + $lang = $cssClassStyle->getLang(); + } } - if ($style) { - $attribute = $fStyleIsObject ? 'style' : 'class'; - $this->openingTags = "<span {$attribute}=\"{$style}\">"; + + if ($lang) { + $attributeLang = $lang->getLatin(); + if (!$attributeLang) { + $attributeLang = $lang->getEastAsia(); + } + if (!$attributeLang) { + $attributeLang = $lang->getBidirectional(); + } + if ($attributeLang) { + $attributeLang = " lang='$attributeLang'"; + } + } + + if ($attributeStyle || $attributeLang) { + $this->openingTags = "<span$attributeLang$attributeStyle>"; $this->closingTags = '</span>'; } } diff --git a/src/PhpWord/Writer/HTML/Element/Title.php b/src/PhpWord/Writer/HTML/Element/Title.php index 3749505013..65e6cb090b 100644 --- a/src/PhpWord/Writer/HTML/Element/Title.php +++ b/src/PhpWord/Writer/HTML/Element/Title.php @@ -17,7 +17,7 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; -use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Writer\HTML; /** * TextRun element HTML writer. @@ -41,10 +41,8 @@ public function write() $text = $this->element->getText(); if (is_string($text)) { - if (Settings::isOutputEscapingEnabled()) { - $text = $this->escaper->escapeHtml($text); - } - } elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { + $text = $this->parentWriter->escapeHTML($text); + } else { $writer = new Container($this->parentWriter, $text); $text = $writer->write(); } diff --git a/src/PhpWord/Writer/HTML/Part/AbstractPart.php b/src/PhpWord/Writer/HTML/Part/AbstractPart.php index 8612e28451..0fd9a40940 100644 --- a/src/PhpWord/Writer/HTML/Part/AbstractPart.php +++ b/src/PhpWord/Writer/HTML/Part/AbstractPart.php @@ -17,9 +17,8 @@ namespace PhpOffice\PhpWord\Writer\HTML\Part; -use Laminas\Escaper\Escaper; use PhpOffice\PhpWord\Exception\Exception; -use PhpOffice\PhpWord\Writer\AbstractWriter; +use PhpOffice\PhpWord\Writer\HTML; /** * @since 0.11.0 @@ -27,35 +26,22 @@ abstract class AbstractPart { /** - * @var \PhpOffice\PhpWord\Writer\AbstractWriter + * @var ?HTML */ private $parentWriter; - /** - * @var \Laminas\Escaper\Escaper - */ - protected $escaper; - - public function __construct() - { - $this->escaper = new Escaper(); - } - /** * @return string */ abstract public function write(); - /** - * @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer - */ - public function setParentWriter(?AbstractWriter $writer = null): void + public function setParentWriter(?HTML $writer = null): void { $this->parentWriter = $writer; } /** - * @return \PhpOffice\PhpWord\Writer\AbstractWriter + * @return HTML */ public function getParentWriter() { diff --git a/src/PhpWord/Writer/HTML/Part/Body.php b/src/PhpWord/Writer/HTML/Part/Body.php index 19aae8aa1f..e5e2a5b80f 100644 --- a/src/PhpWord/Writer/HTML/Part/Body.php +++ b/src/PhpWord/Writer/HTML/Part/Body.php @@ -19,6 +19,7 @@ use PhpOffice\PhpWord\Writer\HTML\Element\Container; use PhpOffice\PhpWord\Writer\HTML\Element\TextRun as TextRunWriter; +use PhpOffice\PhpWord\Writer\PDF\TCPDF; /** * RTF body part writer. @@ -40,9 +41,18 @@ public function write() $content .= '<body>' . PHP_EOL; $sections = $phpWord->getSections(); + $secno = 0; + $isTCPDFWriter = $this->getParentWriter() instanceof TCPDF; foreach ($sections as $section) { + ++$secno; + if ($isTCPDFWriter && $secno > 1) { + $content .= "<div style=\"page: page$secno; page-break-before:always;\">" . PHP_EOL; + } else { + $content .= "<div style='page: page$secno'>" . PHP_EOL; + } $writer = new Container($this->getParentWriter(), $section); $content .= $writer->write(); + $content .= '</div>' . PHP_EOL; } $content .= $this->writeNotes(); diff --git a/src/PhpWord/Writer/HTML/Part/Head.php b/src/PhpWord/Writer/HTML/Part/Head.php index a2541aa4b0..0f3f86e3d2 100644 --- a/src/PhpWord/Writer/HTML/Part/Head.php +++ b/src/PhpWord/Writer/HTML/Part/Head.php @@ -18,12 +18,15 @@ namespace PhpOffice\PhpWord\Writer\HTML\Part; use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; +use PhpOffice\PhpWord\Style\Table; use PhpOffice\PhpWord\Writer\HTML\Style\Font as FontStyleWriter; use PhpOffice\PhpWord\Writer\HTML\Style\Generic as GenericStyleWriter; use PhpOffice\PhpWord\Writer\HTML\Style\Paragraph as ParagraphStyleWriter; +use PhpOffice\PhpWord\Writer\HTML\Style\Table as TableStyleWriter; /** * RTF head part writer. @@ -63,8 +66,10 @@ public function write() $method = 'get' . $key; if ($docProps->$method() != '') { $content .= '<meta name="' . $value . '"' - . ' content="' . (Settings::isOutputEscapingEnabled() ? $this->escaper->escapeHtmlAttr($docProps->$method()) : $docProps->$method()) . '"' - . ' />' . PHP_EOL; + . ' content="' + . $this->getParentWriter()->escapeHTML($docProps->$method()) + . '"' + . ' />' . PHP_EOL; } } $content .= $this->writeStyles(); @@ -75,19 +80,27 @@ public function write() /** * Get styles. - * - * @return string */ - private function writeStyles() + private function writeStyles(): string { $css = '<style>' . PHP_EOL; // Default styles - $defaultStyles = [ - '*' => [ - 'font-family' => Settings::getDefaultFontName(), - 'font-size' => Settings::getDefaultFontSize() . 'pt', - ], + $astarray = [ + 'font-family' => $this->getFontFamily(Settings::getDefaultFontName(), $this->getParentWriter()->getDefaultGenericFont()), + 'font-size' => Settings::getDefaultFontSize() . 'pt', + ]; + // Mpdf sometimes needs separate tag for body; doesn't harm others. + $bodyarray = $astarray; + + $defaultWhiteSpace = $this->getParentWriter()->getDefaultWhiteSpace(); + if ($defaultWhiteSpace) { + $astarray['white-space'] = $defaultWhiteSpace; + } + + foreach ([ + 'body' => $bodyarray, + '*' => $astarray, 'a.NoteRef' => [ 'text-decoration' => 'none', ], @@ -106,8 +119,7 @@ private function writeStyles() 'td' => [ 'border' => '1px solid black', ], - ]; - foreach ($defaultStyles as $selector => $style) { + ] as $selector => $style) { $styleWriter = new GenericStyleWriter($style); $css .= $selector . ' {' . $styleWriter->write() . '}' . PHP_EOL; } @@ -116,23 +128,81 @@ private function writeStyles() $customStyles = Style::getStyles(); if (is_array($customStyles)) { foreach ($customStyles as $name => $style) { + $styleParagraph = null; if ($style instanceof Font) { $styleWriter = new FontStyleWriter($style); if ($style->getStyleType() == 'title') { $name = str_replace('Heading_', 'h', $name); + $styleParagraph = $style->getParagraph(); + $style = $styleParagraph; } else { $name = '.' . $name; } $css .= "{$name} {" . $styleWriter->write() . '}' . PHP_EOL; - } elseif ($style instanceof Paragraph) { + } + if ($style instanceof Paragraph) { $styleWriter = new ParagraphStyleWriter($style); - $name = '.' . $name; + $styleWriter->setParentWriter($this->getParentWriter()); + if (!$styleParagraph) { + $name = '.' . $name; + } + if ($name === '.Normal') { + $name = "p, $name"; + } $css .= "{$name} {" . $styleWriter->write() . '}' . PHP_EOL; } + if ($style instanceof Table) { + $styleWriter = new TableStyleWriter($style); + $css .= ".{$name} {" . $styleWriter->write() . '}' . PHP_EOL; + } + } + } + + $css .= 'body > div + div {page-break-before: always;}' . PHP_EOL; + $css .= 'div > *:first-child {page-break-before: auto;}' . PHP_EOL; + + $sectionNum = 0; + foreach ($this->getParentWriter()->getPhpWord()->getSections() as $section) { + ++$sectionNum; + + $css .= "@page page$sectionNum {"; + + $paperSize = $section->getStyle()->getPaperSize(); + $orientation = $section->getStyle()->getOrientation(); + if ($this->getParentWriter()->isPdf()) { + if ($orientation === 'landscape') { + $paperSize .= '-L'; + } + $css .= "sheet-size: $paperSize; "; + } else { + $css .= "size: $paperSize $orientation; "; } + + $css .= 'margin-right: ' . (string) ($section->getStyle()->getMarginRight() / Converter::INCH_TO_TWIP) . 'in; '; + $css .= 'margin-left: ' . (string) ($section->getStyle()->getMarginLeft() / Converter::INCH_TO_TWIP) . 'in; '; + $css .= 'margin-top: ' . (string) ($section->getStyle()->getMarginTop() / Converter::INCH_TO_TWIP) . 'in; '; + $css .= 'margin-bottom: ' . (string) ($section->getStyle()->getMarginBottom() / Converter::INCH_TO_TWIP) . 'in; '; + $css .= '}' . PHP_EOL; } + $css .= '</style>' . PHP_EOL; return $css; } + + /** + * Set font and alternates for css font-family. + */ + private function getFontFamily(string $font, string $genericFont): string + { + if (empty($font)) { + return ''; + } + $fontfamily = "'" . htmlspecialchars($font, ENT_QUOTES, 'UTF-8') . "'"; + if (!empty($genericFont)) { + $fontfamily .= ", $genericFont"; + } + + return $fontfamily; + } } diff --git a/src/PhpWord/Writer/HTML/Style/AbstractStyle.php b/src/PhpWord/Writer/HTML/Style/AbstractStyle.php index 19362d3670..a650786769 100644 --- a/src/PhpWord/Writer/HTML/Style/AbstractStyle.php +++ b/src/PhpWord/Writer/HTML/Style/AbstractStyle.php @@ -17,7 +17,8 @@ namespace PhpOffice\PhpWord\Writer\HTML\Style; -use PhpOffice\PhpWord\Style\AbstractStyle as Style; +use PhpOffice\PhpWord\Style\AbstractStyle as StyleAbstract; +use PhpOffice\PhpWord\Writer\HTML; /** * Style writer. @@ -29,26 +30,28 @@ abstract class AbstractStyle /** * Parent writer. * - * @var \PhpOffice\PhpWord\Writer\AbstractWriter + * @var HTML */ private $parentWriter; /** * Style. * - * @var array|\PhpOffice\PhpWord\Style\AbstractStyle + * @var null|array|StyleAbstract */ private $style; /** * Write style. + * + * @return mixed */ abstract public function write(); /** * Create new instance. * - * @param array|\PhpOffice\PhpWord\Style\AbstractStyle $style + * @param array|StyleAbstract $style */ public function __construct($style = null) { @@ -58,7 +61,7 @@ public function __construct($style = null) /** * Set parent writer. * - * @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer + * @param HTML $writer */ public function setParentWriter($writer): void { @@ -68,7 +71,7 @@ public function setParentWriter($writer): void /** * Get parent writer. * - * @return \PhpOffice\PhpWord\Writer\AbstractWriter + * @return HTML */ public function getParentWriter() { @@ -78,11 +81,11 @@ public function getParentWriter() /** * Get style. * - * @return array|\PhpOffice\PhpWord\Style\AbstractStyle $style + * @return null|array|string|StyleAbstract */ public function getStyle() { - if (!$this->style instanceof Style && !is_array($this->style)) { + if (!$this->style instanceof StyleAbstract && !is_array($this->style)) { return ''; } diff --git a/src/PhpWord/Writer/HTML/Style/Font.php b/src/PhpWord/Writer/HTML/Style/Font.php index 1fd88f5e8e..eb59d02d1e 100644 --- a/src/PhpWord/Writer/HTML/Style/Font.php +++ b/src/PhpWord/Writer/HTML/Style/Font.php @@ -39,14 +39,14 @@ public function write() } $css = []; - $font = $style->getName(); + $font = $this->getFontFamily($style->getName(), $style->getFallbackFont()); $size = $style->getSize(); $color = $style->getColor(); $fgColor = $style->getFgColor(); $underline = $style->getUnderline() != FontStyle::UNDERLINE_NONE; $lineThrough = $style->isStrikethrough() || $style->isDoubleStrikethrough(); - $css['font-family'] = $this->getValueIf($font !== null, "'{$font}'"); + $css['font-family'] = $this->getValueIf(!empty($font), $font); $css['font-size'] = $this->getValueIf($size !== null, "{$size}pt"); $css['color'] = $this->getValueIf($color !== null, "#{$color}"); $css['background'] = $this->getValueIf($fgColor != '', $fgColor); @@ -61,10 +61,35 @@ public function write() $css['text-transform'] = $this->getValueIf($style->isAllCaps(), 'uppercase'); $css['font-variant'] = $this->getValueIf($style->isSmallCaps(), 'small-caps'); $css['display'] = $this->getValueIf($style->isHidden(), 'none'); + $whitespace = $style->getWhiteSpace(); + if ($whitespace) { + $css['white-space'] = $whitespace; + } $spacing = $style->getSpacing(); $css['letter-spacing'] = $this->getValueIf(null !== $spacing, ($spacing / 20) . 'pt'); + if ($style->isRTL()) { + $css['direction'] = 'rtl'; + } elseif ($style->isRTL() === false) { + $css['direction'] = 'ltr'; + } return $this->assembleCss($css); } + + /** + * Set font and alternates for css font-family. + */ + private function getFontFamily(?string $font, string $genericFont): string + { + if (empty($font)) { + return ''; + } + $fontfamily = "'" . htmlspecialchars($font, ENT_QUOTES, 'UTF-8') . "'"; + if (!empty($genericFont)) { + $fontfamily .= ", $genericFont"; + } + + return $fontfamily; + } } diff --git a/src/PhpWord/Writer/HTML/Style/Paragraph.php b/src/PhpWord/Writer/HTML/Style/Paragraph.php index 865114ff58..07d91f5487 100644 --- a/src/PhpWord/Writer/HTML/Style/Paragraph.php +++ b/src/PhpWord/Writer/HTML/Style/Paragraph.php @@ -17,7 +17,9 @@ namespace PhpOffice\PhpWord\Writer\HTML\Style; +use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\SimpleType\Jc; +use PhpOffice\PhpWord\Writer\PDF\TCPDF; /** * Paragraph style HTML writer. @@ -49,6 +51,9 @@ public function write() break; case Jc::END: + $textAlign = $style->isBidi() ? 'left' : 'right'; + + break; case Jc::MEDIUM_KASHIDA: case Jc::HIGH_KASHIDA: case Jc::LOW_KASHIDA: @@ -63,9 +68,13 @@ public function write() $textAlign = 'justify'; break; - default: //all others, align left + case Jc::LEFT: $textAlign = 'left'; + break; + default: //all others, including Jc::START + $textAlign = $style->isBidi() ? 'right' : 'left'; + break; } @@ -79,9 +88,32 @@ public function write() $after = $spacing->getAfter(); $css['margin-top'] = $this->getValueIf(null !== $before, ($before / 20) . 'pt'); $css['margin-bottom'] = $this->getValueIf(null !== $after, ($after / 20) . 'pt'); - } else { - $css['margin-top'] = '0'; - $css['margin-bottom'] = '0'; + } + + // Line Height + $lineHeight = $style->getLineHeight(); + if (!empty($lineHeight)) { + $css['line-height'] = $lineHeight; + } + + // Indentation (Margin) + $indentation = $style->getIndentation(); + if ($indentation) { + $inches = $indentation->getLeft() * 1.0 / Converter::INCH_TO_TWIP; + $css[$this->getParentWriter() instanceof TCPDF ? 'text-indent' : 'margin-left'] = ((string) $inches) . 'in'; + + $inches = $indentation->getRight() * 1.0 / Converter::INCH_TO_TWIP; + $css['margin-right'] = ((string) $inches) . 'in'; + } + + // Page Break Before + if ($style->hasPageBreakBefore()) { + $css['page-break-before'] = 'always'; + } + + // Bidirectional + if ($style->isBidi()) { + $css['direction'] = 'rtl'; } return $this->assembleCss($css); diff --git a/src/PhpWord/Writer/HTML/Style/Table.php b/src/PhpWord/Writer/HTML/Style/Table.php new file mode 100644 index 0000000000..53eea0e96f --- /dev/null +++ b/src/PhpWord/Writer/HTML/Style/Table.php @@ -0,0 +1,85 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ +declare(strict_types=1); + +namespace PhpOffice\PhpWord\Writer\HTML\Style; + +use PhpOffice\PhpWord\Style\Cell as StyleCell; +use PhpOffice\PhpWord\Style\Table as StyleTable; + +class Table extends AbstractStyle +{ + /** + * @return string + */ + public function write() + { + $style = $this->getStyle(); + if (!$style instanceof StyleTable && !$style instanceof StyleCell) { + return ''; + } + + $css = []; + if (is_object($style) && method_exists($style, 'getLayout')) { + if ($style->getLayout() == StyleTable::LAYOUT_FIXED) { + $css['table-layout'] = 'fixed'; + } elseif ($style->getLayout() == StyleTable::LAYOUT_AUTO) { + $css['table-layout'] = 'auto'; + } + } + if (is_object($style) && method_exists($style, 'isBidiVisual')) { + if ($style->isBidiVisual()) { + $css['direction'] = 'rtl'; + } + } + if (is_object($style) && method_exists($style, 'getVAlign')) { + $css['vertical-align'] = $style->getVAlign(); + } + + foreach (['Top', 'Left', 'Bottom', 'Right'] as $direction) { + $method = 'getBorder' . $direction . 'Style'; + if (method_exists($style, $method)) { + $outval = $style->{$method}(); + if ($outval === 'single') { + $outval = 'solid'; + } + if (is_string($outval) && 1 == preg_match('/^[a-z]+$/', $outval)) { + $css['border-' . lcfirst($direction) . '-style'] = $outval; + } + } + + $method = 'getBorder' . $direction . 'Color'; + if (method_exists($style, $method)) { + $outval = $style->{$method}(); + if (is_string($outval) && 1 == preg_match('/^[a-z]+$/', $outval)) { + $css['border-' . lcfirst($direction) . '-color'] = $outval; + } + } + + $method = 'getBorder' . $direction . 'Size'; + if (method_exists($style, $method)) { + $outval = $style->{$method}(); + if (is_numeric($outval)) { + // size is in twips - divide by 20 to get points + $css['border-' . lcfirst($direction) . '-width'] = ((string) ($outval / 20)) . 'pt'; + } + } + } + + return $this->assembleCss($css); + } +} diff --git a/src/PhpWord/Writer/ODText.php b/src/PhpWord/Writer/ODText.php index 10d9d701fa..616119e5cc 100644 --- a/src/PhpWord/Writer/ODText.php +++ b/src/PhpWord/Writer/ODText.php @@ -17,8 +17,12 @@ namespace PhpOffice\PhpWord\Writer; +use PhpOffice\Math\Writer\MathML; +use PhpOffice\PhpWord\Element\AbstractElement; +use PhpOffice\PhpWord\Element\Formula; use PhpOffice\PhpWord\Media; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Writer\ODText\Part\AbstractPart; /** * ODText writer. @@ -27,10 +31,13 @@ */ class ODText extends AbstractWriter implements WriterInterface { + /** + * @var AbstractElement[] + */ + protected $objects = []; + /** * Create new ODText writer. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord */ public function __construct(?PhpWord $phpWord = null) { @@ -61,10 +68,8 @@ public function __construct(?PhpWord $phpWord = null) /** * Save PhpWord to file. - * - * @param string $filename */ - public function save($filename = null): void + public function save(string $filename): void { $filename = $this->getTempFile($filename); $zip = $this->getZipArchive($filename); @@ -77,8 +82,28 @@ public function save($filename = null): void // Write parts foreach ($this->parts as $partName => $fileName) { - if ($fileName != '') { - $zip->addFromString($fileName, $this->getWriterPart($partName)->write()); + if ($fileName === '') { + continue; + } + $part = $this->getWriterPart($partName); + if (!$part instanceof AbstractPart) { + continue; + } + + $part->setObjects($this->objects); + + $zip->addFromString($fileName, $part->write()); + + $this->objects = $part->getObjects(); + } + + // Write objects charts + if (!empty($this->objects)) { + $writer = new MathML(); + foreach ($this->objects as $idxObject => $object) { + if ($object instanceof Formula) { + $zip->addFromString('Formula' . $idxObject . '/content.xml', $writer->write($object->getMath())); + } } } diff --git a/src/PhpWord/Writer/ODText/Element/Container.php b/src/PhpWord/Writer/ODText/Element/Container.php index 6e6b88eabc..6262ad227e 100644 --- a/src/PhpWord/Writer/ODText/Element/Container.php +++ b/src/PhpWord/Writer/ODText/Element/Container.php @@ -32,4 +32,9 @@ class Container extends Word2007Container * @var string */ protected $namespace = 'PhpOffice\\PhpWord\\Writer\\ODText\\Element'; + + /** + * @var array<string> + */ + protected $containerWithoutP = ['TextRun', 'Footnote', 'Endnote']; } diff --git a/src/PhpWord/Writer/ODText/Element/Field.php b/src/PhpWord/Writer/ODText/Element/Field.php index 1bfdbc9301..46f62b0f02 100644 --- a/src/PhpWord/Writer/ODText/Element/Field.php +++ b/src/PhpWord/Writer/ODText/Element/Field.php @@ -15,7 +15,7 @@ * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ // Not fully implemented -// - supports only PAGE and NUMPAGES +// - supports only PAGE, NUMPAGES, DATE and FILENAME // - supports only default formats and options // - supports style only if specified by name // - spaces before and after field may be dropped @@ -44,6 +44,7 @@ public function write(): void case 'date': case 'page': case 'numpages': + case 'filename': $this->writeDefault($element, $type); break; @@ -78,6 +79,18 @@ private function writeDefault(\PhpOffice\PhpWord\Element\Field $element, $type): $xmlWriter->startElement('text:page-count'); $xmlWriter->endElement(); + break; + case 'filename': + $xmlWriter->startElement('text:file-name'); + $xmlWriter->writeAttribute('text:fixed', 'false'); + $options = $element->getOptions(); + if ($options != null && in_array('Path', $options)) { + $xmlWriter->writeAttribute('text:display', 'full'); + } else { + $xmlWriter->writeAttribute('text:display', 'name'); + } + $xmlWriter->endElement(); + break; } $xmlWriter->endElement(); // text:span diff --git a/src/PhpWord/Writer/ODText/Element/Formula.php b/src/PhpWord/Writer/ODText/Element/Formula.php new file mode 100644 index 0000000000..ddb1d81aee --- /dev/null +++ b/src/PhpWord/Writer/ODText/Element/Formula.php @@ -0,0 +1,74 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWord\Writer\ODText\Element; + +use PhpOffice\PhpWord\Element\Formula as ElementFormula; +use PhpOffice\PhpWord\Shared\Converter; +use PhpOffice\PhpWord\Writer\ODText\Part\AbstractPart; + +/** + * Formula element writer. + * + * @since 0.10.0 + */ +class Formula extends AbstractElement +{ + /** + * Write element. + */ + public function write(): void + { + $xmlWriter = $this->getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof ElementFormula) { + return; + } + + $part = $this->getPart(); + if (!$part instanceof AbstractPart) { + return; + } + + $objectIdx = $part->addObject($element); + + //$style = $element->getStyle(); + //$width = Converter::pixelToCm($style->getWidth()); + //$height = Converter::pixelToCm($style->getHeight()); + + $xmlWriter->startElement('text:p'); + $xmlWriter->writeAttribute('text:style-name', 'OB' . $objectIdx); + + $xmlWriter->startElement('draw:frame'); + $xmlWriter->writeAttribute('draw:name', $element->getElementId()); + $xmlWriter->writeAttribute('text:anchor-type', 'as-char'); + //$xmlWriter->writeAttribute('svg:width', $width . 'cm'); + //$xmlWriter->writeAttribute('svg:height', $height . 'cm'); + //$xmlWriter->writeAttribute('draw:z-index', $mediaIndex); + + $xmlWriter->startElement('draw:object'); + $xmlWriter->writeAttribute('xlink:href', 'Formula' . $objectIdx); + $xmlWriter->writeAttribute('xlink:type', 'simple'); + $xmlWriter->writeAttribute('xlink:show', 'embed'); + $xmlWriter->writeAttribute('xlink:actuate', 'onLoad'); + $xmlWriter->endElement(); // draw:object + + $xmlWriter->endElement(); // draw:frame + + $xmlWriter->endElement(); // text:p + } +} diff --git a/src/PhpWord/Writer/ODText/Element/Image.php b/src/PhpWord/Writer/ODText/Element/Image.php index 051c79ce57..e5a7fba284 100644 --- a/src/PhpWord/Writer/ODText/Element/Image.php +++ b/src/PhpWord/Writer/ODText/Element/Image.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWord\Writer\ODText\Element; +use PhpOffice\PhpWord\Element\Image as ElementImage; use PhpOffice\PhpWord\Shared\Converter; /** @@ -31,9 +32,8 @@ class Image extends AbstractElement */ public function write(): void { - $xmlWriter = $this->getXmlWriter(); $element = $this->getElement(); - if (!$element instanceof \PhpOffice\PhpWord\Element\Image) { + if (!$element instanceof ElementImage) { return; } @@ -43,11 +43,16 @@ public function write(): void $width = Converter::pixelToCm($style->getWidth()); $height = Converter::pixelToCm($style->getHeight()); - $xmlWriter->startElement('text:p'); - $xmlWriter->writeAttribute('text:style-name', 'IM' . $mediaIndex); + $xmlWriter = $this->getXmlWriter(); + + if (!$this->withoutP) { + $xmlWriter->startElement('text:p'); + $xmlWriter->writeAttribute('text:style-name', 'IM' . $mediaIndex); + } $xmlWriter->startElement('draw:frame'); $xmlWriter->writeAttribute('draw:style-name', 'fr' . $mediaIndex); + $xmlWriter->writeAttributeIf($this->withoutP, 'draw:text-style-name', 'IM' . $mediaIndex); $xmlWriter->writeAttribute('draw:name', $element->getElementId()); $xmlWriter->writeAttribute('text:anchor-type', 'as-char'); $xmlWriter->writeAttribute('svg:width', $width . 'cm'); @@ -63,6 +68,8 @@ public function write(): void $xmlWriter->endElement(); // draw:frame - $xmlWriter->endElement(); // text:p + if (!$this->withoutP) { + $xmlWriter->endElement(); // text:p + } } } diff --git a/src/PhpWord/Writer/ODText/Element/ListItemRun.php b/src/PhpWord/Writer/ODText/Element/ListItemRun.php new file mode 100644 index 0000000000..89352bbbd2 --- /dev/null +++ b/src/PhpWord/Writer/ODText/Element/ListItemRun.php @@ -0,0 +1,56 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWord\Writer\ODText\Element; + +use PhpOffice\PhpWord\Element\ListItemRun as ListItemRunElement; + +/** + * ListItemRun element writer. + * + * @since 0.10.0 + */ +class ListItemRun extends AbstractElement +{ + /** + * Write list item element. + */ + public function write(): void + { + $element = $this->getElement(); + if (!$element instanceof ListItemRunElement) { + return; + } + $depth = $element->getDepth() + 1; + + $xmlWriter = $this->getXmlWriter(); + + for ($iDepth = 1; $iDepth <= $depth; ++$iDepth) { + $xmlWriter->startElement('text:list'); + $xmlWriter->writeAttribute('text:style-name', $element->getStyle()->getNumStyle()); + $xmlWriter->startElement('text:list-item'); + } + + $containerWriter = new Container($xmlWriter, $element, false); + $containerWriter->write(); + + for ($iDepth = 1; $iDepth <= $depth; ++$iDepth) { + $xmlWriter->endElement(); // text:list-item + $xmlWriter->endElement(); // text:list + } + } +} diff --git a/src/PhpWord/Writer/ODText/Element/Table.php b/src/PhpWord/Writer/ODText/Element/Table.php index 783bb21232..e12ae24b58 100644 --- a/src/PhpWord/Writer/ODText/Element/Table.php +++ b/src/PhpWord/Writer/ODText/Element/Table.php @@ -44,7 +44,7 @@ public function write(): void if ($rowCount > 0) { $xmlWriter->startElement('table:table'); $xmlWriter->writeAttribute('table:name', $element->getElementId()); - $xmlWriter->writeAttribute('table:style', $element->getElementId()); + $xmlWriter->writeAttribute('table:style-name', $element->getElementId()); // Write columns $this->writeColumns($xmlWriter, $element); diff --git a/src/PhpWord/Writer/ODText/Element/Text.php b/src/PhpWord/Writer/ODText/Element/Text.php index 75fb930856..7d54071471 100644 --- a/src/PhpWord/Writer/ODText/Element/Text.php +++ b/src/PhpWord/Writer/ODText/Element/Text.php @@ -57,37 +57,29 @@ public function write(): void $xmlWriter->writeAttribute('text:change-id', $element->getTrackChange()->getElementId()); $xmlWriter->endElement(); } else { - if (empty($fontStyle)) { - if (empty($paragraphStyle)) { - if (!$this->withoutP) { - $xmlWriter->writeAttribute('text:style-name', 'Normal'); - } - } elseif (is_string($paragraphStyle)) { - if (!$this->withoutP) { - $xmlWriter->writeAttribute('text:style-name', $paragraphStyle); - } + if (empty($paragraphStyle)) { + if (!$this->withoutP) { + $xmlWriter->writeAttribute('text:style-name', 'Normal'); } - $this->writeChangeInsertion(true, $element->getTrackChange()); - $this->replaceTabs($element->getText(), $xmlWriter); - $this->writeChangeInsertion(false, $element->getTrackChange()); - } else { - if (empty($paragraphStyle)) { - if (!$this->withoutP) { - $xmlWriter->writeAttribute('text:style-name', 'Normal'); - } - } elseif (is_string($paragraphStyle)) { - if (!$this->withoutP) { - $xmlWriter->writeAttribute('text:style-name', $paragraphStyle); - } + } elseif (is_string($paragraphStyle)) { + if (!$this->withoutP) { + $xmlWriter->writeAttribute('text:style-name', $paragraphStyle); } + } + + if (!empty($fontStyle)) { // text:span $xmlWriter->startElement('text:span'); if (is_string($fontStyle)) { $xmlWriter->writeAttribute('text:style-name', $fontStyle); } - $this->writeChangeInsertion(true, $element->getTrackChange()); - $this->replaceTabs($element->getText(), $xmlWriter); - $this->writeChangeInsertion(false, $element->getTrackChange()); + } + + $this->writeChangeInsertion(true, $element->getTrackChange()); + $this->replaceTabs($element->getText(), $xmlWriter); + $this->writeChangeInsertion(false, $element->getTrackChange()); + + if (!empty($fontStyle)) { $xmlWriter->endElement(); } } diff --git a/src/PhpWord/Writer/ODText/Element/Title.php b/src/PhpWord/Writer/ODText/Element/Title.php index 45fe65c7f5..ebe7dc4d5f 100644 --- a/src/PhpWord/Writer/ODText/Element/Title.php +++ b/src/PhpWord/Writer/ODText/Element/Title.php @@ -55,7 +55,8 @@ public function write(): void $text = $element->getText(); if (is_string($text)) { $this->writeText($text); - } elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { + } + if ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { $containerWriter = new Container($xmlWriter, $text); $containerWriter->write(); } diff --git a/src/PhpWord/Writer/ODText/Part/AbstractPart.php b/src/PhpWord/Writer/ODText/Part/AbstractPart.php index 4db5ce6e09..59035ff787 100644 --- a/src/PhpWord/Writer/ODText/Part/AbstractPart.php +++ b/src/PhpWord/Writer/ODText/Part/AbstractPart.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWord\Writer\ODText\Part; +use PhpOffice\PhpWord\Element\AbstractElement; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\XMLWriter; use PhpOffice\PhpWord\Style; @@ -28,6 +29,11 @@ */ abstract class AbstractPart extends Word2007AbstractPart { + /** + * @var AbstractElement[] + */ + protected $objects = []; + /** * @var string Date format */ @@ -102,4 +108,29 @@ protected function writeFontFaces(XMLWriter $xmlWriter): void } $xmlWriter->endElement(); } + + public function addObject(AbstractElement $object): int + { + $this->objects[] = $object; + + return count($this->objects) - 1; + } + + /** + * @param AbstractElement[] $objects + */ + public function setObjects(array $objects): self + { + $this->objects = $objects; + + return $this; + } + + /** + * @return AbstractElement[] + */ + public function getObjects(): array + { + return $this->objects; + } } diff --git a/src/PhpWord/Writer/ODText/Part/Content.php b/src/PhpWord/Writer/ODText/Part/Content.php index d4b92cfd79..00871d9c52 100644 --- a/src/PhpWord/Writer/ODText/Part/Content.php +++ b/src/PhpWord/Writer/ODText/Part/Content.php @@ -135,8 +135,11 @@ public function write() $xmlWriter->startElement('text:p'); $xmlWriter->writeAttribute('text:style-name', 'SB' . $section->getSectionId()); $xmlWriter->endElement(); + $containerWriter = new Container($xmlWriter, $section); + $containerWriter->setPart($this); $containerWriter->write(); + $xmlWriter->endElement(); // text:section } @@ -198,7 +201,7 @@ private function writeTextStyles(XMLWriter $xmlWriter): void } foreach ($styles as $style) { - $sty = $style->getStyleName(); + $sty = (string) $style->getStyleName(); if (substr($sty, 0, 8) === 'Heading_') { $style = new Paragraph(); $style->setStyleName('HD' . substr($sty, 8)); @@ -280,7 +283,6 @@ private function getContainerStyle($container, &$paragraphStyleCount, &$fontStyl $sty->setAlignment($style->getAlignment()); $this->imageParagraphStyles[] = $sty; } elseif ($element instanceof Table) { - /** @var \PhpOffice\PhpWord\Style\Table $style */ $style = $element->getStyle(); if (is_string($style)) { $style = Style::getStyle($style); diff --git a/src/PhpWord/Writer/ODText/Part/Manifest.php b/src/PhpWord/Writer/ODText/Part/Manifest.php index 7d428b2c76..37fb797935 100644 --- a/src/PhpWord/Writer/ODText/Part/Manifest.php +++ b/src/PhpWord/Writer/ODText/Part/Manifest.php @@ -17,7 +17,9 @@ namespace PhpOffice\PhpWord\Writer\ODText\Part; +use PhpOffice\PhpWord\Element\Formula; use PhpOffice\PhpWord\Media; +use PhpOffice\PhpWord\Writer\ODText; /** * ODText manifest part writer: META-INF/manifest.xml. @@ -31,7 +33,6 @@ class Manifest extends AbstractPart */ public function write() { - $parts = ['content.xml', 'meta.xml', 'styles.xml']; $xmlWriter = $this->getXmlWriter(); $xmlWriter->startDocument('1.0', 'UTF-8'); @@ -46,7 +47,7 @@ public function write() $xmlWriter->endElement(); // Parts - foreach ($parts as $part) { + foreach (['content.xml', 'meta.xml', 'styles.xml'] as $part) { $xmlWriter->startElement('manifest:file-entry'); $xmlWriter->writeAttribute('manifest:media-type', 'text/xml'); $xmlWriter->writeAttribute('manifest:full-path', $part); @@ -64,6 +65,20 @@ public function write() } } + foreach ($this->getObjects() as $idxObject => $object) { + if ($object instanceof Formula) { + $xmlWriter->startElement('manifest:file-entry'); + $xmlWriter->writeAttribute('manifest:full-path', 'Formula' . $idxObject . '/content.xml'); + $xmlWriter->writeAttribute('manifest:media-type', 'text/xml'); + $xmlWriter->endElement(); + $xmlWriter->startElement('manifest:file-entry'); + $xmlWriter->writeAttribute('manifest:full-path', 'Formula' . $idxObject . '/'); + $xmlWriter->writeAttribute('manifest:version', '1.2'); + $xmlWriter->writeAttribute('manifest:media-type', 'application/vnd.oasis.opendocument.formula'); + $xmlWriter->endElement(); + } + } + $xmlWriter->endElement(); // manifest:manifest return $xmlWriter->getData(); diff --git a/src/PhpWord/Writer/ODText/Style/Numbering.php b/src/PhpWord/Writer/ODText/Style/Numbering.php new file mode 100644 index 0000000000..83a1c9a750 --- /dev/null +++ b/src/PhpWord/Writer/ODText/Style/Numbering.php @@ -0,0 +1,86 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWord\Writer\ODText\Style; + +use PhpOffice\PhpWord\Shared\Converter; +use PhpOffice\PhpWord\Style\Numbering as StyleNumbering; + +/** + * Numbering style writer. + */ +class Numbering extends AbstractStyle +{ + /** + * Write style. + */ + public function write(): void + { + /** @var StyleNumbering $style Type hint */ + $style = $this->getStyle(); + if (!$style instanceof StyleNumbering) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('text:list-style'); + $xmlWriter->writeAttribute('style:name', $style->getStyleName()); + + foreach ($style->getLevels() as $styleLevel) { + $numLevel = $styleLevel->getLevel() + 1; + + // In Twips + $tabPos = $styleLevel->getTabPos(); + // In Inches + $tabPos /= Converter::INCH_TO_TWIP; + // In Centimeters + $tabPos *= Converter::INCH_TO_CM; + + // In Twips + $hanging = $styleLevel->getHanging(); + // In Inches + $hanging /= Converter::INCH_TO_TWIP; + // In Centimeters + $hanging *= Converter::INCH_TO_CM; + + $xmlWriter->startElement('text:list-level-style-bullet'); + $xmlWriter->writeAttribute('text:level', $numLevel); + $xmlWriter->writeAttribute('text:style-name', $style->getStyleName() . '_' . $numLevel); + $xmlWriter->writeAttribute('text:bullet-char', $styleLevel->getText()); + + $xmlWriter->startElement('style:list-level-properties'); + $xmlWriter->writeAttribute('text:list-level-position-and-space-mode', 'label-alignment'); + + $xmlWriter->startElement('style:list-level-label-alignment'); + $xmlWriter->writeAttribute('text:label-followed-by', 'listtab'); + $xmlWriter->writeAttribute('text:list-tab-stop-position', number_format($tabPos, 2, '.', '') . 'cm'); + $xmlWriter->writeAttribute('fo:text-indent', '-' . number_format($hanging, 2, '.', '') . 'cm'); + $xmlWriter->writeAttribute('fo:margin-left', number_format($tabPos, 2, '.', '') . 'cm'); + + $xmlWriter->endElement(); // style:list-level-label-alignment + $xmlWriter->endElement(); // style:list-level-properties + + $xmlWriter->startElement('style:text-properties'); + $xmlWriter->writeAttribute('style:font-name', $styleLevel->getFont()); + $xmlWriter->endElement(); // style:text-properties + + $xmlWriter->endElement(); // text:list-level-style-bullet + } + + $xmlWriter->endElement(); // text:list-style + } +} diff --git a/src/PhpWord/Writer/ODText/Style/Paragraph.php b/src/PhpWord/Writer/ODText/Style/Paragraph.php index 330043e116..4459c76c01 100644 --- a/src/PhpWord/Writer/ODText/Style/Paragraph.php +++ b/src/PhpWord/Writer/ODText/Style/Paragraph.php @@ -17,7 +17,10 @@ namespace PhpOffice\PhpWord\Writer\ODText\Style; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Converter; +use PhpOffice\PhpWord\SimpleType\Jc; +use PhpOffice\PhpWord\Style; /** * Font style writer. @@ -26,6 +29,16 @@ */ class Paragraph extends AbstractStyle { + private const BIDI_MAP = [ + Jc::END => Jc::LEFT, + Jc::START => Jc::RIGHT, + ]; + + private const NON_BIDI_MAP = [ + Jc::START => Jc::LEFT, + Jc::END => Jc::RIGHT, + ]; + /** * Write style. */ @@ -42,7 +55,7 @@ public function write(): void $xmlWriter->startElement('style:style'); - $styleName = $style->getStyleName(); + $styleName = (string) $style->getStyleName(); $styleAuto = false; $mpm = ''; $psm = ''; @@ -111,8 +124,18 @@ public function write(): void $xmlWriter->writeAttributeIf($marginTop !== null, 'fo:margin-top', ($marginTop / $twipToPoint) . 'pt'); $xmlWriter->writeAttributeIf($marginBottom !== null, 'fo:margin-bottom', ($marginBottom / $twipToPoint) . 'pt'); } - $temp = $style->getAlignment(); - $xmlWriter->writeAttributeIf($temp !== '', 'fo:text-align', $temp); + $alignment = $style->getAlignment(); + $bidi = $style->isBidi(); + $defaultRtl = Settings::isDefaultRtl(); + if ($alignment === '' && $bidi !== null) { + $alignment = Jc::START; + } + if ($bidi) { + $alignment = self::BIDI_MAP[$alignment] ?? $alignment; + } elseif ($defaultRtl !== null) { + $alignment = self::NON_BIDI_MAP[$alignment] ?? $alignment; + } + $xmlWriter->writeAttributeIf($alignment !== '', 'fo:text-align', $alignment); $temp = $style->getLineHeight(); $xmlWriter->writeAttributeIf($temp !== null, 'fo:line-height', ((string) ($temp * 100) . '%')); $xmlWriter->writeAttributeIf($style->hasPageBreakBefore() === true, 'fo:break-before', 'page'); diff --git a/src/PhpWord/Writer/PDF.php b/src/PhpWord/Writer/PDF.php index 9a8cd6ac25..f937f599c1 100644 --- a/src/PhpWord/Writer/PDF.php +++ b/src/PhpWord/Writer/PDF.php @@ -20,6 +20,7 @@ use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Writer\PDF\AbstractRenderer; /** * PDF Writer. @@ -71,6 +72,16 @@ public function __call($name, $arguments) // throw new Exception("PDF Rendering library has not been defined."); // } - return call_user_func_array([$this->renderer, $name], $arguments); + return call_user_func_array([$this->getRenderer(), $name], $arguments); + } + + public function save(string $filename): void + { + $this->getRenderer()->save($filename); + } + + public function getRenderer(): AbstractRenderer + { + return $this->renderer; } } diff --git a/src/PhpWord/Writer/PDF/AbstractRenderer.php b/src/PhpWord/Writer/PDF/AbstractRenderer.php index e8be7c06a5..c143a6cb5c 100644 --- a/src/PhpWord/Writer/PDF/AbstractRenderer.php +++ b/src/PhpWord/Writer/PDF/AbstractRenderer.php @@ -81,6 +81,7 @@ abstract class AbstractRenderer extends HTML public function __construct(PhpWord $phpWord) { parent::__construct($phpWord); + $this->isPdf = true; if ($this->includeFile != null) { $includeFile = Settings::getPdfRendererPath() . '/' . $this->includeFile; if (file_exists($includeFile)) { @@ -93,6 +94,12 @@ public function __construct(PhpWord $phpWord) // @codeCoverageIgnoreEnd } } + + // Configuration + $options = Settings::getPdfRendererOptions(); + if (!empty($options['font'])) { + $this->setFont($options['font']); + } } /** @@ -187,7 +194,6 @@ protected function prepareForSave($filename = null) throw new Exception("Could not open file $filename for writing."); } // @codeCoverageIgnoreEnd - $this->isPdf = true; return $fileHandle; } diff --git a/src/PhpWord/Writer/PDF/DomPDF.php b/src/PhpWord/Writer/PDF/DomPDF.php index 26ceb79b55..8e5b4054d5 100644 --- a/src/PhpWord/Writer/PDF/DomPDF.php +++ b/src/PhpWord/Writer/PDF/DomPDF.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Writer\PDF; use Dompdf\Dompdf as DompdfLib; +use Dompdf\Options; use PhpOffice\PhpWord\Writer\WriterInterface; /** @@ -42,15 +43,18 @@ class DomPDF extends AbstractRenderer implements WriterInterface */ protected function createExternalWriterInstance() { - return new DompdfLib(); + $options = new Options(); + if ($this->getFont()) { + $options->set('defaultFont', $this->getFont()); + } + + return new DompdfLib($options); } /** * Save PhpWord to file. - * - * @param string $filename Name of the file to save as */ - public function save($filename = null): void + public function save(string $filename): void { $fileHandle = parent::prepareForSave($filename); diff --git a/src/PhpWord/Writer/PDF/MPDF.php b/src/PhpWord/Writer/PDF/MPDF.php index f37b615561..311f743d6d 100644 --- a/src/PhpWord/Writer/PDF/MPDF.php +++ b/src/PhpWord/Writer/PDF/MPDF.php @@ -29,6 +29,9 @@ */ class MPDF extends AbstractRenderer implements WriterInterface { + public const SIMULATED_BODY_START = '<!-- simulated body start -->'; + private const BODY_TAG = '<body>'; + /** * Overridden to set the correct includefile, only needed for MPDF 5. * @@ -46,21 +49,24 @@ public function __construct(PhpWord $phpWord) /** * Gets the implementation of external PDF library that should be used. * - * @return Mpdf implementation + * @return \Mpdf\Mpdf implementation */ protected function createExternalWriterInstance() { $mPdfClass = $this->getMPdfClassName(); - return new $mPdfClass(); + $options = []; + if ($this->getFont()) { + $options['default_font'] = $this->getFont(); + } + + return new $mPdfClass($options); } /** * Save PhpWord to file. - * - * @param string $filename Name of the file to save as */ - public function save($filename = null): void + public function save(string $filename): void { $fileHandle = parent::prepareForSave($filename); @@ -82,7 +88,24 @@ public function save($filename = null): void $pdf->setKeywords($docProps->getKeywords()); $pdf->setCreator($docProps->getCreator()); - $pdf->writeHTML($this->getContent()); + $html = $this->getContent(); + $bodyLocation = strpos($html, self::SIMULATED_BODY_START); + if ($bodyLocation === false) { + $bodyLocation = strpos($html, self::BODY_TAG); + if ($bodyLocation !== false) { + $bodyLocation += strlen(self::BODY_TAG); + } + } + // Make sure first data presented to Mpdf includes body tag + // (and any htmlpageheader/htmlpagefooter tags) + // so that Mpdf doesn't parse it as content. Issue 2432. + if ($bodyLocation !== false) { + $pdf->WriteHTML(substr($html, 0, $bodyLocation)); + $html = substr($html, $bodyLocation); + } + foreach (explode("\n", $html) as $line) { + $pdf->WriteHTML("$line\n"); + } // Write to file fwrite($fileHandle, $pdf->output($filename, 'S')); diff --git a/src/PhpWord/Writer/PDF/TCPDF.php b/src/PhpWord/Writer/PDF/TCPDF.php index 02853ed7bc..1bb1974244 100644 --- a/src/PhpWord/Writer/PDF/TCPDF.php +++ b/src/PhpWord/Writer/PDF/TCPDF.php @@ -17,7 +17,11 @@ namespace PhpOffice\PhpWord\Writer\PDF; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Writer\WriterInterface; +use TCPDF as TCPDFBase; /** * TCPDF writer. @@ -42,24 +46,56 @@ class TCPDF extends AbstractRenderer implements WriterInterface * @param string $unit Unit measure * @param string $paperSize Paper size * - * @return \TCPDF implementation + * @return TCPDFBase implementation */ protected function createExternalWriterInstance($orientation, $unit, $paperSize) { - return new \TCPDF($orientation, $unit, $paperSize); + $instance = new TCPDFBase($orientation, $unit, $paperSize); + + if ($this->getFont()) { + $instance->setFont($this->getFont(), $instance->getFontStyle(), $instance->getFontSizePt()); + } + + return $instance; + } + + /** + * Overwriteable function to allow user to extend TCPDF. + * There should always be an AddPage call, preceded or followed + * by code to customize TCPDF configuration. + * The customization below sets vertical spacing + * between paragaraphs when the user has + * explicitly set those values to numeric in default style. + */ + protected function prepareToWrite(TCPDFBase $pdf): void + { + $pdf->AddPage(); + $customStyles = Style::getStyles(); + $normal = $customStyles['Normal'] ?? null; + if ($normal instanceof Style\Paragraph) { + $before = $normal->getSpaceBefore(); + $after = $normal->getSpaceAfter(); + if (is_numeric($before) && is_numeric($after)) { + $height = $normal->getLineHeight() ?? ''; + $pdf->setHtmlVSpace([ + 'p' => [ + ['n' => $before, 'h' => $height], + ['n' => $after, 'h' => $height], + ], + ]); + } + } } /** * Save PhpWord to file. - * - * @param string $filename Name of the file to save as */ - public function save($filename = null): void + public function save(string $filename): void { $fileHandle = parent::prepareForSave($filename); // PDF settings - $paperSize = 'A4'; + $paperSize = strtoupper(Settings::getDefaultPaper()); $orientation = 'P'; // Create PDF @@ -67,8 +103,8 @@ public function save($filename = null): void $pdf->setFontSubsetting(false); $pdf->setPrintHeader(false); $pdf->setPrintFooter(false); - $pdf->AddPage(); $pdf->SetFont($this->getFont()); + $this->prepareToWrite($pdf); $pdf->writeHTML($this->getContent()); // Write document properties diff --git a/src/PhpWord/Writer/RTF.php b/src/PhpWord/Writer/RTF.php index ebbf8f56c0..0a04d4f53e 100644 --- a/src/PhpWord/Writer/RTF.php +++ b/src/PhpWord/Writer/RTF.php @@ -35,8 +35,6 @@ class RTF extends AbstractWriter implements WriterInterface /** * Create new instance. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord */ public function __construct(?PhpWord $phpWord = null) { @@ -56,10 +54,8 @@ public function __construct(?PhpWord $phpWord = null) /** * Save content to file. - * - * @param string $filename */ - public function save($filename = null): void + public function save(string $filename): void { $this->writeFile($this->openFile($filename), $this->getContent()); } diff --git a/src/PhpWord/Writer/RTF/Element/AbstractElement.php b/src/PhpWord/Writer/RTF/Element/AbstractElement.php index a12469ad4c..5c33868a8b 100644 --- a/src/PhpWord/Writer/RTF/Element/AbstractElement.php +++ b/src/PhpWord/Writer/RTF/Element/AbstractElement.php @@ -24,8 +24,7 @@ use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font as FontStyle; use PhpOffice\PhpWord\Style\Paragraph as ParagraphStyle; -use PhpOffice\PhpWord\Writer\AbstractWriter; -use PhpOffice\PhpWord\Writer\HTML\Element\AbstractElement as HTMLAbstractElement; +use PhpOffice\PhpWord\Writer\RTF as WriterRTF; use PhpOffice\PhpWord\Writer\RTF\Style\Font as FontStyleWriter; use PhpOffice\PhpWord\Writer\RTF\Style\Paragraph as ParagraphStyleWriter; @@ -34,8 +33,36 @@ * * @since 0.11.0 */ -abstract class AbstractElement extends HTMLAbstractElement +abstract class AbstractElement { + /** + * Parent writer. + * + * @var WriterRTF + */ + protected $parentWriter; + + /** + * Element. + * + * @var \PhpOffice\PhpWord\Element\AbstractElement + */ + protected $element; + + /** + * Without paragraph. + * + * @var bool + */ + protected $withoutP = false; + + /** + * Write element. + * + * @return string + */ + abstract public function write(); + /** * Font style. * @@ -50,10 +77,16 @@ abstract class AbstractElement extends HTMLAbstractElement */ protected $paragraphStyle; - public function __construct(AbstractWriter $parentWriter, Element $element, $withoutP = false) - { - parent::__construct($parentWriter, $element, $withoutP); + /** + * @var \PhpOffice\PhpWord\Escaper\EscaperInterface + */ + protected $escaper; + public function __construct(WriterRTF $parentWriter, Element $element, bool $withoutP = false) + { + $this->parentWriter = $parentWriter; + $this->element = $element; + $this->withoutP = $withoutP; $this->escaper = new Rtf(); } diff --git a/src/PhpWord/Writer/RTF/Element/Container.php b/src/PhpWord/Writer/RTF/Element/Container.php index 7f43cb6aaa..5e198aec86 100644 --- a/src/PhpWord/Writer/RTF/Element/Container.php +++ b/src/PhpWord/Writer/RTF/Element/Container.php @@ -17,14 +17,14 @@ namespace PhpOffice\PhpWord\Writer\RTF\Element; -use PhpOffice\PhpWord\Writer\HTML\Element\Container as HTMLContainer; +use PhpOffice\PhpWord\Element\AbstractContainer as ContainerElement; /** * Container element RTF writer. * * @since 0.11.0 */ -class Container extends HTMLContainer +class Container extends AbstractElement { /** * Namespace; Can't use __NAMESPACE__ in inherited class (RTF). @@ -32,4 +32,33 @@ class Container extends HTMLContainer * @var string */ protected $namespace = 'PhpOffice\\PhpWord\\Writer\\RTF\\Element'; + + /** + * Write container. + * + * @return string + */ + public function write() + { + $container = $this->element; + if (!$container instanceof ContainerElement) { + return ''; + } + $containerClass = substr(get_class($container), strrpos(get_class($container), '\\') + 1); + $withoutP = in_array($containerClass, ['TextRun', 'Footnote', 'Endnote']) ? true : false; + $content = ''; + + $elements = $container->getElements(); + foreach ($elements as $element) { + $elementClass = get_class($element); + $writerClass = str_replace('PhpOffice\\PhpWord\\Element', $this->namespace, $elementClass); + if (class_exists($writerClass)) { + /** @var AbstractElement $writer Type hint */ + $writer = new $writerClass($this->parentWriter, $element, $withoutP); + $content .= $writer->write(); + } + } + + return $content; + } } diff --git a/src/PhpWord/Writer/RTF/Element/Field.php b/src/PhpWord/Writer/RTF/Element/Field.php index 37f203aca8..34024f5e51 100644 --- a/src/PhpWord/Writer/RTF/Element/Field.php +++ b/src/PhpWord/Writer/RTF/Element/Field.php @@ -17,10 +17,12 @@ namespace PhpOffice\PhpWord\Writer\RTF\Element; +use PhpOffice\PhpWord\Element\Field as ElementField; + /** * Field element writer. * - * Note: for now, only date, page and numpages fields are implemented for RTF. + * Note: for now, only date, page, numpages and filename fields are implemented for RTF. */ class Field extends Text { @@ -30,7 +32,7 @@ class Field extends Text public function write() { $element = $this->element; - if (!$element instanceof \PhpOffice\PhpWord\Element\Field) { + if (!$element instanceof ElementField) { return; } @@ -66,7 +68,18 @@ protected function writeNumpages() return 'NUMPAGES'; } - protected function writeDate(\PhpOffice\PhpWord\Element\Field $element) + protected function writeFilename(ElementField $element): string + { + $content = 'FILENAME'; + $options = $element->getOptions(); + if ($options != null && in_array('Path', $options)) { + $content .= ' \\\\p'; + } + + return $content; + } + + protected function writeDate(ElementField $element) { $content = ''; $content .= 'DATE'; diff --git a/src/PhpWord/Writer/RTF/Element/Table.php b/src/PhpWord/Writer/RTF/Element/Table.php index 5d0755c931..f2d5f9fb5a 100644 --- a/src/PhpWord/Writer/RTF/Element/Table.php +++ b/src/PhpWord/Writer/RTF/Element/Table.php @@ -20,6 +20,11 @@ use PhpOffice\PhpWord\Element\Cell as CellElement; use PhpOffice\PhpWord\Element\Row as RowElement; use PhpOffice\PhpWord\Element\Table as TableElement; +use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\SimpleType\Border; +use PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\Style\Cell as CellStyle; +use PhpOffice\PhpWord\Style\Table as TableStyle; /** * Table element RTF writer. @@ -28,6 +33,11 @@ */ class Table extends AbstractElement { + /** + * @var TableElement + */ + protected $element; + /** * Write element. * @@ -45,6 +55,9 @@ public function write() } $content = ''; + $style = $this->element->getStyle(); + $bidiStyle = (is_object($style) && method_exists($style, 'isBidiVisual')) ? $style->isBidiVisual() : Settings::isDefaultRtl(); + $bidi = $bidiStyle ? '\rtlrow' : ''; $rows = $element->getRows(); $rowCount = count($rows); @@ -52,7 +65,7 @@ public function write() $content .= '\pard' . PHP_EOL; for ($i = 0; $i < $rowCount; ++$i) { - $content .= '\trowd '; + $content .= "\\trowd$bidi "; $content .= $this->writeRowDef($rows[$i]); $content .= PHP_EOL; $content .= $this->writeRow($rows[$i]); @@ -72,9 +85,18 @@ public function write() private function writeRowDef(RowElement $row) { $content = ''; + $tableStyle = $this->element->getStyle(); + if (is_string($tableStyle)) { + $tableStyle = Style::getStyle($tableStyle); + if (!($tableStyle instanceof TableStyle)) { + $tableStyle = null; + } + } $rightMargin = 0; foreach ($row->getCells() as $cell) { + $content .= $this->writeCellStyle($cell->getStyle(), $tableStyle); + $width = $cell->getWidth(); $vMerge = $this->getVMerge($cell->getStyle()->getVMerge()); if ($width === null) { @@ -122,6 +144,103 @@ private function writeCell(CellElement $cell) return $content; } + private function writeCellStyle(CellStyle $cell, ?TableStyle $table): string + { + $content = $this->writeCellBorder( + 't', + $cell->getBorderTopStyle() ?: ($table ? $table->getBorderTopStyle() : null), + (int) round($cell->getBorderTopSize() ?: ($table ? ($table->getBorderTopSize() ?: 0) : 0)), + $cell->getBorderTopColor() ?? ($table ? $table->getBorderTopColor() : null) + ); + $content .= $this->writeCellBorder( + 'l', + $cell->getBorderLeftStyle() ?: ($table ? $table->getBorderLeftStyle() : null), + (int) round($cell->getBorderLeftSize() ?: ($table ? ($table->getBorderLeftSize() ?: 0) : 0)), + $cell->getBorderLeftColor() ?? ($table ? $table->getBorderLeftColor() : null) + ); + $content .= $this->writeCellBorder( + 'b', + $cell->getBorderBottomStyle() ?: ($table ? $table->getBorderBottomStyle() : null), + (int) round($cell->getBorderBottomSize() ?: ($table ? ($table->getBorderBottomSize() ?: 0) : 0)), + $cell->getBorderBottomColor() ?? ($table ? $table->getBorderBottomColor() : null) + ); + $content .= $this->writeCellBorder( + 'r', + $cell->getBorderRightStyle() ?: ($table ? $table->getBorderRightStyle() : null), + (int) round($cell->getBorderRightSize() ?: ($table ? ($table->getBorderRightSize() ?: 0) : 0)), + $cell->getBorderRightColor() ?? ($table ? $table->getBorderRightColor() : null) + ); + + return $content; + } + + private function writeCellBorder(string $prefix, ?string $borderStyle, int $borderSize, ?string $borderColor): string + { + if ($borderSize == 0) { + return ''; + } + + $content = '\clbrdr' . $prefix; + /** + * \brdrs Single-thickness border. + * \brdrth Double-thickness border. + * \brdrsh Shadowed border. + * \brdrdb Double border. + * \brdrdot Dotted border. + * \brdrdash Dashed border. + * \brdrhair Hairline border. + * \brdrinset Inset border. + * \brdrdashsm Dash border (small). + * \brdrdashd Dot dash border. + * \brdrdashdd Dot dot dash border. + * \brdroutset Outset border. + * \brdrtriple Triple border. + * \brdrtnthsg Thick thin border (small). + * \brdrthtnsg Thin thick border (small). + * \brdrtnthtnsg Thin thick thin border (small). + * \brdrtnthmg Thick thin border (medium). + * \brdrthtnmg Thin thick border (medium). + * \brdrtnthtnmg Thin thick thin border (medium). + * \brdrtnthlg Thick thin border (large). + * \brdrthtnlg Thin thick border (large). + * \brdrtnthtnlg Thin thick thin border (large). + * \brdrwavy Wavy border. + * \brdrwavydb Double wavy border. + * \brdrdashdotstr Striped border. + * \brdremboss Emboss border. + * \brdrengrave Engrave border. + */ + switch ($borderStyle) { + case Border::DOTTED: + $content .= '\brdrdot'; + + break; + case Border::SINGLE: + default: + $content .= '\brdrs'; + + break; + } + + // \brdrwN N is the width in twips (1/20 pt) of the pen used to draw the paragraph border line. + // N cannot be greater than 75. + // To obtain a larger border width, the \brdth control word can be used to obtain a width double that of N. + // $borderSize is in eights of a point, i.e. 4 / 8 = .5pt + // 1/20 pt => 1/8 / 2.5 + $content .= '\brdrw' . (int) ($borderSize / 2.5); + + // \brdrcfN N is the color of the paragraph border, specified as an index into the color table in the RTF header. + $colorIndex = 0; + $index = array_search($borderColor, $this->parentWriter->getColorTable()); + if ($index !== false) { + $colorIndex = (int) $index + 1; + } + $content .= '\brdrcf' . $colorIndex; + $content .= PHP_EOL; + + return $content; + } + /** * Get vertical merge style. * diff --git a/src/PhpWord/Writer/RTF/Element/Title.php b/src/PhpWord/Writer/RTF/Element/Title.php index 653ba93df7..fb11da7849 100644 --- a/src/PhpWord/Writer/RTF/Element/Title.php +++ b/src/PhpWord/Writer/RTF/Element/Title.php @@ -71,7 +71,7 @@ public function write() $style = $element->getStyle(); if (is_string($style)) { $style = str_replace('Heading', '', $style); - if (is_numeric($style)) { + if ("$style" !== '') { $style = (int) $style - 1; if ($style >= 0 && $style <= 8) { $content .= '{\\outlinelevel' . $style; diff --git a/src/PhpWord/Writer/RTF/Part/AbstractPart.php b/src/PhpWord/Writer/RTF/Part/AbstractPart.php index d1bca0a4e2..be772b93a0 100644 --- a/src/PhpWord/Writer/RTF/Part/AbstractPart.php +++ b/src/PhpWord/Writer/RTF/Part/AbstractPart.php @@ -27,7 +27,7 @@ abstract class AbstractPart { /** - * @var \PhpOffice\PhpWord\Writer\AbstractWriter + * @var \PhpOffice\PhpWord\Writer\RTF */ private $parentWriter; @@ -47,7 +47,7 @@ public function __construct() abstract public function write(); /** - * @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer + * @param \PhpOffice\PhpWord\Writer\RTF $writer */ public function setParentWriter(?AbstractWriter $writer = null): void { @@ -55,7 +55,7 @@ public function setParentWriter(?AbstractWriter $writer = null): void } /** - * @return \PhpOffice\PhpWord\Writer\AbstractWriter + * @return \PhpOffice\PhpWord\Writer\RTF */ public function getParentWriter() { diff --git a/src/PhpWord/Writer/RTF/Part/Document.php b/src/PhpWord/Writer/RTF/Part/Document.php index b452b9e937..a0002583a9 100644 --- a/src/PhpWord/Writer/RTF/Part/Document.php +++ b/src/PhpWord/Writer/RTF/Part/Document.php @@ -148,7 +148,13 @@ private function writeSections() $sections = $this->getParentWriter()->getPhpWord()->getSections(); $evenOdd = $this->getParentWriter()->getPhpWord()->getSettings()->hasEvenAndOddHeaders(); + $sectOwed = false; foreach ($sections as $section) { + if ($sectOwed) { + $content .= '\sect' . PHP_EOL; + } else { + $sectOwed = true; + } $styleWriter = new SectionStyleWriter($section->getStyle()); $styleWriter->setParentWriter($this->getParentWriter()); $content .= $styleWriter->write(); @@ -197,8 +203,6 @@ private function writeSections() $elementWriter = new Container($this->getParentWriter(), $section); $content .= $elementWriter->write(); - - $content .= '\sect' . PHP_EOL; } return $content; diff --git a/src/PhpWord/Writer/RTF/Part/Header.php b/src/PhpWord/Writer/RTF/Part/Header.php index e4ad4bee0b..7f8cc84b97 100644 --- a/src/PhpWord/Writer/RTF/Part/Header.php +++ b/src/PhpWord/Writer/RTF/Part/Header.php @@ -21,6 +21,7 @@ use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style\Font; +use PhpOffice\PhpWord\Style\Table; /** * RTF header part writer. @@ -236,6 +237,14 @@ private function registerFontItems($style): void $this->registerTableItem($this->fontTable, $style->getName(), $defaultFont); $this->registerTableItem($this->colorTable, $style->getColor(), $defaultColor); $this->registerTableItem($this->colorTable, $style->getFgColor(), $defaultColor); + + return; + } + if ($style instanceof Table) { + $this->registerTableItem($this->colorTable, $style->getBorderTopColor(), $defaultColor); + $this->registerTableItem($this->colorTable, $style->getBorderRightColor(), $defaultColor); + $this->registerTableItem($this->colorTable, $style->getBorderLeftColor(), $defaultColor); + $this->registerTableItem($this->colorTable, $style->getBorderBottomColor(), $defaultColor); } } diff --git a/src/PhpWord/Writer/RTF/Style/AbstractStyle.php b/src/PhpWord/Writer/RTF/Style/AbstractStyle.php index e7117d3261..355e384440 100644 --- a/src/PhpWord/Writer/RTF/Style/AbstractStyle.php +++ b/src/PhpWord/Writer/RTF/Style/AbstractStyle.php @@ -17,13 +17,91 @@ namespace PhpOffice\PhpWord\Writer\RTF\Style; -use PhpOffice\PhpWord\Writer\HTML\Style\AbstractStyle as HTMLAbstractStyle; +use PhpOffice\PhpWord\Style\AbstractStyle as StyleAbstract; +use PhpOffice\PhpWord\Writer\RTF; /** * Abstract RTF style writer. * * @since 0.11.0 */ -abstract class AbstractStyle extends HTMLAbstractStyle +abstract class AbstractStyle { + /** + * Parent writer. + * + * @var RTF + */ + private $parentWriter; + + /** + * Style. + * + * @var null|array|StyleAbstract + */ + private $style; + + /** + * Write style. + * + * @return mixed + */ + abstract public function write(); + + /** + * Create new instance. + * + * @param array|StyleAbstract $style + */ + public function __construct($style = null) + { + $this->style = $style; + } + + /** + * Set parent writer. + * + * @param RTF $writer + */ + public function setParentWriter($writer): void + { + $this->parentWriter = $writer; + } + + /** + * Get parent writer. + * + * @return RTF + */ + public function getParentWriter() + { + return $this->parentWriter; + } + + /** + * Get style. + * + * @return null|array|string|StyleAbstract + */ + public function getStyle() + { + if (!$this->style instanceof StyleAbstract && !is_array($this->style)) { + return ''; + } + + return $this->style; + } + + /** + * Get value if ... + * + * @param null|bool $condition + * @param string $value + * + * @return string + */ + protected function getValueIf($condition, $value) + { + return $condition == true ? $value : ''; + } } diff --git a/src/PhpWord/Writer/RTF/Style/Paragraph.php b/src/PhpWord/Writer/RTF/Style/Paragraph.php index c61f3ab23d..e19d24bbad 100644 --- a/src/PhpWord/Writer/RTF/Style/Paragraph.php +++ b/src/PhpWord/Writer/RTF/Style/Paragraph.php @@ -35,6 +35,10 @@ class Paragraph extends AbstractStyle */ private $nestedLevel = 0; + private const LEFT = Jc::LEFT; + private const RIGHT = Jc::RIGHT; + private const JUSTIFY = Jc::JUSTIFY; + /** * Write style. * @@ -52,6 +56,18 @@ public function write() Jc::END => '\qr', Jc::CENTER => '\qc', Jc::BOTH => '\qj', + self::LEFT => '\ql', + self::RIGHT => '\qr', + self::JUSTIFY => '\qj', + ]; + $bidiAlignments = [ + Jc::START => '\qr', + Jc::END => '\ql', + Jc::CENTER => '\qc', + Jc::BOTH => '\qj', + self::LEFT => '\ql', + self::RIGHT => '\qr', + self::JUSTIFY => '\qj', ]; $spaceAfter = $style->getSpaceAfter(); @@ -61,8 +77,13 @@ public function write() if ($this->nestedLevel == 0) { $content .= '\pard\nowidctlpar '; } - if (isset($alignments[$style->getAlignment()])) { - $content .= $alignments[$style->getAlignment()]; + $alignment = $style->getAlignment(); + $bidi = $style->isBidi(); + if ($alignment === '' && $bidi !== null) { + $alignment = Jc::START; + } + if (isset($alignments[$alignment])) { + $content .= $bidi ? $bidiAlignments[$alignment] : $alignments[$alignment]; } $content .= $this->writeIndentation($style->getIndentation()); $content .= $this->getValueIf($spaceBefore !== null, '\sb' . round($spaceBefore ?? 0)); diff --git a/src/PhpWord/Writer/Word2007.php b/src/PhpWord/Writer/Word2007.php index 3895584671..e7801c04c2 100644 --- a/src/PhpWord/Writer/Word2007.php +++ b/src/PhpWord/Writer/Word2007.php @@ -52,14 +52,15 @@ public function __construct(?PhpWord $phpWord = null) $this->setPhpWord($phpWord); // Create parts + // The first four files need to be in this order for Mimetype detection to work $this->parts = [ 'ContentTypes' => '[Content_Types].xml', 'Rels' => '_rels/.rels', + 'RelsDocument' => 'word/_rels/document.xml.rels', + 'Document' => 'word/document.xml', 'DocPropsApp' => 'docProps/app.xml', 'DocPropsCore' => 'docProps/core.xml', 'DocPropsCustom' => 'docProps/custom.xml', - 'RelsDocument' => 'word/_rels/document.xml.rels', - 'Document' => 'word/document.xml', 'Comments' => 'word/comments.xml', 'Styles' => 'word/styles.xml', 'Numbering' => 'word/numbering.xml', @@ -90,10 +91,8 @@ public function __construct(?PhpWord $phpWord = null) /** * Save document by name. - * - * @param string $filename */ - public function save($filename = null): void + public function save(string $filename): void { $filename = $this->getTempFile($filename); $zip = $this->getZipArchive($filename); @@ -120,7 +119,7 @@ public function save($filename = null): void $this->addHeaderFooterMedia($zip, 'footer'); // Add header/footer contents - $rId = Media::countElements('section') + 6; // @see Rels::writeDocRels for 6 first elements + $rId = Media::countElements('section') + 6; //@see Rels::writeDocRels for 6 first elements $sections = $phpWord->getSections(); foreach ($sections as $section) { $this->addHeaderFooterContent($section, $zip, 'header', $rId); @@ -228,7 +227,6 @@ private function addNotes(ZipArchive $zip, &$rId, $noteType = 'footnote'): void $collection = $phpWord->$method(); // Add footnotes media files, relations, and contents - /** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collection Type hint */ if ($collection->countItems() > 0) { $media = Media::getElements($noteType); $this->addFilesToPackage($zip, $media); @@ -261,7 +259,6 @@ private function addComments(ZipArchive $zip, &$rId): void $partName = 'comments'; // Add comment relations and contents - /** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collection Type hint */ if ($collection->countItems() > 0) { $this->relationships[] = ['target' => "{$partName}.xml", 'type' => $partName, 'rID' => ++$rId]; diff --git a/src/PhpWord/Writer/Word2007/Element/AbstractElement.php b/src/PhpWord/Writer/Word2007/Element/AbstractElement.php index a7bfeab78b..a71ec6f95e 100644 --- a/src/PhpWord/Writer/Word2007/Element/AbstractElement.php +++ b/src/PhpWord/Writer/Word2007/Element/AbstractElement.php @@ -21,6 +21,7 @@ use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\Text as SharedText; use PhpOffice\PhpWord\Shared\XMLWriter; +use PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart; /** * Abstract element writer. @@ -50,6 +51,11 @@ abstract class AbstractElement */ protected $withoutP = false; + /** + * @var null|AbstractPart + */ + protected $part; + /** * Write element. */ @@ -57,10 +63,8 @@ abstract public function write(); /** * Create new instance. - * - * @param bool $withoutP */ - public function __construct(XMLWriter $xmlWriter, Element $element, $withoutP = false) + public function __construct(XMLWriter $xmlWriter, Element $element, bool $withoutP = false) { $this->xmlWriter = $xmlWriter; $this->element = $element; @@ -120,14 +124,10 @@ protected function endElementP(): void */ protected function writeCommentRangeStart(): void { - if ($this->element->getCommentRangeStart() != null) { - $comment = $this->element->getCommentRangeStart(); - //only set the ID if it is not yet set, otherwise it will overwrite it - if ($comment->getElementId() == null) { - $comment->setElementId(); + if ($this->element->getCommentsRangeStart() != null) { + foreach ($this->element->getCommentsRangeStart()->getItems() as $comment) { + $this->xmlWriter->writeElementBlock('w:commentRangeStart', ['w:id' => $comment->getElementId()]); } - - $this->xmlWriter->writeElementBlock('w:commentRangeStart', ['w:id' => $comment->getElementId()]); } } @@ -136,28 +136,23 @@ protected function writeCommentRangeStart(): void */ protected function writeCommentRangeEnd(): void { - if ($this->element->getCommentRangeEnd() != null) { - $comment = $this->element->getCommentRangeEnd(); - //only set the ID if it is not yet set, otherwise it will overwrite it, this should normally not happen - if ($comment->getElementId() == null) { - $comment->setElementId(); // @codeCoverageIgnore - } // @codeCoverageIgnore - - $this->xmlWriter->writeElementBlock('w:commentRangeEnd', ['w:id' => $comment->getElementId()]); - $this->xmlWriter->startElement('w:r'); - $this->xmlWriter->writeElementBlock('w:commentReference', ['w:id' => $comment->getElementId()]); - $this->xmlWriter->endElement(); - } elseif ($this->element->getCommentRangeStart() != null && $this->element->getCommentRangeStart()->getEndElement() == null) { - $comment = $this->element->getCommentRangeStart(); - //only set the ID if it is not yet set, otherwise it will overwrite it, this should normally not happen - if ($comment->getElementId() == null) { - $comment->setElementId(); // @codeCoverageIgnore - } // @codeCoverageIgnore - - $this->xmlWriter->writeElementBlock('w:commentRangeEnd', ['w:id' => $comment->getElementId()]); - $this->xmlWriter->startElement('w:r'); - $this->xmlWriter->writeElementBlock('w:commentReference', ['w:id' => $comment->getElementId()]); - $this->xmlWriter->endElement(); + if ($this->element->getCommentsRangeEnd() != null) { + foreach ($this->element->getCommentsRangeEnd()->getItems() as $comment) { + $this->xmlWriter->writeElementBlock('w:commentRangeEnd', ['w:id' => $comment->getElementId()]); + $this->xmlWriter->startElement('w:r'); + $this->xmlWriter->writeElementBlock('w:commentReference', ['w:id' => $comment->getElementId()]); + $this->xmlWriter->endElement(); + } + } + if ($this->element->getCommentsRangeStart() != null) { + foreach ($this->element->getCommentsRangeStart()->getItems() as $comment) { + if ($comment->getEndElement() == null) { + $this->xmlWriter->writeElementBlock('w:commentRangeEnd', ['w:id' => $comment->getElementId()]); + $this->xmlWriter->startElement('w:r'); + $this->xmlWriter->writeElementBlock('w:commentReference', ['w:id' => $comment->getElementId()]); + $this->xmlWriter->endElement(); + } + } } } @@ -224,4 +219,16 @@ protected function writeText($content) return $this->getXmlWriter()->writeRaw($content); } + + public function setPart(?AbstractPart $part): self + { + $this->part = $part; + + return $this; + } + + public function getPart(): ?AbstractPart + { + return $this->part; + } } diff --git a/src/PhpWord/Writer/Word2007/Element/Container.php b/src/PhpWord/Writer/Word2007/Element/Container.php index b6db45197a..919954c152 100644 --- a/src/PhpWord/Writer/Word2007/Element/Container.php +++ b/src/PhpWord/Writer/Word2007/Element/Container.php @@ -36,6 +36,11 @@ class Container extends AbstractElement */ protected $namespace = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element'; + /** + * @var array<string> + */ + protected $containerWithoutP = ['TextRun', 'Footnote', 'Endnote', 'ListItemRun']; + /** * Write element. */ @@ -46,7 +51,7 @@ public function write(): void return; } $containerClass = substr(get_class($container), strrpos(get_class($container), '\\') + 1); - $withoutP = in_array($containerClass, ['TextRun', 'Footnote', 'Endnote', 'ListItemRun']); + $withoutP = in_array($containerClass, $this->containerWithoutP); $xmlWriter = $this->getXmlWriter(); // Loop through elements @@ -70,12 +75,8 @@ public function write(): void /** * Write individual element. - * - * @param bool $withoutP - * - * @return string */ - private function writeElement(XMLWriter $xmlWriter, Element $element, $withoutP) + private function writeElement(XMLWriter $xmlWriter, Element $element, bool $withoutP): string { $elementClass = substr(get_class($element), strrpos(get_class($element), '\\') + 1); $writerClass = $this->namespace . '\\' . $elementClass; @@ -83,6 +84,7 @@ private function writeElement(XMLWriter $xmlWriter, Element $element, $withoutP) if (class_exists($writerClass)) { /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $writer Type hint */ $writer = new $writerClass($xmlWriter, $element, $withoutP); + $writer->setPart($this->getPart()); $writer->write(); } diff --git a/src/PhpWord/Writer/Word2007/Element/Field.php b/src/PhpWord/Writer/Word2007/Element/Field.php index 6fdb48b0f4..2977c01626 100644 --- a/src/PhpWord/Writer/Word2007/Element/Field.php +++ b/src/PhpWord/Writer/Word2007/Element/Field.php @@ -17,6 +17,9 @@ namespace PhpOffice\PhpWord\Writer\Word2007\Element; +use PhpOffice\PhpWord\Element\Field as ElementField; +use PhpOffice\PhpWord\Element\TextRun; + /** * Field element writer. * @@ -30,7 +33,7 @@ class Field extends Text public function write(): void { $element = $this->getElement(); - if (!$element instanceof \PhpOffice\PhpWord\Element\Field) { + if (!$element instanceof ElementField) { return; } @@ -42,7 +45,7 @@ public function write(): void } } - private function writeDefault(\PhpOffice\PhpWord\Element\Field $element): void + private function writeDefault(ElementField $element): void { $xmlWriter = $this->getXmlWriter(); $this->startElementP(); @@ -73,7 +76,7 @@ private function writeDefault(\PhpOffice\PhpWord\Element\Field $element): void $xmlWriter->endElement(); // w:r if ($element->getText() != null) { - if ($element->getText() instanceof \PhpOffice\PhpWord\Element\TextRun) { + if ($element->getText() instanceof TextRun) { $containerWriter = new Container($xmlWriter, $element->getText(), true); $containerWriter->write(); @@ -120,7 +123,7 @@ private function writeDefault(\PhpOffice\PhpWord\Element\Field $element): void * * //TODO A lot of code duplication with general method, should maybe be refactored */ - protected function writeMacrobutton(\PhpOffice\PhpWord\Element\Field $element): void + protected function writeMacrobutton(ElementField $element): void { $xmlWriter = $this->getXmlWriter(); $this->startElementP(); @@ -159,22 +162,22 @@ protected function writeMacrobutton(\PhpOffice\PhpWord\Element\Field $element): $this->endElementP(); // w:p } - private function buildPropertiesAndOptions(\PhpOffice\PhpWord\Element\Field $element) + private function buildPropertiesAndOptions(ElementField $element) { $propertiesAndOptions = ''; $properties = $element->getProperties(); foreach ($properties as $propkey => $propval) { switch ($propkey) { case 'format': - $propertiesAndOptions .= '\* ' . $propval . ' '; + $propertiesAndOptions .= '\\* ' . $propval . ' '; break; case 'numformat': - $propertiesAndOptions .= '\# ' . $propval . ' '; + $propertiesAndOptions .= '\\# ' . $propval . ' '; break; case 'dateformat': - $propertiesAndOptions .= '\@ "' . $propval . '" '; + $propertiesAndOptions .= '\\@ "' . $propval . '" '; break; case 'macroname': @@ -192,27 +195,31 @@ private function buildPropertiesAndOptions(\PhpOffice\PhpWord\Element\Field $ele foreach ($options as $option) { switch ($option) { case 'PreserveFormat': - $propertiesAndOptions .= '\* MERGEFORMAT '; + $propertiesAndOptions .= '\\* MERGEFORMAT '; break; case 'LunarCalendar': - $propertiesAndOptions .= '\h '; + $propertiesAndOptions .= '\\h '; break; case 'SakaEraCalendar': - $propertiesAndOptions .= '\s '; + $propertiesAndOptions .= '\\s '; break; case 'LastUsedFormat': - $propertiesAndOptions .= '\l '; + $propertiesAndOptions .= '\\l '; break; case 'Bold': - $propertiesAndOptions .= '\b '; + $propertiesAndOptions .= '\\b '; break; case 'Italic': - $propertiesAndOptions .= '\i '; + $propertiesAndOptions .= '\\i '; + + break; + case 'Path': + $propertiesAndOptions .= '\\p '; break; default: @@ -222,4 +229,104 @@ private function buildPropertiesAndOptions(\PhpOffice\PhpWord\Element\Field $ele return $propertiesAndOptions; } + + /** + * Writes a REF field. + */ + protected function writeRef(ElementField $element): void + { + $xmlWriter = $this->getXmlWriter(); + $this->startElementP(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'begin'); + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + + $instruction = ' ' . $element->getType() . ' '; + + foreach ($element->getProperties() as $property) { + $instruction .= $property . ' '; + } + foreach ($element->getOptions() as $optionKey => $optionValue) { + $instruction .= $this->convertRefOption($optionKey, $optionValue) . ' '; + } + + $xmlWriter->startElement('w:r'); + $this->writeFontStyle(); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->text($instruction); + $xmlWriter->endElement(); // w:instrText + $xmlWriter->endElement(); // w:r + + if ($element->getText() != null) { + if ($element->getText() instanceof \PhpOffice\PhpWord\Element\TextRun) { + $containerWriter = new Container($xmlWriter, $element->getText(), true); + $containerWriter->write(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->text('"' . $this->buildPropertiesAndOptions($element)); + $xmlWriter->endElement(); // w:instrText + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->text(' '); + $xmlWriter->endElement(); // w:instrText + $xmlWriter->endElement(); // w:r + } + } + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'separate'); + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:noProof'); + $xmlWriter->endElement(); // w:noProof + $xmlWriter->endElement(); // w:rPr + $xmlWriter->writeElement('w:t', $element->getText() != null && is_string($element->getText()) ? $element->getText() : '1'); + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'end'); + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + + $this->endElementP(); // w:p + } + + private function convertRefOption(string $optionKey, string $optionValue): string + { + if ($optionKey === 'NumberSeperatorSequence') { + return '\\d ' . $optionValue; + } + + switch ($optionValue) { + case 'IncrementAndInsertText': + return '\\f'; + case 'CreateHyperLink': + return '\\h'; + case 'NoTrailingPeriod': + return '\\n'; + case 'IncludeAboveOrBelow': + return '\\p'; + case 'InsertParagraphNumberRelativeContext': + return '\\r'; + case 'SuppressNonDelimiterNonNumericalText': + return '\\t'; + case 'InsertParagraphNumberFullContext': + return '\\w'; + default: + return ''; + } + } } diff --git a/src/PhpWord/Writer/Word2007/Element/Footnote.php b/src/PhpWord/Writer/Word2007/Element/Footnote.php index b59fc5e1cc..77073a239d 100644 --- a/src/PhpWord/Writer/Word2007/Element/Footnote.php +++ b/src/PhpWord/Writer/Word2007/Element/Footnote.php @@ -51,7 +51,7 @@ public function write(): void $xmlWriter->endElement(); // w:rStyle $xmlWriter->endElement(); // w:rPr $xmlWriter->startElement("w:{$this->referenceType}"); - $xmlWriter->writeAttribute('w:id', $element->getRelationId()); + $xmlWriter->writeAttribute('w:id', $element->getRelationId() + 1); $xmlWriter->endElement(); // w:$referenceType $xmlWriter->endElement(); // w:r diff --git a/src/PhpWord/Writer/Word2007/Element/Formula.php b/src/PhpWord/Writer/Word2007/Element/Formula.php new file mode 100644 index 0000000000..6abb74b782 --- /dev/null +++ b/src/PhpWord/Writer/Word2007/Element/Formula.php @@ -0,0 +1,50 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWord\Writer\Word2007\Element; + +use PhpOffice\Math\Writer\OfficeMathML; +use PhpOffice\PhpWord\Element\Formula as FormulaElement; + +/** + * Formula element writer. + */ +class Formula extends AbstractElement +{ + /** + * Write element. + */ + public function write(): void + { + $element = $this->getElement(); + if (!$element instanceof FormulaElement) { + return; + } + + $this->startElementP(); + + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->writeElement('w:rPr'); + $xmlWriter->endElement(); + + $xmlWriter->writeRaw((new OfficeMathML())->write($element->getMath())); + + $this->endElementP(); + } +} diff --git a/src/PhpWord/Writer/Word2007/Element/TOC.php b/src/PhpWord/Writer/Word2007/Element/TOC.php index 90b4ce3d9e..2cf76155e4 100644 --- a/src/PhpWord/Writer/Word2007/Element/TOC.php +++ b/src/PhpWord/Writer/Word2007/Element/TOC.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWord\Writer\Word2007\Element; +use PhpOffice\PhpWord\Element\Title; use PhpOffice\PhpWord\Element\TOC as TOCElement; use PhpOffice\PhpWord\Shared\XMLWriter; use PhpOffice\PhpWord\Style\Font; @@ -63,17 +64,14 @@ public function write(): void /** * Write title. - * - * @param \PhpOffice\PhpWord\Element\Title $title - * @param bool $writeFieldMark */ - private function writeTitle(XMLWriter $xmlWriter, TOCElement $element, $title, $writeFieldMark): void + private function writeTitle(XMLWriter $xmlWriter, TOCElement $element, Title $title, bool $writeFieldMark): void { $tocStyle = $element->getStyleTOC(); $fontStyle = $element->getStyleFont(); $isObject = ($fontStyle instanceof Font) ? true : false; $rId = $title->getRelationId(); - $indent = ($title->getDepth() - 1) * $tocStyle->getIndent(); + $indent = (int) (($title->getDepth() - 1) * $tocStyle->getIndent()); $xmlWriter->startElement('w:p'); @@ -95,7 +93,10 @@ private function writeTitle(XMLWriter $xmlWriter, TOCElement $element, $title, $ $styleWriter->write(); } $xmlWriter->startElement('w:t'); - $this->writeText($title->getText()); + + $titleText = $title->getText(); + $this->writeText(is_string($titleText) ? $titleText : ''); + $xmlWriter->endElement(); // w:t $xmlWriter->endElement(); // w:r @@ -116,6 +117,20 @@ private function writeTitle(XMLWriter $xmlWriter, TOCElement $element, $title, $ $xmlWriter->endElement(); $xmlWriter->endElement(); + if ($title->getPageNumber() !== null) { + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'separate'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:t'); + $xmlWriter->text((string) $title->getPageNumber()); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + } + $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:fldChar'); $xmlWriter->writeAttribute('w:fldCharType', 'end'); @@ -129,10 +144,8 @@ private function writeTitle(XMLWriter $xmlWriter, TOCElement $element, $title, $ /** * Write style. - * - * @param int $indent */ - private function writeStyle(XMLWriter $xmlWriter, TOCElement $element, $indent): void + private function writeStyle(XMLWriter $xmlWriter, TOCElement $element, int $indent): void { $tocStyle = $element->getStyleTOC(); $fontStyle = $element->getStyleFont(); diff --git a/src/PhpWord/Writer/Word2007/Element/Table.php b/src/PhpWord/Writer/Word2007/Element/Table.php index 9364fe45c1..a32cc19639 100644 --- a/src/PhpWord/Writer/Word2007/Element/Table.php +++ b/src/PhpWord/Writer/Word2007/Element/Table.php @@ -103,8 +103,14 @@ private function writeRow(XMLWriter $xmlWriter, RowElement $row): void } // Write cells - foreach ($row->getCells() as $cell) { - $this->writeCell($xmlWriter, $cell); + $cells = $row->getCells(); + if (count($cells) === 0) { + // issue 2505 - Word treats doc as corrupt if row without cell + $this->writeCell($xmlWriter, new CellElement()); + } else { + foreach ($cells as $cell) { + $this->writeCell($xmlWriter, $cell); + } } $xmlWriter->endElement(); // w:tr diff --git a/src/PhpWord/Writer/Word2007/Element/TextBox.php b/src/PhpWord/Writer/Word2007/Element/TextBox.php index d264f084dd..ff94094de7 100644 --- a/src/PhpWord/Writer/Word2007/Element/TextBox.php +++ b/src/PhpWord/Writer/Word2007/Element/TextBox.php @@ -2,10 +2,8 @@ /** * This file is part of PHPWord - A pure PHP library for reading and writing * word processing documents. - * * PHPWord is free software distributed under the terms of the GNU Lesser * General Public License version 3 as published by the Free Software Foundation. - * * For the full copyright and license information, please read the LICENSE * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. @@ -50,6 +48,10 @@ public function write(): void $xmlWriter->startElement('v:shape'); $xmlWriter->writeAttribute('type', '#_x0000_t0202'); + if ($style->getBgColor()) { + $xmlWriter->writeAttribute('fillcolor', $style->getBgColor()); + } + $styleWriter->write(); $styleWriter->writeBorder(); diff --git a/src/PhpWord/Writer/Word2007/Element/Title.php b/src/PhpWord/Writer/Word2007/Element/Title.php index dd46d755e3..072dcc8d84 100644 --- a/src/PhpWord/Writer/Word2007/Element/Title.php +++ b/src/PhpWord/Writer/Word2007/Element/Title.php @@ -67,7 +67,8 @@ public function write(): void $this->writeText($text); $xmlWriter->endElement(); // w:t $xmlWriter->endElement(); // w:r - } elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { + } + if ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { $containerWriter = new Container($xmlWriter, $text); $containerWriter->write(); } diff --git a/src/PhpWord/Writer/Word2007/Part/AbstractPart.php b/src/PhpWord/Writer/Word2007/Part/AbstractPart.php index def05e537f..ef823f104d 100644 --- a/src/PhpWord/Writer/Word2007/Part/AbstractPart.php +++ b/src/PhpWord/Writer/Word2007/Part/AbstractPart.php @@ -48,8 +48,6 @@ abstract public function write(); /** * Set parent writer. - * - * @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer */ public function setParentWriter(?AbstractWriter $writer = null): void { diff --git a/src/PhpWord/Writer/Word2007/Part/Footnotes.php b/src/PhpWord/Writer/Word2007/Part/Footnotes.php index 9624ab7459..e284c674b5 100644 --- a/src/PhpWord/Writer/Word2007/Part/Footnotes.php +++ b/src/PhpWord/Writer/Word2007/Part/Footnotes.php @@ -141,7 +141,7 @@ public function setElements($elements) protected function writeNote(XMLWriter $xmlWriter, $element): void { $xmlWriter->startElement($this->elementNode); - $xmlWriter->writeAttribute('w:id', $element->getRelationId()); + $xmlWriter->writeAttribute('w:id', $element->getRelationId() + 1); $xmlWriter->startElement('w:p'); // Paragraph style diff --git a/src/PhpWord/Writer/Word2007/Part/Settings.php b/src/PhpWord/Writer/Word2007/Part/Settings.php index 2b126bb0fb..85a7fe2c50 100644 --- a/src/PhpWord/Writer/Word2007/Part/Settings.php +++ b/src/PhpWord/Writer/Word2007/Part/Settings.php @@ -151,6 +151,7 @@ private function getSettings(): void $this->setOnOffValue('w:updateFields', $documentSettings->hasUpdateFields()); $this->setOnOffValue('w:autoHyphenation', $documentSettings->hasAutoHyphenation()); $this->setOnOffValue('w:doNotHyphenateCaps', $documentSettings->hasDoNotHyphenateCaps()); + $this->setOnOffValue('w:bookFoldPrinting', $documentSettings->hasBookFoldPrinting()); $this->setThemeFontLang($documentSettings->getThemeFontLang()); $this->setRevisionView($documentSettings->getRevisionView()); @@ -217,8 +218,6 @@ private function setDocumentProtection($documentProtection): void /** * Set the Proof state. - * - * @param ProofState $proofState */ private function setProofState(?ProofState $proofState = null): void { @@ -234,8 +233,6 @@ private function setProofState(?ProofState $proofState = null): void /** * Set the Revision View. - * - * @param TrackChangesView $trackChangesView */ private function setRevisionView(?TrackChangesView $trackChangesView = null): void { @@ -253,8 +250,6 @@ private function setRevisionView(?TrackChangesView $trackChangesView = null): vo /** * Sets the language. - * - * @param Language $language */ private function setThemeFontLang(?Language $language = null): void { diff --git a/src/PhpWord/Writer/Word2007/Style/Cell.php b/src/PhpWord/Writer/Word2007/Style/Cell.php index 9a489b6622..6e22597dd3 100644 --- a/src/PhpWord/Writer/Word2007/Style/Cell.php +++ b/src/PhpWord/Writer/Word2007/Style/Cell.php @@ -88,6 +88,7 @@ public function write(): void $vMerge = $style->getVMerge(); $xmlWriter->writeElementIf(null !== $gridSpan, 'w:gridSpan', 'w:val', $gridSpan); $xmlWriter->writeElementIf(null !== $vMerge, 'w:vMerge', 'w:val', $vMerge); + $xmlWriter->writeElementIf($style->getNoWrap(), 'w:noWrap'); $xmlWriter->endElement(); // w:tcPr } diff --git a/src/PhpWord/Writer/Word2007/Style/Font.php b/src/PhpWord/Writer/Word2007/Style/Font.php index 8d9697715c..1f6db009e4 100644 --- a/src/PhpWord/Writer/Word2007/Style/Font.php +++ b/src/PhpWord/Writer/Word2007/Style/Font.php @@ -117,8 +117,8 @@ private function writeStyle(): void $xmlWriter->writeElementIf($style->isItalic() !== null, 'w:iCs', 'w:val', $this->writeOnOf($style->isItalic())); // Strikethrough, double strikethrough - $xmlWriter->writeElementIf($style->isStrikethrough() !== null, 'w:strike', 'w:val', $this->writeOnOf($style->isStrikethrough())); - $xmlWriter->writeElementIf($style->isDoubleStrikethrough() !== null, 'w:dstrike', 'w:val', $this->writeOnOf($style->isDoubleStrikethrough())); + $xmlWriter->writeElementIf($style->isStrikethrough(), 'w:strike', 'w:val', $this->writeOnOf($style->isStrikethrough())); + $xmlWriter->writeElementIf($style->isDoubleStrikethrough(), 'w:dstrike', 'w:val', $this->writeOnOf($style->isDoubleStrikethrough())); // Small caps, all caps $xmlWriter->writeElementIf($style->isSmallCaps() !== null, 'w:smallCaps', 'w:val', $this->writeOnOf($style->isSmallCaps())); diff --git a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php index 8d08eec3cc..ce250e54d4 100644 --- a/src/PhpWord/Writer/Word2007/Style/MarginBorder.php +++ b/src/PhpWord/Writer/Word2007/Style/MarginBorder.php @@ -120,7 +120,7 @@ public function setSizes($value): void /** * Set colors. * - * @param string[] $value + * @param array<null|string> $value */ public function setColors($value): void { diff --git a/src/PhpWord/Writer/Word2007/Style/TextBox.php b/src/PhpWord/Writer/Word2007/Style/TextBox.php index 6aa6b0199e..d5ccf7a83b 100644 --- a/src/PhpWord/Writer/Word2007/Style/TextBox.php +++ b/src/PhpWord/Writer/Word2007/Style/TextBox.php @@ -2,10 +2,8 @@ /** * This file is part of PHPWord - A pure PHP library for reading and writing * word processing documents. - * * PHPWord is free software distributed under the terms of the GNU Lesser * General Public License version 3 as published by the Free Software Foundation. - * * For the full copyright and license information, please read the LICENSE * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. diff --git a/src/PhpWord/Writer/WriterInterface.php b/src/PhpWord/Writer/WriterInterface.php index 58bd455756..f205eed019 100644 --- a/src/PhpWord/Writer/WriterInterface.php +++ b/src/PhpWord/Writer/WriterInterface.php @@ -24,8 +24,6 @@ interface WriterInterface { /** * Save PhpWord to file. - * - * @param string $filename */ - public function save($filename = null); + public function save(string $filename): void; } diff --git a/tests/PhpWordTests/AbstractWebServerEmbeddedTest.php b/tests/PhpWordTests/AbstractWebServerEmbeddedTest.php index f291f79b8a..38420b4462 100644 --- a/tests/PhpWordTests/AbstractWebServerEmbeddedTest.php +++ b/tests/PhpWordTests/AbstractWebServerEmbeddedTest.php @@ -53,6 +53,15 @@ protected static function getRemoteImageUrl() return 'http://php.net/images/logos/new-php-logo.png'; } + protected static function getRemoteImageUrlWithoutExtension(): string + { + if (self::$httpServer) { + return self::getBaseUrl() . '/images/new-php-logo'; + } + + return 'http://placekitten.com/200/300'; + } + protected static function getRemoteGifImageUrl() { if (self::$httpServer) { diff --git a/tests/PhpWordTests/Collection/CollectionTest.php b/tests/PhpWordTests/Collection/CollectionTest.php index 8f7e3c2ebf..55425a333a 100644 --- a/tests/PhpWordTests/Collection/CollectionTest.php +++ b/tests/PhpWordTests/Collection/CollectionTest.php @@ -35,13 +35,23 @@ public function testCollection(): void $object = new Footnotes(); $object->addItem(new Footnote()); // addItem #1 - self::assertEquals(2, $object->addItem(new Footnote())); // addItem #2. Should returns new item index + self::assertEquals(1, $object->addItem(new Footnote())); // addItem #2. Should returns new item index self::assertCount(2, $object->getItems()); // getItems returns array - self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Footnote', $object->getItem(1)); // getItem returns object + self::assertInstanceOf(Footnote::class, $object->getItem(1)); // getItem returns object self::assertNull($object->getItem(3)); // getItem returns null when invalid index is referenced $object->setItem(2, null); // Set item #2 to null self::assertNull($object->getItem(2)); // Check if it's null } + + public function testCollectionSetItem(): void + { + $object = new Footnotes(); + $object->addItem(new Footnote()); + self::assertCount(1, $object->getItems()); + + $object->setItem(0, new Footnote()); + self::assertCount(1, $object->getItems()); + } } diff --git a/tests/PhpWordTests/Element/AbstractElementTest.php b/tests/PhpWordTests/Element/AbstractElementTest.php index 8a0a878eb2..a059712b1a 100644 --- a/tests/PhpWordTests/Element/AbstractElementTest.php +++ b/tests/PhpWordTests/Element/AbstractElementTest.php @@ -17,6 +17,8 @@ namespace PhpOffice\PhpWordTests\Element; +use PhpOffice\PhpWord\Element\AbstractElement; + /** * Test class for PhpOffice\PhpWord\Element\AbstractElement. */ @@ -27,7 +29,7 @@ class AbstractElementTest extends \PHPUnit\Framework\TestCase */ public function testElementIndex(): void { - $stub = $this->getMockForAbstractClass('\PhpOffice\PhpWord\Element\AbstractElement'); + $stub = $this->getMockForAbstractClass(AbstractElement::class); $ival = mt_rand(0, 100); $stub->setElementIndex($ival); self::assertEquals($ival, $stub->getElementIndex()); @@ -38,7 +40,7 @@ public function testElementIndex(): void */ public function testElementId(): void { - $stub = $this->getMockForAbstractClass('\PhpOffice\PhpWord\Element\AbstractElement'); + $stub = $this->getMockForAbstractClass(AbstractElement::class); $stub->setElementId(); self::assertEquals(6, strlen($stub->getElementId())); } diff --git a/tests/PhpWordTests/Element/CellTest.php b/tests/PhpWordTests/Element/CellTest.php index a6affafe7f..700e16d58e 100644 --- a/tests/PhpWordTests/Element/CellTest.php +++ b/tests/PhpWordTests/Element/CellTest.php @@ -68,7 +68,7 @@ public function testAddText(): void public function testAddTextNotUTF8(): void { $oCell = new Cell(); - $element = $oCell->addText(utf8_decode('ééé')); + $element = $oCell->addText(utf8decode('ééé')); self::assertCount(1, $oCell->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); @@ -81,7 +81,7 @@ public function testAddTextNotUTF8(): void public function testAddLink(): void { $oCell = new Cell(); - $element = $oCell->addLink(utf8_decode('ééé'), utf8_decode('ééé')); + $element = $oCell->addLink(utf8decode('ééé'), utf8decode('ééé')); self::assertCount(1, $oCell->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Link', $element); @@ -117,7 +117,7 @@ public function testAddListItem(): void public function testAddListItemNotUTF8(): void { $oCell = new Cell(); - $element = $oCell->addListItem(utf8_decode('ééé')); + $element = $oCell->addListItem(utf8decode('ééé')); self::assertCount(1, $oCell->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\ListItem', $element); @@ -219,7 +219,7 @@ public function testAddPreserveTextNotUTF8(): void { $oCell = new Cell(); $oCell->setDocPart('Header', 1); - $element = $oCell->addPreserveText(utf8_decode('ééé')); + $element = $oCell->addPreserveText(utf8decode('ééé')); self::assertCount(1, $oCell->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); @@ -255,7 +255,7 @@ public function testCreateTextRun(): void public function testAddCheckBox(): void { $oCell = new Cell(); - $element = $oCell->addCheckBox(utf8_decode('ééé'), utf8_decode('ééé')); + $element = $oCell->addCheckBox(utf8decode('ééé'), utf8decode('ééé')); self::assertCount(1, $oCell->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\CheckBox', $element); diff --git a/tests/PhpWordTests/Element/CommentTest.php b/tests/PhpWordTests/Element/CommentTest.php index fe346307c4..b4d9cc6a5d 100644 --- a/tests/PhpWordTests/Element/CommentTest.php +++ b/tests/PhpWordTests/Element/CommentTest.php @@ -20,6 +20,7 @@ use DateTime; use InvalidArgumentException; use PhpOffice\PhpWord\Element\Comment; +use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\Element\Text; /** @@ -51,6 +52,39 @@ public function testConstructDefault(): void self::assertEquals($oText, $oComment->getEndElement()); } + /** + * Two comments on same text. + */ + public function testTwoCommentsOnSameText(): void + { + $section = new Section(0); + $text = $section->addText('Text'); + + $comment1 = new Comment('Author1', new DateTime(), 'A1'); + $comment1->addText('Comment1'); + + $comment2 = new Comment('Author2', new DateTime(), 'A2'); + $comment2->addText('Comment2'); + + $comment1->setStartElement($text); + $comment2->setStartElement($text); + + $text->setCommentRangeStart($comment1); + $text->setCommentRangeEnd($comment1); + + $text->setCommentRangeStart($comment2); + $text->setCommentRangeEnd($comment2); + + self::assertEquals(2, $text->getCommentsRangeStart()->countItems()); + self::assertEquals(2, $text->getCommentsRangeEnd()->countItems()); + + self::assertEquals($text->getCommentsRangeStart()->getItem(0)->getElementId(), $comment1->getElementId()); + self::assertEquals($text->getCommentsRangeEnd()->getItem(0)->getElementId(), $comment1->getElementId()); + + self::assertEquals($text->getCommentsRangeStart()->getItem(1)->getElementId(), $comment2->getElementId()); + self::assertEquals($text->getCommentsRangeEnd()->getItem(1)->getElementId(), $comment2->getElementId()); + } + /** * Add text. */ diff --git a/tests/PhpWordTests/Element/FooterTest.php b/tests/PhpWordTests/Element/FooterTest.php index 672f3db75a..87a857b702 100644 --- a/tests/PhpWordTests/Element/FooterTest.php +++ b/tests/PhpWordTests/Element/FooterTest.php @@ -57,7 +57,7 @@ public function testAddText(): void public function testAddTextNotUTF8(): void { $oFooter = new Footer(1); - $element = $oFooter->addText(utf8_decode('ééé')); + $element = $oFooter->addText(utf8decode('ééé')); self::assertCount(1, $oFooter->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); @@ -143,7 +143,7 @@ public function testAddPreserveText(): void public function testAddPreserveTextNotUTF8(): void { $oFooter = new Footer(1); - $element = $oFooter->addPreserveText(utf8_decode('ééé')); + $element = $oFooter->addPreserveText(utf8decode('ééé')); self::assertCount(1, $oFooter->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); diff --git a/tests/PhpWordTests/Element/FormulaTest.php b/tests/PhpWordTests/Element/FormulaTest.php new file mode 100644 index 0000000000..7e368e8995 --- /dev/null +++ b/tests/PhpWordTests/Element/FormulaTest.php @@ -0,0 +1,64 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Element; + +use PhpOffice\Math\Element; +use PhpOffice\Math\Math; +use PhpOffice\PhpWord\Element\Formula; +use PhpOffice\PhpWordTests\AbstractWebServerEmbeddedTest; + +/** + * Test class for PhpOffice\PhpWord\Element\Formula. + * + * @runTestsInSeparateProcesses + */ +class FormulaTest extends AbstractWebServerEmbeddedTest +{ + /** + * @covers \PhpOffice\PhpWord\Element\Formula::__construct + */ + public function testConstruct(): void + { + $element = new Formula(new Math()); + + self::assertInstanceOf(Formula::class, $element); + } + + /** + * @covers \PhpOffice\PhpWord\Element\Formula::getMath + * @covers \PhpOffice\PhpWord\Element\Formula::setMath + */ + public function testMath(): void + { + $math = new Math(); + $math->add(new Element\Fraction( + new Element\Numeric(2), + new Element\Identifier('π') + )); + + $element = new Formula(new Math()); + + self::assertInstanceOf(Formula::class, $element); + self::assertEquals(new Math(), $element->getMath()); + self::assertNotEquals($math, $element->getMath()); + + self::assertInstanceOf(Formula::class, $element->setMath($math)); + self::assertNotEquals(new Math(), $element->getMath()); + self::assertEquals($math, $element->getMath()); + } +} diff --git a/tests/PhpWordTests/Element/HeaderTest.php b/tests/PhpWordTests/Element/HeaderTest.php index 74e7c12685..12bf5df3e2 100644 --- a/tests/PhpWordTests/Element/HeaderTest.php +++ b/tests/PhpWordTests/Element/HeaderTest.php @@ -60,7 +60,7 @@ public function testAddText(): void public function testAddTextNotUTF8(): void { $oHeader = new Header(1); - $element = $oHeader->addText(utf8_decode('ééé')); + $element = $oHeader->addText(utf8decode('ééé')); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); self::assertCount(1, $oHeader->getElements()); @@ -153,7 +153,7 @@ public function testAddPreserveText(): void public function testAddPreserveTextNotUTF8(): void { $oHeader = new Header(1); - $element = $oHeader->addPreserveText(utf8_decode('ééé')); + $element = $oHeader->addPreserveText(utf8decode('ééé')); self::assertCount(1, $oHeader->getElements()); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\PreserveText', $element); diff --git a/tests/PhpWordTests/Element/ImageTest.php b/tests/PhpWordTests/Element/ImageTest.php index b30801cad5..725331a2df 100644 --- a/tests/PhpWordTests/Element/ImageTest.php +++ b/tests/PhpWordTests/Element/ImageTest.php @@ -23,8 +23,6 @@ /** * Test class for PhpOffice\PhpWord\Element\Image. - * - * @runTestsInSeparateProcesses */ class ImageTest extends AbstractWebServerEmbeddedTest { @@ -65,33 +63,40 @@ public function testConstructWithStyle(): void /** * Valid image types. + * + * @dataProvider providerImages */ - public function testImages(): void + public function testImages($source, $type, $extension, $createFunction, $imageFunction, $imageQuality): void { - $images = [ - ['mars.jpg', 'image/jpeg', 'jpg', 'imagecreatefromjpeg', 'imagejpeg'], - ['mario.gif', 'image/gif', 'gif', 'imagecreatefromgif', 'imagegif'], - ['firefox.png', 'image/png', 'png', 'imagecreatefrompng', 'imagepng'], - ['duke_nukem.bmp', 'image/bmp', 'bmp', null, null], - ['angela_merkel.tif', 'image/tiff', 'tif', null, null], - ]; - - foreach ($images as $imageData) { - [$source, $type, $extension, $createFunction, $imageFunction] = $imageData; - $nam = ucfirst(strtok($source, '.')); - $source = __DIR__ . "/../_files/images/{$source}"; - $image = new Image($source, null, null, $nam); - self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $image); - self::assertEquals($source, $image->getSource()); - self::assertEquals($nam, $image->getName()); - self::assertEquals(md5($source), $image->getMediaId()); - self::assertEquals($type, $image->getImageType()); - self::assertEquals($extension, $image->getImageExtension()); - self::assertEquals($createFunction, $image->getImageCreateFunction()); - self::assertEquals($imageFunction, $image->getImageFunction()); - self::assertFalse($image->isMemImage()); - self::assertNotNull($image->getImageStringData()); + $nam = ucfirst(strtok($source, '.')); + $source = __DIR__ . "/../_files/images/{$source}"; + $image = new Image($source, null, null, $nam); + self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Image', $image); + self::assertEquals($source, $image->getSource()); + self::assertEquals($nam, $image->getName()); + self::assertEquals(md5($source), $image->getMediaId()); + self::assertEquals($type, $image->getImageType()); + self::assertEquals($extension, $image->getImageExtension()); + self::assertEquals($createFunction, $image->getImageCreateFunction()); + if ($imageFunction) { + self::assertNotNull($image->getImageFunction()); + } else { + self::assertNull($image->getImageFunction()); } + self::assertEquals($imageQuality, $image->getImageQuality()); + self::assertFalse($image->isMemImage()); + self::assertNotNull($image->getImageStringData()); + } + + public static function providerImages(): array + { + return [ + ['mars.jpg', 'image/jpeg', 'jpg', 'imagecreatefromjpeg', true, 100], + ['mario.gif', 'image/gif', 'gif', 'imagecreatefromgif', true, null], + ['firefox.png', 'image/png', 'png', 'imagecreatefrompng', true, -1], + ['duke_nukem.bmp', 'image/bmp', 'bmp', null, false, null], + ['angela_merkel.tif', 'image/tiff', 'tif', null, false, null], + ]; } /** @@ -204,7 +209,8 @@ public function testConstructFromString(): void self::assertEquals('image/jpeg', $image->getImageType()); self::assertEquals('jpg', $image->getImageExtension()); self::assertEquals('imagecreatefromstring', $image->getImageCreateFunction()); - self::assertEquals('imagejpeg', $image->getImageFunction()); + self::assertNotNull($image->getImageFunction()); + self::assertEquals(100, $image->getImageQuality()); self::assertTrue($image->isMemImage()); self::assertNotNull($image->getImageStringData()); @@ -225,7 +231,8 @@ public function testConstructFromGd(): void self::assertEquals('image/png', $image->getImageType()); self::assertEquals('png', $image->getImageExtension()); self::assertEquals('imagecreatefrompng', $image->getImageCreateFunction()); - self::assertEquals('imagepng', $image->getImageFunction()); + self::assertNotNull($image->getImageFunction()); + self::assertEquals(-1, $image->getImageQuality()); self::assertTrue($image->isMemImage()); self::assertNotNull($image->getImageStringData()); diff --git a/tests/PhpWordTests/Element/ListItemRunTest.php b/tests/PhpWordTests/Element/ListItemRunTest.php index e438d186d2..f22c171018 100644 --- a/tests/PhpWordTests/Element/ListItemRunTest.php +++ b/tests/PhpWordTests/Element/ListItemRunTest.php @@ -114,7 +114,7 @@ public function testAddText(): void public function testAddTextNotUTF8(): void { $oListItemRun = new ListItemRun(); - $element = $oListItemRun->addText(utf8_decode('ééé')); + $element = $oListItemRun->addText(utf8decode('ééé')); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); self::assertCount(1, $oListItemRun->getElements()); diff --git a/tests/PhpWordTests/Element/SectionTest.php b/tests/PhpWordTests/Element/SectionTest.php index 72aada10d6..f978188349 100644 --- a/tests/PhpWordTests/Element/SectionTest.php +++ b/tests/PhpWordTests/Element/SectionTest.php @@ -75,18 +75,18 @@ public function testAddElements(): void $section = new Section(0); $section->setPhpWord(new PhpWord()); - $section->addText(utf8_decode('ä')); - $section->addLink(utf8_decode('http://äää.com'), utf8_decode('ä')); + $section->addText(utf8decode('ä')); + $section->addLink(utf8decode('http://äää.com'), utf8decode('ä')); $section->addTextBreak(); $section->addPageBreak(); $section->addTable(); - $section->addListItem(utf8_decode('ä')); + $section->addListItem(utf8decode('ä')); $section->addObject($objectSource); $section->addImage($imageSource); - $section->addTitle(utf8_decode('ä'), 1); + $section->addTitle(utf8decode('ä'), 1); $section->addTextRun(); $section->addFootnote(); - $section->addCheckBox(utf8_decode('chkä'), utf8_decode('Contentä')); + $section->addCheckBox(utf8decode('chkä'), utf8decode('Contentä')); $section->addTOC(); $elementCollection = $section->getElements(); diff --git a/tests/PhpWordTests/Element/TextRunTest.php b/tests/PhpWordTests/Element/TextRunTest.php index 2a1ad6efcd..d1318199f9 100644 --- a/tests/PhpWordTests/Element/TextRunTest.php +++ b/tests/PhpWordTests/Element/TextRunTest.php @@ -99,7 +99,7 @@ public function testAddText(): void public function testAddTextNotUTF8(): void { $oTextRun = new TextRun(); - $element = $oTextRun->addText(utf8_decode('ééé')); + $element = $oTextRun->addText(utf8decode('ééé')); self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Text', $element); self::assertCount(1, $oTextRun->getElements()); diff --git a/tests/PhpWordTests/Element/TitleTest.php b/tests/PhpWordTests/Element/TitleTest.php index 0e3e481510..e48a163721 100644 --- a/tests/PhpWordTests/Element/TitleTest.php +++ b/tests/PhpWordTests/Element/TitleTest.php @@ -14,6 +14,7 @@ * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); namespace PhpOffice\PhpWordTests\Element; @@ -32,24 +33,17 @@ class TitleTest extends \PHPUnit\Framework\TestCase { /** - * Create new instance. + * Create new instance with string. */ public function testConstruct(): void { - $oTitle = new Title('text'); + $title = new Title('text'); - self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Title', $oTitle); - self::assertEquals('text', $oTitle->getText()); - } - - /** - * Get style null. - */ - public function testStyleNull(): void - { - $oTitle = new Title('text'); - - self::assertNull($oTitle->getStyle()); + self::assertInstanceOf(Title::class, $title); + self::assertEquals('text', $title->getText()); + self::assertEquals(1, $title->getDepth()); + self::assertNull($title->getPageNumber()); + self::assertNull($title->getStyle()); } /** @@ -57,17 +51,30 @@ public function testStyleNull(): void */ public function testConstructWithTextRun(): void { - $oTextRun = new TextRun(); - $oTextRun->addText('text'); - $oTitle = new Title($oTextRun); + $textRun = new TextRun(); + $textRun->addText('text'); + $title = new Title($textRun); - self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\TextRun', $oTitle->getText()); + self::assertInstanceOf(TextRun::class, $title->getText()); + self::assertEquals(1, $title->getDepth()); + self::assertNull($title->getPageNumber()); + self::assertNull($title->getStyle()); } public function testConstructWithInvalidArgument(): void { $this->expectException(InvalidArgumentException::class); - $oPageBreak = new PageBreak(); - new Title($oPageBreak); + + new Title(new PageBreak()); + } + + public function testConstructWithPageNumber(): void + { + $title = new Title('text', 1, 0); + + self::assertInstanceOf(Title::class, $title); + self::assertEquals('text', $title->getText()); + self::assertEquals(0, $title->getPageNumber()); + self::assertNull($title->getStyle()); } } diff --git a/tests/PhpWordTests/Escaper/RtfEscaper3Test.php b/tests/PhpWordTests/Escaper/RtfEscaper3Test.php new file mode 100644 index 0000000000..6bbc089a2a --- /dev/null +++ b/tests/PhpWordTests/Escaper/RtfEscaper3Test.php @@ -0,0 +1,95 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Escaper; + +use PhpOffice\PhpWord\Settings; + +/** + * Test class for PhpOffice\PhpWord\Escaper\RTF. + */ +class RtfEscaper3Test extends \PHPUnit\Framework\TestCase +{ + const HEADER = '\\pard\\nowidctlpar \ql{\\cf0\\f0 '; + const HEADER_RTL = '\\pard\\nowidctlpar \qr{\\rtlch\\cf0\\f0 '; + const TRAILER = '}\\par'; + + protected function tearDown(): void + { + Settings::setDefaultRtl(null); + } + + public function escapestring(string $str): string + { + \PhpOffice\PhpWord\Settings::setOutputEscapingEnabled(true); + $parentWriter = new \PhpOffice\PhpWord\Writer\RTF(); + $element = new \PhpOffice\PhpWord\Element\Text($str); + $txt = new \PhpOffice\PhpWord\Writer\RTF\Element\Text($parentWriter, $element); + $txt2 = trim($txt->write()); + + return $txt2; + } + + public function expect(string $str, bool $rtl = false): string + { + return ($rtl ? self:: HEADER_RTL : self::HEADER) . $str . self::TRAILER; + } + + /** + * Test special characters which require escaping. + */ + public function testSpecial(): void + { + Settings::setDefaultRtl(false); + $str = 'Special characters { open brace } close brace \\ backslash'; + $expect = $this->expect('Special characters \\{ open brace \\} close brace \\\\ backslash'); + self::assertEquals($expect, $this->escapestring($str)); + } + + /** + * Test accented character. + */ + public function testAccent(): void + { + Settings::setDefaultRtl(false); + $str = 'Voilà - string with accented char'; + $expect = $this->expect('Voil\\uc0{\\u224} - string with accented char'); + self::assertEquals($expect, $this->escapestring($str)); + } + + /** + * Test Hebrew. + */ + public function testHebrew(): void + { + Settings::setDefaultRtl(true); + $str = 'Hebrew - שלום'; + $expect = $this->expect('Hebrew - \\uc0{\\u1513}\\uc0{\\u1500}\\uc0{\\u1493}\\uc0{\\u1501}', true); + self::assertEquals($expect, $this->escapestring($str)); + } + + /** + * Test tab. + */ + public function testTab(): void + { + Settings::setDefaultRtl(false); + $str = "Here's a tab\tfollowed by more characters."; + $expect = $this->expect("Here's a tab{\\tab}followed by more characters."); + self::assertEquals($expect, $this->escapestring($str)); + } +} diff --git a/tests/PhpWordTests/IOFactoryTest.php b/tests/PhpWordTests/IOFactoryTest.php index 890f45c78e..79f0fd0c76 100644 --- a/tests/PhpWordTests/IOFactoryTest.php +++ b/tests/PhpWordTests/IOFactoryTest.php @@ -54,7 +54,7 @@ public function testCreateWriter(string $name, string $expected): void self::assertInstanceOf($expected, $actual); } - public function providerCreateWriter(): iterable + public static function providerCreateWriter(): iterable { return [ ['ODText', ODText::class], @@ -116,4 +116,17 @@ public function testLoad(): void IOFactory::load($file) ); } + + /** + * Test for extractVariables method. + */ + public function testExtractVariables(): void + { + $file = __DIR__ . '/_files/templates/extract-variable.docx'; + $extractedVariables = IOFactory::extractVariables($file, 'Word2007'); + + $expectedVariables = ['date', 'A1', 'B1']; + + self::assertEquals($expectedVariables, $extractedVariables, 'Extracted variables do not match expected variables.'); + } } diff --git a/tests/PhpWordTests/Metadata/SettingsTest.php b/tests/PhpWordTests/Metadata/SettingsTest.php index 48af68eab8..14f19f318d 100644 --- a/tests/PhpWordTests/Metadata/SettingsTest.php +++ b/tests/PhpWordTests/Metadata/SettingsTest.php @@ -225,4 +225,19 @@ public function testDefaultDoNotHyphenateCaps(): void $oSettings = new Settings(); self::assertNull($oSettings->hasDoNotHyphenateCaps()); } + + public function testBookFoldPrinting(): void + { + $oSettings = new Settings(); + self::assertInstanceOf(Settings::class, $oSettings->setBookFoldPrinting(true)); + self::assertTrue($oSettings->hasBookFoldPrinting()); + self::assertInstanceOf(Settings::class, $oSettings->setBookFoldPrinting(false)); + self::assertFalse($oSettings->hasBookFoldPrinting()); + } + + public function testDefaultBookFoldPrinting(): void + { + $oSettings = new Settings(); + self::assertFalse($oSettings->hasBookFoldPrinting()); + } } diff --git a/tests/PhpWordTests/PhpWordTest.php b/tests/PhpWordTests/PhpWordTest.php index 19e141f49e..33118a11e8 100644 --- a/tests/PhpWordTests/PhpWordTest.php +++ b/tests/PhpWordTests/PhpWordTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWordTests; use BadMethodCallException; +use DateTimeImmutable; use PhpOffice\PhpWord\Metadata\DocInfo; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; @@ -35,8 +36,14 @@ class PhpWordTest extends \PHPUnit\Framework\TestCase */ public function testConstruct(): void { - $phpWord = new PhpWord(); - self::assertEquals(new DocInfo(), $phpWord->getDocInfo()); + do { + $dtStart = new DateTimeImmutable(); + $startSecond = $dtStart->format('s'); + $phpWord = new PhpWord(); + $docInfo = new DocInfo(); + $endSecond = (new DateTimeImmutable('now'))->format('s'); + } while ($startSecond !== $endSecond); + self::assertEquals($docInfo, $phpWord->getDocInfo()); self::assertEquals(Settings::DEFAULT_FONT_NAME, $phpWord->getDefaultFontName()); self::assertEquals(Settings::DEFAULT_FONT_SIZE, $phpWord->getDefaultFontSize()); } @@ -122,13 +129,14 @@ public function testAddTitleStyle(): void */ public function testSave(): void { - $this->setOutputCallback(function (): void { - }); $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText('Hello world!'); - + ob_start(); self::assertTrue($phpWord->save('test.docx', 'Word2007', true)); + $contents = ob_get_contents(); + self::assertTrue(ob_end_clean()); + self::assertNotEmpty($contents); } /** diff --git a/tests/PhpWordTests/Reader/MsDocTest.php b/tests/PhpWordTests/Reader/MsDocTest.php index deb8b9badd..b243fccfee 100644 --- a/tests/PhpWordTests/Reader/MsDocTest.php +++ b/tests/PhpWordTests/Reader/MsDocTest.php @@ -18,7 +18,9 @@ namespace PhpOffice\PhpWordTests\Reader; use Exception; +use PhpOffice\PhpWord\Element\Text; use PhpOffice\PhpWord\IOFactory; +use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Reader\MsDoc; /** @@ -50,14 +52,84 @@ public function testCanReadFailed(): void self::assertFalse($object->canRead($filename)); } - /** - * Load. - */ - public function testLoad(): void + public function testLoadBasic(): void { $filename = __DIR__ . '/../_files/documents/reader.doc'; $phpWord = IOFactory::load($filename, 'MsDoc'); - self::assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); + self::assertInstanceOf(PhpWord::class, $phpWord); + + $sections = $phpWord->getSections(); + self::assertCount(1, $sections); + $elements = $sections[0]->getElements(); + self::assertArrayHasKey(0, $elements); + /** @var Text $element0 */ + $element0 = $elements[0]; + self::assertInstanceOf(Text::class, $element0); + self::assertEquals('Welcome to PhpWord', $element0->getText()); + } + + public function testLoadHalfPointFont(): void + { + $filename = __DIR__ . '/../_files/documents/reader.font-halfpoint.doc'; + $phpWord = IOFactory::load($filename, 'MsDoc'); + $sections = $phpWord->getSections(); + self::assertCount(1, $sections); + $elements = $sections[0]->getElements(); + self::assertArrayHasKey(0, $elements); + $element0 = $elements[0]; + if (method_exists($element0, 'getFontStyle')) { + self::assertSame(19.5, $element0->getFontStyle()->getSize()); + } else { + self::fail('Unexpected no font style for first element'); + } + } + + public function testLoadChinese(): void + { + $filename = __DIR__ . '/../_files/documents/docChinese.doc'; + $phpWord = IOFactory::load($filename, 'MsDoc'); + self::assertInstanceOf(PhpWord::class, $phpWord); + + $sections = $phpWord->getSections(); + self::assertCount(1, $sections); + $elements = $sections[0]->getElements(); + self::assertArrayHasKey(0, $elements); + /** @var Text $element0 */ + $element0 = $elements[0]; + self::assertInstanceOf(Text::class, $element0); + self::assertEquals('OKKI AI 客户案例', $element0->getText()); + } + + public function testLoadCzech(): void + { + $filename = __DIR__ . '/../_files/documents/docCzech.doc'; + $phpWord = IOFactory::load($filename, 'MsDoc'); + self::assertInstanceOf(PhpWord::class, $phpWord); + + $sections = $phpWord->getSections(); + self::assertCount(1, $sections); + $elements = $sections[0]->getElements(); + self::assertArrayHasKey(0, $elements); + /** @var Text $element0 */ + $element0 = $elements[0]; + self::assertInstanceOf(Text::class, $element0); + self::assertEquals('Příliš žluťoučký kůň pěl ďábelské ódy', $element0->getText()); + } + + public function testLoadSlovak(): void + { + $filename = __DIR__ . '/../_files/documents/docSlovak.doc'; + $phpWord = IOFactory::load($filename, 'MsDoc'); + self::assertInstanceOf(PhpWord::class, $phpWord); + + $sections = $phpWord->getSections(); + self::assertCount(1, $sections); + $elements = $sections[0]->getElements(); + self::assertArrayHasKey(0, $elements); + /** @var Text $element0 */ + $element0 = $elements[0]; + self::assertInstanceOf(Text::class, $element0); + self::assertEquals('Pondelok', $element0->getText()); } /** diff --git a/tests/PhpWordTests/Reader/ODText/ODTextSectionTest.php b/tests/PhpWordTests/Reader/ODText/ODTextSectionTest.php new file mode 100644 index 0000000000..0a1a4512db --- /dev/null +++ b/tests/PhpWordTests/Reader/ODText/ODTextSectionTest.php @@ -0,0 +1,83 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Reader\ODText; + +use PhpOffice\PhpWord\IOFactory; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; + +class ODTextSectionTest extends \PHPUnit\Framework\TestCase +{ + /** @var string */ + private $filename = ''; + + protected function tearDown(): void + { + if ($this->filename !== '') { + unlink($this->filename); + $this->filename = ''; + } + } + + public function testWriteThenReadSection(): void + { + $dir = 'tests/PhpWordTests/_files'; + Settings::setOutputEscapingEnabled(true); + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $inputText = ['days', 'monday', 'tuesday']; + $inputText[] = "Tab\tthen two spaces then done."; + foreach ($inputText as $text) { + $section->addText($text); + } + $writer = IOFactory::createWriter($phpWord, 'ODText'); + $this->filename = "$dir/sectiontest.odt"; + $writer->save($this->filename); + + $reader = IOFactory::createReader('ODText'); + $phpWord2 = $reader->load($this->filename); + $outputText = []; + foreach ($phpWord2->getSections() as $section) { + foreach ($section->getElements() as $element) { + if (is_object($element) && method_exists($element, 'getText')) { + $outputText[] = $element->getText(); + } + } + } + self::assertSame($inputText, $outputText); + } + + public function testReadNoSections(): void + { + $dir = 'tests/PhpWordTests/_files/documents'; + $inputText = ['days', 'monday', 'tuesday']; + + $reader = IOFactory::createReader('ODText'); + $filename = "$dir/word.2493.nosection.odt"; + $phpWord2 = $reader->load($filename); + $outputText = []; + foreach ($phpWord2->getSections() as $section) { + foreach ($section->getElements() as $element) { + if (is_object($element) && method_exists($element, 'getText')) { + $outputText[] = $element->getText(); + } + } + } + self::assertSame($inputText, $outputText); + } +} diff --git a/tests/PhpWordTests/Reader/ODTextTest.php b/tests/PhpWordTests/Reader/ODTextTest.php index e4baf8d480..20c5916c7d 100644 --- a/tests/PhpWordTests/Reader/ODTextTest.php +++ b/tests/PhpWordTests/Reader/ODTextTest.php @@ -17,7 +17,11 @@ namespace PhpOffice\PhpWordTests\Reader; +use PhpOffice\Math\Element; +use PhpOffice\PhpWord\Element\Formula; +use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\IOFactory; +use PhpOffice\PhpWord\PhpWord; /** * Test class for PhpOffice\PhpWord\Reader\ODText. @@ -33,8 +37,31 @@ class ODTextTest extends \PHPUnit\Framework\TestCase */ public function testLoad(): void { - $filename = __DIR__ . '/../_files/documents/reader.odt'; - $phpWord = IOFactory::load($filename, 'ODText'); - self::assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); + $phpWord = IOFactory::load(dirname(__DIR__, 1) . '/_files/documents/reader.odt', 'ODText'); + self::assertInstanceOf(PhpWord::class, $phpWord); + } + + public function testLoadFormula(): void + { + $phpWord = IOFactory::load(dirname(__DIR__, 1) . '/_files/documents/reader-formula.odt', 'ODText'); + + self::assertInstanceOf(PhpWord::class, $phpWord); + + $sections = $phpWord->getSections(); + self::assertCount(1, $sections); + + $section = $sections[0]; + self::assertInstanceOf(Section::class, $section); + + $elements = $section->getElements(); + self::assertCount(1, $elements); + + $element = $elements[0]; + self::assertInstanceOf(Formula::class, $element); + + $elements = $element->getMath()->getElements(); + self::assertCount(1, $elements); + + self::assertInstanceOf(Element\Semantics::class, $elements[0]); } } diff --git a/tests/PhpWordTests/Reader/Word2007/ElementTest.php b/tests/PhpWordTests/Reader/Word2007/ElementTest.php index 8403f44f7b..3685cbea03 100644 --- a/tests/PhpWordTests/Reader/Word2007/ElementTest.php +++ b/tests/PhpWordTests/Reader/Word2007/ElementTest.php @@ -17,7 +17,9 @@ namespace PhpOffice\PhpWordTests\Reader\Word2007; +use PhpOffice\PhpWord\Element\Text; use PhpOffice\PhpWord\Element\TrackChange; +use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWordTests\AbstractTestReader; /** @@ -60,7 +62,7 @@ public function testReadAlternateContent(): void $elements = $phpWord->getSection(0)->getElements(); self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); - self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $elements[0]->getElement(0)); + self::assertInstanceOf(Text::class, $elements[0]->getElement(0)); $text = $elements[0]; self::assertEquals('Test node value', trim($text->getElement(0)->getText())); } @@ -84,7 +86,7 @@ public function testReadTextBreak(): void /** @var \PhpOffice\PhpWord\Element\TextRun $textRun */ $textRun = $elements[0]; self::assertInstanceOf('PhpOffice\PhpWord\Element\TextBreak', $textRun->getElement(0)); - self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $textRun->getElement(1)); + self::assertInstanceOf(Text::class, $textRun->getElement(1)); self::assertEquals('test string', $textRun->getElement(1)->getText()); } @@ -107,7 +109,7 @@ public function testSmartTag(): void self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); /** @var \PhpOffice\PhpWord\Element\TextRun $textRun */ $textRun = $elements[0]; - self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $textRun->getElement(0)); + self::assertInstanceOf(Text::class, $textRun->getElement(0)); self::assertEquals('test string', $textRun->getElement(0)->getText()); } @@ -145,11 +147,20 @@ public function testReadListItemRunWithFormatting(): void self::assertEquals(0, $sections->getElement(0)->getDepth()); $listElements = $sections->getElement(0)->getElements(); - self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $listElements[0]); - self::assertEquals('Two', $listElements[0]->getText()); - self::assertEquals(' with ', $listElements[1]->getText()); - self::assertEquals('bold', $listElements[2]->getText()); - self::assertTrue($listElements[2]->getFontStyle()->isBold()); + /** @var Text $listElement0 */ + $listElement0 = $listElements[0]; + self::assertInstanceOf(Text::class, $listElement0); + self::assertEquals('Two', $listElement0->getText()); + /** @var Text $listElement1 */ + $listElement1 = $listElements[1]; + self::assertEquals(' with ', $listElement1->getText()); + /** @var Text $listElement2 */ + $listElement2 = $listElements[2]; + self::assertEquals('bold', $listElement2->getText()); + /** @var Font $listElement2FontStyle */ + $listElement2FontStyle = $listElement2->getFontStyle(); + self::assertInstanceOf(Font::class, $listElement2FontStyle); + self::assertTrue($listElement2FontStyle->isBold()); } /** @@ -214,11 +225,11 @@ public function testReadTab(): void self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); /** @var \PhpOffice\PhpWord\Element\TextRun $textRun */ $textRun = $elements[0]; - self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $textRun->getElement(0)); + self::assertInstanceOf(Text::class, $textRun->getElement(0)); self::assertEquals('One', $textRun->getElement(0)->getText()); - self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $textRun->getElement(1)); + self::assertInstanceOf(Text::class, $textRun->getElement(1)); self::assertEquals("\t", $textRun->getElement(1)->getText()); - self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $textRun->getElement(2)); + self::assertInstanceOf(Text::class, $textRun->getElement(2)); self::assertEquals('Two', $textRun->getElement(2)->getText()); } @@ -270,11 +281,43 @@ public function testReadTitleStyle(): void self::assertEquals('Title', $title->getStyle()); self::assertEquals('This is a non formatted title', $title->getText()); - self::assertInstanceOf('PhpOffice\PhpWord\Element\Title', $elements[1]); + self::assertInstanceOf(\PhpOffice\PhpWord\Element\Title::class, $elements[1]); /** @var \PhpOffice\PhpWord\Element\Title $formattedTitle */ $formattedTitle = $elements[1]; self::assertEquals('Title', $formattedTitle->getStyle()); - self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $formattedTitle->getText()); + self::assertInstanceOf(\PhpOffice\PhpWord\Element\TextRun::class, $formattedTitle->getText()); + } + + /** + * Test reading of nested table. + */ + public function testReadNestedTable(): void + { + $documentXml = '<w:tbl> + <w:tr> + <w:tc> + <w:tbl> + <w:tr> + <w:tc> + <w:p> + <w:t>${Field}</w:t> + </w:p> + </w:tc> + </w:tr> + </w:tbl> + <w:p /> + </w:tc> + </w:tr> + </w:tbl>'; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $section = $phpWord->getSection(0); + $table = $section->getElement(0); + $rows = $table->getRows(); + $cells = $rows[0]->getCells(); + $nestedTable = $cells[0]->getElement(0); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Table', $nestedTable); } /** @@ -312,4 +355,192 @@ public function testReadDrawing(): void $elements = $phpWord->getSection(0)->getElements(); self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); } + + /** + * Test reading FormField - DROPDOWN. + */ + public function testReadFormFieldDropdown(): void + { + $documentXml = '<w:p> + <w:r> + <w:t>Reference</w:t> + </w:r> + <w:r> + <w:fldChar w:fldCharType="begin"> + <w:ffData> + <w:name w:val="DropDownList1"/> + <w:enabled/> + <w:calcOnExit w:val="0"/> + <w:ddList> + <w:result w:val="2"/> + <w:listEntry w:val="TBD"/> + <w:listEntry w:val="Option One"/> + <w:listEntry w:val="Option Two"/> + <w:listEntry w:val="Option Three"/> + <w:listEntry w:val="Other"/> + </w:ddList> + </w:ffData> + </w:fldChar> + </w:r> + <w:r> + <w:instrText xml:space="preserve"> FORMDROPDOWN </w:instrText> + </w:r> + <w:r> + <w:rPr> + <w:lang w:val="en-GB"/> + </w:rPr> + </w:r> + <w:r> + <w:rPr> + <w:lang w:val="en-GB"/> + </w:rPr> + <w:fldChar w:fldCharType="separate"/> + </w:r> + <w:r> + <w:rPr> + <w:lang w:val="en-GB"/> + </w:rPr> + <w:fldChar w:fldCharType="end"/> + </w:r> + </w:p>'; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + + $subElements = $elements[0]->getElements(); + + self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $subElements[0]); + self::assertEquals('Reference', $subElements[0]->getText()); + + self::assertInstanceOf('PhpOffice\PhpWord\Element\FormField', $subElements[1]); + self::assertEquals('dropdown', $subElements[1]->getType()); + self::assertEquals('DropDownList1', $subElements[1]->getName()); + self::assertEquals('2', $subElements[1]->getValue()); + self::assertEquals('Option Two', $subElements[1]->getText()); + self::assertEquals(['TBD', 'Option One', 'Option Two', 'Option Three', 'Other'], $subElements[1]->getEntries()); + } + + /** + * Test reading FormField - textinput. + */ + public function testReadFormFieldTextinput(): void + { + $documentXml = '<w:p> + <w:r> + <w:t>Fieldname</w:t> + </w:r> + <w:r> + <w:fldChar w:fldCharType="begin"> + <w:ffData> + <w:name w:val="TextInput2"/> + <w:enabled/> + <w:calcOnExit w:val="0"/> + <w:textInput> + <w:default w:val="TBD"/> + <w:maxLength w:val="200"/> + </w:textInput> + </w:ffData> + </w:fldChar> + </w:r> + <w:r> + <w:instrText xml:space="preserve"> FORMTEXT </w:instrText> + </w:r> + <w:r> + <w:rPr> + <w:lang w:val="en-GB"/> + </w:rPr> + </w:r> + <w:r> + <w:rPr> + <w:lang w:val="en-GB"/> + </w:rPr> + <w:fldChar w:fldCharType="separate"/> + </w:r> + <w:r w:rsidR="00807709"> + <w:rPr> + <w:noProof/> + <w:lang w:val="en-GB"/> + </w:rPr> + <w:t>This is some sample text</w:t> + </w:r> + <w:r> + <w:rPr> + <w:lang w:val="en-GB"/> + </w:rPr> + <w:fldChar w:fldCharType="end"/> + </w:r> + </w:p>'; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + + $subElements = $elements[0]->getElements(); + + self::assertInstanceOf('PhpOffice\PhpWord\Element\Text', $subElements[0]); + self::assertEquals('Fieldname', $subElements[0]->getText()); + + self::assertInstanceOf('PhpOffice\PhpWord\Element\FormField', $subElements[1]); + self::assertEquals('textinput', $subElements[1]->getType()); + self::assertEquals('TextInput2', $subElements[1]->getName()); + self::assertEquals('This is some sample text', $subElements[1]->getValue()); + self::assertEquals('This is some sample text', $subElements[1]->getText()); + } + + /** + * Test reading FormField - checkbox. + */ + public function testReadFormFieldCheckbox(): void + { + $documentXml = '<w:p> + <w:pPr/> + <w:r> + <w:fldChar w:fldCharType="begin"> + <w:ffData> + <w:enabled w:val="1"/> + <w:name w:val="SomeCheckbox"/> + <w:calcOnExit w:val="0"/> + <w:checkBox> + <w:sizeAuto w:val=""/> + <w:default w:val="0"/> + <w:checked w:val="0"/> + </w:checkBox> + </w:ffData> + </w:fldChar> + </w:r> + <w:r> + <w:rPr/> + <w:instrText xml:space="preserve">FORMCHECKBOX</w:instrText> + </w:r> + <w:r> + <w:rPr/> + <w:fldChar w:fldCharType="separate"/> + </w:r> + <w:r> + <w:rPr/> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr/> + <w:fldChar w:fldCharType="end"/> + </w:r> + </w:p>'; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + + $subElements = $elements[0]->getElements(); + +// $this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $subElements[0]); +// $this->assertEquals('Fieldname', $subElements[0]->getText()); + + self::assertInstanceOf('PhpOffice\PhpWord\Element\FormField', $subElements[0]); + self::assertEquals('checkbox', $subElements[0]->getType()); + self::assertEquals('SomeCheckbox', $subElements[0]->getName()); + } } diff --git a/tests/PhpWordTests/Reader/Word2007/StyleTest.php b/tests/PhpWordTests/Reader/Word2007/StyleTest.php index 2aeb651c60..f622c57e90 100644 --- a/tests/PhpWordTests/Reader/Word2007/StyleTest.php +++ b/tests/PhpWordTests/Reader/Word2007/StyleTest.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWordTests\Reader\Word2007; +use PhpOffice\PhpWord\SimpleType\Border; use PhpOffice\PhpWord\SimpleType\TblWidth; use PhpOffice\PhpWord\SimpleType\VerticalJc; use PhpOffice\PhpWord\Style; @@ -48,28 +49,6 @@ public function testReadTableLayout(): void self::assertEquals(Table::LAYOUT_FIXED, $elements[0]->getStyle()->getLayout()); } - /** - * Test reading of cell spacing. - */ - public function testReadCellSpacing(): void - { - $documentXml = '<w:tbl> - <w:tblPr> - <w:tblCellSpacing w:w="10.5" w:type="dxa"/> - </w:tblPr> - </w:tbl>'; - - $phpWord = $this->getDocumentFromString(['document' => $documentXml]); - - $elements = $phpWord->getSection(0)->getElements(); - self::assertInstanceOf('PhpOffice\PhpWord\Element\Table', $elements[0]); - self::assertInstanceOf('PhpOffice\PhpWord\Style\Table', $elements[0]->getStyle()); - /** @var \PhpOffice\PhpWord\Style\Table $tableStyle */ - $tableStyle = $elements[0]->getStyle(); - self::assertEquals(TblWidth::AUTO, $tableStyle->getUnit()); - self::assertEquals(10.5, $tableStyle->getCellSpacing()); - } - /** * Test reading of table position. */ @@ -102,6 +81,128 @@ public function testReadTablePosition(): void self::assertEquals(60, $tableStyle->getTblpY()); } + public function testReadTableCellNoWrap(): void + { + $documentXml = '<w:tbl> + <w:tr> + <w:tc> + <w:tcPr> + <w:noWrap /> + </w:tcPr> + </w:tc> + </w:tr> + </w:tbl>'; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Table', $elements[0]); + $rows = $elements[0]->getRows(); + $cells = $rows[0]->getCells(); + self::assertTrue($cells[0]->getStyle()->getNoWrap()); + } + + /** + * Test reading of cell spacing. + */ + public function testReadTableCellSpacing(): void + { + $documentXml = '<w:tbl> + <w:tblPr> + <w:tblCellSpacing w:w="10.5" w:type="dxa"/> + </w:tblPr> + </w:tbl>'; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Table', $elements[0]); + self::assertInstanceOf('PhpOffice\PhpWord\Style\Table', $elements[0]->getStyle()); + /** @var \PhpOffice\PhpWord\Style\Table $tableStyle */ + $tableStyle = $elements[0]->getStyle(); + self::assertEquals(TblWidth::AUTO, $tableStyle->getUnit()); + self::assertEquals(10.5, $tableStyle->getCellSpacing()); + } + + public function testReadTableCellStyle(): void + { + $documentXml = '<w:tbl> + <w:tr> + <w:tc> + <w:tcPr> + <w:tcBorders> + <w:top w:val="single" w:sz="4" w:space="0" w:color="auto"/> + <w:bottom w:val="double" w:sz="4" w:space="0" w:color="auto"/> + </w:tcBorders> + <w:tcMar> + <w:top w:w="720" w:type="dxa"/> + <w:start w:w="720" w:type="dxa"/> + <w:bottom w:w="0" w:type="dxa"/> + <w:end w:w="720" w:type="dxa"/> + </w:tcMar> + </w:tcPr> + </w:tc> + </w:tr> + </w:tbl>'; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $elements = $phpWord->getSection(0)->getElements(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Table', $elements[0]); + $rows = $elements[0]->getRows(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Row', $rows[0]); + $cells = $rows[0]->getCells(); + self::assertInstanceOf('PhpOffice\PhpWord\Element\Cell', $cells[0]); + $styleCell = $cells[0]->getStyle(); + self::assertInstanceOf('PhpOffice\PhpWord\Style\Cell', $styleCell); + + self::assertEquals(4, $styleCell->getBorderTopSize()); + self::assertEquals(Border::SINGLE, $styleCell->getBorderTopStyle()); + self::assertEquals('auto', $styleCell->getBorderTopColor()); + + self::assertEquals(4, $styleCell->getBorderBottomSize()); + self::assertEquals(Border::DOUBLE, $styleCell->getBorderBottomStyle()); + self::assertEquals('auto', $styleCell->getBorderBottomColor()); + } + + public function testReadTableCellsWithVerticalMerge(): void + { + $documentXml = '<w:tbl> + <w:tr> + <w:tc> + <w:tcPr> + <w:vMerge w:val="restart" /> + </w:tcPr> + </w:tc> + </w:tr> + <w:tr> + <w:tc> + <w:tcPr> + <w:vMerge /> + </w:tcPr> + </w:tc> + </w:tr> + <w:tr> + <w:tc /> + </w:tr> + </w:tbl>'; + + $phpWord = $this->getDocumentFromString(['document' => $documentXml]); + + $table = $phpWord->getSection(0)->getElements()[0]; + self::assertInstanceOf('PhpOffice\PhpWord\Element\Table', $table); + + $rows = $table->getRows(); + self::assertCount(3, $rows); + foreach ($rows as $row) { + self::assertCount(1, $row->getCells()); + } + + self::assertSame('restart', $rows[0]->getCells()[0]->getStyle()->getVMerge()); + self::assertSame('continue', $rows[1]->getCells()[0]->getStyle()->getVMerge()); + self::assertNull($rows[2]->getCells()[0]->getStyle()->getVMerge()); + } + /** * Test reading of position. */ diff --git a/tests/PhpWordTests/Reader/Word2007Test.php b/tests/PhpWordTests/Reader/Word2007Test.php index 999eaf853a..ebfd8ad7a2 100644 --- a/tests/PhpWordTests/Reader/Word2007Test.php +++ b/tests/PhpWordTests/Reader/Word2007Test.php @@ -17,8 +17,19 @@ namespace PhpOffice\PhpWordTests\Reader; +use DateTime; +use PhpOffice\Math\Element; +use PhpOffice\PhpWord\Element\Comment; +use PhpOffice\PhpWord\Element\Formula; +use PhpOffice\PhpWord\Element\Image; +use PhpOffice\PhpWord\Element\Section; +use PhpOffice\PhpWord\Element\Text; +use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\IOFactory; +use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Reader\Word2007; +use PhpOffice\PhpWord\Style\Font; +use PhpOffice\PhpWord\Style\Paragraph; use PhpOffice\PhpWordTests\TestHelperDOCX; /** @@ -30,14 +41,21 @@ */ class Word2007Test extends \PHPUnit\Framework\TestCase { + /** + * Tear down after each test. + */ + protected function tearDown(): void + { + TestHelperDOCX::clear(); + } + /** * Test canRead() method. */ public function testCanRead(): void { $object = new Word2007(); - $filename = __DIR__ . '/../_files/documents/reader.docx'; - self::assertTrue($object->canRead($filename)); + self::assertTrue($object->canRead(dirname(__DIR__, 1) . '/_files/documents/reader.docx')); } /** @@ -46,8 +64,7 @@ public function testCanRead(): void public function testCanReadFailed(): void { $object = new Word2007(); - $filename = __DIR__ . '/../_files/documents/foo.docx'; - self::assertFalse($object->canRead($filename)); + self::assertFalse($object->canRead(dirname(__DIR__, 1) . '/_files/documents/foo.docx')); } /** @@ -55,10 +72,9 @@ public function testCanReadFailed(): void */ public function testLoad(): void { - $filename = __DIR__ . '/../_files/documents/reader.docx'; - $phpWord = IOFactory::load($filename); + $phpWord = IOFactory::load(dirname(__DIR__, 1) . '/_files/documents/reader.docx', 'Word2007'); - self::assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); + self::assertInstanceOf(PhpWord::class, $phpWord); self::assertTrue($phpWord->getSettings()->hasDoNotTrackMoves()); self::assertFalse($phpWord->getSettings()->hasDoNotTrackFormatting()); self::assertEquals(100, $phpWord->getSettings()->getZoom()); @@ -67,17 +83,181 @@ public function testLoad(): void self::assertEquals('0', $doc->getElementAttribute('/w:document/w:body/w:p/w:r[w:t/node()="italics"]/w:rPr/w:b', 'w:val')); } + public function testLoadStyles(): void + { + $phpWord = IOFactory::load(dirname(__DIR__, 1) . '/_files/documents/reader-styles.docx', 'Word2007'); + + self::assertInstanceOf(PhpWord::class, $phpWord); + + $section2 = $phpWord->getSection(2); + self::assertInstanceOf(Section::class, $section2); + + $element2_31 = $section2->getElement(31); + self::assertInstanceOf(TextRun::class, $element2_31); + self::assertEquals('This is a paragraph with border differents', $element2_31->getText()); + + /** @var Paragraph $element2_31_pStyle */ + $element2_31_pStyle = $element2_31->getParagraphStyle(); + self::assertInstanceOf(Paragraph::class, $element2_31_pStyle); + + // Top + self::assertEquals('FFFF00', $element2_31_pStyle->getBorderTopColor()); + self::assertEquals('10', $element2_31_pStyle->getBorderTopSize()); + self::assertEquals('dotted', $element2_31_pStyle->getBorderTopStyle()); + // Right + self::assertEquals('00A933', $element2_31_pStyle->getBorderRightColor()); + self::assertEquals('4', $element2_31_pStyle->getBorderRightSize()); + self::assertEquals('dashed', $element2_31_pStyle->getBorderRightStyle()); + // Bottom + self::assertEquals('F10D0C', $element2_31_pStyle->getBorderBottomColor()); + self::assertEquals('8', $element2_31_pStyle->getBorderBottomSize()); + self::assertEquals('dashSmallGap', $element2_31_pStyle->getBorderBottomStyle()); + // Left + self::assertEquals('3465A4', $element2_31_pStyle->getBorderLeftColor()); + self::assertEquals('8', $element2_31_pStyle->getBorderLeftSize()); + self::assertEquals('dashed', $element2_31_pStyle->getBorderLeftStyle()); + } + /** * Load a Word 2011 file. */ public function testLoadWord2011(): void { - $filename = __DIR__ . '/../_files/documents/reader-2011.docx'; - $phpWord = IOFactory::load($filename); + $reader = new Word2007(); + $phpWord = $reader->load(dirname(__DIR__, 1) . '/_files/documents/reader-2011.docx'); - self::assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); + self::assertInstanceOf(PhpWord::class, $phpWord); $doc = TestHelperDOCX::getDocument($phpWord); self::assertTrue($doc->elementExists('/w:document/w:body/w:p[3]/w:r/w:pict/v:shape/v:imagedata')); } + + /** + * Load a Word without/withoutImages. + * + * @dataProvider providerSettingsImageLoading + */ + public function testLoadWord2011SettingsImageLoading(bool $hasImageLoading): void + { + $reader = new Word2007(); + $reader->setImageLoading($hasImageLoading); + $phpWord = $reader->load(dirname(__DIR__, 1) . '/_files/documents/reader-2011.docx'); + + self::assertInstanceOf(PhpWord::class, $phpWord); + + $sections = $phpWord->getSections(); + self::assertCount(1, $sections); + $section = $sections[0]; + $elements = $section->getElements(); + self::assertCount(3, $elements); + $element = $elements[2]; + self::assertInstanceOf(TextRun::class, $element); + $subElements = $element->getElements(); + if ($hasImageLoading) { + self::assertCount(1, $subElements); + $subElement = $subElements[0]; + self::assertInstanceOf(Image::class, $subElement); + } else { + self::assertCount(0, $subElements); + } + } + + public function providerSettingsImageLoading(): iterable + { + return [ + [true], + [false], + ]; + } + + public function testLoadComments(): void + { + $phpWord = IOFactory::load(dirname(__DIR__, 1) . '/_files/documents/reader-comments.docx'); + + self::assertInstanceOf(PhpWord::class, $phpWord); + + self::assertEquals(2, $phpWord->getComments()->countItems()); + + /** @var Comment $comment */ + $comment = $phpWord->getComments()->getItem(0); + self::assertInstanceOf(Comment::class, $comment); + self::assertEquals('shaedrich', $comment->getAuthor()); + self::assertEquals(new DateTime('2021-10-28T13:56:55Z'), $comment->getDate()); + self::assertEquals('SH', $comment->getInitials()); + self::assertCount(1, $comment->getElements()); + self::assertInstanceOf(Text::class, $comment->getElement(0)); + self::assertEquals('This this be lowercase', $comment->getElement(0)->getText()); + /** @var Font $fontStyle */ + $fontStyle = $comment->getElement(0)->getFontStyle(); + self::assertInstanceOf(Font::class, $fontStyle); + self::assertEquals('de-DE', $fontStyle->getLang()->getLatin()); + + /** @var Comment $comment */ + $comment = $phpWord->getComments()->getItem(1); + self::assertInstanceOf(Comment::class, $comment); + self::assertEquals('shaedrich', $comment->getAuthor()); + self::assertEquals(new DateTime('2021-11-02T19:10:00Z'), $comment->getDate()); + self::assertEquals('SH', $comment->getInitials()); + self::assertCount(1, $comment->getElements()); + self::assertInstanceOf(Text::class, $comment->getElement(0)); + self::assertEquals('But this should be uppercase', $comment->getElement(0)->getText()); + /** @var Font $fontStyle */ + $fontStyle = $comment->getElement(0)->getFontStyle(); + self::assertInstanceOf(Font::class, $fontStyle); + self::assertEquals('de-DE', $fontStyle->getLang()->getLatin()); + } + + public function testLoadFormula(): void + { + $phpWord = IOFactory::load(dirname(__DIR__, 1) . '/_files/documents/reader-formula.docx'); + + self::assertInstanceOf(PhpWord::class, $phpWord); + + $sections = $phpWord->getSections(); + self::assertCount(1, $sections); + + $section = $sections[0]; + self::assertInstanceOf(Section::class, $section); + + $elements = $section->getElements(); + self::assertCount(1, $elements); + + $element = $elements[0]; + self::assertInstanceOf(Formula::class, $element); + + $elements = $element->getMath()->getElements(); + self::assertCount(5, $elements); + + /** @var Element\Fraction $element */ + $element = $elements[0]; + self::assertInstanceOf(Element\Fraction::class, $element); + /** @var Element\Identifier $numerator */ + $numerator = $element->getNumerator(); + self::assertInstanceOf(Element\Identifier::class, $numerator); + self::assertEquals('π', $numerator->getValue()); + /** @var Element\Numeric $denominator */ + $denominator = $element->getDenominator(); + self::assertInstanceOf(Element\Numeric::class, $denominator); + self::assertEquals(2, $denominator->getValue()); + + /** @var Element\Operator $element */ + $element = $elements[1]; + self::assertInstanceOf(Element\Operator::class, $element); + self::assertEquals('+', $element->getValue()); + + /** @var Element\Identifier $element */ + $element = $elements[2]; + self::assertInstanceOf(Element\Identifier::class, $element); + self::assertEquals('a', $element->getValue()); + + /** @var Element\Operator $element */ + $element = $elements[3]; + self::assertInstanceOf(Element\Operator::class, $element); + self::assertEquals('∗', $element->getValue()); + + /** @var Element\Numeric $element */ + $element = $elements[4]; + self::assertInstanceOf(Element\Numeric::class, $element); + self::assertEquals(2, $element->getValue()); + } } diff --git a/tests/PhpWordTests/SettingsTest.php b/tests/PhpWordTests/SettingsTest.php index bf4dd4bf52..46c72eab28 100644 --- a/tests/PhpWordTests/SettingsTest.php +++ b/tests/PhpWordTests/SettingsTest.php @@ -2,10 +2,8 @@ /** * This file is part of PHPWord - A pure PHP library for reading and writing * word processing documents. - * * PHPWord is free software distributed under the terms of the GNU Lesser * General Public License version 3 as published by the Free Software Foundation. - * * For the full copyright and license information, please read the LICENSE * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. @@ -19,6 +17,7 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; +use PHPUnit\Framework\TestCase; /** * Test class for PhpOffice\PhpWord\Settings. @@ -27,7 +26,7 @@ * * @runTestsInSeparateProcesses */ -class SettingsTest extends \PHPUnit\Framework\TestCase +class SettingsTest extends TestCase { private $compatibility; @@ -43,12 +42,20 @@ class SettingsTest extends \PHPUnit\Framework\TestCase private $pdfRendererName; + /** + * @var array + */ + private $pdfRendererOptions; + private $pdfRendererPath; private $tempDir; private $zipClass; + /** @var bool */ + private $defaultRtl; + protected function setUp(): void { $this->compatibility = Settings::hasCompatibility(); @@ -58,9 +65,11 @@ protected function setUp(): void $this->measurementUnit = Settings::getMeasurementUnit(); $this->outputEscapingEnabled = Settings::isOutputEscapingEnabled(); $this->pdfRendererName = Settings::getPdfRendererName(); + $this->pdfRendererOptions = Settings::getPdfRendererOptions(); $this->pdfRendererPath = Settings::getPdfRendererPath(); $this->tempDir = Settings::getTempDir(); $this->zipClass = Settings::getZipClass(); + $this->defaultRtl = Settings::isDefaultRtl(); } protected function tearDown(): void @@ -72,9 +81,11 @@ protected function tearDown(): void Settings::setMeasurementUnit($this->measurementUnit); Settings::setOutputEscapingEnabled($this->outputEscapingEnabled); Settings::setPdfRendererName($this->pdfRendererName); + Settings::setPdfRendererOptions($this->pdfRendererOptions); Settings::setPdfRendererPath($this->pdfRendererPath); Settings::setTempDir($this->tempDir); Settings::setZipClass($this->zipClass); + Settings::setDefaultRtl($this->defaultRtl); } /** @@ -97,6 +108,17 @@ public function testSetGetOutputEscapingEnabled(): void self::assertTrue(Settings::isOutputEscapingEnabled()); } + public function testSetGetDefaultRtl(): void + { + self::assertNull(Settings::isDefaultRtl()); + Settings::setDefaultRtl(true); + self::assertTrue(Settings::isDefaultRtl()); + Settings::setDefaultRtl(false); + self::assertFalse(Settings::isDefaultRtl()); + Settings::setDefaultRtl(null); + self::assertNull(Settings::isDefaultRtl()); + } + /** * Test set/get zip class. */ @@ -126,6 +148,23 @@ public function testSetGetPdfRenderer(): void self::assertEquals($domPdfPath, Settings::getPdfRendererPath()); } + /** + * Test set/get PDF renderer. + */ + public function testSetGetPdfOptions(): void + { + $domPdfPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); + + self::assertEquals([], Settings::getPdfRendererOptions()); + + Settings::setPdfRendererOptions([ + 'font' => 'Arial', + ]); + self::assertEquals([ + 'font' => 'Arial', + ], Settings::getPdfRendererOptions()); + } + /** * Test set/get measurement unit. */ @@ -189,6 +228,12 @@ public function testSetGetDefaultFontSize(): void self::assertEquals(12, Settings::getDefaultFontSize()); self::assertFalse(Settings::setDefaultFontSize(null)); self::assertEquals(12, Settings::getDefaultFontSize()); + self::assertTrue(Settings::setDefaultFontSize(12.5)); + self::assertEquals(12.5, Settings::getDefaultFontSize()); + self::assertFalse(Settings::setDefaultFontSize(0.5)); + self::assertEquals(12.5, Settings::getDefaultFontSize()); + self::assertFalse(Settings::setDefaultFontSize(0)); + self::assertEquals(12.5, Settings::getDefaultFontSize()); } /** diff --git a/tests/PhpWordTests/Shared/CssTest.php b/tests/PhpWordTests/Shared/CssTest.php new file mode 100644 index 0000000000..402e953611 --- /dev/null +++ b/tests/PhpWordTests/Shared/CssTest.php @@ -0,0 +1,54 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Shared; + +use PhpOffice\PhpWord\Shared\Css; +use PHPUnit\Framework\TestCase; + +/** + * Test class for PhpOffice\PhpWord\Shared\Css. + */ +class CssTest extends TestCase +{ + public function testEmptyCss(): void + { + $css = new Css(''); + $css->process(); + + self::assertEquals([], $css->getStyles()); + } + + public function testBasicCss(): void + { + $cssContent = '.pStyle { + font-size:15px; + }'; + + $css = new Css($cssContent); + $css->process(); + + self::assertEquals([ + '.pStyle' => [ + 'font-size' => '15px', + ], + ], $css->getStyles()); + self::assertEquals([ + 'font-size' => '15px', + ], $css->getStyle('.pStyle')); + } +} diff --git a/tests/PhpWordTests/Shared/HtmlTest.php b/tests/PhpWordTests/Shared/HtmlTest.php index 9de29933e4..2d39701cd1 100644 --- a/tests/PhpWordTests/Shared/HtmlTest.php +++ b/tests/PhpWordTests/Shared/HtmlTest.php @@ -19,6 +19,7 @@ use Exception; use PhpOffice\PhpWord\Element\Section; +use PhpOffice\PhpWord\Element\Table; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Shared\Html; use PhpOffice\PhpWord\SimpleType\Jc; @@ -34,6 +35,14 @@ */ class HtmlTest extends AbstractWebServerEmbeddedTest { + /** + * Tear down after each test. + */ + protected function tearDown(): void + { + TestHelperDOCX::clear(); + } + /** * Test unit conversion functions with various numbers. */ @@ -107,6 +116,44 @@ public function testParseHtmlEntities(): void self::assertEquals('text with entities <my text>', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->nodeValue); } + public function testParseStyle(): void + { + $html = '<style type="text/css"> + .pStyle { + font-size:15px; + } + .tableStyle { + width:100%; + background-color:red; + } + </style> + + <p class="pStyle">Calculator</p>'; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:t')); + self::assertEquals('Calculator', $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:t')->nodeValue); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:rPr')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:rPr/w:sz')); + self::assertEquals('22.5', $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:r/w:rPr/w:sz', 'w:val')); + } + + public function testParseStyleTableClassName(): void + { + $html = '<style type="text/css">.pStyle { font-size:15px; }</style><table class="pStyle"><tr><td></td></tr></table>'; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, $html); + + self::assertInstanceOf(Table::class, $section->getElement(0)); + self::assertEquals('pStyle', $section->getElement(0)->getStyle()->getStyleName()); + } + /** * Test underline. */ @@ -137,6 +184,21 @@ public function testParseTextDecoration(): void self::assertEquals('single', $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:rPr/w:u', 'w:val')); } + /** + * Test font-variant style. + */ + public function testParseFontVariant(): void + { + $html = '<span style="font-variant: small-caps;">test</span>'; + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:smallCaps')); + self::assertEquals('1', $doc->getElementAttribute('/w:document/w:body/w:p/w:r/w:rPr/w:smallCaps', 'w:val')); + } + /** * Test font. */ @@ -425,6 +487,58 @@ public function testParseTableAndCellWidth(): void self::assertEquals('dxa', $doc->getElement($xpath)->getAttribute('w:type')); } + /** + * Parse heights in rows, which also allows for controlling column height. + */ + public function testParseTableRowHeight(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection([ + 'orientation' => \PhpOffice\PhpWord\Style\Section::ORIENTATION_LANDSCAPE, + ]); + + $html = <<<HTML +<table> + <tr style="height: 100px;"> + <td>100px</td> + </tr> + <tr style="height: 200pt;"> + <td>200pt</td> + </tr> + <tr> + <td> + <table> + <tr style="height: 300px;"> + <td>300px</td> + </tr> + </table> + </td> + </tr> +</table> +HTML; + + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + // <tr style="height: 100; ... 100px = 1500 twips (100 / 96 * 1440) + $xpath = '/w:document/w:body/w:tbl/w:tr/w:trPr/w:trHeight'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals(1500, $doc->getElement($xpath)->getAttribute('w:val')); + self::assertEquals('exact', $doc->getElement($xpath)->getAttribute('w:hRule')); + + // <tr style="height: 200pt; ... 200pt = 4000 twips (200 / 72 * 1440) + $xpath = '/w:document/w:body/w:tbl/w:tr[2]/w:trPr/w:trHeight'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals(4000, $doc->getElement($xpath)->getAttribute('w:val')); + self::assertEquals('exact', $doc->getElement($xpath)->getAttribute('w:hRule')); + + // <tr style="width: 300; .. 300px = 4500 twips (300 / 72 * 1440) + $xpath = '/w:document/w:body/w:tbl/w:tr[3]/w:tc/w:tbl/w:tr/w:trPr/w:trHeight'; + self::assertTrue($doc->elementExists($xpath)); + self::assertEquals(4500, $doc->getElement($xpath)->getAttribute('w:val')); + self::assertEquals('exact', $doc->getElement($xpath)->getAttribute('w:hRule')); + } + /** * Test parsing table (attribute border). */ @@ -692,6 +806,66 @@ public function testParseImage(): void self::assertStringMatchesFormat('%Smso-position-horizontal:left%S', $doc->getElementAttribute($baseXpath . '[2]/w:pict/v:shape', 'style')); } + /** + * Test parsing of img. + */ + public function testParseImageSizeInPixels(): void + { + $src = __DIR__ . '/../_files/images/firefox.png'; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '<p><img src="' . $src . '" width="150px" height="200px" /></p>'; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $baseXpath = '/w:document/w:body/w:p/w:r'; + self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape')); + self::assertStringMatchesFormat('%Swidth:150px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style')); + self::assertStringMatchesFormat('%Sheight:200px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style')); + } + + /** + * Test parsing of img. + */ + public function testParseImageSizeInPoints(): void + { + $src = __DIR__ . '/../_files/images/firefox.png'; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '<p><img src="' . $src . '" width="150pt" height="200pt" /></p>'; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $baseXpath = '/w:document/w:body/w:p/w:r'; + self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape')); + self::assertStringMatchesFormat('%Swidth:200px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style')); + self::assertStringMatchesFormat('%Sheight:266.66666666667%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style')); + } + + /** + * Test parsing of img. + */ + public function testParseImageSizeWithoutUnits(): void + { + $src = __DIR__ . '/../_files/images/firefox.png'; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '<p><img src="' . $src . '" width="150" height="200" /></p>'; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $baseXpath = '/w:document/w:body/w:p/w:r'; + self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape')); + self::assertStringMatchesFormat('%Swidth:150px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style')); + self::assertStringMatchesFormat('%Sheight:200px%S', $doc->getElementAttribute($baseXpath . '[1]/w:pict/v:shape', 'style')); + } + /** * Test parsing of remote img. */ @@ -710,6 +884,24 @@ public function testParseRemoteImage(): void self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape')); } + /** + * Test parsing of remote img without extension. + */ + public function testParseRemoteImageWithoutExtension(): void + { + $src = self::getRemoteImageUrlWithoutExtension(); + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $html = '<p><img src="' . $src . '" width="150" height="200" style="float: right;"/><img src="' . $src . '" style="float: left;"/></p>'; + Html::addHtml($section, $html); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $baseXpath = '/w:document/w:body/w:p/w:r'; + self::assertTrue($doc->elementExists($baseXpath . '/w:pict/v:shape')); + } + /** * Test parsing embedded image. */ @@ -767,7 +959,7 @@ public function testParseLink(): void { $phpWord = new PhpWord(); $section = $phpWord->addSection(); - $html = '<p><a href="http://phpword.readthedocs.io/" style="text-decoration: underline">link text</a></p>'; + $html = '<p><a href="https://phpoffice.github.io/PHPWord/" style="text-decoration: underline">link text</a></p>'; Html::addHtml($section, $html); $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); @@ -776,7 +968,10 @@ public function testParseLink(): void self::assertEquals('link text', $doc->getElement('/w:document/w:body/w:p/w:hyperlink/w:r/w:t')->nodeValue); self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:hyperlink/w:r/w:rPr/w:u')); self::assertEquals('single', $doc->getElementAttribute('/w:document/w:body/w:p/w:hyperlink/w:r/w:rPr/w:u', 'w:val')); + } + public function testParseLink2(): void + { $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addBookmark('bookmark'); @@ -789,6 +984,27 @@ public function testParseLink(): void self::assertEquals('bookmark', $doc->getElement('/w:document/w:body/w:p/w:hyperlink')->getAttribute('w:anchor')); } + public function testParseLinkAllowsAbsenceOfHref(): void + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $html = '<p><a>text of href-less link</a></p>'; + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:hyperlink')); + self::assertEquals('text of href-less link', $doc->getElement('/w:document/w:body/w:p/w:hyperlink/w:r/w:t')->nodeValue); + + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + $html = '<p><a href="">text of empty-href link</a></p>'; + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:hyperlink')); + self::assertEquals('text of empty-href link', $doc->getElement('/w:document/w:body/w:p/w:hyperlink/w:r/w:t')->nodeValue); + } + public function testParseMalformedStyleIsIgnored(): void { $phpWord = new PhpWord(); diff --git a/tests/PhpWordTests/Shared/TextTest.php b/tests/PhpWordTests/Shared/TextTest.php index fdd9e9df7a..ee49573722 100644 --- a/tests/PhpWordTests/Shared/TextTest.php +++ b/tests/PhpWordTests/Shared/TextTest.php @@ -66,7 +66,7 @@ public function testIsUTF8(): void { self::assertTrue(Text::isUTF8('')); self::assertTrue(Text::isUTF8('éééé')); - self::assertFalse(Text::isUTF8(utf8_decode('éééé'))); + self::assertFalse(Text::isUTF8(utf8decode('éééé'))); } /** diff --git a/tests/PhpWordTests/Shared/ValidateTest.php b/tests/PhpWordTests/Shared/ValidateTest.php new file mode 100644 index 0000000000..b736427150 --- /dev/null +++ b/tests/PhpWordTests/Shared/ValidateTest.php @@ -0,0 +1,75 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Shared; + +use PhpOffice\PhpWord\Shared\Validate; + +class ValidateTest extends \PHPUnit\Framework\TestCase +{ + /** + * @dataProvider providerCSSGenericFont + */ + public function testValidateCSSGenericFont(?string $value, string $expected): void + { + self::assertEquals($expected, Validate::validateCSSGenericFont($value)); + } + + public static function providerCSSGenericFont(): iterable + { + $data = []; + // Valid data + foreach (Validate::CSS_GENERICFONT as $value) { + $data[] = [ + $value, + $value, + ]; + } + // Invalid data + $data[] = ['invalidData', '']; + $data[] = ['', '']; + $data[] = [null, '']; + + return $data; + } + + /** + * @dataProvider providerCSSWhiteSpace + */ + public function testValidateCSSWhiteSpace(?string $value, string $expected): void + { + self::assertEquals($expected, Validate::validateCSSWhiteSpace($value)); + } + + public static function providerCSSWhiteSpace(): iterable + { + $data = []; + // Valid data + foreach (Validate::CSS_WHITESPACE as $value) { + $data[] = [ + $value, + $value, + ]; + } + // Invalid data + $data[] = ['invalidData', '']; + $data[] = ['', '']; + $data[] = [null, '']; + + return $data; + } +} diff --git a/tests/PhpWordTests/Shared/XMLReaderTest.php b/tests/PhpWordTests/Shared/XMLReaderTest.php index 43090cfba1..cc15c85f01 100644 --- a/tests/PhpWordTests/Shared/XMLReaderTest.php +++ b/tests/PhpWordTests/Shared/XMLReaderTest.php @@ -58,6 +58,21 @@ public function testDomFromZip(): void self::assertFalse($reader->getDomFromZip($archiveFile, 'non_existing_xml_file.xml')); } + /** + * Office 365 add some slash before the path of XML file. + */ + public function testDomFromZipOffice365(): void + { + $archiveFile = __DIR__ . '/../_files/xml/reader.zip'; + + $reader = new XMLReader(); + $reader->getDomFromZip($archiveFile, '/test.xml'); + + self::assertTrue($reader->elementExists('/element/child')); + + self::assertFalse($reader->getDomFromZip($archiveFile, 'non_existing_xml_file.xml')); + } + /** * Test that read from non existing archive throws exception. */ @@ -70,6 +85,33 @@ public function testThrowsExceptionOnNonExistingArchive(): void $reader->getDomFromZip($archiveFile, 'test.xml'); } + /** + * Test that read from invalid archive throws exception. + */ + public function testThrowsExceptionOnZipArchiveOpenErrors(): void + { + /** + * @var string + */ + $tempPath = tempnam(sys_get_temp_dir(), 'PhpWord'); + + // Simulate a corrupt archive + file_put_contents($tempPath, mt_rand()); + + $exceptionMessage = null; + + try { + $reader = new XMLReader(); + $reader->getDomFromZip($tempPath, 'test.xml'); + } catch (Exception $e) { + $exceptionMessage = $e->getMessage(); + } + + self::assertNotNull($exceptionMessage); + + unlink($tempPath); + } + /** * Test elements count. */ @@ -87,7 +129,7 @@ public function testCountElements(): void public function testReturnNullOnNonExistingNode(): void { $reader = new XMLReader(); - self::assertEmpty($reader->getElements('/element/children')); + self::assertSame(0, $reader->getElements('/element/children')->length); $reader->getDomFromString('<element><child>AAA</child></element>'); self::assertNull($reader->getElement('/element/children')); diff --git a/tests/PhpWordTests/Style/FontTest.php b/tests/PhpWordTests/Style/FontTest.php index 854a4b5951..d2dba6b73e 100644 --- a/tests/PhpWordTests/Style/FontTest.php +++ b/tests/PhpWordTests/Style/FontTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWordTests\Style; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Language; @@ -79,6 +80,8 @@ public function testSetStyleValueWithNullOrEmpty(): void 'kerning' => null, 'lang' => null, 'hidden' => false, + 'whiteSpace' => '', + 'fallbackFont' => '', ]; foreach ($attributes as $key => $default) { $get = is_bool($default) ? "is{$key}" : "get{$key}"; @@ -121,6 +124,8 @@ public function testSetStyleValueNormal(): void 'noProof' => true, 'lang' => new Language(Language::EN_US), 'hidden' => true, + 'whiteSpace' => 'pre-wrap', + 'fallbackFont' => 'serif', ]; $object->setStyleByArray($attributes); foreach ($attributes as $key => $value) { @@ -150,6 +155,7 @@ public function testLineHeight(): void self::assertEquals('auto', $lineRule); // Test setter + TestHelperDOCX::clear(); $text->getFontStyle()->setLineHeight(3.0); $doc = TestHelperDOCX::getDocument($phpWord); $element = $doc->getElement('/w:document/w:body/w:p/w:pPr/w:spacing'); @@ -191,4 +197,31 @@ public function testSetLangAsString(): void self::assertInstanceOf('PhpOffice\PhpWord\Style\Language', $object->getLang()); self::assertEquals(Language::FR_BE, $object->getLang()->getLatin()); } + + public function testRTL(): void + { + $object = new Font(); + self::assertNull($object->isRTL()); + self::assertInstanceOf(Font::class, $object->setRTL(true)); + self::assertTrue($object->isRTL()); + self::assertInstanceOf(Font::class, $object->setRTL(false)); + self::assertFalse($object->isRTL()); + } + + public function testRTLSettings(): void + { + Settings::setDefaultRtl(null); + $object = new Font(); + self::assertNull($object->isRTL()); + + Settings::setDefaultRtl(true); + $object = new Font(); + self::assertTrue($object->isRTL()); + + Settings::setDefaultRtl(false); + $object = new Font(); + self::assertFalse($object->isRTL()); + + Settings::setDefaultRtl(null); + } } diff --git a/tests/PhpWordTests/Style/ParagraphTest.php b/tests/PhpWordTests/Style/ParagraphTest.php index 124b8829c8..3f44c8eb2d 100644 --- a/tests/PhpWordTests/Style/ParagraphTest.php +++ b/tests/PhpWordTests/Style/ParagraphTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWordTests\Style; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\LineSpacingRule; use PhpOffice\PhpWord\Style\Paragraph; use PhpOffice\PhpWord\Style\Tab; @@ -91,13 +92,20 @@ public function testSetStyleValueNormal(): void foreach ($attributes as $key => $value) { $get = $this->findGetter($key, $value, $object); $object->setStyleValue("$key", $value); - if ('indent' == $key || 'hanging' == $key) { + if (('indent' == $key || 'hanging' == $key) && is_numeric($value)) { $value = $value * 720; } self::assertEquals($value, $object->$get()); } } + /** + * @param string $key + * @param mixed $value + * @param object $object + * + * @return string + */ private function findGetter($key, $value, $object) { if (is_bool($value)) { @@ -157,6 +165,7 @@ public function testLineHeight(): void // Test setter $text->getParagraphStyle()->setLineHeight(3.0); + TestHelperDOCX::clear(); $doc = TestHelperDOCX::getDocument($phpWord); $element = $doc->getElement('/w:document/w:body/w:p/w:pPr/w:spacing'); @@ -186,4 +195,33 @@ public function testLineHeightException(): void $object = new Paragraph(); $object->setLineHeight('a'); } + + public function testBidiVisual(): void + { + $object = new Paragraph(); + self::assertNull($object->isBidi()); + self::assertInstanceOf(Paragraph::class, $object->setBidi(true)); + self::assertTrue($object->isBidi()); + self::assertInstanceOf(Paragraph::class, $object->setBidi(false)); + self::assertFalse($object->isBidi()); + self::assertInstanceOf(Paragraph::class, $object->setBidi(null)); + self::assertNull($object->isBidi()); + } + + public function testBidiVisualSettings(): void + { + Settings::setDefaultRtl(null); + $object = new Paragraph(); + self::assertNull($object->isBidi()); + + Settings::setDefaultRtl(true); + $object = new Paragraph(); + self::assertTrue($object->isBidi()); + + Settings::setDefaultRtl(false); + $object = new Paragraph(); + self::assertFalse($object->isBidi()); + + Settings::setDefaultRtl(null); + } } diff --git a/tests/PhpWordTests/Style/TableTest.php b/tests/PhpWordTests/Style/TableTest.php index 393271ca97..2bdb48be1f 100644 --- a/tests/PhpWordTests/Style/TableTest.php +++ b/tests/PhpWordTests/Style/TableTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWordTests\Style; use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\JcTable; use PhpOffice\PhpWord\SimpleType\TblWidth; use PhpOffice\PhpWord\Style\Table; @@ -101,6 +102,35 @@ public function testSetGetNormal(): void } } + public function testBidiVisual(): void + { + $object = new Table(); + self::assertNull($object->isBidiVisual()); + self::assertInstanceOf(Table::class, $object->setBidiVisual(true)); + self::assertTrue($object->isBidiVisual()); + self::assertInstanceOf(Table::class, $object->setBidiVisual(false)); + self::assertFalse($object->isBidiVisual()); + self::assertInstanceOf(Table::class, $object->setBidiVisual(null)); + self::assertNull($object->isBidiVisual()); + } + + public function testBidiVisualSettings(): void + { + Settings::setDefaultRtl(null); + $object = new Table(); + self::assertNull($object->isBidiVisual()); + + Settings::setDefaultRtl(true); + $object = new Table(); + self::assertTrue($object->isBidiVisual()); + + Settings::setDefaultRtl(false); + $object = new Table(); + self::assertFalse($object->isBidiVisual()); + + Settings::setDefaultRtl(null); + } + /** * Test border color. * diff --git a/tests/PhpWordTests/Style/TextBoxTest.php b/tests/PhpWordTests/Style/TextBoxTest.php index 3c1c243676..c6647c5084 100644 --- a/tests/PhpWordTests/Style/TextBoxTest.php +++ b/tests/PhpWordTests/Style/TextBoxTest.php @@ -2,10 +2,8 @@ /** * This file is part of PHPWord - A pure PHP library for reading and writing * word processing documents. - * * PHPWord is free software distributed under the terms of the GNU Lesser * General Public License version 3 as published by the Free Software Foundation. - * * For the full copyright and license information, please read the LICENSE * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. @@ -20,6 +18,7 @@ use InvalidArgumentException; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\Style\TextBox; +use PHPUnit\Framework\TestCase; /** * Test class for PhpOffice\PhpWord\Style\Image. @@ -28,7 +27,7 @@ * * @runTestsInSeparateProcesses */ -class TextBoxTest extends \PHPUnit\Framework\TestCase +class TextBoxTest extends TestCase { /** * Test setting style with normal value. @@ -55,6 +54,7 @@ public function testSetGetNormal(): void 'innerMarginLeft' => '5', 'borderSize' => '2', 'borderColor' => 'red', + 'bgColor' => 'blue', ]; foreach ($properties as $key => $value) { $set = "set{$key}"; @@ -89,6 +89,7 @@ public function testSetStyleValue(): void 'innerMarginLeft' => '5', 'borderSize' => '2', 'borderColor' => 'red', + 'bgColor' => 'blue', ]; foreach ($properties as $key => $value) { $get = "get{$key}"; @@ -305,4 +306,15 @@ public function testSetGetBorderColor(): void $object->setBorderColor($expected); self::assertEquals($expected, $object->getBorderColor()); } + + /** + * Test set/get bgColor. + */ + public function testSetGetBgColor(): void + { + $expected = 'blue'; + $object = new TextBox(); + $object->setBgColor($expected); + self::assertEquals($expected, $object->getBgColor()); + } } diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index fc975fab57..b8ad970ced 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -25,6 +25,8 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\TemplateProcessor; +use Throwable; +use TypeError; use ZipArchive; /** @@ -36,16 +38,47 @@ */ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase { + /** @var ?TemplateProcessor */ + private $templateProcessor; + + private function getTemplateProcessor(string $filename): TemplateProcessor + { + $this->templateProcessor = new TemplateProcessor($filename); + + return $this->templateProcessor; + } + + protected function tearDown(): void + { + if ($this->templateProcessor !== null) { + $filename = $this->templateProcessor->getTempDocumentFilename(); + $this->templateProcessor = null; + if (file_exists($filename)) { + @unlink($filename); + } + } + } + /** * Construct test. * * @covers ::__construct + * @covers ::__destruct + * @covers \PhpOffice\PhpWord\Shared\ZipArchive::close */ public function testTheConstruct(): void { - $object = new TemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); + $object = $this->getTemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); self::assertInstanceOf('PhpOffice\\PhpWord\\TemplateProcessor', $object); self::assertEquals([], $object->getVariables()); + $object->save(); + + try { + $object->zip()->close(); + self::fail('Expected exception for double close'); + } catch (Throwable $e) { + // nothing to do here + } } /** @@ -54,11 +87,8 @@ public function testTheConstruct(): void * @covers ::save * @covers ::zip */ - public function testTemplateCanBeSavedInTemporaryLocation() + public function xtestTemplateCanBeSavedInTemporaryLocation(string $templateFqfn, TemplateProcessor $templateProcessor): string { - $templateFqfn = __DIR__ . '/_files/templates/with_table_macros.docx'; - - $templateProcessor = new TemplateProcessor($templateFqfn); $xslDomDocument = new DOMDocument(); $xslDomDocument->load(__DIR__ . '/_files/xsl/remove_tables_by_needle.xsl'); foreach (['${employee.', '${scoreboard.', '${reference.'] as $needle) { @@ -103,13 +133,13 @@ public function testTemplateCanBeSavedInTemporaryLocation() * XSL stylesheet can be applied. * * @covers ::applyXslStyleSheet - * - * @depends testTemplateCanBeSavedInTemporaryLocation - * - * @param string $actualDocumentFqfn */ - public function testXslStyleSheetCanBeApplied($actualDocumentFqfn): void + public function testXslStyleSheetCanBeApplied(): void { + $templateFqfn = __DIR__ . '/_files/templates/with_table_macros.docx'; + $templateProcessor = $this->getTemplateProcessor($templateFqfn); + + $actualDocumentFqfn = $this->xtestTemplateCanBeSavedInTemporaryLocation($templateFqfn, $templateProcessor); $expectedDocumentFqfn = __DIR__ . '/_files/documents/without_table_macros.docx'; $actualDocumentZip = new ZipArchive(); @@ -130,9 +160,9 @@ public function testXslStyleSheetCanBeApplied($actualDocumentFqfn): void throw new Exception("Could not close zip file \"{$expectedDocumentFqfn}\"."); } - self::assertXmlStringEqualsXmlString($expectedHeaderXml, $actualHeaderXml); - self::assertXmlStringEqualsXmlString($expectedMainPartXml, $actualMainPartXml); - self::assertXmlStringEqualsXmlString($expectedFooterXml, $actualFooterXml); + self::assertSame($expectedHeaderXml, $actualHeaderXml); + self::assertSame($expectedMainPartXml, $actualMainPartXml); + self::assertSame($expectedFooterXml, $actualFooterXml); } /** @@ -142,14 +172,16 @@ public function testXslStyleSheetCanBeApplied($actualDocumentFqfn): void */ public function testXslStyleSheetCanNotBeAppliedOnFailureOfSettingParameterValue(): void { - $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); - $this->expectExceptionMessage('Could not set values for the given XSL style sheet parameters.'); - // Test is not needed for PHP 8.0, because internally validation throws TypeError exception. if (\PHP_VERSION_ID >= 80000) { - self::markTestSkipped('not needed for PHP 8.0'); + // PHP 8+ internal validation throws TypeError. + $this->expectException(TypeError::class); + $this->expectExceptionMessage('must contain only string keys'); + } else { + $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectExceptionMessage('Could not set values for the given XSL style sheet parameters.'); } - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/blank.docx'); $xslDomDocument = new DOMDocument(); $xslDomDocument->load(__DIR__ . '/_files/xsl/passthrough.xsl'); @@ -170,7 +202,7 @@ public function testXslStyleSheetCanNotBeAppliedOnFailureOfLoadingXmlFromTemplat { $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); $this->expectExceptionMessage('Could not load the given XML document.'); - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/corrupted_main_document_part.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/corrupted_main_document_part.docx'); $xslDomDocument = new DOMDocument(); $xslDomDocument->load(__DIR__ . '/_files/xsl/passthrough.xsl'); @@ -182,6 +214,32 @@ public function testXslStyleSheetCanNotBeAppliedOnFailureOfLoadingXmlFromTemplat @$templateProcessor->applyXslStyleSheet($xslDomDocument); } + /** + * @covers ::deleteRow + * @covers ::getVariables + * @covers ::saveAs + */ + public function testDeleteRow(): void + { + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/delete-row.docx'); + + self::assertEquals( + ['deleteMe', 'deleteMeToo'], + $templateProcessor->getVariables() + ); + + $docName = 'delete-row-test-result.docx'; + $templateProcessor->deleteRow('deleteMe'); + self::assertEquals( + [], + $templateProcessor->getVariables() + ); + $templateProcessor->saveAs($docName); + $docFound = file_exists($docName); + unlink($docName); + self::assertTrue($docFound); + } + /** * @covers ::cloneRow * @covers ::saveAs @@ -189,7 +247,34 @@ public function testXslStyleSheetCanNotBeAppliedOnFailureOfLoadingXmlFromTemplat */ public function testCloneRow(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/clone-merge.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/clone-merge.docx'); + + self::assertEquals( + ['tableHeader', 'userId', 'userName', 'userLocation'], + $templateProcessor->getVariables() + ); + + $docName = 'clone-test-result.docx'; + $templateProcessor->setValue('tableHeader', 'ééé'); + $templateProcessor->cloneRow('userId', 1); + $templateProcessor->setValue('userId#1', 'Test'); + $templateProcessor->saveAs($docName); + $docFound = file_exists($docName); + unlink($docName); + self::assertTrue($docFound); + } + + /** + * @covers ::cloneRow + * @covers ::saveAs + * @covers ::setValue + */ + public function testCloneRowWithCustomMacro(): void + { + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/clone-merge-with-custom-macro.docx'); + + $templateProcessor->setMacroOpeningChars('{#'); + $templateProcessor->setMacroClosingChars('#}'); self::assertEquals( ['tableHeader', 'userId', 'userName', 'userLocation'], @@ -197,7 +282,7 @@ public function testCloneRow(): void ); $docName = 'clone-test-result.docx'; - $templateProcessor->setValue('tableHeader', utf8_decode('ééé')); + $templateProcessor->setValue('tableHeader', 'ééé'); $templateProcessor->cloneRow('userId', 1); $templateProcessor->setValue('userId#1', 'Test'); $templateProcessor->saveAs($docName); @@ -275,13 +360,98 @@ public function testCloneNotExistingRowShouldThrowException(): void $templateProcessor->cloneRow('fake_search', 2); } + /** + * @covers ::cloneRow + * @covers ::saveAs + * @covers ::setValue + */ + public function testCloneRowAndSetValuesWithCustomMacro(): void + { + $mainPart = '<w:tbl> + <w:tr> + <w:tc> + <w:tcPr> + <w:vMerge w:val="restart"/> + </w:tcPr> + <w:p> + <w:r> + <w:t>{{userId}}</w:t> + </w:r> + </w:p> + </w:tc> + <w:tc> + <w:p> + <w:r> + <w:t>{{userName}}</w:t> + </w:r> + </w:p> + </w:tc> + </w:tr> + <w:tr> + <w:tc> + <w:tcPr> + <w:vMerge/> + </w:tcPr> + <w:p/> + </w:tc> + <w:tc> + <w:p> + <w:r> + <w:t>{{userLocation}}</w:t> + </w:r> + </w:p> + </w:tc> + </w:tr> + </w:tbl>'; + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setMacroOpeningChars('{{'); + $templateProcessor->setMacroClosingChars('}}'); + + self::assertEquals( + ['userId', 'userName', 'userLocation'], + $templateProcessor->getVariables() + ); + + $values = [ + ['userId' => 1, 'userName' => 'Batman', 'userLocation' => 'Gotham City'], + ['userId' => 2, 'userName' => 'Superman', 'userLocation' => 'Metropolis'], + ]; + $templateProcessor->setValue('tableHeader', 'My clonable table'); + $templateProcessor->cloneRowAndSetValues('userId', $values); + self::assertStringContainsString('<w:t>Superman</w:t>', $templateProcessor->getMainPart()); + self::assertStringContainsString('<w:t>Metropolis</w:t>', $templateProcessor->getMainPart()); + } + /** * @covers ::saveAs * @covers ::setValue */ public function testMacrosCanBeReplacedInHeaderAndFooter(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx'); + + self::assertEquals(['documentContent', 'headerValue:100:100', 'footerValue'], $templateProcessor->getVariables()); + + $macroNames = ['headerValue', 'documentContent', 'footerValue']; + $macroValues = ['Header Value', 'Document text.', 'Footer Value']; + $templateProcessor->setValue($macroNames, $macroValues); + + $docName = 'header-footer-test-result.docx'; + $templateProcessor->saveAs($docName); + $docFound = file_exists($docName); + unlink($docName); + self::assertTrue($docFound); + } + + /** + * @covers ::saveAs + * @covers ::setValue + */ + public function testCustomMacrosCanBeReplacedInHeaderAndFooter(): void + { + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/header-footer-with-custom-macro.docx'); + $templateProcessor->setMacroOpeningChars('{{'); + $templateProcessor->setMacroClosingChars('}}'); self::assertEquals(['documentContent', 'headerValue:100:100', 'footerValue'], $templateProcessor->getVariables()); @@ -301,7 +471,23 @@ public function testMacrosCanBeReplacedInHeaderAndFooter(): void */ public function testSetValue(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/clone-merge.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/clone-merge.docx'); + Settings::setOutputEscapingEnabled(true); + $helloworld = "hello\nworld"; + $templateProcessor->setValue('userName', $helloworld); + self::assertEquals( + ['tableHeader', 'userId', 'userLocation'], + $templateProcessor->getVariables() + ); + } + + /** + * @covers ::setValue + */ + public function testSetValueWithCustomMacro(): void + { + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/clone-merge-with-custom-macro.docx'); + $templateProcessor->setMacroChars('{#', '#}'); Settings::setOutputEscapingEnabled(true); $helloworld = "hello\nworld"; $templateProcessor->setValue('userName', $helloworld); @@ -364,6 +550,60 @@ public function testSetComplexValue(): void self::assertEquals(preg_replace('/>\s+</', '><', $result), preg_replace('/>\s+</', '><', $templateProcessor->getMainPart())); } + public function testSetComplexValueWithCustomMacro(): void + { + $title = new TextRun(); + $title->addText('This is my title'); + + $firstname = new Text('Donald'); + $lastname = new Text('Duck'); + + $mainPart = '<?xml version="1.0" encoding="UTF-8"?> + <w:p> + <w:r> + <w:t xml:space="preserve">Hello {{document-title}}</w:t> + </w:r> + </w:p> + <w:p> + <w:r> + <w:t xml:space="preserve">Hello {{firstname}} {{lastname}}</w:t> + </w:r> + </w:p>'; + + $result = '<?xml version="1.0" encoding="UTF-8"?> + <w:p> + <w:pPr/> + <w:r> + <w:rPr/> + <w:t xml:space="preserve">This is my title</w:t> + </w:r> + </w:p> + <w:p> + <w:r> + <w:t xml:space="preserve">Hello </w:t> + </w:r> + <w:r> + <w:rPr/> + <w:t xml:space="preserve">Donald</w:t> + </w:r> + <w:r> + <w:t xml:space="preserve"> </w:t> + </w:r> + <w:r> + <w:rPr/> + <w:t xml:space="preserve">Duck</w:t> + </w:r> + </w:p>'; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setMacroChars('{{', '}}'); + $templateProcessor->setComplexBlock('document-title', $title); + $templateProcessor->setComplexValue('firstname', $firstname); + $templateProcessor->setComplexValue('lastname', $lastname); + + self::assertEquals(preg_replace('/>\s+</', '><', $result), preg_replace('/>\s+</', '><', $templateProcessor->getMainPart())); + } + /** * @covers ::setValues */ @@ -383,53 +623,243 @@ public function testSetValues(): void } /** - * @covers ::setImageValue + * @covers ::setValues */ - public function testSetImageValue(): void + public function testSetValuesMultiLine(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx'); - $imagePath = __DIR__ . '/_files/images/earth.jpg'; + $mainPart = '<?xml version="1.0" encoding="UTF-8"?> + <w:p> + <w:r> + <w:t xml:space="preserve">Address: ${address}</w:t> + </w:r> + </w:p>'; - $variablesReplace = [ - 'headerValue' => function () use ($imagePath) { - return $imagePath; - }, - 'documentContent' => ['path' => $imagePath, 'width' => 500, 'height' => 500], - 'footerValue' => ['path' => $imagePath, 'width' => 100, 'height' => 50, 'ratio' => false], - ]; - $templateProcessor->setImageValue(array_keys($variablesReplace), $variablesReplace); + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setValues(['address' => "Peter Pan\nNeverland"]); - $docName = 'header-footer-images-test-result.docx'; - $templateProcessor->saveAs($docName); + self::assertStringContainsString('Address: Peter Pan</w:t><w:br/><w:t>Neverland', $templateProcessor->getMainPart()); + } - self::assertFileExists($docName, "Generated file '{$docName}' not found!"); + /** + * @covers ::setValues + */ + public function testSetValuesWithCustomMacro(): void + { + $mainPart = '<?xml version="1.0" encoding="UTF-8"?> + <w:p> + <w:r> + <w:t xml:space="preserve">Hello {#firstname#} {#lastname#}</w:t> + </w:r> + </w:p>'; - $expectedDocumentZip = new ZipArchive(); - $expectedDocumentZip->open($docName); - $expectedContentTypesXml = $expectedDocumentZip->getFromName('[Content_Types].xml'); - $expectedDocumentRelationsXml = $expectedDocumentZip->getFromName('word/_rels/document.xml.rels'); - $expectedHeaderRelationsXml = $expectedDocumentZip->getFromName('word/_rels/header1.xml.rels'); - $expectedFooterRelationsXml = $expectedDocumentZip->getFromName('word/_rels/footer1.xml.rels'); - $expectedMainPartXml = $expectedDocumentZip->getFromName('word/document.xml'); - $expectedHeaderPartXml = $expectedDocumentZip->getFromName('word/header1.xml'); - $expectedFooterPartXml = $expectedDocumentZip->getFromName('word/footer1.xml'); - $expectedImage = $expectedDocumentZip->getFromName('word/media/image_rId11_document.jpeg'); - if (false === $expectedDocumentZip->close()) { - throw new Exception("Could not close zip file \"{$docName}\"."); - } + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setMacroChars('{#', '#}'); + $templateProcessor->setValues(['firstname' => 'John', 'lastname' => 'Doe']); - self::assertNotEmpty($expectedImage, 'Embed image doesn\'t found.'); - self::assertStringContainsString('/word/media/image_rId11_document.jpeg', $expectedContentTypesXml, '[Content_Types].xml missed "/word/media/image5_document.jpeg"'); - self::assertStringContainsString('/word/_rels/header1.xml.rels', $expectedContentTypesXml, '[Content_Types].xml missed "/word/_rels/header1.xml.rels"'); - self::assertStringContainsString('/word/_rels/footer1.xml.rels', $expectedContentTypesXml, '[Content_Types].xml missed "/word/_rels/footer1.xml.rels"'); - self::assertStringNotContainsString('${documentContent}', $expectedMainPartXml, 'word/document.xml has no image.'); - self::assertStringNotContainsString('${headerValue}', $expectedHeaderPartXml, 'word/header1.xml has no image.'); - self::assertStringNotContainsString('${footerValue}', $expectedFooterPartXml, 'word/footer1.xml has no image.'); - self::assertStringContainsString('media/image_rId11_document.jpeg', $expectedDocumentRelationsXml, 'word/_rels/document.xml.rels missed "media/image5_document.jpeg"'); - self::assertStringContainsString('media/image_rId11_document.jpeg', $expectedHeaderRelationsXml, 'word/_rels/header1.xml.rels missed "media/image5_document.jpeg"'); - self::assertStringContainsString('media/image_rId11_document.jpeg', $expectedFooterRelationsXml, 'word/_rels/footer1.xml.rels missed "media/image5_document.jpeg"'); + self::assertStringContainsString('Hello John Doe', $templateProcessor->getMainPart()); + } - unlink($docName); + /** + * @covers ::setCheckbox + */ + public function testSetCheckbox(): void + { + $mainPart = '<?xml version="1.0" encoding="UTF-8"?> + <w:p> + <w:sdt> + <w:sdtPr> + <w:alias w:val="${checkbox}"/> + <w14:checkbox> + <w14:checked w14:val="0"/> + </w14:checkbox> + </w:sdtPr> + <w:sdtContent> + <w:r> + <w:t>☐</w:t> + </w:r> + </w:sdtContent> + </w:sdt> + </w:p> + <w:p> + <w:sdt> + <w:sdtPr> + <w:alias w:val="${checkbox2}"/> + <w14:checkbox> + <w14:checked w14:val="1"/> + </w14:checkbox> + </w:sdtPr> + <w:sdtContent> + <w:r> + <w:t>☒</w:t> + </w:r> + </w:sdtContent> + </w:sdt> + </w:p>'; + + $result = '<?xml version="1.0" encoding="UTF-8"?> + <w:p> + <w:sdt> + <w:sdtPr> + <w:alias w:val="${checkbox}"/> + <w14:checkbox> + <w14:checked w14:val="1"/> + </w14:checkbox> + </w:sdtPr> + <w:sdtContent> + <w:r> + <w:t>☒</w:t> + </w:r> + </w:sdtContent> + </w:sdt> + </w:p> + <w:p> + <w:sdt> + <w:sdtPr> + <w:alias w:val="${checkbox2}"/> + <w14:checkbox> + <w14:checked w14:val="0"/> + </w14:checkbox> + </w:sdtPr> + <w:sdtContent> + <w:r> + <w:t>☐</w:t> + </w:r> + </w:sdtContent> + </w:sdt> + </w:p>'; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setCheckbox('checkbox', true); + $templateProcessor->setCheckbox('checkbox2', false); + + self::assertEquals(preg_replace('/>\s+</', '><', $result), preg_replace('/>\s+</', '><', $templateProcessor->getMainPart())); + } + + /** + * @covers ::setCheckbox + */ + public function testSetCheckboxWithCustomMacro(): void + { + $mainPart = '<?xml version="1.0" encoding="UTF-8"?> + <w:p> + <w:sdt> + <w:sdtPr> + <w:alias w:val="{#checkbox#}"/> + <w14:checkbox> + <w14:checked w14:val="0"/> + </w14:checkbox> + </w:sdtPr> + <w:sdtContent> + <w:r> + <w:t>☐</w:t> + </w:r> + </w:sdtContent> + </w:sdt> + </w:p> + <w:p> + <w:sdt> + <w:sdtPr> + <w:alias w:val="{#checkbox2#}"/> + <w14:checkbox> + <w14:checked w14:val="1"/> + </w14:checkbox> + </w:sdtPr> + <w:sdtContent> + <w:r> + <w:t>☒</w:t> + </w:r> + </w:sdtContent> + </w:sdt> + </w:p>'; + + $result = '<?xml version="1.0" encoding="UTF-8"?> + <w:p> + <w:sdt> + <w:sdtPr> + <w:alias w:val="{#checkbox#}"/> + <w14:checkbox> + <w14:checked w14:val="1"/> + </w14:checkbox> + </w:sdtPr> + <w:sdtContent> + <w:r> + <w:t>☒</w:t> + </w:r> + </w:sdtContent> + </w:sdt> + </w:p> + <w:p> + <w:sdt> + <w:sdtPr> + <w:alias w:val="{#checkbox2#}"/> + <w14:checkbox> + <w14:checked w14:val="0"/> + </w14:checkbox> + </w:sdtPr> + <w:sdtContent> + <w:r> + <w:t>☐</w:t> + </w:r> + </w:sdtContent> + </w:sdt> + </w:p>'; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setMacroChars('{#', '#}'); + $templateProcessor->setCheckbox('checkbox', true); + $templateProcessor->setCheckbox('checkbox2', false); + + self::assertEquals(preg_replace('/>\s+</', '><', $result), preg_replace('/>\s+</', '><', $templateProcessor->getMainPart())); + } + + /** + * @covers ::setImageValue + */ + public function testSetImageValue(): void + { + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx'); + $imagePath = __DIR__ . '/_files/images/earth.jpg'; + + $variablesReplace = [ + 'headerValue' => function () use ($imagePath) { + return $imagePath; + }, + 'documentContent' => ['path' => $imagePath, 'width' => 500, 'height' => 500], + 'footerValue' => ['path' => $imagePath, 'width' => 100, 'height' => 50, 'ratio' => false], + ]; + $templateProcessor->setImageValue(array_keys($variablesReplace), $variablesReplace); + + $docName = 'header-footer-images-test-result.docx'; + $templateProcessor->saveAs($docName); + + self::assertFileExists($docName, "Generated file '{$docName}' not found!"); + + $expectedDocumentZip = new ZipArchive(); + $expectedDocumentZip->open($docName); + $expectedContentTypesXml = $expectedDocumentZip->getFromName('[Content_Types].xml'); + $expectedDocumentRelationsXml = $expectedDocumentZip->getFromName('word/_rels/document.xml.rels'); + $expectedHeaderRelationsXml = $expectedDocumentZip->getFromName('word/_rels/header1.xml.rels'); + $expectedFooterRelationsXml = $expectedDocumentZip->getFromName('word/_rels/footer1.xml.rels'); + $expectedMainPartXml = $expectedDocumentZip->getFromName('word/document.xml'); + $expectedHeaderPartXml = $expectedDocumentZip->getFromName('word/header1.xml'); + $expectedFooterPartXml = $expectedDocumentZip->getFromName('word/footer1.xml'); + $expectedImage = $expectedDocumentZip->getFromName('word/media/image_rId11_document.jpeg'); + if (false === $expectedDocumentZip->close()) { + throw new Exception("Could not close zip file \"{$docName}\"."); + } + + self::assertNotEmpty($expectedImage, 'Embed image doesn\'t found.'); + self::assertStringContainsString('/word/media/image_rId11_document.jpeg', $expectedContentTypesXml, '[Content_Types].xml missed "/word/media/image5_document.jpeg"'); + self::assertStringContainsString('/word/_rels/header1.xml.rels', $expectedContentTypesXml, '[Content_Types].xml missed "/word/_rels/header1.xml.rels"'); + self::assertStringContainsString('/word/_rels/footer1.xml.rels', $expectedContentTypesXml, '[Content_Types].xml missed "/word/_rels/footer1.xml.rels"'); + self::assertStringNotContainsString('${documentContent}', $expectedMainPartXml, 'word/document.xml has no image.'); + self::assertStringNotContainsString('${headerValue}', $expectedHeaderPartXml, 'word/header1.xml has no image.'); + self::assertStringNotContainsString('${footerValue}', $expectedFooterPartXml, 'word/footer1.xml has no image.'); + self::assertStringContainsString('media/image_rId11_document.jpeg', $expectedDocumentRelationsXml, 'word/_rels/document.xml.rels missed "media/image5_document.jpeg"'); + self::assertStringContainsString('media/image_rId11_document.jpeg', $expectedHeaderRelationsXml, 'word/_rels/header1.xml.rels missed "media/image5_document.jpeg"'); + self::assertStringContainsString('media/image_rId11_document.jpeg', $expectedFooterRelationsXml, 'word/_rels/footer1.xml.rels missed "media/image5_document.jpeg"'); + + unlink($docName); // dynamic generated doc $testFileName = 'images-test-sample.docx'; @@ -467,7 +897,7 @@ public function testSetImageValue(): void */ public function testCloneDeleteBlock(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/clone-delete-block.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/clone-delete-block.docx'); self::assertEquals( ['DELETEME', '/DELETEME', 'CLONEME', 'blockVariable', '/CLONEME'], @@ -507,7 +937,45 @@ public function testGetVariableCountCountsHowManyTimesEachPlaceholderIsPresent() $templatePath = 'test.docx'; $objWriter->save($templatePath); - $templateProcessor = new TemplateProcessor($templatePath); + $templateProcessor = $this->getTemplateProcessor($templatePath); + $variableCount = $templateProcessor->getVariableCount(); + unlink($templatePath); + + self::assertEquals( + [ + 'a_field_that_is_present_three_times' => 3, + 'a_field_that_is_present_twice' => 2, + 'a_field_that_is_present_one_time' => 1, + ], + $variableCount + ); + } + + /** + * @covers ::getVariableCount + */ + public function testGetVariableCountCountsHowManyTimesEachPlaceholderIsPresentWithCustomMacro(): void + { + // create template with placeholders + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $header = $section->addHeader(); + $header->addText('{{a_field_that_is_present_three_times}}'); + $footer = $section->addFooter(); + $footer->addText('{{a_field_that_is_present_twice}}'); + $section2 = $phpWord->addSection(); + $section2->addText(' + {{a_field_that_is_present_one_time}} + {{a_field_that_is_present_three_times}} + {{a_field_that_is_present_twice}} + {{a_field_that_is_present_three_times}} + '); + $objWriter = IOFactory::createWriter($phpWord); + $templatePath = 'test.docx'; + $objWriter->save($templatePath); + + $templateProcessor = $this->getTemplateProcessor($templatePath); + $templateProcessor->setMacroChars('{{', '}}'); $variableCount = $templateProcessor->getVariableCount(); unlink($templatePath); @@ -544,7 +1012,7 @@ public function testCloneBlockCanCloneABlockTwice(): void $objWriter->save($templatePath); // replace placeholders and save the file - $templateProcessor = new TemplateProcessor($templatePath); + $templateProcessor = $this->getTemplateProcessor($templatePath); $templateProcessor->setValue('title', 'Some title'); $templateProcessor->cloneBlock('subreport', 2); $templateProcessor->setValue('subreport.id', '123', 1); @@ -574,6 +1042,61 @@ public function testCloneBlockCanCloneABlockTwice(): void } } + /** + * @covers ::cloneBlock + */ + public function testCloneBlockCanCloneABlockTwiceWithCustomMacro(): void + { + // create template with placeholders and block + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $documentElements = [ + 'Title: {{title}}', + '{{subreport}}', + '{{subreport.id}}: {{subreport.text}}. ', + '{{/subreport}}', + ]; + foreach ($documentElements as $documentElement) { + $section->addText($documentElement); + } + + $objWriter = IOFactory::createWriter($phpWord); + $templatePath = 'test.docx'; + $objWriter->save($templatePath); + + // replace placeholders and save the file + $templateProcessor = $this->getTemplateProcessor($templatePath); + $templateProcessor->setMacroChars('{{', '}}'); + $templateProcessor->setValue('title', 'Some title'); + $templateProcessor->cloneBlock('subreport', 2); + $templateProcessor->setValue('subreport.id', '123', 1); + $templateProcessor->setValue('subreport.text', 'Some text', 1); + $templateProcessor->setValue('subreport.id', '456', 1); + $templateProcessor->setValue('subreport.text', 'Some other text', 1); + $templateProcessor->saveAs($templatePath); + + // assert the block has been cloned twice + // and the placeholders have been replaced correctly + $phpWord = IOFactory::load($templatePath); + $sections = $phpWord->getSections(); + /** @var \PhpOffice\PhpWord\Element\TextRun[] $actualElements */ + $actualElements = $sections[0]->getElements(); + + unlink($templatePath); + $expectedElements = [ + 'Title: Some title', + '123: Some text. ', + '456: Some other text. ', + ]; + self::assertCount(count($expectedElements), $actualElements); + foreach ($expectedElements as $i => $expectedElement) { + self::assertEquals( + $expectedElement, + $actualElements[$i]->getElement(0)->getText() + ); + } + } + /** * @covers ::cloneBlock */ @@ -603,6 +1126,36 @@ public function testCloneBlock(): void self::assertEquals(3, substr_count($templateProcessor->getMainPart(), 'This block will be cloned with ${variable}')); } + /** + * @covers ::cloneBlock + */ + public function testCloneBlockWithCustomMacro(): void + { + $mainPart = '<?xml version="1.0" encoding="UTF-8"?> + <w:p> + <w:r> + <w:rPr></w:rPr> + <w:t>{{CLONEME}}</w:t> + </w:r> + </w:p> + <w:p> + <w:r> + <w:t xml:space="preserve">This block will be cloned with {{variable}}</w:t> + </w:r> + </w:p> + <w:p> + <w:r w:rsidRPr="00204FED"> + <w:t>{{/CLONEME}}</w:t> + </w:r> + </w:p>'; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setMacroChars('{{', '}}'); + $templateProcessor->cloneBlock('CLONEME', 3); + + self::assertEquals(3, substr_count($templateProcessor->getMainPart(), 'This block will be cloned with {{variable}}')); + } + /** * @covers ::cloneBlock */ @@ -634,6 +1187,38 @@ public function testCloneBlockWithVariables(): void self::assertStringContainsString('Address ${address#3}, Street ${street#3}', $templateProcessor->getMainPart()); } + /** + * @covers ::cloneBlock + */ + public function testCloneBlockWithVariablesAndCustomMacro(): void + { + $mainPart = '<?xml version="1.0" encoding="UTF-8"?> + <w:p> + <w:r> + <w:rPr></w:rPr> + <w:t>{{CLONEME}}</w:t> + </w:r> + </w:p> + <w:p> + <w:r> + <w:t xml:space="preserve">Address {{address}}, Street {{street}}</w:t> + </w:r> + </w:p> + <w:p> + <w:r w:rsidRPr="00204FED"> + <w:t>{{/CLONEME}}</w:t> + </w:r> + </w:p>'; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setMacroChars('{{', '}}'); + $templateProcessor->cloneBlock('CLONEME', 3, true, true); + + self::assertStringContainsString('Address {{address#1}}, Street {{street#1}}', $templateProcessor->getMainPart()); + self::assertStringContainsString('Address {{address#2}}, Street {{street#2}}', $templateProcessor->getMainPart()); + self::assertStringContainsString('Address {{address#3}}, Street {{street#3}}', $templateProcessor->getMainPart()); + } + public function testCloneBlockWithVariableReplacements(): void { $mainPart = '<?xml version="1.0" encoding="UTF-8"?> @@ -667,6 +1252,40 @@ public function testCloneBlockWithVariableReplacements(): void self::assertStringContainsString('City: Rome, Street: Via della Conciliazione', $templateProcessor->getMainPart()); } + public function testCloneBlockWithVariableReplacementsAndCustomMacro(): void + { + $mainPart = '<?xml version="1.0" encoding="UTF-8"?> + <w:p> + <w:r> + <w:rPr></w:rPr> + <w:t>{{CLONEME}}</w:t> + </w:r> + </w:p> + <w:p> + <w:r> + <w:t xml:space="preserve">City: {{city}}, Street: {{street}}</w:t> + </w:r> + </w:p> + <w:p> + <w:r w:rsidRPr="00204FED"> + <w:t>{{/CLONEME}}</w:t> + </w:r> + </w:p>'; + + $replacements = [ + ['city' => 'London', 'street' => 'Baker Street'], + ['city' => 'New York', 'street' => '5th Avenue'], + ['city' => 'Rome', 'street' => 'Via della Conciliazione'], + ]; + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setMacroChars('{{', '}}'); + $templateProcessor->cloneBlock('CLONEME', 0, true, false, $replacements); + + self::assertStringContainsString('City: London, Street: Baker Street', $templateProcessor->getMainPart()); + self::assertStringContainsString('City: New York, Street: 5th Avenue', $templateProcessor->getMainPart()); + self::assertStringContainsString('City: Rome, Street: Via della Conciliazione', $templateProcessor->getMainPart()); + } + /** * Template macros can be fixed. * @@ -698,18 +1317,63 @@ public function testFixBrokenMacros(): void self::assertEquals('<w:t>$</w:t></w:r><w:bookmarkStart w:id="0" w:name="_GoBack"/><w:bookmarkEnd w:id="0"/><w:r><w:t xml:space="preserve">15,000.00. </w:t></w:r><w:r w:rsidR="0056499B"><w:t>${variable_name}</w:t></w:r>', $fixed); } + /** + * Template macros can be fixed even with cutome macro. + * + * @covers ::fixBrokenMacros + */ + public function testFixBrokenMacrosWithCustomMacro(): void + { + $templateProcessor = new TestableTemplateProcesor(); + $templateProcessor->setMacroChars('{{', '}}'); + + $fixed = $templateProcessor->fixBrokenMacros('<w:r><w:t>normal text</w:t></w:r>'); + self::assertEquals('<w:r><w:t>normal text</w:t></w:r>', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('<w:r><w:t>{{documentContent}}</w:t></w:r>'); + self::assertEquals('<w:r><w:t>{{documentContent}}</w:t></w:r>', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('<w:r><w:t>{</w:t><w:t>{documentContent}}</w:t></w:r>'); + self::assertEquals('<w:r><w:t>{{documentContent}}</w:t></w:r>', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('<w:r><w:t>$1500</w:t><w:t>{{documentContent}}</w:t></w:r>'); + self::assertEquals('<w:r><w:t>$1500</w:t><w:t>{{documentContent}}</w:t></w:r>', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('<w:r><w:t>$1500</w:t><w:t>{</w:t><w:t>{documentContent}}</w:t></w:r>'); + self::assertEquals('<w:r><w:t>$1500</w:t><w:t>{{documentContent}}</w:t></w:r>', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('<w:r><w:t>25$ plus some info {hint}</w:t></w:r>'); + self::assertEquals('<w:r><w:t>25$ plus some info {hint}</w:t></w:r>', $fixed); + + $fixed = $templateProcessor->fixBrokenMacros('<w:t>$</w:t></w:r><w:bookmarkStart w:id="0" w:name="_GoBack"/><w:bookmarkEnd w:id="0"/><w:r><w:t xml:space="preserve">15,000.00. </w:t></w:r><w:r w:rsidR="0056499B"><w:t>{</w:t></w:r><w:r w:rsidR="00573DFD" w:rsidRPr="00573DFD"><w:rPr><w:iCs/></w:rPr><w:t>{</w:t></w:r><w:proofErr w:type="spellStart"/><w:r w:rsidR="00573DFD" w:rsidRPr="00573DFD"><w:rPr><w:iCs/></w:rPr><w:t>variable_name</w:t></w:r><w:proofErr w:type="spellEnd"/><w:r w:rsidR="00573DFD" w:rsidRPr="00573DFD"><w:rPr><w:iCs/></w:rPr><w:t>}}</w:t></w:r>'); + self::assertEquals('<w:t>$</w:t></w:r><w:bookmarkStart w:id="0" w:name="_GoBack"/><w:bookmarkEnd w:id="0"/><w:r><w:t xml:space="preserve">15,000.00. </w:t></w:r><w:r w:rsidR="0056499B"><w:t>{{variable_name}}</w:t></w:r>', $fixed); + } + /** * @covers ::getMainPartName */ public function testMainPartNameDetection(): void { - $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/document22-xml.docx'); + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/document22-xml.docx'); $variables = ['test']; self::assertEquals($variables, $templateProcessor->getVariables()); } + /** + * @covers ::getMainPartName + */ + public function testMainPartNameDetectionWithCustomMacro(): void + { + $templateProcessor = $this->getTemplateProcessor(__DIR__ . '/_files/templates/document22-with-custom-macro-xml.docx'); + $templateProcessor->setMacroOpeningChars('{#'); + $templateProcessor->setMacroClosingChars('#}'); + $variables = ['test']; + + self::assertEquals($variables, $templateProcessor->getVariables()); + } + /** * @covers ::getVariables */ @@ -727,6 +1391,25 @@ public function testGetVariables(): void self::assertEquals(['variable_name'], $variables); } + /** + * @covers ::getVariables + */ + public function testGetVariablesWithCustomMacro(): void + { + $templateProcessor = new TestableTemplateProcesor(); + $templateProcessor->setMacroOpeningChars('{{'); + $templateProcessor->setMacroClosingChars('}}'); + + $variables = $templateProcessor->getVariablesForPart('<w:r><w:t>normal text</w:t></w:r>'); + self::assertEquals([], $variables); + + $variables = $templateProcessor->getVariablesForPart('<w:r><w:t>{{documentContent}}</w:t></w:r>'); + self::assertEquals(['documentContent'], $variables); + + $variables = $templateProcessor->getVariablesForPart('<w:t>{</w:t></w:r><w:bookmarkStart w:id="0" w:name="_GoBack"/><w:bookmarkEnd w:id="0"/><w:r><w:t xml:space="preserve">15,000.00. </w:t></w:r><w:r w:rsidR="0056499B"><w:t>{</w:t></w:r><w:r w:rsidR="00573DFD" w:rsidRPr="00573DFD"><w:rPr><w:iCs/></w:rPr><w:t>{</w:t></w:r><w:proofErr w:type="spellStart"/><w:r w:rsidR="00573DFD" w:rsidRPr="00573DFD"><w:rPr><w:iCs/></w:rPr><w:t>variable_name</w:t></w:r><w:proofErr w:type="spellEnd"/><w:r w:rsidR="00573DFD" w:rsidRPr="00573DFD"><w:rPr><w:iCs/></w:rPr><w:t>}}</w:t></w:r>'); + self::assertEquals(['variable_name'], $variables); + } + /** * @covers ::textNeedsSplitting */ @@ -742,6 +1425,22 @@ public function testTextNeedsSplitting(): void self::assertFalse($templateProcessor->textNeedsSplitting($splitText)); } + /** + * @covers ::textNeedsSplitting + */ + public function testTextNeedsSplittingWithCustomMacro(): void + { + $templateProcessor = new TestableTemplateProcesor(); + $templateProcessor->setMacroChars('{{', '}}'); + + self::assertFalse($templateProcessor->textNeedsSplitting('<w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">{{nothing-to-replace}}</w:t></w:r>')); + + $text = '<w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">Hello {{firstname}} {{lastname}}</w:t></w:r>'; + self::assertTrue($templateProcessor->textNeedsSplitting($text)); + $splitText = $templateProcessor->splitTextIntoTexts($text); + self::assertFalse($templateProcessor->textNeedsSplitting($splitText)); + } + /** * @covers ::splitTextIntoTexts */ @@ -756,6 +1455,21 @@ public function testSplitTextIntoTexts(): void self::assertEquals('<w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">Hello </w:t></w:r><w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">${firstname}</w:t></w:r><w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve"> </w:t></w:r><w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">${lastname}</w:t></w:r>', $splitText); } + /** + * @covers ::splitTextIntoTexts + */ + public function testSplitTextIntoTextsWithCustomMacro(): void + { + $templateProcessor = new TestableTemplateProcesor(); + $templateProcessor->setMacroChars('{{', '}}'); + + $splitText = $templateProcessor->splitTextIntoTexts('<w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">{{nothing-to-replace}}</w:t></w:r>'); + self::assertEquals('<w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">{{nothing-to-replace}}</w:t></w:r>', $splitText); + + $splitText = $templateProcessor->splitTextIntoTexts('<w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">Hello {{firstname}} {{lastname}}</w:t></w:r>'); + self::assertEquals('<w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">Hello </w:t></w:r><w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">{{firstname}}</w:t></w:r><w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve"> </w:t></w:r><w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">{{lastname}}</w:t></w:r>', $splitText); + } + public function testFindXmlBlockStart(): void { $toFind = '<w:r> @@ -799,6 +1513,50 @@ public function testFindXmlBlockStart(): void self::assertEquals($toFind, $templateProcessor->getSlice($position['start'], $position['end'])); } + public function testFindXmlBlockStartWithCustomMacro(): void + { + $toFind = '<w:r> + <w:rPr> + <w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:cs="Calibri"/> + <w:lang w:val="en-GB"/> + </w:rPr> + <w:t>This whole paragraph will be replaced with my {{title}}</w:t> + </w:r>'; + $mainPart = '<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid wp14"> + <w:p w14:paraId="165D45AF" w14:textId="7FEC9B41" w:rsidR="005B1098" w:rsidRDefault="005B1098"> + <w:r w:rsidR="00A045B2"> + <w:rPr> + <w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:cs="Calibri"/> + <w:lang w:val="en-GB"/> + </w:rPr> + <w:t xml:space="preserve"> {{value1}} {{value2}}</w:t> + </w:r> + <w:r> + <w:rPr> + <w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:cs="Calibri"/> + <w:lang w:val="en-GB"/> + </w:rPr> + <w:t>.</w:t> + </w:r> + </w:p> + <w:p w14:paraId="330D1954" w14:textId="0AB1D347" w:rsidR="00156568" w:rsidRDefault="00156568"> + <w:pPr> + <w:rPr> + <w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:cs="Calibri"/> + <w:lang w:val="en-GB"/> + </w:rPr> + </w:pPr> + ' . $toFind . ' + </w:p> + </w:document>'; + + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setMacroChars('{{', '}}'); + $position = $templateProcessor->findContainingXmlBlockForMacro('{{title}}', 'w:r'); + + self::assertEquals($toFind, $templateProcessor->getSlice($position['start'], $position['end'])); + } + public function testShouldReturnFalseIfXmlBlockNotFound(): void { $mainPart = '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"> @@ -826,6 +1584,34 @@ public function testShouldReturnFalseIfXmlBlockNotFound(): void self::assertFalse($result); } + public function testShouldReturnFalseIfXmlBlockNotFoundWithCustomMacro(): void + { + $mainPart = '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"> + <w:p> + <w:r> + <w:rPr> + <w:lang w:val="en-GB"/> + </w:rPr> + <w:t xml:space="preserve">this is my text containing a ${macro}</w:t> + </w:r> + </w:p> + </w:document>'; + $templateProcessor = new TestableTemplateProcesor($mainPart); + $templateProcessor->setMacroChars('{{', '}}'); + + //non-existing macro + $result = $templateProcessor->findContainingXmlBlockForMacro('{{fake-macro}}', 'w:p'); + self::assertFalse($result); + + //existing macro but not inside node looked for + $result = $templateProcessor->findContainingXmlBlockForMacro('{{macro}}', 'w:fake-node'); + self::assertFalse($result); + + //existing macro but end tag not found after macro + $result = $templateProcessor->findContainingXmlBlockForMacro('{{macro}}', 'w:rPr'); + self::assertFalse($result); + } + public function testShouldMakeFieldsUpdateOnOpen(): void { $settingsPart = '<w:settings xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"> @@ -839,4 +1625,19 @@ public function testShouldMakeFieldsUpdateOnOpen(): void $templateProcessor->setUpdateFields(false); self::assertStringContainsString('<w:updateFields w:val="false"/>', $templateProcessor->getSettingsPart()); } + + public function testShouldMakeFieldsUpdateOnOpenWithCustomMacro(): void + { + $settingsPart = '<w:settings xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"> + <w:zoom w:percent="100"/> + </w:settings>'; + $templateProcessor = new TestableTemplateProcesor(null, $settingsPart); + $templateProcessor->setMacroChars('{{', '}}'); + + $templateProcessor->setUpdateFields(true); + self::assertStringContainsString('<w:updateFields w:val="true"/>', $templateProcessor->getSettingsPart()); + + $templateProcessor->setUpdateFields(false); + self::assertStringContainsString('<w:updateFields w:val="false"/>', $templateProcessor->getSettingsPart()); + } } diff --git a/tests/PhpWordTests/TestHelperDOCX.php b/tests/PhpWordTests/TestHelperDOCX.php index 44766bedaa..de9f1a81b7 100644 --- a/tests/PhpWordTests/TestHelperDOCX.php +++ b/tests/PhpWordTests/TestHelperDOCX.php @@ -80,6 +80,7 @@ public static function clear(): void { if (self::$file && file_exists(self::$file)) { unlink(self::$file); + self::$file = ''; } if (is_dir(Settings::getTempDir() . '/PhpWord_Unit_Test/')) { self::deleteDir(Settings::getTempDir() . '/PhpWord_Unit_Test/'); @@ -103,7 +104,7 @@ public static function deleteDir($dir): void } } - rmdir($dir); + @rmdir($dir); } /** diff --git a/tests/PhpWordTests/Writer/HTML/DirectionTest.php b/tests/PhpWordTests/Writer/HTML/DirectionTest.php new file mode 100644 index 0000000000..4f0755b87f --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/DirectionTest.php @@ -0,0 +1,57 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\HTML; + +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Shared\Html as SharedHtml; +use PhpOffice\PhpWord\Writer\HTML; + +/** + * Test class for PhpOffice\PhpWord\Writer\HTML\Element subnamespace. + */ +class DirectionTest extends \PHPUnit\Framework\TestCase +{ + protected function tearDown(): void + { + Settings::setDefaultRtl(null); + } + + /** + * Test unmatched elements. + */ + public function testDirection(): void + { + $doc = new PhpWord(); + Settings::setDefaultRtl(true); + $section = $doc->addSection(); + $html = '<p> الألم الذي ربما تنجم عنه بعض ا.</p>'; + SharedHtml::addHtml($section, $html, false, false); + $english = '<p style="text-align: left; direction: ltr;">LTR in RTL document.</p>'; + SharedHtml::addHtml($section, $english, false, false); + SharedHtml::addHtml($section, $english, false, false); + SharedHtml::addHtml($section, $html, false, false); + SharedHtml::addHtml($section, $html, false, false); + $writer = new HTML($doc); + $content = $writer->getContent(); + self::assertSame(3, substr_count($content, '<span style="direction: rtl;">')); + self::assertSame(2, substr_count($content, '<span style="direction: ltr;">')); + self::assertSame(3, substr_count($content, '<p style="direction: rtl;">')); + self::assertSame(2, substr_count($content, '<p style="text-align: left;">')); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/Element/PageBreakTest.php b/tests/PhpWordTests/Writer/HTML/Element/PageBreakTest.php new file mode 100644 index 0000000000..37ef71aa07 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Element/PageBreakTest.php @@ -0,0 +1,73 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\HTML\Element; + +use PhpOffice\PhpWord\Element\PageBreak as BasePageBreak; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Writer\HTML; +use PhpOffice\PhpWord\Writer\HTML\Element\PageBreak; +use PhpOffice\PhpWord\Writer\PDF; +use PHPUnit\Framework\TestCase; + +class PageBreakTest extends TestCase +{ + public function testHTML(): void + { + $writer = new HTML(); + $object = new PageBreak($writer, new BasePageBreak()); + + self::assertEquals('<div style="page-break-before: always; height: 0; margin: 0; padding: 0; overflow: hidden;"> </div>' . PHP_EOL, $object->write()); + } + + public function testMPDF(): void + { + $rendererName = Settings::PDF_RENDERER_MPDF; + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/mpdf/mpdf'); + Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + $writer = new PDF(new PhpWord()); + + $object = new PageBreak($writer->getRenderer(), new BasePageBreak()); + + self::assertEquals('<pagebreak style="page-break-before: always;" pagebreak="true"></pagebreak>', $object->write()); + } + + public function testDOMPDF(): void + { + $rendererName = Settings::PDF_RENDERER_DOMPDF; + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); + Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + $writer = new PDF(new PhpWord()); + + $object = new PageBreak($writer->getRenderer(), new BasePageBreak()); + + self::assertEquals('<pagebreak style="page-break-before: always;" pagebreak="true"></pagebreak>', $object->write()); + } + + public function testTCPDF(): void + { + $rendererName = Settings::PDF_RENDERER_TCPDF; + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/tecnickcom/tcpdf'); + Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + $writer = new PDF(new PhpWord()); + + $object = new PageBreak($writer->getRenderer(), new BasePageBreak()); + + self::assertEquals('<br pagebreak="true"/>', $object->write()); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/Element/TableTest.php b/tests/PhpWordTests/Writer/HTML/Element/TableTest.php new file mode 100644 index 0000000000..8dec922108 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Element/TableTest.php @@ -0,0 +1,234 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\HTML\Element; + +use DOMXPath; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\SimpleType\VerticalJc; +use PhpOffice\PhpWord\Style; +use PhpOffice\PhpWordTests\Writer\HTML\Helper; +use PHPUnit\Framework\TestCase; + +class TableTest extends TestCase +{ + /** + * Tests writing table with border styles. + */ + public function testWriteTableBorders(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $bsnone = ['borderStyle' => 'none']; + $table1 = $section->addTable($bsnone); + $row1 = $table1->addRow(); + $row1->addCell(null, $bsnone)->addText('Row 1 Cell 1'); + $row1->addCell(null, $bsnone)->addText('Row 1 Cell 2'); + $row2 = $table1->addRow(); + $row2->addCell(null, $bsnone)->addText('Row 2 Cell 1'); + $row2->addCell(null, $bsnone)->addText('Row 2 Cell 2'); + + $table1 = $section->addTable(); + $row1 = $table1->addRow(); + $row1->addCell()->addText('Row 1 Cell 1'); + $row1->addCell()->addText('Row 1 Cell 2'); + $row2 = $table1->addRow(); + $row2->addCell()->addText('Row 2 Cell 1'); + $row2->addCell()->addText('Row 2 Cell 2'); + + $bstyle = ['borderStyle' => 'dashed', 'borderColor' => 'red']; + $table1 = $section->addTable($bstyle); + $row1 = $table1->addRow(); + $row1->addCell(null, $bstyle)->addText('Row 1 Cell 1'); + $row1->addCell(null, $bstyle)->addText('Row 1 Cell 2'); + $row2 = $table1->addRow(); + $row2->addCell(null, $bstyle)->addText('Row 2 Cell 1'); + $row2->addCell(null, $bstyle)->addText('Row 2 Cell 2'); + + $bstyle = [ + 'borderTopStyle' => 'dotted', + 'borderLeftStyle' => 'dashed', + 'borderRightStyle' => 'dashed', + 'borderBottomStyle' => 'dotted', + 'borderTopColor' => 'blue', + 'borderLeftColor' => 'green', + 'borderRightColor' => 'green', + 'borderBottomColor' => 'blue', + ]; + $table1 = $section->addTable($bstyle); + $row1 = $table1->addRow(); + $row1->addCell(null, $bstyle)->addText('Row 1 Cell 1'); + $row1->addCell(null, $bstyle)->addText('Row 1 Cell 2'); + $row2 = $table1->addRow(); + $row2->addCell(null, $bstyle)->addText('Row 2 Cell 1'); + $row2->addCell(null, $bstyle)->addText('Row 2 Cell 2'); + + $bstyle = ['borderStyle' => 'solid', 'borderSize' => 5]; + $table1 = $section->addTable($bstyle); + $row1 = $table1->addRow(); + $row1->addCell(null, $bstyle)->addText('Row 1 Cell 1'); + $row1->addCell(null, $bstyle)->addText('Row 1 Cell 2'); + $row2 = $table1->addRow(); + $row2->addCell(null, $bstyle)->addText('Row 2 Cell 1'); + $row2->addCell(null, $bstyle)->addText('Row 2 Cell 2'); + + $phpWord->addTableStyle('tstyle', ['borderStyle' => 'solid', 'borderSize' => 5]); + $table1 = $section->addTable('tstyle'); + $row1 = $table1->addRow(); + $row1->addCell(null, 'tstyle')->addText('Row 1 Cell 1'); + $row1->addCell(null, 'tstyle')->addText('Row 1 Cell 2'); + $row2 = $table1->addRow(); + $row2->addCell(null, 'tstyle')->addText('Row 2 Cell 1'); + $row2->addCell(null, 'tstyle')->addText('Row 2 Cell 2'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + $cssnone = 'border-top-style: none;' + . ' border-left-style: none;' + . ' border-bottom-style: none;' + . ' border-right-style: none;'; + self::assertEquals("table-layout: auto; $cssnone", Helper::getTextContent($xpath, '/html/body/div/table[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[1]/tr[1]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[1]/tr[1]/td[2]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[1]/tr[2]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[1]/tr[2]/td[2]', 'style')); + + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/table[2]', 'style')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/table[2]/tr[1]/td[1]', 'style')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/table[2]/tr[1]/td[2]', 'style')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/table[2]/tr[2]/td[1]', 'style')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/table[2]/tr[2]/td[2]', 'style')); + + $cssnone = 'border-top-style: dashed;' + . ' border-top-color: red;' + . ' border-left-style: dashed;' + . ' border-left-color: red;' + . ' border-bottom-style: dashed;' + . ' border-bottom-color: red;' + . ' border-right-style: dashed;' + . ' border-right-color: red;'; + self::assertEquals("table-layout: auto; $cssnone", Helper::getTextContent($xpath, '/html/body/div/table[3]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[3]/tr[1]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[3]/tr[1]/td[2]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[3]/tr[2]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[3]/tr[2]/td[2]', 'style')); + + $cssnone = 'border-top-style: dotted;' + . ' border-top-color: blue;' + . ' border-left-style: dashed;' + . ' border-left-color: green;' + . ' border-bottom-style: dotted;' + . ' border-bottom-color: blue;' + . ' border-right-style: dashed;' + . ' border-right-color: green;'; + self::assertEquals("table-layout: auto; $cssnone", Helper::getTextContent($xpath, '/html/body/div/table[4]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[4]/tr[1]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[4]/tr[1]/td[2]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[4]/tr[2]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[4]/tr[2]/td[2]', 'style')); + + $cssnone = 'border-top-style: solid;' + . ' border-top-width: 0.25pt;' + . ' border-left-style: solid;' + . ' border-left-width: 0.25pt;' + . ' border-bottom-style: solid;' + . ' border-bottom-width: 0.25pt;' + . ' border-right-style: solid;' + . ' border-right-width: 0.25pt;'; + self::assertEquals("table-layout: auto; $cssnone", Helper::getTextContent($xpath, '/html/body/div/table[5]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[5]/tr[1]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[5]/tr[1]/td[2]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[5]/tr[2]/td[1]', 'style')); + self::assertEquals($cssnone, Helper::getTextContent($xpath, '/html/body/div/table[5]/tr[2]/td[2]', 'style')); + + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/table[6]', 'style')); + self::assertEquals('tstyle', Helper::getTextContent($xpath, '/html/body/div/table[6]', 'class')); + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertNotFalse(preg_match('/^[.]tstyle[^\\r\\n]*/m', $style, $matches)); + self::assertEquals(".tstyle {table-layout: auto; $cssnone}", $matches[0]); + } + + public function testWriteTableCellVAlign(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $table = $section->addTable(); + $row = $table->addRow(); + + $cell = $row->addCell(); + $cell->addText('top text'); + $cell->getStyle()->setVAlign(VerticalJc::TOP); + + $cell = $row->addCell(); + $cell->addText('bottom text'); + $cell->getStyle()->setVAlign(VerticalJc::BOTTOM); + + $cell = $row->addCell(); + $cell->addText('no vAlign'); + $cell->getStyle()->setVAlign(VerticalJc::BOTTOM); + $cell->getStyle()->setVAlign(); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + $cell1Style = Helper::getTextContent($xpath, '//table/tr/td[1]', 'style'); + $cell2Style = Helper::getTextContent($xpath, '//table/tr/td[2]', 'style'); + self::assertSame('vertical-align: top;', $cell1Style); + self::assertSame('vertical-align: bottom;', $cell2Style); + + $cell3Query = $xpath->query('//table/tr/td[3]'); + self::assertNotFalse($cell3Query); + self::assertCount(1, $cell3Query); + + $cell3Style = $cell3Query->item(0)->attributes->getNamedItem('style'); + self::assertNull($cell3Style); + } + + public function testWriteTableCellVMerge(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $table = $section->addTable(); + + $cell = $table->addRow()->addCell(); + $cell->addText('text'); + $cell->getStyle()->setVMerge(Style\Cell::VMERGE_RESTART); + + $cell = $table->addRow()->addCell(); + $cell->getStyle()->setVMerge(Style\Cell::VMERGE_CONTINUE); + + $cell = $table->addRow()->addCell(); + $cell->addText('no vMerge'); + $cell->getStyle()->setVMerge(Style\Cell::VMERGE_CONTINUE); + $cell->getStyle()->setVMerge(); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + $cell1Style = Helper::getTextContent($xpath, '//table/tr[1]/td[1]', 'rowspan'); + self::assertSame('2', $cell1Style); + + $cell3Query = $xpath->query('//table/tr[3]/td[1]'); + self::assertNotFalse($cell3Query); + self::assertCount(1, $cell3Query); + self::assertNull($cell3Query->item(0)->attributes->getNamedItem('rowspan')); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/ElementTest.php b/tests/PhpWordTests/Writer/HTML/ElementTest.php index ccdd5eaf06..3fee31a69b 100644 --- a/tests/PhpWordTests/Writer/HTML/ElementTest.php +++ b/tests/PhpWordTests/Writer/HTML/ElementTest.php @@ -73,11 +73,11 @@ public function testWriteTrackChanges(): void $text2 = $section->addText('my other text'); $text2->setTrackChange(new TrackChange(TrackChange::DELETED, 'another author', new DateTime())); - $dom = $this->getAsHTML($phpWord); + $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals(1, $xpath->query('/html/body/p[1]/ins')->length); - self::assertEquals(1, $xpath->query('/html/body/p[2]/del')->length); + self::assertEquals(1, $xpath->query('/html/body/div/p[1]/ins')->length); + self::assertEquals(1, $xpath->query('/html/body/div/p[2]/del')->length); } /** @@ -97,17 +97,17 @@ public function testWriteColSpan(): void $cell22 = $row2->addCell(500); $cell22->addText('second cell'); - $dom = $this->getAsHTML($phpWord); + $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals(1, $xpath->query('/html/body/table/tr[1]/td')->length); - self::assertEquals('2', $xpath->query('/html/body/table/tr/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); - self::assertEquals(2, $xpath->query('/html/body/table/tr[2]/td')->length); + self::assertEquals(1, $xpath->query('/html/body/div/table/tr[1]/td')->length); + self::assertEquals('2', $xpath->query('/html/body/div/table/tr/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); + self::assertEquals(2, $xpath->query('/html/body/div/table/tr[2]/td')->length); - self::assertEquals('#6086B8', $xpath->query('/html/body/table/tr[1]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent); - self::assertEquals('#ffffff', $xpath->query('/html/body/table/tr[1]/td')->item(0)->attributes->getNamedItem('color')->textContent); - self::assertEquals('#ffffff', $xpath->query('/html/body/table/tr[2]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent); - self::assertNull($xpath->query('/html/body/table/tr[2]/td')->item(0)->attributes->getNamedItem('color')); + self::assertEquals('#6086B8', $xpath->query('/html/body/div/table/tr[1]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent); + self::assertEquals('#ffffff', $xpath->query('/html/body/div/table/tr[1]/td')->item(0)->attributes->getNamedItem('color')->textContent); + self::assertEquals('#ffffff', $xpath->query('/html/body/div/table/tr[2]/td')->item(0)->attributes->getNamedItem('bgcolor')->textContent); + self::assertNull($xpath->query('/html/body/div/table/tr[2]/td')->item(0)->attributes->getNamedItem('color')); } /** @@ -131,21 +131,49 @@ public function testWriteRowSpan(): void $row3->addCell(null, ['vMerge' => 'continue']); $row3->addCell(500)->addText('third cell being spanned'); - $dom = $this->getAsHTML($phpWord); + $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals(2, $xpath->query('/html/body/table/tr[1]/td')->length); - self::assertEquals('3', $xpath->query('/html/body/table/tr[1]/td[1]')->item(0)->attributes->getNamedItem('rowspan')->textContent); - self::assertEquals(1, $xpath->query('/html/body/table/tr[2]/td')->length); + self::assertEquals(2, $xpath->query('/html/body/div/table/tr[1]/td')->length); + self::assertEquals('3', $xpath->query('/html/body/div/table/tr[1]/td[1]')->item(0)->attributes->getNamedItem('rowspan')->textContent); + self::assertEquals(1, $xpath->query('/html/body/div/table/tr[2]/td')->length); } - private function getAsHTML(PhpWord $phpWord) + /** + * Tests writing table with rowspan and colspan. + */ + public function testWriteRowSpanAndColSpan(): void { - $htmlWriter = new HTML($phpWord); - $dom = new DOMDocument(); - $dom->loadHTML($htmlWriter->getContent()); + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $table = $section->addTable(); + + $row1 = $table->addRow(); + $row1->addCell(500)->addText('A'); + $row1->addCell(1000, ['gridSpan' => 2])->addText('B'); + $row1->addCell(500, ['vMerge' => 'restart'])->addText('C'); + + $row2 = $table->addRow(); + $row2->addCell(1500, ['gridSpan' => 3])->addText('D'); + $row2->addCell(null, ['vMerge' => 'continue']); + + $row3 = $table->addRow(); + $row3->addCell(500)->addText('E'); + $row3->addCell(500)->addText('F'); + $row3->addCell(500)->addText('G'); + $row3->addCell(null, ['vMerge' => 'continue']); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals(3, $xpath->query('/html/body/div/table/tr[1]/td')->length); + self::assertEquals('2', $xpath->query('/html/body/div/table/tr[1]/td[2]')->item(0)->attributes->getNamedItem('colspan')->textContent); + self::assertEquals('3', $xpath->query('/html/body/div/table/tr[1]/td[3]')->item(0)->attributes->getNamedItem('rowspan')->textContent); + + self::assertEquals(1, $xpath->query('/html/body/div/table/tr[2]/td')->length); + self::assertEquals('3', $xpath->query('/html/body/div/table/tr[2]/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); - return $dom; + self::assertEquals(3, $xpath->query('/html/body/div/table/tr[3]/td')->length); } public function testWriteTitleTextRun(): void @@ -208,10 +236,10 @@ public function testWriteTableLayout(): void $row2 = $table2->addRow(); $row2->addCell()->addText('auto layout table'); - $dom = $this->getAsHTML($phpWord); + $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); - self::assertEquals('table-layout: fixed;', $xpath->query('/html/body/table[1]')->item(0)->attributes->getNamedItem('style')->textContent); - self::assertEquals('table-layout: auto;', $xpath->query('/html/body/table[2]')->item(0)->attributes->getNamedItem('style')->textContent); + self::assertEquals('table-layout: fixed;', $xpath->query('/html/body/div/table[1]')->item(0)->attributes->getNamedItem('style')->textContent); + self::assertEquals('table-layout: auto;', $xpath->query('/html/body/div/table[2]')->item(0)->attributes->getNamedItem('style')->textContent); } } diff --git a/tests/PhpWordTests/Writer/HTML/FontTest.php b/tests/PhpWordTests/Writer/HTML/FontTest.php new file mode 100644 index 0000000000..442c2639c9 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/FontTest.php @@ -0,0 +1,299 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\HTML; + +use DOMXPath; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Style\Language; + +/** + * Test class for PhpOffice\PhpWord\Writer\HTML\Style\Font. + */ +class FontTest extends \PHPUnit\Framework\TestCase +{ + /** @var string */ + private $defaultFontName; + + /** @var float|int */ + private $defaultFontSize; + + /** + * Executed before each method of the class. + */ + protected function setUp(): void + { + $this->defaultFontName = Settings::getDefaultFontName(); + $this->defaultFontSize = Settings::getDefaultFontSize(); + } + + /** + * Executed after each method of the class. + */ + protected function tearDown(): void + { + Settings::setDefaultFontName($this->defaultFontName); + Settings::setDefaultFontSize($this->defaultFontSize); + } + + /** + * Tests font names - without generics. + */ + public function testFontNames1(): void + { + $phpWord = new PhpWord(); + $phpWord->setDefaultFontName('Courier New'); + $phpWord->setDefaultFontSize(12); + $phpWord->addFontStyle('style1', ['name' => 'Tahoma', 'size' => 10, 'color' => '1B2232', 'bold' => true]); + $phpWord->addFontStyle('style2', ['name' => 'Arial', 'size' => 10]); + $phpWord->addFontStyle('style3', ['name' => 'hack attempt\'}; display:none', 'size' => 10]); + $phpWord->addFontStyle('style4', ['name' => 'padmaa 1.1', 'size' => 10, 'bold' => true]); + $phpWord->addFontStyle('style5', ['name' => 'MingLiU-ExtB', 'size' => 10, 'bold' => true]); + $section1 = $phpWord->addSection(); + $section1->addText('Default font'); + $section1->addText('Tahoma', 'style1'); + $section1->addText('Arial', 'style2'); + $section1->addText('hack attempt', 'style3'); + $section1->addText('padmaa 1.1 bold', 'style4'); + $section1->addText('MingLiu-ExtB bold', 'style5'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[1]', 'class')); + self::assertEquals(0, Helper::getLength($xpath, '/html/body/div/p[1]/span')); + self::assertEquals('style1', Helper::getTextContent($xpath, '/html/body/div/p[2]/span', 'class')); + self::assertEquals('style2', Helper::getTextContent($xpath, '/html/body/div/p[3]/span', 'class')); + self::assertEquals('style3', Helper::getTextContent($xpath, '/html/body/div/p[4]/span', 'class')); + self::assertEquals('style4', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'class')); + self::assertEquals('style5', Helper::getTextContent($xpath, '/html/body/div/p[6]/span', 'class')); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + $prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('* {font-family: \'Courier New\'; font-size: 12pt;}', $matches[0]); + $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]); + $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style2 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style3 {font-family: \'hack attempt'}; display:none\'; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style4 {font-family: \'padmaa 1.1\'; font-size: 10pt; font-weight: bold;}', $matches[0]); + $prg = preg_match('/^[.]style5[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style5 {font-family: \'MingLiU-ExtB\'; font-size: 10pt; font-weight: bold;}', $matches[0]); + } + + /** + * Tests font names - with generics. + */ + public function testFontNames2(): void + { + $phpWord = new PhpWord(); + $phpWord->setDefaultFontName('Courier New'); + $phpWord->setDefaultFontSize(12); + $phpWord->addFontStyle('style1', ['name' => 'Tahoma', 'size' => 10, 'color' => '1B2232', 'bold' => true]); + $phpWord->addFontStyle('style2', ['name' => 'Arial', 'size' => 10, 'fallbackFont' => 'sans-serif']); + $phpWord->addFontStyle('style3', ['name' => 'DejaVu Sans Monospace', 'size' => 10, 'fallbackFont' => 'monospace']); + $phpWord->addFontStyle('style4', ['name' => 'Arial', 'size' => 10, 'fallbackFont' => 'invalid']); + $section1 = $phpWord->addSection(); + $section1->addText('Default font'); + $section1->addText('Tahoma', 'style1'); + $section1->addText('Arial', 'style2'); + $section1->addText('DejaVu Sans Monospace', 'style3'); + $section1->addText('Arial with invalid fallback', 'style4'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[1]', 'class')); + self::assertEquals(0, Helper::getLength($xpath, '/html/body/div/p[1]/span')); + self::assertEquals('style1', Helper::getTextContent($xpath, '/html/body/div/p[2]/span', 'class')); + self::assertEquals('style2', Helper::getTextContent($xpath, '/html/body/div/p[3]/span', 'class')); + self::assertEquals('style3', Helper::getTextContent($xpath, '/html/body/div/p[4]/span', 'class')); + self::assertEquals('style4', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'class')); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + $prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('* {font-family: \'Courier New\'; font-size: 12pt;}', $matches[0]); + $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]); + $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style2 {font-family: \'Arial\', sans-serif; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style3 {font-family: \'DejaVu Sans Monospace\', monospace; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style4 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]); + } + + /** + * Tests font names - with generics including for default font. + */ + public function testFontNames3(): void + { + $phpWord = new PhpWord(); + $phpWord->setDefaultFontName('Courier New'); + $phpWord->setDefaultFontSize(12); + $phpWord->addFontStyle('style1', ['name' => 'Tahoma', 'size' => 10, 'color' => '1B2232', 'bold' => true]); + $phpWord->addFontStyle('style2', ['name' => 'Arial', 'size' => 10, 'fallbackFont' => 'sans-serif']); + $phpWord->addFontStyle('style3', ['name' => 'DejaVu Sans Monospace', 'size' => 10, 'fallbackFont' => 'monospace']); + $phpWord->addFontStyle('style4', ['name' => 'Arial', 'size' => 10, 'fallbackFont' => 'invalid']); + $section1 = $phpWord->addSection(); + $section1->addText('Default font'); + $section1->addText('Tahoma', 'style1'); + $section1->addText('Arial', 'style2'); + $section1->addText('DejaVu Sans Monospace', 'style3'); + $section1->addText('Arial with invalid fallback', 'style4'); + + $dom = Helper::getAsHTML($phpWord, '', 'monospace'); + $xpath = new DOMXPath($dom); + + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[1]', 'class')); + self::assertEquals(0, Helper::getLength($xpath, '/html/body/div/p[1]/span')); + self::assertEquals('style1', Helper::getTextContent($xpath, '/html/body/div/p[2]/span', 'class')); + self::assertEquals('style2', Helper::getTextContent($xpath, '/html/body/div/p[3]/span', 'class')); + self::assertEquals('style3', Helper::getTextContent($xpath, '/html/body/div/p[4]/span', 'class')); + self::assertEquals('style4', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'class')); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + $prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('* {font-family: \'Courier New\', monospace; font-size: 12pt;}', $matches[0]); + $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]); + $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style2 {font-family: \'Arial\', sans-serif; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style3 {font-family: \'DejaVu Sans Monospace\', monospace; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style4 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]); + } + + /** + * Tests white space. + */ + public function testWhiteSpace(): void + { + $phpWord = new PhpWord(); + $phpWord->setDefaultFontSize(12); + $phpWord->addFontStyle('style1', ['name' => 'Courier New', 'size' => 10, 'whiteSpace' => 'pre-wrap']); + $phpWord->addFontStyle('style2', ['name' => 'Courier New', 'size' => 10, 'whiteSpace' => 'invalid']); + $phpWord->addFontStyle('style3', ['name' => 'Courier New', 'size' => 10, 'whiteSpace' => 'normal']); + $phpWord->addFontStyle('style4', ['name' => 'Courier New', 'size' => 10, 'whiteSpace' => 'invalid']); + $text = 'This is a long line which will be split over 2 lines with pre-wrap'; + $section1 = $phpWord->addSection(); + $section1->addText($text); + $section1->addText($text, 'style1'); + $section1->addText($text, 'style2'); + $section1->addText($text, 'style3'); + $section1->addText($text, 'style4'); + + $dom = Helper::getAsHTML($phpWord, 'pre-wrap'); + $xpath = new DOMXPath($dom); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertNotFalse(preg_match('/^[*][^\\r\\n]*/m', $style, $matches)); + self::assertEquals('* {font-family: \'Arial\'; font-size: 12pt; white-space: pre-wrap;}', $matches[0]); + $prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style1 {font-family: \'Courier New\'; font-size: 10pt; white-space: pre-wrap;}', $matches[0]); + $prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style2 {font-family: \'Courier New\'; font-size: 10pt;}', $matches[0]); + $prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style3 {font-family: \'Courier New\'; font-size: 10pt; white-space: normal;}', $matches[0]); + $prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches); + self::assertNotFalse($prg); + self::assertEquals('.style4 {font-family: \'Courier New\'; font-size: 10pt;}', $matches[0]); + } + + /** + * Tests inline font style. + */ + public function testInline(): void + { + $phpWord = new PhpWord(); + $style1 = ['name' => 'Courier New', 'size' => 10, 'whiteSpace' => 'pre-wrap']; + $style2 = ['name' => 'Verdana', 'size' => 8.5]; + $text = 'This is a paragraph.'; + $section1 = $phpWord->addSection(); + $section1->addText($text, $style1); + $section1->addText($text, $style2); + $section1->addText($text); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals('font-family: \'Courier New\'; font-size: 10pt; white-space: pre-wrap;', Helper::getTextContent($xpath, '/html/body/div/p[1]/span', 'style')); + self::assertEquals('font-family: \'Verdana\'; font-size: 8.5pt;', Helper::getTextContent($xpath, '/html/body/div/p[2]/span', 'style')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[3]', 'class')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[3]', 'style')); + self::assertEquals(0, Helper::getLength($xpath, '/html/body/div/p[3]/span')); + } + + /** + * Tests languages. + */ + public function testLanguages(): void + { + $phpWord = new PhpWord(); + $langarabic = new Language('', '', 'ar-DZ'); + $phpWord->addFontStyle('arabic', ['lang' => $langarabic]); + $langhindi = new Language('', 'hi-IN'); + $phpWord->addFontStyle('hindi', ['lang' => $langhindi, 'name' => 'Arial']); + $phpWord->addFontStyle('nolang', ['name' => 'Verdana', 'size' => '10']); + $section = $phpWord->addSection(); + $textrun = $section->addTextRun(); + $textrun->addText('سلام این یک پاراگراف راست به چپ است', ['rtl' => true, 'lang' => $langarabic]); + $section->addText('Ce texte-ci est en français.', ['lang' => 'fr-BE']); + $section->addText('Ce texte-ci aussi.', ['lang' => 'fr-BE', 'name' => 'Verdana']); + $section->addText('Text with no language'); + $section->addText('पाठ हिंदी में', 'hindi'); + $section->addText('Non-existent style', 'nonexistent'); + $section->addText('Style without language', 'nolang'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + self::assertEquals('ar-DZ', Helper::getTextContent($xpath, '/html/body/div/p[1]/span', 'lang')); + self::assertEquals('fr-BE', Helper::getTextContent($xpath, '/html/body/div/p[2]/span', 'lang')); + self::assertEquals('fr-BE', Helper::getTextContent($xpath, '/html/body/div/p[3]/span', 'lang')); + self::assertEquals('font-family: \'Verdana\';', Helper::getTextContent($xpath, '/html/body/div/p[3]/span', 'style')); + self::assertEquals(0, Helper::getLength($xpath, '/html/body/div/p[4]/span')); + self::assertEquals('hi-IN', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'lang')); + self::assertEquals('hindi', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'class')); + self::assertEquals('nonexistent', Helper::getTextContent($xpath, '/html/body/div/p[6]/span', 'class')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[6]/span', 'lang')); + self::assertEquals('nolang', Helper::getTextContent($xpath, '/html/body/div/p[7]/span', 'class')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[7]/span', 'lang')); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/Helper.php b/tests/PhpWordTests/Writer/HTML/Helper.php new file mode 100644 index 0000000000..b777d4be14 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/Helper.php @@ -0,0 +1,97 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\HTML; + +use DOMDocument; +use DOMXPath; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Writer\HTML; + +/** + * Test class for PhpOffice\PhpWord\Writer\HTML\Style subnamespace. + */ +class Helper extends \PHPUnit\Framework\TestCase +{ + public static function getTextContent(DOMXPath $xpath, string $query, string $namedItem = '', int $itemNumber = 0): string + { + $returnVal = ''; + $item = $xpath->query($query); + if ($item === false) { + self::fail('Unexpected false return from xpath query'); + } else { + $item2 = $item->item($itemNumber); + if ($item2 === null) { + self::fail('Unexpected null return requesting item'); + } elseif ($namedItem !== '') { + $item3 = $item2->attributes->getNamedItem($namedItem); + if ($item3 === null) { + self::fail('Unexpected null return requesting namedItem'); + } else { + $returnVal = $item3->textContent; + } + } else { + $returnVal = $item2->textContent; + } + } + + return $returnVal; + } + + /** @return mixed */ + public static function getNamedItem(DOMXPath $xpath, string $query, string $namedItem, int $itemNumber = 0) + { + $returnVal = ''; + $item = $xpath->query($query); + if ($item === false) { + self::fail('Unexpected false return from xpath query'); + } else { + $item2 = $item->item($itemNumber); + if ($item2 === null) { + self::fail('Unexpected null return requesting item'); + } else { + $returnValue = $item2->attributes->getNamedItem($namedItem); + } + } + + return $returnVal; + } + + public static function getLength(DOMXPath $xpath, string $query): int + { + $returnVal = 0; + $item = $xpath->query($query); + if ($item === false) { + self::fail('Unexpected false return from xpath query'); + } else { + $returnVal = $item->length; + } + + return $returnVal; + } + + public static function getAsHTML(PhpWord $phpWord, string $defaultWhiteSpace = '', string $defaultGenericFont = ''): DOMDocument + { + $htmlWriter = new HTML($phpWord); + $htmlWriter->setDefaultWhiteSpace($defaultWhiteSpace); + $htmlWriter->setDefaultGenericFont($defaultGenericFont); + $dom = new DOMDocument(); + $dom->loadHTML($htmlWriter->getContent()); + + return $dom; + } +} diff --git a/tests/PhpWordTests/Writer/HTML/ParagraphTest.php b/tests/PhpWordTests/Writer/HTML/ParagraphTest.php new file mode 100644 index 0000000000..dc306df978 --- /dev/null +++ b/tests/PhpWordTests/Writer/HTML/ParagraphTest.php @@ -0,0 +1,137 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\HTML; + +use DOMXPath; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\Converter; +use PhpOffice\PhpWord\Writer\HTML; + +/** + * Test class for PhpOffice\PhpWord\Writer\HTML\Style\Font. + */ +class ParagraphTest extends \PHPUnit\Framework\TestCase +{ + /** + * Tests indentation, line-height, spaceBefore, spaceAfter, both inline and named. + */ + public function testParagraphStyles(): void + { + $phpWord = new PhpWord(); + $pstyle1 = ['spaceBefore' => 0, 'spaceAfter' => 0, 'lineHeight' => 1.08]; + $phpWord->addParagraphStyle('indented', [ + 'indentation' => ['left' => 0.50 * Converter::INCH_TO_TWIP, 'right' => 0.60 * Converter::INCH_TO_TWIP], + ]); + $text = 'This is a paragraph. It should be long enough to show the effects of indentation on both the right and left sides.'; + $section1 = $phpWord->addSection(); + $section1->addText($text, null, $pstyle1); + $section1->addText($text, null, 'indented'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals(0, Helper::getLength($xpath, '/html/body/div/p[1]/span')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[1]', 'class')); + self::assertEquals('margin-top: 0pt; margin-bottom: 0pt; line-height: 1.08;', Helper::getTextContent($xpath, '/html/body/div/p[1]', 'style')); + self::assertEquals(0, Helper::getLength($xpath, '/html/body/div/p[2]/span')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[2]', 'style')); + self::assertEquals('indented', Helper::getTextContent($xpath, '/html/body/div/p[2]', 'class')); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertNotFalse(preg_match('/^[.]indented[^\\r\\n]*/m', $style, $matches)); + self::assertEquals('.indented {margin-left: 0.5in; margin-right: 0.6in;}', $matches[0]); + } + + /** + * Tests paragraph and font styles specified togeter, both inline and named. + */ + public function testParagraphAndFontStyles(): void + { + $phpWord = new PhpWord(); + $pstyle1 = ['spaceBefore' => 0, 'spaceAfter' => 0, 'lineHeight' => 1.08]; + $phpWord->addParagraphStyle('indented', [ + 'indentation' => ['left' => 0.50 * Converter::INCH_TO_TWIP, 'right' => 0.60 * Converter::INCH_TO_TWIP], + ]); + $phpWord->addFontStyle('style1', ['name' => 'Courier New', 'size' => 10, 'whiteSpace' => 'pre-wrap', 'fallbackFont' => 'monospace']); + $text = 'This is a paragraph. It should be long enough to show the effects of indentation on both the right and left sides.'; + $section1 = $phpWord->addSection(); + $section1->addText($text, 'style1', $pstyle1); + $section1->addText($text, ['name' => 'Verdana', 'size' => '12'], 'indented'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals(1, Helper::getLength($xpath, '/html/body/div/p[1]/span')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[1]', 'class')); + self::assertEquals('margin-top: 0pt; margin-bottom: 0pt; line-height: 1.08;', Helper::getTextContent($xpath, '/html/body/div/p[1]', 'style')); + self::assertEquals('style1', Helper::getTextContent($xpath, '/html/body/div/p[1]/span', 'class')); + self::assertEquals(1, Helper::getLength($xpath, '/html/body/div/p[2]/span')); + self::assertEmpty(Helper::getNamedItem($xpath, '/html/body/div/p[2]', 'style')); + self::assertEquals('indented', Helper::getTextContent($xpath, '/html/body/div/p[2]', 'class')); + self::assertEquals('font-family: \'Verdana\'; font-size: 12pt;', Helper::getTextContent($xpath, '/html/body/div/p[2]/span', 'style')); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertNotFalse(preg_match('/^[.]indented[^\\r\\n]*/m', $style, $matches)); + self::assertEquals('.indented {margin-left: 0.5in; margin-right: 0.6in;}', $matches[0]); + self::assertNotFalse(preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches)); + self::assertEquals('.style1 {font-family: \'Courier New\', monospace; font-size: 10pt; white-space: pre-wrap;}', $matches[0]); + } + + /** + * Tests page break before. + */ + public function testPageBreakBefore(): void + { + $phpWord = new PhpWord(); + $pstyle1 = ['lineHeight' => 1.08]; + $pstyle2 = ['lineHeight' => 1.08, 'pageBreakBefore' => true]; + + $section1 = $phpWord->addSection(); + $section1->addText('1st paragraph 1st page', null, $pstyle1); + $section1->addText('2nd paragraph 1st page', null, $pstyle1); + $section1->addText('1st paragraph 2nd page', null, $pstyle2); + $section1->addText('2nd paragraph 2nd page', null, $pstyle1); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + self::assertEquals('line-height: 1.08;', Helper::getTextContent($xpath, '/html/body/div/p[1]', 'style')); + self::assertEquals('line-height: 1.08;', Helper::getTextContent($xpath, '/html/body/div/p[2]', 'style')); + self::assertEquals('line-height: 1.08; page-break-before: always;', Helper::getTextContent($xpath, '/html/body/div/p[3]', 'style')); + self::assertEquals('line-height: 1.08;', Helper::getTextContent($xpath, '/html/body/div/p[4]', 'style')); + } + + /** + * Tests blank paragraph. + */ + public function testBlankParagraph(): void + { + $phpWord = new PhpWord(); + + $section1 = $phpWord->addSection(); + $section1->addText('Text before blank text'); + $section1->addText(''); + $section1->addText('Text after blank text'); + + $htmlWriter = new HTML($phpWord); + $body = $htmlWriter->getWriterPart('Body')->write(); + $bodylines = explode(PHP_EOL, $body); + self::assertEquals('<p>Text before blank text</p>', $bodylines[2]); + self::assertEquals('<p> </p>', $bodylines[3]); + self::assertEquals('<p>Text after blank text</p>', $bodylines[4]); + } +} diff --git a/tests/PhpWordTests/Writer/HTML/PartTest.php b/tests/PhpWordTests/Writer/HTML/PartTest.php index 19f1147467..9515932ac8 100644 --- a/tests/PhpWordTests/Writer/HTML/PartTest.php +++ b/tests/PhpWordTests/Writer/HTML/PartTest.php @@ -17,6 +17,9 @@ namespace PhpOffice\PhpWordTests\Writer\HTML; +use DOMXPath; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\Converter; use PhpOffice\PhpWord\Writer\HTML\Part\Body; /** @@ -33,4 +36,153 @@ public function testGetParentWriterException(): void $object = new Body(); $object->getParentWriter(); } + + /** + * Tests writing multiple sections. + */ + public function testWriteSections(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language('en-US')); + $section1 = $phpWord->addSection(); + $mtop = 0.5 * Converter::INCH_TO_TWIP; + $mbot = 0.5 * Converter::INCH_TO_TWIP; + $mrig = 0.75 * Converter::INCH_TO_TWIP; + $mlef = 0.75 * Converter::INCH_TO_TWIP; + $section1 + ->getStyle() + ->setPaperSize('Letter') + ->setMarginTop($mtop) + ->setMarginBottom($mbot) + ->setMarginLeft($mlef) + ->setMarginRight($mrig); + $section1->getStyle()->setPortrait(); + $section1->addText('In theory, this will be printed portrait on letter paper'); + + $section2 = $phpWord->addSection(); + $mtop = 0.6 * Converter::INCH_TO_TWIP; + $mbot = 0.6 * Converter::INCH_TO_TWIP; + $mrig = 0.65 * Converter::INCH_TO_TWIP; + $mlef = 0.65 * Converter::INCH_TO_TWIP; + $section2 + ->getStyle() + ->setPaperSize('A4') + ->setMarginTop($mtop) + ->setMarginBottom($mbot) + ->setMarginLeft($mlef) + ->setMarginRight($mrig); + $section2->getStyle()->setLandscape(); + $section2->addText('In theory, this will be printed landscape on A4 paper'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals('en-US', Helper::getTextContent($xpath, '/html', 'lang')); + self::assertEquals(2, Helper::getLength($xpath, '/html/body/div')); + self::assertEquals('page: page1', Helper::getTextContent($xpath, '/html/body/div[1]', 'style')); + self::assertEquals('page: page2', Helper::getTextContent($xpath, '/html/body/div[2]', 'style')); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertNotFalse(strpos($style, 'body > div + div {page-break-before: always;}')); + self::assertNotFalse(strpos($style, 'div > *:first-child {page-break-before: auto;}')); + self::assertNotFalse(strpos($style, '@page page1 {size: Letter portrait; margin-right: 0.75in; margin-left: 0.75in; margin-top: 0.5in; margin-bottom: 0.5in; }')); + self::assertNotFalse(strpos($style, '@page page2 {size: A4 landscape; margin-right: 0.65in; margin-left: 0.65in; margin-top: 0.6in; margin-bottom: 0.6in; }')); + } + + /** + * Tests theme font East Asian. + */ + public function testThemeFontEastAsian(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language('', 'hi-IN')); + $section1 = $phpWord->addSection(); + $section1->addText('??? ????? ???'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals('hi-IN', Helper::getTextContent($xpath, '/html', 'lang')); + } + + /** + * Tests theme font bidirectional. + */ + public function testThemeBidirecional(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setThemeFontLang(new \PhpOffice\PhpWord\Style\Language('', '', 'he-IL')); + $section1 = $phpWord->addSection(); + $section1->addText('????'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals('he-IL', Helper::getTextContent($xpath, '/html', 'lang')); + } + + /** + * Tests writing when default paragraph style is specified. + */ + public function testDefaultParagraphStyle(): void + { + $phpWord = new PhpWord(); + $nospacebeforeafter = ['spaceBefore' => 0, 'spaceAfter' => 0]; + $phpWord->setDefaultParagraphStyle($nospacebeforeafter); + $section1 = $phpWord->addSection(); + $section1->addText('First paragraph with no space before or after'); + $section1->addText('Second paragraph with no space before or after'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEmpty(Helper::getNamedItem($xpath, '/html', 'lang')); + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertNotFalse(strpos($style, 'p, .Normal {margin-top: 0pt; margin-bottom: 0pt;}')); + } + + /** + * Tests writing when default paragraph style is omitted. + */ + public function testNoDefaultParagraphStyle(): void + { + $phpWord = new PhpWord(); + $section1 = $phpWord->addSection(); + $section1->addText('First paragraph with no space before or after'); + $section1->addText('Second paragraph with no space before or after'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertFalse(strpos($style, 'Normal')); + } + + /** + * Tests title styles. + */ + public function testTitleStyles(): void + { + $phpWord = new PhpWord(); + $phpWord->setDefaultParagraphStyle(['spaceBefore' => 0, 'spaceAfter' => 0]); + $phpWord->addTitleStyle(1, ['bold' => true, 'name' => 'Calibri'], ['spaceBefore' => 10, 'spaceAfter' => 10]); + $phpWord->addTitleStyle(2, ['italic' => true, 'name' => 'Times New Roman'], ['spaceBefore' => 5, 'spaceAfter' => 5]); + $section1 = $phpWord->addSection(); + $section1->addTitle('Header 1 #1', 1); + $section1->addTitle('Header 2 #1', 2); + $section1->addText('Paragraph under header 2 #1'); + $section1->addTitle('Header 2 #2', 2); + $section1->addText('Paragraph under header 2 #2'); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + $style = Helper::getTextContent($xpath, '/html/head/style'); + self::assertNotFalse(strpos($style, 'h1 {font-family: \'Calibri\'; font-weight: bold;}')); + self::assertNotFalse(strpos($style, 'h1 {margin-top: 0.5pt; margin-bottom: 0.5pt;}')); + self::assertNotFalse(strpos($style, 'h2 {font-family: \'Times New Roman\'; font-style: italic;}')); + self::assertNotFalse(strpos($style, 'h2 {margin-top: 0.25pt; margin-bottom: 0.25pt;}')); + self::assertEquals(1, Helper::getLength($xpath, '/html/body/div/h1')); + self::assertEquals(2, Helper::getLength($xpath, '/html/body/div/h2')); + } } diff --git a/tests/PhpWordTests/Writer/HTML/StyleTest.php b/tests/PhpWordTests/Writer/HTML/StyleTest.php index 7c360e1a6b..0ad5dd46a0 100644 --- a/tests/PhpWordTests/Writer/HTML/StyleTest.php +++ b/tests/PhpWordTests/Writer/HTML/StyleTest.php @@ -27,7 +27,7 @@ class StyleTest extends \PHPUnit\Framework\TestCase */ public function testEmptyStyles(): void { - $styles = ['Font', 'Paragraph', 'Image']; + $styles = ['Font', 'Paragraph', 'Image', 'Table']; foreach ($styles as $style) { $objectClass = 'PhpOffice\\PhpWord\\Writer\\HTML\\Style\\' . $style; $object = new $objectClass(); diff --git a/tests/PhpWordTests/Writer/HTMLTest.php b/tests/PhpWordTests/Writer/HTMLTest.php index 8303570e92..700dd2ed3b 100644 --- a/tests/PhpWordTests/Writer/HTMLTest.php +++ b/tests/PhpWordTests/Writer/HTMLTest.php @@ -37,7 +37,7 @@ public function testConstruct(): void { $object = new HTML(new PhpWord()); - self::assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $object->getPhpWord()); + self::assertInstanceOf(PhpWord::class, $object->getPhpWord()); } /** @@ -51,6 +51,41 @@ public function testConstructWithNull(): void $object->getPhpWord(); } + public function testEditCallback(): void + { + $object = new HTML(new PhpWord()); + + self::assertNull($object->getEditCallback()); + self::assertInstanceOf(HTML::class, $object->setEditCallback(function (string $html): string { + return $html; + })); + self::assertIsCallable($object->getEditCallback()); + self::assertInstanceOf(HTML::class, $object->setEditCallback(null)); + self::assertNull($object->getEditCallback()); + } + + public function testDefaultGenericFont(): void + { + $object = new HTML(new PhpWord()); + + self::assertEquals('', $object->getDefaultGenericFont()); + self::assertInstanceOf(HTML::class, $object->setDefaultGenericFont('test')); + self::assertEquals('', $object->getDefaultGenericFont()); + self::assertInstanceOf(HTML::class, $object->setDefaultGenericFont('cursive')); + self::assertEquals('cursive', $object->getDefaultGenericFont()); + } + + public function testDefaultWhiteSpace(): void + { + $object = new HTML(new PhpWord()); + + self::assertEquals('', $object->getDefaultWhiteSpace()); + self::assertInstanceOf(HTML::class, $object->setDefaultWhiteSpace('test')); + self::assertEquals('', $object->getDefaultWhiteSpace()); + self::assertInstanceOf(HTML::class, $object->setDefaultWhiteSpace('pre-line')); + self::assertEquals('pre-line', $object->getDefaultWhiteSpace()); + } + /** * Save. */ diff --git a/tests/PhpWordTests/Writer/ODText/Element/FieldTest.php b/tests/PhpWordTests/Writer/ODText/Element/FieldTest.php new file mode 100644 index 0000000000..2d9bb7c87d --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Element/FieldTest.php @@ -0,0 +1,64 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\ODText\Element; + +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWordTests\TestHelperDOCX; +use PHPUnit\Framework\TestCase; + +/** + * Test class for PhpOffice\PhpWord\Writer\ODText\Element subnamespace. + */ +class FieldTest extends TestCase +{ + /** + * Executed before each method of the class. + */ + protected function tearDown(): void + { + TestHelperDOCX::clear(); + } + + public function testFieldFilename(): void + { + $phpWord = new PhpWord(); + + $section = $phpWord->addSection(); + $section->addField('FILENAME'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + self::assertTrue($doc->elementExists('/office:document-content/office:body/office:text/text:section/text:span/text:file-name')); + self::assertEquals('false', $doc->getElementAttribute('/office:document-content/office:body/office:text/text:section/text:span/text:file-name', 'text:fixed')); + self::assertEquals('name', $doc->getElementAttribute('/office:document-content/office:body/office:text/text:section/text:span/text:file-name', 'text:display')); + } + + public function testFieldFilenameOptionPath(): void + { + $phpWord = new PhpWord(); + + $section = $phpWord->addSection(); + $section->addField('FILENAME', [], ['Path']); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + self::assertTrue($doc->elementExists('/office:document-content/office:body/office:text/text:section/text:span/text:file-name')); + self::assertEquals('false', $doc->getElementAttribute('/office:document-content/office:body/office:text/text:section/text:span/text:file-name', 'text:fixed')); + self::assertEquals('full', $doc->getElementAttribute('/office:document-content/office:body/office:text/text:section/text:span/text:file-name', 'text:display')); + } +} diff --git a/tests/PhpWordTests/Writer/ODText/Element/FormulaTest.php b/tests/PhpWordTests/Writer/ODText/Element/FormulaTest.php new file mode 100644 index 0000000000..9276372105 --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Element/FormulaTest.php @@ -0,0 +1,71 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\ODText\Element; + +use PhpOffice\Math\Element; +use PhpOffice\Math\Math; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWordTests\TestHelperDOCX; +use PHPUnit\Framework\TestCase; + +/** + * Test class for PhpOffice\PhpWord\Writer\ODText\Element subnamespace. + */ +class FormulaTest extends TestCase +{ + /** + * Executed before each method of the class. + */ + protected function tearDown(): void + { + TestHelperDOCX::clear(); + } + + public function testBasicFormula(): void + { + $math = new Math(); + $math + ->add( + new Element\Fraction( + new Element\Numeric(2), + new Element\Identifier('π') + ) + ) + ->add( + new Element\Operator('+') + ) + ->add( + new Element\Identifier('a') + ) + ->add( + new Element\Operator('∗') + ) + ->add( + new Element\Numeric(2) + ); + + $phpWord = new PhpWord(); + + $section = $phpWord->addSection(); + $section->addFormula($math); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + self::assertTrue($doc->elementExists('/office:document-content/office:body/office:text/text:section/text:p/draw:frame/draw:object')); + } +} diff --git a/tests/PhpWordTests/Writer/ODText/Element/ImageTest.php b/tests/PhpWordTests/Writer/ODText/Element/ImageTest.php index e150418c9f..367f9eee75 100644 --- a/tests/PhpWordTests/Writer/ODText/Element/ImageTest.php +++ b/tests/PhpWordTests/Writer/ODText/Element/ImageTest.php @@ -17,6 +17,8 @@ namespace PhpOffice\PhpWordTests\Writer\ODText\Style; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Style\Image; use PhpOffice\PhpWordTests\TestHelperDOCX; @@ -32,6 +34,7 @@ class ImageTest extends \PHPUnit\Framework\TestCase */ protected function tearDown(): void { + Settings::setDefaultRtl(null); TestHelperDOCX::clear(); } @@ -40,7 +43,7 @@ protected function tearDown(): void */ public function testImage1(): void { - $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addImage(__DIR__ . '/../../../_files/images/earth.jpg'); $section->addImage(__DIR__ . '/../../../_files/images/mario.gif', ['align' => 'end']); @@ -57,9 +60,68 @@ public function testImage1(): void $path = '/office:document-content/office:body/office:text/text:section/text:p[2]'; self::assertTrue($doc->elementExists($path)); + self::assertFalse($doc->hasElementAttribute($path, 'draw:text-style-name')); self::assertEquals('IM1', $doc->getElementAttribute($path, 'text:style-name')); $path = '/office:document-content/office:body/office:text/text:section/text:p[3]'; self::assertTrue($doc->elementExists($path)); + self::assertFalse($doc->hasElementAttribute($path, 'draw:text-style-name')); self::assertEquals('IM2', $doc->getElementAttribute($path, 'text:style-name')); } + + /** + * Test writing image, with non-default bidi. + */ + public function testImage2(): void + { + $phpWord = new PhpWord(); + Settings::setDefaultRtl(false); + $section = $phpWord->addSection(); + $section->addImage(__DIR__ . '/../../../_files/images/earth.jpg'); + $section->addImage(__DIR__ . '/../../../_files/images/mario.gif', ['align' => 'end']); + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $element = "$s2a/style:style[3]"; + self::assertEquals('IM1', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + $element = "$s2a/style:style[4]"; + self::assertEquals('IM2', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + + $path = '/office:document-content/office:body/office:text/text:section/text:p[2]'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals('IM1', $doc->getElementAttribute($path, 'text:style-name')); + self::assertFalse($doc->hasElementAttribute($path, 'draw:text-style-name')); + $path = '/office:document-content/office:body/office:text/text:section/text:p[3]'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals('IM2', $doc->getElementAttribute($path, 'text:style-name')); + self::assertFalse($doc->hasElementAttribute($path, 'draw:text-style-name')); + } + + /** + * Test writing image not in a section. + */ + public function testImageInTextRun(): void + { + $phpWord = new PhpWord(); + Settings::setDefaultRtl(false); + $section = $phpWord->addSection(); + $textRun = $section->addTextRun(); + $textRun->addImage(__DIR__ . '/../../../_files/images/earth.jpg'); + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $element = "$s2a/style:style[4]"; + self::assertEquals('IM1', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + + $path = '/office:document-content/office:body/office:text/text:section/text:p[2]'; + self::assertTrue($doc->elementExists($path)); + self::assertEquals('P1', $doc->getElementAttribute($path, 'text:style-name')); + $path = '/office:document-content/office:body/office:text/text:section/text:p[2]/draw:frame'; + self::assertTrue($doc->elementExists($path)); + self::assertTrue($doc->hasElementAttribute($path, 'draw:text-style-name')); + self::assertEquals('IM1', $doc->getElementAttribute($path, 'draw:text-style-name')); + } } diff --git a/tests/PhpWordTests/Writer/ODText/Element/ListItemRunTest.php b/tests/PhpWordTests/Writer/ODText/Element/ListItemRunTest.php new file mode 100644 index 0000000000..d0509035cc --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Element/ListItemRunTest.php @@ -0,0 +1,82 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\ODText\Element; + +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWordTests\TestHelperDOCX; +use PHPUnit\Framework\TestCase; + +class ListItemRunTest extends TestCase +{ + /** + * Executed after each method of the class. + */ + protected function tearDown(): void + { + TestHelperDOCX::clear(); + } + + public function testAddListItemRun(): void + { + $expected = 'List item run 1'; + + $phpWord = new PhpWord(); + $phpWord + ->addSection() + ->addListItemRun() + ->addText($expected); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $xPath = '/office:document-content/office:body/office:text/text:section/text:list'; + + self::assertTrue($doc->elementExists($xPath)); + self::assertTrue($doc->hasElementAttribute($xPath, 'text:style-name')); + self::assertEquals('PHPWordListType3', $doc->getElementAttribute($xPath, 'text:style-name')); + self::assertTrue($doc->elementExists($xPath . '/text:list-item')); + self::assertTrue($doc->elementExists($xPath . '/text:list-item/text:p')); + self::assertTrue($doc->elementExists($xPath . '/text:list-item/text:p/text:span')); + self::assertEquals($expected, $doc->getElement($xPath . '/text:list-item/text:p/text:span')->nodeValue); + } + + public function testAddListItemRunLevels(): void + { + $expected = 'List item run : '; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addListItemRun(0)->addText($expected . '1'); + $section->addListItemRun(1)->addText($expected . '2'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + $xPath = '/office:document-content/office:body/office:text/text:section/text:list'; + + self::assertTrue($doc->elementExists($xPath)); + self::assertTrue($doc->hasElementAttribute($xPath, 'text:style-name')); + self::assertEquals('PHPWordListType3', $doc->getElementAttribute($xPath, 'text:style-name')); + self::assertTrue($doc->elementExists($xPath . '/text:list-item')); + self::assertTrue($doc->elementExists($xPath . '/text:list-item/text:p')); + self::assertTrue($doc->elementExists($xPath . '/text:list-item/text:p/text:span')); + self::assertEquals($expected . '1', $doc->getElement($xPath . '/text:list-item/text:p/text:span')->nodeValue); + self::assertTrue($doc->elementExists($xPath . '/text:list-item/text:list/text:list-item')); + self::assertTrue($doc->elementExists($xPath . '/text:list-item/text:list/text:list-item/text:p')); + self::assertTrue($doc->elementExists($xPath . '/text:list-item/text:list/text:list-item/text:p/text:span')); + self::assertEquals($expected . '2', $doc->getElement($xPath . '/text:list-item/text:list/text:list-item/text:p/text:span')->nodeValue); + } +} diff --git a/tests/PhpWordTests/Writer/ODText/ElementTest.php b/tests/PhpWordTests/Writer/ODText/ElementTest.php index 45f542b3b1..0ad0325656 100644 --- a/tests/PhpWordTests/Writer/ODText/ElementTest.php +++ b/tests/PhpWordTests/Writer/ODText/ElementTest.php @@ -130,7 +130,7 @@ public function testTableElements(): void $p2t = '/office:document-content/office:body/office:text/text:section'; $tableRootElement = "$p2t/table:table"; self::assertTrue($doc->elementExists($tableRootElement)); - self::assertEquals($tableStyleName, $doc->getElementAttribute($tableRootElement, 'table:style')); + self::assertEquals($tableStyleName, $doc->getElementAttribute($tableRootElement, 'table:style-name')); self::assertTrue($doc->elementExists($tableRootElement . '/table:table-column[4]')); } diff --git a/tests/PhpWordTests/Writer/ODText/Part/ManifestTest.php b/tests/PhpWordTests/Writer/ODText/Part/ManifestTest.php new file mode 100644 index 0000000000..01a8053c8f --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Part/ManifestTest.php @@ -0,0 +1,100 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +declare(strict_types=1); + +namespace PhpOffice\PhpWordTests\Writer\ODText\Part; + +use PhpOffice\Math\Element; +use PhpOffice\Math\Math; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWordTests\TestHelperDOCX; + +/** + * Test class for PhpOffice\PhpWord\Writer\ODText\Part\Manifest. + * + * @coversDefaultClass \PhpOffice\PhpWord\Writer\ODText\Part\Manifest + */ +class ManifestTest extends \PHPUnit\Framework\TestCase +{ + /** + * Executed before each method of the class. + */ + protected function tearDown(): void + { + TestHelperDOCX::clear(); + } + + public function testWriteBasic(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + self::assertFalse($doc->elementExists( + '/manifest:manifest/manifest:file-entry[@manifest:full-path="Formula0/content.xml"]', + 'META-INF/manifest.xml' + )); + self::assertFalse($doc->elementExists( + '/manifest:manifest/manifest:file-entry[@manifest:full-path="Formula0/"]', + 'META-INF/manifest.xml' + )); + } + + public function testWriteFormula(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $math = new Math(); + $math->add( + new Element\Fraction( + new Element\Numeric(2), + new Element\Identifier('π') + ) + ); + $section->addFormula($math); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + + self::assertTrue($doc->elementExists( + '/manifest:manifest/manifest:file-entry[@manifest:full-path="Formula0/content.xml"]', + 'META-INF/manifest.xml' + )); + self::assertEquals('text/xml', $doc->getElementAttribute( + '/manifest:manifest/manifest:file-entry[@manifest:full-path="Formula0/content.xml"]', + 'manifest:media-type', + 'META-INF/manifest.xml' + )); + + self::assertTrue($doc->elementExists( + '/manifest:manifest/manifest:file-entry[@manifest:full-path="Formula0/"]', + 'META-INF/manifest.xml' + )); + self::assertEquals('1.2', $doc->getElementAttribute( + '/manifest:manifest/manifest:file-entry[@manifest:full-path="Formula0/"]', + 'manifest:version', + 'META-INF/manifest.xml' + )); + self::assertEquals('application/vnd.oasis.opendocument.formula', $doc->getElementAttribute( + '/manifest:manifest/manifest:file-entry[@manifest:full-path="Formula0/"]', + 'manifest:media-type', + 'META-INF/manifest.xml' + )); + } +} diff --git a/tests/PhpWordTests/Writer/ODText/Style/FontTest.php b/tests/PhpWordTests/Writer/ODText/Style/FontTest.php index dda0110af5..34e5e506b4 100644 --- a/tests/PhpWordTests/Writer/ODText/Style/FontTest.php +++ b/tests/PhpWordTests/Writer/ODText/Style/FontTest.php @@ -74,7 +74,7 @@ public function testColors(): void self::assertEquals('This should be dark green (FGCOLOR_DARKGREEN)', $doc->getElement($span)->nodeValue); } - public function providerAllNamedColors() + public static function providerAllNamedColors() { return [ [Font::FGCOLOR_YELLOW, 'FFFF00'], diff --git a/tests/PhpWordTests/Writer/ODText/Style/NumberingTest.php b/tests/PhpWordTests/Writer/ODText/Style/NumberingTest.php new file mode 100644 index 0000000000..4b46637045 --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Style/NumberingTest.php @@ -0,0 +1,69 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\ODText\Style; + +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\SimpleType\Jc; +use PhpOffice\PhpWordTests\TestHelperDOCX; + +class NumberingTest extends \PHPUnit\Framework\TestCase +{ + /** + * Executed after each method of the class. + */ + protected function tearDown(): void + { + TestHelperDOCX::clear(); + } + + public function testAddListItemRun(): void + { + $expected = 'MyOwnNumberingStyle'; + + $phpWord = new PhpWord(); + $phpWord->addNumberingStyle($expected, [ + 'type' => 'multilevel', + 'levels' => [ + [ + 'start' => 1, + 'format' => 'decimal', + 'restart' => 1, + 'suffix' => 'space', + 'text' => '%1.', + 'alignment' => Jc::START, + ], + ], + ]); + $phpWord->addSection() + ->addListItemRun(0, $expected) + ->addText('List item run 1'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $doc->setDefaultFile('styles.xml'); + + $xPath = '/office:document-styles/office:styles'; + self::assertTrue($doc->elementExists($xPath)); + self::assertTrue($doc->elementExists($xPath . '/text:list-style')); + self::assertTrue($doc->hasElementAttribute($xPath . '/text:list-style', 'style:name')); + self::assertEquals($expected, $doc->getElementAttribute($xPath . '/text:list-style', 'style:name')); + self::assertTrue($doc->elementExists($xPath . '/text:list-style/text:list-level-style-bullet')); + self::assertTrue($doc->elementExists($xPath . '/text:list-style/text:list-level-style-bullet/style:list-level-properties')); + self::assertTrue($doc->elementExists($xPath . '/text:list-style/text:list-level-style-bullet/style:list-level-properties/style:list-level-label-alignment')); + self::assertTrue($doc->elementExists($xPath . '/text:list-style/text:list-level-style-bullet/style:text-properties')); + } +} diff --git a/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php b/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php new file mode 100644 index 0000000000..b638b380b6 --- /dev/null +++ b/tests/PhpWordTests/Writer/ODText/Style/Paragraph2Test.php @@ -0,0 +1,154 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\ODText\Style; + +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Style; +use PhpOffice\PhpWordTests\TestHelperDOCX; + +class Paragraph2Test extends \PHPUnit\Framework\TestCase +{ + /** + * Test textAlign. + */ + public function testTextAlign(): void + { + $phpWord = new PhpWord(); + Settings::setDefaultRtl(true); + $align1 = ['alignment' => 'end']; + $align2 = ['alignment' => 'start']; + $phpWord->setDefaultParagraphStyle($align1); + $section = $phpWord->addSection(); + $section->addText('Should use default alignment (right for this doc)'); + $section->addText('Explicit right alignment', null, $align2); + $section->addText('Explicit left alignment', null, $align1); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + self::assertTrue($doc->elementExists($s2a)); + + $element = "$s2a/style:style[4]"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('Normal', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + + $element = "$s2a/style:style[6]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + + $element = "$s2a/style:style[8]/style:paragraph-properties"; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + + $doc->setDefaultFile('styles.xml'); + $element = '/office:document-styles/office:styles/style:style'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('Normal', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + } + + /** + * Test text run paragraph style using named style. + */ + public function testTextRun(): void + { + $phpWord = new PhpWord(); + Settings::setDefaultRtl(false); + $phpWord->addParagraphStyle('parstyle1', ['align' => 'start']); + $phpWord->addParagraphStyle('parstyle2', ['align' => 'end']); + $section = $phpWord->addSection(); + $trx = $section->addTextRun('parstyle1'); + $trx->addText('First text in textrun. '); + $trx->addText('Second text - paragraph style is specified but ignored.', null, 'parstyle2'); + $section->addText('Third text added to section not textrun - paragraph style is specified and used.', null, 'parstyle2'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $element = "$s2a/style:style[3]"; + self::assertEquals('P1_parstyle1', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('parstyle1', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element = "$s2a/style:style[9]"; + self::assertEquals('P4_parstyle2', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('parstyle2', $doc->getElementAttribute($element, 'style:parent-style-name')); + + $s2a = '/office:document-content/office:body/office:text/text:section'; + $element = "$s2a/text:p[2]"; + self::assertEquals('P1_parstyle1', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:p[3]"; + self::assertEquals('P4_parstyle2', $doc->getElementAttribute($element, 'text:style-name')); + + $doc->setDefaultFile('styles.xml'); + $element = '/office:document-styles/office:styles/style:style[1]'; + self::assertEquals('parstyle1', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + $element = '/office:document-styles/office:styles/style:style[2]'; + self::assertEquals('parstyle2', $doc->getElementAttribute($element, 'style:name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + } + + /** + * Test text run paragraph style using unnamed style. + */ + public function testTextRunUnnamed(): void + { + $phpWord = new PhpWord(); + Settings::setDefaultRtl(false); + $parstyle1 = ['align' => 'start']; + $parstyle2 = ['align' => 'end']; + $section = $phpWord->addSection(); + $trx = $section->addTextRun($parstyle1); + $trx->addText('First text in textrun. '); + $trx->addText('Second text - paragraph style is specified but ignored.', null, $parstyle2); + $section->addText('Third text added to section not textrun - paragraph style is specified and used.', null, $parstyle2); + + $doc = TestHelperDOCX::getDocument($phpWord, 'ODText'); + $s2a = '/office:document-content/office:automatic-styles'; + $element = "$s2a/style:style[3]"; + self::assertEquals('P1', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('Normal', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('left', $doc->getElementAttribute($element, 'fo:text-align')); + $element = "$s2a/style:style[9]"; + self::assertEquals('P4', $doc->getElementAttribute($element, 'style:name')); + self::assertEquals('Normal', $doc->getElementAttribute($element, 'style:parent-style-name')); + $element .= '/style:paragraph-properties'; + self::assertEquals('right', $doc->getElementAttribute($element, 'fo:text-align')); + + $s2a = '/office:document-content/office:body/office:text/text:section'; + $element = "$s2a/text:p[2]"; + self::assertEquals('P1', $doc->getElementAttribute($element, 'text:style-name')); + $element = "$s2a/text:p[3]"; + self::assertEquals('P4', $doc->getElementAttribute($element, 'text:style-name')); + } + + public function testWhenNullifed(): void + { + $dflt1 = Settings::isDefaultRtl(); + self::assertFalse($dflt1); + $phpWord = new PhpWord(); + $dflt2 = Settings::isDefaultRtl(); + self::assertNull($dflt2); + } +} diff --git a/tests/PhpWordTests/Writer/ODTextTest.php b/tests/PhpWordTests/Writer/ODTextTest.php index 060fda9e45..2405f9db7f 100644 --- a/tests/PhpWordTests/Writer/ODTextTest.php +++ b/tests/PhpWordTests/Writer/ODTextTest.php @@ -103,14 +103,15 @@ public function testSave(): void */ public function testSavePhpOutput(): void { - $this->setOutputCallback(function (): void { - }); $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText('Test'); $writer = new ODText($phpWord); + ob_start(); $writer->save('php://output'); - self::assertNotNull($this->getActualOutput()); + $contents = ob_get_contents(); + self::assertTrue(ob_end_clean()); + self::assertNotEmpty($contents); } /** diff --git a/tests/PhpWordTests/Writer/PDF/DomPDFTest.php b/tests/PhpWordTests/Writer/PDF/DomPDFTest.php index 5365a9dcaa..789519ded1 100644 --- a/tests/PhpWordTests/Writer/PDF/DomPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/DomPDFTest.php @@ -75,4 +75,21 @@ public function testSetGetAbstractRendererProperties(): void $writer->setTempDir(Settings::getTempDir()); self::assertEquals(Settings::getTempDir(), $writer->getTempDir()); } + + /** + * Test set/get abstract renderer options. + */ + public function testSetGetAbstractRendererOptions(): void + { + define('DOMPDF_ENABLE_AUTOLOAD', false); + + $rendererName = Settings::PDF_RENDERER_DOMPDF; + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/dompdf/dompdf'); + Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + Settings::setPdfRendererOptions([ + 'font' => 'Arial', + ]); + $writer = new PDF(new PhpWord()); + self::assertEquals('Arial', $writer->getFont()); + } } diff --git a/tests/PhpWordTests/Writer/PDF/MPDFTest.php b/tests/PhpWordTests/Writer/PDF/MPDFTest.php index 44effe3b26..0fe53456ba 100644 --- a/tests/PhpWordTests/Writer/PDF/MPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/MPDFTest.php @@ -20,6 +20,7 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Writer\PDF; +use PhpOffice\PhpWord\Writer\PDF\MPDF; /** * Test class for PhpOffice\PhpWord\Writer\PDF\MPDF. @@ -39,15 +40,76 @@ public function testConstruct(): void $section = $phpWord->addSection(); $section->addText('Test 1'); $section->addPageBreak(); + $section->addText('Test 2'); + $oSettings = new \PhpOffice\PhpWord\Style\Section(); + $oSettings->setSettingValue('orientation', 'landscape'); + $section = $phpWord->addSection($oSettings); // @phpstan-ignore-line + $section->addText('Section 2 - landscape'); - $rendererName = Settings::PDF_RENDERER_MPDF; - $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/mpdf/mpdf'); - Settings::setPdfRenderer($rendererName, $rendererLibraryPath); - $writer = new PDF($phpWord); + $writer = new MPDF($phpWord); + $writer->save($file); + + self::assertFileExists($file); + + unlink($file); + } + + public function testEditCallback(): void + { + $file = __DIR__ . '/../../_files/mpdf.pdf'; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Test 1'); + $section->addPageBreak(); + $section->addText('Test 2'); + $oSettings = new \PhpOffice\PhpWord\Style\Section(); + $oSettings->setSettingValue('orientation', 'landscape'); + $section = $phpWord->addSection($oSettings); // @phpstan-ignore-line + $section->addText('Section 2 - landscape'); + + $writer = new MPDF($phpWord); + /** @var callable */ + $callback = [self::class, 'cbEditContent']; + $writer->setEditCallback($callback); $writer->save($file); self::assertFileExists($file); unlink($file); } + + // add a footer + public static function cbEditContent(string $html): string + { + $afterBody = '<htmlpagefooter name="myFooter1"><div style=\'text-align: right;\'>{PAGENO}</div></htmlpagefooter>' . MPDF::SIMULATED_BODY_START; + $beforeBody = '<style>@page page1 {odd-footer-name: html_myFooter1;}</style>'; + $needle = '</head>'; + $pos = strpos($html, $needle); + if ($pos !== false) { + $html = (string) substr_replace($html, "$beforeBody\n$needle", $pos, strlen($needle)); + } + $needle = '<body>'; + $pos = strpos($html, $needle); + if ($pos !== false) { + $html = (string) substr_replace($html, "$needle\n$afterBody", $pos, strlen($needle)); + } + + return $html; + } + + /** + * Test set/get abstract renderer options. + */ + public function testSetGetAbstractRendererOptions(): void + { + $rendererName = Settings::PDF_RENDERER_MPDF; + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/mpdf/mpdf'); + Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + Settings::setPdfRendererOptions([ + 'font' => 'Arial', + ]); + $writer = new PDF(new PhpWord()); + self::assertEquals('Arial', $writer->getFont()); + } } diff --git a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php index 89e6acb701..00587cb7f3 100644 --- a/tests/PhpWordTests/Writer/PDF/TCPDFTest.php +++ b/tests/PhpWordTests/Writer/PDF/TCPDFTest.php @@ -36,6 +36,7 @@ public function testConstruct(): void $file = __DIR__ . '/../../_files/tcpdf.pdf'; $phpWord = new PhpWord(); + $phpWord->setDefaultParagraphStyle(['spaceBefore' => 0, 'spaceAfter' => 0]); $section = $phpWord->addSection(); $section->addText('Test 1'); @@ -49,4 +50,19 @@ public function testConstruct(): void unlink($file); } + + /** + * Test set/get abstract renderer options. + */ + public function testSetGetAbstractRendererOptions(): void + { + $rendererName = Settings::PDF_RENDERER_TCPDF; + $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/tecnickcom/tcpdf'); + Settings::setPdfRenderer($rendererName, $rendererLibraryPath); + Settings::setPdfRendererOptions([ + 'font' => 'Arial', + ]); + $writer = new PDF(new PhpWord()); + self::assertEquals('Arial', $writer->getFont()); + } } diff --git a/tests/PhpWordTests/Writer/PDFTest.php b/tests/PhpWordTests/Writer/PDFTest.php index 72fae65e7f..fc4064a23f 100644 --- a/tests/PhpWordTests/Writer/PDFTest.php +++ b/tests/PhpWordTests/Writer/PDFTest.php @@ -55,6 +55,6 @@ public function testConstructException(): void $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); $this->expectExceptionMessage('PDF rendering library or library path has not been defined.'); $writer = new PDF(new PhpWord()); - $writer->save(); + $writer->save('unknown.file'); } } diff --git a/tests/PhpWordTests/Writer/RTF/Element/TableTest.php b/tests/PhpWordTests/Writer/RTF/Element/TableTest.php new file mode 100644 index 0000000000..35cdfbec28 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Element/TableTest.php @@ -0,0 +1,169 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\RTF\Element; + +use PhpOffice\PhpWord\Element\Table; +use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\SimpleType\Border; +use PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\Writer\RTF; +use PhpOffice\PhpWord\Writer\RTF\Element\Table as WriterTable; + +class TableTest extends \PHPUnit\Framework\TestCase +{ + protected function tearDown(): void + { + Settings::setDefaultRtl(null); + } + + public function removeCr(WriterTable $field): string + { + return str_replace("\r\n", "\n", $field->write()); + } + + public function testTable(): void + { + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Table(); + $width = 100; + $width2 = 2 * $width; + $element->addRow(); + $tce = $element->addCell($width); + $tce->addText('1'); + $tce = $element->addCell($width); + $tce->addText('2'); + $element->addRow(); + $tce = $element->addCell($width); + $tce->addText('3'); + $tce = $element->addCell($width); + $tce->addText('4'); + $table = new WriterTable($parentWriter, $element); + $expect = implode("\n", [ + '\\pard', + "\\trowd \\cellx$width \\cellx$width2 ", + '\\intbl', + '\\ql{\\cf0\\f0 1}\\par', + '\\cell', + '\\intbl', + '{\\cf0\\f0 2}\\par', + '\\cell', + '\\row', + "\\trowd \\cellx$width \\cellx$width2 ", + '\\intbl', + '\\ql{\\cf0\\f0 3}\\par', + '\\cell', + '\\intbl', + '{\\cf0\\f0 4}\par', + '\\cell', + '\\row', + '\\pard', + '', + ]); + + self::assertEquals($expect, $this->removeCr($table)); + } + + public function testTableStyle(): void + { + $width = 100; + + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + + Style::addTableStyle('TableStyle', ['borderSize' => 6, 'borderColor' => '006699']); + + $element = new Table('TableStyle'); + $element->addRow(); + $elementCell = $element->addCell($width); + $elementCell->addText('1'); + + $expect = implode("\n", [ + '\\pard', + '\\trowd \\clbrdrt\\brdrs\\brdrw2\\brdrcf0', + '\\clbrdrl\\brdrs\\brdrw2\\brdrcf0', + '\\clbrdrb\\brdrs\\brdrw2\\brdrcf0', + '\\clbrdrr\\brdrs\\brdrw2\\brdrcf0', + "\\cellx$width ", + '\\intbl', + '\\ql{\\cf0\\f0 1}\\par', + '\\cell', + '\\row', + '\\pard', + '', + ]); + + self::assertEquals($expect, $this->removeCr(new WriterTable($parentWriter, $element))); + } + + public function testTableStyleNotExisting(): void + { + $width = 100; + + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + + $element = new Table('TableStyleNotExisting'); + $element->addRow(); + $elementCell = $element->addCell($width); + $elementCell->addText('1'); + + $expect = implode("\n", [ + '\\pard', + "\\trowd \\cellx$width ", + '\\intbl', + '\\ql{\\cf0\\f0 1}\\par', + '\\cell', + '\\row', + '\\pard', + '', + ]); + + self::assertEquals($expect, $this->removeCr(new WriterTable($parentWriter, $element))); + } + + public function testTableCellStyle(): void + { + $width = 100; + + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + + $element = new Table(); + $element->addRow(); + $elementCell = $element->addCell($width, ['borderSize' => 6, 'borderColor' => '006699', 'borderStyle' => Border::DOTTED]); + $elementCell->addText('1'); + + $expect = implode("\n", [ + '\\pard', + '\\trowd \\clbrdrt\\brdrdot\\brdrw2\\brdrcf0', + '\\clbrdrl\\brdrdot\\brdrw2\\brdrcf0', + '\\clbrdrb\\brdrdot\\brdrw2\\brdrcf0', + '\\clbrdrr\\brdrdot\\brdrw2\\brdrcf0', + "\\cellx$width ", + '\\intbl', + '\\ql{\\cf0\\f0 1}\\par', + '\\cell', + '\\row', + '\\pard', + '', + ]); + + self::assertEquals($expect, $this->removeCr(new WriterTable($parentWriter, $element))); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/Element2Test.php b/tests/PhpWordTests/Writer/RTF/Element2Test.php new file mode 100644 index 0000000000..7d36fac869 --- /dev/null +++ b/tests/PhpWordTests/Writer/RTF/Element2Test.php @@ -0,0 +1,78 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\RTF; + +use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Writer\RTF; +use PhpOffice\PhpWord\Writer\RTF\Element\TextRun as WriterTextRun; +use PhpOffice\PhpWord\Writer\RTF\Element\Title as WriterTitle; + +/** + * Test class for PhpOffice\PhpWord\Writer\RTF\Element subnamespace. + */ +class Element2Test extends \PHPUnit\Framework\TestCase +{ + protected function tearDown(): void + { + Settings::setDefaultRtl(null); + } + + /** @param WriterTextRun|WriterTitle $field */ + public function removeCr($field): string + { + return str_replace("\r\n", "\n", $field->write()); + } + + public function testTextRun(): void + { + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\TextRun(); + $element->addText('Hello '); + $element->addText('there.'); + $textrun = new WriterTextRun($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\ql{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; + self::assertEquals($expect, $this->removeCr($textrun)); + } + + public function testTextRunParagraphStyle(): void + { + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\TextRun(['spaceBefore' => 0, 'spaceAfter' => 0]); + $element->addText('Hello '); + $element->addText('there.'); + $textrun = new WriterTextRun($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\ql\\sb0\\sa0{{\\cf0\\f0 Hello }{\\cf0\\f0 there.}}\\par\n"; + self::assertEquals($expect, $this->removeCr($textrun)); + } + + public function testTitle(): void + { + $parentWriter = new RTF(); + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + Settings::setDefaultRtl(false); + $phpWord->addTitleStyle(1, [], ['spaceBefore' => 0, 'spaceAfter' => 0]); + $section = $phpWord->addSection(); + $element = $section->addTitle('First Heading', 1); + $elwrite = new WriterTitle($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\ql\\sb0\\sa0{\\outlinelevel0{\\cf0\\f0 First Heading}\\par\n}"; + self::assertEquals($expect, $this->removeCr($elwrite)); + Settings::setDefaultRtl(null); + } +} diff --git a/tests/PhpWordTests/Writer/RTF/ElementTest.php b/tests/PhpWordTests/Writer/RTF/ElementTest.php index 7c1549371d..fd0842cd6c 100644 --- a/tests/PhpWordTests/Writer/RTF/ElementTest.php +++ b/tests/PhpWordTests/Writer/RTF/ElementTest.php @@ -45,6 +45,24 @@ public function testUnmatchedElements(): void } } + public function testFilenameField(): void + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Field('FILENAME'); + $field = new \PhpOffice\PhpWord\Writer\RTF\Element\Field($parentWriter, $element); + + self::assertEquals("{\\field{\\*\\fldinst FILENAME}{\\fldrslt}}\\par\n", $this->removeCr($field)); + } + + public function testFilenameFieldOptionsPath(): void + { + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Field('FILENAME', [], ['Path']); + $field = new \PhpOffice\PhpWord\Writer\RTF\Element\Field($parentWriter, $element); + + self::assertEquals("{\\field{\\*\\fldinst FILENAME \\\\p}{\\fldrslt}}\\par\n", $this->removeCr($field)); + } + public function testPageField(): void { $parentWriter = new RTF(); diff --git a/tests/PhpWordTests/Writer/RTF/StyleTest.php b/tests/PhpWordTests/Writer/RTF/StyleTest.php index 0605e104fd..8f09fec40c 100644 --- a/tests/PhpWordTests/Writer/RTF/StyleTest.php +++ b/tests/PhpWordTests/Writer/RTF/StyleTest.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWordTests\Writer\RTF; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Writer\RTF; use PhpOffice\PhpWord\Writer\RTF\Style\Border; use PHPUnit\Framework\Assert; @@ -26,6 +27,11 @@ */ class StyleTest extends \PHPUnit\Framework\TestCase { + protected function tearDown(): void + { + Settings::setDefaultRtl(null); + } + public function removeCr($field) { return str_replace("\r\n", "\n", $field->write()); @@ -123,6 +129,16 @@ public function testRTL(): void self::assertEquals($expect, $this->removeCr($text)); } + public function testRTL2(): void + { + Settings::setDefaultRtl(true); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('אב גד'); + $text = new \PhpOffice\PhpWord\Writer\RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\qr{\\rtlch\\cf0\\f0 \\uc0{\\u1488}\\uc0{\\u1489} \\uc0{\\u1490}\\uc0{\\u1491}}\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } + public function testPageBreakLineHeight(): void { $parentWriter = new RTF(); @@ -132,6 +148,16 @@ public function testPageBreakLineHeight(): void self::assertEquals($expect, $this->removeCr($text)); } + public function testPageBreakLineHeight2(): void + { + Settings::setDefaultRtl(false); + $parentWriter = new RTF(); + $element = new \PhpOffice\PhpWord\Element\Text('New page', null, ['lineHeight' => 1.08, 'pageBreakBefore' => true]); + $text = new \PhpOffice\PhpWord\Writer\RTF\Element\Text($parentWriter, $element); + $expect = "\\pard\\nowidctlpar \\ql\\sl259\\slmult1\\page{\\cf0\\f0 New page}\\par\n"; + self::assertEquals($expect, $this->removeCr($text)); + } + public function testPageNumberRestart(): void { //$parentWriter = new RTF(); diff --git a/tests/PhpWordTests/Writer/RTFTest.php b/tests/PhpWordTests/Writer/RTFTest.php index 9b49eaf758..c56bc9f3dc 100644 --- a/tests/PhpWordTests/Writer/RTFTest.php +++ b/tests/PhpWordTests/Writer/RTFTest.php @@ -104,13 +104,14 @@ public function testSave(): void */ public function testSavePhpOutput(): void { - $this->setOutputCallback(function (): void { - }); $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText(htmlspecialchars('Test', ENT_COMPAT, 'UTF-8')); $writer = new RTF($phpWord); + ob_start(); $writer->save('php://output'); - self::assertNotNull($this->getActualOutput()); + $contents = ob_get_contents(); + self::assertTrue(ob_end_clean()); + self::assertNotEmpty($contents); } } diff --git a/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php b/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php new file mode 100644 index 0000000000..30f875c08c --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/FieldTest.php @@ -0,0 +1,73 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\Word2007\Element; + +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWordTests\TestHelperDOCX; +use PHPUnit\Framework\TestCase; + +/** + * Test class for PhpOffice\PhpWord\Writer\Word2007\Field. + */ +class FieldTest extends TestCase +{ + /** + * Executed before each method of the class. + */ + protected function tearDown(): void + { + TestHelperDOCX::clear(); + } + + /** + * Test Field write. + */ + public function testWriteWithRefType(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addField( + 'REF', + [ + 'name' => 'my-bookmark', + ], + [ + 'InsertParagraphNumberRelativeContext', + 'CreateHyperLink', + ] + ); + + $section->addListItem('line one item'); + $section->addListItem('line two item'); + $section->addBookmark('my-bookmark'); + $section->addListItem('line three item'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $refFieldPath = '/w:document/w:body/w:p[1]/w:r[2]/w:instrText'; + self::assertTrue($doc->elementExists($refFieldPath)); + + $bookMarkElement = $doc->getElement($refFieldPath); + self::assertNotNull($bookMarkElement); + self::assertEquals(' REF my-bookmark \r \h ', $bookMarkElement->textContent); + + $bookmarkPath = '/w:document/w:body/w:bookmarkStart'; + self::assertTrue($doc->elementExists($bookmarkPath)); + self::assertEquals('my-bookmark', $doc->getElementAttribute("$bookmarkPath", 'w:name')); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Element/FormulaTest.php b/tests/PhpWordTests/Writer/Word2007/Element/FormulaTest.php new file mode 100644 index 0000000000..b9acb1c226 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/FormulaTest.php @@ -0,0 +1,72 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\Word2007\Element; + +use PhpOffice\Math\Element; +use PhpOffice\Math\Math; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWordTests\TestHelperDOCX; +use PHPUnit\Framework\TestCase; + +/** + * Test class for PhpOffice\PhpWord\Writer\Word2007\Element subnamespace. + */ +class FormulaTest extends TestCase +{ + /** + * Executed before each method of the class. + */ + protected function tearDown(): void + { + TestHelperDOCX::clear(); + } + + public function testBasicFormula(): void + { + $math = new Math(); + $math + ->add( + new Element\Fraction( + new Element\Numeric(2), + new Element\Identifier('π') + ) + ) + ->add( + new Element\Operator('+') + ) + ->add( + new Element\Identifier('a') + ) + ->add( + new Element\Operator('∗') + ) + ->add( + new Element\Numeric(2) + ); + + $phpWord = new PhpWord(); + + $section = $phpWord->addSection(); + $section->addFormula($math); + + $doc = TestHelperDOCX::getDocument($phpWord); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/m:oMathPara')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/m:oMathPara/m:oMath')); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Element/TOCTest.php b/tests/PhpWordTests/Writer/Word2007/Element/TOCTest.php new file mode 100644 index 0000000000..537fb93d1a --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/TOCTest.php @@ -0,0 +1,56 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ +declare(strict_types=1); + +namespace PhpOffice\PhpWordTests\Writer\Word2007\Element; + +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWordTests\TestHelperDOCX; + +/** + * Test class for PhpOffice\PhpWord\Writer\Word2007\Element subnamespace. + */ +class TOCTest extends \PHPUnit\Framework\TestCase +{ + /** + * Executed after each method of the class. + */ + protected function tearDown(): void + { + TestHelperDOCX::clear(); + } + + public function testWriteTitlePageNumber(): void + { + $expectedPageNum = mt_rand(1, 1000); + + $phpWord = new PhpWord(); + + $section = $phpWord->addSection(); + $section->addTOC(); + $section->addTitle('TestTitle 1', 1, $expectedPageNum); + + $doc = TestHelperDOCX::getDocument($phpWord); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:hyperlink/w:r[1]/w:t')); + self::assertEquals('TestTitle 1', $doc->getElement('/w:document/w:body/w:p[1]/w:hyperlink/w:r[1]/w:t')->textContent); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:hyperlink/w:r[5]/w:fldChar')); + self::assertEquals('separate', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:hyperlink/w:r[5]/w:fldChar', 'w:fldCharType')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:hyperlink/w:r[6]/w:t')); + self::assertEquals($expectedPageNum, $doc->getElement('/w:document/w:body/w:p[1]/w:hyperlink/w:r[6]/w:t')->textContent); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php b/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php new file mode 100644 index 0000000000..57010893ae --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php @@ -0,0 +1,147 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ +declare(strict_types=1); + +namespace PhpOffice\PhpWordTests\Writer\Word2007\Element; + +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\SimpleType\TblWidth; +use PhpOffice\PhpWordTests\TestHelperDOCX; + +/** + * Test class for PhpOffice\PhpWord\Writer\Word2007\Element subnamespace. + */ +class TableTest extends \PHPUnit\Framework\TestCase +{ + /** + * Executed after each method of the class. + */ + protected function tearDown(): void + { + TestHelperDOCX::clear(); + } + + public static function testTableNormal(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Before table (normal).'); + $table = $section->addTable(['width' => 5000, 'unit' => TblWidth::PERCENT]); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R1C1'); + $tc = $table->addCell(); + $tc->addText('R1C2'); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R2C1'); + $tc = $table->addCell(); + $tc->addText('R2C2'); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R3C1'); + $tc = $table->addCell(); + $tc->addText('R3C2'); + $section->addText('After table.'); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl[2]'), 'should be only 1 table'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[3]')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[3]')); + } + + public static function testSomeRowWithNoCells(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Before table (row 2 has no cells).'); + $table = $section->addTable(['width' => 5000, 'unit' => TblWidth::PERCENT]); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R1C1'); + $tc = $table->addCell(); + $tc->addText('R1C2'); + $row = $table->addRow(); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R3C1'); + $tc = $table->addCell(); + $tc->addText('R3C2'); + $section->addText('After table.'); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl[2]'), 'should be only 1 table'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[2]')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[3]')); + } + + public static function testOnly1RowWithNoCells(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Before table (only 1 row and it has no cells).'); + $table = $section->addTable(['width' => 5000, 'unit' => TblWidth::PERCENT]); + $row = $table->addRow(); + $section->addText('After table.'); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl[2]'), 'only 1 table should be written'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]')); + + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]')); + } + + public static function testNoRows(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Before table (no rows therefore omitted).'); + $table = $section->addTable(['width' => 5000, 'unit' => TblWidth::PERCENT]); + $section->addText('After table.'); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl[1]'), 'no table should be written'); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Element/TitleTest.php b/tests/PhpWordTests/Writer/Word2007/Element/TitleTest.php new file mode 100644 index 0000000000..180a319eb1 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/TitleTest.php @@ -0,0 +1,96 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ +declare(strict_types=1); + +namespace PhpOffice\PhpWordTests\Writer\Word2007\Element; + +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWordTests\TestHelperDOCX; + +/** + * Test class for PhpOffice\PhpWord\Writer\Word2007\Element subnamespace. + */ +class TitleTest extends \PHPUnit\Framework\TestCase +{ + /** + * Executed after each method of the class. + */ + protected function tearDown(): void + { + TestHelperDOCX::clear(); + } + + public function testWriteTitleWithStyle(): void + { + $phpWord = new PhpWord(); + $phpWord->addTitleStyle(0, ['size' => 14, 'italic' => true]); + + $section = $phpWord->addSection(); + $section->addTitle('Test Title0', 0); + + $doc = TestHelperDOCX::getDocument($phpWord); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:r/w:t')); + self::assertEquals('Test Title0', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->textContent); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:pPr/w:pStyle')); + self::assertEquals('Title', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:pStyle', 'w:val')); + } + + public function testWriteTitleWithoutStyle(): void + { + $phpWord = new PhpWord(); + + $section = $phpWord->addSection(); + $section->addTitle('Test Title0', 0); + + $doc = TestHelperDOCX::getDocument($phpWord); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:r/w:t')); + self::assertEquals('Test Title0', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->textContent); + self::assertFalse($doc->elementExists('/w:document/w:body/w:p[1]/w:pPr')); + } + + public function testWriteHeadingWithStyle(): void + { + $phpWord = new PhpWord(); + $phpWord->addTitleStyle(1, ['bold' => true], ['spaceAfter' => 240]); + + $section = $phpWord->addSection(); + $section->addTitle('TestHeading 1', 1); + + $doc = TestHelperDOCX::getDocument($phpWord); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:r/w:t')); + self::assertEquals('TestHeading 1', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->textContent); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:pPr/w:pStyle')); + self::assertEquals('Heading1', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:pStyle', 'w:val')); + } + + public function testWriteHeadingWithoutStyle(): void + { + $phpWord = new PhpWord(); + + $section = $phpWord->addSection(); + $section->addTitle('TestHeading 1', 1); + + $doc = TestHelperDOCX::getDocument($phpWord); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:r/w:t')); + self::assertEquals('TestHeading 1', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->textContent); + self::assertFalse($doc->elementExists('/w:document/w:body/w:p[1]/w:pPr')); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/ElementTest.php b/tests/PhpWordTests/Writer/Word2007/ElementTest.php index 41c86f6b84..3ed3e7a91b 100644 --- a/tests/PhpWordTests/Writer/Word2007/ElementTest.php +++ b/tests/PhpWordTests/Writer/Word2007/ElementTest.php @@ -306,6 +306,44 @@ public function testStyledFieldElement(): void self::assertEquals($stnam, $doc->getElementAttribute($sty . '/w:rStyle', 'w:val')); } + public function testFieldElementFilename(): void + { + $phpWord = new PhpWord(); + $stnam = 'h1'; + $phpWord->addFontStyle($stnam, ['name' => 'Courier New', 'size' => 8]); + $section = $phpWord->addSection(); + + $fld = $section->addField('FILENAME'); + $fld->setFontStyle($stnam); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = '/w:document/w:body/w:p/w:r[2]/w:instrText'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals(' FILENAME ', $doc->getElement($element)->textContent); + $sty = '/w:document/w:body/w:p/w:r[2]/w:rPr'; + self::assertTrue($doc->elementExists($sty)); + self::assertEquals($stnam, $doc->getElementAttribute($sty . '/w:rStyle', 'w:val')); + } + + public function testFieldElementFilenameOptionsPath(): void + { + $phpWord = new PhpWord(); + $stnam = 'h1'; + $phpWord->addFontStyle($stnam, ['name' => 'Courier New', 'size' => 8]); + $section = $phpWord->addSection(); + + $fld = $section->addField('FILENAME', [], ['Path']); + $fld->setFontStyle($stnam); + $doc = TestHelperDOCX::getDocument($phpWord); + + $element = '/w:document/w:body/w:p/w:r[2]/w:instrText'; + self::assertTrue($doc->elementExists($element)); + self::assertEquals(' FILENAME \p ', $doc->getElement($element)->textContent); + $sty = '/w:document/w:body/w:p/w:r[2]/w:rPr'; + self::assertTrue($doc->elementExists($sty)); + self::assertEquals($stnam, $doc->getElementAttribute($sty . '/w:rStyle', 'w:val')); + } + public function testFieldElementWithComplexText(): void { $phpWord = new PhpWord(); @@ -460,32 +498,6 @@ public function testTrackChange(): void self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:del/w:r/w:delText')); } - /** - * Test Title and Headings. - */ - public function testTitleAndHeading(): void - { - $phpWord = new PhpWord(); - $phpWord->addTitleStyle(0, ['size' => 14, 'italic' => true]); - $phpWord->addTitleStyle(1, ['size' => 20, 'color' => '333333', 'bold' => true]); - - $section = $phpWord->addSection(); - $section->addTitle('This is a title', 0); - $section->addTitle('Heading 1', 1); - - $doc = TestHelperDOCX::getDocument($phpWord); - - self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:r/w:t')); - self::assertEquals('This is a title', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->textContent); - self::assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:pPr/w:pStyle')); - self::assertEquals('Title', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:pStyle', 'w:val')); - - self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:t')); - self::assertEquals('Heading 1', $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:t')->textContent); - self::assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:pPr/w:pStyle')); - self::assertEquals('Heading1', $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:pPr/w:pStyle', 'w:val')); - } - /** * Test correct writing of text with ampersant in it. */ diff --git a/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php b/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php index 98449f3b6d..76c05786c0 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/DocumentTest.php @@ -2,10 +2,8 @@ /** * This file is part of PHPWord - A pure PHP library for reading and writing * word processing documents. - * * PHPWord is free software distributed under the terms of the GNU Lesser * General Public License version 3 as published by the Free Software Foundation. - * * For the full copyright and license information, please read the LICENSE * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. @@ -61,11 +59,11 @@ public function testWriteCustomProps(): void $doc = TestHelperDOCX::getDocument($phpWord); self::assertNotNull($doc); -// $this->assertTrue($doc->elementExists('/Properties/property[name="key1"]/vt:lpwstr')); -// $this->assertTrue($doc->elementExists('/Properties/property[name="key2"]/vt:bool')); -// $this->assertTrue($doc->elementExists('/Properties/property[name="key3"]/vt:i4')); -// $this->assertTrue($doc->elementExists('/Properties/property[name="key4"]/vt:r8')); -// $this->assertTrue($doc->elementExists('/Properties/property[name="key5"]/vt:lpwstr')); + // $this->assertTrue($doc->elementExists('/Properties/property[name="key1"]/vt:lpwstr')); + // $this->assertTrue($doc->elementExists('/Properties/property[name="key2"]/vt:bool')); + // $this->assertTrue($doc->elementExists('/Properties/property[name="key3"]/vt:i4')); + // $this->assertTrue($doc->elementExists('/Properties/property[name="key4"]/vt:r8')); + // $this->assertTrue($doc->elementExists('/Properties/property[name="key5"]/vt:lpwstr')); } /** @@ -408,7 +406,13 @@ public function testWriteImage(): void // behind $element = $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:pict/v:shape'); $style = $element->getAttribute('style'); - self::assertRegExp('/z\-index:\-[0-9]*/', $style); + if (method_exists(self::class, 'assertMatchesRegularExpression')) { + self::assertMatchesRegularExpression('/z\-index:\-[0-9]*/', $style); + } elseif (method_exists(self::class, 'assertRegExp')) { + self::assertRegExp('/z\-index:\-[0-9]*/', $style); + } else { + self::fail('Unsure how to test regexp'); + } // square $element = $doc->getElement('/w:document/w:body/w:p[4]/w:r/w:pict/v:shape/w10:wrap'); @@ -529,12 +533,47 @@ public function testWriteFontStyle(): void self::assertTrue($doc->elementExists("{$parent}/w:i")); self::assertEquals($styles['underline'], $doc->getElementAttribute("{$parent}/w:u", 'w:val')); self::assertTrue($doc->elementExists("{$parent}/w:strike")); + self::assertFalse($doc->elementExists("{$parent}/w:dstrike")); self::assertEquals('superscript', $doc->getElementAttribute("{$parent}/w:vertAlign", 'w:val')); self::assertEquals($styles['color'], $doc->getElementAttribute("{$parent}/w:color", 'w:val')); self::assertEquals($styles['fgColor'], $doc->getElementAttribute("{$parent}/w:highlight", 'w:val')); self::assertTrue($doc->elementExists("{$parent}/w:smallCaps")); } + /** + * covers ::_writeTextStyle. + * + * @dataProvider providerFontStyleStrikethrough + */ + public function testWriteFontStyleStrikethrough( + bool $isStrikethrough, + bool $isDoubleStrikethrough, + bool $expectedStrikethrough, + bool $expectedDoubleStrikethrough + ): void { + $phpWord = new PhpWord(); + $styles['strikethrough'] = $isStrikethrough; + $styles['doublestrikethrough'] = $isDoubleStrikethrough; + + $section = $phpWord->addSection(); + $section->addText('Test', $styles); + $doc = TestHelperDOCX::getDocument($phpWord); + + $parent = '/w:document/w:body/w:p/w:r/w:rPr'; + self::assertSame($expectedStrikethrough, $doc->elementExists("{$parent}/w:strike")); + self::assertSame($expectedDoubleStrikethrough, $doc->elementExists("{$parent}/w:dstrike")); + } + + public static function providerFontStyleStrikethrough(): iterable + { + return [ + [true, true, false, true], + [true, false, true, false], + [false, true, false, true], + [false, false, false, false], + ]; + } + /** * Tests that if no color is set on a cell a border gets writen with the default color. */ @@ -551,7 +590,13 @@ public function testWriteDefaultColor(): void $cell->addText('Test'); $doc = TestHelperDOCX::getDocument($phpWord); - self::assertEquals(Cell::DEFAULT_BORDER_COLOR, $doc->getElementAttribute('/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr/w:tcBorders/w:top', 'w:color')); + self::assertEquals( + Cell::DEFAULT_BORDER_COLOR, + $doc->getElementAttribute( + '/w:document/w:body/w:tbl/w:tr/w:tc/w:tcPr/w:tcBorders/w:top', + 'w:color' + ) + ); } /** diff --git a/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php b/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php index 97e8a646fc..0dc99e7e37 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/FooterTest.php @@ -17,8 +17,10 @@ namespace PhpOffice\PhpWordTests\Writer\Word2007\Part; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Writer\Word2007; use PhpOffice\PhpWord\Writer\Word2007\Part\Footer; +use PhpOffice\PhpWordTests\TestHelperDOCX; /** * Test class for PhpOffice\PhpWord\Writer\Word2007\Part\Footer. @@ -44,12 +46,17 @@ public function testWriteFooter(): void $container->addImage($imageSrc); $writer = new Word2007(); - $writer->setUseDiskCaching(true); + $dir = Settings::getTempDir() . DIRECTORY_SEPARATOR . 'phpwordcachefooter'; + if (!is_dir($dir) && !mkdir($dir)) { + self::fail('Unable to create temp directory'); + } + $writer->setUseDiskCaching(true, $dir); $object = new Footer(); $object->setParentWriter($writer); $object->setElement($container); $xml = simplexml_load_string($object->write()); self::assertInstanceOf('SimpleXMLElement', $xml); + TestHelperDOCX::deleteDir($dir); } } diff --git a/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php b/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php index 14872970a1..526d3591e8 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/FootnotesTest.php @@ -49,5 +49,8 @@ public function testWriteFootnotes(): void self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:footnoteReference')); self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:endnoteReference')); + + self::assertFalse($doc->elementExists('/w:document/w:body/w:p/w:r/w:footnoteReference[@w:id="0"]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:footnoteReference[@w:id="1"]')); } } diff --git a/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php b/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php index 6832643b5e..27e9bcb9ba 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/HeaderTest.php @@ -17,8 +17,10 @@ namespace PhpOffice\PhpWordTests\Writer\Word2007\Part; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Writer\Word2007; use PhpOffice\PhpWord\Writer\Word2007\Part\Header; +use PhpOffice\PhpWordTests\TestHelperDOCX; /** * Test class for PhpOffice\PhpWord\Writer\Word2007\Part\Header. @@ -44,12 +46,17 @@ public function testWriteHeader(): void $container->addWatermark($imageSrc); $writer = new Word2007(); - $writer->setUseDiskCaching(true); + $dir = Settings::getTempDir() . DIRECTORY_SEPARATOR . 'phpwordcachefooter'; + if (!is_dir($dir) && !mkdir($dir)) { + self::fail('Unable to create temp directory'); + } + $writer->setUseDiskCaching(true, $dir); $object = new Header(); $object->setParentWriter($writer); $object->setElement($container); $xml = simplexml_load_string($object->write()); self::assertInstanceOf('SimpleXMLElement', $xml); + TestHelperDOCX::deleteDir($dir); } } diff --git a/tests/PhpWordTests/Writer/Word2007/Part/SettingsTest.php b/tests/PhpWordTests/Writer/Word2007/Part/SettingsTest.php index e60e03f74f..db8f5198a4 100644 --- a/tests/PhpWordTests/Writer/Word2007/Part/SettingsTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Part/SettingsTest.php @@ -468,4 +468,20 @@ public function testDoNotHyphenateCaps(): void $element = $doc->getElement($path, $file); self::assertSame('true', $element->getAttribute('w:val')); } + + public function testBookFoldPrinting(): void + { + $phpWord = new PhpWord(); + $phpWord->getSettings()->setBookFoldPrinting(true); + + $doc = TestHelperDOCX::getDocument($phpWord); + + $file = 'word/settings.xml'; + + $path = '/w:settings/w:bookFoldPrinting'; + self::assertTrue($doc->elementExists($path, $file)); + + $element = $doc->getElement($path, $file); + self::assertSame('true', $element->getAttribute('w:val')); + } } diff --git a/tests/PhpWordTests/Writer/Word2007/Style/DirectionTest.php b/tests/PhpWordTests/Writer/Word2007/Style/DirectionTest.php new file mode 100644 index 0000000000..b5e93d2374 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Style/DirectionTest.php @@ -0,0 +1,61 @@ +<?php +/** + * This file is part of PHPWord - A pure PHP library for reading and writing + * word processing documents. + * + * PHPWord is free software distributed under the terms of the GNU Lesser + * General Public License version 3 as published by the Free Software Foundation. + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. For the full list of + * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. + * + * @see https://github.com/PHPOffice/PHPWord + * + * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 + */ + +namespace PhpOffice\PhpWordTests\Writer\Word2007\Style; + +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; +use PhpOffice\PhpWord\Shared\Html as SharedHtml; +use PhpOffice\PhpWordTests\TestHelperDOCX; + +class DirectionTest extends \PHPUnit\Framework\TestCase +{ + /** + * Executed before each method of the class. + */ + protected function tearDown(): void + { + Settings::setDefaultRtl(null); + TestHelperDOCX::clear(); + } + + /** + * Test write styles. + */ + public function testDirection(): void + { + $word = new PhpWord(); + Settings::setDefaultRtl(true); + $section = $word->addSection(); + $html = '<p> الألم الذي ربما تنجم عنه بعض ا.</p>'; + SharedHtml::addHtml($section, $html, false, false); + $english = '<p style="text-align: left; direction: ltr;">LTR in RTL document.</p>'; + SharedHtml::addHtml($section, $english, false, false); + $doc = TestHelperDOCX::getDocument($word, 'Word2007'); + + $path = '/w:document/w:body/w:p[1]/w:pPr/w:bidi'; + self::assertTrue($doc->elementExists($path)); + $path = '/w:document/w:body/w:p[2]/w:pPr/w:bidi'; + self::assertFalse($doc->elementExists($path)); + + $path = '/w:document/w:body/w:p[1]/w:pPr/w:jc'; + self::assertFalse($doc->elementExists($path)); + $path = '/w:document/w:body/w:p[2]/w:pPr/w:jc'; + self::assertTrue($doc->elementExists($path)); + self::assertSame('start', $doc->getElementAttribute($path, 'w:val')); + } +} diff --git a/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php b/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php index 2c10b5ff2f..a8214ec35b 100644 --- a/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Style/FontTest.php @@ -17,6 +17,8 @@ namespace PhpOffice\PhpWordTests\Writer\Word2007\Style; +use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\Html; use PhpOffice\PhpWordTests\TestHelperDOCX; /** @@ -155,4 +157,44 @@ public function testPosition(): void self::assertTrue($doc->elementExists($path)); self::assertEquals(-20, $doc->getElementAttribute($path, 'w:val')); } + + public static function testRgb(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(['pageNumberingStart' => 1]); + $html = implode( + "\n", + [ + '<table>', + '<tbody>', + '<tr>', + '<td style="color: #A7D9C1;">This one is in color.</td>', + '<td style="color: rgb(167, 217, 193);">This one too.</td>', + '</tr>', + '</tbody>', + '</table>', + ] + ); + + Html::addHtml($section, $html, false, false); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + $element = '/w:document/w:body/w:tbl/w:tr/w:tc/w:p/w:r'; + $txtelem = $element . '/w:t'; + $styelem = $element . '/w:rPr'; + self::assertTrue($doc->elementExists($txtelem)); + self::assertSame('This one is in color.', $doc->getElement($txtelem)->textContent); + self::assertTrue($doc->elementExists($styelem)); + self::assertTrue($doc->elementExists($styelem . '/w:color')); + self::assertSame('A7D9C1', $doc->getElementAttribute($styelem . '/w:color', 'w:val')); + + $element = '/w:document/w:body/w:tbl/w:tr/w:tc[2]/w:p/w:r'; + $txtelem = $element . '/w:t'; + $styelem = $element . '/w:rPr'; + self::assertTrue($doc->elementExists($txtelem)); + self::assertSame('This one too.', $doc->getElement($txtelem)->textContent); + self::assertTrue($doc->elementExists($styelem)); + self::assertTrue($doc->elementExists($styelem . '/w:color')); + self::assertSame('A7D9C1', $doc->getElementAttribute($styelem . '/w:color', 'w:val')); + } } diff --git a/tests/PhpWordTests/Writer/Word2007Test.php b/tests/PhpWordTests/Writer/Word2007Test.php index 32b046f01d..0965222fb3 100644 --- a/tests/PhpWordTests/Writer/Word2007Test.php +++ b/tests/PhpWordTests/Writer/Word2007Test.php @@ -17,7 +17,9 @@ namespace PhpOffice\PhpWordTests\Writer; +use finfo; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\Writer\Word2007; use PhpOffice\PhpWordTests\AbstractWebServerEmbeddedTest; @@ -116,13 +118,18 @@ public function testSaveUseDiskCaching(): void $footnote->addText('Test'); $writer = new Word2007($phpWord); - $writer->setUseDiskCaching(true); + $dir = Settings::getTempDir() . DIRECTORY_SEPARATOR . 'phpwordcachefooter'; + if (!is_dir($dir) && !mkdir($dir)) { + self::fail('Unable to create temp directory'); + } + $writer->setUseDiskCaching(true, $dir); $file = __DIR__ . '/../_files/temp.docx'; $writer->save($file); self::assertFileExists($file); unlink($file); + TestHelperDOCX::deleteDir($dir); } /** @@ -169,16 +176,17 @@ public function testGetWriterPartNull(): void */ public function testSetGetUseDiskCaching(): void { - $this->setOutputCallback(function (): void { - }); $phpWord = new PhpWord(); $phpWord->addSection(); $object = new Word2007($phpWord); $object->setUseDiskCaching(true, PHPWORD_TESTS_BASE_DIR); $writer = new Word2007($phpWord); + ob_start(); $writer->save('php://output'); - + $contents = ob_get_contents(); + self::assertTrue(ob_end_clean()); self::assertTrue($object->isUseDiskCaching()); + self::assertNotEmpty($contents); } /** @@ -192,4 +200,25 @@ public function testSetUseDiskCachingException(): void $object = new Word2007(); $object->setUseDiskCaching(true, $dir); } + + /** + * File is detected as Word 2007. + */ + public function testMime(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Test 1'); + + $writer = new Word2007($phpWord); + $file = __DIR__ . '/../_files/temp.docx'; + $writer->save($file); + + $finfo = new finfo(FILEINFO_MIME_TYPE); + $mime = $finfo->file($file); + + self::assertEquals('application/vnd.openxmlformats-officedocument.wordprocessingml.document', $mime); + + unlink($file); + } } diff --git a/tests/PhpWordTests/XmlDocument.php b/tests/PhpWordTests/XmlDocument.php index 6604eccfa0..1b309d0c71 100644 --- a/tests/PhpWordTests/XmlDocument.php +++ b/tests/PhpWordTests/XmlDocument.php @@ -19,6 +19,7 @@ use DOMDocument; use DOMElement; +use DOMNode; use DOMNodeList; use DOMXPath; @@ -63,48 +64,55 @@ class XmlDocument private $defaultFile = 'word/document.xml'; /** - * Get default file. + * Create new instance. * - * @return string + * @param string $path + */ + public function __construct($path) + { + $this->path = realpath($path); + } + + /** + * Get default file. */ - public function getDefaultFile() + public function getDefaultFile(): string { return $this->defaultFile; } /** * Set default file. - * - * @param string $file - * - * @return string */ - public function setDefaultFile($file) + public function setDefaultFile(string $file): string { $temp = $this->defaultFile; + $this->defaultFile = $file; return $temp; } /** - * Create new instance. - * - * @param string $path + * Get file name. */ - public function __construct($path) + public function getFile(): string { - $this->path = realpath($path); + return $this->file; + } + + /** + * Get path. + */ + public function getPath(): string + { + return $this->path; } /** * Get DOM from file. - * - * @param string $file - * - * @return DOMDocument */ - public function getFileDom($file = '') + public function getFileDom(string $file = ''): DOMDocument { if (!$file) { $file = $this->defaultFile; @@ -132,12 +140,9 @@ public function getFileDom($file = '') /** * Get node list. * - * @param string $path - * @param string $file - * - * @return DOMNodeList + * @return DOMNodeList<DOMNode> */ - public function getNodeList($path, $file = '') + public function getNodeList(string $path, string $file = ''): DOMNodeList { if (!$file) { $file = $this->defaultFile; @@ -156,73 +161,33 @@ public function getNodeList($path, $file = '') /** * Get element. - * - * @param string $path - * @param string $file - * - * @return DOMElement - */ - public function getElement($path, $file = '') - { - if (!$file) { - $file = $this->defaultFile; - } - $elements = $this->getNodeList($path, $file); - - return $elements->item(0); - } - - /** - * Get file name. - * - * @return string */ - public function getFile() + public function getElement(string $path, string $file = ''): ?DOMElement { - return $this->file; + return $this->getNodeList($path, $file)->item(0); } /** - * Get path. - * - * @return string + * Get element attribute. */ - public function getPath() + public function getElementAttribute(string $path, string $attribute, string $file = ''): string { - return $this->path; + return $this->getElement($path, $file)->getAttribute($attribute); } /** - * Get element attribute. - * - * @param string $path - * @param string $attribute - * @param string $file - * - * @return string + * Return if element attribute exists. */ - public function getElementAttribute($path, $attribute, $file = '') + public function hasElementAttribute(string $path, string $attribute, string $file = ''): bool { - if (!$file) { - $file = $this->defaultFile; - } - - return $this->getElement($path, $file)->getAttribute($attribute); + return $this->getElement($path, $file)->hasAttribute($attribute); } /** * Check if element exists. - * - * @param string $path - * @param string $file - * - * @return string */ - public function elementExists($path, $file = '') + public function elementExists(string $path, string $file = ''): bool { - if (!$file) { - $file = $this->defaultFile; - } $nodeList = $this->getNodeList($path, $file); return $nodeList->length != 0; @@ -231,23 +196,11 @@ public function elementExists($path, $file = '') /** * Returns the xml, or part of it as a formatted string. * - * @param string $path - * @param string $file - * - * @return string + * @return false|string */ - public function printXml($path = '/', $file = '') + public function printXml(string $path = '/', string $file = '') { - if (!$file) { - $file = $this->defaultFile; - } $element = $this->getElement($path, $file); - if ($element instanceof DOMDocument) { - $element->formatOutput = true; - $element->preserveWhiteSpace = false; - - return $element->saveXML(); - } $newdoc = new DOMDocument(); $newdoc->formatOutput = true; diff --git a/tests/PhpWordTests/_files/documents/docChinese.doc b/tests/PhpWordTests/_files/documents/docChinese.doc new file mode 100644 index 0000000000..db245953e5 Binary files /dev/null and b/tests/PhpWordTests/_files/documents/docChinese.doc differ diff --git a/tests/PhpWordTests/_files/documents/docCzech.doc b/tests/PhpWordTests/_files/documents/docCzech.doc new file mode 100644 index 0000000000..8def81b13e Binary files /dev/null and b/tests/PhpWordTests/_files/documents/docCzech.doc differ diff --git a/tests/PhpWordTests/_files/documents/docSlovak.doc b/tests/PhpWordTests/_files/documents/docSlovak.doc new file mode 100644 index 0000000000..dede581e55 Binary files /dev/null and b/tests/PhpWordTests/_files/documents/docSlovak.doc differ diff --git a/tests/PhpWordTests/_files/documents/reader-comments.docx b/tests/PhpWordTests/_files/documents/reader-comments.docx new file mode 100644 index 0000000000..4748da6838 Binary files /dev/null and b/tests/PhpWordTests/_files/documents/reader-comments.docx differ diff --git a/tests/PhpWordTests/_files/documents/reader-formula.docx b/tests/PhpWordTests/_files/documents/reader-formula.docx new file mode 100644 index 0000000000..0e40f6672c Binary files /dev/null and b/tests/PhpWordTests/_files/documents/reader-formula.docx differ diff --git a/tests/PhpWordTests/_files/documents/reader-formula.odt b/tests/PhpWordTests/_files/documents/reader-formula.odt new file mode 100644 index 0000000000..f94c44afbb Binary files /dev/null and b/tests/PhpWordTests/_files/documents/reader-formula.odt differ diff --git a/tests/PhpWordTests/_files/documents/reader-styles.docx b/tests/PhpWordTests/_files/documents/reader-styles.docx new file mode 100644 index 0000000000..b02cf73c5f Binary files /dev/null and b/tests/PhpWordTests/_files/documents/reader-styles.docx differ diff --git a/tests/PhpWordTests/_files/documents/reader.font-halfpoint.doc b/tests/PhpWordTests/_files/documents/reader.font-halfpoint.doc new file mode 100644 index 0000000000..94fc5ed8fd Binary files /dev/null and b/tests/PhpWordTests/_files/documents/reader.font-halfpoint.doc differ diff --git a/tests/PhpWordTests/_files/documents/word.2493.nosection.odt b/tests/PhpWordTests/_files/documents/word.2493.nosection.odt new file mode 100644 index 0000000000..eb0fa20764 Binary files /dev/null and b/tests/PhpWordTests/_files/documents/word.2493.nosection.odt differ diff --git a/tests/PhpWordTests/_files/images/new-php-logo b/tests/PhpWordTests/_files/images/new-php-logo new file mode 100644 index 0000000000..6649079930 Binary files /dev/null and b/tests/PhpWordTests/_files/images/new-php-logo differ diff --git a/tests/PhpWordTests/_files/templates/clone-merge-with-custom-macro.docx b/tests/PhpWordTests/_files/templates/clone-merge-with-custom-macro.docx new file mode 100644 index 0000000000..e023ed0765 Binary files /dev/null and b/tests/PhpWordTests/_files/templates/clone-merge-with-custom-macro.docx differ diff --git a/tests/PhpWordTests/_files/templates/delete-row.docx b/tests/PhpWordTests/_files/templates/delete-row.docx new file mode 100644 index 0000000000..dd8d8a3188 Binary files /dev/null and b/tests/PhpWordTests/_files/templates/delete-row.docx differ diff --git a/tests/PhpWordTests/_files/templates/document22-with-custom-macro-xml.docx b/tests/PhpWordTests/_files/templates/document22-with-custom-macro-xml.docx new file mode 100644 index 0000000000..5aba782b65 Binary files /dev/null and b/tests/PhpWordTests/_files/templates/document22-with-custom-macro-xml.docx differ diff --git a/tests/PhpWordTests/_files/templates/extract-variable.docx b/tests/PhpWordTests/_files/templates/extract-variable.docx new file mode 100644 index 0000000000..f95ec61862 Binary files /dev/null and b/tests/PhpWordTests/_files/templates/extract-variable.docx differ diff --git a/tests/PhpWordTests/_files/templates/header-footer-with-custom-macro.docx b/tests/PhpWordTests/_files/templates/header-footer-with-custom-macro.docx new file mode 100644 index 0000000000..19d5669335 Binary files /dev/null and b/tests/PhpWordTests/_files/templates/header-footer-with-custom-macro.docx differ diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 330abe133c..845e59db70 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -22,3 +22,40 @@ if (!defined('PHPWORD_TESTS_BASE_DIR')) { define('PHPWORD_TESTS_BASE_DIR', realpath(__DIR__)); } + +function phpunit10ErrorHandler(int $errno, string $errstr, string $filename, int $lineno): bool +{ + $x = error_reporting() & $errno; + if ( + in_array( + $errno, + [ + E_DEPRECATED, + E_WARNING, + E_NOTICE, + E_USER_DEPRECATED, + E_USER_NOTICE, + E_USER_WARNING, + ], + true + ) + ) { + if (0 === $x) { + return true; // message suppressed - stop error handling + } + + throw new \Exception("$errstr $filename $lineno"); + } + + return false; // continue error handling +} + +function utf8decode(string $value, string $toEncoding = 'ISO-8859-1'): string +{ + return function_exists('mb_convert_encoding') ? mb_convert_encoding($value, $toEncoding, 'UTF-8') : utf8_decode($value); +} + +if (!method_exists(\PHPUnit\Framework\TestCase::class, 'setOutputCallback')) { + ini_set('error_reporting', (string) E_ALL); + set_error_handler('phpunit10ErrorHandler'); +}